From 03b1a9422ae961122464601f3f0ca1d809e2646a Mon Sep 17 00:00:00 2001 From: LightCreator1007 Date: Mon, 5 Oct 2026 23:57:20 +0530 Subject: [PATCH 1/3] Replace CountryField VAutocomplete with KDS KMultiSelect Render KMultiSelect in CountryField instead of the Vuetify VAutocomplete inside DropdownWrapper. The value, required, multiple, label and fullWidth props are unchanged, so the four consumers keep the same English-name values while the list shows translated country names. - Validate required through invalid/invalidText once the field is left - Size the field with appearanceOverrides instead of v-bind() in CSS - Drop the setTimeout that cleared the search text; KMultiSelect does it - Build messages with createMultiSelectMessages and add country strings to commonStrings - Remove Vuetify-only props (box, outline, hide-details, menu-props, clearable) from Create, RequestForm, UserTable and the community library side panel - Rewrite countryField.spec.js with Vue Testing Library - Re-enable the skipped create.spec.js tests and fix the store setup and form filling they needed to pass Closes #6139 --- .../frontend/accounts/pages/Create.vue | 5 +- .../accounts/pages/__tests__/create.spec.js | 77 +++++---- .../administration/pages/Users/UserTable.vue | 1 - .../index.vue | 1 - .../settings/pages/Storage/RequestForm.vue | 6 +- .../frontend/shared/strings/commonStrings.js | 8 + .../shared/views/form/CountryField.vue | 116 ++++++-------- .../views/form/__tests__/countryField.spec.js | 147 +++++++++++++----- 8 files changed, 209 insertions(+), 152 deletions(-) diff --git a/contentcuration/contentcuration/frontend/accounts/pages/Create.vue b/contentcuration/contentcuration/frontend/accounts/pages/Create.vue index ec8908cdef..d075f136c0 100644 --- a/contentcuration/contentcuration/frontend/accounts/pages/Create.vue +++ b/contentcuration/contentcuration/frontend/accounts/pages/Create.vue @@ -131,10 +131,7 @@

{{ $tr('locationLabel') }}*

- +
online: !offline, }, }, - actions: { - register: mockRegisterAction, + modules: { + account: { + namespaced: true, + actions: { + register: mockRegisterAction, + }, + }, + policies: { + namespaced: true, + getters: { + getPolicyAcceptedData: () => () => ({}), + }, + }, }, }); @@ -49,6 +60,24 @@ const renderComponent = async ({ routeQuery = {}, offline = false } = {}) => { }); }; +const fillValidForm = async () => { + await userEvent.type(screen.getByLabelText(/first name/i), 'Test'); + await userEvent.type(screen.getByLabelText(/last name/i), 'User'); + await userEvent.type(screen.getByLabelText(/email/i), 'test@test.com'); + await userEvent.type(screen.getByLabelText(/^password$/i), 'tester123'); + await userEvent.type(screen.getByLabelText(/confirm password/i), 'tester123'); + + await userEvent.click(screen.getByLabelText(/tagging content sources/i)); + + await userEvent.type(screen.getByRole('combobox', { name: /select all that apply/i }), 'Kenya'); + await userEvent.click(await screen.findByRole('option', { name: 'Kenya' })); + + await userEvent.click(screen.getByText(/select one/i)); + await userEvent.click(await screen.findByText(/learning equality website/i)); + + await userEvent.click(screen.getByLabelText(/i have read and agree to terms of service/i)); +}; + describe('Create account page', () => { beforeEach(() => { jest.clearAllMocks(); @@ -102,31 +131,9 @@ describe('Create account page', () => { }); }); - // NOTE: - // Full form submission tests are intentionally skipped here. - // - // The "locations" field uses CountryField, which internally uses VAutocomplete - // (a Vuetify component). VAutocomplete does not reliably update its v-model - // state when interacted with via Vue Testing Library's userEvent APIs, which - // prevents a fully user-centric submission flow from being exercised. - // - // The "source" field has been migrated to KSelect (KDS) and no longer has - // this limitation, but "locations" remains the blocker. - // - // These tests will be re-enabled once CountryField is migrated away from - // VAutocomplete as part of the ongoing Vuetify removal effort. - it.skip('creates an account when the user submits valid information', async () => { + it('creates an account when the user submits valid information', async () => { await renderComponent(); - - await userEvent.type(screen.getByLabelText(/first name/i), 'Test'); - await userEvent.type(screen.getByLabelText(/last name/i), 'User'); - await userEvent.type(screen.getByLabelText(/email/i), 'test@test.com'); - await userEvent.type(screen.getByLabelText(/^password$/i), 'tester123'); - await userEvent.type(screen.getByLabelText(/confirm password/i), 'tester123'); - - await userEvent.click(screen.getByLabelText(/tagging content sources/i)); - - await userEvent.click(screen.getByLabelText(/i have read and agree to terms of service/i)); + await fillValidForm(); const finishButton = screen.getByRole('button', { name: /finish/i }); @@ -141,23 +148,15 @@ describe('Create account page', () => { }); }); - // Skipped for the same reason as above — CountryField (VAutocomplete) blocker - it.skip('shows an offline error when the user is offline', async () => { + it('shows an offline error when the user is offline', async () => { await renderComponent({ offline: true }); - - await userEvent.type(screen.getByLabelText(/first name/i), 'Test'); - await userEvent.type(screen.getByLabelText(/last name/i), 'User'); - await userEvent.type(screen.getByLabelText(/email/i), 'test@test.com'); - await userEvent.type(screen.getByLabelText(/^password$/i), 'tester123'); - await userEvent.type(screen.getByLabelText(/confirm password/i), 'tester123'); - - await userEvent.click(screen.getByLabelText(/tagging content sources/i)); - - await userEvent.click(screen.getByLabelText(/i have read and agree to terms of service/i)); + await fillValidForm(); const finishButton = screen.getByRole('button', { name: /finish/i }); await userEvent.click(finishButton); - expect(await screen.findByText(/offline/i)).toBeInTheDocument(); + expect( + await screen.findByText(/please connect to the internet to create an account/i), + ).toBeInTheDocument(); }); }); diff --git a/contentcuration/contentcuration/frontend/administration/pages/Users/UserTable.vue b/contentcuration/contentcuration/frontend/administration/pages/Users/UserTable.vue index c409ddd82b..40eeb617fa 100644 --- a/contentcuration/contentcuration/frontend/administration/pages/Users/UserTable.vue +++ b/contentcuration/contentcuration/frontend/administration/pages/Users/UserTable.vue @@ -57,7 +57,6 @@ diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/sidePanels/SubmitToCommunityLibrarySidePanel/index.vue b/contentcuration/contentcuration/frontend/channelEdit/components/sidePanels/SubmitToCommunityLibrarySidePanel/index.vue index ffceef6479..3223210536 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/sidePanels/SubmitToCommunityLibrarySidePanel/index.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/sidePanels/SubmitToCommunityLibrarySidePanel/index.vue @@ -204,7 +204,6 @@ :disabled="!canBeEdited" :label="countryLabel$()" fullWidth - :hide-details="true" />
diff --git a/contentcuration/contentcuration/frontend/settings/pages/Storage/RequestForm.vue b/contentcuration/contentcuration/frontend/settings/pages/Storage/RequestForm.vue index 9d8cca72c7..91883b31e5 100644 --- a/contentcuration/contentcuration/frontend/settings/pages/Storage/RequestForm.vue +++ b/contentcuration/contentcuration/frontend/settings/pages/Storage/RequestForm.vue @@ -133,11 +133,7 @@
- + - - - + - - - diff --git a/contentcuration/contentcuration/frontend/shared/views/form/__tests__/countryField.spec.js b/contentcuration/contentcuration/frontend/shared/views/form/__tests__/countryField.spec.js index 69d01df9cf..62a9fe8268 100644 --- a/contentcuration/contentcuration/frontend/shared/views/form/__tests__/countryField.spec.js +++ b/contentcuration/contentcuration/frontend/shared/views/form/__tests__/countryField.spec.js @@ -1,55 +1,130 @@ -import { mount } from '@vue/test-utils'; +import { render, screen } from '@testing-library/vue'; +import userEvent from '@testing-library/user-event'; +import VueRouter from 'vue-router'; import CountryField from '../CountryField.vue'; -function makeWrapper() { - return mount(CountryField, { - propsData: { - required: true, - }, - }); +function renderComponent(props = {}) { + return render(CountryField, { props, routes: new VueRouter() }); +} + +// Tabbing straight out of an open dropdown moves focus into its listbox, +// so close it first to actually leave the field. +async function leaveField() { + await userEvent.click(screen.getByRole('combobox')); + await userEvent.keyboard('{Escape}'); + await userEvent.tab(); } -function runValidation(wrapper, value) { - return wrapper.vm.rules.every(rule => rule(value) === true); +function lastInput(emitted) { + const events = emitted().input; + return events[events.length - 1][0]; } -describe('countryField', () => { - let wrapper; - beforeEach(() => { - wrapper = makeWrapper(); +describe('CountryField', () => { + it('renders the default label', () => { + renderComponent(); + expect(screen.getByRole('combobox', { name: 'Select all that apply' })).toBeInTheDocument(); }); - it('should validate if no selections are made', () => { - expect(runValidation(wrapper, [])).toBe(false); - expect(runValidation(wrapper, ['item'])).toBe(true); + + it('renders a custom label', () => { + renderComponent({ label: 'Target location' }); + expect(screen.getByRole('combobox', { name: 'Target location' })).toBeInTheDocument(); + }); + + describe('in multiple selection mode', () => { + it('emits the selected countries as an array of English names', async () => { + const { emitted } = renderComponent({ value: ['Kenya'] }); + + await userEvent.type(screen.getByRole('combobox'), 'Czech'); + await userEvent.click(await screen.findByRole('option', { name: 'Czech Republic' })); + + expect(lastInput(emitted)).toEqual(['Kenya', 'Czech Republic']); + }); + + it('clears the search text after a selection', async () => { + renderComponent(); + + const input = screen.getByRole('combobox'); + await userEvent.type(input, 'Czech'); + await userEvent.click(await screen.findByRole('option', { name: 'Czech Republic' })); + + expect(input).toHaveValue(''); + }); }); - it('in multiple selection mode, search input is cleared after selection', async () => { - await wrapper.setProps({ multiple: true }); + describe('in single selection mode', () => { + it('emits the selected country as an English name', async () => { + const { emitted } = renderComponent({ multiple: false }); - const autocomplete = wrapper.findComponent({ name: 'VAutocomplete' }); - wrapper.vm.searchInput = 'Czech'; + await userEvent.type(screen.getByRole('combobox'), 'Czech'); + await userEvent.click(await screen.findByRole('option', { name: 'Czech Republic' })); - // Handling selection change uses `setTimeout` so we need to fake the timer - jest.useFakeTimers(); - autocomplete.vm.$emit('input', ['Czech Republic']); - jest.runAllTimers(); - jest.useRealTimers(); + expect(lastInput(emitted)).toBe('Czech Republic'); + }); - expect(wrapper.vm.searchInput).toBe(''); + it('shows the selected country in the input', () => { + renderComponent({ multiple: false, value: 'Kenya' }); + expect(screen.getByRole('combobox')).toHaveValue('Kenya'); + }); + + it('renders with no selection when no value is given', () => { + renderComponent({ multiple: false }); + expect(screen.getByRole('combobox')).toHaveValue(''); + }); }); - it('in single selection mode, search input is not cleared after selection', async () => { - await wrapper.setProps({ multiple: false }); + describe('with a non-English interface language', () => { + beforeEach(() => { + window.languageCode = 'es'; + }); + + afterEach(() => { + delete window.languageCode; + }); + + it('shows translated country names but emits English names', async () => { + const { emitted } = renderComponent(); + + await userEvent.type(screen.getByRole('combobox'), 'Alemania'); + await userEvent.click(await screen.findByRole('option', { name: 'Alemania' })); + + expect(lastInput(emitted)).toEqual(['Germany']); + }); + }); + + it('shows a message when no country matches the search', async () => { + renderComponent(); + + await userEvent.type(screen.getByRole('combobox'), 'zzzzz'); + + expect(await screen.findByText('No countries found')).toBeInTheDocument(); + }); + + it('disables the input when disabled', () => { + renderComponent({ disabled: true }); + expect(screen.getByRole('combobox')).toBeDisabled(); + }); + + describe('when required', () => { + it('shows an error after leaving the field empty', async () => { + renderComponent({ required: true }); + + await leaveField(); + + expect(await screen.findByText('Field is required')).toBeInTheDocument(); + }); + + it('does not show an error before the field is touched', () => { + renderComponent({ required: true }); + expect(screen.queryByText('Field is required')).not.toBeInTheDocument(); + }); - const autocomplete = wrapper.findComponent({ name: 'VAutocomplete' }); - wrapper.vm.searchInput = 'Czech'; + it('does not show an error when a country is selected', async () => { + renderComponent({ required: true, value: ['Kenya'] }); - // Handling selection change uses `setTimeout` so we need to fake the timer - jest.useFakeTimers(); - autocomplete.vm.$emit('input', 'Czech Republic'); - jest.runAllTimers(); - jest.useRealTimers(); + await leaveField(); - expect(wrapper.vm.searchInput).not.toBe(''); + expect(screen.queryByText('Field is required')).not.toBeInTheDocument(); + }); }); }); From 9da4b5908be02ced3359c32014a94c8ae3140187 Mon Sep 17 00:00:00 2001 From: LightCreator1007 Date: Tue, 6 Oct 2026 01:39:05 +0530 Subject: [PATCH 2/3] Fill the account form inline in the re-enabled create tests --- .../accounts/pages/__tests__/create.spec.js | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js index bed0344c9d..7f1b85ac50 100644 --- a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js +++ b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js @@ -60,24 +60,6 @@ const renderComponent = async ({ routeQuery = {}, offline = false } = {}) => { }); }; -const fillValidForm = async () => { - await userEvent.type(screen.getByLabelText(/first name/i), 'Test'); - await userEvent.type(screen.getByLabelText(/last name/i), 'User'); - await userEvent.type(screen.getByLabelText(/email/i), 'test@test.com'); - await userEvent.type(screen.getByLabelText(/^password$/i), 'tester123'); - await userEvent.type(screen.getByLabelText(/confirm password/i), 'tester123'); - - await userEvent.click(screen.getByLabelText(/tagging content sources/i)); - - await userEvent.type(screen.getByRole('combobox', { name: /select all that apply/i }), 'Kenya'); - await userEvent.click(await screen.findByRole('option', { name: 'Kenya' })); - - await userEvent.click(screen.getByText(/select one/i)); - await userEvent.click(await screen.findByText(/learning equality website/i)); - - await userEvent.click(screen.getByLabelText(/i have read and agree to terms of service/i)); -}; - describe('Create account page', () => { beforeEach(() => { jest.clearAllMocks(); @@ -133,7 +115,22 @@ describe('Create account page', () => { it('creates an account when the user submits valid information', async () => { await renderComponent(); - await fillValidForm(); + + await userEvent.type(screen.getByLabelText(/first name/i), 'Test'); + await userEvent.type(screen.getByLabelText(/last name/i), 'User'); + await userEvent.type(screen.getByLabelText(/email/i), 'test@test.com'); + await userEvent.type(screen.getByLabelText(/^password$/i), 'tester123'); + await userEvent.type(screen.getByLabelText(/confirm password/i), 'tester123'); + + await userEvent.click(screen.getByLabelText(/tagging content sources/i)); + + await userEvent.type(screen.getByRole('combobox', { name: /select all that apply/i }), 'Kenya'); + await userEvent.click(await screen.findByRole('option', { name: 'Kenya' })); + + await userEvent.click(screen.getByText(/select one/i)); + await userEvent.click(await screen.findByText(/learning equality website/i)); + + await userEvent.click(screen.getByLabelText(/i have read and agree to terms of service/i)); const finishButton = screen.getByRole('button', { name: /finish/i }); @@ -150,7 +147,22 @@ describe('Create account page', () => { it('shows an offline error when the user is offline', async () => { await renderComponent({ offline: true }); - await fillValidForm(); + + await userEvent.type(screen.getByLabelText(/first name/i), 'Test'); + await userEvent.type(screen.getByLabelText(/last name/i), 'User'); + await userEvent.type(screen.getByLabelText(/email/i), 'test@test.com'); + await userEvent.type(screen.getByLabelText(/^password$/i), 'tester123'); + await userEvent.type(screen.getByLabelText(/confirm password/i), 'tester123'); + + await userEvent.click(screen.getByLabelText(/tagging content sources/i)); + + await userEvent.type(screen.getByRole('combobox', { name: /select all that apply/i }), 'Kenya'); + await userEvent.click(await screen.findByRole('option', { name: 'Kenya' })); + + await userEvent.click(screen.getByText(/select one/i)); + await userEvent.click(await screen.findByText(/learning equality website/i)); + + await userEvent.click(screen.getByLabelText(/i have read and agree to terms of service/i)); const finishButton = screen.getByRole('button', { name: /finish/i }); await userEvent.click(finishButton); From 6b77938dcf87085775213146dd52585f5fb13694 Mon Sep 17 00:00:00 2001 From: LightCreator1007 Date: Wed, 21 Oct 2026 17:21:28 +0530 Subject: [PATCH 3/3] Address review feedback on CountryField tests and strings - Check that register receives the English country name in the re-enabled create account test - Check the required error through the combobox's accessible description instead of only the rendered text - Look for "No countries found" inside the field, since the live region announces the same text, and check that no options remain - Share one selectionsClearedLabel between the language and country pickers instead of two identical strings --- .../frontend/accounts/pages/__tests__/create.spec.js | 5 ++++- .../views/Channel/components/LanguageFilter.vue | 4 ++-- .../frontend/shared/strings/commonStrings.js | 8 ++------ .../frontend/shared/views/LanguageDropdown.vue | 6 ++---- .../frontend/shared/views/form/CountryField.vue | 6 ++---- .../shared/views/form/__tests__/countryField.spec.js | 12 ++++++++---- 6 files changed, 20 insertions(+), 21 deletions(-) diff --git a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js index 7f1b85ac50..6b356aa05b 100644 --- a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js +++ b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js @@ -141,7 +141,10 @@ describe('Create account page', () => { await userEvent.click(finishButton); await waitFor(() => { - expect(mockRegisterAction).toHaveBeenCalled(); + expect(mockRegisterAction).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ locations: 'Kenya' }), + ); }); }); diff --git a/contentcuration/contentcuration/frontend/channelList/views/Channel/components/LanguageFilter.vue b/contentcuration/contentcuration/frontend/channelList/views/Channel/components/LanguageFilter.vue index 7f71ff50a8..50d9763440 100644 --- a/contentcuration/contentcuration/frontend/channelList/views/Channel/components/LanguageFilter.vue +++ b/contentcuration/contentcuration/frontend/channelList/views/Channel/components/LanguageFilter.vue @@ -64,12 +64,12 @@ }, }, messages() { - const { languageItemsSelectedLabel$, languageSelectionsClearedLabel$ } = commonStrings; + const { languageItemsSelectedLabel$, selectionsClearedLabel$ } = commonStrings; const { clearAllAction$ } = communityChannelsStrings; return createMultiSelectMessages({ clearText: clearAllAction$, itemsSelected: languageItemsSelectedLabel$, - cleared: languageSelectionsClearedLabel$, + cleared: selectionsClearedLabel$, }); }, }, diff --git a/contentcuration/contentcuration/frontend/shared/strings/commonStrings.js b/contentcuration/contentcuration/frontend/shared/strings/commonStrings.js index 158097f4d9..0551c59397 100644 --- a/contentcuration/contentcuration/frontend/shared/strings/commonStrings.js +++ b/contentcuration/contentcuration/frontend/shared/strings/commonStrings.js @@ -102,16 +102,12 @@ export const commonStrings = createTranslator('CommonStrings', { message: '{count, plural, one {# language selected} other {# languages selected}}', context: 'Announced with the number of languages currently selected in a language picker', }, - languageSelectionsClearedLabel: { - message: '{count, plural, one {Cleared # selection} other {Cleared # selections}}', - context: 'Announced when all selected languages are cleared from a language picker', - }, countryItemsSelectedLabel: { message: '{count, plural, one {# country selected} other {# countries selected}}', context: 'Announced with the number of countries currently selected in a country picker', }, - countrySelectionsClearedLabel: { + selectionsClearedLabel: { message: '{count, plural, one {Cleared # selection} other {Cleared # selections}}', - context: 'Announced when all selected countries are cleared from a country picker', + context: 'Announced when all selected options are cleared from a multiple selection dropdown', }, }); diff --git a/contentcuration/contentcuration/frontend/shared/views/LanguageDropdown.vue b/contentcuration/contentcuration/frontend/shared/views/LanguageDropdown.vue index 334cf5ff91..496f37d476 100644 --- a/contentcuration/contentcuration/frontend/shared/views/LanguageDropdown.vue +++ b/contentcuration/contentcuration/frontend/shared/views/LanguageDropdown.vue @@ -103,15 +103,13 @@ clearAction$, optionRemovedLabel$, languageItemsSelectedLabel$, - languageSelectionsClearedLabel$, + selectionsClearedLabel$, } = commonStrings; return createMultiSelectMessages({ clearText: clearAction$, itemsSelected: languageItemsSelectedLabel$, cleared: ({ label, count }) => - this.multiple - ? languageSelectionsClearedLabel$({ count }) - : optionRemovedLabel$({ label }), + this.multiple ? selectionsClearedLabel$({ count }) : optionRemovedLabel$({ label }), }); }, }, diff --git a/contentcuration/contentcuration/frontend/shared/views/form/CountryField.vue b/contentcuration/contentcuration/frontend/shared/views/form/CountryField.vue index 220d68f94e..31c43c4c33 100644 --- a/contentcuration/contentcuration/frontend/shared/views/form/CountryField.vue +++ b/contentcuration/contentcuration/frontend/shared/views/form/CountryField.vue @@ -94,16 +94,14 @@ clearAction$, optionRemovedLabel$, countryItemsSelectedLabel$, - countrySelectionsClearedLabel$, + selectionsClearedLabel$, } = commonStrings; return createMultiSelectMessages({ clearText: clearAction$, itemsSelected: countryItemsSelectedLabel$, // KMultiSelect passes a label in single mode and a count in multiple mode cleared: ({ label, count }) => - this.multiple - ? countrySelectionsClearedLabel$({ count }) - : optionRemovedLabel$({ label }), + this.multiple ? selectionsClearedLabel$({ count }) : optionRemovedLabel$({ label }), }); }, }, diff --git a/contentcuration/contentcuration/frontend/shared/views/form/__tests__/countryField.spec.js b/contentcuration/contentcuration/frontend/shared/views/form/__tests__/countryField.spec.js index 62a9fe8268..1188324c18 100644 --- a/contentcuration/contentcuration/frontend/shared/views/form/__tests__/countryField.spec.js +++ b/contentcuration/contentcuration/frontend/shared/views/form/__tests__/countryField.spec.js @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/vue'; +import { render, screen, waitFor, within } from '@testing-library/vue'; import userEvent from '@testing-library/user-event'; import VueRouter from 'vue-router'; import CountryField from '../CountryField.vue'; @@ -93,11 +93,13 @@ describe('CountryField', () => { }); it('shows a message when no country matches the search', async () => { - renderComponent(); + const { container } = renderComponent(); await userEvent.type(screen.getByRole('combobox'), 'zzzzz'); - expect(await screen.findByText('No countries found')).toBeInTheDocument(); + // The page-level live region also announces this text, so only look inside the field + expect(await within(container).findByText('No countries found')).toBeInTheDocument(); + expect(within(container).queryAllByRole('option', { hidden: true })).toHaveLength(0); }); it('disables the input when disabled', () => { @@ -111,7 +113,9 @@ describe('CountryField', () => { await leaveField(); - expect(await screen.findByText('Field is required')).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByRole('combobox')).toHaveAccessibleDescription('Field is required'); + }); }); it('does not show an error before the field is touched', () => {