From a72212f10e351496134e4b1eac2f9ae668bebfed Mon Sep 17 00:00:00 2001 From: Laurent Caouissin <38245508+laurentC35@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:36:58 +0100 Subject: [PATCH] feat: add talking surveys (#258) * build: add env var for talking surveys * feat(#240): add capmi app to the router rules * feat(#242): inject externalResources script at start * feat(#238): add global variable before initalize with Lunatic fix: #238 * fix: types in LunaticData * fix: types again We use only one function to initalize surveyUnit correctly Co-Authored-By: Quentin Ruhier <99256289+QRuhier@users.noreply.github.com> * feat(#231): add external service-worker in condition where VITE_EXTERNAL_RESOURCES_URL is defined (#252) fix: #231 * feat: add talking questionnaires synchro (#246) * feat: get externalResources during synchronization * chore: add comments & remove unused imports/exports * fix: get external resources only for needed questionnaires * feat: delete external resources for not needed questionnaires * feat: delete external resources root-cache if no external questionnaire needed * feat: handle progress bar for external resources synchro * fix: handle promises for external resources * perf: optimize cache lookup during external resources synchronization * feat : handle errors on fetching external url * chore: remove unused functions & imports * feat: delete old external resources caches * docs: add external resources synchronization * refactor: simplify externalResources functions * test: set vitest * test: add tests for external resources synchro * fix: import const * ci: add test in jobs * ci: remove condition on target branch for pull_request * ci: fix test job * test : fix sonar config * docs : update external resources synchro * docs: improve external resources synchro * refactor: remove multiple imports + uppercase constants (#257) * refactor : set const for imports of env var VITE_EXTERNAL_RESOURCES_URL * refactor: uppercase constants * refactor: regroup core exported constants * chore: remove unused import * fix: duplicate export * test(sonar) : fix coverage config * ci : adapt CI for tests & sonar * ci: rename ci & jobs --------- Co-authored-by: Quentin Ruhier Co-authored-by: Quentin Ruhier <99256289+QRuhier@users.noreply.github.com> Co-authored-by: Emmanuel DEMEY --- .env.drama.sample | 1 + .github/workflows/ci.yaml | 4 +- .github/workflows/sonar.yaml | 23 +- .gitignore | 1 + drama-queen/.env | 2 + drama-queen/package.json | 10 +- drama-queen/src/bootstrap.tsx | 9 +- drama-queen/src/core/constants.ts | 4 + drama-queen/src/core/index.ts | 2 - .../src/core/model/ExternalResources.ts | 20 + drama-queen/src/core/model/index.ts | 3 +- .../src/core/tools/SurveyModelBreaking.ts | 7 +- .../src/core/tools/externalResources.test.ts | 341 ++++++++++ .../src/core/tools/externalResources.ts | 129 ++++ drama-queen/src/core/tools/fetchUrl.ts | 16 +- .../usecases/synchronizeData/selectors.ts | 19 + .../core/usecases/synchronizeData/state.ts | 26 + .../core/usecases/synchronizeData/thunks.ts | 90 +++ .../core/usecases/visualizeSurvey/thunks.ts | 14 +- drama-queen/src/custom.d.ts | 7 + drama-queen/src/i18n/resources/en.ts | 5 +- drama-queen/src/i18n/resources/fr.ts | 5 +- drama-queen/src/i18n/types.ts | 1 + drama-queen/src/main.tsx | 6 +- drama-queen/src/queen-service-worker.js | 18 + drama-queen/src/tests/setup.ts | 7 + .../components/orchestrator/Header/Header.tsx | 2 +- .../components/orchestrator/Orchestrator.tsx | 5 +- .../orchestrator/tools/functions.tsx | 28 +- .../src/ui/pages/External/External.tsx | 3 + .../ui/pages/synchronize/SynchronizeData.tsx | 10 + drama-queen/src/ui/routing/routes.tsx | 80 ++- drama-queen/src/unsubscribe_old_sw.ts | 2 +- drama-queen/src/vite-env.d.ts | 1 + drama-queen/vitest.config.ts | 17 + nx.json | 11 +- package.json | 2 + sonar-project.properties | 16 +- website/docs/synchronize.mdx | 51 +- yarn.lock | 625 +++++++++++++++++- 40 files changed, 1525 insertions(+), 98 deletions(-) create mode 100644 drama-queen/src/core/constants.ts create mode 100644 drama-queen/src/core/model/ExternalResources.ts create mode 100644 drama-queen/src/core/tools/externalResources.test.ts create mode 100644 drama-queen/src/core/tools/externalResources.ts create mode 100644 drama-queen/src/custom.d.ts create mode 100644 drama-queen/src/tests/setup.ts create mode 100644 drama-queen/src/ui/pages/External/External.tsx create mode 100644 drama-queen/vitest.config.ts diff --git a/.env.drama.sample b/.env.drama.sample index e4a28f80..2c374d81 100644 --- a/.env.drama.sample +++ b/.env.drama.sample @@ -1,4 +1,5 @@ VITE_BASE_URL=http://localhost:5000 VITE_QUEEN_API_URL= +VITE_EXTERNAL_RESOURCES_URL= VITE_OIDC_ISSUER= VITE_OIDC_CLIENT_ID= \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 907c4551..3e0bd685 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,12 +1,12 @@ -name: ci +name: Main CI on: push: branches: ["main", "2.*"] pull_request: - branches: ["main", "2.*"] jobs: build: + name: Build runs-on: ubuntu-latest if: github.event.head_commit.author.name != 'github-actions[bot]' environment: demo diff --git a/.github/workflows/sonar.yaml b/.github/workflows/sonar.yaml index 39ed8834..19062c93 100644 --- a/.github/workflows/sonar.yaml +++ b/.github/workflows/sonar.yaml @@ -1,4 +1,4 @@ -name: Sonar +name: Sonar analysis on: push: branches: ["main", "2.*"] @@ -6,13 +6,34 @@ on: types: [opened, synchronize, reopened] jobs: + test: + name: Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + - run: yarn + # Run tests sonar reports + - run: yarn test:coverage + - name: Upload coverage artifact + uses: actions/upload-artifact@v4 + with: + name: coverage + path: drama-queen/coverage + sonarcloud: name: SonarCloud runs-on: ubuntu-latest + needs: test steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Download coverage artifact + uses: actions/download-artifact@v4 + with: + name: coverage + path: drama-queen/coverage - name: SonarCloud Scan uses: SonarSource/sonarcloud-github-action@master env: diff --git a/.gitignore b/.gitignore index 0fcb2fb1..f206f958 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ pnpm-debug.log* lerna-debug.log* node_modules +coverage dist dist-ssr *.local diff --git a/drama-queen/.env b/drama-queen/.env index 88269bd3..f97ef6a8 100644 --- a/drama-queen/.env +++ b/drama-queen/.env @@ -1,4 +1,6 @@ VITE_QUEEN_API_URL= +# If no environment variable for external resources is provided, the application will not trigger external resources treatments. +VITE_EXTERNAL_RESOURCES_URL= # If no environment variables for OIDC are provided, the application will use a mock OIDC. VITE_OIDC_ISSUER= VITE_OIDC_CLIENT_ID= \ No newline at end of file diff --git a/drama-queen/package.json b/drama-queen/package.json index 1ad6a2ea..143b75a6 100644 --- a/drama-queen/package.json +++ b/drama-queen/package.json @@ -5,6 +5,8 @@ "type": "module", "scripts": { "dev": "vite --port 5001 --strictPort", + "test": "vitest", + "test:coverage": "vitest --coverage", "build": "tsc && vite build", "postbuild": "node build/remote-env.cjs remoteEntry.js", "preview": "vite preview --port 5001 --strictPort", @@ -37,16 +39,22 @@ "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", + "@vitest/coverage-v8": "^2.1.3", + "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/bootstrap.tsx b/drama-queen/src/bootstrap.tsx index c8f06b9e..e179d36b 100644 --- a/drama-queen/src/bootstrap.tsx +++ b/drama-queen/src/bootstrap.tsx @@ -37,4 +37,11 @@ const mount = ({ return () => queueMicrotask(() => root.unmount()) } -export { mount } +const mountExternalResources = (externalResourcesUrl: string) => { + console.log('Mount External resources') + const script = document.createElement('script') + script.src = `${externalResourcesUrl}/entry.js` + document.body.appendChild(script) +} + +export { mount, mountExternalResources } diff --git a/drama-queen/src/core/constants.ts b/drama-queen/src/core/constants.ts new file mode 100644 index 00000000..ab51c12a --- /dev/null +++ b/drama-queen/src/core/constants.ts @@ -0,0 +1,4 @@ +export const DYNAMIC_PUBLIC_URL = new URL(import.meta.url).origin +export const EXTERNAL_RESOURCES_URL = import.meta.env + .VITE_EXTERNAL_RESOURCES_URL +export const LUNATIC_MODEL_VERSION_BREAKING = '2.2.10' diff --git a/drama-queen/src/core/index.ts b/drama-queen/src/core/index.ts index 609a2f9d..3cfbbd7c 100644 --- a/drama-queen/src/core/index.ts +++ b/drama-queen/src/core/index.ts @@ -8,5 +8,3 @@ import { bootstrapCore } from 'core/bootstrap' export const { createCoreProvider, useCore, useCoreState } = createReactApi({ bootstrapCore, }) - -export const DYNAMIC_PUBLIC_URL = new URL(import.meta.url).origin diff --git a/drama-queen/src/core/model/ExternalResources.ts b/drama-queen/src/core/model/ExternalResources.ts new file mode 100644 index 00000000..8c8cae0c --- /dev/null +++ b/drama-queen/src/core/model/ExternalResources.ts @@ -0,0 +1,20 @@ +export type ExternalQuestionnaire = { + id: string + cacheName: string +} + +export type ExternalQuestionnaires = ExternalQuestionnaire[] + +export type ExternalQuestionnairesWrapper = { + questionnaires: ExternalQuestionnaires + version?: string +} + +export type ExternalQuestionnairesFiltered = { + neededQuestionnaires: ExternalQuestionnaires + notNeededQuestionnaires: ExternalQuestionnaires +} + +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/SurveyModelBreaking.ts b/drama-queen/src/core/tools/SurveyModelBreaking.ts index cd5817f6..8b0ad155 100644 --- a/drama-queen/src/core/tools/SurveyModelBreaking.ts +++ b/drama-queen/src/core/tools/SurveyModelBreaking.ts @@ -1,8 +1,7 @@ +import { LUNATIC_MODEL_VERSION_BREAKING } from 'core/constants' import type { Questionnaire } from 'core/model' import { getTranslation } from 'i18n' -export const lunaticModelVersionBreaking = '2.2.10' - const { t } = getTranslation('errorMessage') const semverCompare = new Intl.Collator('en', { numeric: true }).compare @@ -23,5 +22,7 @@ export const isSurveyCompatibleWithQueen = (params: { return true } - return semverCompare(lunaticModelVersion, lunaticModelVersionBreaking) === 1 + return ( + semverCompare(lunaticModelVersion, LUNATIC_MODEL_VERSION_BREAKING) === 1 + ) } 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..86a26315 --- /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 { + filterTransformedManifest, + getExternalQuestionnaireFiltered, + getExternalQuestionnaires, + getOldExternalCacheNames, + getTransformedManifest, +} from './externalResources' +import { fetchUrl } from './fetchUrl' +import type { + ExternalQuestionnaire, + ExternalQuestionnaires, + ExternalQuestionnairesFiltered, + ExternalQuestionnairesWrapper, + Manifest, +} from 'core/model' +import { EXTERNAL_RESOURCES_URL } from 'core/constants' + +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: `${EXTERNAL_RESOURCES_URL}/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: `${EXTERNAL_RESOURCES_URL}/${questionnaireId}/assets-manifest.json`, + }) + + const expectedResult = [ + `${EXTERNAL_RESOURCES_URL}/qt/resource1`, + `${EXTERNAL_RESOURCES_URL}/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 new file mode 100644 index 00000000..808b726a --- /dev/null +++ b/drama-queen/src/core/tools/externalResources.ts @@ -0,0 +1,129 @@ +import type { + ExternalQuestionnaire, + ExternalQuestionnaires, + ExternalQuestionnairesFiltered, + ExternalQuestionnairesWrapper, + Manifest, +} from 'core/model' +import { fetchUrl } from './fetchUrl' +import { EXTERNAL_RESOURCES_URL } from 'core/constants' + +const EXTERNAL_QUESTIONNAIRES_KEYWORD = 'gide' + +// Get the list of external questionnaires +export async function getExternalQuestionnaires(): Promise { + const questionnairesWrapper = await fetchUrl({ + url: `${EXTERNAL_RESOURCES_URL}/gide-questionnaires.json`, + }) + + return questionnairesWrapper.questionnaires +} + +// Get the list of external resource URLs for a questionnaireId +export async function getTransformedManifest( + questionnaireId: string +): Promise { + // get the manifest for a questionnaireId + const manifest = await fetchUrl({ + url: `${EXTERNAL_RESOURCES_URL}/${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) => `${EXTERNAL_RESOURCES_URL}/${resourceUrl}` + ) + + return transformedManifest +} + +// 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 cache is not available, return a copy of transformedManifest + if (!cacheForManifest) { + 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 +export async function getResourcesFromExternalQuestionnaire( + questionnaire: ExternalQuestionnaire +): Promise { + const transformedManifest = await getTransformedManifest(questionnaire.id) + + const filteredTransformedManifest = await filterTransformedManifest( + questionnaire.cacheName, + transformedManifest + ) + + 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 + ) +} + +// 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(EXTERNAL_QUESTIONNAIRES_KEYWORD.toLowerCase()) && + !neededCaches.includes(cacheName) + ) + + return oldExternalCacheNames +} diff --git a/drama-queen/src/core/tools/fetchUrl.ts b/drama-queen/src/core/tools/fetchUrl.ts index 776a51b5..b1b24032 100644 --- a/drama-queen/src/core/tools/fetchUrl.ts +++ b/drama-queen/src/core/tools/fetchUrl.ts @@ -1,19 +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) - .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/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..1ec25885 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 { EXTERNAL_RESOURCES_URL } from 'core/constants' 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: EXTERNAL_RESOURCES_URL ? 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 983f49cd..081a48f0 100644 --- a/drama-queen/src/core/usecases/synchronizeData/thunks.ts +++ b/drama-queen/src/core/usecases/synchronizeData/thunks.ts @@ -2,6 +2,15 @@ import type { Thunks } from 'core/bootstrap' import { actions, name } from './state' import { AxiosError } from 'axios' import type { Questionnaire } from 'core/model' +import { + getExternalQuestionnaireFiltered, + getExternalQuestionnaires, + getOldExternalCacheNames, + getResourcesFromExternalQuestionnaire, +} from 'core/tools/externalResources' +import { EXTERNAL_RESOURCES_URL } from 'core/constants' + +const EXTERNAL_RESOURCES_ROOT_CACHE_NAME = 'cache-root-external' export const thunks = { download: @@ -172,6 +181,87 @@ export const thunks = { ) ) + /* + * External special ressources + */ + + // we sychronize the external ressource only if there is a url for getting them + if (EXTERNAL_RESOURCES_URL) { + // get the list of external questionnaires + 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( + questionnaireIdInSuccess, + 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 + const prGetExternalResources = Promise.all( + neededQuestionnaires.map(async (questionnaire) => { + await getResourcesFromExternalQuestionnaire(questionnaire) + .then(() => + dispatch(actions.downloadExternalResourceCompleted()) + ) + .catch((error) => + console.error( + `An error occurred while fetching external resources of questionnaire ${questionnaire.id}`, + error + ) + ) + }) + ) + + // delete the cache of every not needed external questionnaires + const prDeleteExternalResources = Promise.all( + notNeededQuestionnaires.map((questionnaire) => + caches.delete(questionnaire.cacheName) + ) + ) + + // delete the root-cache of external resources if no external questionnaire is needed + const prDeleteExternalRootCache = + neededQuestionnaires.length === 0 + ? caches.delete(EXTERNAL_RESOURCES_ROOT_CACHE_NAME) + : 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, + ]) + } + //We await untill all the promises are finished await Promise.all([prSurveyUnit, prNomenclatures]) 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 => { diff --git a/drama-queen/src/custom.d.ts b/drama-queen/src/custom.d.ts new file mode 100644 index 00000000..f6fc9a5e --- /dev/null +++ b/drama-queen/src/custom.d.ts @@ -0,0 +1,7 @@ +declare module 'capmi' + +declare namespace JSX { + interface IntrinsicElements { + 'capmi-app': any + } +} diff --git a/drama-queen/src/i18n/resources/en.ts b/drama-queen/src/i18n/resources/en.ts index 1bf1b535..aaea9d08 100644 --- a/drama-queen/src/i18n/resources/en.ts +++ b/drama-queen/src/i18n/resources/en.ts @@ -1,5 +1,5 @@ +import { LUNATIC_MODEL_VERSION_BREAKING } from 'core/constants' import type { Translations } from '../types' -import { lunaticModelVersionBreaking } from 'core/tools/SurveyModelBreaking' export const translations: Translations<'en'> = { errorMessage: { @@ -22,7 +22,7 @@ export const translations: Translations<'en'> = { `Survey unit ${surveyUnitId} not found.`, lunaticModelVersionNotFound: 'The questionnaire has no lunaticModelVersion field', - questionnaireNotCompatible: `The questionnaire is not compatible. The 'lunaticModelVersion' must be higher than ${lunaticModelVersionBreaking}`, + questionnaireNotCompatible: `The questionnaire is not compatible. The 'lunaticModelVersion' must be higher than ${LUNATIC_MODEL_VERSION_BREAKING}`, questionnaireNotFound: ({ questionnaireId }) => `Unable to retrieve questionnaire ${questionnaireId}.`, wrongQuestionnaire: ({ surveyUnitId, questionnaireId }) => @@ -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..cb3517b7 100644 --- a/drama-queen/src/i18n/resources/fr.ts +++ b/drama-queen/src/i18n/resources/fr.ts @@ -1,5 +1,5 @@ +import { LUNATIC_MODEL_VERSION_BREAKING } from 'core/constants' import type { Translations } from '../types' -import { lunaticModelVersionBreaking } from 'core/tools/SurveyModelBreaking' export const translations: Translations<'fr'> = { errorMessage: { @@ -23,7 +23,7 @@ export const translations: Translations<'fr'> = { `L'unité enquêtée ${surveyUnitId} est introuvable.`, lunaticModelVersionNotFound: 'Le questionnaire ne comporte pas de champ lunaticModelVersion', - questionnaireNotCompatible: `Le questionnaire est incompatible. La version du 'lunaticModelVersion' doit être supérieure à ${lunaticModelVersionBreaking}`, + questionnaireNotCompatible: `Le questionnaire est incompatible. La version du 'lunaticModelVersion' doit être supérieure à ${LUNATIC_MODEL_VERSION_BREAKING}`, questionnaireNotFound: ({ questionnaireId }) => `Impossible de récupérer le questionnaire ${questionnaireId}.`, wrongQuestionnaire: ({ surveyUnitId, questionnaireId }) => @@ -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/main.tsx b/drama-queen/src/main.tsx index 21e0dcc9..87475d8b 100644 --- a/drama-queen/src/main.tsx +++ b/drama-queen/src/main.tsx @@ -1,10 +1,14 @@ -import('./bootstrap').then(({ mount }) => { +import { EXTERNAL_RESOURCES_URL } from 'core/constants' + +import('./bootstrap').then(({ mount, mountExternalResources }) => { const localRoot = document.getElementById('drama-queen') mount({ mountPoint: localRoot!, routingStrategy: 'browser', }) + + if (EXTERNAL_RESOURCES_URL) mountExternalResources(EXTERNAL_RESOURCES_URL) }) export {} diff --git a/drama-queen/src/queen-service-worker.js b/drama-queen/src/queen-service-worker.js index 39ad99e8..50d45c4c 100644 --- a/drama-queen/src/queen-service-worker.js +++ b/drama-queen/src/queen-service-worker.js @@ -6,9 +6,27 @@ importScripts( 'https://storage.googleapis.com/workbox-cdn/releases/7.1.0/workbox-sw.js' ) +/** + * Load all functions needed by all service-workers + */ const { CacheableResponsePlugin } = workbox.cacheableResponse const { registerRoute } = workbox.routing const { NetworkFirst, CacheFirst } = workbox.strategies +const { RangeRequestsPlugin } = workbox.rangeRequests + +/** + * Load env variable with swEnv.js (manage by vite-envs plugin) + */ +importScripts(`${self._DRAMAQUEEN_URL}/swEnv.js`) + +/** + * Load external resource service-worker + */ +if (self.__VITE_ENVS.VITE_EXTERNAL_RESOURCES_URL) { + // In external service-worker, self._QUEEN_CAPMI_URL has to be defined to get root url of externalResourcesUrl + self._QUEEN_CAPMI_URL = self.__VITE_ENVS.VITE_EXTERNAL_RESOURCES_URL + importScripts(`${self._QUEEN_CAPMI_URL}/queen-service-worker.js`) +} const getDramaQueenUrlRegex = (url) => { return url 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/src/ui/components/orchestrator/Header/Header.tsx b/drama-queen/src/ui/components/orchestrator/Header/Header.tsx index 4c9f696b..a9385342 100644 --- a/drama-queen/src/ui/components/orchestrator/Header/Header.tsx +++ b/drama-queen/src/ui/components/orchestrator/Header/Header.tsx @@ -14,7 +14,7 @@ import { BreadCrumb } from '../Breadcrumb/Breadcrumb' import { Menu } from '../Menu/Menu' import { ShortCut } from '../buttons/ShortCut/ShortCut' import type { GoToPage, Overview, OverviewItem } from '../lunaticType' -import { DYNAMIC_PUBLIC_URL } from 'core' +import { DYNAMIC_PUBLIC_URL } from 'core/constants' type HeaderProps = { questionnaireTitle: string diff --git a/drama-queen/src/ui/components/orchestrator/Orchestrator.tsx b/drama-queen/src/ui/components/orchestrator/Orchestrator.tsx index 7026fad6..1224f7df 100644 --- a/drama-queen/src/ui/components/orchestrator/Orchestrator.tsx +++ b/drama-queen/src/ui/components/orchestrator/Orchestrator.tsx @@ -52,9 +52,6 @@ export function Orchestrator(props: OrchestratorProps) { const { t: t2 } = useTranslation('modalMessage') const { onChange, ref } = useAutoNext() - // get the initial data for useLunatic - const initialData = surveyUnit?.data as LunaticData | undefined - // the given surveyUnit can be empty or partial, we initialize it for having the waited format const initialSurveyUnit = getinitialSurveyUnit(surveyUnit) @@ -98,7 +95,7 @@ export function Orchestrator(props: OrchestratorProps) { hasPageResponse, getChangedData, loopVariables, - } = useLunatic(source, initialData, { + } = useLunatic(source, initialSurveyUnit.data, { lastReachedPage: initialSurveyUnit.stateData?.currentPage, onChange, getReferentiel: getReferentiel, diff --git a/drama-queen/src/ui/components/orchestrator/tools/functions.tsx b/drama-queen/src/ui/components/orchestrator/tools/functions.tsx index 5476e2a3..bc0054e4 100644 --- a/drama-queen/src/ui/components/orchestrator/tools/functions.tsx +++ b/drama-queen/src/ui/components/orchestrator/tools/functions.tsx @@ -1,5 +1,6 @@ -import type { PageTag, SurveyUnit } from 'core/model' +import type { PageTag, SurveyUnit, SurveyUnitData } from 'core/model' import type { Component, Components } from '../lunaticType' +import { EXTERNAL_RESOURCES_URL } from 'core/constants' /** * temporary : should be handle by Lunatic @@ -81,14 +82,33 @@ export function downloadAsJson(params: { data: object; filename?: string }) { URL.revokeObjectURL(url) } +function getInitialData( + surveyUnitId: string, + questionnaireId: string, + data?: SurveyUnitData +): SurveyUnitData { + if (!EXTERNAL_RESOURCES_URL) return data ?? {} + return { + ...data, + EXTERNAL: { + ...data?.EXTERNAL, + GLOBAL_QUESTIONNAIRE_ID: surveyUnitId, + GLOBAL_SURVEY_UNIT_ID: questionnaireId, + }, + } +} + export function getinitialSurveyUnit( partial?: Partial ): SurveyUnit { + const surveyUnitId = partial?.id ?? '' + const questionnaireId = partial?.questionnaireId ?? '' + return { - id: partial?.id ?? '', - questionnaireId: partial?.questionnaireId ?? '', + id: surveyUnitId, + questionnaireId: questionnaireId, personalization: partial?.personalization, - data: partial?.data ?? {}, + data: getInitialData(surveyUnitId, questionnaireId, partial?.data), comment: partial?.comment, stateData: partial?.stateData ?? { state: null, diff --git a/drama-queen/src/ui/pages/External/External.tsx b/drama-queen/src/ui/pages/External/External.tsx new file mode 100644 index 00000000..3385eed3 --- /dev/null +++ b/drama-queen/src/ui/pages/External/External.tsx @@ -0,0 +1,3 @@ +export function ExternalRessources() { + return +} 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')} /> diff --git a/drama-queen/src/ui/routing/routes.tsx b/drama-queen/src/ui/routing/routes.tsx index 0a1a42e2..9672424d 100644 --- a/drama-queen/src/ui/routing/routes.tsx +++ b/drama-queen/src/ui/routing/routes.tsx @@ -12,43 +12,59 @@ import { } from './loader' import { Review } from 'ui/pages/review/Review' import { ErrorPage } from 'ui/pages/Error/Error' +import { ExternalRessources } from 'ui/pages/External/External' +import { EXTERNAL_RESOURCES_URL } from 'core/constants' + +const getChildrenRoutes = () => { + const baseRoutes = [ + { + path: '/env', + Component: DisplayEnvValues, + loader: protectedRouteLoader, + }, + { + path: '/visualize', + Component: Visualize, + loader: visualizeLoader, + }, + { + path: '/synchronize', + Component: SynchronizeData, + loader: protectedRouteLoader, + }, + { + path: '/survey-unit/:surveyUnitId', + Component: ErrorPage, // This route do not contains UI components, all things are done in loader, if not there is an error + loader: surveyUnitLoader, + }, + { + path: `/questionnaire/:questionnaireId/survey-unit/:surveyUnitId`, + Component: Collect, + loader: collectLoader, + }, + { + path: `/readonly/questionnaire/:questionnaireId/survey-unit/:surveyUnitId`, + Component: Review, + loader: reviewLoader, + }, + ] + + if (!EXTERNAL_RESOURCES_URL) return baseRoutes + + return [ + { + path: '/gide/*', + Component: ExternalRessources, + }, + ...baseRoutes, + ] +} export const routes = [ { path: '/', Component: Layout, errorElement: , - children: [ - { - path: '/env', - Component: DisplayEnvValues, - loader: protectedRouteLoader, - }, - { - path: '/visualize', - Component: Visualize, - loader: visualizeLoader, - }, - { - path: '/synchronize', - Component: SynchronizeData, - loader: protectedRouteLoader, - }, - { - path: '/survey-unit/:surveyUnitId', - Component: ErrorPage, // This route do not contains UI components, all things are done in loader, if not there is an error - loader: surveyUnitLoader, - }, - { - path: `/questionnaire/:questionnaireId/survey-unit/:surveyUnitId`, - Component: Collect, - loader: collectLoader, - }, - { - path: `/readonly/questionnaire/:questionnaireId/survey-unit/:surveyUnitId`, - Component: Review, - loader: reviewLoader, - }, - ], + children: getChildrenRoutes(), }, ] diff --git a/drama-queen/src/unsubscribe_old_sw.ts b/drama-queen/src/unsubscribe_old_sw.ts index 77b7440b..7e3478ad 100644 --- a/drama-queen/src/unsubscribe_old_sw.ts +++ b/drama-queen/src/unsubscribe_old_sw.ts @@ -1,4 +1,4 @@ -import { DYNAMIC_PUBLIC_URL } from 'core' +import { DYNAMIC_PUBLIC_URL } from 'core/constants' // Unregister old service workers for standalone export const unsubscribeOldSW = () => { diff --git a/drama-queen/src/vite-env.d.ts b/drama-queen/src/vite-env.d.ts index caf3a162..015c0600 100644 --- a/drama-queen/src/vite-env.d.ts +++ b/drama-queen/src/vite-env.d.ts @@ -4,6 +4,7 @@ type ImportMetaEnv = { // Auto-generated by `npx vite-envs update-types` and hot-reloaded by the `vite-env` plugin VITE_QUEEN_API_URL: string + VITE_EXTERNAL_RESOURCES_URL: string VITE_OIDC_ISSUER: string VITE_OIDC_CLIENT_ID: string BASE_URL: string diff --git a/drama-queen/vitest.config.ts b/drama-queen/vitest.config.ts new file mode 100644 index 00000000..0afc35c8 --- /dev/null +++ b/drama-queen/vitest.config.ts @@ -0,0 +1,17 @@ +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', + coverage: { + reporter: ['text', 'lcov'], + }, + }, + }) +) diff --git a/nx.json b/nx.json index 82410dbc..b658969b 100644 --- a/nx.json +++ b/nx.json @@ -4,12 +4,19 @@ "runner": "nx/tasks-runners/default", "options": { "cacheableOperations": [ - "build" + "build", + "test:coverage" ] } } }, - "targetDefaults": {}, + "targetDefaults": { + "test:coverage": { + "outputs": [ + "{projectRoot}/coverage" + ] + } + }, "$schema": "./node_modules/nx/schemas/nx-schema.json", "namedInputs": { "default": [ diff --git a/package.json b/package.json index ab165cc5..9020c488 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "drama-queen" ], "scripts": { + "test": "lerna run test", + "test:coverage": "lerna run test:coverage", "build": "lerna run build", "build:drama-queen": "lerna run build --scope 'drama-queen'", "serve:drama-queen": "lerna run serve --scope 'drama-queen'", diff --git a/sonar-project.properties b/sonar-project.properties index 4de6ef16..9b15b3c9 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.javascript.lcov.reportPaths=drama-queen/coverage/lcov.info +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 diff --git a/website/docs/synchronize.mdx b/website/docs/synchronize.mdx index 14f69f10..ae1434bd 100644 --- a/website/docs/synchronize.mdx +++ b/website/docs/synchronize.mdx @@ -13,6 +13,51 @@ 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 spécifiée. + +#### 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é (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}, ...]` + - 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 des ressources inutiles + +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 +65,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 diff --git a/yarn.lock b/yarn.lock index 77379b27..c728c83d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,7 +2,12 @@ # yarn lockfile v1 -"@ampproject/remapping@^2.2.0": +"@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", "@ampproject/remapping@^2.3.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== @@ -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" @@ -962,6 +990,11 @@ "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -1274,6 +1307,11 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" +"@istanbuljs/schema@^0.1.2": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + "@jest/schemas@^29.6.3": version "29.6.3" resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" @@ -1308,7 +1346,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== @@ -1321,7 +1359,7 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": +"@jridgewell/trace-mapping@^0.3.23", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== @@ -1997,6 +2035,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 +2107,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 +2291,83 @@ "@types/babel__core" "^7.20.5" react-refresh "^0.14.2" +"@vitest/coverage-v8@^2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@vitest/coverage-v8/-/coverage-v8-2.1.3.tgz#22d519e5e56385ec126305492f5a3cfe5b44b14d" + integrity sha512-2OJ3c7UPoFSmBZwqD2VEkUw6A/tzPF0LmW0ZZhhB8PFxuc+9IBG/FaSM+RLEenc7ljzFvGN+G0nGQoZnh7sy2A== + dependencies: + "@ampproject/remapping" "^2.3.0" + "@bcoe/v8-coverage" "^0.2.3" + debug "^4.3.6" + istanbul-lib-coverage "^3.2.2" + istanbul-lib-report "^3.0.1" + istanbul-lib-source-maps "^5.0.6" + istanbul-reports "^3.1.7" + magic-string "^0.30.11" + magicast "^0.3.4" + std-env "^3.7.0" + test-exclude "^7.0.1" + tinyrainbow "^1.2.0" + +"@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 +2425,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 +2551,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 +2610,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 +2803,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 +2890,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 +2918,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 +2964,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 +3295,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 +3338,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 +3404,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 +3424,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 +3441,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 +3496,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 +3540,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 +3645,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 +3877,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== @@ -4029,7 +4288,7 @@ glob@7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^10.2.2: +glob@^10.2.2, glob@^10.4.1: version "10.4.5" resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== @@ -4262,6 +4521,18 @@ 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-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + 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 +4552,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 +4596,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 +4966,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" @@ -4788,6 +5080,37 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" + integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg== + +istanbul-lib-report@^3.0.0, istanbul-lib-report@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d" + integrity sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^4.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz#acaef948df7747c8eb5fbf1265cb980f6353a441" + integrity sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A== + dependencies: + "@jridgewell/trace-mapping" "^0.3.23" + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + +istanbul-reports@^3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b" + integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + jackspeak@^3.1.2: version "3.4.3" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" @@ -4847,6 +5170,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 +5477,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 +5513,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,7 +5532,23 @@ magic-string@^0.27.0: dependencies: "@jridgewell/sourcemap-codec" "^1.4.13" -make-dir@4.0.0: +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" + +magicast@^0.3.4: + version "0.3.5" + resolved "https://registry.yarnpkg.com/magicast/-/magicast-0.3.5.tgz#8301c3c7d66704a0771eb1bad74274f0ec036739" + integrity sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ== + dependencies: + "@babel/parser" "^7.25.4" + "@babel/types" "^7.25.4" + source-map-js "^1.2.0" + +make-dir@4.0.0, make-dir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-4.0.0.tgz#c3c2307a771277cd9638305f915c29ae741b614e" integrity sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw== @@ -5807,7 +6183,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 +6444,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 +6754,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 +6806,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 +6905,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 +6977,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 +7039,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 +7450,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 +7524,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 +7633,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" @@ -7281,7 +7710,7 @@ sort-keys@^2.0.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== -source-map-js@^1.2.1: +source-map-js@^1.2.0, source-map-js@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== @@ -7385,6 +7814,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 +8016,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" @@ -7642,6 +8086,15 @@ terser@^5.17.4: commander "^2.20.0" source-map-support "~0.5.20" +test-exclude@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-7.0.1.tgz#20b3ba4906ac20994e275bbcafd68d510264c2a2" + integrity sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^10.4.1" + minimatch "^9.0.4" + text-extensions@^1.0.0: version "1.9.0" resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" @@ -7668,6 +8121,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 +8182,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 +8196,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 +8634,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 +8664,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 +8686,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 +8735,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 +8747,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 +8820,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 +9077,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"