Repository: arco-design/arco-design (TypeScript)

Issue:
【React】TreeSelect单选模式下，选中某一项下拉面板收起后没有触发onVisibleChange
- [ ] I'm sure this does not appear in [the issue list of the repository](https://github.com/arco-design/arco-design/issues)

## 基本信息 
- **依赖包名 及 版本:** @arco-design/web-react@2.44.1
- **浏览器版本:** chrome109.0.0.0
- **复现地址:** https://codesandbox.io/s/jovial-tesla-yfz3pk

## 预期结果
选中下拉面板中的某一项后触发onVisibleChange的回调，参数为false

## 复现步骤
点开TreeSelect下拉面板，选中某一项，查看console控制台内容

<!--- 声明: 如果提交带有攻击性的 issue，提交者将会被禁止在 arco-design 组织下发言。 -->
<!-- generated by arco-issue. DO NOT REMOVE -->


Discussion:
<del>
可能就是这么设计的😂 
选中某一项触发 `onChange`，不选的情况下直接展开收起触发 `onVisibleChange`
</del>

gg，原来真的是bug



Patch:
diff --git a/components/DatePicker/interface.tsx b/components/DatePicker/interface.tsx
index 016c8c2fa6..92c8c50f92 100644
--- a/components/DatePicker/interface.tsx
+++ b/components/DatePicker/interface.tsx
@@ -252,7 +252,7 @@ export interface BaseDatePickerProps {
   /**
    * @zh 展示日期的格式，参考[dayjs](https://github.com/iamkun/dayjs)。使用 `string` 时，可以手动键入和编辑日期。使用 `(value: Dayjs) => string` 时，只能在 Picker 中选取日期。
    * @en Date format, refer to [dayjs](https://github.com/iamkun/dayjs). When using a `string`, manual editing is allowed. When using `(value: Dayjs) => string`, value must be picked from Picker.
-   * @defaultValue YYYY-MM-DD 
+   * @defaultValue YYYY-MM-DD
    */
   format?: string | ((value: Dayjs) => string);
   /**
diff --git a/components/TreeSelect/__demo__/basic.md b/components/TreeSelect/__demo__/basic.md
index bcb23eb3d4..9054405342 100644
--- a/components/TreeSelect/__demo__/basic.md
+++ b/components/TreeSelect/__demo__/basic.md
@@ -19,7 +19,9 @@ const TreeNode = TreeSelect.Node;
 
 const App = () => {
   return (
-    <TreeSelect defaultValue="node1" style={{ width: 300 }} allowClear>
+    <TreeSelect defaultValue="node1" style={{ width: 300 }} allowClear onVisibleChange={() => {
+      console.log('a')
+    }}>
       <TreeNode key="node1" title="Trunk">
         <TreeNode key="node2" title="Leaf" />
       </TreeNode>
diff --git a/components/TreeSelect/tree-select.tsx b/components/TreeSelect/tree-select.tsx
index e49e6889a8..cc27ad8a24 100644
--- a/components/TreeSelect/tree-select.tsx
+++ b/components/TreeSelect/tree-select.tsx
@@ -94,6 +94,13 @@ const TreeSelect: ForwardRefRenderFunction<
   // Unique ID of this select instance
   const instancePopupID = useId(`${prefixCls}-popup-`);
 
+  const tryUpdatePopupVisible = (visible) => {
+    if (visible !== popupVisible) {
+      setPopupVisible(visible);
+      props.onVisibleChange?.(visible);
+    }
+  };
+
   // 尝试更新 inputValue，并触发 onInputValueChange
   const tryUpdateInputValue = (value: string, reason: InputValueChangeReason) => {
     if (
@@ -162,10 +169,10 @@ const TreeSelect: ForwardRefRenderFunction<
       setValue(newValue, extra);
       resetInputValue();
       if (!multiple) {
-        setPopupVisible(false);
+        tryUpdatePopupVisible(false);
       }
     },
-    [setValue, resetInputValue]
+    [setValue, resetInputValue, popupVisible]
   );
 
   const handleRemoveCheckedItem = (item, index, e) => {
@@ -309,8 +316,8 @@ const TreeSelect: ForwardRefRenderFunction<
         }}
         disabled={props.disabled}
         onVisibleChange={(visible: boolean) => {
-          setPopupVisible(visible);
-          props.onVisibleChange && props.onVisibleChange(visible);
+          tryUpdatePopupVisible(visible);
+          // props.onVisibleChange && props.onVisibleChange(visible);
         }}
         popupVisible={popupVisible}
       >


Test patch:
diff --git a/components/TreeSelect/__test__/index.test.tsx b/components/TreeSelect/__test__/index.test.tsx
index d9279f4159..40c75c369e 100644
--- a/components/TreeSelect/__test__/index.test.tsx
+++ b/components/TreeSelect/__test__/index.test.tsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import TreeSelect from '..';
 import { normalizeValueToArray } from '../utils';
-import { cleanup, fireEvent, render } from '../../../tests/util';
+import { $, cleanup, fireEvent, render } from '../../../tests/util';
 
 const treeData = [
   {
@@ -97,7 +97,22 @@ describe('TreeSelect', () => {
     expect(mockVisibleChange).toHaveBeenCalledTimes(2);
     expect(mockVisibleChange.mock.calls[1]).toEqual([false]);
     // todo
-    // expect(wrapper.find('Tree')).toHaveLength(0);
+    expect(wrapper.find('Tree')).toHaveLength(0);
+    clickInput();
+
+    jest.runAllTimers();
+    expect(mockVisibleChange).toHaveBeenCalledTimes(3);
+    expect(mockVisibleChange.mock.calls[2]).toEqual([true]);
+
+    const text = $('.arco-tree-node-title-text').item(0).innerHTML;
+    fireEvent.click($('.arco-tree-node-title').item(0) as HTMLDivElement);
+
+    jest.runAllTimers();
+    expect(wrapper.find('.arco-tree')).toHaveLength(0);
+    expect($('.arco-tree-select-view-value').item(0).innerHTML).toBe(text);
+
+    expect(mockVisibleChange).toHaveBeenCalledTimes(4);
+    expect(mockVisibleChange.mock.calls[3]).toEqual([false]);
   });
 
   it('select correctly', () => {
@@ -683,8 +698,8 @@ describe('TreeSelect', () => {
 
   it('onKeyDown is called', () => {
     const onKeyDown = jest.fn();
-    const wrapper = render(<TreeSelect onKeyDown={onKeyDown} />);
-    fireEvent.keyDown(wrapper.querySelector('input'));
+    render(<TreeSelect onKeyDown={onKeyDown} />);
+    fireEvent.keyDown($('input').item(0));
     expect(onKeyDown).toHaveBeenCalled();
   });
 });