UNPKG

@octopusdeploy/design-system-components

Version:
753 lines (752 loc) 38.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const jsx_runtime_1 = require("react/jsx-runtime"); const testing_library_1 = require("@octopusdeploy/testing-library"); const react_1 = require("@testing-library/react"); const react_2 = require("react"); const vitest_1 = require("vitest"); const Select_1 = require("../Select"); const getSharedSelectInteractions_1 = require("./getSharedSelectInteractions"); const option1 = { label: "Option 1", value: "1" }; const option2 = { label: "Option 2", value: "2" }; const option3 = { label: "Option 3", value: "3" }; const items = [option1, option2, option3]; (0, vitest_1.describe)("<Select />", () => { (0, vitest_1.test)("renders with correct label", () => { const select = renderSelect({ label: "Custom Label" }); select.shouldHaveLabel("Custom Label"); select.shouldBeInDocument(); }); (0, vitest_1.test)("renders with placeholder text", () => { const placeholder = "Select an option"; const select = renderSelect({ placeholder }); select.shouldHavePlaceholder(placeholder); }); (0, vitest_1.test)("renders with selected value", () => { const select = renderSelect({ items, value: option2.value }); select.shouldShowSelectedOption("Option 2"); }); (0, vitest_1.test)("renders with description", () => { const description = "This is a helpful description"; const select = renderSelect({ description }); select.shouldHaveDescription(description); }); (0, vitest_1.test)("renders with error state", () => { const validationMessage = "This field is required"; const select = renderSelect({ validationMessage }); select.shouldHaveError(validationMessage); select.shouldHaveAriaInvalid(true); }); (0, vitest_1.test)("has autofocus when autoFocus is true", () => { const select = renderSelect({ autoFocus: true }); select.shouldHaveFocus(); }); (0, vitest_1.test)("opens dropdown when clicked", async () => { const select = renderSelect(); await select.click(); select.shouldShowDropdown(); }); (0, vitest_1.test)("displays all options when dropdown is open", async () => { const select = renderSelect(); await select.click(); select.shouldShowAllOptions(items); }); (0, vitest_1.test)("calls onChange when option is selected", async () => { const select = renderSelect({ value: option1.value }); await select.click(); await select.selectOption("Option 2"); select.shouldHaveCalledOnChangeWith(option2.value); }); (0, vitest_1.test)("closes dropdown when option is selected", async () => { const select = renderSelect({ value: option1.value }); await select.click(); await select.selectOption("Option 2"); select.shouldNotShowDropdown(); }); (0, vitest_1.test)("shows no results message when there are no options", async () => { const select = renderSelect({ items: [] }); await select.click(); select.shouldShowNoResultsMessage(); }); (0, vitest_1.test)("does not sort the options by default", async () => { const unsortedOptions = [option3, option2, option1]; const select = renderSelect({ items: unsortedOptions }); await select.click(); select.shouldShowAllOptionsInOrder(unsortedOptions); }); (0, vitest_1.test)("sorting the options can be enabled", async () => { const unsortedOptions = [option3, option2, option1]; const sortedOptions = [option1, option2, option3]; const select = renderSelect({ items: unsortedOptions, sortItems: true }); await select.click(); select.shouldShowAllOptionsInOrder(sortedOptions); }); (0, vitest_1.test)("does not trigger form submission when clicked inside a form", async () => { const handleSubmit = vitest_1.vi.fn((e) => e.preventDefault()); const select = renderSelect({ withForm: true, onSubmit: handleSubmit, }); await select.click(); (0, vitest_1.expect)(handleSubmit).not.toHaveBeenCalled(); }); (0, vitest_1.describe)("keyboard navigation", () => { (0, vitest_1.test)("opens dropdown with keyboard (Enter key)", async () => { const select = renderSelect(); await select.tab(); await select.pressEnter(); select.shouldShowDropdown(); }); (0, vitest_1.test)("opens dropdown with keyboard (Space key)", async () => { const select = renderSelect(); await select.tab(); await select.pressSpace(); select.shouldShowDropdown(); }); (0, vitest_1.test)("opens dropdown with keyboard (ArrowDown key)", async () => { const select = renderSelect(); await select.tab(); await select.pressArrowDown(); select.shouldShowDropdown(); }); (0, vitest_1.describe)("within listbox", () => { (0, vitest_1.test)("navigates down with ArrowDown key", async () => { const select = renderSelect({ value: option1.value }); await select.click(); await select.pressArrowDown(); select.shouldHaveOptionFocused("Option 2"); }); (0, vitest_1.test)("navigates up with ArrowUp key", async () => { const select = renderSelect({ value: option2.value }); await select.click(); await select.pressArrowUp(); select.shouldHaveOptionFocused("Option 1"); }); (0, vitest_1.test)("jumps to first option with Home key", async () => { const select = renderSelect({ value: option3.value }); await select.click(); await select.pressHome(); select.shouldHaveOptionFocused("Option 1"); }); (0, vitest_1.test)("jumps to last option with End key", async () => { const select = renderSelect({ value: option1.value }); await select.click(); await select.pressEnd(); select.shouldHaveOptionFocused("Option 3"); }); (0, vitest_1.test)("selects focused option with Enter key", async () => { const select = renderSelect({ value: option1.value }); await select.click(); await select.pressArrowDown(); await select.pressEnter(); select.shouldHaveCalledOnChangeWith(option2.value); }); (0, vitest_1.test)("selects focused option with Space key", async () => { const select = renderSelect({ value: option1.value }); await select.click(); await select.pressArrowDown(); await select.pressSpace(); select.shouldHaveCalledOnChangeWith(option2.value); }); (0, vitest_1.test)("closes dropdown with Escape key", async () => { const select = renderSelect(); await select.click(); select.shouldShowDropdown(); await select.pressEscape(); select.shouldNotShowDropdown(); select.shouldHaveFocus(); }); (0, vitest_1.test)("does not call onChange when closing with Escape key", async () => { const select = renderSelect({ value: option1.value }); await select.click(); await select.pressArrowDown(); await select.pressEscape(); select.shouldNotHaveCalledOnChange(); }); }); }); (0, vitest_1.describe)("disabled and readonly properties", () => { (0, vitest_1.test)("renders as disabled when disabled prop is true", () => { const select = renderSelect({ disabled: true }); select.shouldBeDisabled(); }); (0, vitest_1.test)("disabled select cannot be opened", async () => { const select = renderSelect({ disabled: true }); await select.click(); select.shouldNotShowDropdown(); }); (0, vitest_1.test)("renders as readonly when readOnly prop is true", () => { const select = renderSelect({ readOnly: true }); select.shouldBeReadOnly(); }); (0, vitest_1.test)("readonly select cannot be opened", async () => { const select = renderSelect({ readOnly: true }); await select.click(); select.shouldNotShowDropdown(); }); }); (0, vitest_1.describe)("default, required and optional marker properties", () => { (0, vitest_1.test)("renders with required marker when hasRequiredMarker is true", () => { const select = renderSelect({ hasRequiredMarker: true }); select.shouldHaveRequiredMarker(); }); (0, vitest_1.test)("renders with optional marker when hasOptionalMarker is true", () => { const select = renderSelect({ hasOptionalMarker: true }); select.shouldHaveOptionalMarker(); }); (0, vitest_1.test)("renders with default marker when hasDefaultMarker is true", () => { const select = renderSelect({ hasDefaultMarker: true }); select.shouldHaveDefaultMarker(); }); }); (0, vitest_1.describe)("accessibility attributes", () => { (0, vitest_1.test)("has correct accessibility attributes when error is present", () => { const validationMessage = "Invalid selection"; const select = renderSelect({ validationMessage }); select.shouldHaveAriaInvalid(true); select.shouldHaveAriaDescribedByError(); }); (0, vitest_1.test)("has correct accessibility attributes when description is present", () => { const description = "Help text"; const select = renderSelect({ description }); select.shouldHaveAriaDescribedByDescription(); }); (0, vitest_1.test)("has correct accessibility attributes when both description and error are present", () => { const description = "Help text"; const validationMessage = "Error message"; const select = renderSelect({ description, validationMessage }); select.shouldHaveAriaDescribedByBoth(); select.shouldHaveAriaInvalid(true); }); (0, vitest_1.test)("has aria-required when hasRequiredMarker is true", () => { const select = renderSelect({ hasRequiredMarker: true }); select.shouldHaveAriaRequired(true); }); (0, vitest_1.test)("has aria-activedescendant pointing to focused option when navigating with keyboard", async () => { const select = renderSelect({ items, value: option1.value }); await select.click(); select.shouldHaveAriaActiveDescendantForOption("Option 1"); await select.pressArrowDown(); select.shouldHaveAriaActiveDescendantForOption("Option 2"); }); (0, vitest_1.test)("does not have aria-activedescendant when dropdown is closed", () => { const select = renderSelect({ items, value: option1.value }); select.shouldNotHaveAriaActiveDescendant(); }); }); (0, vitest_1.describe)("required validation", () => { (0, vitest_1.test)("does not show a required error before any change is made, even when empty", () => { const select = renderSelect({ required: true, value: undefined }); select.shouldNotHaveError("This field is required."); }); (0, vitest_1.test)("shows a required error once the selected value is cleared", async () => { const select = renderSelect({ required: true, value: option1.value, allowClear: true }); await select.clickClearButton(); select.shouldHaveError("This field is required."); }); (0, vitest_1.test)("does not show a required error when a different value is selected", async () => { const select = renderSelect({ required: true, value: option1.value }); await select.click(); await select.selectOption("Option 2"); select.shouldNotHaveError("This field is required."); }); }); (0, vitest_1.describe)("clear button", () => { (0, vitest_1.test)("does not render clear button by default", () => { const select = renderSelect({ value: option1.value }); select.shouldNotShowClearButton(); }); (0, vitest_1.test)("does not render clear button when no value is selected", () => { const select = renderSelect({ value: undefined, allowClear: true }); select.shouldNotShowClearButton(); }); (0, vitest_1.test)("renders clear button when value is selected", () => { const select = renderSelect({ value: option1.value, allowClear: true }); select.shouldShowClearButton(); }); (0, vitest_1.test)("does not render clear button when disabled with a value selected", () => { const select = renderSelect({ value: option1.value, disabled: true, allowClear: true }); select.shouldNotShowClearButton(); }); (0, vitest_1.test)("does not render clear button when readonly with a value selected", () => { const select = renderSelect({ value: option1.value, readOnly: true, allowClear: true }); select.shouldNotShowClearButton(); }); (0, vitest_1.test)("calls onChange with undefined when clear button is clicked", async () => { const select = renderSelect({ value: option2.value, allowClear: true }); await select.clickClearButton(); select.shouldHaveCalledOnChangeWith(undefined); }); }); (0, vitest_1.describe)("filtering", () => { (0, vitest_1.test)("shows search input when allowFilter is true", async () => { const select = renderSelect({ allowFilter: true }); await select.click(); select.shouldShowSearchInput(); }); (0, vitest_1.test)("filters options based on search text", async () => { const select = renderSelect({ allowFilter: true }); await select.click(); await select.typeInSearchInput("1"); select.shouldShowOnlyOption("Option 1"); }); (0, vitest_1.test)("filters options case-insensitively", async () => { const select = renderSelect({ allowFilter: true }); await select.click(); await select.typeInSearchInput("OPTION 2"); select.shouldShowOnlyOption("Option 2"); }); (0, vitest_1.test)("shows multiple matching options when filter matches", async () => { const select = renderSelect({ allowFilter: true }); await select.click(); await select.typeInSearchInput("Option"); select.shouldShowAllOptions(items); }); (0, vitest_1.test)("shows no results message when filter matches nothing", async () => { const select = renderSelect({ allowFilter: true }); await select.click(); await select.typeInSearchInput("xyz"); select.shouldShowNoResultsMessage(); }); (0, vitest_1.test)("clears filter when dropdown closes", async () => { const select = renderSelect({ allowFilter: true }); await select.click(); await select.typeInSearchInput("1"); select.shouldShowOnlyOption("Option 1"); await select.pressEscape(); await select.click(); select.shouldShowAllOptions(items); }); (0, vitest_1.test)("navigates filtered options with keyboard", async () => { const select = renderSelect({ allowFilter: true }); await select.click(); await select.pressArrowDown(); select.shouldHaveOptionActive("Option 2"); }); (0, vitest_1.test)("selects filtered option with Enter key from search input", async () => { const select = renderSelect({ allowFilter: true }); await select.click(); await select.typeInSearchInput("Option 2"); await select.pressEnter(); select.shouldHaveCalledOnChangeWith(option2.value); }); (0, vitest_1.test)("jumps to first option with Home key in search input", async () => { const select = renderSelect({ allowFilter: true, value: option3.value }); await select.click(); await select.pressHome(); select.shouldHaveOptionActive("Option 1"); }); (0, vitest_1.test)("jumps to last option with End key in search input", async () => { const select = renderSelect({ allowFilter: true }); await select.click(); await select.pressEnd(); select.shouldHaveOptionActive("Option 3"); }); (0, vitest_1.test)("search input has correct aria attributes", async () => { const select = renderSelect({ label: "Test Select", allowFilter: true }); await select.click(); select.shouldHaveSearchInputWithAriaLabel("Filter Test Select"); }); }); (0, vitest_1.describe)("AsyncList items", () => { (0, vitest_1.test)("displays selected option from getItemById when not in loadedItems", () => { const asyncList = createMockAsyncList({ loadedItems: [], getItemById: (id) => (id === "1" ? option1 : undefined), }); const select = renderSelect({ items: asyncList, value: option1.value }); select.shouldShowSelectedOption("Option 1"); }); (0, vitest_1.test)("displays loaded items in dropdown", async () => { const asyncList = createMockAsyncList({ loadedItems: [option1, option2, option3], }); const select = renderSelect({ items: asyncList }); await select.click(); select.shouldShowAllOptions([option1, option2, option3]); }); (0, vitest_1.test)("shows a refresh button when the async list exposes refresh", () => { const asyncList = createMockAsyncList({ loadedItems: [option1], refresh: vitest_1.vi.fn() }); const select = renderSelect({ items: asyncList }); select.shouldShowRefreshButton(); }); (0, vitest_1.test)("does not show a refresh button when the async list has no refresh", () => { const asyncList = createMockAsyncList({ loadedItems: [option1] }); const select = renderSelect({ items: asyncList }); select.shouldNotShowRefreshButton(); }); (0, vitest_1.test)("calls refresh when the refresh button is clicked", async () => { const refresh = vitest_1.vi.fn(); const asyncList = createMockAsyncList({ loadedItems: [option1], refresh }); const select = renderSelect({ items: asyncList }); await select.clickRefreshButton(); (0, vitest_1.expect)(refresh).toHaveBeenCalledTimes(1); }); (0, vitest_1.test)("keeps the refresh button focusable while a refresh is in flight", async () => { const deferredRefresh = (0, testing_library_1.createControlledPromise)(); const asyncList = createMockAsyncList({ loadedItems: [option1], refresh: () => deferredRefresh.promise }); const select = renderSelect({ items: asyncList }); await select.clickRefreshButton(); // While the reload is in flight the trigger must stay focusable rather than being disabled, // so keyboard / screen-reader users don't lose their place mid-action. select.refreshButtonShouldBeEnabled(); select.refreshButtonShouldHaveFocus(); await deferredRefresh.resolve(); }); (0, vitest_1.test)("ignores repeat refresh clicks while one is already in flight", async () => { const deferredRefresh = (0, testing_library_1.createControlledPromise)(); const refresh = vitest_1.vi.fn(() => deferredRefresh.promise); const asyncList = createMockAsyncList({ loadedItems: [option1], refresh }); const select = renderSelect({ items: asyncList }); await select.clickRefreshButton(); await select.clickRefreshButton(); (0, vitest_1.expect)(refresh).toHaveBeenCalledTimes(1); await deferredRefresh.resolve(); }); (0, vitest_1.test)("displays loading indicator when isLoading is true", async () => { const asyncList = createMockAsyncList({ loadedItems: [option1], isLoading: true, }); const select = renderSelect({ items: asyncList }); await select.click(); select.shouldShowLoadingIndicator(); }); (0, vitest_1.test)("does not display loading indicator when isLoading is false", async () => { const asyncList = createMockAsyncList({ loadedItems: [option1], isLoading: false, }); const select = renderSelect({ items: asyncList }); await select.click(); select.shouldNotShowLoadingIndicator(); }); (0, vitest_1.test)("calls loadMore when scrolling in dropdown with more items available", async () => { const loadMore = vitest_1.vi.fn(); const asyncList = createMockAsyncList({ loadedItems: [option1, option2], loadMore, }); const select = renderSelect({ items: asyncList }); await select.click(); await select.triggerScrollForLoadMore(); (0, vitest_1.expect)(loadMore).toHaveBeenCalled(); }); (0, vitest_1.test)("does not call loadMore when isLoading is true", async () => { const loadMore = vitest_1.vi.fn(); const asyncList = createMockAsyncList({ loadedItems: [option1, option2], isLoading: true, loadMore, }); const select = renderSelect({ items: asyncList }); await select.click(); await select.triggerScrollForLoadMore(); (0, vitest_1.expect)(loadMore).not.toHaveBeenCalled(); }); (0, vitest_1.test)("only triggers loadMore once when multiple scroll events occur near the end of the list", async () => { const loadMore = vitest_1.vi.fn(); const asyncList = createMockAsyncList({ loadedItems: [option1, option2], isLoading: false, error: null, loadMore, }); const select = renderSelect({ items: asyncList }); await select.click(); // Trigger multiple scroll events rapidly await select.triggerScrollForLoadMore(); await select.triggerScrollForLoadMore(); await select.triggerScrollForLoadMore(); // Should only be called once despite multiple scroll events (0, vitest_1.expect)(loadMore).toHaveBeenCalledTimes(1); }); (0, vitest_1.test)("calls loadMore when listbox is initially rendered and already near end of list", async () => { const restoreScrollDimensions = mockListboxScrollDimensions({ scrollHeight: 150, clientHeight: 100 }); const loadMore = vitest_1.vi.fn(); const asyncList = createMockAsyncList({ loadedItems: [option1], isLoading: false, error: null, loadMore, }); const select = renderSelect({ items: asyncList }); await select.click(); (0, vitest_1.expect)(loadMore).toHaveBeenCalledTimes(1); restoreScrollDimensions(); }); (0, vitest_1.test)("does not call loadMore when listbox is near end of list and is already loading", async () => { const restoreScrollDimensions = mockListboxScrollDimensions({ scrollHeight: 150, clientHeight: 100 }); const loadMore = vitest_1.vi.fn(); const asyncList = createMockAsyncList({ loadedItems: [option1, option2, option3], isLoading: true, error: null, loadMore, }); const select = renderSelect({ items: asyncList }); await select.click(); (0, vitest_1.expect)(loadMore).not.toHaveBeenCalled(); restoreScrollDimensions(); }); (0, vitest_1.test)("does not call loadMore when listbox is initially rendered and not near end of list", async () => { const restoreScrollDimensions = mockListboxScrollDimensions({ scrollHeight: 1000, clientHeight: 100 }); const loadMore = vitest_1.vi.fn(); const asyncList = createMockAsyncList({ loadedItems: [option1, option2, option3], isLoading: false, error: null, loadMore, }); const select = renderSelect({ items: asyncList }); await select.click(); (0, vitest_1.expect)(loadMore).not.toHaveBeenCalled(); restoreScrollDimensions(); }); (0, vitest_1.test)("can select option from loaded items", async () => { const asyncList = createMockAsyncList({ loadedItems: [option1, option2, option3], }); const select = renderSelect({ items: asyncList, value: option1.value }); await select.click(); await select.selectOption("Option 2"); select.shouldHaveCalledOnChangeWith(option2.value); }); (0, vitest_1.describe)("with filtering", () => { (0, vitest_1.test)("shows search input when allowFilter is true with AsyncList", async () => { const asyncList = createMockAsyncList({ loadedItems: [option1, option2, option3], }); const select = renderSelect({ items: asyncList, allowFilter: true }); await select.click(); select.shouldShowSearchInput(); }); (0, vitest_1.test)("calls setFilterText when typing in search input with AsyncList", async () => { const setFilterText = vitest_1.vi.fn(); const asyncList = createMockAsyncList({ loadedItems: [option1, option2, option3], setFilterText, }); const select = renderSelect({ items: asyncList, allowFilter: true }); await select.click(); await select.typeInSearchInput("test"); (0, vitest_1.expect)(setFilterText).toHaveBeenCalledWith("test"); }); (0, vitest_1.test)("displays all loadedItems without client-side filtering when using AsyncList", async () => { const setFilterText = vitest_1.vi.fn(); const asyncList = createMockAsyncList({ loadedItems: [option1], setFilterText, }); const select = renderSelect({ items: asyncList, allowFilter: true }); await select.click(); await select.typeInSearchInput("2"); select.shouldShowOnlyOption("Option 1"); (0, vitest_1.expect)(setFilterText).toHaveBeenCalledWith("2"); }); (0, vitest_1.test)("shows no results view when filtering returns no results", async () => { const setFilterText = vitest_1.vi.fn(); const asyncList = createMockAsyncList({ loadedItems: [], setFilterText, }); const select = renderSelect({ items: asyncList, allowFilter: true }); await select.click(); await select.typeInSearchInput("xyz"); select.shouldShowNoResultsMessage(); }); (0, vitest_1.test)("clears filter text and calls setFilterText when dropdown closes", async () => { const setFilterText = vitest_1.vi.fn(); const asyncList = createMockAsyncList({ loadedItems: [option1], setFilterText, }); const select = renderSelect({ items: asyncList, allowFilter: true }); await select.click(); await select.typeInSearchInput("test"); setFilterText.mockClear(); await select.pressEscape(); (0, vitest_1.expect)(setFilterText).toHaveBeenCalledWith(""); }); }); (0, vitest_1.describe)("with error", () => { (0, vitest_1.test)("shows loaded items when last load has an error", async () => { const asyncList = createMockAsyncList({ loadedItems: [option1, option2, option3], error: new Error(), }); const select = renderSelect({ items: asyncList }); await select.click(); select.shouldShowAllOptions([option1, option2, option3]); }); (0, vitest_1.test)("shows error message when last load has an error", async () => { const asyncList = createMockAsyncList({ loadedItems: [option1, option2, option3], error: new Error(), }); const select = renderSelect({ items: asyncList }); await select.click(); select.shouldHaveError("Failed to load results"); }); (0, vitest_1.test)("shows a load error on the field, without needing to open the dropdown", () => { const asyncList = createMockAsyncList({ loadedItems: [option1, option2, option3], error: new Error("Raw server error that should not be shown"), }); const select = renderSelect({ items: asyncList, label: "Runbook" }); // A fixed message matching the dropdown footer, not the raw error, and visible without opening the dropdown. select.shouldHaveError("Failed to load results"); }); (0, vitest_1.test)("does not mark the field invalid when a load fails but the value is valid", () => { const asyncList = createMockAsyncList({ loadedItems: [option1, option2, option3], error: new Error("load failed"), }); const select = renderSelect({ items: asyncList }); // A load failure isn't a validation error — the selected value is still valid. select.shouldHaveAriaInvalid(false); }); (0, vitest_1.test)("shows error message when there is an error and no items", async () => { const asyncList = createMockAsyncList({ loadedItems: [], error: new Error(), }); const select = renderSelect({ items: asyncList }); await select.click(); select.shouldHaveError("Failed to load results"); }); (0, vitest_1.test)("shows retry button when last load has an error", async () => { const asyncList = createMockAsyncList({ loadedItems: [option1, option2, option3], error: new Error(), loadMore: vitest_1.vi.fn(), }); const select = renderSelect({ items: asyncList }); await select.click(); select.shouldHaveRetryButton(); }); (0, vitest_1.test)("does not show a retry button when the list has no loadMore or refresh", async () => { const asyncList = createMockAsyncList({ loadedItems: [option1, option2, option3], error: new Error(), }); const select = renderSelect({ items: asyncList }); await select.click(); select.shouldNotHaveRetryButton(); }); (0, vitest_1.test)("calls loadMore when retry is clicked", async () => { const loadMore = vitest_1.vi.fn(); const asyncList = createMockAsyncList({ loadedItems: [option1, option2, option3], error: new Error(), loadMore, }); const select = renderSelect({ items: asyncList }); await select.click(); await select.clickRetryButton(); (0, vitest_1.expect)(loadMore).toHaveBeenCalled(); }); (0, vitest_1.test)("calls refresh when retry is clicked and the list has no loadMore", async () => { const refresh = vitest_1.vi.fn(); const asyncList = createMockAsyncList({ loadedItems: [option1, option2, option3], error: new Error(), refresh, }); const select = renderSelect({ items: asyncList }); await select.click(); await select.clickRetryButton(); (0, vitest_1.expect)(refresh).toHaveBeenCalledTimes(1); }); (0, vitest_1.test)("does not call loadMore on scroll when there is an error", async () => { const loadMore = vitest_1.vi.fn(); const asyncList = createMockAsyncList({ loadedItems: [option1, option2, option3], error: new Error(), loadMore, }); const select = renderSelect({ items: asyncList }); await select.click(); await select.triggerScrollForLoadMore(); (0, vitest_1.expect)(loadMore).not.toHaveBeenCalled(); }); }); }); }); function mockListboxScrollDimensions({ scrollHeight, clientHeight, scrollTop = 0 }) { const scrollHeightDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "scrollHeight"); const clientHeightDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "clientHeight"); const scrollTopDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "scrollTop"); Object.defineProperty(HTMLElement.prototype, "scrollHeight", { configurable: true, get: () => scrollHeight }); Object.defineProperty(HTMLElement.prototype, "clientHeight", { configurable: true, get: () => clientHeight }); Object.defineProperty(HTMLElement.prototype, "scrollTop", { configurable: true, get: () => scrollTop }); return () => { if (scrollHeightDescriptor) { Object.defineProperty(HTMLElement.prototype, "scrollHeight", scrollHeightDescriptor); } else { Reflect.deleteProperty(HTMLElement.prototype, "scrollHeight"); } if (clientHeightDescriptor) { Object.defineProperty(HTMLElement.prototype, "clientHeight", clientHeightDescriptor); } else { Reflect.deleteProperty(HTMLElement.prototype, "clientHeight"); } if (scrollTopDescriptor) { Object.defineProperty(HTMLElement.prototype, "scrollTop", scrollTopDescriptor); } else { Reflect.deleteProperty(HTMLElement.prototype, "scrollTop"); } }; } // Keeps the field genuinely controlled (mirroring how a real consumer feeds onChange back into value), so // SelectBase's required-validation (which detects a change by comparing the value prop across renders) can // actually observe a change instead of the value staying fixed at whatever the test passed in initially. function ControlledSelect({ onChange, value: initialValue, ...props }) { const [value, setValue] = (0, react_2.useState)(initialValue); return ((0, jsx_runtime_1.jsx)(Select_1.Select, { ...props, value: value, onChange: (newValue) => { setValue(newValue); onChange(newValue); } })); } function renderSelect(opts = {}) { const { withForm, onSubmit, ...selectProps } = opts; const onChange = vitest_1.vi.fn(); const props = { label: "Test Label", items: items, getOption: (item) => item, value: undefined, onChange, ...selectProps, }; const component = withForm ? ((0, jsx_runtime_1.jsx)("form", { onSubmit: onSubmit, children: (0, jsx_runtime_1.jsx)(ControlledSelect, { ...props }) })) : ((0, jsx_runtime_1.jsx)(ControlledSelect, { ...props })); (0, react_1.render)(component); return { ...getSelectInteractions(props.label, onChange), onSubmit }; } function getSelectInteractions(label, onChange) { const select = () => react_1.screen.getByRole("combobox", { name: new RegExp(label) }); return { ...(0, getSharedSelectInteractions_1.getSharedSelectInteractions)(label, onChange), shouldShowSelectedOption: (optionLabel) => { testing_library_1.domQueries.getText(optionLabel, { containerElement: select() }).assertIsInTheDocument(); }, shouldHaveCalledOnChangeWith: (value) => { (0, vitest_1.expect)(onChange).toHaveBeenCalledWith(value); }, }; } function createMockAsyncList(options) { return { getItemId: options.getItemId ?? ((item) => item.value), getItemById: options.getItemById ?? (() => undefined), loadedItems: options.loadedItems ?? [], isLoading: options.isLoading ?? false, error: options.error ?? null, loadMore: options.loadMore, setFilterText: options.setFilterText ?? vitest_1.vi.fn(), refresh: options.refresh, }; }