From dfc0b678de1fd337e6cef4ddfb4b39eabac07042 Mon Sep 17 00:00:00 2001 From: Lee Calcote Date: Wed, 3 Jun 2026 17:38:42 +0000 Subject: [PATCH 1/2] fix(UserSearchField): restore type-ahead by preserving Autocomplete htmlInput slot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MUI v9 slotProps migration handed each picker's renderInput TextField an explicit `slotProps` object that only set the `input` (and `inputLabel`) slot. An explicit `slotProps` prop replaces the one spread from `{...params}`, so this dropped `params.slotProps.htmlInput` — the native wiring (value, onChange, keydown/focus handlers and the anchor ref from getInputProps). The input was silently disconnected from the Autocomplete: keystrokes never reached onInputChange, the search never fired and the suggestion list never opened. Spread `...params.slotProps` into the explicit object so htmlInput survives, and merge (instead of replace) the input/inputLabel slots. Restores the type-ahead across UserSearchField (Add User / Add Team Member), UserShareSearch (share modal picker), TeamSearchField and InputSearchField. Add a regression test that types into the picker and asserts the search callback fires and the matching option renders. Signed-off-by: Lee Calcote --- .../GettingStartedWidget/TeamSearchField.tsx | 1 + .../InputSearchField/InputSearchField.tsx | 2 + .../UserSearchField/UserSearchField.tsx | 5 +- .../UserSearchFieldInput.test.tsx | 69 +++++++++++++++++++ .../UserSearchField/UserSearchFieldInput.tsx | 2 + 5 files changed, 76 insertions(+), 3 deletions(-) create mode 100644 src/custom/UserSearchField/UserSearchFieldInput.test.tsx diff --git a/src/custom/DashboardWidgets/GettingStartedWidget/TeamSearchField.tsx b/src/custom/DashboardWidgets/GettingStartedWidget/TeamSearchField.tsx index 7a1b8d1fd..5517a4b74 100644 --- a/src/custom/DashboardWidgets/GettingStartedWidget/TeamSearchField.tsx +++ b/src/custom/DashboardWidgets/GettingStartedWidget/TeamSearchField.tsx @@ -147,6 +147,7 @@ const TeamSearchField: React.FC = ({ helperText={error ? 'Team Already Selected' : ''} fullWidth slotProps={{ + ...params.slotProps, input: { ...params.slotProps?.input, endAdornment: isLoading ? : null diff --git a/src/custom/InputSearchField/InputSearchField.tsx b/src/custom/InputSearchField/InputSearchField.tsx index 0ab226111..7fa941688 100644 --- a/src/custom/InputSearchField/InputSearchField.tsx +++ b/src/custom/InputSearchField/InputSearchField.tsx @@ -128,7 +128,9 @@ const InputSearchField: React.FC = ({ helperText={error} fullWidth slotProps={{ + ...params.slotProps, inputLabel: { + ...params.slotProps?.inputLabel, style: { fontFamily: 'inherit' } diff --git a/src/custom/UserSearchField/UserSearchField.tsx b/src/custom/UserSearchField/UserSearchField.tsx index d74f50144..c9215b369 100644 --- a/src/custom/UserSearchField/UserSearchField.tsx +++ b/src/custom/UserSearchField/UserSearchField.tsx @@ -156,12 +156,11 @@ const UserShareSearch: React.FC = ({ } }} slotProps={{ + ...params.slotProps, input: { ...params.slotProps?.input, endAdornment: ( - <> - {searchUserLoading ? : null} - + <>{searchUserLoading ? : null} ) } }} diff --git a/src/custom/UserSearchField/UserSearchFieldInput.test.tsx b/src/custom/UserSearchField/UserSearchFieldInput.test.tsx new file mode 100644 index 000000000..5e25f66f6 --- /dev/null +++ b/src/custom/UserSearchField/UserSearchFieldInput.test.tsx @@ -0,0 +1,69 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import React from 'react'; +import { SistentThemeProvider } from '../../theme'; +import UserSearchField from './UserSearchFieldInput'; + +// Regression guard for the user-picker type-ahead. +// +// The picker is a controlled MUI Autocomplete. In MUI v9 the +// `renderInput` params expose the native wiring (value, onChange, +// onKeyDown, focus handlers and the anchor ref from `getInputProps`) under +// `params.slotProps.htmlInput`. If the TextField is handed an explicit +// `slotProps` object that omits `htmlInput`, that object REPLACES the one +// spread from `{...params}` and the input is silently disconnected from the +// Autocomplete: keystrokes no longer reach `onInputChange`, the search never +// fires and the suggestion list never opens. These tests assert the wiring +// survives so the type-ahead keeps working. + +const renderWithTheme = (ui: React.ReactElement) => + render({ui}); + +const jane = { + userId: 'u-jane', + firstName: 'Jane', + lastName: 'Doe', + email: 'jane@example.com' +}; + +const renderField = (overrides: Record = {}) => { + const props = { + usersData: [], + setUsersData: jest.fn(), + currentUserData: null, + searchedUsers: [jane], + isUserSearchLoading: false, + fetchSearchedUsers: jest.fn(), + usersSearch: '', + setUsersSearch: jest.fn(), + ...overrides + }; + const utils = renderWithTheme( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + + ); + const input = utils.container.querySelector('input') as HTMLInputElement; + return { props, input, ...utils }; +}; + +describe('UserSearchField type-ahead wiring', () => { + it('forwards keystrokes to the search callback', () => { + const { props, input } = renderField(); + + fireEvent.change(input, { target: { value: 'jane' } }); + + // Only reachable when the Autocomplete's htmlInput slot is forwarded to + // the TextField; a dropped slot leaves the input inert and this stays 0. + expect(props.fetchSearchedUsers).toHaveBeenCalledWith('jane'); + }); + + it('opens the suggestion list with matching users as the query is typed', async () => { + const { input } = renderField({ usersSearch: 'jane' }); + + fireEvent.change(input, { target: { value: 'jane' } }); + + await waitFor(() => { + expect(screen.queryByText('jane@example.com')).not.toBeNull(); + }); + expect(screen.queryByText('Jane Doe')).not.toBeNull(); + }); +}); diff --git a/src/custom/UserSearchField/UserSearchFieldInput.tsx b/src/custom/UserSearchField/UserSearchFieldInput.tsx index f229345e7..c0858afbb 100644 --- a/src/custom/UserSearchField/UserSearchFieldInput.tsx +++ b/src/custom/UserSearchField/UserSearchFieldInput.tsx @@ -183,7 +183,9 @@ const UserSearchField: React.FC = ({ error={!!error} helperText={error} slotProps={{ + ...params.slotProps, inputLabel: { + ...params.slotProps?.inputLabel, style: { fontFamily: 'inherit' } From e1a4df5acb5243cd2e20a7b79f68756926de1a9c Mon Sep 17 00:00:00 2001 From: Lee Calcote Date: Wed, 3 Jun 2026 19:17:54 +0000 Subject: [PATCH 2/2] test(UserSearchField): centralize regression test and drop any-casts Move the picker type-ahead regression test to src/__testing__ to match the repository's established test layout, and type its props via React.ComponentProps instead of an `any` cast, selecting the input through Testing Library's combobox role. Signed-off-by: Lee Calcote --- .../UserSearchFieldInput.test.tsx | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) rename src/{custom/UserSearchField => __testing__}/UserSearchFieldInput.test.tsx (75%) diff --git a/src/custom/UserSearchField/UserSearchFieldInput.test.tsx b/src/__testing__/UserSearchFieldInput.test.tsx similarity index 75% rename from src/custom/UserSearchField/UserSearchFieldInput.test.tsx rename to src/__testing__/UserSearchFieldInput.test.tsx index 5e25f66f6..f6e421caa 100644 --- a/src/custom/UserSearchField/UserSearchFieldInput.test.tsx +++ b/src/__testing__/UserSearchFieldInput.test.tsx @@ -1,13 +1,13 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import React from 'react'; -import { SistentThemeProvider } from '../../theme'; -import UserSearchField from './UserSearchFieldInput'; +import UserSearchField from '../custom/UserSearchField/UserSearchFieldInput'; +import { SistentThemeProvider } from '../theme'; // Regression guard for the user-picker type-ahead. // -// The picker is a controlled MUI Autocomplete. In MUI v9 the -// `renderInput` params expose the native wiring (value, onChange, -// onKeyDown, focus handlers and the anchor ref from `getInputProps`) under +// The picker is a controlled MUI Autocomplete. In MUI v9 the `renderInput` +// params expose the native wiring (value, onChange, onKeyDown, focus +// handlers and the anchor ref from `getInputProps`) under // `params.slotProps.htmlInput`. If the TextField is handed an explicit // `slotProps` object that omits `htmlInput`, that object REPLACES the one // spread from `{...params}` and the input is silently disconnected from the @@ -15,6 +15,8 @@ import UserSearchField from './UserSearchFieldInput'; // fires and the suggestion list never opens. These tests assert the wiring // survives so the type-ahead keeps working. +type FieldProps = React.ComponentProps; + const renderWithTheme = (ui: React.ReactElement) => render({ui}); @@ -25,8 +27,8 @@ const jane = { email: 'jane@example.com' }; -const renderField = (overrides: Record = {}) => { - const props = { +const renderField = (overrides: Partial = {}) => { + const props: FieldProps = { usersData: [], setUsersData: jest.fn(), currentUserData: null, @@ -37,11 +39,8 @@ const renderField = (overrides: Record = {}) => { setUsersSearch: jest.fn(), ...overrides }; - const utils = renderWithTheme( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - - ); - const input = utils.container.querySelector('input') as HTMLInputElement; + const utils = renderWithTheme(); + const input = screen.getByRole('combobox') as HTMLInputElement; return { props, input, ...utils }; };