From b1484b7c85d00038c7aa153fd6dc437836544480 Mon Sep 17 00:00:00 2001 From: hallieswan <26949006+hallieswan@users.noreply.github.com> Date: Wed, 15 Jan 2025 12:23:15 -0800 Subject: [PATCH] feat(agora): migrate Agora e2e tests (AG-1609) (#2965) --- apps/agora/app/.eslintrc.json | 15 +- ...gene-comparison-tool-pinning-cache.spec.ts | 341 +++++++++++++++ .../gene-comparison-tool-pinning-ui.spec.ts | 63 +++ .../gene-comparison-tool-pinning-url.spec.ts | 262 +++++++++++ .../app/e2e/gene-comparison-tool.spec.ts | 77 ++++ apps/agora/app/e2e/gene-details.spec.ts | 30 ++ apps/agora/app/e2e/gene-resources.spec.ts | 35 ++ apps/agora/app/e2e/gene-similar.spec.ts | 38 ++ apps/agora/app/e2e/helpers/constants.ts | 21 + apps/agora/app/e2e/helpers/data.ts | 119 +++++ apps/agora/app/e2e/helpers/gct-pinning.ts | 115 +++++ apps/agora/app/e2e/helpers/gct.ts | 115 +++++ apps/agora/app/e2e/helpers/utils.ts | 9 + apps/agora/app/e2e/homepage.spec.ts | 49 +++ apps/agora/app/e2e/nominated-targets.spec.ts | 14 + apps/agora/app/e2e/search.spec.ts | 30 ++ apps/agora/app/jest.config.ts | 1 + apps/agora/app/playwright.config.ts | 84 ++++ apps/agora/app/project.json | 11 +- apps/agora/app/tsconfig.e2e.json | 10 + apps/agora/app/tsconfig.json | 3 + docker/agora/services/apex.yml | 5 + package.json | 30 +- pnpm-lock.yaml | 405 ++++++------------ 24 files changed, 1580 insertions(+), 302 deletions(-) create mode 100644 apps/agora/app/e2e/gene-comparison-tool-pinning-cache.spec.ts create mode 100644 apps/agora/app/e2e/gene-comparison-tool-pinning-ui.spec.ts create mode 100644 apps/agora/app/e2e/gene-comparison-tool-pinning-url.spec.ts create mode 100644 apps/agora/app/e2e/gene-comparison-tool.spec.ts create mode 100644 apps/agora/app/e2e/gene-details.spec.ts create mode 100644 apps/agora/app/e2e/gene-resources.spec.ts create mode 100644 apps/agora/app/e2e/gene-similar.spec.ts create mode 100644 apps/agora/app/e2e/helpers/constants.ts create mode 100644 apps/agora/app/e2e/helpers/data.ts create mode 100644 apps/agora/app/e2e/helpers/gct-pinning.ts create mode 100644 apps/agora/app/e2e/helpers/gct.ts create mode 100644 apps/agora/app/e2e/helpers/utils.ts create mode 100644 apps/agora/app/e2e/homepage.spec.ts create mode 100644 apps/agora/app/e2e/nominated-targets.spec.ts create mode 100644 apps/agora/app/e2e/search.spec.ts create mode 100644 apps/agora/app/playwright.config.ts create mode 100644 apps/agora/app/tsconfig.e2e.json diff --git a/apps/agora/app/.eslintrc.json b/apps/agora/app/.eslintrc.json index 579327ff90..e1216fd55b 100644 --- a/apps/agora/app/.eslintrc.json +++ b/apps/agora/app/.eslintrc.json @@ -1,5 +1,5 @@ { - "extends": ["../../../.eslintrc.json"], + "extends": ["plugin:playwright/recommended", "../../../.eslintrc.json"], "ignorePatterns": ["!**/*"], "overrides": [ { @@ -29,6 +29,19 @@ "files": ["*.html"], "extends": ["plugin:@nx/angular-template"], "rules": {} + }, + { + "files": ["e2e/**/*.{ts,js,tsx,jsx}", "e2e/**/*.spec.{ts,js,tsx,jsx}"], + "plugins": ["@typescript-eslint"], + "parserOptions": { + "project": "./apps/agora/app/tsconfig.*?.json" + }, + "rules": { + "@typescript-eslint/no-floating-promises": "error", + "playwright/no-nested-step": "off", + "playwright/no-conditional-in-test": "off", + "playwright/expect-expect": "off" + } } ] } diff --git a/apps/agora/app/e2e/gene-comparison-tool-pinning-cache.spec.ts b/apps/agora/app/e2e/gene-comparison-tool-pinning-cache.spec.ts new file mode 100644 index 0000000000..16ca257716 --- /dev/null +++ b/apps/agora/app/e2e/gene-comparison-tool-pinning-cache.spec.ts @@ -0,0 +1,341 @@ +import { expect, test } from '@playwright/test'; +import { + GCT_CATEGORIES, + GCT_PROTEIN_SUBCATEGORIES, + GCT_RNA_SUBCATEGORIES, + URL_GCT, + URL_GCT_PROTEIN_TMT, +} from './helpers/constants'; +import { gene2WithMultipleProteinsTMT, geneWithMultipleProteinsTMT } from './helpers/data'; +import { changeGctCategory, changeGctSubcategory, expectGctPageLoaded } from './helpers/gct'; +import { + confirmPinnedItemsByGeneName, + confirmPinnedItemsByItemName, + confirmPinnedItemsCount, + confirmPinnedProteins, + expectPinnedGenesCountText, + expectPinnedProteinsCountText, + pinAllItemsViaSearchByGene, + pinGeneViaSearch, + pinMultipleGenesViaSearch, +} from './helpers/gct-pinning'; + +test.describe('GCT: Caching pinned genes', () => { + test('Pinned genes are maintained when switching between RNA subcategories', async ({ page }) => { + const genes = ['CYB561A3', 'MANBAL', 'PLEC', 'GFAP']; + const nGenes = genes.length; + + await page.goto(URL_GCT); + await expectGctPageLoaded(page, GCT_CATEGORIES.RNA, GCT_RNA_SUBCATEGORIES.AD); + + await pinMultipleGenesViaSearch(page, genes); + + await test.step('confirm # genes pinned on AD subcategory page', async () => { + await expectPinnedGenesCountText(page, nGenes); + await confirmPinnedItemsCount(page, nGenes); + }); + + await changeGctSubcategory( + page, + GCT_CATEGORIES.RNA, + GCT_RNA_SUBCATEGORIES.AD, + GCT_RNA_SUBCATEGORIES.AD_AOD, + ); + + await changeGctSubcategory( + page, + GCT_CATEGORIES.RNA, + GCT_RNA_SUBCATEGORIES.AD_AOD, + GCT_RNA_SUBCATEGORIES.AD_SEX_F, + ); + + await changeGctSubcategory( + page, + GCT_CATEGORIES.RNA, + GCT_RNA_SUBCATEGORIES.AD_SEX_F, + GCT_RNA_SUBCATEGORIES.AD_SEX_M, + ); + + // back to original subcategory + await changeGctSubcategory( + page, + GCT_CATEGORIES.RNA, + GCT_RNA_SUBCATEGORIES.AD_SEX_M, + GCT_RNA_SUBCATEGORIES.AD, + ); + + await test.step('confirm same genes pinned on AD subcategory page', async () => { + await expectPinnedGenesCountText(page, nGenes); + await confirmPinnedItemsCount(page, nGenes); + await confirmPinnedItemsByItemName(page, genes); + }); + }); + + test('Pinned genes are maintained when switching categories: RNA -> Protein -> RNA', async ({ + page, + }) => { + const genes = ['CYB561A3', 'MANBAL', 'PLEC', 'GFAP']; + const nGenes = genes.length; + + await page.goto(URL_GCT); + await expectGctPageLoaded(page, GCT_CATEGORIES.RNA, GCT_RNA_SUBCATEGORIES.AD); + + await pinMultipleGenesViaSearch(page, genes); + + await test.step('confirm # genes pinned on RNA page', async () => { + await expectPinnedGenesCountText(page, nGenes); + await confirmPinnedItemsCount(page, nGenes); + }); + + await changeGctCategory(page, GCT_CATEGORIES.RNA, GCT_CATEGORIES.PROTEIN); + + await changeGctSubcategory( + page, + GCT_CATEGORIES.PROTEIN, + GCT_PROTEIN_SUBCATEGORIES.SRM, + GCT_PROTEIN_SUBCATEGORIES.TMT, + ); + + await changeGctSubcategory( + page, + GCT_CATEGORIES.PROTEIN, + GCT_PROTEIN_SUBCATEGORIES.TMT, + GCT_PROTEIN_SUBCATEGORIES.LFQ, + ); + + // back to original category + await changeGctCategory(page, GCT_CATEGORIES.PROTEIN, GCT_CATEGORIES.RNA); + + await test.step('confirm same genes pinned on RNA page', async () => { + await expectPinnedGenesCountText(page, nGenes); + await confirmPinnedItemsCount(page, nGenes); + await confirmPinnedItemsByItemName(page, genes); + }); + }); + + test('Pinned proteins are maintained when switching between Protein subcategories', async ({ + page, + }) => { + const gene1 = geneWithMultipleProteinsTMT.name; + const gene2 = gene2WithMultipleProteinsTMT.name; + const genes = [gene1, gene2]; + const nGenes = genes.length; + + const proteins1 = geneWithMultipleProteinsTMT.uniProtIds; + const proteins2 = gene2WithMultipleProteinsTMT.uniProtIds; + const nProteins = proteins1.length + proteins2.length; + + await page.goto(URL_GCT_PROTEIN_TMT); + await expectGctPageLoaded(page, GCT_CATEGORIES.PROTEIN, GCT_PROTEIN_SUBCATEGORIES.TMT); + + for (const gene of genes) { + await pinAllItemsViaSearchByGene(page, gene); + } + + await test.step('confirm # genes and proteins pinned on Protein page', async () => { + await expectPinnedGenesCountText(page, nGenes); + await expectPinnedProteinsCountText(page, nProteins); + await confirmPinnedItemsCount(page, nProteins); + }); + + await changeGctSubcategory( + page, + GCT_CATEGORIES.PROTEIN, + GCT_PROTEIN_SUBCATEGORIES.TMT, + GCT_PROTEIN_SUBCATEGORIES.SRM, + ); + + await changeGctSubcategory( + page, + GCT_CATEGORIES.PROTEIN, + GCT_PROTEIN_SUBCATEGORIES.SRM, + GCT_PROTEIN_SUBCATEGORIES.LFQ, + ); + + // back to original subcategory + await changeGctSubcategory( + page, + GCT_CATEGORIES.PROTEIN, + GCT_PROTEIN_SUBCATEGORIES.LFQ, + GCT_PROTEIN_SUBCATEGORIES.TMT, + ); + + await test.step('confirm same genes and proteins pinned on Protein page', async () => { + await expectPinnedGenesCountText(page, nGenes); + await expectPinnedProteinsCountText(page, nProteins); + await confirmPinnedItemsCount(page, nProteins); + + await confirmPinnedProteins(page, gene1, proteins1); + await confirmPinnedProteins(page, gene2, proteins2); + }); + }); + + test('Pinned proteins are maintained when switching categories: Protein -> RNA -> Protein', async ({ + page, + }) => { + const gene1 = geneWithMultipleProteinsTMT.name; + const gene2 = gene2WithMultipleProteinsTMT.name; + const genes = [gene1, gene2]; + const nGenes = genes.length; + + const proteins1 = geneWithMultipleProteinsTMT.uniProtIds; + const proteins2 = gene2WithMultipleProteinsTMT.uniProtIds; + const nProteins = proteins1.length + proteins2.length; + + await page.goto(URL_GCT_PROTEIN_TMT); + await expectGctPageLoaded(page, GCT_CATEGORIES.PROTEIN, GCT_PROTEIN_SUBCATEGORIES.TMT); + + for (const gene of genes) { + await pinAllItemsViaSearchByGene(page, gene); + } + + await test.step('confirm # genes and proteins pinned on Protein page', async () => { + await expectPinnedGenesCountText(page, nGenes); + await expectPinnedProteinsCountText(page, nProteins); + await confirmPinnedItemsCount(page, nProteins); + }); + + await changeGctCategory(page, GCT_CATEGORIES.PROTEIN, GCT_CATEGORIES.RNA); + + await changeGctSubcategory( + page, + GCT_CATEGORIES.RNA, + GCT_RNA_SUBCATEGORIES.AD, + GCT_RNA_SUBCATEGORIES.AD_AOD, + ); + + // back to original category + await changeGctCategory(page, GCT_CATEGORIES.RNA, GCT_CATEGORIES.PROTEIN); + + // back to original subcategory + await changeGctSubcategory( + page, + GCT_CATEGORIES.PROTEIN, + GCT_PROTEIN_SUBCATEGORIES.SRM, + GCT_PROTEIN_SUBCATEGORIES.TMT, + ); + + await test.step('confirm same genes and proteins pinned on Protein page', async () => { + await expectPinnedGenesCountText(page, nGenes); + await expectPinnedProteinsCountText(page, nProteins); + + await confirmPinnedItemsCount(page, nProteins); + await confirmPinnedProteins(page, gene1, proteins1); + await confirmPinnedProteins(page, gene2, proteins2); + }); + }); + + test('Last pinned genes are maintained even if proteins were pinned initially', async ({ + page, + }) => { + const rnaCategoryGene = geneWithMultipleProteinsTMT.name; + const rnaCategoryProteins = geneWithMultipleProteinsTMT.uniProtIds; + const proteinCategoryGene = gene2WithMultipleProteinsTMT.name; + + await test.step('pin proteins in Protein category', async () => { + await page.goto(URL_GCT_PROTEIN_TMT); + await expectGctPageLoaded(page, GCT_CATEGORIES.PROTEIN, GCT_PROTEIN_SUBCATEGORIES.TMT); + + await pinAllItemsViaSearchByGene(page, proteinCategoryGene); + }); + + await test.step('pin genes in RNA category', async () => { + await changeGctCategory(page, GCT_CATEGORIES.PROTEIN, GCT_CATEGORIES.RNA); + + await test.step('clear all items', async () => { + await page.getByRole('button', { name: 'Clear All' }).click(); + await expect(page.getByText(/Pinned Genes/i)).toBeHidden(); + }); + + await pinGeneViaSearch(page, rnaCategoryGene); + + await test.step('confirm pinned gene', async () => { + await expectPinnedGenesCountText(page, 1); + await confirmPinnedItemsByGeneName(page, rnaCategoryGene, 1); + }); + }); + + await test.step('Protein category pins have changed', async () => { + await changeGctCategory(page, GCT_CATEGORIES.RNA, GCT_CATEGORIES.PROTEIN); + await changeGctSubcategory( + page, + GCT_CATEGORIES.PROTEIN, + GCT_PROTEIN_SUBCATEGORIES.SRM, + GCT_PROTEIN_SUBCATEGORIES.TMT, + ); + await expectPinnedGenesCountText(page, 1); + await expectPinnedProteinsCountText(page, rnaCategoryProteins.length); + await confirmPinnedProteins(page, rnaCategoryGene, rnaCategoryProteins); + }); + + await test.step('RNA category pins are maintained', async () => { + await changeGctCategory(page, GCT_CATEGORIES.PROTEIN, GCT_CATEGORIES.RNA); + + await test.step('confirm pinned gene', async () => { + await expectPinnedGenesCountText(page, 1); + await confirmPinnedItemsByGeneName(page, rnaCategoryGene, 1); + }); + }); + }); + + test('Last pinned proteins are maintained even if genes were pinned initially', async ({ + page, + }) => { + const rnaCategoryGene = geneWithMultipleProteinsTMT.name; + const proteinCategoryGene = gene2WithMultipleProteinsTMT.name; + const proteinCategoryProteins = gene2WithMultipleProteinsTMT.uniProtIds; + + await test.step('pin genes in RNA category', async () => { + await page.goto(URL_GCT); + await expectGctPageLoaded(page, GCT_CATEGORIES.RNA, GCT_RNA_SUBCATEGORIES.AD); + + await pinGeneViaSearch(page, rnaCategoryGene); + await confirmPinnedItemsByGeneName(page, rnaCategoryGene, 1); + }); + + await test.step('pin proteins in Protein category', async () => { + await changeGctCategory(page, GCT_CATEGORIES.RNA, GCT_CATEGORIES.PROTEIN); + await changeGctSubcategory( + page, + GCT_CATEGORIES.PROTEIN, + GCT_PROTEIN_SUBCATEGORIES.SRM, + GCT_PROTEIN_SUBCATEGORIES.TMT, + ); + + await test.step('clear all items', async () => { + await page.getByRole('button', { name: 'Clear All' }).click(); + await expect(page.getByText(/Pinned Genes/i)).toBeHidden(); + }); + + await pinAllItemsViaSearchByGene(page, proteinCategoryGene); + + await test.step('confirm pinned items', async () => { + await expectPinnedGenesCountText(page, 1); + await expectPinnedProteinsCountText(page, proteinCategoryProteins.length); + await confirmPinnedProteins(page, proteinCategoryGene, proteinCategoryProteins); + }); + }); + + await test.step('RNA category pins have changed', async () => { + await changeGctCategory(page, GCT_CATEGORIES.PROTEIN, GCT_CATEGORIES.RNA); + await expectPinnedGenesCountText(page, 1); + await confirmPinnedItemsByGeneName(page, proteinCategoryGene, 1); + }); + + await test.step('Protein category pins are maintained', async () => { + await changeGctCategory(page, GCT_CATEGORIES.RNA, GCT_CATEGORIES.PROTEIN); + await changeGctSubcategory( + page, + GCT_CATEGORIES.PROTEIN, + GCT_PROTEIN_SUBCATEGORIES.SRM, + GCT_PROTEIN_SUBCATEGORIES.TMT, + ); + + await test.step('confirm pinned items', async () => { + await expectPinnedGenesCountText(page, 1); + await expectPinnedProteinsCountText(page, proteinCategoryProteins.length); + await confirmPinnedProteins(page, proteinCategoryGene, proteinCategoryProteins); + }); + }); + }); +}); diff --git a/apps/agora/app/e2e/gene-comparison-tool-pinning-ui.spec.ts b/apps/agora/app/e2e/gene-comparison-tool-pinning-ui.spec.ts new file mode 100644 index 0000000000..7ed53bab69 --- /dev/null +++ b/apps/agora/app/e2e/gene-comparison-tool-pinning-ui.spec.ts @@ -0,0 +1,63 @@ +import { expect, test } from '@playwright/test'; +import { baseURL } from '../playwright.config'; +import { GCT_CATEGORIES, GCT_RNA_SUBCATEGORIES, URL_GCT } from './helpers/constants'; +import { expectGctPageLoaded } from './helpers/gct'; +import { + expectPinnedGenesCountText, + expectTooManyPinnedGenesToast, + formatPinnedGenesQueryParam, + getPinnedItemsFromUrl, + pinGeneViaSearch, +} from './helpers/gct-pinning'; + +test.describe('GCT: Pinning Genes via UI', () => { + test('one gene can be directly pinned', async ({ page }) => { + const geneName = 'GFAP'; + const geneEnsemblId = 'ENSG00000131095'; + + await page.goto(URL_GCT); + await expectGctPageLoaded(page, GCT_CATEGORIES.RNA, GCT_RNA_SUBCATEGORIES.AD); + + await pinGeneViaSearch(page, geneName); + await expectPinnedGenesCountText(page, 1); + + await test.step('url has been updated', async () => { + const expectedUrl = `${baseURL}${URL_GCT}?${formatPinnedGenesQueryParam([geneEnsemblId])}`; + expect(page.url()).toEqual(expectedUrl); + }); + }); + + test('only 50 genes are pinned when pinning all genes matched by a quick filter', async ({ + page, + }) => { + await page.goto(URL_GCT); + await expectGctPageLoaded(page, GCT_CATEGORIES.RNA, GCT_RNA_SUBCATEGORIES.AD); + + await test.step('apply a quick filter', async () => { + await page.getByRole('button', { name: 'Filter Genes' }).click(); + await page.getByRole('button', { name: 'Quick Filters' }).click(); + await page.getByText('All Nominated targets').click(); + + const filterPanel = page + .locator('.gct-filter-panel-pane-inner') + .filter({ hasText: 'Quick Filters' }); + const filterCloseBtn = filterPanel.locator('.gct-filter-panel-close'); + await filterCloseBtn.click(); + await expect(filterPanel).toBeHidden(); + }); + + await test.step('pin genes', async () => { + const pinAllBtn = page.getByRole('button', { name: 'Pin All' }); + await pinAllBtn.click(); + await expect(pinAllBtn).toBeDisabled(); + }); + + await test.step('50 genes were pinned', async () => { + await expectTooManyPinnedGenesToast(page); + await expectPinnedGenesCountText(page, 50); + + const genes = getPinnedItemsFromUrl(page.url()); + expect(genes).toHaveLength(50); + }); + }); +}); diff --git a/apps/agora/app/e2e/gene-comparison-tool-pinning-url.spec.ts b/apps/agora/app/e2e/gene-comparison-tool-pinning-url.spec.ts new file mode 100644 index 0000000000..948c6a399f --- /dev/null +++ b/apps/agora/app/e2e/gene-comparison-tool-pinning-url.spec.ts @@ -0,0 +1,262 @@ +import { expect, test } from '@playwright/test'; +import { + GCT_CATEGORIES, + GCT_PROTEIN_SUBCATEGORIES, + GCT_RNA_SUBCATEGORIES, + URL_GCT, + URL_GCT_PROTEIN, + URL_GCT_PROTEIN_TMT, +} from './helpers/constants'; +import { + fiftyGenes, + fiftyOneGenes, + fiftyProteinsToFiftyUniqueGenesTMT, + geneWithMultipleProteinsTMT, +} from './helpers/data'; +import { expectGctPageLoaded, getGeneRowButtons } from './helpers/gct'; +import { + confirmPinnedItemsByGeneName, + confirmPinnedItemsCount, + expectPinnedGenesCountText, + expectPinnedProteinsCountText, + expectTooManyPinnedGenesToast, + formatPinnedGenesQueryParam, + getPinnedItemsFromUrl, +} from './helpers/gct-pinning'; + +test.describe('GCT: Pinning Genes from URL', () => { + test('when RNA url does not include pinned genes, no genes are pinned', async ({ page }) => { + await page.goto(URL_GCT); + await expectGctPageLoaded(page, GCT_CATEGORIES.RNA, GCT_RNA_SUBCATEGORIES.AD); + + await expect(page.getByText('Pinned Genes')).toBeHidden(); + await expect(page.getByText('All Genes')).toBeHidden(); + await confirmPinnedItemsCount(page, 0); + }); + + test('when Protein url does not include pinned genes, no genes are pinned', async ({ page }) => { + await page.goto(URL_GCT_PROTEIN); + await expectGctPageLoaded(page, GCT_CATEGORIES.PROTEIN, GCT_PROTEIN_SUBCATEGORIES.SRM); + + await expect(page.getByText('Pinned Genes')).toBeHidden(); + await expect(page.getByText('All Genes')).toBeHidden(); + await confirmPinnedItemsCount(page, 0); + }); + + test('when RNA url includes 50 pinned genes, all genes are pinned', async ({ page }) => { + const url = `${URL_GCT}?${formatPinnedGenesQueryParam(fiftyGenes)}`; + await page.goto(url); + + await expectGctPageLoaded(page, GCT_CATEGORIES.RNA, GCT_RNA_SUBCATEGORIES.AD); + + await test.step('confirm pinned all 50 genes', async () => { + await expect(page.getByText('All Genes')).toBeVisible(); + await expectPinnedGenesCountText(page, 50); + await confirmPinnedItemsCount(page, 50); + await expect(page.getByRole('alert')).toBeHidden(); + }); + + await test.step('confirm cannot pin other genes', async () => { + const btns = await getGeneRowButtons(page, 'PYGL'); + await expect(btns.open).toBeEnabled(); + await expect(btns.pin).toBeDisabled(); + }); + }); + + test('when RNA url includes >50 pinned genes, only 50 genes are pinned and toast is displayed', async ({ + page, + }) => { + const url = `${URL_GCT}?${formatPinnedGenesQueryParam(fiftyOneGenes)}`; + await page.goto(url); + + await expectGctPageLoaded(page, GCT_CATEGORIES.RNA, GCT_RNA_SUBCATEGORIES.AD); + + await test.step('confirm only pinned 50 genes', async () => { + await expect(page.getByText('All Genes')).toBeVisible(); + await expectPinnedGenesCountText(page, 50); + await confirmPinnedItemsCount(page, 50); + await expectTooManyPinnedGenesToast(page); + }); + + await test.step('confirm url dropped 51st gene', () => { + const genes = getPinnedItemsFromUrl(page.url()); + expect(genes).toHaveLength(50); + expect(genes).toEqual(fiftyOneGenes.sort().slice(0, -1)); + }); + }); + + test('when RNA url includes invalid gene, that gene is dropped from the url', async ({ + page, + }) => { + const validGeneId = geneWithMultipleProteinsTMT.ensemblId; + const url = `${URL_GCT}?${formatPinnedGenesQueryParam([validGeneId, 'invalidGene'])}`; + await page.goto(url); + + await expectGctPageLoaded(page, GCT_CATEGORIES.RNA, GCT_RNA_SUBCATEGORIES.AD); + + await test.step('confirm toast not shown', async () => { + await expect(page.getByRole('alert')).toBeHidden(); + }); + + await test.step('confirm only pinned 1 gene', async () => { + await expect(page.getByText('All Genes')).toBeVisible(); + await expectPinnedGenesCountText(page, 1); + await confirmPinnedItemsCount(page, 1); + }); + + await test.step('confirm url dropped invalid gene', () => { + const genes = getPinnedItemsFromUrl(page.url()); + expect(genes).toHaveLength(1); + expect(genes).toEqual([validGeneId]); + }); + }); + + test.fail( + 'when RNA url includes proteins, the related gene is pinned', + { + annotation: { + type: 'fail', + description: 'Since AG-1425, only genes will be pinned from RNA url', + }, + }, + async ({ page }) => { + const geneProteins = geneWithMultipleProteinsTMT.uniProtIds.map( + (uniProtId) => `${geneWithMultipleProteinsTMT.ensemblId}${uniProtId}`, + ); + const url = `${URL_GCT}?${formatPinnedGenesQueryParam(geneProteins)}`; + await page.goto(url); + + await expectGctPageLoaded(page, GCT_CATEGORIES.RNA, GCT_RNA_SUBCATEGORIES.AD); + + await confirmPinnedItemsByGeneName(page, geneWithMultipleProteinsTMT.name, 1); + await expectPinnedGenesCountText(page, 1); + await confirmPinnedItemsCount(page, 1); + }, + ); + + test.fail( + 'when Protein url includes a gene, all related proteins are pinned', + { + annotation: { + type: 'fail', + description: 'Since AG-1425, only proteins will be pinned from Protein url', + }, + }, + async ({ page }) => { + const url = `${URL_GCT_PROTEIN_TMT}&${formatPinnedGenesQueryParam([ + geneWithMultipleProteinsTMT.ensemblId, + ])}`; + await page.goto(url); + await expectGctPageLoaded(page, GCT_CATEGORIES.PROTEIN, GCT_PROTEIN_SUBCATEGORIES.TMT); + + await confirmPinnedItemsByGeneName( + page, + geneWithMultipleProteinsTMT.name, + geneWithMultipleProteinsTMT.uniProtIds.length, + ); + + await test.step('confirm counts', async () => { + await confirmPinnedItemsCount(page, 5); + await expectPinnedGenesCountText(page, 1); + await expectPinnedProteinsCountText(page, 5); + }); + }, + ); + + test('when Protein url includes 50 proteins from 50 unique genes, all proteins are pinned', async ({ + page, + }) => { + const url = `${URL_GCT_PROTEIN_TMT}&${formatPinnedGenesQueryParam( + fiftyProteinsToFiftyUniqueGenesTMT, + )}`; + await page.goto(url); + await expectGctPageLoaded(page, GCT_CATEGORIES.PROTEIN, GCT_PROTEIN_SUBCATEGORIES.TMT); + + await test.step('confirm counts', async () => { + await confirmPinnedItemsCount(page, 50); + await expectPinnedGenesCountText(page, 50); + await expectPinnedProteinsCountText(page, 50); + }); + }); + + test('when Protein url includes >50 proteins from 50 unique genes, all proteins are pinned', async ({ + page, + }) => { + const fortyNineProteinsToUniqueGenes = fiftyProteinsToFiftyUniqueGenesTMT.slice(0, -1); + const oneGeneWithManyProteins = geneWithMultipleProteinsTMT.uniProtIds.map( + (uniProtId) => `${geneWithMultipleProteinsTMT.ensemblId}${uniProtId}`, + ); + const allProteins = [...fortyNineProteinsToUniqueGenes, ...oneGeneWithManyProteins]; + const url = `${URL_GCT_PROTEIN_TMT}&${formatPinnedGenesQueryParam(allProteins)}`; + + await page.goto(url); + + await expectGctPageLoaded(page, GCT_CATEGORIES.PROTEIN, GCT_PROTEIN_SUBCATEGORIES.TMT); + + await test.step('confirm counts', async () => { + await expectPinnedGenesCountText(page, 50); + await confirmPinnedItemsCount(page, allProteins.length); + await expectPinnedProteinsCountText(page, allProteins.length); + }); + + await confirmPinnedItemsByGeneName( + page, + geneWithMultipleProteinsTMT.name, + geneWithMultipleProteinsTMT.uniProtIds.length, + ); + }); + + test('when Protein url includes proteins from 51 unique genes, only proteins from 50 genes are pinned', async ({ + page, + }) => { + const oneGeneWithManyProteins = geneWithMultipleProteinsTMT.uniProtIds.map( + (uniProtId) => `${geneWithMultipleProteinsTMT.ensemblId}${uniProtId}`, + ); + const allProteins = [...fiftyProteinsToFiftyUniqueGenesTMT, ...oneGeneWithManyProteins]; + const expectedPinnedProteins = [ + ...fiftyProteinsToFiftyUniqueGenesTMT.slice(0, -1), + ...oneGeneWithManyProteins, + ]; + + const url = `${URL_GCT_PROTEIN_TMT}&${formatPinnedGenesQueryParam(allProteins)}`; + + await page.goto(url); + + await expectGctPageLoaded(page, GCT_CATEGORIES.PROTEIN, GCT_PROTEIN_SUBCATEGORIES.TMT); + + await test.step('confirm counts', async () => { + await expectPinnedGenesCountText(page, 50); + await confirmPinnedItemsCount(page, expectedPinnedProteins.length); + await expectPinnedProteinsCountText(page, expectedPinnedProteins.length); + }); + }); + + test('when Protein url includes invalid protein, that protein is dropped from the url', async ({ + page, + }) => { + const validGeneProtein = fiftyProteinsToFiftyUniqueGenesTMT[1]; + const url = `${URL_GCT_PROTEIN_TMT}&${formatPinnedGenesQueryParam([ + validGeneProtein, + 'invalidGeneProtein', + ])}`; + await page.goto(url); + + await expectGctPageLoaded(page, GCT_CATEGORIES.PROTEIN, GCT_PROTEIN_SUBCATEGORIES.TMT); + + await test.step('confirm toast not shown', async () => { + await expect(page.getByRole('alert')).toBeHidden(); + }); + + await test.step('confirm only pinned 1 protein', async () => { + await expectPinnedGenesCountText(page, 1); + await confirmPinnedItemsCount(page, 1); + await expectPinnedProteinsCountText(page, 1); + }); + + await test.step('confirm url dropped invalid protein', () => { + const geneProteins = getPinnedItemsFromUrl(page.url()); + expect(geneProteins).toHaveLength(1); + expect(geneProteins).toEqual([validGeneProtein]); + }); + }); +}); diff --git a/apps/agora/app/e2e/gene-comparison-tool.spec.ts b/apps/agora/app/e2e/gene-comparison-tool.spec.ts new file mode 100644 index 0000000000..fababcd256 --- /dev/null +++ b/apps/agora/app/e2e/gene-comparison-tool.spec.ts @@ -0,0 +1,77 @@ +import { expect, test } from '@playwright/test'; +import { baseURL } from '../playwright.config'; +import { GCT_CATEGORIES, URL_GCT_PROTEIN } from './helpers/constants'; +import { changeGctCategory, closeGctHelpDialog } from './helpers/gct'; +import { waitForSpinnerNotVisible } from './helpers/utils'; + +const URL_GCT = '/genes/comparison'; +const URL_RNA = '/genes/comparison?'; +const URL_PROTEIN = '/genes/comparison?category=Protein+-+Differential+Expression'; + +test.describe('specific viewport block', () => { + test.slow(); + test.use({ viewport: { width: 1600, height: 1200 } }); + + test('has title', async ({ page }) => { + await page.goto(`${URL_GCT}`); + + // wait for page to load (i.e. spinner to disappear) + await waitForSpinnerNotVisible(page, 250_000); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle('Gene Comparison | Visual comparison tool for AD genes'); + }); + + test('protein has sub-category of SRM by default', async ({ page }) => { + // set category for Protein - Differential Expression + await page.goto(URL_GCT_PROTEIN); + + // wait for page to load (i.e. spinner to disappear) + await waitForSpinnerNotVisible(page, 150_000); + + // expect sub-category dropdown to be SRM + const dropdown = page.locator('#subCategory'); + await expect(dropdown).toHaveText('Targeted Selected Reaction Monitoring (SRM)'); + }); + + test('switching from RNA to Protein with RNA-specific column ordering reverts back to Risk Score descending', async ({ + page, + }) => { + // set category to RNA + await page.goto(`${URL_RNA}`); + + // wait for page to load (i.e. spinner to disappear) + await waitForSpinnerNotVisible(page, 150_000); + + // Gene Comparison Overview tutorial modal + const tutorialModal = page.getByText('Gene Comparison Overview'); + await expect(tutorialModal).toBeVisible({ timeout: 10_000 }); + + // close the Gene Comparison Overview tutorial modal + await closeGctHelpDialog(page); + + // Gene Comparison Overview tutorial modal + await expect(tutorialModal).toBeHidden({ timeout: 10_000 }); + + // sort by FP ascending + const FPColumn = page.getByText('FP'); + // first click sorts descending + await FPColumn.click(); + // second click sorts ascending + await FPColumn.click(); + + // expect url to be correct + expect(page.url()).toBe(`${baseURL}${URL_RNA}sortField=FP&sortOrder=1`); + + // change category to Protein + await changeGctCategory(page, GCT_CATEGORIES.RNA, GCT_CATEGORIES.PROTEIN); + + // expect url to be correct + expect(page.url()).toBe(`${baseURL}${URL_PROTEIN}`); + + // expect sort arrow to be descending + await expect(page.getByRole('columnheader', { name: 'RISK SCORE' }).locator('i')).toHaveClass( + /pi-sort-amount-down/, + ); + }); +}); diff --git a/apps/agora/app/e2e/gene-details.spec.ts b/apps/agora/app/e2e/gene-details.spec.ts new file mode 100644 index 0000000000..2be39f707f --- /dev/null +++ b/apps/agora/app/e2e/gene-details.spec.ts @@ -0,0 +1,30 @@ +import { expect, test } from '@playwright/test'; +import { waitForSpinnerNotVisible } from './helpers/utils'; + +test.describe('specific viewport block', () => { + test.slow(); + test.use({ viewport: { width: 1600, height: 1200 } }); + + test('invalid gene results in a 404 redirect', async ({ page }) => { + // go to invalid ENSG page + await page.goto('/genes/ENSG00000000000'); + await waitForSpinnerNotVisible(page); + + // expect a title "to contain" a substring. + await expect(page).toHaveTitle('Page not found'); + + // expect div for page not found content to be visible + await expect(page.getByRole('heading', { name: 'Page not found.' })).toBeVisible(); + }); + + test('consistency of change section heading is visible when using anchor link', async ({ + page, + }) => { + await page.goto('/genes/ENSG00000178209/evidence/rna#consistency-of-change'); + + await waitForSpinnerNotVisible(page); + + const header = page.getByRole('heading', { name: 'Consistency of Change in Expression' }); + await expect(header).toBeInViewport(); + }); +}); diff --git a/apps/agora/app/e2e/gene-resources.spec.ts b/apps/agora/app/e2e/gene-resources.spec.ts new file mode 100644 index 0000000000..5d47db4248 --- /dev/null +++ b/apps/agora/app/e2e/gene-resources.spec.ts @@ -0,0 +1,35 @@ +import { expect, test } from '@playwright/test'; +import { waitForSpinnerNotVisible } from './helpers/utils'; + +test.describe('specific viewport block', () => { + test.slow(); + test.use({ viewport: { width: 1600, height: 1200 } }); + + test('has title', async ({ page }) => { + await page.goto('/genes/ENSG00000178209/resources'); + + // wait for page to load (i.e. spinner to disappear) + await waitForSpinnerNotVisible(page); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle('Agora'); + }); + + test('AMP-PD explorer link to go to gene', async ({ page }) => { + test.slow(); + await page.goto('/genes/ENSG00000178209/resources'); + + // wait for page to load (i.e. spinner to disappear) + await waitForSpinnerNotVisible(page, 150000); + + // expect link named 'Visit AMP-PD' + const link = page.getByRole('link', { name: 'Visit AMP-PD' }); + await expect(link).toHaveText('Visit AMP-PD'); + + // expect url to have ensembleid + await expect(link).toHaveAttribute( + 'href', + 'https://target-explorer.amp-pd.org/genes/target-search?gene=ENSG00000178209', + ); + }); +}); diff --git a/apps/agora/app/e2e/gene-similar.spec.ts b/apps/agora/app/e2e/gene-similar.spec.ts new file mode 100644 index 0000000000..478df2fae0 --- /dev/null +++ b/apps/agora/app/e2e/gene-similar.spec.ts @@ -0,0 +1,38 @@ +import { expect, test } from '@playwright/test'; +import { waitForSpinnerNotVisible } from './helpers/utils'; + +test.describe('specific viewport block', () => { + test.slow(); + test.use({ viewport: { width: 1600, height: 1200 } }); + + test('invalid gene results in a 404 redirect', async ({ page }) => { + // go to invalid ENSG page + await page.goto('/genes/ENSG00000000000/similar'); + await waitForSpinnerNotVisible(page); + + // expect a title "to contain" a substring. + await expect(page).toHaveTitle('Page not found'); + + // expect div for page not found content to be visible + await expect(page.getByRole('heading', { name: 'Page not found.' })).toBeVisible(); + }); + + test('has title', async ({ page }) => { + await page.goto('/genes/ENSG00000178209/similar'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle('Agora'); + }); + + test('has true and false values for nominated target column', async ({ page }) => { + await page.goto('/genes/ENSG00000178209/similar'); + + await expect(page.locator('table')).toBeVisible(); + + // sort forward on nominated target + await page.getByRole('cell', { name: 'Nominated Target' }).click(); + + const cell = await page.getByRole('row').nth(1).getByRole('cell').nth(1).innerText(); + expect(cell).not.toBe(''); + }); +}); diff --git a/apps/agora/app/e2e/helpers/constants.ts b/apps/agora/app/e2e/helpers/constants.ts new file mode 100644 index 0000000000..1b4ba4d1e5 --- /dev/null +++ b/apps/agora/app/e2e/helpers/constants.ts @@ -0,0 +1,21 @@ +export const GCT_CATEGORIES = { + RNA: 'RNA - Differential Expression', + PROTEIN: 'Protein - Differential Expression', +}; + +export const GCT_RNA_SUBCATEGORIES = { + AD: 'AD Diagnosis (males and females)', + AD_AOD: 'AD Diagnosis x AOD (males and females)', + AD_SEX_F: 'AD Diagnosis x Sex (females only)', + AD_SEX_M: 'AD Diagnosis x Sex (males only)', +}; + +export const GCT_PROTEIN_SUBCATEGORIES = { + SRM: 'Targeted Selected Reaction Monitoring (SRM)', + TMT: 'Genome-wide Tandem Mass Tag (TMT)', + LFQ: 'Genome-wide Label-free Quantification (LFQ)', +}; + +export const URL_GCT = '/genes/comparison'; +export const URL_GCT_PROTEIN = `${URL_GCT}?category=${GCT_CATEGORIES.PROTEIN}`; +export const URL_GCT_PROTEIN_TMT = `${URL_GCT_PROTEIN}&subCategory=TMT`; diff --git a/apps/agora/app/e2e/helpers/data.ts b/apps/agora/app/e2e/helpers/data.ts new file mode 100644 index 0000000000..53d6b43eda --- /dev/null +++ b/apps/agora/app/e2e/helpers/data.ts @@ -0,0 +1,119 @@ +export const fiftyGenes = [ + 'ENSG00000008283', + 'ENSG00000089041', + 'ENSG00000089250', + 'ENSG00000095970', + 'ENSG00000103197', + 'ENSG00000106066', + 'ENSG00000107099', + 'ENSG00000107902', + 'ENSG00000110514', + 'ENSG00000112294', + 'ENSG00000124299', + 'ENSG00000125877', + 'ENSG00000126602', + 'ENSG00000130203', + 'ENSG00000130414', + 'ENSG00000130702', + 'ENSG00000132561', + 'ENSG00000134324', + 'ENSG00000138095', + 'ENSG00000140945', + 'ENSG00000141338', + 'ENSG00000142192', + 'ENSG00000143252', + 'ENSG00000144455', + 'ENSG00000146648', + 'ENSG00000147852', + 'ENSG00000153113', + 'ENSG00000158864', + 'ENSG00000158887', + 'ENSG00000160285', + 'ENSG00000161011', + 'ENSG00000161904', + 'ENSG00000162688', + 'ENSG00000164347', + 'ENSG00000171608', + 'ENSG00000178209', + 'ENSG00000182512', + 'ENSG00000185532', + 'ENSG00000186153', + 'ENSG00000186868', + 'ENSG00000187323', + 'ENSG00000196126', + 'ENSG00000197535', + 'ENSG00000197912', + 'ENSG00000197943', + 'ENSG00000204287', + 'ENSG00000204525', + 'ENSG00000214960', + 'ENSG00000234745', + 'ENSG00000273079', +]; + +export const fiftyOneGenes = [...fiftyGenes, 'ENSG00000100504']; + +export const geneWithMultipleProteinsTMT = { + name: 'GFAP', + ensemblId: 'ENSG00000131095', + uniProtIds: ['K7EKD1', 'K7ELP4', 'K7EPI4', 'P14136', 'P14136-3'], +}; +export const gene2WithMultipleProteinsTMT = { + name: 'PLEC', + ensemblId: 'ENSG00000178209', + uniProtIds: ['Q15149', 'Q15149-3', 'Q15149-8'], +}; + +// Includes 50 unique genes for a specific protein in TMT dataset +export const fiftyProteinsToFiftyUniqueGenesTMT = [ + 'ENSG00000003147Q05084', + 'ENSG00000023228P28331', + 'ENSG00000038427P13611', + 'ENSG00000069966O14775', + 'ENSG00000078674Q15154', + 'ENSG00000091140P09622', + 'ENSG00000100504P06737', + 'ENSG00000103485Q15274', + 'ENSG00000103723Q13367-2', + 'ENSG00000104763Q13510', + 'ENSG00000105607Q92947', + 'ENSG00000106976Q05193', + 'ENSG00000108515P13929', + 'ENSG00000118473Q9BQI5-5', + 'ENSG00000126267P14854', + 'ENSG00000130203P02649', + 'ENSG00000130414O95299', + 'ENSG00000132470P16144-3', + 'ENSG00000134324Q14693', + 'ENSG00000136153Q8WWI1-2', + 'ENSG00000138095P42704', + 'ENSG00000139180F5GY40', + 'ENSG00000141295J3QL71', + 'ENSG00000141338O94911-3', + 'ENSG00000143603A0A087WYJ0', + 'ENSG00000150995Q14643-2', + 'ENSG00000151150A0A087WVC2', + 'ENSG00000151655P19823', + 'ENSG00000151834E9PBQ7', + 'ENSG00000158864O75306', + 'ENSG00000159082C9JFZ1', + 'ENSG00000160789P02545', + 'ENSG00000162946Q9NRI5', + 'ENSG00000163596F8WBQ1', + 'ENSG00000172803Q86XE0', + 'ENSG00000174469Q9UHC6', + 'ENSG00000174886Q86Y39', + 'ENSG00000175161Q8N3J6', + 'ENSG00000175287Q5SRE7', + 'ENSG00000178209Q15149', + 'ENSG00000183117E5RIG2', + 'ENSG00000183454Q12879', + 'ENSG00000196126P20039', + 'ENSG00000197912Q9UQ90', + 'ENSG00000197943P16885', + 'ENSG00000198087Q9Y5K6', + 'ENSG00000204287P01903', + 'ENSG00000213722O95865', + 'ENSG00000261701P00739', + 'ENSG00000273079Q13224', +]; diff --git a/apps/agora/app/e2e/helpers/gct-pinning.ts b/apps/agora/app/e2e/helpers/gct-pinning.ts new file mode 100644 index 0000000000..7dfe547340 --- /dev/null +++ b/apps/agora/app/e2e/helpers/gct-pinning.ts @@ -0,0 +1,115 @@ +import test, { expect, Page } from '@playwright/test'; +import { getGeneRowButtons } from './gct'; +import { convertToQueryParam } from './utils'; + +export const closePinnedGeneWarningModal = async (page: Page) => { + await test.step('click through warning modal', async () => { + const dialog = page.getByRole('dialog').filter({ + hasText: /you will lose some of your pins if you proceed/i, + }); + const proceedBtn = dialog.getByRole('button', { name: 'Proceed' }); + await proceedBtn.click(); + await expect(dialog).toBeHidden(); + }); +}; + +export const formatPinnedGenesQueryParam = (pinnedGenes: string[]) => { + return convertToQueryParam(pinnedGenes, 'pinned'); +}; + +export const expectPinnedGenesCountText = async (page: Page, nGenes: number) => { + await expect(page.getByText(`Pinned Genes (${nGenes}/50)`)).toBeVisible(); +}; + +export const expectPinnedProteinsCountText = async (page: Page, nProteins: number) => { + await expect(page.getByText(`${nProteins} Protein${nProteins > 1 ? 's' : ''}`)).toBeVisible(); +}; + +export const expectTooManyPinnedGenesToast = async (page: Page) => { + const alert = page.getByRole('alert'); + await expect(alert).toHaveText( + 'Only 50 rows were pinned, because you reached the maximum of 50 pinned genes.', + ); +}; + +export const getPinnedItemsFromUrl = (url: string) => { + const queryString = url.split('?'); + if (queryString.length !== 2) return null; + + const urlParams = new URLSearchParams(queryString[1]); + const genesString = urlParams.get('pinned'); + return genesString?.split(/%2C|,/); +}; + +export const confirmPinnedItemsCount = async (page: Page, nPinned: number) => { + await test.step(`confirm pinned items count is ${nPinned}`, async () => { + const rows = page.locator('tr.pinned'); + const rowsCount = await rows.count(); + expect(rowsCount).toBe(nPinned); + }); +}; + +export const confirmPinnedItemsByGeneName = async ( + page: Page, + geneName: string, + nItems: number, +) => { + await test.step(`confirm ${nItems} pinned items for ${geneName}`, async () => { + const rows = page.getByRole('row').filter({ hasText: geneName }); + const nRows = await rows.count(); + expect(nRows).toEqual(nItems); + + for (let i = 0; i < nRows; ++i) { + await expect(rows.nth(i)).toHaveClass(/pinned/i); + } + }); +}; + +export const confirmPinnedItemsByItemName = async (page: Page, itemNames: string[]) => { + for (const itemName of itemNames) { + await confirmPinnedItemsByGeneName(page, itemName, 1); + } +}; + +export const confirmPinnedProteins = async (page: Page, geneName: string, proteins: string[]) => { + const geneProteins = proteins.map((protein) => `${geneName} (${protein})`); + await confirmPinnedItemsByItemName(page, geneProteins); +}; + +// assumes that geneName will resolve to a single search result +export const pinGeneViaSearch = async (page: Page, geneName: string) => { + await test.step(`search for a gene named ${geneName}`, async () => { + const search = page.getByPlaceholder('Search', { exact: true }); + await search.fill(geneName); + }); + + await test.step('pin the gene', async () => { + const btns = await getGeneRowButtons(page, geneName); + await btns.pin.click(); + }); +}; + +// assumes that each geneName will resolve to a single search result +export const pinMultipleGenesViaSearch = async (page: Page, geneNames: string[]) => { + await test.step('pin genes', async () => { + let pinnedCount = 0; + for (const gene of geneNames) { + await pinGeneViaSearch(page, gene); + pinnedCount++; + await expectPinnedGenesCountText(page, pinnedCount); + await confirmPinnedItemsByGeneName(page, gene, 1); + } + }); +}; + +export const pinAllItemsViaSearchByGene = async (page: Page, geneName: string) => { + await test.step(`search for items named ${geneName}`, async () => { + const search = page.getByPlaceholder('Search', { exact: true }); + await search.fill(geneName); + }); + + await test.step('pin the gene', async () => { + const pinAllBtn = page.getByRole('button', { name: 'Pin All' }); + await pinAllBtn.click(); + }); +}; diff --git a/apps/agora/app/e2e/helpers/gct.ts b/apps/agora/app/e2e/helpers/gct.ts new file mode 100644 index 0000000000..e3f53a0444 --- /dev/null +++ b/apps/agora/app/e2e/helpers/gct.ts @@ -0,0 +1,115 @@ +import { Page, expect, test } from '@playwright/test'; +import { GCT_CATEGORIES, GCT_PROTEIN_SUBCATEGORIES, GCT_RNA_SUBCATEGORIES } from './constants'; +import { waitForSpinnerNotVisible } from './utils'; + +export const expectGctPageLoaded = async ( + page: Page, + category: string, + subcategory: string, + closeHelpDialog = true, +) => { + await test.step('wait for GCT page to load', async () => { + if (closeHelpDialog) await closeGctHelpDialog(page); + + await waitForSpinnerNotVisible(page); + + await expect(page.getByRole('heading', { name: 'Gene Comparison Tool' })).toBeVisible(); + + await expect(page.getByRole('button', { name: 'Filter Genes' })).toBeVisible(); + await expect(page.getByRole('button', { name: 'Share URL' })).toBeVisible(); + + await expect(page.getByText(category)).toBeVisible(); + await expect(page.getByText(subcategory)).toBeVisible(); + }); +}; + +export const closeGctHelpDialog = async (page: Page) => { + await test.step('close GCT help dialog', async () => { + const dialog = page.getByRole('dialog'); + await expect(dialog).toHaveText(/Gene Comparison Overview/i); + + const closeBtn = dialog.getByRole('button').first(); + await closeBtn.click(); + + await expect(dialog).toBeHidden(); + }); +}; + +export const getGeneRowButtons = async (page: Page, name: string) => { + const geneName = page.locator('.gene-controls').filter({ hasText: name }); + await geneName.hover(); + + const buttons = page.getByRole('cell', { name: name }).getByRole('button'); + const count = await buttons.count(); + expect(count).toBe(2); + + return { + open: buttons.first(), + pin: buttons.nth(1), + }; +}; + +const changeGctDropdown = async (page: Page, current: string, desired: string) => { + await test.step(`change dropdown to ${desired}`, async () => { + const url = page.url(); + + await test.step('open dropdown', async () => { + const menu = page.getByText(current); + await menu.click(); + }); + + await test.step('wait for dropdown to stabilize', async () => { + // Must wait for the listbox displaying the options to be stable + // ...before attempting to click on an option + // ...otherwise, clicking an option will be flaky and intermittently fail + // eslint-disable-next-line playwright/no-element-handle + const listbox = await page.$('ul[role=listbox]'); + await listbox?.waitForElementState('stable'); + }); + + const option = await test.step('select new option', async () => { + const option = page.getByRole('option', { name: desired }); + await option.click({ timeout: 30_000 }); + return option; + }); + + await test.step('wait for page to redirect', async () => { + await expect(() => { + expect(page.url()).not.toEqual(url); + }).toPass({ timeout: 60_000 }); + }); + + // Handle case where listbox doesn't close after page redirects + await test.step('ensure dropdown closes', async () => { + await page.keyboard.press('Escape'); + await expect(option).toBeHidden(); + }); + }); +}; + +export const changeGctSubcategory = async ( + page: Page, + category: string, + currentSubCategory: string, + desiredSubCategory: string, +) => { + await test.step(`change to ${desiredSubCategory} subcategory`, async () => { + await changeGctDropdown(page, currentSubCategory, desiredSubCategory); + await expectGctPageLoaded(page, category, desiredSubCategory, false); + }); +}; + +export const changeGctCategory = async ( + page: Page, + currentCategory: string, + desiredCategory: string, +) => { + await test.step(`change to ${desiredCategory} category`, async () => { + const defaultSubcategory = + desiredCategory === GCT_CATEGORIES.RNA + ? GCT_RNA_SUBCATEGORIES.AD + : GCT_PROTEIN_SUBCATEGORIES.SRM; + await changeGctDropdown(page, currentCategory, desiredCategory); + await expectGctPageLoaded(page, desiredCategory, defaultSubcategory, false); + }); +}; diff --git a/apps/agora/app/e2e/helpers/utils.ts b/apps/agora/app/e2e/helpers/utils.ts new file mode 100644 index 0000000000..a810b291a2 --- /dev/null +++ b/apps/agora/app/e2e/helpers/utils.ts @@ -0,0 +1,9 @@ +import { Page, expect } from '@playwright/test'; + +export const waitForSpinnerNotVisible = async (page: Page, timeout = 60 * 2 * 1000) => { + await expect(page.locator('div:nth-child(4) > div > .spinner')).toBeHidden({ timeout: timeout }); +}; + +export const convertToQueryParam = (list: string[], queryKey: string) => { + return `${queryKey}=${list.join(',')}`; +}; diff --git a/apps/agora/app/e2e/homepage.spec.ts b/apps/agora/app/e2e/homepage.spec.ts new file mode 100644 index 0000000000..dc70887a5a --- /dev/null +++ b/apps/agora/app/e2e/homepage.spec.ts @@ -0,0 +1,49 @@ +import { expect, test } from '@playwright/test'; + +test.describe('specific viewport block', () => { + test.use({ viewport: { width: 1600, height: 1200 } }); + + test('has title', async ({ page }) => { + await page.goto('/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle("Agora | Explore Alzheimer's Disease Genes"); + }); + + test('has header', async ({ page }) => { + await page.goto('/'); + + // Expect header to be visible + await expect(page.locator('div#header')).toBeVisible(); + }); + + test('has heading', async ({ page }) => { + await page.goto('/'); + + // Expect header to be visible + await expect(page.locator('h1')).toBeVisible(); + }); + + test('has footer', async ({ page }) => { + await page.goto('/'); + + // Expect footer to be visible + await expect(page.getByAltText('footer logo')).toBeVisible(); + }); + + test('has news', async ({ page }) => { + await page.goto('/'); + + // Hamburger menu should be hidden for the desktop viewport + await expect(page.locator('button.header-nav-toggle')).toBeHidden(); + + // look for news link and click it + const newsLink = page.getByRole('link', { name: 'News' }); + + // news link should be visible on the home page + await expect(newsLink).toBeVisible(); + + await newsLink.click(); + await expect(page).toHaveTitle('News | Agora Releases'); + }); +}); diff --git a/apps/agora/app/e2e/nominated-targets.spec.ts b/apps/agora/app/e2e/nominated-targets.spec.ts new file mode 100644 index 0000000000..5d2a684d15 --- /dev/null +++ b/apps/agora/app/e2e/nominated-targets.spec.ts @@ -0,0 +1,14 @@ +import { expect, test } from '@playwright/test'; + +test.describe('specific viewport block', () => { + test.use({ viewport: { width: 1600, height: 1200 } }); + + test('has title', async ({ page }) => { + await page.goto('/genes/nominated-targets'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle( + 'Nominated Targets | Candidate genes for AD treatment or prevention', + ); + }); +}); diff --git a/apps/agora/app/e2e/search.spec.ts b/apps/agora/app/e2e/search.spec.ts new file mode 100644 index 0000000000..b60b9f7c1b --- /dev/null +++ b/apps/agora/app/e2e/search.spec.ts @@ -0,0 +1,30 @@ +import { expect, test } from '@playwright/test'; + +test('can search for gene', async ({ page }) => { + const geneName = 'PTEN'; + const geneHgnc = 'ENSG00000171862'; + + await page.goto('/'); + + expect(await page.locator('h1').innerText()).toContain("Discover Alzheimer's Disease Genes"); + + // open hamburger menu + await page.locator('#header').getByRole('button').click(); + + const responsePromise = page.waitForResponse(`**/genes/search?id=${geneName}`); + const input = page.getByPlaceholder('Search genes'); + await input.pressSequentially(geneName); + await responsePromise; + + const searchList = page.getByRole('list').filter({ hasText: geneName }); + const searchListItems = searchList.getByRole('listitem'); + await expect(searchListItems).toHaveCount(3); + + const searchListItem = searchListItems.first(); + await expect(searchListItem).toHaveText(geneName); + + await searchListItem.click(); + + await page.waitForURL(`/genes/${geneHgnc}`); + await expect(page.getByRole('heading', { level: 1 })).toHaveText(geneName); +}); diff --git a/apps/agora/app/jest.config.ts b/apps/agora/app/jest.config.ts index 8bc3837de1..3b9f6b600c 100644 --- a/apps/agora/app/jest.config.ts +++ b/apps/agora/app/jest.config.ts @@ -14,6 +14,7 @@ export default { ], }, transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], + testPathIgnorePatterns: ['/e2e'], snapshotSerializers: [ 'jest-preset-angular/build/serializers/no-ng-attributes', 'jest-preset-angular/build/serializers/ng-snapshot', diff --git a/apps/agora/app/playwright.config.ts b/apps/agora/app/playwright.config.ts new file mode 100644 index 0000000000..3cce18e49d --- /dev/null +++ b/apps/agora/app/playwright.config.ts @@ -0,0 +1,84 @@ +import { nxE2EPreset } from '@nx/playwright/preset'; +import { defineConfig, devices } from '@playwright/test'; + +import { workspaceRoot } from '@nx/devkit'; + +const port = 8000; +export const baseURL = process.env['BASE_URL'] || `http://localhost:${port}`; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + ...nxE2EPreset(__filename, { testDir: './e2e' }), + // timeout for every test + timeout: 3 * 60 * 1000, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env['CI'], + /* Retry on CI only */ + retries: process.env['CI'] ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env['CI'] ? 2 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: process.env['CI'] ? [['list'], ['html']] : 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + baseURL, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + /* Run your local dev server before starting the tests */ + webServer: { + command: 'nx run agora-apex:serve-detach', + url: `${baseURL}/health`, + reuseExistingServer: !process.env['CI'], + cwd: workspaceRoot, + timeout: 60000 * 3, // give time for agora-data to populate agora-mongo + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + // TODO: remove this line as part of AG-1618 + testIgnore: 'e2e/gene-comparison-tool*.spec.ts', + }, + + /* { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, */ + + // Uncomment for mobile browsers support + /* { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, */ + + // Uncomment for branded browsers + /* { + name: 'Microsoft Edge', + use: { ...devices['Desktop Edge'], channel: 'msedge' }, + }, + { + name: 'Google Chrome', + use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + } */ + ], +}); diff --git a/apps/agora/app/project.json b/apps/agora/app/project.json index 655ec44f49..33798b83fc 100644 --- a/apps/agora/app/project.json +++ b/apps/agora/app/project.json @@ -5,6 +5,7 @@ "prefix": "app", "sourceRoot": "apps/agora/app/src", "tags": [], + "implicitDependencies": ["agora-styles", "agora-themes", "shared-typescript-assets"], "targets": { "create-config": { "executor": "nx:run-commands", @@ -161,7 +162,13 @@ "command": "trivy image ghcr.io/sage-bionetworks/agora-app:local --quiet", "color": true } + }, + "e2e": { + "executor": "@nx/playwright:playwright", + "outputs": ["{workspaceRoot}/dist/.playwright/{projectRoot}"], + "options": { + "config": "{projectRoot}/playwright.config.ts" + } } - }, - "implicitDependencies": ["agora-styles", "agora-themes", "shared-typescript-assets"] + } } diff --git a/apps/agora/app/tsconfig.e2e.json b/apps/agora/app/tsconfig.e2e.json new file mode 100644 index 0000000000..16b5953c05 --- /dev/null +++ b/apps/agora/app/tsconfig.e2e.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "types": ["node"] + }, + "files": [], + "include": ["e2e/**/*.spec.ts", "e2e/**/*.ts"], + "exclude": [] +} diff --git a/apps/agora/app/tsconfig.json b/apps/agora/app/tsconfig.json index a3935e67e6..0daa5b6d7f 100644 --- a/apps/agora/app/tsconfig.json +++ b/apps/agora/app/tsconfig.json @@ -25,6 +25,9 @@ }, { "path": "./tsconfig.server.json" + }, + { + "path": "./tsconfig.e2e.json" } ], "extends": "../../../tsconfig.base.json", diff --git a/docker/agora/services/apex.yml b/docker/agora/services/apex.yml index 680c27f14d..a2a6b268e4 100644 --- a/docker/agora/services/apex.yml +++ b/docker/agora/services/apex.yml @@ -11,6 +11,11 @@ services: - agora ports: - '8000:80' + healthcheck: + test: ['CMD', 'curl', '--fail', 'http://localhost:80/health'] + interval: 30s + timeout: 10s + retries: 3 depends_on: agora-api-docs: condition: service_healthy diff --git a/package.json b/package.json index 1ea3db57af..f2092b931b 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "json5": "2.2.3", "lodash": "4.17.21", "mariadb": "3.3.1", - "mongoose": "^8.4.4", + "mongoose": "8.5.1", "ngx-avatars": "1.5.0", "ngx-cookie-service": "18.0.0", "ngx-countup": "13.1.0", @@ -104,7 +104,7 @@ "@nx/workspace": "20.3.1", "@nxlv/python": "20.0.0", "@openapitools/openapi-generator-cli": "2.5.2", - "@playwright/test": "^1.36.0", + "@playwright/test": "1.49.1", "@prettier/plugin-xml": "2.2.0", "@redocly/cli": "1.25.3", "@schematics/angular": "19.0.7", @@ -114,8 +114,8 @@ "@swc-node/register": "~1.9.1", "@swc/cli": "~0.3.12", "@swc/core": "~1.5.7", - "@testing-library/angular": "^16.0.0", - "@testing-library/jest-dom": "^6.4.5", + "@testing-library/angular": "16.0.0", + "@testing-library/jest-dom": "6.4.6", "@testing-library/react": "15.0.6", "@types/d3": "7.4.3", "@types/dc": "4.2.5", @@ -129,16 +129,16 @@ "@types/react": "18.3.7", "@types/react-dom": "18.3.0", "@types/sanitize-html": "2.13.0", - "@types/supertest": "^2.0.12", + "@types/supertest": "2.0.16", "@types/validator": "13.7.10", "@typescript-eslint/eslint-plugin": "7.18.0", "@typescript-eslint/parser": "7.18.0", "@typescript-eslint/utils": "7.18.0", - "@vitejs/plugin-react": "^4.2.0", - "@vitest/ui": "^1.3.1", + "@vitejs/plugin-react": "4.3.1", + "@vitest/ui": "1.6.0", "autoprefixer": "10.4.13", "babel-jest": "29.7.0", - "browser-sync": "^3.0.0", + "browser-sync": "3.0.2", "cdktf-cli": "0.16.1", "colors": "1.4.0", "coveralls": "3.1.1", @@ -152,7 +152,7 @@ "eslint-plugin-jsx-a11y": "6.10.1", "eslint-plugin-n": "15.6.0", "eslint-plugin-node": "11.1.0", - "eslint-plugin-playwright": "^0.15.3", + "eslint-plugin-playwright": "0.15.3", "eslint-plugin-promise": "6.1.1", "eslint-plugin-react": "7.32.2", "eslint-plugin-react-hooks": "5.0.0", @@ -169,10 +169,10 @@ "nock": "13.2.9", "nodemon": "2.0.20", "nx": "20.3.1", - "openapi-merge-cli": "^1.3.1", + "openapi-merge-cli": "1.3.2", "postcss": "8.4.38", - "postcss-preset-env": "^7.8.3", - "postcss-url": "^10.1.3", + "postcss-preset-env": "7.8.3", + "postcss-url": "10.1.3", "prettier": "3.3.3", "prettier-plugin-java": "2.6.4", "prettier-plugin-sql": "0.18.1", @@ -188,13 +188,13 @@ "stylelint-config-standard": "36.0.1", "stylelint-config-standard-scss": "13.1.0", "stylelint-scss": "6.7.0", - "supertest": "^6.3.0", + "supertest": "6.3.4", "tailwindcss": "3.4.3", "ts-jest": "29.1.1", "ts-node": "10.9.1", "typescript": "5.6.3", - "vite": "^5.0.0", - "vitest": "^1.3.1" + "vite": "5.2.11", + "vitest": "1.6.0" }, "packageManager": "pnpm@9.9.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f599e5e6d4..9baaf5793f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -108,7 +108,7 @@ importers: specifier: 3.3.1 version: 3.3.1 mongoose: - specifier: ^8.4.4 + specifier: 8.5.1 version: 8.5.1(socks@2.8.3) ngx-avatars: specifier: 1.5.0 @@ -239,7 +239,7 @@ importers: version: 20.3.1(@babel/traverse@7.26.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.1)(@zkochan/js-yaml@0.0.7)(debug@4.3.7)(eslint@8.57.0)(nx@20.3.1(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(ts-node@10.9.1(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.1)(typescript@5.6.3))(typescript@5.6.3) '@nx/playwright': specifier: 20.3.1 - version: 20.3.1(@babel/traverse@7.26.4)(@playwright/test@1.45.2)(@rspack/core@1.1.8(@swc/helpers@0.5.12))(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.1)(@zkochan/js-yaml@0.0.7)(debug@4.3.7)(esbuild@0.23.0)(eslint@8.57.0)(html-webpack-plugin@5.6.0(@rspack/core@1.1.8(@swc/helpers@0.5.12))(webpack@5.94.0(@swc/core@1.5.29(@swc/helpers@0.5.12))(esbuild@0.23.0)))(nx@20.3.1(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(typescript@5.6.3)(vite@5.2.11(@types/node@22.5.1)(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0))(vitest@1.6.0(@types/node@22.5.1)(@vitest/ui@1.6.0)(jsdom@22.1.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0)) + version: 20.3.1(@babel/traverse@7.26.4)(@playwright/test@1.49.1)(@rspack/core@1.1.8(@swc/helpers@0.5.12))(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.1)(@zkochan/js-yaml@0.0.7)(debug@4.3.7)(esbuild@0.23.0)(eslint@8.57.0)(html-webpack-plugin@5.6.0(@rspack/core@1.1.8(@swc/helpers@0.5.12))(webpack@5.94.0(@swc/core@1.5.29(@swc/helpers@0.5.12))(esbuild@0.23.0)))(nx@20.3.1(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(typescript@5.6.3)(vite@5.2.11(@types/node@22.5.1)(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0))(vitest@1.6.0(@types/node@22.5.1)(@vitest/ui@1.6.0)(jsdom@22.1.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0)) '@nx/plugin': specifier: 20.3.1 version: 20.3.1(@babel/traverse@7.26.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.1)(@zkochan/js-yaml@0.0.7)(debug@4.3.7)(eslint@8.57.0)(nx@20.3.1(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(ts-node@10.9.1(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.1)(typescript@5.6.3))(typescript@5.6.3) @@ -268,8 +268,8 @@ importers: specifier: 2.5.2 version: 2.5.2(debug@4.3.7)(encoding@0.1.13) '@playwright/test': - specifier: ^1.36.0 - version: 1.45.2 + specifier: 1.49.1 + version: 1.49.1 '@prettier/plugin-xml': specifier: 2.2.0 version: 2.2.0 @@ -298,10 +298,10 @@ importers: specifier: ~1.5.7 version: 1.5.29(@swc/helpers@0.5.12) '@testing-library/angular': - specifier: ^16.0.0 + specifier: 16.0.0 version: 16.0.0(@angular/common@19.0.6(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0))(@angular/platform-browser@19.0.6(@angular/animations@19.0.6(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0)))(@angular/common@19.0.6(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0)))(@angular/router@19.0.6(@angular/common@19.0.6(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0))(@angular/platform-browser@19.0.6(@angular/animations@19.0.6(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0)))(@angular/common@19.0.6(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0)))(rxjs@7.8.1)) '@testing-library/jest-dom': - specifier: ^6.4.5 + specifier: 6.4.6 version: 6.4.6(@jest/globals@29.7.0)(@types/jest@29.5.13)(jest@29.7.0(@types/node@22.5.1)(ts-node@10.9.1(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.1)(typescript@5.6.3)))(vitest@1.6.0(@types/node@22.5.1)(@vitest/ui@1.6.0)(jsdom@22.1.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0)) '@testing-library/react': specifier: 15.0.6 @@ -343,7 +343,7 @@ importers: specifier: 2.13.0 version: 2.13.0 '@types/supertest': - specifier: ^2.0.12 + specifier: 2.0.16 version: 2.0.16 '@types/validator': specifier: 13.7.10 @@ -358,10 +358,10 @@ importers: specifier: 7.18.0 version: 7.18.0(eslint@8.57.0)(typescript@5.6.3) '@vitejs/plugin-react': - specifier: ^4.2.0 + specifier: 4.3.1 version: 4.3.1(vite@5.2.11(@types/node@22.5.1)(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0)) '@vitest/ui': - specifier: ^1.3.1 + specifier: 1.6.0 version: 1.6.0(vitest@1.6.0) autoprefixer: specifier: 10.4.13 @@ -370,7 +370,7 @@ importers: specifier: 29.7.0 version: 29.7.0(@babel/core@7.25.2) browser-sync: - specifier: ^3.0.0 + specifier: 3.0.2 version: 3.0.2(debug@4.3.7) cdktf-cli: specifier: 0.16.1 @@ -412,7 +412,7 @@ importers: specifier: 11.1.0 version: 11.1.0(eslint@8.57.0) eslint-plugin-playwright: - specifier: ^0.15.3 + specifier: 0.15.3 version: 0.15.3(eslint-plugin-jest@27.1.7(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0)(jest@29.7.0(@types/node@22.5.1)(ts-node@10.9.1(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.1)(typescript@5.6.3)))(typescript@5.6.3))(eslint@8.57.0) eslint-plugin-promise: specifier: 6.1.1 @@ -463,16 +463,16 @@ importers: specifier: 20.3.1 version: 20.3.1(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) openapi-merge-cli: - specifier: ^1.3.1 + specifier: 1.3.2 version: 1.3.2(encoding@0.1.13) postcss: specifier: 8.4.38 version: 8.4.38 postcss-preset-env: - specifier: ^7.8.3 + specifier: 7.8.3 version: 7.8.3(postcss@8.4.38) postcss-url: - specifier: ^10.1.3 + specifier: 10.1.3 version: 10.1.3(postcss@8.4.38) prettier: specifier: 3.3.3 @@ -520,7 +520,7 @@ importers: specifier: 6.7.0 version: 6.7.0(stylelint@16.9.0(typescript@5.6.3)) supertest: - specifier: ^6.3.0 + specifier: 6.3.4 version: 6.3.4 tailwindcss: specifier: 3.4.3 @@ -535,23 +535,23 @@ importers: specifier: 5.6.3 version: 5.6.3 vite: - specifier: ^5.0.0 + specifier: 5.2.11 version: 5.2.11(@types/node@22.5.1)(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0) vitest: - specifier: ^1.3.1 + specifier: 1.6.0 version: 1.6.0(@types/node@22.5.1)(@vitest/ui@1.6.0)(jsdom@22.1.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0) libs/sage-monorepo/nx-plugin: dependencies: '@nx/devkit': specifier: 19.8.0 - version: 19.8.0(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)) + version: 19.8.0(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)) '@nx/js': specifier: 19.8.0 - version: 19.8.0(@babel/traverse@7.26.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.5)(debug@4.3.7)(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(typescript@5.8.0-dev.20250109) + version: 19.8.0(@babel/traverse@7.26.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.5)(debug@4.3.7)(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(typescript@5.8.0-dev.20250115) nx: specifier: 19.8.0 - version: 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) + version: 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) tslib: specifier: ^2.3.0 version: 2.4.1 @@ -4173,8 +4173,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@playwright/test@1.45.2': - resolution: {integrity: sha512-JxG9eq92ET75EbVi3s+4sYbcG7q72ECeZNbdBlaMkGcNbiDQ4cAi8U2QP5oKkOx+1gpaiL1LDStmzCaEM1Z6fQ==} + '@playwright/test@1.49.1': + resolution: {integrity: sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==} engines: {node: '>=18'} hasBin: true @@ -4227,11 +4227,6 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.18.1': - resolution: {integrity: sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==} - cpu: [arm] - os: [android] - '@rollup/rollup-android-arm-eabi@4.26.0': resolution: {integrity: sha512-gJNwtPDGEaOEgejbaseY6xMFu+CPltsc8/T+diUTTbOQLqD+bnrJq9ulH6WD69TqwqWmrfRAtUv30cCFZlbGTQ==} cpu: [arm] @@ -4242,11 +4237,6 @@ packages: cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.18.1': - resolution: {integrity: sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==} - cpu: [arm64] - os: [android] - '@rollup/rollup-android-arm64@4.26.0': resolution: {integrity: sha512-YJa5Gy8mEZgz5JquFruhJODMq3lTHWLm1fOy+HIANquLzfIOzE9RA5ie3JjCdVb9r46qfAQY/l947V0zfGJ0OQ==} cpu: [arm64] @@ -4257,11 +4247,6 @@ packages: cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.18.1': - resolution: {integrity: sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==} - cpu: [arm64] - os: [darwin] - '@rollup/rollup-darwin-arm64@4.26.0': resolution: {integrity: sha512-ErTASs8YKbqTBoPLp/kA1B1Um5YSom8QAc4rKhg7b9tyyVqDBlQxy7Bf2wW7yIlPGPg2UODDQcbkTlruPzDosw==} cpu: [arm64] @@ -4272,11 +4257,6 @@ packages: cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.18.1': - resolution: {integrity: sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==} - cpu: [x64] - os: [darwin] - '@rollup/rollup-darwin-x64@4.26.0': resolution: {integrity: sha512-wbgkYDHcdWW+NqP2mnf2NOuEbOLzDblalrOWcPyY6+BRbVhliavon15UploG7PpBRQ2bZJnbmh8o3yLoBvDIHA==} cpu: [x64] @@ -4307,11 +4287,6 @@ packages: cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.18.1': - resolution: {integrity: sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==} - cpu: [arm] - os: [linux] - '@rollup/rollup-linux-arm-gnueabihf@4.26.0': resolution: {integrity: sha512-paHF1bMXKDuizaMODm2bBTjRiHxESWiIyIdMugKeLnjuS1TCS54MF5+Y5Dx8Ui/1RBPVRE09i5OUlaLnv8OGnA==} cpu: [arm] @@ -4322,11 +4297,6 @@ packages: cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.18.1': - resolution: {integrity: sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==} - cpu: [arm] - os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.26.0': resolution: {integrity: sha512-cwxiHZU1GAs+TMxvgPfUDtVZjdBdTsQwVnNlzRXC5QzIJ6nhfB4I1ahKoe9yPmoaA/Vhf7m9dB1chGPpDRdGXg==} cpu: [arm] @@ -4337,11 +4307,6 @@ packages: cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.18.1': - resolution: {integrity: sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==} - cpu: [arm64] - os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.26.0': resolution: {integrity: sha512-4daeEUQutGRCW/9zEo8JtdAgtJ1q2g5oHaoQaZbMSKaIWKDQwQ3Yx0/3jJNmpzrsScIPtx/V+1AfibLisb3AMQ==} cpu: [arm64] @@ -4352,11 +4317,6 @@ packages: cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.18.1': - resolution: {integrity: sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==} - cpu: [arm64] - os: [linux] - '@rollup/rollup-linux-arm64-musl@4.26.0': resolution: {integrity: sha512-eGkX7zzkNxvvS05ROzJ/cO/AKqNvR/7t1jA3VZDi2vRniLKwAWxUr85fH3NsvtxU5vnUUKFHKh8flIBdlo2b3Q==} cpu: [arm64] @@ -4372,11 +4332,6 @@ packages: cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.18.1': - resolution: {integrity: sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==} - cpu: [ppc64] - os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.26.0': resolution: {integrity: sha512-Odp/lgHbW/mAqw/pU21goo5ruWsytP7/HCC/liOt0zcGG0llYWKrd10k9Fj0pdj3prQ63N5yQLCLiE7HTX+MYw==} cpu: [ppc64] @@ -4387,11 +4342,6 @@ packages: cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.18.1': - resolution: {integrity: sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==} - cpu: [riscv64] - os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.26.0': resolution: {integrity: sha512-MBR2ZhCTzUgVD0OJdTzNeF4+zsVogIR1U/FsyuFerwcqjZGvg2nYe24SAHp8O5sN8ZkRVbHwlYeHqcSQ8tcYew==} cpu: [riscv64] @@ -4402,11 +4352,6 @@ packages: cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.18.1': - resolution: {integrity: sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==} - cpu: [s390x] - os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.26.0': resolution: {integrity: sha512-YYcg8MkbN17fMbRMZuxwmxWqsmQufh3ZJFxFGoHjrE7bv0X+T6l3glcdzd7IKLiwhT+PZOJCblpnNlz1/C3kGQ==} cpu: [s390x] @@ -4417,11 +4362,6 @@ packages: cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.18.1': - resolution: {integrity: sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==} - cpu: [x64] - os: [linux] - '@rollup/rollup-linux-x64-gnu@4.26.0': resolution: {integrity: sha512-ZuwpfjCwjPkAOxpjAEjabg6LRSfL7cAJb6gSQGZYjGhadlzKKywDkCUnJ+KEfrNY1jH5EEoSIKLCb572jSiglA==} cpu: [x64] @@ -4432,11 +4372,6 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.18.1': - resolution: {integrity: sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==} - cpu: [x64] - os: [linux] - '@rollup/rollup-linux-x64-musl@4.26.0': resolution: {integrity: sha512-+HJD2lFS86qkeF8kNu0kALtifMpPCZU80HvwztIKnYwym3KnA1os6nsX4BGSTLtS2QVAGG1P3guRgsYyMA0Yhg==} cpu: [x64] @@ -4447,11 +4382,6 @@ packages: cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.18.1': - resolution: {integrity: sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==} - cpu: [arm64] - os: [win32] - '@rollup/rollup-win32-arm64-msvc@4.26.0': resolution: {integrity: sha512-WUQzVFWPSw2uJzX4j6YEbMAiLbs0BUysgysh8s817doAYhR5ybqTI1wtKARQKo6cGop3pHnrUJPFCsXdoFaimQ==} cpu: [arm64] @@ -4462,11 +4392,6 @@ packages: cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.18.1': - resolution: {integrity: sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==} - cpu: [ia32] - os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.26.0': resolution: {integrity: sha512-D4CxkazFKBfN1akAIY6ieyOqzoOoBV1OICxgUblWxff/pSjCA2khXlASUx7mK6W1oP4McqhgcCsu6QaLj3WMWg==} cpu: [ia32] @@ -4477,11 +4402,6 @@ packages: cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.18.1': - resolution: {integrity: sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==} - cpu: [x64] - os: [win32] - '@rollup/rollup-win32-x64-msvc@4.26.0': resolution: {integrity: sha512-2x8MO1rm4PGEP0xWbubJW5RtbNLk3puzAMaLQd3B3JHVw4KcHlmXcO+Wewx9zCoo7EUFiMlu/aZbCJ7VjMzAag==} cpu: [x64] @@ -10272,9 +10192,6 @@ packages: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true - magic-string@0.30.10: - resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} - magic-string@0.30.11: resolution: {integrity: sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==} @@ -11441,13 +11358,13 @@ packages: pkg-types@1.1.3: resolution: {integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==} - playwright-core@1.45.2: - resolution: {integrity: sha512-ha175tAWb0dTK0X4orvBIqi3jGEt701SMxMhyujxNrgd8K0Uy5wMSwwcQHtyB4om7INUkfndx02XnQ2p6dvLDw==} + playwright-core@1.49.1: + resolution: {integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==} engines: {node: '>=18'} hasBin: true - playwright@1.45.2: - resolution: {integrity: sha512-ReywF2t/0teRvNBpfIgh5e4wnrI/8Su8ssdo5XsQKpjxJj+jspm00jSoz9BTg91TT0c9HRjXO7LBNVrgYj9X0g==} + playwright@1.49.1: + resolution: {integrity: sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==} engines: {node: '>=18'} hasBin: true @@ -12503,11 +12420,6 @@ packages: rollbar@2.26.4: resolution: {integrity: sha512-JKmrj6riYm9ZPJisgxljgH4uCsvjMHDHXrinDF7aAFaP+eoF51HomVPtLcDTYLsrJ568aKVNLUhedFajONBwSg==} - rollup@4.18.1: - resolution: {integrity: sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - rollup@4.26.0: resolution: {integrity: sha512-ilcl12hnWonG8f+NxU6BlgysVA0gvY2l8N0R84S1HcINbW20bvwuCngJkkInV6LXhwRpucsW5k1ovDwEdBVrNg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -13789,8 +13701,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@5.8.0-dev.20250109: - resolution: {integrity: sha512-p7pWPypjuK2v/76T8q25sTmuG17m53WWranllky9WiG/PUcIJOMXoGfNrRF3r5ufFfi49QM9TipJyfmeKgXRjg==} + typescript@5.8.0-dev.20250115: + resolution: {integrity: sha512-SHJfXbiAiu4xOYnv1pw/sBiXcqmCEbN4q7ueEGD9PypKdFVKdz6pgcVK9pLQ7b+bw8Bjii7YPwD9ofWDL97s5Q==} engines: {node: '>=14.17'} hasBin: true @@ -14921,13 +14833,13 @@ snapshots: '@babel/code-frame@7.24.7': dependencies: '@babel/highlight': 7.24.7 - picocolors: 1.1.0 + picocolors: 1.1.1 '@babel/code-frame@7.26.2': dependencies: '@babel/helper-validator-identifier': 7.25.9 js-tokens: 4.0.0 - picocolors: 1.1.0 + picocolors: 1.1.1 '@babel/compat-data@7.24.9': {} @@ -15044,7 +14956,7 @@ snapshots: dependencies: '@babel/compat-data': 7.24.9 '@babel/helper-validator-option': 7.24.8 - browserslist: 4.23.2 + browserslist: 4.24.4 lru-cache: 5.1.1 semver: 6.3.1 @@ -15052,7 +14964,7 @@ snapshots: dependencies: '@babel/compat-data': 7.25.4 '@babel/helper-validator-option': 7.24.8 - browserslist: 4.23.3 + browserslist: 4.24.4 lru-cache: 5.1.1 semver: 6.3.1 @@ -15153,7 +15065,7 @@ snapshots: '@babel/helper-hoist-variables@7.24.7': dependencies: - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@babel/helper-member-expression-to-functions@7.24.8': dependencies: @@ -15311,7 +15223,7 @@ snapshots: dependencies: '@babel/template': 7.25.0 '@babel/traverse': 7.25.6 - '@babel/types': 7.25.6 + '@babel/types': 7.26.3 transitivePeerDependencies: - supports-color @@ -15343,7 +15255,7 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.1.0 + picocolors: 1.1.1 '@babel/parser@7.24.8': dependencies: @@ -16106,15 +16018,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.24.9)': + '@babel/plugin-transform-react-jsx-self@7.24.7(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.9 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.24.9)': + '@babel/plugin-transform-react-jsx-source@7.24.7(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.9 - '@babel/helper-plugin-utils': 7.24.8 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 '@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2)': dependencies: @@ -16537,7 +16449,7 @@ snapshots: '@babel/traverse@7.24.8': dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@babel/generator': 7.24.10 '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-function-name': 7.24.7 @@ -18204,9 +18116,9 @@ snapshots: transitivePeerDependencies: - nx - '@nrwl/devkit@19.8.0(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))': + '@nrwl/devkit@19.8.0(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))': dependencies: - '@nx/devkit': 19.8.0(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)) + '@nx/devkit': 19.8.0(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)) transitivePeerDependencies: - nx @@ -18231,9 +18143,9 @@ snapshots: - typescript - verdaccio - '@nrwl/js@19.8.0(@babel/traverse@7.26.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.5)(debug@4.3.7)(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(typescript@5.8.0-dev.20250109)': + '@nrwl/js@19.8.0(@babel/traverse@7.26.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.5)(debug@4.3.7)(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(typescript@5.8.0-dev.20250115)': dependencies: - '@nx/js': 19.8.0(@babel/traverse@7.26.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.5)(debug@4.3.7)(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(typescript@5.8.0-dev.20250109) + '@nx/js': 19.8.0(@babel/traverse@7.26.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.5)(debug@4.3.7)(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(typescript@5.8.0-dev.20250115) transitivePeerDependencies: - '@babel/traverse' - '@swc-node/register' @@ -18255,9 +18167,9 @@ snapshots: - '@swc/core' - debug - '@nrwl/tao@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)': + '@nrwl/tao@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)': dependencies: - nx: 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) + nx: 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) tslib: 2.4.1 transitivePeerDependencies: - '@swc-node/register' @@ -18272,9 +18184,9 @@ snapshots: - '@swc/core' - debug - '@nrwl/workspace@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)': + '@nrwl/workspace@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)': dependencies: - '@nx/workspace': 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) + '@nx/workspace': 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) transitivePeerDependencies: - '@swc-node/register' - '@swc/core' @@ -18430,14 +18342,14 @@ snapshots: tslib: 2.4.1 yargs-parser: 21.1.1 - '@nx/devkit@19.8.0(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))': + '@nx/devkit@19.8.0(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))': dependencies: - '@nrwl/devkit': 19.8.0(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)) + '@nrwl/devkit': 19.8.0(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)) ejs: 3.1.10 enquirer: 2.3.6 ignore: 5.3.2 minimatch: 9.0.3 - nx: 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) + nx: 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) semver: 7.6.3 tmp: 0.2.3 tslib: 2.4.1 @@ -18627,7 +18539,7 @@ snapshots: - supports-color - typescript - '@nx/js@19.8.0(@babel/traverse@7.26.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.5)(debug@4.3.7)(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(typescript@5.8.0-dev.20250109)': + '@nx/js@19.8.0(@babel/traverse@7.26.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.5)(debug@4.3.7)(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(typescript@5.8.0-dev.20250115)': dependencies: '@babel/core': 7.25.2 '@babel/plugin-proposal-decorators': 7.24.7(@babel/core@7.25.2) @@ -18636,9 +18548,9 @@ snapshots: '@babel/preset-env': 7.25.3(@babel/core@7.25.2) '@babel/preset-typescript': 7.24.7(@babel/core@7.25.2) '@babel/runtime': 7.25.0 - '@nrwl/js': 19.8.0(@babel/traverse@7.26.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.5)(debug@4.3.7)(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(typescript@5.8.0-dev.20250109) - '@nx/devkit': 19.8.0(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)) - '@nx/workspace': 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) + '@nrwl/js': 19.8.0(@babel/traverse@7.26.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.5)(debug@4.3.7)(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(typescript@5.8.0-dev.20250115) + '@nx/devkit': 19.8.0(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)) + '@nx/workspace': 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) babel-plugin-const-enum: 1.2.0(@babel/core@7.25.2) babel-plugin-macros: 2.8.0 babel-plugin-transform-typescript-metadata: 0.3.2(@babel/core@7.25.2)(@babel/traverse@7.26.4) @@ -18655,7 +18567,7 @@ snapshots: ora: 5.3.0 semver: 7.6.3 source-map-support: 0.5.19 - ts-node: 10.9.1(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.5)(typescript@5.8.0-dev.20250109) + ts-node: 10.9.1(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.5)(typescript@5.8.0-dev.20250115) tsconfig-paths: 4.2.0 tslib: 2.4.1 transitivePeerDependencies: @@ -18862,7 +18774,7 @@ snapshots: '@nx/nx-win32-x64-msvc@20.3.1': optional: true - '@nx/playwright@20.3.1(@babel/traverse@7.26.4)(@playwright/test@1.45.2)(@rspack/core@1.1.8(@swc/helpers@0.5.12))(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.1)(@zkochan/js-yaml@0.0.7)(debug@4.3.7)(esbuild@0.23.0)(eslint@8.57.0)(html-webpack-plugin@5.6.0(@rspack/core@1.1.8(@swc/helpers@0.5.12))(webpack@5.94.0(@swc/core@1.5.29(@swc/helpers@0.5.12))(esbuild@0.23.0)))(nx@20.3.1(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(typescript@5.6.3)(vite@5.2.11(@types/node@22.5.1)(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0))(vitest@1.6.0(@types/node@22.5.1)(@vitest/ui@1.6.0)(jsdom@22.1.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0))': + '@nx/playwright@20.3.1(@babel/traverse@7.26.4)(@playwright/test@1.49.1)(@rspack/core@1.1.8(@swc/helpers@0.5.12))(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.1)(@zkochan/js-yaml@0.0.7)(debug@4.3.7)(esbuild@0.23.0)(eslint@8.57.0)(html-webpack-plugin@5.6.0(@rspack/core@1.1.8(@swc/helpers@0.5.12))(webpack@5.94.0(@swc/core@1.5.29(@swc/helpers@0.5.12))(esbuild@0.23.0)))(nx@20.3.1(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7))(typescript@5.6.3)(vite@5.2.11(@types/node@22.5.1)(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0))(vitest@1.6.0(@types/node@22.5.1)(@vitest/ui@1.6.0)(jsdom@22.1.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0))': dependencies: '@nx/devkit': 20.3.1(nx@20.3.1(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)) '@nx/eslint': 20.3.1(@babel/traverse@7.26.4)(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.1)(@zkochan/js-yaml@0.0.7)(debug@4.3.7)(eslint@8.57.0)(nx@20.3.1(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.6.3))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)) @@ -18873,7 +18785,7 @@ snapshots: minimatch: 9.0.3 tslib: 2.4.1 optionalDependencies: - '@playwright/test': 1.45.2 + '@playwright/test': 1.49.1 transitivePeerDependencies: - '@babel/traverse' - '@parcel/css' @@ -19118,13 +19030,13 @@ snapshots: - '@swc/core' - debug - '@nx/workspace@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)': + '@nx/workspace@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)': dependencies: - '@nrwl/workspace': 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) - '@nx/devkit': 19.8.0(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)) + '@nrwl/workspace': 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) + '@nx/devkit': 19.8.0(nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7)) chalk: 4.1.2 enquirer: 2.3.6 - nx: 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) + nx: 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) tslib: 2.4.1 yargs-parser: 21.1.1 transitivePeerDependencies: @@ -19328,9 +19240,9 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@playwright/test@1.45.2': + '@playwright/test@1.49.1': dependencies: - playwright: 1.45.2 + playwright: 1.49.1 '@polka/url@1.0.0-next.28': {} @@ -19416,36 +19328,24 @@ snapshots: optionalDependencies: rollup: 4.30.1 - '@rollup/rollup-android-arm-eabi@4.18.1': - optional: true - '@rollup/rollup-android-arm-eabi@4.26.0': optional: true '@rollup/rollup-android-arm-eabi@4.30.1': optional: true - '@rollup/rollup-android-arm64@4.18.1': - optional: true - '@rollup/rollup-android-arm64@4.26.0': optional: true '@rollup/rollup-android-arm64@4.30.1': optional: true - '@rollup/rollup-darwin-arm64@4.18.1': - optional: true - '@rollup/rollup-darwin-arm64@4.26.0': optional: true '@rollup/rollup-darwin-arm64@4.30.1': optional: true - '@rollup/rollup-darwin-x64@4.18.1': - optional: true - '@rollup/rollup-darwin-x64@4.26.0': optional: true @@ -19464,36 +19364,24 @@ snapshots: '@rollup/rollup-freebsd-x64@4.30.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.18.1': - optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.26.0': optional: true '@rollup/rollup-linux-arm-gnueabihf@4.30.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.18.1': - optional: true - '@rollup/rollup-linux-arm-musleabihf@4.26.0': optional: true '@rollup/rollup-linux-arm-musleabihf@4.30.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.18.1': - optional: true - '@rollup/rollup-linux-arm64-gnu@4.26.0': optional: true '@rollup/rollup-linux-arm64-gnu@4.30.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.18.1': - optional: true - '@rollup/rollup-linux-arm64-musl@4.26.0': optional: true @@ -19503,72 +19391,48 @@ snapshots: '@rollup/rollup-linux-loongarch64-gnu@4.30.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.18.1': - optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.26.0': optional: true '@rollup/rollup-linux-powerpc64le-gnu@4.30.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.18.1': - optional: true - '@rollup/rollup-linux-riscv64-gnu@4.26.0': optional: true '@rollup/rollup-linux-riscv64-gnu@4.30.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.18.1': - optional: true - '@rollup/rollup-linux-s390x-gnu@4.26.0': optional: true '@rollup/rollup-linux-s390x-gnu@4.30.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.18.1': - optional: true - '@rollup/rollup-linux-x64-gnu@4.26.0': optional: true '@rollup/rollup-linux-x64-gnu@4.30.1': optional: true - '@rollup/rollup-linux-x64-musl@4.18.1': - optional: true - '@rollup/rollup-linux-x64-musl@4.26.0': optional: true '@rollup/rollup-linux-x64-musl@4.30.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.18.1': - optional: true - '@rollup/rollup-win32-arm64-msvc@4.26.0': optional: true '@rollup/rollup-win32-arm64-msvc@4.30.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.18.1': - optional: true - '@rollup/rollup-win32-ia32-msvc@4.26.0': optional: true '@rollup/rollup-win32-ia32-msvc@4.30.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.18.1': - optional: true - '@rollup/rollup-win32-x64-msvc@4.26.0': optional: true @@ -20201,7 +20065,7 @@ snapshots: - '@swc/types' - supports-color - '@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109)': + '@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115)': dependencies: '@swc-node/core': 1.13.3(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9) '@swc-node/sourcemap-support': 0.5.1 @@ -20210,7 +20074,7 @@ snapshots: debug: 4.3.7(supports-color@8.1.1) pirates: 4.0.6 tslib: 2.6.3 - typescript: 5.8.0-dev.20250109 + typescript: 5.8.0-dev.20250115 transitivePeerDependencies: - '@swc/types' - supports-color @@ -20303,8 +20167,8 @@ snapshots: '@angular/core': 19.0.6(rxjs@7.8.1)(zone.js@0.15.0) '@angular/platform-browser': 19.0.6(@angular/animations@19.0.6(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0)))(@angular/common@19.0.6(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0)) '@angular/router': 19.0.6(@angular/common@19.0.6(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0))(@angular/platform-browser@19.0.6(@angular/animations@19.0.6(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0)))(@angular/common@19.0.6(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0))(rxjs@7.8.1))(@angular/core@19.0.6(rxjs@7.8.1)(zone.js@0.15.0)))(rxjs@7.8.1) - '@testing-library/dom': 10.3.2 - tslib: 2.6.3 + '@testing-library/dom': 10.4.0 + tslib: 2.4.1 '@testing-library/dom@10.3.2': dependencies: @@ -20319,8 +20183,8 @@ snapshots: '@testing-library/dom@10.4.0': dependencies: - '@babel/code-frame': 7.24.7 - '@babel/runtime': 7.25.0 + '@babel/code-frame': 7.26.2 + '@babel/runtime': 7.26.0 '@types/aria-query': 5.0.4 aria-query: 5.3.0 chalk: 4.1.2 @@ -20331,8 +20195,8 @@ snapshots: '@testing-library/jest-dom@6.4.6(@jest/globals@29.7.0)(@types/jest@29.5.13)(jest@29.7.0(@types/node@22.5.1)(ts-node@10.9.1(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.1)(typescript@5.6.3)))(vitest@1.6.0(@types/node@22.5.1)(@vitest/ui@1.6.0)(jsdom@22.1.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0))': dependencies: '@adobe/css-tools': 4.4.0 - '@babel/runtime': 7.24.8 - aria-query: 5.3.0 + '@babel/runtime': 7.26.0 + aria-query: 5.3.2 chalk: 3.0.0 css.escape: 1.5.1 dom-accessibility-api: 0.6.3 @@ -20397,20 +20261,20 @@ snapshots: '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.24.8 - '@babel/types': 7.24.9 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 '@types/babel__generator@7.6.8': dependencies: - '@babel/types': 7.24.9 + '@babel/types': 7.26.3 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.24.8 - '@babel/types': 7.24.9 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 '@types/babel__traverse@7.20.6': dependencies: @@ -21099,9 +20963,9 @@ snapshots: '@vitejs/plugin-react@4.3.1(vite@5.2.11(@types/node@22.5.1)(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0))': dependencies: - '@babel/core': 7.24.9 - '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.9) - '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.9) + '@babel/core': 7.26.0 + '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.26.0) + '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.26.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 vite: 5.2.11(@types/node@22.5.1)(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0) @@ -21137,7 +21001,7 @@ snapshots: '@vitest/snapshot@1.6.0': dependencies: - magic-string: 0.30.10 + magic-string: 0.30.12 pathe: 1.1.2 pretty-format: 29.7.0 @@ -21156,7 +21020,7 @@ snapshots: fflate: 0.8.2 flatted: 3.3.1 pathe: 1.1.2 - picocolors: 1.0.1 + picocolors: 1.1.1 sirv: 2.0.4 vitest: 1.6.0(@types/node@22.5.1)(@vitest/ui@1.6.0)(jsdom@22.1.0(canvas@2.11.2(encoding@0.1.13)))(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0) @@ -21951,7 +21815,7 @@ snapshots: fs-extra: 3.0.1 http-proxy: 1.18.1(debug@4.3.7) immutable: 3.8.2 - micromatch: 4.0.7 + micromatch: 4.0.8 opn: 5.3.0 portscanner: 2.2.0 raw-body: 2.5.2 @@ -22108,7 +21972,7 @@ snapshots: caniuse-api@3.0.0: dependencies: - browserslist: 4.23.3 + browserslist: 4.24.4 caniuse-lite: 1.0.30001663 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 @@ -22635,7 +22499,7 @@ snapshots: core-js-compat@3.37.1: dependencies: - browserslist: 4.23.3 + browserslist: 4.24.4 core-js-compat@3.40.0: dependencies: @@ -22871,7 +22735,7 @@ snapshots: cssnano-preset-default@6.1.2(postcss@8.4.38): dependencies: - browserslist: 4.23.3 + browserslist: 4.24.4 css-declaration-sorter: 7.2.0(postcss@8.4.38) cssnano-utils: 4.0.2(postcss@8.4.38) postcss: 8.4.38 @@ -23327,7 +23191,7 @@ snapshots: date-fns@2.30.0: dependencies: - '@babel/runtime': 7.25.0 + '@babel/runtime': 7.26.0 date-format@4.0.14: {} @@ -23643,7 +23507,7 @@ snapshots: dependencies: semver: 7.6.3 shelljs: 0.8.5 - typescript: 5.8.0-dev.20250109 + typescript: 5.8.0-dev.20250115 duplexer@0.1.2: {} @@ -24459,7 +24323,7 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 esutils@2.0.3: {} @@ -26783,7 +26647,7 @@ snapshots: launch-editor@2.8.0: dependencies: - picocolors: 1.1.0 + picocolors: 1.1.1 shell-quote: 1.8.1 lazy-ass@1.6.0: {} @@ -27103,10 +26967,6 @@ snapshots: lz-string@1.5.0: {} - magic-string@0.30.10: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - magic-string@0.30.11: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 @@ -27359,7 +27219,7 @@ snapshots: mlly@1.7.1: dependencies: - acorn: 8.12.1 + acorn: 8.14.0 pathe: 1.1.2 pkg-types: 1.1.3 ufo: 1.5.3 @@ -27889,10 +27749,10 @@ snapshots: transitivePeerDependencies: - debug - nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7): + nx@19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7): dependencies: '@napi-rs/wasm-runtime': 0.2.4 - '@nrwl/tao': 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) + '@nrwl/tao': 19.8.0(@swc-node/register@1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115))(@swc/core@1.5.29(@swc/helpers@0.5.12))(debug@4.3.7) '@yarnpkg/lockfile': 1.1.0 '@yarnpkg/parsers': 3.0.0-rc.46 '@zkochan/js-yaml': 0.0.7 @@ -27937,7 +27797,7 @@ snapshots: '@nx/nx-linux-x64-musl': 19.8.0 '@nx/nx-win32-arm64-msvc': 19.8.0 '@nx/nx-win32-x64-msvc': 19.8.0 - '@swc-node/register': 1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250109) + '@swc-node/register': 1.9.2(@swc/core@1.5.29(@swc/helpers@0.5.12))(@swc/types@0.1.9)(typescript@5.8.0-dev.20250115) '@swc/core': 1.5.29(@swc/helpers@0.5.12) transitivePeerDependencies: - debug @@ -28512,11 +28372,11 @@ snapshots: mlly: 1.7.1 pathe: 1.1.2 - playwright-core@1.45.2: {} + playwright-core@1.49.1: {} - playwright@1.45.2: + playwright@1.49.1: dependencies: - playwright-core: 1.45.2 + playwright-core: 1.49.1 optionalDependencies: fsevents: 2.3.2 @@ -28524,7 +28384,7 @@ snapshots: polished@4.3.1: dependencies: - '@babel/runtime': 7.24.8 + '@babel/runtime': 7.26.0 portfinder@1.0.32: dependencies: @@ -28574,7 +28434,7 @@ snapshots: postcss-colormin@6.1.0(postcss@8.4.38): dependencies: - browserslist: 4.23.3 + browserslist: 4.24.4 caniuse-api: 3.0.0 colord: 2.9.3 postcss: 8.4.38 @@ -28582,7 +28442,7 @@ snapshots: postcss-convert-values@6.1.0(postcss@8.4.38): dependencies: - browserslist: 4.23.3 + browserslist: 4.24.4 postcss: 8.4.38 postcss-value-parser: 4.2.0 @@ -28731,7 +28591,7 @@ snapshots: postcss-merge-rules@6.1.1(postcss@8.4.38): dependencies: - browserslist: 4.23.3 + browserslist: 4.24.4 caniuse-api: 3.0.0 cssnano-utils: 4.0.2(postcss@8.4.38) postcss: 8.4.38 @@ -28751,7 +28611,7 @@ snapshots: postcss-minify-params@6.1.0(postcss@8.4.38): dependencies: - browserslist: 4.23.3 + browserslist: 4.24.4 cssnano-utils: 4.0.2(postcss@8.4.38) postcss: 8.4.38 postcss-value-parser: 4.2.0 @@ -28824,7 +28684,7 @@ snapshots: postcss-normalize-unicode@6.1.0(postcss@8.4.38): dependencies: - browserslist: 4.23.3 + browserslist: 4.24.4 postcss: 8.4.38 postcss-value-parser: 4.2.0 @@ -28879,7 +28739,7 @@ snapshots: '@csstools/postcss-trigonometric-functions': 1.0.2(postcss@8.4.38) '@csstools/postcss-unset-value': 1.0.2(postcss@8.4.38) autoprefixer: 10.4.13(postcss@8.4.38) - browserslist: 4.23.2 + browserslist: 4.24.4 css-blank-pseudo: 3.0.3(postcss@8.4.38) css-has-pseudo: 3.0.4(postcss@8.4.38) css-prefers-color-scheme: 6.0.3(postcss@8.4.38) @@ -28922,7 +28782,7 @@ snapshots: postcss-reduce-initial@6.1.0(postcss@8.4.38): dependencies: - browserslist: 4.23.3 + browserslist: 4.24.4 caniuse-api: 3.0.0 postcss: 8.4.38 @@ -28990,7 +28850,7 @@ snapshots: postcss@8.4.47: dependencies: nanoid: 3.3.7 - picocolors: 1.1.0 + picocolors: 1.1.1 source-map-js: 1.2.1 postcss@8.4.49: @@ -29383,7 +29243,7 @@ snapshots: regenerator-transform@0.15.2: dependencies: - '@babel/runtime': 7.25.0 + '@babel/runtime': 7.26.0 regex-parser@2.3.0: {} @@ -29572,28 +29432,6 @@ snapshots: optionalDependencies: decache: 3.1.0 - rollup@4.18.1: - dependencies: - '@types/estree': 1.0.5 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.18.1 - '@rollup/rollup-android-arm64': 4.18.1 - '@rollup/rollup-darwin-arm64': 4.18.1 - '@rollup/rollup-darwin-x64': 4.18.1 - '@rollup/rollup-linux-arm-gnueabihf': 4.18.1 - '@rollup/rollup-linux-arm-musleabihf': 4.18.1 - '@rollup/rollup-linux-arm64-gnu': 4.18.1 - '@rollup/rollup-linux-arm64-musl': 4.18.1 - '@rollup/rollup-linux-powerpc64le-gnu': 4.18.1 - '@rollup/rollup-linux-riscv64-gnu': 4.18.1 - '@rollup/rollup-linux-s390x-gnu': 4.18.1 - '@rollup/rollup-linux-x64-gnu': 4.18.1 - '@rollup/rollup-linux-x64-musl': 4.18.1 - '@rollup/rollup-win32-arm64-msvc': 4.18.1 - '@rollup/rollup-win32-ia32-msvc': 4.18.1 - '@rollup/rollup-win32-x64-msvc': 4.18.1 - fsevents: 2.3.3 - rollup@4.26.0: dependencies: '@types/estree': 1.0.6 @@ -29642,7 +29480,6 @@ snapshots: '@rollup/rollup-win32-ia32-msvc': 4.30.1 '@rollup/rollup-win32-x64-msvc': 4.30.1 fsevents: 2.3.3 - optional: true rrweb-cssom@0.6.0: {} @@ -30559,7 +30396,7 @@ snapshots: stylehacks@6.1.1(postcss@8.4.38): dependencies: - browserslist: 4.23.3 + browserslist: 4.24.4 postcss: 8.4.38 postcss-selector-parser: 6.1.2 @@ -30751,7 +30588,7 @@ snapshots: css-tree: 2.3.1 css-what: 6.1.0 csso: 5.0.5 - picocolors: 1.1.0 + picocolors: 1.1.1 swagger2openapi@7.0.8(encoding@0.1.13): dependencies: @@ -31098,7 +30935,7 @@ snapshots: optionalDependencies: '@swc/core': 1.5.29(@swc/helpers@0.5.12) - ts-node@10.9.1(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.5)(typescript@5.8.0-dev.20250109): + ts-node@10.9.1(@swc/core@1.5.29(@swc/helpers@0.5.12))(@types/node@22.5.5)(typescript@5.8.0-dev.20250115): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -31112,7 +30949,7 @@ snapshots: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.8.0-dev.20250109 + typescript: 5.8.0-dev.20250115 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: @@ -31260,7 +31097,7 @@ snapshots: typescript@5.6.3: {} - typescript@5.8.0-dev.20250109: {} + typescript@5.8.0-dev.20250115: {} ua-parser-js@1.0.38: {} @@ -31345,13 +31182,13 @@ snapshots: dependencies: browserslist: 4.23.2 escalade: 3.2.0 - picocolors: 1.1.0 + picocolors: 1.1.1 update-browserslist-db@1.1.0(browserslist@4.23.3): dependencies: browserslist: 4.23.3 escalade: 3.2.0 - picocolors: 1.1.0 + picocolors: 1.1.1 update-browserslist-db@1.1.2(browserslist@4.24.4): dependencies: @@ -31450,7 +31287,7 @@ snapshots: cac: 6.7.14 debug: 4.3.7(supports-color@8.1.1) pathe: 1.1.2 - picocolors: 1.1.0 + picocolors: 1.1.1 vite: 5.2.11(@types/node@22.5.1)(less@4.1.3)(sass@1.77.8)(stylus@0.64.0)(terser@5.36.0) transitivePeerDependencies: - '@types/node' @@ -31466,7 +31303,7 @@ snapshots: dependencies: esbuild: 0.20.2 postcss: 8.4.38 - rollup: 4.18.1 + rollup: 4.30.1 optionalDependencies: '@types/node': 22.5.1 fsevents: 2.3.3 @@ -31500,9 +31337,9 @@ snapshots: debug: 4.3.7(supports-color@8.1.1) execa: 8.0.1 local-pkg: 0.5.0 - magic-string: 0.30.10 + magic-string: 0.30.12 pathe: 1.1.2 - picocolors: 1.0.1 + picocolors: 1.1.1 std-env: 3.7.0 strip-literal: 2.1.0 tinybench: 2.9.0 @@ -31715,7 +31552,7 @@ snapshots: '@webassemblyjs/wasm-parser': 1.12.1 acorn: 8.12.1 acorn-import-assertions: 1.9.0(acorn@8.12.1) - browserslist: 4.23.3 + browserslist: 4.24.4 chrome-trace-event: 1.0.4 enhanced-resolve: 5.17.1 es-module-lexer: 1.5.4