diff --git a/src/core/adapters/localSyncStorage/parser/localSyncObjectSchema.test.ts b/src/core/adapters/localSyncStorage/parser/localSyncObjectSchema.test.ts new file mode 100644 index 00000000..791df49b --- /dev/null +++ b/src/core/adapters/localSyncStorage/parser/localSyncObjectSchema.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from 'vitest' +import { localStorageObjectSchema } from './localSyncObjectSchema' + +describe('localStorageObjectSchema', () => { + it('should validate a valid object', () => { + const validData = { + error: false, + surveyUnitsSuccess: ['unit1', 'unit2'], + surveyUnitsInTempZone: ['unit3', 'unit4'], + } + + const result = localStorageObjectSchema.safeParse(validData) + expect(result.success).toBe(true) + expect(result.success && result.data).toEqual(validData) + }) + + it('should fail validation when "error" is not a boolean', () => { + const invalidData = { + error: 'notABoolean', + surveyUnitsSuccess: ['unit1'], + surveyUnitsInTempZone: ['unit2'], + } + + const result = localStorageObjectSchema.safeParse(invalidData) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Expected boolean') + } + }) + + it('should fail validation when "surveyUnitsSuccess" is not an array of strings', () => { + const invalidData = { + error: true, + surveyUnitsSuccess: [123], + surveyUnitsInTempZone: ['unit2'], + } + + const result = localStorageObjectSchema.safeParse(invalidData) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Expected string') + } + }) + + it('should fail validation when "surveyUnitsInTempZone" is missing', () => { + const invalidData = { + error: false, + surveyUnitsSuccess: ['unit1', 'unit2'], + } + + const result = localStorageObjectSchema.safeParse(invalidData) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Required') + } + }) +}) diff --git a/src/core/adapters/queenApi/parserSchema/campaignSchema.test.ts b/src/core/adapters/queenApi/parserSchema/campaignSchema.test.ts new file mode 100644 index 00000000..e65703fc --- /dev/null +++ b/src/core/adapters/queenApi/parserSchema/campaignSchema.test.ts @@ -0,0 +1,52 @@ +import { describe, expect, it } from 'vitest' +import { campaignSchema } from './campaignSchema' + +describe('campaignSchema', () => { + it('should validate a valid campaign object', () => { + const validData = { + id: 'campaign1', + questionnaireIds: ['q1', 'q2', 'q3'], + } + + const result = campaignSchema.safeParse(validData) + expect(result.success).toBe(true) + expect(result.success && result.data).toEqual(validData) + }) + + it('should fail validation when "id" is missing', () => { + const invalidData = { + questionnaireIds: ['q1', 'q2'], + } + + const result = campaignSchema.safeParse(invalidData) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Required') + } + }) + + it('should fail validation when "questionnaireIds" is not an array of strings', () => { + const invalidData = { + id: 'campaign2', + questionnaireIds: [123, 'q2'], + } + + const result = campaignSchema.safeParse(invalidData) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Expected string') + } + }) + + it('should fail validation when "questionnaireIds" is missing', () => { + const invalidData = { + id: 'campaign3', + } + + const result = campaignSchema.safeParse(invalidData) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Required') + } + }) +}) diff --git a/src/core/adapters/queenApi/parserSchema/nomenclatureSchema.test.ts b/src/core/adapters/queenApi/parserSchema/nomenclatureSchema.test.ts new file mode 100644 index 00000000..8f51d170 --- /dev/null +++ b/src/core/adapters/queenApi/parserSchema/nomenclatureSchema.test.ts @@ -0,0 +1,84 @@ +import { describe, it, expect } from 'vitest' +import { + nomenclatureSchema, + requiredNomenclaturesSchema, +} from './nomenclatureSchema' + +describe('nomenclatureSchema', () => { + it('should validate a valid array of nomenclature objects', () => { + const validData = [ + { id: 'n1', label: 'Label 1', extraField: 'extraValue1' }, + { id: 'n2', label: 'Label 2', anotherExtra: 'extraValue2' }, + ] + + const result = nomenclatureSchema.safeParse(validData) + expect(result.success).toBe(true) + expect(result.success && result.data).toEqual(validData) + }) + + it('should fail validation when "id" is missing in one object', () => { + const invalidData = [ + { label: 'Label 1', extraField: 'extraValue1' }, + { id: 'n2', label: 'Label 2' }, + ] + + const result = nomenclatureSchema.safeParse(invalidData) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Required') + } + }) + + it('should fail validation when "label" is missing in one object', () => { + const invalidData = [ + { id: 'n1', extraField: 'extraValue1' }, + { id: 'n2', label: 'Label 2' }, + ] + + const result = nomenclatureSchema.safeParse(invalidData) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Required') + } + }) + + it('should fail validation when an object is not valid', () => { + const invalidData = [{ id: 'n1', label: 123 }] + + const result = nomenclatureSchema.safeParse(invalidData) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Expected string') + } + }) +}) + +describe('requiredNomenclaturesSchema', () => { + it('should validate a valid array of strings', () => { + const validData = ['n1', 'n2', 'n3'] + + const result = requiredNomenclaturesSchema.safeParse(validData) + expect(result.success).toBe(true) + expect(result.success && result.data).toEqual(validData) + }) + + it('should fail validation when an element is not a string', () => { + const invalidData = ['n1', 123, 'n3'] + + const result = requiredNomenclaturesSchema.safeParse(invalidData) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Expected string') + } + }) + + it('should fail validation when the value is not an array', () => { + const invalidData = 'notAnArray' + + const result = requiredNomenclaturesSchema.safeParse(invalidData) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Expected array') + } + }) +}) diff --git a/src/core/adapters/queenApi/parserSchema/paradataSchema.test.ts b/src/core/adapters/queenApi/parserSchema/paradataSchema.test.ts new file mode 100644 index 00000000..7deb02d4 --- /dev/null +++ b/src/core/adapters/queenApi/parserSchema/paradataSchema.test.ts @@ -0,0 +1,134 @@ +import { describe, expect, it } from 'vitest' +import { paradataSchema } from './paradataSchema' + +describe('eventSchema', () => { + const eventSchema = paradataSchema.shape.event.element + const validEvent = { + type: 'click', + timestamp: 1634567890, + userAgent: 'Mozilla/5.0', + idSurveyUnit: 'su123', + idOrchestrator: 'orchestrator-collect', + idQuestionnaire: 'q123', + idParadataObject: 'po123', + typeParadataObject: 'orchestrator', + page: null, + } + + it('should validate a valid event object', () => { + const result = eventSchema.safeParse(validEvent) + expect(result.success).toBe(true) + expect(result.success && result.data).toEqual(validEvent) + }) + + it('should fail validation for invalid "type"', () => { + const invalidEvent = { + ...validEvent, + type: 'invalid-type', + } + + const result = eventSchema.safeParse(invalidEvent) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain( + "Invalid enum value. Expected 'click' | 'session-started' | 'orchestrator-create', received 'invalid-type'" + ) + } + }) + + it('should fail validation for negative timestamp', () => { + const invalidEvent = { + ...validEvent, + timestamp: -1, + } + + const result = eventSchema.safeParse(invalidEvent) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain( + 'Number must be greater than or equal to 0' + ) + } + }) + + it('should fail validation for missing required properties', () => { + const invalidEvent = { + type: 'click', + timestamp: 1634567890, + userAgent: 'Mozilla/5.0', + } + + const result = eventSchema.safeParse(invalidEvent) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Required') + } + }) +}) + +describe('paradataSchema', () => { + it('should validate a valid paradata object', () => { + const validParadata = { + idSu: 'su123', + event: [ + { + type: 'click', + timestamp: 1634567890, + userAgent: 'Mozilla/5.0', + idSurveyUnit: 'su123', + idOrchestrator: 'orchestrator-collect', + idQuestionnaire: 'q123', + idParadataObject: 'po123', + typeParadataObject: 'orchestrator', + page: null, + }, + ], + } + + const result = paradataSchema.safeParse(validParadata) + expect(result.success).toBe(true) + expect(result.success && result.data).toEqual(validParadata) + }) + + it('should fail validation for missing "idSu"', () => { + const invalidParadata = { + event: [ + { + type: 'click', + timestamp: 1634567890, + userAgent: 'Mozilla/5.0', + idSurveyUnit: 'su123', + idOrchestrator: 'orchestrator-collect', + idQuestionnaire: 'q123', + idParadataObject: 'po123', + typeParadataObject: 'orchestrator', + page: null, + }, + ], + } + + const result = paradataSchema.safeParse(invalidParadata) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Required') + } + }) + + it('should fail validation for invalid event array', () => { + const invalidParadata = { + idSu: 'su123', + event: [ + { + type: 'invalid-type', + timestamp: -1, + }, + ], + } + + const result = paradataSchema.safeParse(invalidParadata) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Invalid enum value') + } + }) +}) diff --git a/src/core/adapters/queenApi/parserSchema/surveyUnitDataSchema.test.ts b/src/core/adapters/queenApi/parserSchema/surveyUnitDataSchema.test.ts new file mode 100644 index 00000000..1f3a30f8 --- /dev/null +++ b/src/core/adapters/queenApi/parserSchema/surveyUnitDataSchema.test.ts @@ -0,0 +1,101 @@ +import { describe, it, expect } from 'vitest' +import { surveyUnitDataSchema } from './surveyUnitDataSchema' + +describe('surveyUnitDataSchema', () => { + it('should validate a correct structure', () => { + const validData = { + CALCULATED: { + variable1: 'stringValue', + variable2: 123, + variable3: [null, 456, 'anotherValue'], + }, + EXTERNAL: { + variable1: true, + variable2: [false, null, 'arrayValue'], + }, + COLLECTED: { + variable1: { + COLLECTED: 'value', + EDITED: [null, 'string', true], + FORCED: null, + INPUTED: [123, 456], + PREVIOUS: null, + }, + }, + } + + const result = surveyUnitDataSchema.safeParse(validData) + + expect(result.success).toBe(true) + expect(result.data).toEqual(validData) + }) + + it('should reject invalid CALCULATED field', () => { + const invalidData = { + CALCULATED: { + variable1: { notAllowed: 'object' }, + }, + EXTERNAL: {}, + COLLECTED: {}, + } + + const result = surveyUnitDataSchema.safeParse(invalidData) + + expect(result.success).toBe(false) + expect(result.error).toBeDefined() + }) + + it('should reject invalid EXTERNAL field', () => { + const invalidData = { + CALCULATED: {}, + EXTERNAL: { + invalidKey: undefined, + }, + COLLECTED: {}, + } + + const result = surveyUnitDataSchema.safeParse(invalidData) + + expect(result.success).toBe(false) + expect(result.error).toBeDefined() + }) + + it('should reject invalid COLLECTED field', () => { + const invalidData = { + CALCULATED: {}, + EXTERNAL: {}, + COLLECTED: { + invalidKey: { + COLLECTED: () => {}, + }, + }, + } + + const result = surveyUnitDataSchema.safeParse(invalidData) + + expect(result.success).toBe(false) + expect(result.error).toBeDefined() + }) + + it('should allow partial data', () => { + const partialData = { + CALCULATED: { + variable1: 'partialValue', + }, + } + + const result = surveyUnitDataSchema.safeParse(partialData) + + expect(result.success).toBe(true) + expect(result.data).toEqual(partialData) + }) + + it('should allow null or empty objects', () => { + const emptyData = {} + + const result = surveyUnitDataSchema.safeParse(emptyData) + + expect(result.success).toBe(true) + expect(result.data).toEqual(emptyData) + }) +}) diff --git a/src/core/adapters/queenApi/parserSchema/surveyUnitSchema.test.ts b/src/core/adapters/queenApi/parserSchema/surveyUnitSchema.test.ts new file mode 100644 index 00000000..56ef0b6f --- /dev/null +++ b/src/core/adapters/queenApi/parserSchema/surveyUnitSchema.test.ts @@ -0,0 +1,118 @@ +import { describe, expect, it } from 'vitest' +import { + idAndQuestionnaireIdSchema, + surveyUnitSchema, +} from './surveyUnitSchema' + +describe('idAndQuestionnaireIdSchema', () => { + it('should validate a valid id and questionnaireId', () => { + const validData = { + id: '12345', + questionnaireId: '67890', + } + + const result = idAndQuestionnaireIdSchema.safeParse(validData) + + expect(result.success).toBe(true) + expect(result.data).toEqual(validData) + }) + + it('should reject invalid id or questionnaireId', () => { + const invalidData = { + id: 12345, + questionnaireId: null, + } + + const result = idAndQuestionnaireIdSchema.safeParse(invalidData) + + expect(result.success).toBe(false) + expect(result.error).toBeDefined() + }) +}) + +describe('surveyUnitSchema', () => { + it('should validate a valid survey unit object', () => { + const validData = { + id: '12345', + questionnaireId: '67890', + personalization: [ + { name: 'age', value: '30' }, + { name: 'gender', value: 'male' }, + ], + data: { + CALCULATED: { variable1: 'value1' }, + EXTERNAL: { variable2: 42 }, + COLLECTED: { + collectedKey: { + COLLECTED: 'value', + EDITED: null, + }, + }, + }, + comment: {}, + stateData: { + state: 'VALIDATED', + date: 1633036800, + currentPage: '12.3#4', + }, + } + + const result = surveyUnitSchema.safeParse(validData) + + expect(result.success).toBe(true) + expect(result.data).toEqual(validData) + }) + + it('should reject invalid survey unit objects', () => { + const invalidData = { + id: 12345, + questionnaireId: '67890', + personalization: 'not-an-array', + data: {}, + } + + const result = surveyUnitSchema.safeParse(invalidData) + + expect(result.success).toBe(false) + expect(result.error).toBeDefined() + }) + + it('should allow optional fields to be omitted', () => { + const partialData = { + id: '12345', + questionnaireId: '67890', + data: { + CALCULATED: {}, + EXTERNAL: {}, + COLLECTED: {}, + }, + } + + const result = surveyUnitSchema.safeParse(partialData) + + expect(result.success).toBe(true) + expect(result.data).toEqual(partialData) + }) + + it('should reject invalid stateData format', () => { + const invalidData = { + id: '12345', + questionnaireId: '67890', + data: { + CALCULATED: {}, + EXTERNAL: {}, + COLLECTED: {}, + }, + stateData: { + state: 'INVALID_STATE', + date: 1633036800, + currentPage: 'invalidPageFormat', + }, + } + + const result = surveyUnitSchema.safeParse(invalidData) + + expect(result.success).toBe(false) + expect(result.error).toBeDefined() + }) +}) diff --git a/src/core/tools/axiosError.test.ts b/src/core/tools/axiosError.test.ts new file mode 100644 index 00000000..e363b8a4 --- /dev/null +++ b/src/core/tools/axiosError.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect, vi } from 'vitest' +import { handleAxiosError } from './axiosError' +import { AxiosError } from 'axios' + +vi.mock('i18n', () => ({ + getTranslation: () => ({ t: (keyMessage: string) => keyMessage }), +})) + +describe('handleAxiosError', () => { + it('should return a custom message for no response', () => { + const error: AxiosError = { + message: '', + name: '', + config: {}, + isAxiosError: true, + toJSON: () => ({}), + response: undefined, + } as AxiosError + + const result = handleAxiosError(error) + expect(result.message).toBe( + "Une erreur s'est produite lors du traitement de la requête. Veuillez réessayer plus tard." + ) + }) + + it('should return the correct message for status 400', () => { + const error: AxiosError = { + response: { status: 400 } as any, + message: '', + name: '', + config: {}, + isAxiosError: true, + toJSON: () => ({}), + } as AxiosError + + const result = handleAxiosError(error) + expect(result.message).toBe('400') + }) + + it('should return the correct message for status 401', () => { + const error: AxiosError = { + response: { status: 401 } as any, + message: '', + name: '', + config: {}, + isAxiosError: true, + toJSON: () => ({}), + } as AxiosError + + const result = handleAxiosError(error) + expect(result.message).toBe('401') + }) + + it('should return the correct message for unknown status', () => { + const error: AxiosError = { + response: { status: 999 } as any, + message: '', + name: '', + config: {}, + isAxiosError: true, + toJSON: () => ({}), + } as AxiosError + + const result = handleAxiosError(error) + expect(result.message).toBe('longUnknownError') + }) +}) diff --git a/src/core/tools/fetchUrl.test.ts b/src/core/tools/fetchUrl.test.ts new file mode 100644 index 00000000..cdfdafac --- /dev/null +++ b/src/core/tools/fetchUrl.test.ts @@ -0,0 +1,35 @@ +import { vi, describe, beforeEach, it, expect } from 'vitest' +import axios from 'axios' +import { fetchUrl } from './fetchUrl' + +vi.mock('axios') + +describe('fetchUrl', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should fetch data from the provided URL and return the response data', async () => { + const mockUrl = 'https://example.com/api/data' + const mockResponseData = { key: 'value' } + + vi.mocked(axios.get).mockResolvedValueOnce({ data: mockResponseData }) + + const result = await fetchUrl<{ key: string }>({ url: mockUrl }) + + expect(axios.get).toHaveBeenCalledWith(decodeURIComponent(mockUrl)) + + expect(result).toEqual(mockResponseData) + }) + + it('should throw an error if the request fails', async () => { + const mockUrl = 'https://example.com/api/error' + const mockError = new Error('Request failed') + + vi.mocked(axios.get).mockRejectedValueOnce(mockError) + + await expect(fetchUrl({ url: mockUrl })).rejects.toThrow(mockError) + + expect(axios.get).toHaveBeenCalledWith(decodeURIComponent(mockUrl)) + }) +}) diff --git a/src/core/tools/fetchUrl.ts b/src/core/tools/fetchUrl.ts index b1b24032..aaec96c3 100644 --- a/src/core/tools/fetchUrl.ts +++ b/src/core/tools/fetchUrl.ts @@ -1,10 +1,5 @@ import axios from 'axios' -/** - * - * @param params: { url : string} - * @returns {Promise} fetched data of type T. - */ export async function fetchUrl(params: { url: string }): Promise { const { url } = params return axios.get(decodeURIComponent(url)).then(({ data }) => data) diff --git a/src/core/tools/makeSearchParamsObjectSchema.test.ts b/src/core/tools/makeSearchParamsObjectSchema.test.ts new file mode 100644 index 00000000..932f6d8b --- /dev/null +++ b/src/core/tools/makeSearchParamsObjectSchema.test.ts @@ -0,0 +1,103 @@ +import { describe, expect, it } from 'vitest' +import { z } from 'zod' +import { makeSearchParamsObjSchema } from './makeSearchParamsObjectSchema' + +describe('makeSearchParamsObjSchema', () => { + it('should correctly parse and validate valid URLSearchParams', () => { + const schema = makeSearchParamsObjSchema( + z.object({ + key1: z.string(), + key2: z.number(), + key3: z.boolean(), + }) + ) + + const searchParams = new URLSearchParams() + searchParams.append('key1', 'value1') + searchParams.append('key2', '42') + searchParams.append('key3', 'true') + + const result = schema.safeParse(searchParams) + + expect(result.success).toBe(true) + expect(result.data).toEqual({ + key1: 'value1', + key2: 42, + key3: true, + }) + }) + + it('should handle arrays for repeated keys', () => { + const schema = makeSearchParamsObjSchema( + z.object({ + key1: z.string().array(), + key2: z.string(), + }) + ) + + const searchParams = new URLSearchParams() + searchParams.append('key1', 'value1') + searchParams.append('key1', 'value2') + searchParams.append('key2', 'value3') + + const result = schema.safeParse(searchParams) + + expect(result.success).toBe(true) + expect(result.data).toEqual({ + key1: ['value1', 'value2'], + key2: 'value3', + }) + }) + + it('should fail validation when the data does not match the schema', () => { + const schema = makeSearchParamsObjSchema( + z.object({ + key1: z.string(), + key2: z.number(), + }) + ) + + const searchParams = new URLSearchParams() + searchParams.append('key1', 'value1') + searchParams.append('key2', 'notANumber') + + const result = schema.safeParse(searchParams) + + expect(result.success).toBe(false) + expect(result.error).toBeDefined() + }) + + it('should handle partial schemas', () => { + const schema = makeSearchParamsObjSchema( + z.object({ + key1: z.string(), + key2: z.number().optional(), + }) + ) + + const searchParams = new URLSearchParams() + searchParams.append('key1', 'value1') + + const result = schema.safeParse(searchParams) + + expect(result.success).toBe(true) + expect(result.data).toEqual({ + key1: 'value1', + }) + }) + + it('should handle empty URLSearchParams', () => { + const schema = makeSearchParamsObjSchema( + z.object({ + key1: z.string().optional(), + }) + ) + + const searchParams = new URLSearchParams() + + const result = schema.safeParse(searchParams) + + expect(result.success).toBe(true) + expect(result.data).toEqual({}) + }) +}) diff --git a/src/core/usecases/collectSurvey/eventSender.test.ts b/src/core/usecases/collectSurvey/eventSender.test.ts new file mode 100644 index 00000000..4e28fe5a --- /dev/null +++ b/src/core/usecases/collectSurvey/eventSender.test.ts @@ -0,0 +1,52 @@ +import { vi, describe, beforeEach, it, expect } from 'vitest' +import { + sendQuestionnaireStateChangedEvent, + sendCloseEvent, +} from './eventSender' +import type { EventQuestionnaireState } from 'core/model/QuestionnaireState' + +describe('Event Dispatchers', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should dispatch a "QUEEN" event for questionnaire state change', () => { + const surveyUnitId = 'unit1' + const state: EventQuestionnaireState = 'COMPLETED' + + const dispatchEventMock = vi.spyOn(window, 'dispatchEvent') + + sendQuestionnaireStateChangedEvent(surveyUnitId, state) + + expect(dispatchEventMock).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'QUEEN', + detail: { + type: 'QUEEN', + command: 'UPDATE_SURVEY_UNIT', + surveyUnit: surveyUnitId, + state: state, + }, + }) + ) + }) + + it('should dispatch a "QUEEN" event for closing the survey', () => { + const surveyUnitId = 'unit2' + + const dispatchEventMock = vi.spyOn(window, 'dispatchEvent') + + sendCloseEvent(surveyUnitId) + + expect(dispatchEventMock).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'QUEEN', + detail: { + type: 'QUEEN', + command: 'CLOSE_QUEEN', + surveyUnit: surveyUnitId, + }, + }) + ) + }) +}) diff --git a/src/core/usecases/userAuthentication.test.ts b/src/core/usecases/userAuthentication.test.ts new file mode 100644 index 00000000..b5291cae --- /dev/null +++ b/src/core/usecases/userAuthentication.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect, vi } from 'vitest' +import { thunks } from './userAuthentication' +import type { Oidc } from 'oidc-spa' + +describe('userAuthentication thunks', () => { + describe('loginIfNotLoggedIn', () => { + it('should do nothing if the user is already logged in', async () => { + const mockGetOidc = vi.fn(() => + Promise.resolve({ + isUserLoggedIn: true, + login: vi.fn(), + } as unknown as Oidc) + ) + + await thunks.loginIfNotLoggedIn()( + null as any, + null as any, + { + getOidc: mockGetOidc, + } as any + ) + + expect(mockGetOidc).toHaveBeenCalled() + expect( + (await mockGetOidc.mock.results[0].value).login + ).not.toHaveBeenCalled() + }) + + it('should call login if the user is not logged in', async () => { + const mockLogin = vi.fn() + + const mockGetOidc = vi.fn(() => + Promise.resolve({ + isUserLoggedIn: false, + login: mockLogin, + } as unknown as Oidc) + ) + + await thunks.loginIfNotLoggedIn()( + null as any, + null as any, + { + getOidc: mockGetOidc, + } as any + ) + + expect(mockGetOidc).toHaveBeenCalled() + expect(mockLogin).toHaveBeenCalled() + }) + }) +}) diff --git a/src/core/usecases/visualizeSurvey/parser/searchParamsSchema.test.ts b/src/core/usecases/visualizeSurvey/parser/searchParamsSchema.test.ts new file mode 100644 index 00000000..02e9f411 --- /dev/null +++ b/src/core/usecases/visualizeSurvey/parser/searchParamsSchema.test.ts @@ -0,0 +1,59 @@ +import { describe, it, expect } from 'vitest' +import { searchParamsSchema } from './searchParamsSchema' + +describe('searchParamsSchema', () => { + it('should validate an object with all valid fields', () => { + const validData = { + questionnaire: 'survey123', + data: 'responseData', + nomenclature: { key1: 'value1', key2: 'value2' }, + readonly: true, + } + + const result = searchParamsSchema.safeParse(validData) + expect(result.success).toBe(true) + expect(result.success && result.data).toEqual(validData) + }) + + it('should validate an object with partial fields', () => { + const partialData = { + readonly: false, + } + + const result = searchParamsSchema.safeParse(partialData) + expect(result.success).toBe(true) + expect(result.success && result.data).toEqual(partialData) + }) + + it('should fail validation when readonly is not a boolean', () => { + const invalidData = { + readonly: 'notABoolean', + } + + const result = searchParamsSchema.safeParse(invalidData) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Expected boolean') + } + }) + + it('should fail validation when nomenclature has non-string values', () => { + const invalidData = { + nomenclature: { key1: 123 }, + } + + const result = searchParamsSchema.safeParse(invalidData) + expect(result.success).toBe(false) + if (!result.success) { + expect(result.error.issues[0].message).toContain('Expected string') + } + }) + + it('should validate an empty object', () => { + const emptyData = {} + + const result = searchParamsSchema.safeParse(emptyData) + expect(result.success).toBe(true) + expect(result.success && result.data).toEqual(emptyData) + }) +}) diff --git a/src/ui/routing/NavigationManager.test.tsx b/src/ui/routing/NavigationManager.test.tsx new file mode 100644 index 00000000..9767d189 --- /dev/null +++ b/src/ui/routing/NavigationManager.test.tsx @@ -0,0 +1,84 @@ +import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest' +import { render } from '@testing-library/react' +import { + matchRoutes, + MemoryRouter, + useLocation, + useNavigate, +} from 'react-router-dom' +import { NavigationManager } from './NavigationManager' + +vi.mock('react-router-dom', async () => { + const actual = await vi.importActual('react-router-dom') + return { + ...actual, + useLocation: vi.fn(), + useNavigate: vi.fn(), + matchRoutes: vi.fn(), + } +}) + +describe('NavigationManager', () => { + let useLocationMock: ReturnType + let useNavigateMock: ReturnType + let matchRoutesMock: ReturnType + + beforeEach(() => { + useLocationMock = vi.fn() + useNavigateMock = vi.fn() + matchRoutesMock = vi.fn() + + // Mock hooks used by the component + vi.mocked(useLocation).mockImplementation(useLocationMock) + vi.mocked(useNavigate).mockImplementation(() => useNavigateMock) + vi.mocked(matchRoutes).mockImplementation(matchRoutesMock) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + it('does not navigate if the path is the same', () => { + const mockLocation = { pathname: '/same-path' } + const mockNavigate = vi.fn() + + useLocationMock.mockReturnValue(mockLocation) + useNavigateMock.mockImplementation(() => mockNavigate) + matchRoutesMock.mockImplementation( + (routes, { pathname }) => pathname === '/same-path' + ) + + render( + + +
Children
+
+
+ ) + + const event = new CustomEvent('[Pearl] navigated', { detail: '/same-path' }) + window.dispatchEvent(event) + + expect(mockNavigate).not.toHaveBeenCalled() + }) + + it('dispatches a custom event on navigation', () => { + const mockLocation = { pathname: '/test-path' } + + useLocationMock.mockReturnValue(mockLocation) + + const customEventSpy = vi.spyOn(window, 'dispatchEvent') + + render( + + +
Children
+
+
+ ) + + expect(customEventSpy).toHaveBeenCalledWith( + new CustomEvent('[Drama Queen] navigated', { detail: '/test-path' }) + ) + }) +}) diff --git a/src/ui/routing/loader/collectLoader.test.ts b/src/ui/routing/loader/collectLoader.test.ts new file mode 100644 index 00000000..7af117f2 --- /dev/null +++ b/src/ui/routing/loader/collectLoader.test.ts @@ -0,0 +1,62 @@ +import { prCore } from 'createCore' +import type { LoaderFunctionArgs } from 'react-router-dom' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { collectLoader } from './collectLoader' + +vi.mock('createCore', () => ({ + prCore: { + functions: { + collectSurvey: { + loader: vi.fn(), + }, + }, + }, +})) + +describe('collectLoader', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should call collectSurvey.loader with the correct parameters', async () => { + const mockLoader = vi.fn() + + ;(await prCore).functions.collectSurvey.loader = mockLoader + + const mockParams = { + questionnaireId: 'test-questionnaire-id', + surveyUnitId: 'test-survey-unit-id', + } + + const mockLoaderArgs = { + params: mockParams, + } as unknown as LoaderFunctionArgs + + await collectLoader(mockLoaderArgs) + + expect(mockLoader).toHaveBeenCalledWith({ + questionnaireId: mockParams.questionnaireId, + surveyUnitId: mockParams.surveyUnitId, + }) + }) + + it('should throw an error if questionnaireId or surveyUnitId is undefined', async () => { + await expect( + collectLoader({ + params: { + questionnaireId: undefined, + surveyUnitId: 'test-survey-unit-id', + }, + } as unknown as LoaderFunctionArgs) + ).rejects.toThrow('Wrong assertion encountered') + + await expect( + collectLoader({ + params: { + questionnaireId: 'questionnaireId', + surveyUnitId: undefined, + }, + } as unknown as LoaderFunctionArgs) + ).rejects.toThrow('Wrong assertion encountered') + }) +}) diff --git a/src/ui/routing/loader/protectedLoader.test.ts b/src/ui/routing/loader/protectedLoader.test.ts new file mode 100644 index 00000000..eb4d3ba7 --- /dev/null +++ b/src/ui/routing/loader/protectedLoader.test.ts @@ -0,0 +1,28 @@ +import { prCore } from 'createCore' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { protectedRouteLoader } from './protectedLoader' + +vi.mock('createCore', () => ({ + prCore: { + functions: { + userAuthentication: { + loginIfNotLoggedIn: vi.fn(), + }, + }, + }, +})) + +describe('protectedRouteLoader', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should call collectSurvey.loader with the correct parameters', async () => { + const mockLoader = vi.fn() + ;(await prCore).functions.userAuthentication.loginIfNotLoggedIn = mockLoader + + await protectedRouteLoader() + + expect(mockLoader).toHaveBeenCalledWith() + }) +}) diff --git a/src/ui/routing/loader/reviewLoader.test.ts b/src/ui/routing/loader/reviewLoader.test.ts new file mode 100644 index 00000000..48edc9d9 --- /dev/null +++ b/src/ui/routing/loader/reviewLoader.test.ts @@ -0,0 +1,65 @@ +import { prCore } from 'createCore' +import type { LoaderFunctionArgs } from 'react-router-dom' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { reviewLoader } from './reviewLoader' + +vi.mock('createCore', () => ({ + prCore: { + functions: { + reviewSurvey: { + loader: vi.fn(), + }, + userAuthentication: { + loginIfNotLoggedIn: vi.fn(), + }, + }, + }, +})) + +describe('reviewLoader', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should call collectSurvey.loader with the correct parameters', async () => { + const mockLoader = vi.fn() + + ;(await prCore).functions.reviewSurvey.loader = mockLoader + + const mockParams = { + questionnaireId: 'test-questionnaire-id', + surveyUnitId: 'test-survey-unit-id', + } + + const mockLoaderArgs = { + params: mockParams, + } as unknown as LoaderFunctionArgs + + await reviewLoader(mockLoaderArgs) + + expect(mockLoader).toHaveBeenCalledWith({ + questionnaireId: mockParams.questionnaireId, + surveyUnitId: mockParams.surveyUnitId, + }) + }) + + it('should throw an error if questionnaireId or surveyUnitId is undefined', async () => { + await expect( + reviewLoader({ + params: { + questionnaireId: undefined, + surveyUnitId: 'test-survey-unit-id', + }, + } as unknown as LoaderFunctionArgs) + ).rejects.toThrow('Wrong assertion encountered') + + await expect( + reviewLoader({ + params: { + questionnaireId: 'questionnaireId', + surveyUnitId: undefined, + }, + } as unknown as LoaderFunctionArgs) + ).rejects.toThrow('Wrong assertion encountered') + }) +}) diff --git a/src/ui/routing/loader/surveyUnitLoader.test.ts b/src/ui/routing/loader/surveyUnitLoader.test.ts new file mode 100644 index 00000000..09720a62 --- /dev/null +++ b/src/ui/routing/loader/surveyUnitLoader.test.ts @@ -0,0 +1,63 @@ +import { prCore } from 'createCore' +import type { LoaderFunctionArgs } from 'react-router-dom' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { surveyUnitLoader } from './surveyUnitLoader' +import { redirect } from 'react-router-dom' + +vi.mock('react-router-dom', () => ({ + redirect: vi.fn(), +})) + +vi.mock('createCore', () => ({ + prCore: { + functions: { + collectSurvey: { + retrieveQuestionnaireId: vi.fn(), + }, + }, + }, +})) + +describe('collectLoader', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should return null if questionnaireId is undefined', async () => { + const mockLoader = vi.fn().mockResolvedValueOnce(undefined) + + ;(await prCore).functions.collectSurvey.retrieveQuestionnaireId = mockLoader + + const result = await surveyUnitLoader({ + params: { surveyUnitId: 1 }, + } as unknown as LoaderFunctionArgs) + + expect(result).toBeNull() + }) + + it('should return an url if questionnaireId is defined', async () => { + const mockLoader = vi.fn().mockResolvedValueOnce('questionnaireId') + + ;(await prCore).functions.collectSurvey.retrieveQuestionnaireId = mockLoader + + const result = await surveyUnitLoader({ + params: { surveyUnitId: 1 }, + } as unknown as LoaderFunctionArgs) + + expect(redirect).toHaveBeenCalledWith( + '/questionnaire/questionnaireId/survey-unit/1' + ) + }) + + it('should thrown an exception if the surveyUnitId is undefined', async () => { + const mockLoader = vi.fn().mockResolvedValueOnce(undefined) + + ;(await prCore).functions.collectSurvey.retrieveQuestionnaireId = mockLoader + + await await expect( + surveyUnitLoader({ + params: {}, + } as unknown as LoaderFunctionArgs) + ).rejects.toThrow('Wrong assertion encountered') + }) +}) diff --git a/src/ui/routing/loader/visualizeLoader.test.ts b/src/ui/routing/loader/visualizeLoader.test.ts new file mode 100644 index 00000000..49d6dceb --- /dev/null +++ b/src/ui/routing/loader/visualizeLoader.test.ts @@ -0,0 +1,32 @@ +import { prCore } from 'createCore' +import type { LoaderFunctionArgs } from 'react-router-dom' +import { assert } from 'tsafe' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { visualizeLoader } from './visualizeLoader' + +vi.mock('createCore', () => ({ + prCore: { + functions: { + visualizeSurvey: { + loader: vi.fn(), + }, + }, + }, +})) + +describe('protectedRouteLoader', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should call collectSurvey.loader with the correct parameters', async () => { + const mockLoader = vi.fn() + ;(await prCore).functions.visualizeSurvey.loader = mockLoader + + await visualizeLoader({ request: { url: 'url' } } as LoaderFunctionArgs) + + expect(mockLoader).toHaveBeenCalledWith({ + requestUrl: 'url', + }) + }) +})