feat(frontend): move fullSuffix management in a standalone file to simplify testing (#73)

- move `fullSuffix` into a standalone function
- setup `vitest` as test framework for frontend package
This commit is contained in:
Marco Pasqualetti
2026-02-11 11:21:50 +01:00
committed by GitHub
parent 668b0475f4
commit d6a08721c1
8 changed files with 574 additions and 116 deletions
+3
View File
@@ -46,6 +46,9 @@ jobs:
- name: Build frontend
run: pnpm --filter frontend run build
- name: Run frontend tests
run: pnpm --filter frontend run test
- name: Run backend tests
run: pnpm --filter github-readme-stats run test
+4 -2
View File
@@ -31,14 +31,16 @@
"clsx": "2.1.1",
"postcss": "^8.4.31",
"tailwindcss": "^3.3.5",
"vite": "7.3.1",
"vite": "catalog:default",
"vite-plugin-node-polyfills": "0.25.0",
"vite-plugin-string-replace": "^1.1.5"
"vite-plugin-string-replace": "1.1.5",
"vitest": "catalog:default"
},
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest",
"test:e2e": "playwright test",
"typecheck": "tsc --noEmit"
},
+22 -101
View File
@@ -26,6 +26,7 @@ import {
} from "../../redux/selectors/userSelectors";
import { login } from "../../redux/slices/user";
import { getFullSuffix } from "./getFullSuffix";
import { CustomizeStage } from "./stages/Customize";
import { DisplayStage } from "./stages/Display";
import { LoginStage } from "./stages/Login/Login";
@@ -105,107 +106,27 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element {
setStage(2);
};
let fullSuffix = `${selectedCard === CardType.STATS ? "" : "/" + selectedCard}?`;
switch (selectedCard) {
case CardType.STATS:
case CardType.TOP_LANGS:
fullSuffix += `username=${selectedUserId}`;
break;
case CardType.PIN:
fullSuffix += `username=${userId}&repo=${repo}`;
break;
case CardType.GIST:
fullSuffix += `id=${gist}`;
break;
case CardType.WAKATIME:
fullSuffix += `username=${wakatimeUser}`;
break;
default:
selectedCard satisfies never;
}
if (
selectedStatsRank !== STATS_DEFAULT_RANK &&
selectedCard === CardType.STATS
) {
fullSuffix += `&rank_icon=${selectedStatsRank.value}`;
}
if (
selectedLanguagesLayout !== LANGUAGES_DEFAULT_LAYOUT &&
selectedCard === CardType.TOP_LANGS
) {
fullSuffix += `&layout=${selectedLanguagesLayout.value}`;
}
if (
selectedWakatimeLayout !== WAKATIME_DEFAULT_LAYOUT &&
selectedCard === CardType.WAKATIME
) {
fullSuffix += `&layout=${selectedWakatimeLayout.value}`;
}
if (
!showTitle &&
(selectedCard === CardType.STATS ||
selectedCard === CardType.TOP_LANGS ||
selectedCard === CardType.WAKATIME)
) {
fullSuffix += "&hide_title=true";
}
if (
showOwner &&
(selectedCard === CardType.PIN || selectedCard === CardType.GIST)
) {
fullSuffix += "&show_owner=true";
}
if (descriptionLines && selectedCard === CardType.PIN) {
fullSuffix += `&description_lines_count=${descriptionLines}`;
}
if (
customTitle &&
(selectedCard === CardType.STATS || selectedCard === CardType.WAKATIME)
) {
const encodedTitle = encodeURIComponent(customTitle);
fullSuffix += `&custom_title=${encodedTitle}`;
}
if (
langsCount &&
(selectedCard === CardType.TOP_LANGS || selectedCard === CardType.WAKATIME)
) {
fullSuffix += `&langs_count=${langsCount}`;
}
if (showAllStats && selectedCard === CardType.STATS) {
fullSuffix += `&show=reviews,discussions_started,discussions_answered,prs_merged,prs_merged_percentage,prs_commented,prs_reviewed,issues_commented`;
}
if (showIcons && selectedCard === CardType.STATS) {
fullSuffix += `&show_icons=true`;
}
if (includeAllCommits && selectedCard === CardType.STATS) {
fullSuffix += `&include_all_commits=true`;
}
if (
!enableAnimations &&
(selectedCard === CardType.STATS ||
selectedCard === CardType.TOP_LANGS ||
selectedCard === CardType.WAKATIME)
) {
fullSuffix += `&disable_animations=${!enableAnimations}`;
}
if (usePercent && selectedCard === CardType.WAKATIME) {
fullSuffix += `&display_format=percent`;
}
const fullSuffix = getFullSuffix({
userId,
selectedCard,
selectedUserId,
repo,
gist,
wakatimeUser,
selectedStatsRank,
selectedLanguagesLayout,
selectedWakatimeLayout,
showTitle,
showOwner,
descriptionLines,
customTitle,
langsCount,
showAllStats,
showIcons,
includeAllCommits,
enableAnimations,
usePercent,
});
// for stage four
let themeSuffix = fullSuffix;
@@ -0,0 +1,117 @@
import { describe, expect, it } from "vitest";
import { DEFAULT_OPTION as LANGUAGES_DEFAULT_LAYOUT } from "../../components/Home/LanguagesLayoutSection";
import { DEFAULT_OPTION as STATS_DEFAULT_RANK } from "../../components/Home/StatsRankSection";
import { DEFAULT_OPTION as WAKATIME_DEFAULT_LAYOUT } from "../../components/Home/WakatimeLayoutSection";
import { CardType } from "../../models/CardType";
import { getFullSuffix } from "./getFullSuffix";
const baseOptions = {
selectedCard: CardType.STATS,
selectedUserId: "john",
userId: "john-github",
repo: "repo1",
gist: "gist1",
wakatimeUser: "wakaUser",
selectedStatsRank: STATS_DEFAULT_RANK,
selectedLanguagesLayout: LANGUAGES_DEFAULT_LAYOUT,
selectedWakatimeLayout: WAKATIME_DEFAULT_LAYOUT,
showTitle: true,
showOwner: false,
descriptionLines: undefined,
customTitle: "",
langsCount: undefined,
showAllStats: false,
showIcons: false,
includeAllCommits: false,
enableAnimations: true,
usePercent: false,
};
describe("getFullSuffix", () => {
it("builds stats suffix with defaults", () => {
const result = getFullSuffix(baseOptions);
expect(result).toBe("?username=john");
});
it("adds stats options", () => {
const result = getFullSuffix({
...baseOptions,
showIcons: true,
includeAllCommits: true,
showAllStats: true,
showTitle: false,
});
expect(result).toBe(
"?username=john" +
"&hide_title=true" +
"&show=reviews,discussions_started,discussions_answered,prs_merged,prs_merged_percentage,prs_commented,prs_reviewed,issues_commented" +
"&show_icons=true" +
"&include_all_commits=true",
);
});
it("builds top-langs suffix", () => {
const result = getFullSuffix({
...baseOptions,
selectedCard: CardType.TOP_LANGS,
langsCount: 5,
showTitle: false,
});
expect(result).toBe(
"/top-langs?username=john&hide_title=true&langs_count=5",
);
});
it("builds pin suffix using userId not selectedUserId", () => {
const result = getFullSuffix({
...baseOptions,
selectedCard: CardType.PIN,
showOwner: true,
descriptionLines: 3,
});
expect(result).toBe(
"/pin?username=john-github&repo=repo1&show_owner=true&description_lines_count=3",
);
});
it("builds gist suffix", () => {
const result = getFullSuffix({
...baseOptions,
selectedCard: CardType.GIST,
showOwner: true,
});
expect(result).toBe("/gist?id=gist1&show_owner=true");
});
it("builds wakatime suffix with percent and custom title", () => {
const result = getFullSuffix({
...baseOptions,
selectedCard: CardType.WAKATIME,
wakatimeUser: "waka",
usePercent: true,
customTitle: "My Stats",
showTitle: false,
});
expect(result).toBe(
"/wakatime?username=waka&hide_title=true&custom_title=My%20Stats&display_format=percent",
);
});
it("adds non-default layouts", () => {
const result = getFullSuffix({
...baseOptions,
selectedCard: CardType.TOP_LANGS,
selectedLanguagesLayout: { id: 2, value: "compact", label: "Compact" },
});
expect(result).toBe("/top-langs?username=john&layout=compact");
});
});
@@ -0,0 +1,167 @@
import type { SelectOption } from "../../components/Generic/Select";
import { DEFAULT_OPTION as LANGUAGES_DEFAULT_LAYOUT } from "../../components/Home/LanguagesLayoutSection";
import { DEFAULT_OPTION as STATS_DEFAULT_RANK } from "../../components/Home/StatsRankSection";
import { DEFAULT_OPTION as WAKATIME_DEFAULT_LAYOUT } from "../../components/Home/WakatimeLayoutSection";
import { CardType } from "../../models/CardType";
interface Options {
userId: string;
selectedUserId: string;
selectedCard: CardType;
repo: string;
gist: string;
wakatimeUser: string;
selectedStatsRank: SelectOption;
selectedLanguagesLayout: SelectOption;
selectedWakatimeLayout: SelectOption;
showTitle: boolean;
showOwner: boolean;
descriptionLines: number | undefined;
customTitle: string;
langsCount: number | undefined;
showAllStats: boolean;
showIcons: boolean;
includeAllCommits: boolean;
enableAnimations: boolean;
usePercent: boolean;
}
export function getFullSuffix({
userId,
selectedCard,
selectedUserId,
repo,
gist,
wakatimeUser,
selectedStatsRank,
selectedLanguagesLayout,
selectedWakatimeLayout,
showTitle,
showOwner,
descriptionLines,
customTitle,
langsCount,
showAllStats,
showIcons,
includeAllCommits,
enableAnimations,
usePercent,
}: Options): string {
let fullSuffix = `${selectedCard === CardType.STATS ? "" : "/" + selectedCard}?`;
switch (selectedCard) {
case CardType.STATS:
case CardType.TOP_LANGS:
fullSuffix += `username=${selectedUserId}`;
break;
case CardType.PIN:
/**
* We should use the name of the logged-in user, not the value entered in the
* username field in step 3.
*
* This input is not shown when the PIN card type is selected,
* but it may still contain a different value if the user previously chose another card type.
*
* For example,
* 1. the user could select the STATS card
* 2. enter a username
* 3. then go back and switch to the PIN card, leaving the old value behind.
*
* @see https://github.com/stats-organization/github-stats-extended/pull/73#discussion_r2792177515
*/
fullSuffix += `username=${userId}&repo=${repo}`;
break;
case CardType.GIST:
fullSuffix += `id=${gist}`;
break;
case CardType.WAKATIME:
fullSuffix += `username=${wakatimeUser}`;
break;
default:
selectedCard satisfies never;
}
if (
selectedStatsRank !== STATS_DEFAULT_RANK &&
selectedCard === CardType.STATS
) {
fullSuffix += `&rank_icon=${selectedStatsRank.value}`;
}
if (
selectedLanguagesLayout !== LANGUAGES_DEFAULT_LAYOUT &&
selectedCard === CardType.TOP_LANGS
) {
fullSuffix += `&layout=${selectedLanguagesLayout.value}`;
}
if (
selectedWakatimeLayout !== WAKATIME_DEFAULT_LAYOUT &&
selectedCard === CardType.WAKATIME
) {
fullSuffix += `&layout=${selectedWakatimeLayout.value}`;
}
if (
!showTitle &&
(selectedCard === CardType.STATS ||
selectedCard === CardType.TOP_LANGS ||
selectedCard === CardType.WAKATIME)
) {
fullSuffix += "&hide_title=true";
}
if (
showOwner &&
(selectedCard === CardType.PIN || selectedCard === CardType.GIST)
) {
fullSuffix += "&show_owner=true";
}
if (descriptionLines && selectedCard === CardType.PIN) {
fullSuffix += `&description_lines_count=${descriptionLines}`;
}
if (
customTitle &&
(selectedCard === CardType.STATS || selectedCard === CardType.WAKATIME)
) {
const encodedTitle = encodeURIComponent(customTitle);
fullSuffix += `&custom_title=${encodedTitle}`;
}
if (
langsCount &&
(selectedCard === CardType.TOP_LANGS || selectedCard === CardType.WAKATIME)
) {
fullSuffix += `&langs_count=${langsCount}`;
}
if (showAllStats && selectedCard === CardType.STATS) {
fullSuffix += `&show=reviews,discussions_started,discussions_answered,prs_merged,prs_merged_percentage,prs_commented,prs_reviewed,issues_commented`;
}
if (showIcons && selectedCard === CardType.STATS) {
fullSuffix += `&show_icons=true`;
}
if (includeAllCommits && selectedCard === CardType.STATS) {
fullSuffix += `&include_all_commits=true`;
}
if (
!enableAnimations &&
(selectedCard === CardType.STATS ||
selectedCard === CardType.TOP_LANGS ||
selectedCard === CardType.WAKATIME)
) {
fullSuffix += `&disable_animations=${!enableAnimations}`;
}
if (usePercent && selectedCard === CardType.WAKATIME) {
fullSuffix += `&display_format=percent`;
}
return fullSuffix;
}
+5 -1
View File
@@ -1,9 +1,9 @@
import path from "node:path";
import react from "@vitejs/plugin-react-swc";
import { defineConfig } from "vite";
import { nodePolyfills } from "vite-plugin-node-polyfills";
import StringReplace from "vite-plugin-string-replace";
import { defineConfig } from "vitest/config";
// https://vitejs.dev/config/
export default defineConfig({
@@ -75,4 +75,8 @@ export default defineConfig({
},
],
},
test: {
dir: "./src",
exclude: ["**/backend/**"],
},
});
+253 -11
View File
@@ -4,6 +4,15 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
catalogs:
default:
vite:
specifier: 7.3.1
version: 7.3.1
vitest:
specifier: 4.0.18
version: 4.0.18
importers:
.:
@@ -206,14 +215,17 @@ importers:
specifier: ^3.3.5
version: 3.4.19(yaml@2.8.2)
vite:
specifier: 7.3.1
specifier: catalog:default
version: 7.3.1(@types/node@25.0.3)(jiti@1.21.7)(terser@5.44.1)(yaml@2.8.2)
vite-plugin-node-polyfills:
specifier: 0.25.0
version: 0.25.0(rollup@4.55.1)(vite@7.3.1(@types/node@25.0.3)(jiti@1.21.7)(terser@5.44.1)(yaml@2.8.2))
vite-plugin-string-replace:
specifier: ^1.1.5
specifier: 1.1.5
version: 1.1.5
vitest:
specifier: catalog:default
version: 4.0.18(@types/node@25.0.3)(jiti@1.21.7)(jsdom@26.1.0)(terser@5.44.1)(yaml@2.8.2)
packages:
@@ -1313,6 +1325,12 @@ packages:
'@types/babel__traverse@7.28.0':
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@@ -1543,6 +1561,35 @@ packages:
peerDependencies:
vite: ^4 || ^5 || ^6 || ^7
'@vitest/expect@4.0.18':
resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==}
'@vitest/mocker@4.0.18':
resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==}
peerDependencies:
msw: ^2.4.9
vite: ^6.0.0 || ^7.0.0-0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
'@vitest/pretty-format@4.0.18':
resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==}
'@vitest/runner@4.0.18':
resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==}
'@vitest/snapshot@4.0.18':
resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==}
'@vitest/spy@4.0.18':
resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==}
'@vitest/utils@4.0.18':
resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==}
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
engines: {node: '>= 0.6'}
@@ -1660,6 +1707,10 @@ packages:
assert@2.1.0:
resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==}
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
async-function@1.0.0:
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
engines: {node: '>= 0.4'}
@@ -1842,6 +1893,10 @@ packages:
caniuse-lite@1.0.30001767:
resolution: {integrity: sha512-34+zUAMhSH+r+9eKmYG+k2Rpt8XttfE4yXAjoZvkAPs15xcYQhyBYdalJ65BzivAvGRMViEjy6oKr/S91loekQ==}
chai@6.2.2:
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
engines: {node: '>=18'}
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@@ -2164,6 +2219,9 @@ packages:
resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==}
engines: {node: '>= 0.4'}
es-module-lexer@1.7.0:
resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
@@ -2311,6 +2369,9 @@ packages:
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@@ -2337,6 +2398,10 @@ packages:
resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==}
engines: {node: '>= 0.8.0'}
expect-type@1.3.0:
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
engines: {node: '>=12.0.0'}
expect@30.2.0:
resolution: {integrity: sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
@@ -3317,6 +3382,9 @@ packages:
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
engines: {node: '>= 0.4'}
obug@2.1.1:
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
@@ -3433,6 +3501,9 @@ packages:
resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==}
engines: {node: '>=18'}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
pbkdf2@3.1.5:
resolution: {integrity: sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==}
engines: {node: '>= 0.10'}
@@ -3898,6 +3969,9 @@ packages:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'}
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
@@ -3959,10 +4033,16 @@ packages:
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
engines: {node: '>=10'}
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
std-env@3.10.0:
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
stop-iteration-iterator@1.1.0:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
@@ -4099,10 +4179,21 @@ packages:
resolution: {integrity: sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==}
engines: {node: '>=0.6.0'}
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
tinyexec@1.0.2:
resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
engines: {node: '>=18'}
tinyglobby@0.2.15:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
tinyrainbow@3.0.3:
resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==}
engines: {node: '>=14.0.0'}
tldts-core@6.1.86:
resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
@@ -4329,6 +4420,40 @@ packages:
yaml:
optional: true
vitest@4.0.18:
resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==}
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
'@vitest/browser-playwright': 4.0.18
'@vitest/browser-preview': 4.0.18
'@vitest/browser-webdriverio': 4.0.18
'@vitest/ui': 4.0.18
happy-dom: '*'
jsdom: '*'
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
'@opentelemetry/api':
optional: true
'@types/node':
optional: true
'@vitest/browser-playwright':
optional: true
'@vitest/browser-preview':
optional: true
'@vitest/browser-webdriverio':
optional: true
'@vitest/ui':
optional: true
happy-dom:
optional: true
jsdom:
optional: true
vm-browserify@1.1.2:
resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==}
@@ -4381,6 +4506,11 @@ packages:
engines: {node: '>= 8'}
hasBin: true
why-is-node-running@2.3.0:
resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
engines: {node: '>=8'}
hasBin: true
word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
@@ -4465,9 +4595,6 @@ packages:
peerDependencies:
zod: ^3.25.0 || ^4.0.0
zod@4.3.5:
resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==}
zod@4.3.6:
resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
@@ -5535,6 +5662,13 @@ snapshots:
dependencies:
'@babel/types': 7.28.5
'@types/chai@5.2.3':
dependencies:
'@types/deep-eql': 4.0.2
assertion-error: 2.0.1
'@types/deep-eql@4.0.2': {}
'@types/estree@1.0.8': {}
'@types/istanbul-lib-coverage@2.0.6': {}
@@ -5754,6 +5888,45 @@ snapshots:
transitivePeerDependencies:
- '@swc/helpers'
'@vitest/expect@4.0.18':
dependencies:
'@standard-schema/spec': 1.1.0
'@types/chai': 5.2.3
'@vitest/spy': 4.0.18
'@vitest/utils': 4.0.18
chai: 6.2.2
tinyrainbow: 3.0.3
'@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.0.3)(jiti@1.21.7)(terser@5.44.1)(yaml@2.8.2))':
dependencies:
'@vitest/spy': 4.0.18
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 7.3.1(@types/node@25.0.3)(jiti@1.21.7)(terser@5.44.1)(yaml@2.8.2)
'@vitest/pretty-format@4.0.18':
dependencies:
tinyrainbow: 3.0.3
'@vitest/runner@4.0.18':
dependencies:
'@vitest/utils': 4.0.18
pathe: 2.0.3
'@vitest/snapshot@4.0.18':
dependencies:
'@vitest/pretty-format': 4.0.18
magic-string: 0.30.21
pathe: 2.0.3
'@vitest/spy@4.0.18': {}
'@vitest/utils@4.0.18':
dependencies:
'@vitest/pretty-format': 4.0.18
tinyrainbow: 3.0.3
accepts@2.0.0:
dependencies:
mime-types: 3.0.2
@@ -5896,6 +6069,8 @@ snapshots:
object.assign: 4.1.7
util: 0.12.5
assertion-error@2.0.1: {}
async-function@1.0.0: {}
asynckit@0.4.0: {}
@@ -6140,6 +6315,8 @@ snapshots:
caniuse-lite@1.0.30001767: {}
chai@6.2.2: {}
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
@@ -6511,6 +6688,8 @@ snapshots:
iterator.prototype: 1.1.5
safe-array-concat: 1.1.3
es-module-lexer@1.7.0: {}
es-object-atoms@1.1.1:
dependencies:
es-errors: 1.3.0
@@ -6636,8 +6815,8 @@ snapshots:
'@babel/parser': 7.28.5
eslint: 9.39.2(jiti@2.6.1)
hermes-parser: 0.25.1
zod: 4.3.5
zod-validation-error: 4.0.2(zod@4.3.5)
zod: 4.3.6
zod-validation-error: 4.0.2(zod@4.3.6)
transitivePeerDependencies:
- supports-color
@@ -6741,6 +6920,10 @@ snapshots:
estree-walker@2.0.2: {}
estree-walker@3.0.3:
dependencies:
'@types/estree': 1.0.8
esutils@2.0.3: {}
etag@1.8.1: {}
@@ -6768,6 +6951,8 @@ snapshots:
exit-x@0.2.2: {}
expect-type@1.3.0: {}
expect@30.2.0:
dependencies:
'@jest/expect-utils': 30.2.0
@@ -8004,6 +8189,8 @@ snapshots:
define-properties: 1.2.1
es-object-atoms: 1.1.1
obug@2.1.1: {}
on-finished@2.4.1:
dependencies:
ee-first: 1.1.1
@@ -8137,6 +8324,8 @@ snapshots:
path-type@6.0.0: {}
pathe@2.0.3: {}
pbkdf2@3.1.5:
dependencies:
create-hash: 1.2.0
@@ -8651,6 +8840,8 @@ snapshots:
side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2
siginfo@2.0.0: {}
signal-exit@3.0.7: {}
signal-exit@4.1.0: {}
@@ -8700,8 +8891,12 @@ snapshots:
dependencies:
escape-string-regexp: 2.0.0
stackback@0.0.2: {}
statuses@2.0.2: {}
std-env@3.10.0: {}
stop-iteration-iterator@1.1.0:
dependencies:
es-errors: 1.3.0
@@ -8901,11 +9096,17 @@ snapshots:
dependencies:
setimmediate: 1.0.5
tinybench@2.9.0: {}
tinyexec@1.0.2: {}
tinyglobby@0.2.15:
dependencies:
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
tinyrainbow@3.0.3: {}
tldts-core@6.1.86: {}
tldts@6.1.86:
@@ -9148,6 +9349,44 @@ snapshots:
terser: 5.44.1
yaml: 2.8.2
vitest@4.0.18(@types/node@25.0.3)(jiti@1.21.7)(jsdom@26.1.0)(terser@5.44.1)(yaml@2.8.2):
dependencies:
'@vitest/expect': 4.0.18
'@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.0.3)(jiti@1.21.7)(terser@5.44.1)(yaml@2.8.2))
'@vitest/pretty-format': 4.0.18
'@vitest/runner': 4.0.18
'@vitest/snapshot': 4.0.18
'@vitest/spy': 4.0.18
'@vitest/utils': 4.0.18
es-module-lexer: 1.7.0
expect-type: 1.3.0
magic-string: 0.30.21
obug: 2.1.1
pathe: 2.0.3
picomatch: 4.0.3
std-env: 3.10.0
tinybench: 2.9.0
tinyexec: 1.0.2
tinyglobby: 0.2.15
tinyrainbow: 3.0.3
vite: 7.3.1(@types/node@25.0.3)(jiti@1.21.7)(terser@5.44.1)(yaml@2.8.2)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/node': 25.0.3
jsdom: 26.1.0
transitivePeerDependencies:
- jiti
- less
- lightningcss
- msw
- sass
- sass-embedded
- stylus
- sugarss
- terser
- tsx
- yaml
vm-browserify@1.1.2: {}
w3c-xmlserializer@5.0.0:
@@ -9218,6 +9457,11 @@ snapshots:
dependencies:
isexe: 2.0.0
why-is-node-running@2.3.0:
dependencies:
siginfo: 2.0.0
stackback: 0.0.2
word-wrap@1.2.5: {}
wrap-ansi@7.0.0:
@@ -9279,10 +9523,8 @@ snapshots:
yocto-queue@0.1.0: {}
zod-validation-error@4.0.2(zod@4.3.5):
zod-validation-error@4.0.2(zod@4.3.6):
dependencies:
zod: 4.3.5
zod@4.3.5: {}
zod: 4.3.6
zod@4.3.6: {}
+3 -1
View File
@@ -1,7 +1,9 @@
packages:
- apps/*
catalog: null
catalog:
vite: 7.3.1
vitest: 4.0.18
minimumReleaseAge: 10080