From 068248035c01d6dc3e2a92458980f59672e9a416 Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Thu, 10 Oct 2024 10:32:37 +0200 Subject: [PATCH 01/21] feat: get externalResources during synchronization --- .../src/core/model/ExternalResources.ts | 15 ++++ drama-queen/src/core/model/index.ts | 3 +- .../src/core/tools/externalResources.ts | 83 +++++++++++++++++++ .../core/usecases/synchronizeData/thunks.ts | 31 ++++++- 4 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 drama-queen/src/core/model/ExternalResources.ts create mode 100644 drama-queen/src/core/tools/externalResources.ts diff --git a/drama-queen/src/core/model/ExternalResources.ts b/drama-queen/src/core/model/ExternalResources.ts new file mode 100644 index 00000000..1d1bd48e --- /dev/null +++ b/drama-queen/src/core/model/ExternalResources.ts @@ -0,0 +1,15 @@ +export type ExternalQuestionnaire = { + id: string + cacheName: string +} + +export type ExternalQuestionnaires = ExternalQuestionnaire[] + +export type ExternalQuestionnairesWrapper = { + questionnaires: ExternalQuestionnaires + version?: string +} + +export type Manifest = { + [key: string]: string +} diff --git a/drama-queen/src/core/model/index.ts b/drama-queen/src/core/model/index.ts index 027ce480..dfa48274 100644 --- a/drama-queen/src/core/model/index.ts +++ b/drama-queen/src/core/model/index.ts @@ -1,7 +1,8 @@ export * from './Campaign' +export * from './ExternalResources' +export * from './IdAndQuestionnaireId' export * from './Nomenclature' export * from './Paradata' export * from './Questionnaire' export * from './SurveyUnit' export * from './SurveyUnitData' -export * from './IdAndQuestionnaireId' diff --git a/drama-queen/src/core/tools/externalResources.ts b/drama-queen/src/core/tools/externalResources.ts new file mode 100644 index 00000000..93897161 --- /dev/null +++ b/drama-queen/src/core/tools/externalResources.ts @@ -0,0 +1,83 @@ +import type { + ExternalQuestionnaire, + ExternalQuestionnaires, + ExternalQuestionnairesWrapper, + Manifest, +} from 'core/model' +import { fetchUrl } from './fetchUrl' + +export async function getExternalQuestionnaires( + baseUrl: string +): Promise { + const questionnairesWrapper = await fetchUrl({ + url: `${baseUrl}/gide-questionnaires.json`, + }) + + return questionnairesWrapper.questionnaires +} + +export async function getTransformedManifest( + baseUrl: string, + questionnaireId: string +): Promise { + // get the manifest for a questionnaireId + const manifest = await fetchUrl({ + url: `${baseUrl}/${questionnaireId}/assets-manifest.json`, + }) + + // Transform the manifest values into resource URLs, and get an array of these resource URLs + const transformedManifest = Object.values(manifest).map( + (resourceUrl) => `${baseUrl}/${resourceUrl}` + ) + + return transformedManifest +} + +async function asyncFilter( + arr: T[], + predicate: (element: T) => Promise +): Promise { + // Map each element to a promise that resolves to a boolean + const results = await Promise.all(arr.map(predicate)) + + // Return elements where the corresponding result is true + return arr.filter((_, index) => results[index]) +} + +// Filter resources from manifest that are already cached, to avoid useless requests (overly large files). +export async function filterTransformedManifest( + cacheName: string, + transformedManifest: string[] +): Promise { + // Open the specified cache + const cacheForManifest = await caches.open(cacheName) + + // If the cache is available, filter the transformedManifest for keeping only files that are not cached yet + if (cacheForManifest) { + return await asyncFilter(transformedManifest, async (resourceUrl) => { + const cacheResponse = await cacheForManifest.match(resourceUrl) + return !cacheResponse?.ok + }) + } + + // If cache is not available, return a copy of transformedManifest + return [...transformedManifest] +} + +export async function getResourcesFromExternalQuestionnaire( + baseUrl: string, + questionnaire: ExternalQuestionnaire +): Promise { + const transformedManifest = await getTransformedManifest( + baseUrl, + questionnaire.id + ) + + const filteredTransformedManifest = await filterTransformedManifest( + questionnaire.cacheName, + transformedManifest + ) + + const manifestCache = await caches.open(questionnaire.cacheName) + return await manifestCache.addAll(filteredTransformedManifest) +} diff --git a/drama-queen/src/core/usecases/synchronizeData/thunks.ts b/drama-queen/src/core/usecases/synchronizeData/thunks.ts index 983f49cd..f1a625fb 100644 --- a/drama-queen/src/core/usecases/synchronizeData/thunks.ts +++ b/drama-queen/src/core/usecases/synchronizeData/thunks.ts @@ -1,7 +1,15 @@ import type { Thunks } from 'core/bootstrap' import { actions, name } from './state' import { AxiosError } from 'axios' -import type { Questionnaire } from 'core/model' +import type { ExternalQuestionnaires, Questionnaire } from 'core/model' +import { fetchUrl } from 'core/tools/fetchUrl' +import { + getExternalQuestionnaires, + getResourcesFromExternalQuestionnaire, + getTransformedManifest, +} from 'core/tools/externalResources' + +const externalResourcesUrl = import.meta.env.VITE_EXTERNAL_RESOURCES_URL export const thunks = { download: @@ -172,6 +180,27 @@ export const thunks = { ) ) + /* + * External special ressources + */ + + // we sychronize the external ressource only if there is a url for getting them + if (externalResourcesUrl) { + // get the list of external questionnaires + const externalQuestionnaires = + await getExternalQuestionnaires(externalResourcesUrl) + + // add in cache the missing external resources + await Promise.all( + externalQuestionnaires.map((questionnaire) => + getResourcesFromExternalQuestionnaire( + externalResourcesUrl, + questionnaire + ) + ) + ) + } + //We await untill all the promises are finished await Promise.all([prSurveyUnit, prNomenclatures]) From 058c7b780b43b1a9e414dc02d4d7f4dd86b16f42 Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Thu, 10 Oct 2024 10:47:14 +0200 Subject: [PATCH 02/21] chore: add comments & remove unused imports/exports --- drama-queen/src/core/tools/externalResources.ts | 7 +++++-- drama-queen/src/core/usecases/synchronizeData/thunks.ts | 4 +--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/drama-queen/src/core/tools/externalResources.ts b/drama-queen/src/core/tools/externalResources.ts index 93897161..14be4a17 100644 --- a/drama-queen/src/core/tools/externalResources.ts +++ b/drama-queen/src/core/tools/externalResources.ts @@ -6,6 +6,7 @@ import type { } from 'core/model' import { fetchUrl } from './fetchUrl' +// Get the list of external questionnaires from url export async function getExternalQuestionnaires( baseUrl: string ): Promise { @@ -16,7 +17,8 @@ export async function getExternalQuestionnaires( return questionnairesWrapper.questionnaires } -export async function getTransformedManifest( +// Get the list of external resource URLs for a questionnaireId from url +async function getTransformedManifest( baseUrl: string, questionnaireId: string ): Promise { @@ -45,7 +47,7 @@ async function asyncFilter( } // Filter resources from manifest that are already cached, to avoid useless requests (overly large files). -export async function filterTransformedManifest( +async function filterTransformedManifest( cacheName: string, transformedManifest: string[] ): Promise { @@ -64,6 +66,7 @@ export async function filterTransformedManifest( return [...transformedManifest] } +// Cache every external resources (not already cached) for a particular questionnaire export async function getResourcesFromExternalQuestionnaire( baseUrl: string, questionnaire: ExternalQuestionnaire diff --git a/drama-queen/src/core/usecases/synchronizeData/thunks.ts b/drama-queen/src/core/usecases/synchronizeData/thunks.ts index f1a625fb..2fc95c0c 100644 --- a/drama-queen/src/core/usecases/synchronizeData/thunks.ts +++ b/drama-queen/src/core/usecases/synchronizeData/thunks.ts @@ -1,12 +1,10 @@ import type { Thunks } from 'core/bootstrap' import { actions, name } from './state' import { AxiosError } from 'axios' -import type { ExternalQuestionnaires, Questionnaire } from 'core/model' -import { fetchUrl } from 'core/tools/fetchUrl' +import type { Questionnaire } from 'core/model' import { getExternalQuestionnaires, getResourcesFromExternalQuestionnaire, - getTransformedManifest, } from 'core/tools/externalResources' const externalResourcesUrl = import.meta.env.VITE_EXTERNAL_RESOURCES_URL From 8a7eda1681574aad7448973dbb49d9cbd13b7e6a Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Thu, 10 Oct 2024 14:05:36 +0200 Subject: [PATCH 03/21] fix: get external resources only for needed questionnaires --- .../src/core/model/ExternalResources.ts | 5 +++ .../src/core/tools/externalResources.ts | 33 +++++++++++++++++++ .../core/usecases/synchronizeData/thunks.ts | 10 ++++-- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/drama-queen/src/core/model/ExternalResources.ts b/drama-queen/src/core/model/ExternalResources.ts index 1d1bd48e..8c8cae0c 100644 --- a/drama-queen/src/core/model/ExternalResources.ts +++ b/drama-queen/src/core/model/ExternalResources.ts @@ -10,6 +10,11 @@ export type ExternalQuestionnairesWrapper = { version?: string } +export type ExternalQuestionnairesFiltered = { + neededQuestionnaires: ExternalQuestionnaires + notNeededQuestionnaires: ExternalQuestionnaires +} + export type Manifest = { [key: string]: string } diff --git a/drama-queen/src/core/tools/externalResources.ts b/drama-queen/src/core/tools/externalResources.ts index 14be4a17..db44b508 100644 --- a/drama-queen/src/core/tools/externalResources.ts +++ b/drama-queen/src/core/tools/externalResources.ts @@ -1,6 +1,7 @@ import type { ExternalQuestionnaire, ExternalQuestionnaires, + ExternalQuestionnairesFiltered, ExternalQuestionnairesWrapper, Manifest, } from 'core/model' @@ -84,3 +85,35 @@ export async function getResourcesFromExternalQuestionnaire( const manifestCache = await caches.open(questionnaire.cacheName) return await manifestCache.addAll(filteredTransformedManifest) } + +// Separate, from the list of external questionnaires, those that are needed and those that are not needed +export function getExternalQuestionnaireFiltered( + neededQuestionnaireIds: string[] = [], + externalQuestionnaires: ExternalQuestionnaires = [] +): ExternalQuestionnairesFiltered { + return externalQuestionnaires.reduce( + (result, questionnaire) => { + // Check if the current questionnaire's id matches any of the needed IDs + const isNeeded = neededQuestionnaireIds.some((neededId) => + neededId.toLowerCase().includes(questionnaire.id.toLowerCase()) + ) + + // If the external questionnaire is needed + if (isNeeded) { + result.neededQuestionnaires.push(questionnaire) + } + // If it's not needed + else { + result.notNeededQuestionnaires.push(questionnaire) + } + + // Return the updated result object after processing this questionnaire + return result + }, + // Initial value of result: an object with two empty arrays (needed and noNeeded) + { + neededQuestionnaires: [], + notNeededQuestionnaires: [], + } as ExternalQuestionnairesFiltered + ) +} diff --git a/drama-queen/src/core/usecases/synchronizeData/thunks.ts b/drama-queen/src/core/usecases/synchronizeData/thunks.ts index 2fc95c0c..25457eb2 100644 --- a/drama-queen/src/core/usecases/synchronizeData/thunks.ts +++ b/drama-queen/src/core/usecases/synchronizeData/thunks.ts @@ -3,6 +3,7 @@ import { actions, name } from './state' import { AxiosError } from 'axios' import type { Questionnaire } from 'core/model' import { + getExternalQuestionnaireFiltered, getExternalQuestionnaires, getResourcesFromExternalQuestionnaire, } from 'core/tools/externalResources' @@ -188,9 +189,14 @@ export const thunks = { const externalQuestionnaires = await getExternalQuestionnaires(externalResourcesUrl) - // add in cache the missing external resources + const { neededQuestionnaires } = getExternalQuestionnaireFiltered( + questionnaireIdInSuccess, + externalQuestionnaires + ) + + // add in cache the missing external resources for needed questionnaires await Promise.all( - externalQuestionnaires.map((questionnaire) => + neededQuestionnaires.map((questionnaire) => getResourcesFromExternalQuestionnaire( externalResourcesUrl, questionnaire From c1b4c7a5aaf5cb2bf830cdda2691a34c30a402f1 Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Fri, 11 Oct 2024 11:09:12 +0200 Subject: [PATCH 04/21] feat: delete external resources for not needed questionnaires --- .../src/core/usecases/synchronizeData/thunks.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drama-queen/src/core/usecases/synchronizeData/thunks.ts b/drama-queen/src/core/usecases/synchronizeData/thunks.ts index 25457eb2..2df1aece 100644 --- a/drama-queen/src/core/usecases/synchronizeData/thunks.ts +++ b/drama-queen/src/core/usecases/synchronizeData/thunks.ts @@ -189,10 +189,11 @@ export const thunks = { const externalQuestionnaires = await getExternalQuestionnaires(externalResourcesUrl) - const { neededQuestionnaires } = getExternalQuestionnaireFiltered( - questionnaireIdInSuccess, - externalQuestionnaires - ) + const { neededQuestionnaires, notNeededQuestionnaires } = + getExternalQuestionnaireFiltered( + questionnaireIdInSuccess, + externalQuestionnaires + ) // add in cache the missing external resources for needed questionnaires await Promise.all( @@ -203,6 +204,13 @@ export const thunks = { ) ) ) + + // delete the cache of every not needed questionnaire + await Promise.all( + notNeededQuestionnaires.map((questionnaire) => + caches.delete(questionnaire.cacheName) + ) + ) } //We await untill all the promises are finished From a53bb5ebbf80ab6596326342afa507f11544ed1c Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Fri, 11 Oct 2024 11:35:01 +0200 Subject: [PATCH 05/21] feat: delete external resources root-cache if no external questionnaire needed --- drama-queen/src/core/usecases/synchronizeData/thunks.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drama-queen/src/core/usecases/synchronizeData/thunks.ts b/drama-queen/src/core/usecases/synchronizeData/thunks.ts index 2df1aece..711d472d 100644 --- a/drama-queen/src/core/usecases/synchronizeData/thunks.ts +++ b/drama-queen/src/core/usecases/synchronizeData/thunks.ts @@ -9,6 +9,7 @@ import { } from 'core/tools/externalResources' const externalResourcesUrl = import.meta.env.VITE_EXTERNAL_RESOURCES_URL +const externalResourcesRootCacheName = 'cache-root-external' export const thunks = { download: @@ -205,12 +206,17 @@ export const thunks = { ) ) - // delete the cache of every not needed questionnaire + // delete the cache of every not needed external questionnaires await Promise.all( notNeededQuestionnaires.map((questionnaire) => caches.delete(questionnaire.cacheName) ) ) + + // delete the root-cache of external resources if no external questionnaire is needed + if (neededQuestionnaires.length === 0) { + await caches.delete(externalResourcesRootCacheName) + } } //We await untill all the promises are finished From 944dbe7ab40dac106d511c33e88b5b706239d57a Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Fri, 11 Oct 2024 17:53:03 +0200 Subject: [PATCH 06/21] feat: handle progress bar for external resources synchro --- .../usecases/synchronizeData/selectors.ts | 19 ++++++++++++++ .../core/usecases/synchronizeData/state.ts | 26 +++++++++++++++++++ .../core/usecases/synchronizeData/thunks.ts | 15 ++++++++--- drama-queen/src/i18n/resources/en.ts | 1 + drama-queen/src/i18n/resources/fr.ts | 1 + drama-queen/src/i18n/types.ts | 1 + .../ui/pages/synchronize/SynchronizeData.tsx | 10 +++++++ 7 files changed, 70 insertions(+), 3 deletions(-) diff --git a/drama-queen/src/core/usecases/synchronizeData/selectors.ts b/drama-queen/src/core/usecases/synchronizeData/selectors.ts index 0b3afaf7..81f22f55 100644 --- a/drama-queen/src/core/usecases/synchronizeData/selectors.ts +++ b/drama-queen/src/core/usecases/synchronizeData/selectors.ts @@ -40,6 +40,22 @@ const surveyProgress = createSelector(downloadingState, (state) => { return (state.surveyCompleted * 100) / state.totalSurvey }) +const externalResourcesProgress = createSelector(downloadingState, (state) => { + if (state === undefined) { + return undefined + } + // if there is no external resources, we don't show the progress bar + if (state.totalExternalResources === undefined) { + return undefined + } + if ( + state.externalResourcesCompleted === 0 && + state.totalExternalResources === 0 + ) + return 100 + return (state.externalResourcesCompleted * 100) / state.totalExternalResources +}) + const uploadProgress = createSelector(state, (state) => { if (state.stateDescription !== 'running') { return undefined @@ -58,12 +74,14 @@ const main = createSelector( surveyUnitProgress, nomenclatureProgress, surveyProgress, + externalResourcesProgress, uploadProgress, ( state, surveyUnitProgress, nomenclatureProgress, surveyProgress, + externalResourcesProgress, uploadProgress ) => { switch (state.stateDescription) { @@ -86,6 +104,7 @@ const main = createSelector( surveyUnitProgress, nomenclatureProgress, surveyProgress, + externalResourcesProgress, } } } diff --git a/drama-queen/src/core/usecases/synchronizeData/state.ts b/drama-queen/src/core/usecases/synchronizeData/state.ts index 3dbeed3e..c04c14fc 100644 --- a/drama-queen/src/core/usecases/synchronizeData/state.ts +++ b/drama-queen/src/core/usecases/synchronizeData/state.ts @@ -1,6 +1,7 @@ import { createUsecaseActions } from 'redux-clean-architecture' import { id } from 'tsafe/id' import { assert } from 'tsafe/assert' +import { externalResourcesUrl } from './thunks' export type State = State.NotRunning | State.Running @@ -30,6 +31,8 @@ export namespace State { nomenclatureCompleted: number totalSurvey: number surveyCompleted: number + totalExternalResources?: number + externalResourcesCompleted: number } } } @@ -54,6 +57,11 @@ export const { reducer, actions } = createUsecaseActions({ nomenclatureCompleted: 0, totalSurvey: Infinity, surveyCompleted: 0, + // for total external resources, we make difference for displaying progress bar between : + // 0 : external synchro is triggered but there is no needed questionnaire so we want a fullfilled progress bar + // undefined : external synchro is not triggered so we don't want the progress bar + totalExternalResources: externalResourcesUrl ? Infinity : undefined, + externalResourcesCompleted: 0, }) ), runningUpload: () => @@ -116,6 +124,24 @@ export const { reducer, actions } = createUsecaseActions({ nomenclatureCompleted: state.nomenclatureCompleted + 1, } }, + setDownloadTotalExternalResources: ( + state, + { payload }: { payload: { totalExternalResources: number } } + ) => { + const { totalExternalResources } = payload + assert(state.stateDescription === 'running' && state.type === 'download') + return { + ...state, + totalExternalResources, + } + }, + downloadExternalResourceCompleted: (state) => { + assert(state.stateDescription === 'running' && state.type === 'download') + return { + ...state, + externalResourcesCompleted: state.externalResourcesCompleted + 1, + } + }, setUploadTotal: (state, { payload }: { payload: { total: number } }) => { const { total } = payload assert(state.stateDescription === 'running' && state.type === 'upload') diff --git a/drama-queen/src/core/usecases/synchronizeData/thunks.ts b/drama-queen/src/core/usecases/synchronizeData/thunks.ts index 711d472d..865f38e6 100644 --- a/drama-queen/src/core/usecases/synchronizeData/thunks.ts +++ b/drama-queen/src/core/usecases/synchronizeData/thunks.ts @@ -8,7 +8,7 @@ import { getResourcesFromExternalQuestionnaire, } from 'core/tools/externalResources' -const externalResourcesUrl = import.meta.env.VITE_EXTERNAL_RESOURCES_URL +export const externalResourcesUrl = import.meta.env.VITE_EXTERNAL_RESOURCES_URL const externalResourcesRootCacheName = 'cache-root-external' export const thunks = { @@ -196,14 +196,23 @@ export const thunks = { externalQuestionnaires ) + // set the total of needed external questionnaires for progress bar + dispatch( + actions.setDownloadTotalExternalResources({ + totalExternalResources: neededQuestionnaires.length, + }) + ) + // add in cache the missing external resources for needed questionnaires await Promise.all( - neededQuestionnaires.map((questionnaire) => + neededQuestionnaires.map((questionnaire) => { getResourcesFromExternalQuestionnaire( externalResourcesUrl, questionnaire + ).then(() => + dispatch(actions.downloadExternalResourceCompleted()) ) - ) + }) ) // delete the cache of every not needed external questionnaires diff --git a/drama-queen/src/i18n/resources/en.ts b/drama-queen/src/i18n/resources/en.ts index 1bf1b535..c5024a2b 100644 --- a/drama-queen/src/i18n/resources/en.ts +++ b/drama-queen/src/i18n/resources/en.ts @@ -77,6 +77,7 @@ export const translations: Translations<'en'> = { surveyUnitsProgress: 'Survey units', questionnairesProgress: 'Questionnaires', nomenclaturesProgress: 'Nomenclatures', + externalResourcesProgress: 'External resources', uploadingData: 'Sending data...', }, visualizeMessage: { diff --git a/drama-queen/src/i18n/resources/fr.ts b/drama-queen/src/i18n/resources/fr.ts index ba77118a..d22983ca 100644 --- a/drama-queen/src/i18n/resources/fr.ts +++ b/drama-queen/src/i18n/resources/fr.ts @@ -78,6 +78,7 @@ export const translations: Translations<'fr'> = { surveyUnitsProgress: 'Unités enquêtées', questionnairesProgress: 'Questionnaires', nomenclaturesProgress: 'Nomenclatures', + externalResourcesProgress: 'Ressources externes', uploadingData: 'Envoi des données...', }, visualizeMessage: { diff --git a/drama-queen/src/i18n/types.ts b/drama-queen/src/i18n/types.ts index 06a67a01..7a5beac6 100644 --- a/drama-queen/src/i18n/types.ts +++ b/drama-queen/src/i18n/types.ts @@ -76,6 +76,7 @@ export type SynchronizeMessage = | 'surveyUnitsProgress' | 'questionnairesProgress' | 'nomenclaturesProgress' + | 'externalResourcesProgress' | 'uploadingData' export type VisualizeMessage = diff --git a/drama-queen/src/ui/pages/synchronize/SynchronizeData.tsx b/drama-queen/src/ui/pages/synchronize/SynchronizeData.tsx index 018bc869..36786ebc 100644 --- a/drama-queen/src/ui/pages/synchronize/SynchronizeData.tsx +++ b/drama-queen/src/ui/pages/synchronize/SynchronizeData.tsx @@ -14,6 +14,7 @@ export function SynchronizeData() { nomenclatureProgress, surveyProgress, surveyUnitProgress, + externalResourcesProgress, uploadProgress, } = useCoreState('synchronizeData', 'main') @@ -68,6 +69,15 @@ export function SynchronizeData() { progress: surveyUnitProgress, label: t('surveyUnitsProgress'), }, + // render external resources progress bar only if there are external resources + ...(externalResourcesProgress !== undefined + ? [ + { + progress: externalResourcesProgress, + label: t('externalResourcesProgress'), + }, + ] + : []), ]} syncStepTitle={t('downloadingData')} /> From baaf348ec88e0023ad549c0684e82d2074a2d9db Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Fri, 11 Oct 2024 18:45:13 +0200 Subject: [PATCH 07/21] fix: handle promises for external resources --- .../core/usecases/synchronizeData/thunks.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/drama-queen/src/core/usecases/synchronizeData/thunks.ts b/drama-queen/src/core/usecases/synchronizeData/thunks.ts index 865f38e6..7f80c024 100644 --- a/drama-queen/src/core/usecases/synchronizeData/thunks.ts +++ b/drama-queen/src/core/usecases/synchronizeData/thunks.ts @@ -204,9 +204,9 @@ export const thunks = { ) // add in cache the missing external resources for needed questionnaires - await Promise.all( - neededQuestionnaires.map((questionnaire) => { - getResourcesFromExternalQuestionnaire( + const prGetExternalResources = Promise.all( + neededQuestionnaires.map(async (questionnaire) => { + await getResourcesFromExternalQuestionnaire( externalResourcesUrl, questionnaire ).then(() => @@ -216,16 +216,24 @@ export const thunks = { ) // delete the cache of every not needed external questionnaires - await Promise.all( + const prDeleteExternalResources = Promise.all( notNeededQuestionnaires.map((questionnaire) => caches.delete(questionnaire.cacheName) ) ) // delete the root-cache of external resources if no external questionnaire is needed - if (neededQuestionnaires.length === 0) { - await caches.delete(externalResourcesRootCacheName) - } + const prDeleteExternalRootCache = + neededQuestionnaires.length === 0 + ? caches.delete(externalResourcesRootCacheName) + : Promise.resolve() + + // We await untill the promises for external resources are finished + await Promise.all([ + prGetExternalResources, + prDeleteExternalResources, + prDeleteExternalRootCache, + ]) } //We await untill all the promises are finished From db959e1ad0c4729feb90eb5c6f042f53850576dd Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Fri, 11 Oct 2024 20:00:59 +0200 Subject: [PATCH 08/21] perf: optimize cache lookup during external resources synchronization --- .../src/core/tools/externalResources.ts | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/drama-queen/src/core/tools/externalResources.ts b/drama-queen/src/core/tools/externalResources.ts index db44b508..531c7824 100644 --- a/drama-queen/src/core/tools/externalResources.ts +++ b/drama-queen/src/core/tools/externalResources.ts @@ -55,16 +55,22 @@ async function filterTransformedManifest( // Open the specified cache const cacheForManifest = await caches.open(cacheName) - // If the cache is available, filter the transformedManifest for keeping only files that are not cached yet - if (cacheForManifest) { - return await asyncFilter(transformedManifest, async (resourceUrl) => { - const cacheResponse = await cacheForManifest.match(resourceUrl) - return !cacheResponse?.ok - }) + // If cache is not available, return a copy of transformedManifest + if (!cacheForManifest) { + return [...transformedManifest] } - // If cache is not available, return a copy of transformedManifest - return [...transformedManifest] + // Get all urls from the cache + const cachedUrls = (await cacheForManifest.keys()).map( + (request) => request.url + ) + + // filter the transformedManifest for keeping only files that are not cached yet + const uncachedResources = transformedManifest.filter( + (resourceUrl) => !cachedUrls.includes(resourceUrl) + ) + + return uncachedResources } // Cache every external resources (not already cached) for a particular questionnaire From 4b2a111aa2d0c45478c6880083fe1d245d8c1b32 Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Mon, 14 Oct 2024 11:39:42 +0200 Subject: [PATCH 09/21] feat : handle errors on fetching external url --- drama-queen/src/core/tools/fetchUrl.ts | 9 +----- .../core/usecases/synchronizeData/thunks.ts | 28 ++++++++++++++++--- .../core/usecases/visualizeSurvey/thunks.ts | 14 +++++++--- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/drama-queen/src/core/tools/fetchUrl.ts b/drama-queen/src/core/tools/fetchUrl.ts index 776a51b5..5a604b46 100644 --- a/drama-queen/src/core/tools/fetchUrl.ts +++ b/drama-queen/src/core/tools/fetchUrl.ts @@ -8,12 +8,5 @@ import { handleAxiosError } from './axiosError' */ export async function fetchUrl(params: { url: string }) { const { url } = params - return axios - .get(decodeURIComponent(url)) - .then(({ data }) => data) - .catch((error) => { - console.error(`An error occured, we could not retrieve ${url}`, error) - if (!(error instanceof AxiosError)) throw error - throw handleAxiosError(error) - }) + return axios.get(decodeURIComponent(url)).then(({ data }) => data) } diff --git a/drama-queen/src/core/usecases/synchronizeData/thunks.ts b/drama-queen/src/core/usecases/synchronizeData/thunks.ts index 7f80c024..3b87d178 100644 --- a/drama-queen/src/core/usecases/synchronizeData/thunks.ts +++ b/drama-queen/src/core/usecases/synchronizeData/thunks.ts @@ -187,8 +187,21 @@ export const thunks = { // we sychronize the external ressource only if there is a url for getting them if (externalResourcesUrl) { // get the list of external questionnaires - const externalQuestionnaires = - await getExternalQuestionnaires(externalResourcesUrl) + const externalQuestionnaires = await getExternalQuestionnaires( + externalResourcesUrl + ).catch((error) => { + if ( + error instanceof AxiosError && + error.response && + [400, 403, 404, 500].includes(error.response.status) + ) { + console.error( + `An error occurred while fetching external questionnaires list`, + error + ) + } + throw error + }) const { neededQuestionnaires, notNeededQuestionnaires } = getExternalQuestionnaireFiltered( @@ -209,9 +222,16 @@ export const thunks = { await getResourcesFromExternalQuestionnaire( externalResourcesUrl, questionnaire - ).then(() => - dispatch(actions.downloadExternalResourceCompleted()) ) + .then(() => + dispatch(actions.downloadExternalResourceCompleted()) + ) + .catch((error) => + console.error( + `An error occurred while fetching external resources of questionnaire ${questionnaire.id}`, + error + ) + ) }) ) diff --git a/drama-queen/src/core/usecases/visualizeSurvey/thunks.ts b/drama-queen/src/core/usecases/visualizeSurvey/thunks.ts index 7704312a..a382bafc 100644 --- a/drama-queen/src/core/usecases/visualizeSurvey/thunks.ts +++ b/drama-queen/src/core/usecases/visualizeSurvey/thunks.ts @@ -10,6 +10,7 @@ import { makeSearchParamsObjSchema } from 'core/tools/makeSearchParamsObjectSche import { fetchUrl } from 'core/tools/fetchUrl' import { isSurveyCompatibleWithQueen } from 'core/tools/SurveyModelBreaking' import { getTranslation } from 'i18n' +import { AxiosError } from 'axios' const { t } = getTranslation('errorMessage') @@ -49,12 +50,17 @@ export const thunks = { Questionnaire | WrappedQuestionnaire >({ url: questionnaire, + }).catch((error) => { + if ( + error instanceof AxiosError && + error.response && + [400, 403, 404, 500].includes(error.response.status) + ) { + throw new Error(t('questionnaireNotFound', { questionnaireId: '' })) + } + throw error }) - if (fetchedSource === undefined) { - return null - } - const isWrappedQuestionnaire = ( source: Questionnaire | WrappedQuestionnaire ): source is WrappedQuestionnaire => { From 02e9aa376dffc1bf186b163e581dfd882bd3f5b0 Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Mon, 14 Oct 2024 11:45:52 +0200 Subject: [PATCH 10/21] chore: remove unused functions & imports --- drama-queen/src/core/tools/externalResources.ts | 11 ----------- drama-queen/src/core/tools/fetchUrl.ts | 7 +++---- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/drama-queen/src/core/tools/externalResources.ts b/drama-queen/src/core/tools/externalResources.ts index 531c7824..5fb193e9 100644 --- a/drama-queen/src/core/tools/externalResources.ts +++ b/drama-queen/src/core/tools/externalResources.ts @@ -36,17 +36,6 @@ async function getTransformedManifest( return transformedManifest } -async function asyncFilter( - arr: T[], - predicate: (element: T) => Promise -): Promise { - // Map each element to a promise that resolves to a boolean - const results = await Promise.all(arr.map(predicate)) - - // Return elements where the corresponding result is true - return arr.filter((_, index) => results[index]) -} - // Filter resources from manifest that are already cached, to avoid useless requests (overly large files). async function filterTransformedManifest( cacheName: string, diff --git a/drama-queen/src/core/tools/fetchUrl.ts b/drama-queen/src/core/tools/fetchUrl.ts index 5a604b46..b1b24032 100644 --- a/drama-queen/src/core/tools/fetchUrl.ts +++ b/drama-queen/src/core/tools/fetchUrl.ts @@ -1,12 +1,11 @@ -import axios, { AxiosError } from 'axios' -import { handleAxiosError } from './axiosError' +import axios from 'axios' /** * * @param params: { url : string} - * @returns {Promise} fetched data of type T or undefined if the request fails. + * @returns {Promise} fetched data of type T. */ -export async function fetchUrl(params: { url: string }) { +export async function fetchUrl(params: { url: string }): Promise { const { url } = params return axios.get(decodeURIComponent(url)).then(({ data }) => data) } From 2a0e2eae63794e56f12c6f3383cc0e63c40cf451 Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Mon, 14 Oct 2024 14:36:25 +0200 Subject: [PATCH 11/21] feat: delete old external resources caches --- .../src/core/tools/externalResources.ts | 19 +++++++++++++++++++ .../core/usecases/synchronizeData/thunks.ts | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/drama-queen/src/core/tools/externalResources.ts b/drama-queen/src/core/tools/externalResources.ts index 5fb193e9..162533dc 100644 --- a/drama-queen/src/core/tools/externalResources.ts +++ b/drama-queen/src/core/tools/externalResources.ts @@ -7,6 +7,8 @@ import type { } from 'core/model' import { fetchUrl } from './fetchUrl' +const externalQuestionnairesKeyword = 'gide' + // Get the list of external questionnaires from url export async function getExternalQuestionnaires( baseUrl: string @@ -112,3 +114,20 @@ export function getExternalQuestionnaireFiltered( } as ExternalQuestionnairesFiltered ) } + +// Get the list of caches where cacheName includes externalQuestionnairesKeyword, and that are not in needed external questionnaires +export async function getOldExternalCacheNames( + neededQuestionnaires: ExternalQuestionnaires +): Promise { + const neededCaches = neededQuestionnaires.map( + (questionnaire) => questionnaire.cacheName + ) + + const oldExternalCacheNames = (await caches.keys()).filter( + (cacheName) => + cacheName.toLowerCase().includes(externalQuestionnairesKeyword) && + !neededCaches.includes(cacheName) + ) + + return oldExternalCacheNames +} diff --git a/drama-queen/src/core/usecases/synchronizeData/thunks.ts b/drama-queen/src/core/usecases/synchronizeData/thunks.ts index 3b87d178..a21db62a 100644 --- a/drama-queen/src/core/usecases/synchronizeData/thunks.ts +++ b/drama-queen/src/core/usecases/synchronizeData/thunks.ts @@ -5,6 +5,7 @@ import type { Questionnaire } from 'core/model' import { getExternalQuestionnaireFiltered, getExternalQuestionnaires, + getOldExternalCacheNames, getResourcesFromExternalQuestionnaire, } from 'core/tools/externalResources' @@ -248,11 +249,20 @@ export const thunks = { ? caches.delete(externalResourcesRootCacheName) : Promise.resolve() + // delete old caches (that are not in external questionnaires list but sill in browser) : + const oldExternalCacheNames = + await getOldExternalCacheNames(neededQuestionnaires) + + const prDeleteOldExternalCaches = Promise.all( + oldExternalCacheNames.map((cacheName) => caches.delete(cacheName)) + ) + // We await untill the promises for external resources are finished await Promise.all([ prGetExternalResources, prDeleteExternalResources, prDeleteExternalRootCache, + prDeleteOldExternalCaches, ]) } From 98c59b8d691b998abd97d78df690e617390c23f8 Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Tue, 15 Oct 2024 11:05:56 +0200 Subject: [PATCH 12/21] docs: add external resources synchronization --- website/docs/synchronize.mdx | 50 +++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/website/docs/synchronize.mdx b/website/docs/synchronize.mdx index 14f69f10..32f012be 100644 --- a/website/docs/synchronize.mdx +++ b/website/docs/synchronize.mdx @@ -13,6 +13,50 @@ Cela assure que les données conservées sont bien celles modifiées en local pa - récupération, en parallèle : - des unités enquêtées pour chacune de ces campagnes - des nomenclatures pour chacun de ces modèles de questionnaire +- si nécessaire, phase de synchronisation de ressources externes + +### Synchronisation de ressources externes + +Si la variable d'environnement `VITE_EXTERNAL_RESOURCES_URL` est renseignée, une phase de synchronisation de ressources externes est effectuée. +Elle permet de récupérer en cache, des ressources externes nécessaires pour des questionnaires externes, récupérées à partir de l'url de ressources externes. + +#### Récupération de nouvelles ressources + +Phase de mise en cache des ressources externes nécessaires : + +- récupération de la liste des questionnaires externes : `${VITE_EXTERNAL_RESOURCES_URL}/gide-questionnaires.json` + +Exemple : +```` +{ + "questionnaires": [ + { + "id": "ms-2022", + "cacheName": "gide-cache-ms-2022-v1" + }, + { + "id": "st-2024", + "cacheName": "gide-cache-gide-st-2024-v2" + } + ] +} +```` + +- filtrage de cette liste pour ne conserver que les questionnaires dont l'utilisateur est concerné (cf. étape 3 de la synchronisation) +- pour chaque questionnaire conservé, mise en cache de ses ressources nécessaires + - récupération de la liste des ressources : `${VITE_EXTERNAL_RESOURCES_URL}/${questionnaire.id}/assets-manifest.json` + format : `[{ressource1: valeur1}, {ressource2: valeur2}, ...]` + - récupération de chaque valeur des ressources, en les transformant en url : `${VITE_EXTERNAL_RESOURCES_URL}/${valeur}` + - pour chaque 'url de ressources', on l'ajoute dans le cache `${questionnaire.cacheName}` si elle n'est pas encore présente + +#### Suppression de ressources plus utiles + +Phase de suppression du cache des ressources externes qui ne sont plus nécessaires : + +- filtrage de la liste des questionnaires externes (récupérée dans la phase précédente) pour ne conserver que les questionnaires dont l'utilisateur n'est pas concerné +- pour chaque questionnaire conservé, suppression du cache `${questionnaire.cacheName}` +- suppression de tous les caches incluant le mot `gide` : l'idée est de supprimer les caches des questionnaires externes qui ne seraient plus dans la liste de questionnaires externes +- si l'utilisateur n'est concerné par aucun questionnaire externe, on supprime le cache `cache-root-external` ## Gestion des erreurs @@ -20,7 +64,11 @@ Cela assure que les données conservées sont bien celles modifiées en local pa | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------ | | Récupération
  • d'une campagne
  • d'un modèle de questionnaire
  • d'une unité enquêtée
  • d'une nomenclature
| format non reconnu | Arrêt de la synchronisation | | Récupération d'une unité enquêtée | 400, 403, 404, 500 | Unité non récupérée, poursuite de la synchronisation | -| Envoi d'une unité enquêtée | 400, 403, 404, 500 | Unité non mise à jour, suppression de l'unité en locale (déplacée en "temp zone"), poursuite de la synchronisation | +| Envoi d'une unité enquêtée | 400, 403, 404, 500 | Unité non mise à jour, suppression de l'unité en locale (déplacée en "temp zone"), poursuite de la synchronisation | +| Envoi d'une unité enquêtée en "temp zone" | 400, 403, 404, 500 | Arrêt de la synchronisation | +| Récupération da la liste des questionnaires externes | 400, 403, 404, 500 | Arrêt de la synchronisation | +| Récupération das ressources d'un questionnaire externe | toutes | Ressource non récupérée, poursuite de la synchronisation | + ## Appels Api From 8a3256a748b04612ace329f53b05ef30b7507b94 Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Tue, 15 Oct 2024 11:21:41 +0200 Subject: [PATCH 13/21] refactor: simplify externalResources functions --- .../src/core/tools/externalResources.ts | 22 +++++------- .../core/usecases/synchronizeData/thunks.ts | 36 +++++++++---------- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/drama-queen/src/core/tools/externalResources.ts b/drama-queen/src/core/tools/externalResources.ts index 162533dc..b6c87572 100644 --- a/drama-queen/src/core/tools/externalResources.ts +++ b/drama-queen/src/core/tools/externalResources.ts @@ -7,32 +7,30 @@ import type { } from 'core/model' import { fetchUrl } from './fetchUrl' +export const externalResourcesUrl = import.meta.env.VITE_EXTERNAL_RESOURCES_URL const externalQuestionnairesKeyword = 'gide' -// Get the list of external questionnaires from url -export async function getExternalQuestionnaires( - baseUrl: string -): Promise { +// Get the list of external questionnaires +export async function getExternalQuestionnaires(): Promise { const questionnairesWrapper = await fetchUrl({ - url: `${baseUrl}/gide-questionnaires.json`, + url: `${externalResourcesUrl}/gide-questionnaires.json`, }) return questionnairesWrapper.questionnaires } -// Get the list of external resource URLs for a questionnaireId from url +// Get the list of external resource URLs for a questionnaireId async function getTransformedManifest( - baseUrl: string, questionnaireId: string ): Promise { // get the manifest for a questionnaireId const manifest = await fetchUrl({ - url: `${baseUrl}/${questionnaireId}/assets-manifest.json`, + url: `${externalResourcesUrl}/${questionnaireId}/assets-manifest.json`, }) // Transform the manifest values into resource URLs, and get an array of these resource URLs const transformedManifest = Object.values(manifest).map( - (resourceUrl) => `${baseUrl}/${resourceUrl}` + (resourceUrl) => `${externalResourcesUrl}/${resourceUrl}` ) return transformedManifest @@ -66,13 +64,9 @@ async function filterTransformedManifest( // Cache every external resources (not already cached) for a particular questionnaire export async function getResourcesFromExternalQuestionnaire( - baseUrl: string, questionnaire: ExternalQuestionnaire ): Promise { - const transformedManifest = await getTransformedManifest( - baseUrl, - questionnaire.id - ) + const transformedManifest = await getTransformedManifest(questionnaire.id) const filteredTransformedManifest = await filterTransformedManifest( questionnaire.cacheName, diff --git a/drama-queen/src/core/usecases/synchronizeData/thunks.ts b/drama-queen/src/core/usecases/synchronizeData/thunks.ts index a21db62a..eede8552 100644 --- a/drama-queen/src/core/usecases/synchronizeData/thunks.ts +++ b/drama-queen/src/core/usecases/synchronizeData/thunks.ts @@ -3,13 +3,13 @@ import { actions, name } from './state' import { AxiosError } from 'axios' import type { Questionnaire } from 'core/model' import { + externalResourcesUrl, getExternalQuestionnaireFiltered, getExternalQuestionnaires, getOldExternalCacheNames, getResourcesFromExternalQuestionnaire, } from 'core/tools/externalResources' -export const externalResourcesUrl = import.meta.env.VITE_EXTERNAL_RESOURCES_URL const externalResourcesRootCacheName = 'cache-root-external' export const thunks = { @@ -188,21 +188,20 @@ export const thunks = { // we sychronize the external ressource only if there is a url for getting them if (externalResourcesUrl) { // get the list of external questionnaires - const externalQuestionnaires = await getExternalQuestionnaires( - externalResourcesUrl - ).catch((error) => { - if ( - error instanceof AxiosError && - error.response && - [400, 403, 404, 500].includes(error.response.status) - ) { - console.error( - `An error occurred while fetching external questionnaires list`, - error - ) - } - throw error - }) + const externalQuestionnaires = + await getExternalQuestionnaires().catch((error) => { + if ( + error instanceof AxiosError && + error.response && + [400, 403, 404, 500].includes(error.response.status) + ) { + console.error( + `An error occurred while fetching external questionnaires list`, + error + ) + } + throw error + }) const { neededQuestionnaires, notNeededQuestionnaires } = getExternalQuestionnaireFiltered( @@ -220,10 +219,7 @@ export const thunks = { // add in cache the missing external resources for needed questionnaires const prGetExternalResources = Promise.all( neededQuestionnaires.map(async (questionnaire) => { - await getResourcesFromExternalQuestionnaire( - externalResourcesUrl, - questionnaire - ) + await getResourcesFromExternalQuestionnaire(questionnaire) .then(() => dispatch(actions.downloadExternalResourceCompleted()) ) From b5d374efa3fe7c74f4cc79f1dad7c2520a95e2f0 Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Tue, 15 Oct 2024 14:38:41 +0200 Subject: [PATCH 14/21] test: set vitest --- drama-queen/package.json | 8 +- drama-queen/src/tests/setup.ts | 7 + drama-queen/vitest.config.ts | 14 + yarn.lock | 533 ++++++++++++++++++++++++++++++++- 4 files changed, 556 insertions(+), 6 deletions(-) create mode 100644 drama-queen/src/tests/setup.ts create mode 100644 drama-queen/vitest.config.ts diff --git a/drama-queen/package.json b/drama-queen/package.json index 1ad6a2ea..5589ca3d 100644 --- a/drama-queen/package.json +++ b/drama-queen/package.json @@ -5,6 +5,7 @@ "type": "module", "scripts": { "dev": "vite --port 5001 --strictPort", + "test": "vitest run", "build": "tsc && vite build", "postbuild": "node build/remote-env.cjs remoteEntry.js", "preview": "vite preview --port 5001 --strictPort", @@ -37,16 +38,21 @@ "zod": "^3.22.4" }, "devDependencies": { + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.5.0", + "@testing-library/react": "^16.0.1", "@types/node": "^20.16.10", "@types/react": "^18.3.10", "@types/react-dom": "^18.2.22", "@vitejs/plugin-react": "^4.3.2", + "jsdom": "^25.0.1", "prettier": "^3.2.5", "ts-node": "^10.9.2", "typescript": "^5.6.2", "vite": "^5.4.8", "vite-envs": "^4.4.5", "vite-plugin-pwa": "^0.19.8", - "vite-tsconfig-paths": "^4.3.2" + "vite-tsconfig-paths": "^4.3.2", + "vitest": "^2.1.3" } } diff --git a/drama-queen/src/tests/setup.ts b/drama-queen/src/tests/setup.ts new file mode 100644 index 00000000..17cdac27 --- /dev/null +++ b/drama-queen/src/tests/setup.ts @@ -0,0 +1,7 @@ +import '@testing-library/jest-dom' +import { cleanup } from '@testing-library/react' +import { afterEach } from 'vitest' + +afterEach(() => { + cleanup() +}) diff --git a/drama-queen/vitest.config.ts b/drama-queen/vitest.config.ts new file mode 100644 index 00000000..d6ded49d --- /dev/null +++ b/drama-queen/vitest.config.ts @@ -0,0 +1,14 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import viteConfig from './vite.config' + +export default mergeConfig( + viteConfig, + defineConfig({ + test: { + globals: true, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + setupFiles: 'src/tests/setup.ts', + }, + }) +) diff --git a/yarn.lock b/yarn.lock index 77379b27..9f8c8e4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adobe/css-tools@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.0.tgz#728c484f4e10df03d5a3acd0d8adcbbebff8ad63" + integrity sha512-Ff9+ksdQQB3rMncgqDK78uLznstjyfIf2Arnh22pW8kBpLs6rpKDwgnZT46hin5Hl1WzazzK64DOrhSwYpS7bQ== + "@ampproject/remapping@^2.2.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" @@ -27,6 +32,14 @@ "@babel/highlight" "^7.24.7" picocolors "^1.0.0" +"@babel/code-frame@^7.10.4": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.25.7.tgz#438f2c524071531d643c6f0188e1e28f130cebc7" + integrity sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g== + dependencies: + "@babel/highlight" "^7.25.7" + picocolors "^1.0.0" + "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.2", "@babel/compat-data@^7.25.4": version "7.25.4" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb" @@ -204,6 +217,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== +"@babel/helper-validator-identifier@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz#77b7f60c40b15c97df735b38a66ba1d7c3e93da5" + integrity sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg== + "@babel/helper-validator-option@^7.24.8": version "7.24.8" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" @@ -236,6 +254,16 @@ js-tokens "^4.0.0" picocolors "^1.0.0" +"@babel/highlight@^7.25.7": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.25.7.tgz#20383b5f442aa606e7b5e3043b0b1aafe9f37de5" + integrity sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw== + dependencies: + "@babel/helper-validator-identifier" "^7.25.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.25.0", "@babel/parser@^7.25.4": version "7.25.4" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.4.tgz#af4f2df7d02440286b7de57b1c21acfb2a6f257a" @@ -1308,7 +1336,7 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== @@ -1997,6 +2025,40 @@ magic-string "^0.25.0" string.prototype.matchall "^4.0.6" +"@testing-library/dom@^10.4.0": + version "10.4.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-10.4.0.tgz#82a9d9462f11d240ecadbf406607c6ceeeff43a8" + integrity sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^5.0.1" + aria-query "5.3.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.5.0" + pretty-format "^27.0.2" + +"@testing-library/jest-dom@^6.5.0": + version "6.5.0" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.5.0.tgz#50484da3f80fb222a853479f618a9ce5c47bfe54" + integrity sha512-xGGHpBXYSHUUr6XsKBfs85TWlYKpTc37cSBBVrXcib2MkHLboWlkClhWF37JKlDb9KEq3dHs+f2xR7XJEWGBxA== + dependencies: + "@adobe/css-tools" "^4.4.0" + aria-query "^5.0.0" + chalk "^3.0.0" + css.escape "^1.5.1" + dom-accessibility-api "^0.6.3" + lodash "^4.17.21" + redent "^3.0.0" + +"@testing-library/react@^16.0.1": + version "16.0.1" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.0.1.tgz#29c0ee878d672703f5e7579f239005e4e0faa875" + integrity sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg== + dependencies: + "@babel/runtime" "^7.12.5" + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" @@ -2035,6 +2097,11 @@ "@tufjs/canonical-json" "1.0.0" minimatch "^9.0.0" +"@types/aria-query@^5.0.1": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-5.0.4.tgz#1a31c3d378850d2778dabb6374d036dcba4ba708" + integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw== + "@types/babel__core@^7.20.5": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -2214,6 +2281,65 @@ "@types/babel__core" "^7.20.5" react-refresh "^0.14.2" +"@vitest/expect@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-2.1.3.tgz#4b9a6fff22be4c4cd5d57e687cfda611b514b0ad" + integrity sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ== + dependencies: + "@vitest/spy" "2.1.3" + "@vitest/utils" "2.1.3" + chai "^5.1.1" + tinyrainbow "^1.2.0" + +"@vitest/mocker@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@vitest/mocker/-/mocker-2.1.3.tgz#a3593b426551be5715fa108faf04f8a9ddb0a9cc" + integrity sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ== + dependencies: + "@vitest/spy" "2.1.3" + estree-walker "^3.0.3" + magic-string "^0.30.11" + +"@vitest/pretty-format@2.1.3", "@vitest/pretty-format@^2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@vitest/pretty-format/-/pretty-format-2.1.3.tgz#48b9b03de75507d1d493df7beb48dc39a1946a3e" + integrity sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ== + dependencies: + tinyrainbow "^1.2.0" + +"@vitest/runner@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@vitest/runner/-/runner-2.1.3.tgz#20a6da112007dfd92969951df189c6da66c9dac4" + integrity sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ== + dependencies: + "@vitest/utils" "2.1.3" + pathe "^1.1.2" + +"@vitest/snapshot@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@vitest/snapshot/-/snapshot-2.1.3.tgz#1b405a9c40a82563605b13fdc045217751069e58" + integrity sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg== + dependencies: + "@vitest/pretty-format" "2.1.3" + magic-string "^0.30.11" + pathe "^1.1.2" + +"@vitest/spy@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@vitest/spy/-/spy-2.1.3.tgz#2c8a457673094ec4c1ab7c50cb11c58e3624ada2" + integrity sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ== + dependencies: + tinyspy "^3.0.0" + +"@vitest/utils@2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@vitest/utils/-/utils-2.1.3.tgz#e52aa5745384091b151cbdf79bb5a3ad2bea88d2" + integrity sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA== + dependencies: + "@vitest/pretty-format" "2.1.3" + loupe "^3.1.1" + tinyrainbow "^1.2.0" + "@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" @@ -2271,6 +2397,13 @@ agent-base@6, agent-base@^6.0.2: dependencies: debug "4" +agent-base@^7.0.2, agent-base@^7.1.0: + version "7.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" + integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== + dependencies: + debug "^4.3.4" + agentkeepalive@^4.2.1: version "4.5.0" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" @@ -2390,6 +2523,18 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +aria-query@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== + dependencies: + dequal "^2.0.3" + +aria-query@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.2.tgz#93f81a43480e33a338f19163a3d10a50c01dcd59" + integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw== + array-buffer-byte-length@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz#1e5583ec16763540a27ae52eed99ff899223568f" @@ -2437,6 +2582,11 @@ arrify@^2.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + async@^2.6.4: version "2.6.4" resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221" @@ -2625,6 +2775,11 @@ byte-size@8.1.1: resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-8.1.1.tgz#3424608c62d59de5bfda05d31e0313c6174842ae" integrity sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg== +cac@^6.7.14: + version "6.7.14" + resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" + integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ== + cacache@^16.1.0: version "16.1.3" resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" @@ -2707,6 +2862,17 @@ ccount@^2.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== +chai@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/chai/-/chai-5.1.1.tgz#f035d9792a22b481ead1c65908d14bb62ec1c82c" + integrity sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA== + dependencies: + assertion-error "^2.0.1" + check-error "^2.1.1" + deep-eql "^5.0.1" + loupe "^3.1.0" + pathval "^2.0.0" + chalk@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" @@ -2724,6 +2890,14 @@ chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -2762,6 +2936,11 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +check-error@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" + integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== + "chokidar@>=3.0.0 <4.0.0": version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" @@ -3088,6 +3267,18 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== +css.escape@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" + integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg== + +cssstyle@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-4.1.0.tgz#161faee382af1bafadb6d3867a92a19bcb4aea70" + integrity sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA== + dependencies: + rrweb-cssom "^0.7.1" + csstype@^3.0.2, csstype@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" @@ -3119,6 +3310,14 @@ data-forge@^1.8.8: papaparse "5.2.0" typy "^3.0.1" +data-urls@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-5.0.0.tgz#2f76906bce1824429ffecb6920f45a0b30f00dde" + integrity sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg== + dependencies: + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" + data-view-buffer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" @@ -3177,6 +3376,13 @@ debug@^3.2.7: dependencies: ms "^2.1.1" +debug@^4.3.6: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + decamelize-keys@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.1.tgz#04a2d523b2f18d80d0158a43b895d56dff8d19d8" @@ -3190,6 +3396,11 @@ decamelize@^1.1.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== +decimal.js@^10.4.3: + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + decode-named-character-reference@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz#daabac9690874c394c81e4162a0304b35d824f0e" @@ -3202,6 +3413,11 @@ dedent@0.7.0: resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== +deep-eql@^5.0.1: + version "5.0.2" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-5.0.2.tgz#4b756d8d770a9257300825d52a2c2cff99c3a341" + integrity sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q== + deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" @@ -3252,7 +3468,7 @@ deprecation@^2.0.0: resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919" integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ== -dequal@^2.0.0: +dequal@^2.0.0, dequal@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== @@ -3296,6 +3512,16 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +dom-accessibility-api@^0.5.9: + version "0.5.16" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz#5a7429e6066eb3664d911e33fb0e45de8eb08453" + integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg== + +dom-accessibility-api@^0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz#993e925cc1d73f2c662e7d75dd5a5445259a8fd8" + integrity sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w== + dom-helpers@^5.0.1: version "5.2.1" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" @@ -3391,6 +3617,11 @@ enquirer@~2.3.6: dependencies: ansi-colors "^4.1.1" +entities@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + env-paths@^2.2.0: version "2.2.1" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" @@ -3618,7 +3849,7 @@ estree-walker@^2.0.2: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== -estree-walker@^3.0.2: +estree-walker@^3.0.2, estree-walker@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== @@ -4262,6 +4493,13 @@ html-encoding-sniffer@^3.0.0: dependencies: whatwg-encoding "^2.0.0" +html-encoding-sniffer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz#696df529a7cfd82446369dc5193e590a3735b448" + integrity sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ== + dependencies: + whatwg-encoding "^3.1.1" + html-url-attributes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/html-url-attributes/-/html-url-attributes-3.0.0.tgz#fc4abf0c3fb437e2329c678b80abb3c62cff6f08" @@ -4281,6 +4519,14 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" +http-proxy-agent@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz#9a8b1f246866c028509486585f62b8f2c18c270e" + integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== + dependencies: + agent-base "^7.1.0" + debug "^4.3.4" + http-proxy@^1.18.1: version "1.18.1" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" @@ -4317,6 +4563,14 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +https-proxy-agent@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" + integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== + dependencies: + agent-base "^7.0.2" + debug "4" + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" @@ -4679,6 +4933,11 @@ is-plain-object@^5.0.0: resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + is-promise@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" @@ -4847,6 +5106,33 @@ jsbn@1.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== +jsdom@^25.0.1: + version "25.0.1" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-25.0.1.tgz#536ec685c288fc8a5773a65f82d8b44badcc73ef" + integrity sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw== + dependencies: + cssstyle "^4.1.0" + data-urls "^5.0.0" + decimal.js "^10.4.3" + form-data "^4.0.0" + html-encoding-sniffer "^4.0.0" + http-proxy-agent "^7.0.2" + https-proxy-agent "^7.0.5" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.12" + parse5 "^7.1.2" + rrweb-cssom "^0.7.1" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^5.0.0" + w3c-xmlserializer "^5.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^3.1.1" + whatwg-mimetype "^4.0.0" + whatwg-url "^14.0.0" + ws "^8.18.0" + xml-name-validator "^5.0.0" + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -5127,6 +5413,11 @@ loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +loupe@^3.1.0, loupe@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-3.1.2.tgz#c86e0696804a02218f2206124c45d8b15291a240" + integrity sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg== + lru-cache@^10.2.0: version "10.4.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" @@ -5158,6 +5449,11 @@ lru-queue@^0.1.0: dependencies: es5-ext "~0.10.2" +lz-string@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941" + integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ== + magic-string@^0.25.0, magic-string@^0.25.7: version "0.25.9" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" @@ -5172,6 +5468,13 @@ magic-string@^0.27.0: dependencies: "@jridgewell/sourcemap-codec" "^1.4.13" +magic-string@^0.30.11: + version "0.30.12" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.12.tgz#9eb11c9d072b9bcb4940a5b2c2e1a217e4ee1a60" + integrity sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + make-dir@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" @@ -5807,7 +6110,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@^2.0.0, ms@^2.1.1: +ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -6068,6 +6371,11 @@ numeral@^2.0.6: resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506" integrity sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA== +nwsapi@^2.2.12: + version "2.2.13" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.13.tgz#e56b4e98960e7a040e5474536587e599c4ff4655" + integrity sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ== + nx@16.10.0, "nx@>=16.5.1 < 17": version "16.10.0" resolved "https://registry.yarnpkg.com/nx/-/nx-16.10.0.tgz#b070461f7de0a3d7988bd78558ea84cda3543ace" @@ -6373,6 +6681,13 @@ parse-url@^8.1.0: dependencies: parse-path "^7.0.0" +parse5@^7.1.2: + version "7.2.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.2.0.tgz#8a0591ce9b7c5e2027173ab737d4d3fc3d826fab" + integrity sha512-ZkDsAOcxsUMZ4Lz5fVciOehNcJ+Gb8gTzcA4yl3wnc273BAybYWrQ+Ks/OjCjSEpjvQkDSeZbybK9qj2VHHdGA== + dependencies: + entities "^4.5.0" + path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" @@ -6418,6 +6733,16 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + +pathval@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-2.0.0.tgz#7e2550b422601d4f6b8e26f1301bc8f15a741a25" + integrity sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA== + picocolors@^1.0.0, picocolors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" @@ -6507,6 +6832,15 @@ pretty-bytes@^6.1.1: resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-6.1.1.tgz#38cd6bb46f47afbf667c202cfc754bffd2016a3b" integrity sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ== +pretty-format@^27.0.2: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" @@ -6570,7 +6904,7 @@ proxy-from-env@^1.1.0: resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -6632,6 +6966,11 @@ react-is@^16.13.1, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + react-is@^18.0.0, react-is@^18.3.1: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" @@ -7038,6 +7377,11 @@ rollup@^4.20.0: "@rollup/rollup-win32-x64-msvc" "4.21.0" fsevents "~2.3.2" +rrweb-cssom@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz#c73451a484b86dd7cfb1e0b2898df4b703183e4b" + integrity sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg== + run-async@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -7107,6 +7451,13 @@ sass@^1.77.8: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + dependencies: + xmlchars "^2.2.0" + scheduler@^0.23.2: version "0.23.2" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" @@ -7209,6 +7560,11 @@ side-channel@^1.0.4, side-channel@^1.0.6: get-intrinsic "^1.2.4" object-inspect "^1.13.1" +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + signal-exit@3.0.7, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -7385,6 +7741,16 @@ ssri@^9.0.0, ssri@^9.0.1: dependencies: minipass "^3.1.1" +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +std-env@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" + integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== + "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -7577,6 +7943,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + tar-stream@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" @@ -7668,6 +8039,43 @@ timers-ext@^0.1.7: es5-ext "^0.10.64" next-tick "^1.1.0" +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.0.tgz#ed60cfce19c17799d4a241e06b31b0ec2bee69e6" + integrity sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg== + +tinypool@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-1.0.1.tgz#c64233c4fac4304e109a64340178760116dbe1fe" + integrity sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA== + +tinyrainbow@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz#5c57d2fc0fb3d1afd78465c33ca885d04f02abb5" + integrity sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ== + +tinyspy@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" + integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== + +tldts-core@^6.1.51: + version "6.1.51" + resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.51.tgz#abbb005cccc1c469ed7ddf1ec472acd91efda4d0" + integrity sha512-bu9oCYYWC1iRjx+3UnAjqCsfrWNZV1ghNQf49b3w5xE8J/tNShHTzp5syWJfwGH+pxUgTTLUnzHnfuydW7wmbg== + +tldts@^6.1.32: + version "6.1.51" + resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.51.tgz#ee5b35a939e733515f8cbfc882791ec87962e12c" + integrity sha512-33lfQoL0JsDogIbZ8fgRyvv77GnRtwkNE/MOKocwUgPO1WrSfsq7+vQRKxRQZai5zd+zg97Iv9fpFQSzHyWdLA== + dependencies: + tldts-core "^6.1.51" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -7692,6 +8100,13 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tough-cookie@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.0.0.tgz#6b6518e2b5c070cf742d872ee0f4f92d69eac1af" + integrity sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q== + dependencies: + tldts "^6.1.32" + tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -7699,6 +8114,13 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" +tr46@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec" + integrity sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g== + dependencies: + punycode "^2.3.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -8130,6 +8552,16 @@ vite-envs@^4.4.5: resolved "https://registry.yarnpkg.com/vite-envs/-/vite-envs-4.4.5.tgz#a8cfa780dbf7984ee28fa34f2c316ad1f9967c1f" integrity sha512-/fFJB2fkUM8U/qb3AAf9H1tt6vltAiiqMZScoAzne4pm0WPivWKS6hyJV4ei64C5M869FyBQ00M7wkuRVCy/9Q== +vite-node@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.3.tgz#8291d31f91c69dc22fea7909f4394c2b3cc2e2d9" + integrity sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA== + dependencies: + cac "^6.7.14" + debug "^4.3.6" + pathe "^1.1.2" + vite "^5.0.0" + vite-plugin-pwa@^0.19.8: version "0.19.8" resolved "https://registry.yarnpkg.com/vite-plugin-pwa/-/vite-plugin-pwa-0.19.8.tgz#f7be200a4426207358aef807b4a6e1ecbc14d345" @@ -8150,6 +8582,17 @@ vite-tsconfig-paths@^4.3.2: globrex "^0.1.2" tsconfck "^3.0.3" +vite@^5.0.0: + version "5.4.9" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.9.tgz#215c80cbebfd09ccbb9ceb8c0621391c9abdc19c" + integrity sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" + optionalDependencies: + fsevents "~2.3.3" + vite@^5.4.8: version "5.4.8" resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.8.tgz#af548ce1c211b2785478d3ba3e8da51e39a287e8" @@ -8161,6 +8604,38 @@ vite@^5.4.8: optionalDependencies: fsevents "~2.3.3" +vitest@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-2.1.3.tgz#dae1055dd328621b59fc6e594fd988fbf2e5370e" + integrity sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA== + dependencies: + "@vitest/expect" "2.1.3" + "@vitest/mocker" "2.1.3" + "@vitest/pretty-format" "^2.1.3" + "@vitest/runner" "2.1.3" + "@vitest/snapshot" "2.1.3" + "@vitest/spy" "2.1.3" + "@vitest/utils" "2.1.3" + chai "^5.1.1" + debug "^4.3.6" + magic-string "^0.30.11" + pathe "^1.1.2" + std-env "^3.7.0" + tinybench "^2.9.0" + tinyexec "^0.3.0" + tinypool "^1.0.0" + tinyrainbow "^1.2.0" + vite "^5.0.0" + vite-node "2.1.3" + why-is-node-running "^2.3.0" + +w3c-xmlserializer@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c" + integrity sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA== + dependencies: + xml-name-validator "^5.0.0" + wcwidth@>=1.0.1, wcwidth@^1.0.0, wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" @@ -8178,6 +8653,11 @@ webidl-conversions@^4.0.2: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + whatwg-encoding@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" @@ -8185,6 +8665,26 @@ whatwg-encoding@^2.0.0: dependencies: iconv-lite "0.6.3" +whatwg-encoding@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz#d0f4ef769905d426e1688f3e34381a99b60b76e5" + integrity sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ== + dependencies: + iconv-lite "0.6.3" + +whatwg-mimetype@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" + integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== + +whatwg-url@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-14.0.0.tgz#00baaa7fd198744910c4b1ef68378f2200e4ceb6" + integrity sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw== + dependencies: + tr46 "^5.0.0" + webidl-conversions "^7.0.0" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -8238,6 +8738,14 @@ which@^3.0.0: dependencies: isexe "^2.0.0" +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + wide-align@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" @@ -8487,6 +8995,21 @@ write-pkg@4.0.0: type-fest "^0.4.1" write-json-file "^3.2.0" +ws@^8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + +xml-name-validator@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673" + integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" From 0eba0a6ff70fb8e279c9e07628267c0f9d005a80 Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Wed, 16 Oct 2024 13:58:36 +0200 Subject: [PATCH 15/21] test: add tests for external resources synchro --- .../src/core/tools/externalResources.test.ts | 341 ++++++++++++++++++ .../src/core/tools/externalResources.ts | 12 +- 2 files changed, 348 insertions(+), 5 deletions(-) create mode 100644 drama-queen/src/core/tools/externalResources.test.ts diff --git a/drama-queen/src/core/tools/externalResources.test.ts b/drama-queen/src/core/tools/externalResources.test.ts new file mode 100644 index 00000000..64d3616d --- /dev/null +++ b/drama-queen/src/core/tools/externalResources.test.ts @@ -0,0 +1,341 @@ +import { describe, it, expect, vi, beforeAll, beforeEach } from 'vitest' +import { + externalResourcesUrl, + filterTransformedManifest, + getExternalQuestionnaireFiltered, + getExternalQuestionnaires, + getOldExternalCacheNames, + getTransformedManifest, +} from './externalResources' +import { fetchUrl } from './fetchUrl' +import type { + ExternalQuestionnaire, + ExternalQuestionnaires, + ExternalQuestionnairesFiltered, + ExternalQuestionnairesWrapper, + Manifest, +} from 'core/model' + +vi.mock('./fetchUrl', () => { + return { + fetchUrl: vi.fn(), + } +}) + +describe('getExternalQuestionnaires', () => { + it('should fetch and return list of external questionnaires', async () => { + // we must mock the response of fetch, expected to be ExternalQuestionnairesWrapper type + const questionnairesList: ExternalQuestionnairesWrapper = { + questionnaires: [ + { id: 'q1', cacheName: 'cache1' }, + { id: 'q2', cacheName: 'cache2' }, + ], + version: '1.0.0', + } + + // Mock fetchUrl to return the mockQuestionnaires + vi.mocked(fetchUrl).mockResolvedValueOnce(questionnairesList) + + const result = await getExternalQuestionnaires() + + // Assert fetchUrl was called with the correct url + expect(fetchUrl).toHaveBeenCalledWith({ + url: `${externalResourcesUrl}/gide-questionnaires.json`, + }) + + expect(result).toEqual(questionnairesList.questionnaires) + }) + + it('should return a empty list', async () => { + const emptyQuestionnaires: ExternalQuestionnairesWrapper = { + questionnaires: [], + } + vi.mocked(fetchUrl).mockResolvedValueOnce(emptyQuestionnaires) + + const result = await getExternalQuestionnaires() + + expect(result).toEqual([]) + }) +}) + +describe('getTransformedManifest', () => { + const questionnaireId = 'questionnaire-2024' + + it('should fetch and transform the manifest for a questionnaire', async () => { + // we must mock the response of fetch, expected to be Manifest type + const manifest: Manifest = { + resource1: 'qt/resource1', + resource2: 'qt/resource2', + } + + // Mock fetchUrl to return the mockManifest + vi.mocked(fetchUrl).mockResolvedValueOnce(manifest) + + const result = await getTransformedManifest(questionnaireId) + + // Assert fetchUrl was called with the correct URL + expect(fetchUrl).toHaveBeenCalledWith({ + url: `${externalResourcesUrl}/${questionnaireId}/assets-manifest.json`, + }) + + const expectedResult = [ + `${externalResourcesUrl}/qt/resource1`, + `${externalResourcesUrl}/qt/resource2`, + ] + + expect(result).toEqual(expectedResult) + }) + + it('should return a empty manifest', async () => { + const emptyManifest: Manifest = {} + vi.mocked(fetchUrl).mockResolvedValueOnce(emptyManifest) + + const result = await getTransformedManifest(questionnaireId) + + expect(result).toEqual([]) + }) +}) + +describe('filterTransformedManifest', () => { + const mockCaches: Record = {} + + const getMockCache = (cacheName: string) => { + if (!mockCaches[cacheName]) { + mockCaches[cacheName] = { + keys: vi.fn().mockResolvedValue([]), // Initialize with an empty keys method + } + } + return mockCaches[cacheName] + } + + beforeAll(() => { + // Mock the global caches object + ;(global as any).caches = { + open: vi.fn().mockImplementation(async (cacheName: string) => { + return getMockCache(cacheName) + }), + } + }) + + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should return an array with uncached resources', async () => { + const cacheName = 'testCache' + const transformedManifest = [ + 'https://example.com/resource1', + 'https://example.com/resource3', + 'https://example.com/resource4', + ] + + const mockCache = getMockCache(cacheName) + + // Initialize the cached resources for the specific cacheName + mockCache.keys.mockResolvedValueOnce([ + new Request('https://example.com/resource1'), + new Request('https://example.com/resource2'), + ]) + + const result = await filterTransformedManifest( + cacheName, + transformedManifest + ) + + const expectedResult = [ + 'https://example.com/resource3', + 'https://example.com/resource4', + ] + + expect(result).toEqual(expectedResult) + }) + + it('should return all transformedManifest if cache is empty', async () => { + const cacheName = 'emptyCache' + const transformedManifest = [ + 'https://example.com/resource1', + 'https://example.com/resource3', + ] + + const mockCache = getMockCache(cacheName) + + // Initialize the cached resources (empty list) for the specific cacheName + mockCache.keys.mockResolvedValueOnce([]) + + const result = await filterTransformedManifest( + cacheName, + transformedManifest + ) + + expect(result).toEqual(transformedManifest) + }) + + it('should return an empty array if transformedManifest is empty', async () => { + const cacheName = 'testCache' + const transformedManifest: string[] = [] + + const result = await filterTransformedManifest( + cacheName, + transformedManifest + ) + + expect(result).toEqual([]) + }) +}) + +describe('getExternalQuestionnaireFiltered', () => { + it('should separate needed and not needed questionnaires', () => { + const neededIds = ['quest-2024-v2', 'quest-2025'] + const externalQuestionnaires: ExternalQuestionnaires = [ + { id: 'quest-2024', cacheName: 'cache1' }, + { id: 'quest-2025', cacheName: 'cache2' }, + { id: 'quest-2026', cacheName: 'cache3' }, + ] + + const result: ExternalQuestionnairesFiltered = + getExternalQuestionnaireFiltered(neededIds, externalQuestionnaires) + + expect(result.neededQuestionnaires).toEqual([ + { id: 'quest-2024', cacheName: 'cache1' }, + { id: 'quest-2025', cacheName: 'cache2' }, + ]) + expect(result.notNeededQuestionnaires).toEqual([ + { id: 'quest-2026', cacheName: 'cache3' }, + ]) + }) + + it('should return all questionnaires as needed', () => { + const neededIds = ['quest-2024-v2', 'quest-2025'] + const externalQuestionnaires: ExternalQuestionnaires = [ + { id: 'quest-2024', cacheName: 'cache1' }, + { id: 'quest-2025', cacheName: 'cache2' }, + ] + + const result: ExternalQuestionnairesFiltered = + getExternalQuestionnaireFiltered(neededIds, externalQuestionnaires) + + expect(result.neededQuestionnaires).toEqual(externalQuestionnaires) + expect(result.notNeededQuestionnaires).toEqual([]) + }) + + it('should return all questionnaires as not needed if none match', () => { + const neededIds = ['quest-2024-v2', 'quest-2025'] + const externalQuestionnaires: ExternalQuestionnaires = [ + { id: 'quest-2026', cacheName: 'cache1' }, + { id: 'quest-2027', cacheName: 'cache2' }, + ] + + const result: ExternalQuestionnairesFiltered = + getExternalQuestionnaireFiltered(neededIds, externalQuestionnaires) + + expect(result.neededQuestionnaires).toEqual([]) + expect(result.notNeededQuestionnaires).toEqual(externalQuestionnaires) + }) + + it('should handle empty external questionnaires list', () => { + const neededIds: string[] = ['quest-2024-v2', 'quest-2025'] + const externalQuestionnaires: ExternalQuestionnaires = [] + + const result: ExternalQuestionnairesFiltered = + getExternalQuestionnaireFiltered(neededIds, externalQuestionnaires) + + expect(result.neededQuestionnaires).toEqual([]) + expect(result.notNeededQuestionnaires).toEqual([]) + }) + + it('should be case insensitive when matching IDs', () => { + const neededIds = ['QUEST-2024-v2'] + const externalQuestionnaires: ExternalQuestionnaires = [ + { id: 'quest-2024', cacheName: 'cache1' }, + { id: 'quest-2026', cacheName: 'cache2' }, + ] + + const result: ExternalQuestionnairesFiltered = + getExternalQuestionnaireFiltered(neededIds, externalQuestionnaires) + + expect(result.neededQuestionnaires).toEqual([ + { id: 'quest-2024', cacheName: 'cache1' }, + ]) + expect(result.notNeededQuestionnaires).toEqual([ + { id: 'quest-2026', cacheName: 'cache2' }, + ]) + }) +}) + +describe('getOldExternalCacheNames', () => { + const mockCaches = { + keys: vi.fn(), + } + + beforeAll(() => { + // Mock the global caches object + ;(global as any).caches = mockCaches + }) + + beforeEach(() => { + vi.clearAllMocks() + }) + + it('should return old cache names not in the needed questionnaires', async () => { + const neededQuestionnaires: ExternalQuestionnaire[] = [ + { id: 'q1', cacheName: 'gide-cache1' }, + ] + + // Mock the caches.keys to return a list of cache names + mockCaches.keys.mockResolvedValueOnce([ + 'cache1', + 'gide-cache1', + 'cache2', + 'gide-cache2', + ]) + + const result = await getOldExternalCacheNames(neededQuestionnaires) + + expect(result).toEqual(['gide-cache2']) + }) + + it('should return all cache names containing "gide" if no needed questionnaires', async () => { + const neededQuestionnaires: ExternalQuestionnaire[] = [] + + mockCaches.keys.mockResolvedValueOnce([ + 'cache1', + 'gide-cache1', + 'cache2', + 'gide-cache2', + ]) + + const result = await getOldExternalCacheNames(neededQuestionnaires) + + const expectedResult = ['gide-cache1', 'gide-cache2'] + + expect(result).toEqual(expectedResult) + }) + + it('should return an empty array if all questionnaires are needed or no cache name contains "gide" ', async () => { + const neededQuestionnaires: ExternalQuestionnaire[] = [ + { id: 'q1', cacheName: 'gide-cache1' }, + ] + + mockCaches.keys.mockResolvedValueOnce(['cache1', 'gide-cache1']) + + const result = await getOldExternalCacheNames(neededQuestionnaires) + + expect(result).toEqual([]) + }) + + it('should handle case sensitivity in cache names', async () => { + const neededQuestionnaires: ExternalQuestionnaire[] = [ + { id: 'q1', cacheName: 'gide-CACHE1' }, + ] + + mockCaches.keys.mockResolvedValueOnce([ + 'gide-cache1', + 'gide-CACHE1', + 'GIDE-CACHE2', + ]) + + const result = await getOldExternalCacheNames(neededQuestionnaires) + + expect(result).toEqual(['gide-cache1', 'GIDE-CACHE2']) + }) +}) diff --git a/drama-queen/src/core/tools/externalResources.ts b/drama-queen/src/core/tools/externalResources.ts index b6c87572..8419fabf 100644 --- a/drama-queen/src/core/tools/externalResources.ts +++ b/drama-queen/src/core/tools/externalResources.ts @@ -20,7 +20,7 @@ export async function getExternalQuestionnaires(): Promise { // get the manifest for a questionnaireId @@ -37,7 +37,7 @@ async function getTransformedManifest( } // Filter resources from manifest that are already cached, to avoid useless requests (overly large files). -async function filterTransformedManifest( +export async function filterTransformedManifest( cacheName: string, transformedManifest: string[] ): Promise { @@ -79,8 +79,8 @@ export async function getResourcesFromExternalQuestionnaire( // Separate, from the list of external questionnaires, those that are needed and those that are not needed export function getExternalQuestionnaireFiltered( - neededQuestionnaireIds: string[] = [], - externalQuestionnaires: ExternalQuestionnaires = [] + neededQuestionnaireIds: string[], + externalQuestionnaires: ExternalQuestionnaires ): ExternalQuestionnairesFiltered { return externalQuestionnaires.reduce( (result, questionnaire) => { @@ -119,7 +119,9 @@ export async function getOldExternalCacheNames( const oldExternalCacheNames = (await caches.keys()).filter( (cacheName) => - cacheName.toLowerCase().includes(externalQuestionnairesKeyword) && + cacheName + .toLowerCase() + .includes(externalQuestionnairesKeyword.toLowerCase()) && !neededCaches.includes(cacheName) ) From 7c1b55c8c50134ba6d6c093303c04d483dd0e793 Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Wed, 16 Oct 2024 14:01:02 +0200 Subject: [PATCH 16/21] fix: import const --- drama-queen/src/core/usecases/synchronizeData/state.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drama-queen/src/core/usecases/synchronizeData/state.ts b/drama-queen/src/core/usecases/synchronizeData/state.ts index c04c14fc..b6088a76 100644 --- a/drama-queen/src/core/usecases/synchronizeData/state.ts +++ b/drama-queen/src/core/usecases/synchronizeData/state.ts @@ -1,7 +1,7 @@ import { createUsecaseActions } from 'redux-clean-architecture' import { id } from 'tsafe/id' import { assert } from 'tsafe/assert' -import { externalResourcesUrl } from './thunks' +import { externalResourcesUrl } from 'core/tools/externalResources' export type State = State.NotRunning | State.Running From d0451711c678792d6c4800c8a338b23dd89086a3 Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Wed, 16 Oct 2024 14:01:36 +0200 Subject: [PATCH 17/21] ci: add test in jobs --- .github/workflows/ci.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 907c4551..e71158c0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,7 +6,8 @@ on: branches: ["main", "2.*"] jobs: - build: + test_and_build: + name: Test & Build runs-on: ubuntu-latest if: github.event.head_commit.author.name != 'github-actions[bot]' environment: demo @@ -14,6 +15,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 - run: yarn + - run: yarn test - run: yarn build - uses: actions/upload-artifact@v4 with: @@ -21,7 +23,7 @@ jobs: path: drama-queen/dist check_if_version_upgraded: - needs: build + needs: test_and_build runs-on: ubuntu-latest if: | github.event_name == 'push' || From eacd59158f206c2b99f6ba05c9f3f525201b1279 Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Wed, 16 Oct 2024 14:10:48 +0200 Subject: [PATCH 18/21] ci: remove condition on target branch for pull_request --- .github/workflows/ci.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e71158c0..f9ccb1c9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,7 +3,6 @@ on: push: branches: ["main", "2.*"] pull_request: - branches: ["main", "2.*"] jobs: test_and_build: From 69397b986fefd93af5db1a4bba7c43bddbb2863f Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Wed, 16 Oct 2024 14:24:46 +0200 Subject: [PATCH 19/21] ci: fix test job --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index ab165cc5..f3fc1fb7 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "drama-queen" ], "scripts": { + "test": "lerna run test", "build": "lerna run build", "build:drama-queen": "lerna run build --scope 'drama-queen'", "serve:drama-queen": "lerna run serve --scope 'drama-queen'", From f66c946f0c2a61124d6e6126b2fd8e38bee3be98 Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Thu, 17 Oct 2024 11:20:54 +0200 Subject: [PATCH 20/21] test : fix sonar config --- sonar-project.properties | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/sonar-project.properties b/sonar-project.properties index 4de6ef16..aba72a22 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -7,20 +7,14 @@ sonar.projectVersion=1.0 # Path to sources sonar.sources=drama-queen/src -sonar.exclusions=**/*.spec.js, **/*.spec.jsx, **/*.spec.ts, **/*.spec.tsx +sonar.exclusions=**/*.test.js, **/*.test.jsx, **/*.test.ts, **/*.test.tsx # Path to tests -sonar.test.inclusions=drama-queen/src/**/*.spec.js, drama-queen/src/**/*.spec.jsx, drama-queen/src/**/*.spec.ts, drama-queen/src/**/*.spec.tsx +sonar.test.inclusions=drama-queen/src/**/*.test.js, drama-queen/src/**/*.test.jsx, drama-queen/src/**/*.test.ts, drama-queen/src/**/*.test.tsx # Coverage sonar.javascript.lcov.reportPaths=coverage/lcov.info -sonar.coverage.exclusions=drama-queen/src/**/*.spec.js, drama-queen/src/**/*.spec.jsx, drama-queen/src/**/*.spec.ts, drama-queen/src/**/*.spec.tsx +sonar.coverage.exclusions=drama-queen/src/**/*.test.js, drama-queen/src/**/*.test.jsx, drama-queen/src/**/*.test.ts, drama-queen/src/**/*.test.tsx # Source encoding -sonar.sourceEncoding=UTF-8 - -sonar.issue.ignore.multicriteria=ignorePropsForJsFile - -#ignoreMethodNameConventionForTest -sonar.issue.ignore.multicriteria.ignorePropsForJsFile.ruleKey=javascript:S6774 -sonar.issue.ignore.multicriteria.ignorePropsForJsFile.resourceKey=** \ No newline at end of file +sonar.sourceEncoding=UTF-8 \ No newline at end of file From 8785b88a5bde1c5b4a37f0a682ea6b8770ab914f Mon Sep 17 00:00:00 2001 From: Quentin Ruhier Date: Fri, 18 Oct 2024 10:10:27 +0200 Subject: [PATCH 21/21] docs : update external resources synchro --- website/docs/synchronize.mdx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/docs/synchronize.mdx b/website/docs/synchronize.mdx index 32f012be..fd3c9b74 100644 --- a/website/docs/synchronize.mdx +++ b/website/docs/synchronize.mdx @@ -42,7 +42,8 @@ Exemple : } ```` -- filtrage de cette liste pour ne conserver que les questionnaires dont l'utilisateur est concerné (cf. étape 3 de la synchronisation) +- filtrage de cette liste pour ne conserver que les questionnaires dont l'utilisateur est concerné (questionnaires récupérés lors de l'étape 3 de la synchronisation), selon la règle suivante : + un questionnaire de la liste est conservé si son `id` est inclus dans au moins un des `questionnaireId` dont l'utilisateur est concerné (comparaison insensible à la casse) - pour chaque questionnaire conservé, mise en cache de ses ressources nécessaires - récupération de la liste des ressources : `${VITE_EXTERNAL_RESOURCES_URL}/${questionnaire.id}/assets-manifest.json` format : `[{ressource1: valeur1}, {ressource2: valeur2}, ...]`