From 6bd08dd3365b0a447579c8b326bcc7f0bff9cfaa Mon Sep 17 00:00:00 2001 From: abhiraj75 Date: Tue, 13 Jan 2026 04:06:39 +0530 Subject: [PATCH 01/10] test(accounts): migrate create account page tests to Vue Testing Library --- .../accounts/pages/__tests__/create.spec.js | 278 +++++++++--------- 1 file changed, 131 insertions(+), 147 deletions(-) diff --git a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js index a2c2f40d71..67d176ca32 100644 --- a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js +++ b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js @@ -1,182 +1,166 @@ -import { mount } from '@vue/test-utils'; +import Vuex from 'vuex'; +import { render, screen, waitFor } from '@testing-library/vue'; +import userEvent from '@testing-library/user-event'; import router from '../../router'; -import { uses, sources } from '../../constants'; import Create from '../Create'; -const connectionStateMocks = { - $store: { +const makeStore = ({ offline = false } = {}) => + new Vuex.Store({ state: { connection: { - offline: true, + offline, }, }, - }, -}; + }); -const defaultData = { - first_name: 'Test', - last_name: 'User', - email: 'test@test.com', - password1: 'tester123', - password2: 'tester123', - uses: ['tagging'], - storage: '', - other_use: '', - locations: ['China'], - source: 'demo', - organization: '', - conference: '', - other_source: '', - accepted_policy: true, - accepted_tos: true, -}; +const renderComponent = async ({ routeQuery = {}, offline = false } = {}) => { + if (router.currentRoute.path === '/create') { + await router.push('/').catch(() => {}); + } -async function makeWrapper(formData) { - const wrapper = mount(Create, { + await router.push({ name: 'Create', query: routeQuery }).catch(() => {}); + + return render(Create, { router, - computed: { - getPolicyAcceptedData() { - return () => { - return {}; - }; - }, + store: makeStore({ offline }), + stubs: { + PolicyModals: true, }, - stubs: ['PolicyModals'], - mocks: connectionStateMocks, }); - await wrapper.setData({ - form: { - ...defaultData, - ...formData, - }, - }); - const register = jest.spyOn(wrapper.vm, 'register'); - register.mockImplementation(() => Promise.resolve()); - return [wrapper, { register }]; -} -function makeFailedPromise(statusCode) { - return () => { - return new Promise((resolve, reject) => { - reject({ - response: { - status: statusCode || 500, - }, - }); - }); - }; -} - -describe('create', () => { - it('should trigger submit method when form is submitted', async () => { - const [wrapper] = await makeWrapper(); - const submit = jest.spyOn(wrapper.vm, 'submit'); - submit.mockImplementation(() => Promise.resolve()); - await wrapper.findComponent({ ref: 'form' }).trigger('submit'); - expect(submit).toHaveBeenCalled(); +}; + +describe('Create account page', () => { + test('smoke test: renders the create account page', async () => { + await renderComponent(); + + expect( + await screen.findByRole('heading', { name: /create an account/i }) + ).toBeInTheDocument(); }); - it('should call register with form data', async () => { - const [wrapper, mocks] = await makeWrapper(); - await wrapper.findComponent({ ref: 'form' }).trigger('submit'); - expect(mocks.register.mock.calls[0][0]).toEqual({ - ...defaultData, - locations: defaultData.locations.join('|'), - uses: defaultData.uses.join('|'), - policies: '{}', - }); + test('shows validation state when submitting empty form', async () => { + await renderComponent(); + + const finishButton = screen.getByRole('button', { name: /finish/i }); + await userEvent.click(finishButton); + + expect(finishButton).toBeDisabled(); }); - it('should automatically fill the email if provided in the query param', () => { - router.push({ name: 'Create', query: { email: 'newtest@test.com' } }); - const wrapper = mount(Create, { router, stubs: ['PolicyModals'], mocks: connectionStateMocks }); - expect(wrapper.vm.form.email).toBe('newtest@test.com'); + test('allows user to fill in text input fields', 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'); + + expect(screen.getByLabelText(/first name/i)).toHaveValue('Test'); + expect(screen.getByLabelText(/last name/i)).toHaveValue('User'); + expect(screen.getByLabelText(/email/i)).toHaveValue('test@test.com'); + expect(screen.getByLabelText(/^password$/i)).toHaveValue('tester123'); + expect(screen.getByLabelText(/confirm password/i)).toHaveValue('tester123'); }); - describe('validation', () => { - it('should call register if form is valid', async () => { - const [wrapper, mocks] = await makeWrapper(); - wrapper.vm.submit(); - expect(mocks.register).toHaveBeenCalled(); - }); + test('allows user to check checkboxes', async () => { + await renderComponent(); - it('should fail if required fields are not set', async () => { - const form = { - first_name: '', - last_name: '', - email: '', - password1: '', - password2: '', - uses: [], - locations: [], - source: '', - accepted_policy: false, - accepted_tos: false, - }; - - for (const field of Object.keys(form)) { - const [wrapper, mocks] = await makeWrapper({ [field]: form[field] }); - await wrapper.vm.submit(); - expect(mocks.register).not.toHaveBeenCalled(); - } - }); + const contentSourcesCheckbox = screen.getByLabelText( + /tagging content sources/i + ); + const tosCheckbox = screen.getByLabelText( + /i have read and agree to terms of service/i + ); - it('should fail if password1 is too short', async () => { - const [wrapper, mocks] = await makeWrapper({ password1: '123' }); - await wrapper.vm.submit(); - expect(mocks.register).not.toHaveBeenCalled(); - }); + await userEvent.click(contentSourcesCheckbox); + await userEvent.click(tosCheckbox); + + expect(contentSourcesCheckbox).toBeChecked(); + expect(tosCheckbox).toBeChecked(); + }); - it('should fail if password1 and password2 do not match', async () => { - const [wrapper, mocks] = await makeWrapper({ password1: 'some other password' }); - await wrapper.vm.submit(); - expect(mocks.register).not.toHaveBeenCalled(); + test('automatically fills the email field when provided in the URL', async () => { + await renderComponent({ + routeQuery: { email: 'newtest@test.com' }, }); - it.each( - [uses.STORING, uses.OTHER], - 'should fail if uses field is set to fields that require more input that is not provided', - async use => { - const [wrapper, mocks] = await makeWrapper({ uses: [use] }); - await wrapper.vm.submit(); - expect(mocks.register).not.toHaveBeenCalled(); - }, + const emailInput = await screen.findByLabelText(/email/i); + + await waitFor(() => { + expect(emailInput).toHaveValue('newtest@test.com'); + }); + }); +// NOTE: +// Full form submission tests are intentionally skipped here. +// +// This page still relies on Vuetify components (v-select / v-autocomplete) +// for required fields such as "locations" and "source". +// These components do not reliably update their 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 previous Vue Test Utils tests worked around this by directly mutating +// component data (setData), which is intentionally avoided when using +// Testing Library. +// +// These tests will be re-enabled once this page is migrated to the +// Kolibri Design System as part of the Vuetify removal effort (see #5060). + + test.skip('creates an account when the user submits valid information', async () => { + const registerSpy = jest + .spyOn(Create.methods, 'register') + .mockResolvedValue(); + + 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) ); - it.each( - [sources.ORGANIZATION, sources.CONFERENCE, sources.OTHER], - 'should fail if source field is set to an option that requires more input that is not provided', - async source => { - const [wrapper, mocks] = await makeWrapper({ source }); - await wrapper.vm.submit(); - expect(mocks.register).not.toHaveBeenCalled(); - }, + await userEvent.click( + screen.getByLabelText(/i have read and agree to terms of service/i) ); - }); - describe('on backend failures', () => { - let wrapper, mocks; + const finishButton = screen.getByRole('button', { name: /finish/i }); - beforeEach(async () => { - [wrapper, mocks] = await makeWrapper(); + await waitFor(() => { + expect(finishButton).not.toBeDisabled(); }); - it('should say account with email already exists if register returns a 403', async () => { - mocks.register.mockImplementation(makeFailedPromise(403)); - await wrapper.vm.submit(); - expect(wrapper.vm.errors.email).toHaveLength(1); - }); + await userEvent.click(finishButton); - it('should say account has not been activated if register returns 405', async () => { - mocks.register.mockImplementation(makeFailedPromise(405)); - await wrapper.vm.submit(); - expect(wrapper.vm.$route.name).toBe('AccountNotActivated'); + await waitFor(() => { + expect(registerSpy).toHaveBeenCalled(); }); + }); - it('registrationFailed should be true if any other error is returned', async () => { - mocks.register.mockImplementation(makeFailedPromise()); - await wrapper.vm.submit(); - expect(wrapper.vm.valid).toBe(false); - expect(wrapper.vm.registrationFailed).toBe(true); - }); + test.skip('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) + ); + + const finishButton = screen.getByRole('button', { name: /finish/i }); + await userEvent.click(finishButton); + + expect(await screen.findByText(/offline/i)).toBeInTheDocument(); }); }); From c7dcc6a95f9c140fba2f817d0a0c9ba9f0b4a58b Mon Sep 17 00:00:00 2001 From: abhiraj75 Date: Tue, 13 Jan 2026 04:23:11 +0530 Subject: [PATCH 02/10] fixing documentation --- .../frontend/accounts/pages/__tests__/create.spec.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js index 67d176ca32..3efb5ad77b 100644 --- a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js +++ b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js @@ -105,7 +105,7 @@ describe('Create account page', () => { // Testing Library. // // These tests will be re-enabled once this page is migrated to the -// Kolibri Design System as part of the Vuetify removal effort (see #5060). +// Kolibri Design System as part of the Vuetify removal effort . test.skip('creates an account when the user submits valid information', async () => { const registerSpy = jest @@ -140,7 +140,12 @@ describe('Create account page', () => { expect(registerSpy).toHaveBeenCalled(); }); }); - +// NOTE: +// Offline submission depends on the same required Vuetify select fields +// as the successful submission flow. +// Since those fields cannot be reliably exercised via userEvent, +// this scenario cannot currently reach the submission state. +// This test will be re-enabled once Vuetify is removed . test.skip('shows an offline error when the user is offline', async () => { await renderComponent({ offline: true }); From 1a270193cbf2fc5cebc37d5ae708a35c3ba43da8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:10:16 +0000 Subject: [PATCH 03/10] [pre-commit.ci lite] apply automatic fixes --- .../accounts/pages/__tests__/create.spec.js | 76 ++++++++----------- 1 file changed, 30 insertions(+), 46 deletions(-) diff --git a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js index 3efb5ad77b..37db094c53 100644 --- a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js +++ b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js @@ -33,9 +33,7 @@ describe('Create account page', () => { test('smoke test: renders the create account page', async () => { await renderComponent(); - expect( - await screen.findByRole('heading', { name: /create an account/i }) - ).toBeInTheDocument(); + expect(await screen.findByRole('heading', { name: /create an account/i })).toBeInTheDocument(); }); test('shows validation state when submitting empty form', async () => { @@ -66,12 +64,8 @@ describe('Create account page', () => { test('allows user to check checkboxes', async () => { await renderComponent(); - const contentSourcesCheckbox = screen.getByLabelText( - /tagging content sources/i - ); - const tosCheckbox = screen.getByLabelText( - /i have read and agree to terms of service/i - ); + const contentSourcesCheckbox = screen.getByLabelText(/tagging content sources/i); + const tosCheckbox = screen.getByLabelText(/i have read and agree to terms of service/i); await userEvent.click(contentSourcesCheckbox); await userEvent.click(tosCheckbox); @@ -91,26 +85,24 @@ describe('Create account page', () => { expect(emailInput).toHaveValue('newtest@test.com'); }); }); -// NOTE: -// Full form submission tests are intentionally skipped here. -// -// This page still relies on Vuetify components (v-select / v-autocomplete) -// for required fields such as "locations" and "source". -// These components do not reliably update their 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 previous Vue Test Utils tests worked around this by directly mutating -// component data (setData), which is intentionally avoided when using -// Testing Library. -// -// These tests will be re-enabled once this page is migrated to the -// Kolibri Design System as part of the Vuetify removal effort . + // NOTE: + // Full form submission tests are intentionally skipped here. + // + // This page still relies on Vuetify components (v-select / v-autocomplete) + // for required fields such as "locations" and "source". + // These components do not reliably update their 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 previous Vue Test Utils tests worked around this by directly mutating + // component data (setData), which is intentionally avoided when using + // Testing Library. + // + // These tests will be re-enabled once this page is migrated to the + // Kolibri Design System as part of the Vuetify removal effort . test.skip('creates an account when the user submits valid information', async () => { - const registerSpy = jest - .spyOn(Create.methods, 'register') - .mockResolvedValue(); + const registerSpy = jest.spyOn(Create.methods, 'register').mockResolvedValue(); await renderComponent(); @@ -120,18 +112,14 @@ describe('Create account page', () => { 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(/tagging content sources/i)); - await userEvent.click( - screen.getByLabelText(/i have read and agree to terms of service/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 waitFor(() => { - expect(finishButton).not.toBeDisabled(); + expect(finishButton).toBeEnabled(); }); await userEvent.click(finishButton); @@ -140,12 +128,12 @@ describe('Create account page', () => { expect(registerSpy).toHaveBeenCalled(); }); }); -// NOTE: -// Offline submission depends on the same required Vuetify select fields -// as the successful submission flow. -// Since those fields cannot be reliably exercised via userEvent, -// this scenario cannot currently reach the submission state. -// This test will be re-enabled once Vuetify is removed . + // NOTE: + // Offline submission depends on the same required Vuetify select fields + // as the successful submission flow. + // Since those fields cannot be reliably exercised via userEvent, + // this scenario cannot currently reach the submission state. + // This test will be re-enabled once Vuetify is removed . test.skip('shows an offline error when the user is offline', async () => { await renderComponent({ offline: true }); @@ -155,13 +143,9 @@ describe('Create account page', () => { 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(/tagging content sources/i)); - await userEvent.click( - screen.getByLabelText(/i have read and agree to terms of service/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 a42449177f47e0ac5176a6532f3e801ca1d16b5e Mon Sep 17 00:00:00 2001 From: abhiraj75 Date: Wed, 14 Jan 2026 16:26:52 +0530 Subject: [PATCH 04/10] Trigger CI From 3dc33c46f0c021334210f8daf1cfd783c76c1f47 Mon Sep 17 00:00:00 2001 From: abhiraj75 Date: Wed, 14 Jan 2026 16:31:17 +0530 Subject: [PATCH 05/10] Trigger CI From 1149107c71c66b31d3d8d3eacb873aeb73bbf0a8 Mon Sep 17 00:00:00 2001 From: abhiraj75 Date: Wed, 21 Jan 2026 03:01:21 +0530 Subject: [PATCH 06/10] address review feedback and improve coverage parity --- .../accounts/pages/__tests__/create.spec.js | 104 ++++++++++++++++-- 1 file changed, 94 insertions(+), 10 deletions(-) diff --git a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js index 37db094c53..6bcf14941c 100644 --- a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js +++ b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js @@ -1,23 +1,57 @@ +import VueRouter from 'vue-router' import Vuex from 'vuex'; import { render, screen, waitFor } from '@testing-library/vue'; import userEvent from '@testing-library/user-event'; -import router from '../../router'; import Create from '../Create'; +// We use a stub for all routes except the one we are testing ('Create'). +// This ensures true unit isolation and prevents bugs in other pages from breaking this test. +const Stub = { template: '
' }; + +const routes = [ + { path: '/', component: Stub, name: 'Main' }, + { path: '/create', component: Create, name: 'Create' }, + { path: '/activation-sent', component: Stub }, + { path: '/account-created', component: Stub }, + { path: '/account-not-active', component: Stub }, + { path: '/activation-expired', component: Stub }, + { path: '/request-activation-link', component: Stub }, + { path: '/activation-resent', component: Stub }, + { path: '/forgot-password', component: Stub }, + { path: '/reset-password', component: Stub }, + { path: '/password-reset-sent', component: Stub }, + { path: '/password-reset-success', component: Stub }, + { path: '/reset-expired', component: Stub }, + { path: '/account-deleted', component: Stub }, + { path: '*', redirect: '/' }, +]; + +//Setup Mocks +const mockRegisterAction = jest.fn().mockResolvedValue({}); + const makeStore = ({ offline = false } = {}) => new Vuex.Store({ state: { connection: { - offline, + online: !offline, + }, + }, + modules: { + policies: { + namespaced: true, + getters: { + getPolicyAcceptedData: () => () => ({}), + }, }, }, + actions: { + register: mockRegisterAction, + }, }); const renderComponent = async ({ routeQuery = {}, offline = false } = {}) => { - if (router.currentRoute.path === '/create') { - await router.push('/').catch(() => {}); - } - + // Fresh router instance for every test + const router = new VueRouter({ routes }); await router.push({ name: 'Create', query: routeQuery }).catch(() => {}); return render(Create, { @@ -30,7 +64,10 @@ const renderComponent = async ({ routeQuery = {}, offline = false } = {}) => { }; describe('Create account page', () => { - test('smoke test: renders the create account page', async () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + test('renders the create account page', async () => { await renderComponent(); expect(await screen.findByRole('heading', { name: /create an account/i })).toBeInTheDocument(); @@ -42,7 +79,7 @@ describe('Create account page', () => { const finishButton = screen.getByRole('button', { name: /finish/i }); await userEvent.click(finishButton); - expect(finishButton).toBeDisabled(); + expect(mockRegisterAction).not.toHaveBeenCalled(); }); test('allows user to fill in text input fields', async () => { @@ -85,6 +122,30 @@ describe('Create account page', () => { expect(emailInput).toHaveValue('newtest@test.com'); }); }); + + describe('password validation', () => { + test('shows error when password is too short', async () => { + await renderComponent(); + const passwordInput = screen.getByLabelText(/^password$/i); + await userEvent.type(passwordInput, '123'); + await userEvent.tab(); + + expect( + await screen.findByText(/password should be at least 8 characters long/i) + ).toBeInTheDocument(); + }); + + test('shows error when passwords do not match', async () => { + await renderComponent(); + await userEvent.type(screen.getByLabelText(/^password$/i), 'tester123'); + await userEvent.type(screen.getByLabelText(/confirm password/i), 'different456'); + await userEvent.tab(); + + expect(await screen.findByText(/passwords don't match/i)).toBeInTheDocument(); + }); + }); + + // NOTE: // Full form submission tests are intentionally skipped here. // @@ -100,9 +161,32 @@ describe('Create account page', () => { // // These tests will be re-enabled once this page is migrated to the // Kolibri Design System as part of the Vuetify removal effort . + describe('backend failures', () => { + test.skip('shows specific error when email already exists (403)', async () => { + mockRegisterAction.mockRejectedValue({ response: { status: 403 } }); + await renderComponent(); + // fill form... + // click submit... + // expect(await screen.findByText(/account with this email already exists/i)).toBeInTheDocument(); + }); + test.skip('redirects if account not activated (405)', async () => { + mockRegisterAction.mockRejectedValue({ response: { status: 405 } }); + await renderComponent(); + // fill form... + // click submit... + // expect(router.currentRoute.name).toBe('AccountNotActivated'); + }); + + test.skip('shows generic error for other server errors', async () => { + mockRegisterAction.mockRejectedValue({ response: { status: 500 } }); + await renderComponent(); + // fill form... + // click submit... + // expect(await screen.findByText(/problem creating account/i)).toBeInTheDocument(); + }); + }); test.skip('creates an account when the user submits valid information', async () => { - const registerSpy = jest.spyOn(Create.methods, 'register').mockResolvedValue(); await renderComponent(); @@ -125,7 +209,7 @@ describe('Create account page', () => { await userEvent.click(finishButton); await waitFor(() => { - expect(registerSpy).toHaveBeenCalled(); + expect(mockRegisterAction).toHaveBeenCalled(); }); }); // NOTE: From f3fb474d13dcde259bfb6deea525f81ca3cd67ad Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:37:23 +0000 Subject: [PATCH 07/10] [pre-commit.ci lite] apply automatic fixes --- .../frontend/accounts/pages/__tests__/create.spec.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js index 6bcf14941c..a03f058de8 100644 --- a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js +++ b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js @@ -1,4 +1,4 @@ -import VueRouter from 'vue-router' +import VueRouter from 'vue-router'; import Vuex from 'vuex'; import { render, screen, waitFor } from '@testing-library/vue'; import userEvent from '@testing-library/user-event'; @@ -131,7 +131,7 @@ describe('Create account page', () => { await userEvent.tab(); expect( - await screen.findByText(/password should be at least 8 characters long/i) + await screen.findByText(/password should be at least 8 characters long/i), ).toBeInTheDocument(); }); @@ -144,7 +144,6 @@ describe('Create account page', () => { expect(await screen.findByText(/passwords don't match/i)).toBeInTheDocument(); }); }); - // NOTE: // Full form submission tests are intentionally skipped here. @@ -187,7 +186,6 @@ describe('Create account page', () => { }); }); test.skip('creates an account when the user submits valid information', async () => { - await renderComponent(); await userEvent.type(screen.getByLabelText(/first name/i), 'Test'); From e975df4acb598cbea56721fa6cdfe62081cc0d54 Mon Sep 17 00:00:00 2001 From: abhiraj75 Date: Wed, 21 Jan 2026 22:41:25 +0530 Subject: [PATCH 08/10] fixed linting --- .../frontend/accounts/pages/__tests__/create.spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js index a03f058de8..7644554af3 100644 --- a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js +++ b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js @@ -166,7 +166,8 @@ describe('Create account page', () => { await renderComponent(); // fill form... // click submit... - // expect(await screen.findByText(/account with this email already exists/i)).toBeInTheDocument(); + // expect(await screen.findByText(/account with this email already exists/i)) + // .toBeInTheDocument(); }); test.skip('redirects if account not activated (405)', async () => { From fbfe5ed5455f65df51c411412d7384e77774e980 Mon Sep 17 00:00:00 2001 From: Michaela Robosova Date: Mon, 26 Jan 2026 11:23:57 +0100 Subject: [PATCH 09/10] Cleanup unnecessary code --- .../accounts/pages/__tests__/create.spec.js | 109 +++--------------- 1 file changed, 18 insertions(+), 91 deletions(-) diff --git a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js index 7644554af3..b146181257 100644 --- a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js +++ b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js @@ -4,29 +4,24 @@ import { render, screen, waitFor } from '@testing-library/vue'; import userEvent from '@testing-library/user-event'; import Create from '../Create'; -// We use a stub for all routes except the one we are testing ('Create'). -// This ensures true unit isolation and prevents bugs in other pages from breaking this test. -const Stub = { template: '
' }; - const routes = [ - { path: '/', component: Stub, name: 'Main' }, - { path: '/create', component: Create, name: 'Create' }, - { path: '/activation-sent', component: Stub }, - { path: '/account-created', component: Stub }, - { path: '/account-not-active', component: Stub }, - { path: '/activation-expired', component: Stub }, - { path: '/request-activation-link', component: Stub }, - { path: '/activation-resent', component: Stub }, - { path: '/forgot-password', component: Stub }, - { path: '/reset-password', component: Stub }, - { path: '/password-reset-sent', component: Stub }, - { path: '/password-reset-success', component: Stub }, - { path: '/reset-expired', component: Stub }, - { path: '/account-deleted', component: Stub }, + { path: '/', name: 'Main' }, + { path: '/create', name: 'Create' }, + { path: '/activation-sent' }, + { path: '/account-created' }, + { path: '/account-not-active' }, + { path: '/activation-expired' }, + { path: '/request-activation-link' }, + { path: '/activation-resent' }, + { path: '/forgot-password' }, + { path: '/reset-password' }, + { path: '/password-reset-sent' }, + { path: '/password-reset-success' }, + { path: '/reset-expired' }, + { path: '/account-deleted' }, { path: '*', redirect: '/' }, ]; -//Setup Mocks const mockRegisterAction = jest.fn().mockResolvedValue({}); const makeStore = ({ offline = false } = {}) => @@ -36,21 +31,12 @@ const makeStore = ({ offline = false } = {}) => online: !offline, }, }, - modules: { - policies: { - namespaced: true, - getters: { - getPolicyAcceptedData: () => () => ({}), - }, - }, - }, actions: { register: mockRegisterAction, }, }); const renderComponent = async ({ routeQuery = {}, offline = false } = {}) => { - // Fresh router instance for every test const router = new VueRouter({ routes }); await router.push({ name: 'Create', query: routeQuery }).catch(() => {}); @@ -67,13 +53,13 @@ describe('Create account page', () => { beforeEach(() => { jest.clearAllMocks(); }); - test('renders the create account page', async () => { + test('renders the page', async () => { await renderComponent(); expect(await screen.findByRole('heading', { name: /create an account/i })).toBeInTheDocument(); }); - test('shows validation state when submitting empty form', async () => { + test("doesn't call register action when fields are empty", async () => { await renderComponent(); const finishButton = screen.getByRole('button', { name: /finish/i }); @@ -82,35 +68,6 @@ describe('Create account page', () => { expect(mockRegisterAction).not.toHaveBeenCalled(); }); - test('allows user to fill in text input fields', 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'); - - expect(screen.getByLabelText(/first name/i)).toHaveValue('Test'); - expect(screen.getByLabelText(/last name/i)).toHaveValue('User'); - expect(screen.getByLabelText(/email/i)).toHaveValue('test@test.com'); - expect(screen.getByLabelText(/^password$/i)).toHaveValue('tester123'); - expect(screen.getByLabelText(/confirm password/i)).toHaveValue('tester123'); - }); - - test('allows user to check checkboxes', async () => { - await renderComponent(); - - const contentSourcesCheckbox = screen.getByLabelText(/tagging content sources/i); - const tosCheckbox = screen.getByLabelText(/i have read and agree to terms of service/i); - - await userEvent.click(contentSourcesCheckbox); - await userEvent.click(tosCheckbox); - - expect(contentSourcesCheckbox).toBeChecked(); - expect(tosCheckbox).toBeChecked(); - }); - test('automatically fills the email field when provided in the URL', async () => { await renderComponent({ routeQuery: { email: 'newtest@test.com' }, @@ -160,32 +117,6 @@ describe('Create account page', () => { // // These tests will be re-enabled once this page is migrated to the // Kolibri Design System as part of the Vuetify removal effort . - describe('backend failures', () => { - test.skip('shows specific error when email already exists (403)', async () => { - mockRegisterAction.mockRejectedValue({ response: { status: 403 } }); - await renderComponent(); - // fill form... - // click submit... - // expect(await screen.findByText(/account with this email already exists/i)) - // .toBeInTheDocument(); - }); - - test.skip('redirects if account not activated (405)', async () => { - mockRegisterAction.mockRejectedValue({ response: { status: 405 } }); - await renderComponent(); - // fill form... - // click submit... - // expect(router.currentRoute.name).toBe('AccountNotActivated'); - }); - - test.skip('shows generic error for other server errors', async () => { - mockRegisterAction.mockRejectedValue({ response: { status: 500 } }); - await renderComponent(); - // fill form... - // click submit... - // expect(await screen.findByText(/problem creating account/i)).toBeInTheDocument(); - }); - }); test.skip('creates an account when the user submits valid information', async () => { await renderComponent(); @@ -211,12 +142,8 @@ describe('Create account page', () => { expect(mockRegisterAction).toHaveBeenCalled(); }); }); - // NOTE: - // Offline submission depends on the same required Vuetify select fields - // as the successful submission flow. - // Since those fields cannot be reliably exercised via userEvent, - // this scenario cannot currently reach the submission state. - // This test will be re-enabled once Vuetify is removed . + + // Skipped for the same reason as above test.skip('shows an offline error when the user is offline', async () => { await renderComponent({ offline: true }); From 95e81d61c840d3ff959ed3a66cd8a4ae2bbe5695 Mon Sep 17 00:00:00 2001 From: Michaela Robosova Date: Mon, 26 Jan 2026 11:24:05 +0100 Subject: [PATCH 10/10] Use our test namig convention --- .../accounts/pages/__tests__/create.spec.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js index b146181257..591c26fb3c 100644 --- a/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js +++ b/contentcuration/contentcuration/frontend/accounts/pages/__tests__/create.spec.js @@ -53,13 +53,13 @@ describe('Create account page', () => { beforeEach(() => { jest.clearAllMocks(); }); - test('renders the page', async () => { + it('renders the page', async () => { await renderComponent(); expect(await screen.findByRole('heading', { name: /create an account/i })).toBeInTheDocument(); }); - test("doesn't call register action when fields are empty", async () => { + it("doesn't call register action when fields are empty", async () => { await renderComponent(); const finishButton = screen.getByRole('button', { name: /finish/i }); @@ -68,7 +68,7 @@ describe('Create account page', () => { expect(mockRegisterAction).not.toHaveBeenCalled(); }); - test('automatically fills the email field when provided in the URL', async () => { + it('automatically fills the email field when provided in the URL', async () => { await renderComponent({ routeQuery: { email: 'newtest@test.com' }, }); @@ -81,7 +81,7 @@ describe('Create account page', () => { }); describe('password validation', () => { - test('shows error when password is too short', async () => { + it('shows error when password is too short', async () => { await renderComponent(); const passwordInput = screen.getByLabelText(/^password$/i); await userEvent.type(passwordInput, '123'); @@ -92,7 +92,7 @@ describe('Create account page', () => { ).toBeInTheDocument(); }); - test('shows error when passwords do not match', async () => { + it('shows error when passwords do not match', async () => { await renderComponent(); await userEvent.type(screen.getByLabelText(/^password$/i), 'tester123'); await userEvent.type(screen.getByLabelText(/confirm password/i), 'different456'); @@ -117,7 +117,7 @@ describe('Create account page', () => { // // These tests will be re-enabled once this page is migrated to the // Kolibri Design System as part of the Vuetify removal effort . - test.skip('creates an account when the user submits valid information', async () => { + it.skip('creates an account when the user submits valid information', async () => { await renderComponent(); await userEvent.type(screen.getByLabelText(/first name/i), 'Test'); @@ -144,7 +144,7 @@ describe('Create account page', () => { }); // Skipped for the same reason as above - test.skip('shows an offline error when the user is offline', async () => { + it.skip('shows an offline error when the user is offline', async () => { await renderComponent({ offline: true }); await userEvent.type(screen.getByLabelText(/first name/i), 'Test');