import { PeopleSearch, User } from "@/components/Attendees/PeopleSearch"; import { searchUsers } from "@/features/User/userAPI"; import { act, fireEvent, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { renderWithProviders } from "../utils/Renderwithproviders"; jest.mock("@/features/User/userAPI"); const mockedSearchUsers = searchUsers as jest.MockedFunction< typeof searchUsers >; describe("PeopleSearch", () => { const baseUser: User = { email: "test@example.com", displayName: "Test User", avatarUrl: "https://example.com/avatar.png", openpaasId: "1234567890", }; function setup( selectedUsers: User[] = [], props?: Partial> ) { const onChange = jest.fn(); renderWithProviders( ); return { onChange }; } beforeEach(() => { jest.useFakeTimers(); mockedSearchUsers.mockReset(); }); afterEach(() => { jest.useRealTimers(); }); it("calls searchUsers after debounce when typing", async () => { mockedSearchUsers.mockResolvedValueOnce([baseUser]); setup(); const input = screen.getByRole("combobox"); await userEvent.type(input, "Test"); await act(async () => { jest.advanceTimersByTime(300); }); await waitFor(() => { expect(mockedSearchUsers).toHaveBeenCalledWith("Test", ["user"]); }); }); it("renders search results and allows selection", async () => { mockedSearchUsers.mockResolvedValueOnce([baseUser]); const { onChange } = setup(); const input = screen.getByRole("combobox"); await userEvent.type(input, "Test"); await act(async () => { jest.advanceTimersByTime(300); }); const option = await screen.findByText("Test User"); await userEvent.click(option); await waitFor(() => { expect(onChange).toHaveBeenCalled(); }); }); it("does not show already selected users in options", async () => { mockedSearchUsers.mockResolvedValueOnce([baseUser]); setup([baseUser]); const input = screen.getByRole("combobox"); await userEvent.type(input, "Test"); await act(async () => { jest.advanceTimersByTime(300); }); await waitFor(() => { expect(screen.queryByText("test@example.com")).not.toBeInTheDocument(); }); }); it("triggers onToggleEventPreview on Enter key press", () => { const onToggleEventPreview = jest.fn(); setup([], { onToggleEventPreview }); const input = screen.getByRole("combobox"); fireEvent.keyDown(input, { key: "Enter" }); expect(onToggleEventPreview).toHaveBeenCalled(); }); it("respects disabled state", () => { setup([], { disabled: true }); expect(screen.getByRole("combobox")).toBeDisabled(); }); it("no options doesn't show dropdown when input is empty", async () => { mockedSearchUsers.mockResolvedValueOnce([baseUser]); setup(); const input = screen.getByRole("combobox"); userEvent.type(input, "Test"); await act(async () => { jest.advanceTimersByTime(300); }); await waitFor(() => { expect(screen.getByRole("listbox")).toBeInTheDocument(); }); userEvent.clear(input); await waitFor(() => { expect(screen.queryByRole("listbox")).not.toBeInTheDocument(); }); }); it("shows 'No results' when search succeeds but returns empty array", async () => { mockedSearchUsers.mockResolvedValueOnce([]); setup(); const input = screen.getByRole("combobox"); await userEvent.type(input, "Test"); await act(async () => { jest.advanceTimersByTime(300); await Promise.resolve(); await Promise.resolve(); }); const noResults = await screen.findByText( "peopleSearch.noResults", {}, { timeout: 5000 } ); expect(noResults).toBeInTheDocument(); }); it("does not clear options when search fails and shows error snackbar", async () => { mockedSearchUsers.mockResolvedValueOnce([baseUser]); setup(); const input = screen.getByRole("combobox"); await userEvent.type(input, "Test"); await act(async () => { jest.advanceTimersByTime(300); await Promise.resolve(); }); await waitFor(() => { expect(screen.getByText("Test User")).toBeInTheDocument(); }); mockedSearchUsers.mockRejectedValueOnce(new Error("Network error")); await userEvent.clear(input); await userEvent.type(input, "Error"); await act(async () => { jest.advanceTimersByTime(300); await Promise.resolve(); }); const errorMessage = await screen.findByText("peopleSearch.searchError"); expect(errorMessage).toBeInTheDocument(); expect(screen.queryByText("Test User")).not.toBeInTheDocument(); mockedSearchUsers.mockResolvedValueOnce([baseUser]); await userEvent.clear(input); await userEvent.type(input, "Test"); await act(async () => { jest.advanceTimersByTime(300); await Promise.resolve(); }); await waitFor(() => { expect(screen.getByText("Test User")).toBeInTheDocument(); }); }); it("shows loading text when searching", async () => { let resolveSearch: (value: User[]) => void; const searchPromise = new Promise((resolve) => { resolveSearch = resolve; }); mockedSearchUsers.mockReturnValueOnce(searchPromise); setup(); const input = screen.getByRole("combobox"); await userEvent.type(input, "Test"); await act(async () => { jest.advanceTimersByTime(300); }); const loadingText = await screen.findByText( "peopleSearch.loading", {}, { timeout: 5000 } ); expect(loadingText).toBeInTheDocument(); await act(async () => { resolveSearch!([baseUser]); await searchPromise; }); }); it("retains input value when field loses focus (blur)", async () => { let resolveSearch: (value: User[]) => void; const searchPromise = new Promise((resolve) => { resolveSearch = resolve; }); mockedSearchUsers.mockReturnValueOnce(searchPromise); await act(async () => { setup(); }); const input = screen.getByRole("combobox"); await act(async () => { userEvent.type(input, "Test"); }); await waitFor(() => { expect(mockedSearchUsers).toHaveBeenCalledWith("Test", ["user"]); }); expect(input).toHaveValue("Test"); await act(async () => { input.blur(); }); await waitFor(() => { expect(input).toHaveValue("Test"); }); }); });