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 @@
-
+
online: !offline,
},
},
- actions: {
- register: mockRegisterAction,
+ modules: {
+ account: {
+ namespaced: true,
+ actions: {
+ register: mockRegisterAction,
+ },
+ },
+ policies: {
+ namespaced: true,
+ getters: {
+ getPolicyAcceptedData: () => () => ({}),
+ },
+ },
},
});
@@ -102,20 +113,7 @@ 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');
@@ -126,6 +124,12 @@ describe('Create account page', () => {
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 });
@@ -141,8 +145,7 @@ 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');
@@ -153,11 +156,19 @@ describe('Create account page', () => {
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);
- 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();
+ });
});
});