diff --git a/kolibri/core/assets/src/views/sync/__tests__/ConfirmationRegisterModal.spec.js b/kolibri/core/assets/src/views/sync/__tests__/ConfirmationRegisterModal.spec.js new file mode 100644 index 00000000000..c6d209135e6 --- /dev/null +++ b/kolibri/core/assets/src/views/sync/__tests__/ConfirmationRegisterModal.spec.js @@ -0,0 +1,152 @@ +import { render, screen, fireEvent, waitFor } from '@testing-library/vue'; +import VueRouter from 'vue-router'; +import { PortalResource, FacilityDatasetResource } from 'kolibri.resources'; +import { ERROR_CONSTANTS } from 'kolibri.coreVue.vuex.constants'; +import ConfirmationRegisterModal from '../ConfirmationRegisterModal.vue'; + +const sampleProjectName = 'Test Project'; +const sampleFacility = { + id: 'facility-id', + name: 'Facility Name', + dataset: { id: 'dataset-id' }, +}; +const sampleToken = 'test token'; + +const renderComponent = props => { + return render(ConfirmationRegisterModal, { + props: { + projectName: sampleProjectName, + targetFacility: sampleFacility, + token: sampleToken, + ...props, + }, + routes: new VueRouter(), + }); +}; + +// Mock necessary resources and modules +jest.mock('kolibri.resources', () => ({ + PortalResource: { + registerFacility: jest.fn(() => Promise.resolve()), + }, + FacilityDatasetResource: { + saveModel: jest.fn(() => Promise.resolve()), + }, +})); + +describe('ConfirmationRegisterModal', () => { + it('renders with correct texts in the modal', async () => { + renderComponent({ projectName: sampleProjectName }); + + // Checking the text content of the modal + expect(screen.getByText(`Register with '${sampleProjectName}'?`)).toBeInTheDocument(); + expect(screen.getByText('Data will be saved to the cloud')).toBeInTheDocument(); + + // Checking the content on the buttons + expect(screen.getByRole('button', { name: 'Register' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument(); + }); + + it("emits the cancel event when 'Cancel' button is clicked without registering", async () => { + const { emitted } = renderComponent(); + + // Clicking the 'Cancel' button + await fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); + expect(emitted()).toHaveProperty('cancel'); + expect(emitted().cancel).toHaveLength(1); + }); + + describe("when 'Register' button is clicked to register the faculty successfully", () => { + it('emits the success event with sample facility as argument', async () => { + const { emitted } = renderComponent({ + projectName: sampleProjectName, + targetFacility: sampleFacility, + }); + await fireEvent.click(screen.getByRole('button', { name: 'Register' })); + + expect(emitted()).toHaveProperty('success'); + expect(emitted().success).toHaveLength(1); + expect(emitted().success[0]).toEqual([sampleFacility]); + }); + + it('calls the necessary resources to register the facility', async () => { + renderComponent({ + projectName: sampleProjectName, + targetFacility: sampleFacility, + token: sampleToken, + }); + await fireEvent.click(screen.getByRole('button', { name: 'Register' })); + + expect(PortalResource.registerFacility).toHaveBeenCalledWith({ + facility_id: sampleFacility.id, + name: sampleFacility.name, + token: sampleToken, + }); + expect(FacilityDatasetResource.saveModel).toHaveBeenCalledWith({ + id: sampleFacility.dataset.id, + data: { registered: true }, + exists: true, + }); + }); + }); + + describe('when the facility is already registered with the project', () => { + beforeEach(() => { + // Mock the API call to return an error response + // showing that the facility is already registered + PortalResource.registerFacility.mockRejectedValue({ + response: { + data: [{ id: ERROR_CONSTANTS.ALREADY_REGISTERED_FOR_COMMUNITY }], + }, + }); + }); + + it('renders with correct text in the body of the modal', async () => { + renderComponent({ projectName: sampleProjectName }); + await fireEvent.click(screen.getByRole('button', { name: 'Register' })); + + await waitFor(() => + expect( + screen.getByText(`Already registered with '${sampleProjectName}'`) + ).toBeInTheDocument() + ); + }); + + it('the buttons show the appropiate texts', async () => { + renderComponent({ projectName: sampleProjectName }); + await fireEvent.click(screen.getByRole('button', { name: 'Register' })); + + await waitFor(() => { + expect(screen.getByRole('button', { name: 'Close' })).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'Register' })).not.toBeInTheDocument(); + }); + }); + + it('emits success event with sample facility as argument when Close button is clicked if successOnAlreadyRegistered is set as true', async () => { + const { emitted } = renderComponent({ + successOnAlreadyRegistered: true, + targetFacility: sampleFacility, + }); + await fireEvent.click(screen.getByRole('button', { name: 'Register' })); + + await waitFor(async () => { + await fireEvent.click(screen.getByRole('button', { name: 'Close' })); + + expect(emitted()).toHaveProperty('success'); + expect(emitted().success).toHaveLength(1); + expect(emitted().success[0]).toEqual([sampleFacility]); + }); + }); + + it("does not emit success event when 'Close' button is clicked if successOnAlreadyRegistered is not set", async () => { + const { emitted } = renderComponent(); + await fireEvent.click(screen.getByRole('button', { name: 'Register' })); + + await waitFor(async () => { + await fireEvent.click(screen.getByRole('button', { name: 'Close' })); + + expect(emitted()).not.toHaveProperty('success'); + }); + }); + }); +}); diff --git a/kolibri/core/assets/src/views/sync/__tests__/SelectSourceModal.spec.js b/kolibri/core/assets/src/views/sync/__tests__/SelectSourceModal.spec.js new file mode 100644 index 00000000000..d045e1fe6ee --- /dev/null +++ b/kolibri/core/assets/src/views/sync/__tests__/SelectSourceModal.spec.js @@ -0,0 +1,46 @@ +import { render, fireEvent, screen } from '@testing-library/vue'; +import VueRouter from 'vue-router'; +import SelectSourceModal from '../SelectSourceModal.vue'; + +const renderComponent = props => { + return render(SelectSourceModal, { + props, + routes: new VueRouter(), + }); +}; + +describe('SelectSourceModal', () => { + it('renders the correct default body and button labels', async () => { + renderComponent(); + + expect(screen.getByText('Select a source')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Continue' })).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument(); + }); + + it('clicking the Continue button emits the submit event', async () => { + const { emitted } = renderComponent(); + await fireEvent.click(screen.getByRole('button', { name: 'Continue' })); + + expect(emitted()).toHaveProperty('submit'); + expect(emitted().submit).toHaveLength(1); + }); + + it('clicking the Cancel button emits the cancel event', async () => { + const { emitted } = renderComponent(); + await fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); + + expect(emitted()).toHaveProperty('cancel'); + expect(emitted().cancel).toHaveLength(1); + }); + + it('displays the loading message when showLoadingMessage is true', async () => { + renderComponent({ showLoadingMessage: true }); + expect(screen.getByText('Loading connections…')).toBeInTheDocument(); + }); + + it('the submit button is disabled when the submitDisabled prop is true', async () => { + renderComponent({ submitDisabled: true }); + expect(screen.getByRole('button', { name: 'Continue' })).toBeDisabled(); + }); +});