Repository: Sage/carbon (ts)

Issue:
FilterableSelect calls onOpen without user interaction
### Current behaviour

Reproduced on codesandbox.

`FilterableSelect` does not have the same behaviour as `Select` when providing the exact same props.

`onOpen` can be repeatedly called without any user input if some props are modified.

### Expected behaviour

`onOpen` is only called when the user clicks on a `FilterableSelect` component.

### CodeSandbox or Storybook URL

https://codesandbox.io/s/lucid-waterfall-8thv8k?file=/src/LoadingSelect.tsx:897-909

### JIRA Ticket (Sage Only)

SBS-47539

### Suggested Solution

quote from @robinzigmond :

"
On initial investigation the cause is this useEffect in FilterableSelect:

```
useEffect(() => {
  if (!isOpen) {
    setFilterText("");
  } else if (onOpen) {
    onOpen();
  }
}, [isOpen, onOpen]);
```
    
In your sandbox example, this actually fires on every render because onOpen (your loadItems) is not memoised in any way. 

I've been attempting to fix this with useCallback but I don't think it's entirely solvable within your example, because the callback in your case updates remainingFailedRequests which is impossible to get rid of as a dependency of getItems and therefore of loadItems.

As you observe, simple Select does not have this problem (because it has no similar useEffect) so we should investigate how we might be able to achieve the desired effect in FilterableSelect without running onOpen every time its dependencies change. 
"

### Carbon Version

111.9.1

### Design Tokens Version

_No response_

### What browsers are you seeing the problem on?

Firefox

### What Operating System are you seeing the problem on?

MacOS

### Anything else we should know?

111.3.0 on codesandbox but happened originally with 111.9.1

### Confidentiality

- [X] I confirm there is no confidential or commercially sensitive information included.

Pull request description:
fix: resolve issue with multiple execution of onOpen and onFilterChange callbacks - FE-5612
### Proposed behaviour

Fixes #5719

This PR also contains a fix for a similar issue happening with the `onFilterChange` callback in both `FilterableSelect` and `MultiSelect`

### Current behaviour

MultiSelect, FilterableSelect: 
The onFilterChange callback function is intended to be triggered only when the filter values are changed by the user. However, onFilterChange callback is being called on the first render of the component and then on each rerender. 

FilterableSelect:
The onOpen callback function is intended to be triggered only when the select list is being opened. Currently though onOpen callback is being called on each rerender of the component.

<!--
A clear and concise description of the behaviour before this change.

If applicable, add screenshots. You can paste these directly into GitHub.
-->

### Checklist

<!-- Each PR should include the following -->

- [x] Commits follow our style guide
- [x] Related issues linked in commit messages if required
- [x] Screenshots are included in the PR if useful
- [x] All themes are supported if required
- [x] Unit tests added or updated if required
- [x] Cypress automation tests added or updated if required
- [x] Storybook added or updated if required
- [x] Translations added or updated (including creating or amending translation keys table in storybook) if required
- [x] Typescript `d.ts` file added or updated if required
- [x] Related docs have been updated if required

#### QA

- [x] Tested in CodeSandbox/storybook
- [x] Add new Cypress test coverage if required
- [x] Carbon implementation matches Design System/designs
- [x] UI Tests GitHub check reviewed if required

### Additional context

<!-- Add any other context or links about the pull request here. -->

### Testing instructions

Issue codesandbox:
https://codesandbox.io/s/elegant-dream-frnvwm

Codesandbox to test that neither `onChange` nor `onOpen` are triggered on first render or on any subsequential rerenders
https://codesandbox.io/s/carbon-quickstart-typescript-forked-bi1hks


Patch:
diff --git a/package-lock.json b/package-lock.json
index 1d4832c63..55330fd39 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -67,6 +67,7 @@
         "@storybook/theming": "^6.4.18",
         "@testing-library/jest-dom": "^5.16.2",
         "@testing-library/react": "^12.1.3",
+        "@testing-library/react-hooks": "^8.0.1",
         "@testing-library/user-event": "^14.4.3",
         "@types/crypto-js": "^4.1.1",
         "@types/draft-js": "^0.11.7",
@@ -9278,6 +9279,36 @@
         "react-dom": "*"
       }
     },
+    "node_modules/@testing-library/react-hooks": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz",
+      "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==",
+      "dev": true,
+      "dependencies": {
+        "@babel/runtime": "^7.12.5",
+        "react-error-boundary": "^3.1.0"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "peerDependencies": {
+        "@types/react": "^16.9.0 || ^17.0.0",
+        "react": "^16.9.0 || ^17.0.0",
+        "react-dom": "^16.9.0 || ^17.0.0",
+        "react-test-renderer": "^16.9.0 || ^17.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "react-dom": {
+          "optional": true
+        },
+        "react-test-renderer": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@testing-library/react/node_modules/@testing-library/dom": {
       "version": "8.11.3",
       "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.3.tgz",
@@ -28380,6 +28411,8 @@
     },
     "node_modules/npm/node_modules/node-gyp/node_modules/minimatch": {
       "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
       "dev": true,
       "inBundle": true,
       "license": "ISC",
@@ -28877,6 +28910,8 @@
     },
     "node_modules/npm/node_modules/rimraf/node_modules/minimatch": {
       "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
       "dev": true,
       "inBundle": true,
       "license": "ISC",
@@ -31433,6 +31468,22 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/react-error-boundary": {
+      "version": "3.1.4",
+      "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz",
+      "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/runtime": "^7.12.5"
+      },
+      "engines": {
+        "node": ">=10",
+        "npm": ">=6"
+      },
+      "peerDependencies": {
+        "react": ">=16.13.1"
+      }
+    },
     "node_modules/react-fast-compare": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
@@ -44102,6 +44153,16 @@
         }
       }
     },
+    "@testing-library/react-hooks": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz",
+      "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==",
+      "dev": true,
+      "requires": {
+        "@babel/runtime": "^7.12.5",
+        "react-error-boundary": "^3.1.0"
+      }
+    },
     "@testing-library/user-event": {
       "version": "14.4.3",
       "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.4.3.tgz",
@@ -58831,6 +58892,8 @@
             },
             "minimatch": {
               "version": "3.1.2",
+              "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+              "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
               "bundled": true,
               "dev": true,
               "requires": {
@@ -59189,6 +59252,8 @@
             },
             "minimatch": {
               "version": "3.1.2",
+              "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+              "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
               "bundled": true,
               "dev": true,
               "requires": {
@@ -61137,6 +61202,15 @@
         }
       }
     },
+    "react-error-boundary": {
+      "version": "3.1.4",
+      "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz",
+      "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==",
+      "dev": true,
+      "requires": {
+        "@babel/runtime": "^7.12.5"
+      }
+    },
     "react-fast-compare": {
       "version": "3.2.0",
       "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
diff --git a/package.json b/package.json
index 1640be0af..c0fd0d66e 100644
--- a/package.json
+++ b/package.json
@@ -80,6 +80,7 @@
     "@storybook/theming": "^6.4.18",
     "@testing-library/jest-dom": "^5.16.2",
     "@testing-library/react": "^12.1.3",
+    "@testing-library/react-hooks": "^8.0.1",
     "@testing-library/user-event": "^14.4.3",
     "@types/crypto-js": "^4.1.1",
     "@types/draft-js": "^0.11.7",
diff --git a/src/components/select/filterable-select/filterable-select.component.js b/src/components/select/filterable-select/filterable-select.component.js
index d122f451e..8c3a5fe7a 100644
--- a/src/components/select/filterable-select/filterable-select.component.js
+++ b/src/components/select/filterable-select/filterable-select.component.js
@@ -16,6 +16,7 @@ import SelectList from "../select-list/select-list.component";
 import isExpectedOption from "../utils/is-expected-option";
 import isNavigationKey from "../utils/is-navigation-key";
 import Logger from "../../../__internal__/utils/logger";
+import useStableCallback from "../../../hooks/__internal__/useStableCallback";
 
 let deprecateInputRefWarnTriggered = false;
 
@@ -30,9 +31,9 @@ const FilterableSelect = React.forwardRef(
       name,
       label,
       children,
-      onOpen,
+      onOpen: onOpenProp,
       onChange,
-      onFilterChange,
+      onFilterChange: onFilterChangeProp,
       onClick,
       onKeyDown,
       onFocus,
@@ -283,13 +284,14 @@ const FilterableSelect = React.forwardRef(
       // eslint-disable-next-line react-hooks/exhaustive-deps
     }, [value, onChange, children]);
 
+    const onOpen = useStableCallback(onOpenProp);
     useEffect(() => {
       if (!isOpen) {
         setFilterText("");
       } else if (onOpen) {
         onOpen();
       }
-    }, [isOpen, onOpen]);
+    }, [onOpen, isOpen]);
 
     useEffect(() => {
       const hasListActionButton = listActionButton !== undefined;
@@ -310,11 +312,17 @@ const FilterableSelect = React.forwardRef(
       // eslint-disable-next-line react-hooks/exhaustive-deps
     }, [value, children]);
 
+    const onFilterChange = useStableCallback(onFilterChangeProp);
+    const isFirstRender = useRef(true);
     useEffect(() => {
-      if (onFilterChange) {
+      if (onFilterChange && !isFirstRender.current) {
         onFilterChange(filterText);
       }
-    }, [filterText, onFilterChange]);
+    }, [onFilterChange, filterText]);
+
+    useEffect(() => {
+      isFirstRender.current = false;
+    }, []);
 
     useEffect(() => {
       const clickEvent = "click";
@@ -411,9 +419,6 @@ const FilterableSelect = React.forwardRef(
             return true;
           }
 
-          if (onOpen) {
-            onOpen();
-          }
           if (onFocus && !isInputFocused.current) {
             triggerFocus();
             isInputFocused.current = true;
diff --git a/src/components/select/filterable-select/filterable-select.spec.js b/src/components/select/filterable-select/filterable-select.spec.js
index 572dc2ba9..e4d509805 100644
--- a/src/components/select/filterable-select/filterable-select.spec.js
+++ b/src/components/select/filterable-select/filterable-select.spec.js
@@ -1,4 +1,4 @@
-import React, { useRef } from "react";
+import React, { useRef, useState } from "react";
 import { act } from "react-dom/test-utils";
 import { mount } from "enzyme";
 
@@ -939,8 +939,8 @@ describe("FilterableSelect", () => {
     });
   });
 
-  describe('when the "onFilter" prop has been passed and the input text changed', () => {
-    it("then that prop should be called", () => {
+  describe('when the "onFilterChange" prop has been passed', () => {
+    it("then that prop should be invoked when the input text has changed", () => {
       const filterText = "foo";
       const onFilterChangeFn = jest.fn();
       const wrapper = renderSelect({ onFilterChange: onFilterChangeFn });
@@ -950,6 +950,35 @@ describe("FilterableSelect", () => {
         .simulate("change", { target: { value: filterText } });
       expect(onFilterChangeFn).toHaveBeenCalledWith(filterText);
     });
+
+    it("then it should not be invoked when the component rerenders", () => {
+      const Component = () => {
+        const [onFilterChangeCalled, setOnFilterChangeCalled] = useState(false);
+
+        return (
+          <>
+            <div id="on-filter-called">
+              {onFilterChangeCalled ? "true" : "false"}
+            </div>
+            <FilterableSelect
+              name="testSelect"
+              id="testSelect"
+              onFilterChange={() => setOnFilterChangeCalled(true)}
+            >
+              <Option value="opt1" text="red" />
+              <Option value="opt2" text="green" />
+              <Option value="opt3" text="blue" />
+              <Option value="opt4" text="black" />
+            </FilterableSelect>
+          </>
+        );
+      };
+
+      const wrapper = mount(<Component />);
+      expect(wrapper.find("#on-filter-called").text()).toBe("false");
+      wrapper.setProps({});
+      expect(wrapper.find("#on-filter-called").text()).toBe("false");
+    });
   });
 
   describe('when the "openOnFocus" prop is set', () => {
@@ -1007,6 +1036,40 @@ describe("FilterableSelect", () => {
           expect(onOpenFn).toHaveBeenCalled();
         });
 
+        it("then it should not be invoked when the component rerenders", () => {
+          const Component = () => {
+            const [callCounter, setCallCounter] = useState(0);
+
+            return (
+              <>
+                <div id="call-counter">{callCounter}</div>
+                <FilterableSelect
+                  name="testSelect"
+                  id="testSelect"
+                  openOnFocus
+                  onOpen={() => {
+                    if (callCounter < 2) {
+                      setCallCounter(callCounter + 1);
+                    }
+                  }}
+                >
+                  <Option value="opt1" text="red" />
+                  <Option value="opt2" text="green" />
+                  <Option value="opt3" text="blue" />
+                  <Option value="opt4" text="black" />
+                </FilterableSelect>
+              </>
+            );
+          };
+
+          wrapper = mount(<Component />);
+          expect(wrapper.find("#call-counter").text()).toBe("0");
+          wrapper.find("input").simulate("focus");
+          expect(wrapper.find("#call-counter").text()).toBe("1");
+          wrapper.setProps({});
+          expect(wrapper.find("#call-counter").text()).toBe("1");
+        });
+
         describe("and with the SelectList already open", () => {
           it("then that prop should not be called", () => {
             wrapper.find("input").simulate("focus");
@@ -1021,7 +1084,7 @@ describe("FilterableSelect", () => {
 
         describe("and the focus triggered by mouseDown on the input", () => {
           it("then that prop should have been called", () => {
-            wrapper.find("input").simulate("mouseDown");
+            wrapper.find("input").simulate("mousedown");
             wrapper.find("input").simulate("focus");
             expect(onOpenFn).toHaveBeenCalled();
           });
@@ -1033,15 +1096,14 @@ describe("FilterableSelect", () => {
       describe('with the "onOpen" prop passed', () => {
         const onOpenFn = jest.fn();
         const wrapper = renderSelect({ onOpen: onOpenFn, openOnFocus: true });
-
-        it("then that prop should have been called", () => {
+        it("then that prop should not have been called", () => {
           wrapper
             .find(Textbox)
             .find('[type="dropdown"]')
             .first()
-            .simulate("mouseDown");
+            .simulate("mousedown");
           wrapper.find("input").simulate("focus");
-          expect(onOpenFn).toHaveBeenCalled();
+          expect(onOpenFn).not.toHaveBeenCalled();
         });
       });
     });
diff --git a/src/components/select/multi-select/multi-select.component.js b/src/components/select/multi-select/multi-select.component.js
index 1f585585e..1b8d12bca 100644
--- a/src/components/select/multi-select/multi-select.component.js
+++ b/src/components/select/multi-select/multi-select.component.js
@@ -28,6 +28,7 @@ import isExpectedOption from "../utils/is-expected-option";
 import isExpectedValue from "../utils/is-expected-value";
 import isNavigationKey from "../utils/is-navigation-key";
 import Logger from "../../../__internal__/utils/logger";
+import useStableCallback from "../../../hooks/__internal__/useStableCallback";
 
 let deprecateInputRefWarnTriggered = false;
 
@@ -44,7 +45,7 @@ const MultiSelect = React.forwardRef(
       readOnly,
       children,
       onOpen,
-      onFilterChange,
+      onFilterChange: onFilterChangeProp,
       onChange,
       onClick,
       onFocus,
@@ -328,11 +329,17 @@ const MultiSelect = React.forwardRef(
       };
     }, [handleGlobalClick]);
 
+    const onFilterChange = useStableCallback(onFilterChangeProp);
+    const isFirstRender = useRef(true);
     useEffect(() => {
-      if (onFilterChange) {
+      if (onFilterChange && !isFirstRender.current) {
         onFilterChange(filterText);
       }
-    }, [filterText, onFilterChange]);
+    }, [onFilterChange, filterText]);
+
+    useEffect(() => {
+      isFirstRender.current = false;
+    }, []);
 
     function handleTextboxClick(event) {
       isMouseDownReported.current = false;
diff --git a/src/components/select/multi-select/multi-select.spec.js b/src/components/select/multi-select/multi-select.spec.js
index 2ef2f3e7f..dc6976e48 100644
--- a/src/components/select/multi-select/multi-select.spec.js
+++ b/src/components/select/multi-select/multi-select.spec.js
@@ -1,4 +1,4 @@
-import React, { useRef } from "react";
+import React, { useRef, useState } from "react";
 import { act } from "react-dom/test-utils";
 import { mount } from "enzyme";
 
@@ -809,8 +809,8 @@ describe("MultiSelect", () => {
     });
   });
 
-  describe('when the "onFilter" prop has been passed and the input text changed', () => {
-    it("then that prop should be called", () => {
+  describe('when the "onFilter" prop has been passed', () => {
+    it("then that prop should be invoked when the input text has changed", () => {
       const filterText = "foo";
       const onFilterChangeFn = jest.fn();
       const wrapper = renderSelect({ onFilterChange: onFilterChangeFn });
@@ -820,6 +820,35 @@ describe("MultiSelect", () => {
         .simulate("change", { target: { value: filterText } });
       expect(onFilterChangeFn).toHaveBeenCalledWith(filterText);
     });
+
+    it("then it should not be invoked when the component rerenders", () => {
+      const Component = () => {
+        const [onFilterChangeCalled, setOnFilterChangeCalled] = useState(false);
+
+        return (
+          <>
+            <div id="on-filter-called">
+              {onFilterChangeCalled ? "true" : "false"}
+            </div>
+            <MultiSelect
+              name="testSelect"
+              id="testSelect"
+              onFilterChange={() => setOnFilterChangeCalled(true)}
+            >
+              <Option value="opt1" text="red" />
+              <Option value="opt2" text="green" />
+              <Option value="opt3" text="blue" />
+              <Option value="opt4" text="black" />
+            </MultiSelect>
+          </>
+        );
+      };
+
+      const wrapper = mount(<Component />);
+      expect(wrapper.find("#on-filter-called").text()).toBe("false");
+      wrapper.setProps({});
+      expect(wrapper.find("#on-filter-called").text()).toBe("false");
+    });
   });
 
   describe('when the "onSelectListClose" is called in the SelectList', () => {
diff --git a/src/hooks/__internal__/useStableCallback/index.ts b/src/hooks/__internal__/useStableCallback/index.ts
new file mode 100644
index 000000000..e8b11354b
--- /dev/null
+++ b/src/hooks/__internal__/useStableCallback/index.ts
@@ -0,0 +1,1 @@
+export { default } from "./useStableCallback";
diff --git a/src/hooks/__internal__/useStableCallback/useStableCallback.spec.ts b/src/hooks/__internal__/useStableCallback/useStableCallback.spec.ts
new file mode 100644
index 000000000..7fb41ded2
--- /dev/null
+++ b/src/hooks/__internal__/useStableCallback/useStableCallback.spec.ts
@@ -0,0 +1,21 @@
+import { renderHook } from "@testing-library/react-hooks";
+import useStableCallback from "./useStableCallback";
+
+describe("useStableCallback", () => {
+  it("should return a stable callback", () => {
+    const { result, rerender } = renderHook(() => useStableCallback(() => {}));
+    const { current: callback1 } = result;
+
+    rerender();
+    const { current: callback2 } = result;
+
+    expect(callback1).toBe(callback2);
+  });
+
+  it("should return undefined if no callback is provided", () => {
+    const { result } = renderHook(() => useStableCallback());
+    const { current } = result;
+
+    expect(current).toBeUndefined();
+  });
+});
diff --git a/src/hooks/__internal__/useStableCallback/useStableCallback.ts b/src/hooks/__internal__/useStableCallback/useStableCallback.ts
new file mode 100644
index 000000000..defa9ba1c
--- /dev/null
+++ b/src/hooks/__internal__/useStableCallback/useStableCallback.ts
@@ -0,0 +1,15 @@
+import { useRef, useCallback } from "react";
+
+// This is a hook that returns a memoized callback that is guaranteed to be the same reference between renders.
+// This is useful when a stable reference is required to prevent unnecessary re-renders.
+
+export function useStableCallback(callback?: (...args: unknown[]) => unknown) {
+  const ref = useRef(callback);
+  ref.current = callback;
+
+  const stableCallback = useCallback((...args) => ref.current?.(...args), []);
+
+  return callback ? stableCallback : undefined;
+}
+
+export default useStableCallback;


Test patch:
diff --git a/src/components/select/filterable-select/filterable-select.test.js b/src/components/select/filterable-select/filterable-select.test.js
index 6c3e65e19..26b5ec2df 100644
--- a/src/components/select/filterable-select/filterable-select.test.js
+++ b/src/components/select/filterable-select/filterable-select.test.js
@@ -1375,7 +1375,20 @@ context("Tests for Filterable Select component", () => {
         });
     });
 
-    it("should call onOpen when Filterable Select is opened", () => {
+    it("should call onOpen when Filterable Select is opened by focusing the input", () => {
+      CypressMountWithProviders(
+        <FilterableSelectComponent openOnFocus onOpen={callback} />
+      );
+
+      commonDataElementInputPreview()
+        .focus()
+        .then(() => {
+          // eslint-disable-next-line no-unused-expressions
+          cy.wrap(callback).should("have.been.calledOnce");
+        });
+    });
+
+    it("should call onOpen when Filterable Select is opened by clicking on Icon", () => {
       CypressMountWithProviders(
         <FilterableSelectComponent onOpen={callback} />
       );
@@ -1450,8 +1463,8 @@ context("Tests for Filterable Select component", () => {
         .type(text)
         .then(() => {
           // eslint-disable-next-line no-unused-expressions
-          expect(callback).to.have.been.calledTwice;
-          expect(callback.getCalls()[1].args[0]).to.equals(text);
+          expect(callback).to.have.been.calledOnce;
+          expect(callback).to.have.been.calledWith(text);
         });
     });
 
diff --git a/src/components/select/multi-select/multi-select.test.js b/src/components/select/multi-select/multi-select.test.js
index 4046ae6ae..c636c2715 100644
--- a/src/components/select/multi-select/multi-select.test.js
+++ b/src/components/select/multi-select/multi-select.test.js
@@ -1327,8 +1327,8 @@ context("Tests for Multi Select component", () => {
         .type(text)
         .then(() => {
           // eslint-disable-next-line no-unused-expressions
-          expect(callback).to.have.been.calledTwice;
-          expect(callback.getCalls()[1].args[0]).to.equals(text);
+          expect(callback).to.have.been.calledOnce;
+          expect(callback).to.have.been.calledWith(text);
         });
     });
   });