diff --git a/.changeset/clever-lizards-draw.md b/.changeset/clever-lizards-draw.md new file mode 100644 index 00000000..83e81712 --- /dev/null +++ b/.changeset/clever-lizards-draw.md @@ -0,0 +1,6 @@ +--- +"melt": patch +--- + +1. change avatar callbacks trigger order to fire before dom updates +2. add test suite for avatar diff --git a/docs/src/api.json b/docs/src/api.json index 9d0c10d5..987278aa 100644 --- a/docs/src/api.json +++ b/docs/src/api.json @@ -1717,7 +1717,7 @@ "properties": [ { "name": "src", - "type": "string", + "type": "string | undefined", "description": "" }, { @@ -1725,6 +1725,11 @@ "type": "number", "description": "" }, + { + "name": "id", + "type": "string", + "description": "" + }, { "name": "loadingStatus", "type": "ImageLoadingStatus", @@ -1732,7 +1737,7 @@ }, { "name": "image", - "type": "{\n readonly \"data-melt-avatar-image\": \"\"\n readonly src: string\n readonly style: `display: ${string}`\n readonly onload: () => (() => void) | undefined\n readonly onerror: () => void\n}", + "type": "{\n readonly \"data-melt-avatar-image\": \"\"\n readonly src: string | undefined\n readonly style: `display: ${string}`\n readonly onload: () => (() => void) | undefined\n readonly onerror: () => void\n}", "description": "" }, { diff --git a/packages/melt/package.json b/packages/melt/package.json index 3a399d19..3a81e796 100644 --- a/packages/melt/package.json +++ b/packages/melt/package.json @@ -42,6 +42,7 @@ "test:headless": "vitest --browser.headless", "sync": "svelte-kit sync", "build": "npm run package", + "prepare": "svelte-kit sync || echo ''", "preview": "vite preview", "package": "svelte-kit sync && svelte-package && publint", "prepublishOnly": "npm run package", @@ -57,7 +58,6 @@ "@sveltejs/kit": "^2.15.2", "@sveltejs/package": "^2.3.9", "@sveltejs/vite-plugin-svelte": "^5.0.3", - "jsdom": "^24.1.0", "publint": "^0.1.9", "svelte": "5.34.8", "svelte-check": "^4.1.3", @@ -70,8 +70,8 @@ "@floating-ui/dom": "^1.6.13", "dequal": "^2.0.3", "focus-trap": "^7.6.5", - "jest-axe": "^9.0.0", - "runed": "^0.23.3" + "runed": "^0.23.3", + "vitest-axe": "1.0.0-pre.3" }, "publishConfig": { "access": "public" diff --git a/packages/melt/src/lib/builders/Avatar.svelte.ts b/packages/melt/src/lib/builders/Avatar.svelte.ts index 7a63ab6f..cda2b29f 100644 --- a/packages/melt/src/lib/builders/Avatar.svelte.ts +++ b/packages/melt/src/lib/builders/Avatar.svelte.ts @@ -1,11 +1,11 @@ import { extract } from "$lib/utils/extract"; -import { createDataIds } from "$lib/utils/identifiers"; +import { createBuilderMetadata, } from "$lib/utils/identifiers"; import { styleAttr } from "$lib/utils/attribute"; import { inBrowser } from "$lib/utils/browser"; import type { MaybeGetter } from "$lib/types"; import { watch } from "runed"; -const identifiers = createDataIds("avatar", ["image", "fallback"]); +const { dataAttrs, createIds } = createBuilderMetadata("avatar", ["image", "fallback"]); export type ImageLoadingStatus = "loading" | "loaded" | "error"; @@ -29,27 +29,43 @@ export type AvatarProps = { }; export class Avatar { + #ids = createIds(); /* Props */ #props!: AvatarProps; - readonly src = $derived(extract(this.#props.src, "")); + readonly src = $derived(extract(this.#props.src, undefined)); readonly delayMs = $derived(extract(this.#props.delayMs, 0)); /* State */ #loadingStatus: ImageLoadingStatus = $state("loading"); constructor(props: AvatarProps = {}) { - $effect(() => { - this.#props.onLoadingStatusChange?.(this.#loadingStatus); - }); + // Should be defined at the top before the effects + // when using $effect.pre or $watch.pre + this.#props = props; - watch( + // Run effects before dom updates + // for provide handlers with some execution time + watch.pre( + () => this.#loadingStatus, + () => { + this.#props.onLoadingStatusChange?.(this.#loadingStatus); + } + ); + + watch.pre( () => this.src, () => { - this.#loadingStatus = "loading"; + this.#loadingStatus = "loading"; }, + { + lazy: true + } ); - this.#props = props; + } + + get id() { + return this.#ids.image; } get loadingStatus() { @@ -58,7 +74,7 @@ export class Avatar { get image() { return { - [identifiers.image]: "", + [dataAttrs.image]: "", src: this.src, style: styleAttr({ display: this.#loadingStatus === "loaded" ? "block" : "none" }), onload: () => { @@ -76,7 +92,7 @@ export class Avatar { get fallback() { return { - [identifiers.fallback]: "", + [dataAttrs.fallback]: "", style: this.#loadingStatus === "loaded" ? styleAttr({ display: "none" }) : undefined, hidden: this.#loadingStatus === "loaded" ? true : undefined, } as const; diff --git a/packages/melt/src/lib/builders/tests/Avatar.spec.svelte.ts b/packages/melt/src/lib/builders/tests/Avatar.spec.svelte.ts new file mode 100644 index 00000000..3bf2634a --- /dev/null +++ b/packages/melt/src/lib/builders/tests/Avatar.spec.svelte.ts @@ -0,0 +1,364 @@ +import { describe, expect, vi, beforeEach, afterEach, } from "vitest"; +import { testWithEffect } from "$lib/utils/test.svelte"; +import { expectTypeOf } from "vitest"; +import { Avatar, type ImageLoadingStatus } from "../Avatar.svelte"; +import { render } from "vitest-browser-svelte"; +import AvatarTest from "./AvatarTest.svelte"; +import { tick } from "svelte"; + +describe("Avatar", () => { + testWithEffect("should have valid types", () => { + const avatar = new Avatar(); + expectTypeOf(avatar).toEqualTypeOf(); + expectTypeOf(avatar.loadingStatus).toEqualTypeOf(); + expectTypeOf(avatar.src).toEqualTypeOf(); + expectTypeOf(avatar.delayMs).toEqualTypeOf(); + }); + + describe("Constructor and Initial State", () => { + testWithEffect("should create avatar with default props", () => { + const avatar = new Avatar(); + + expect(avatar).toHaveProperty("src", undefined); + expect(avatar).toHaveProperty("delayMs", 0); + expect(avatar).toHaveProperty("loadingStatus", "loading"); + }); + + testWithEffect("should create avatar with custom props", () => { + const onLoadingStatusChange = vi.fn(); + const avatar = new Avatar({ + src: "favicon.png", + delayMs: 500, + onLoadingStatusChange, + }); + + expect(avatar).toHaveProperty("src", "favicon.png"); + expect(avatar).toHaveProperty("delayMs", 500); + expect(avatar).toHaveProperty("loadingStatus", "loading"); + }); + + testWithEffect("should handle getter functions for props", () => { + const srcGetter = () => "favicon.png"; + const delayGetter = () => 1000; + + const avatar = new Avatar({ + src: srcGetter, + delayMs: delayGetter, + }); + + expect(avatar).toHaveProperty("src", "favicon.png"); + expect(avatar).toHaveProperty("delayMs", 1000); + }); + }); + + describe("State Management", () => { + testWithEffect("should reset to loading when src changes", async () => { + vi.useFakeTimers(); + let src = $state("favicon.png"); + const avatar2 = new Avatar({ src: () => src }); + expect(avatar2.loadingStatus).toBe("loading"); + + avatar2.image.onload?.(); + vi.advanceTimersByTime(avatar2.delayMs + 1); + expect(avatar2.loadingStatus).toBe("loaded"); + + // Simulate changing src through reactive update + src = "banner.png"; + await tick(); + expect(avatar2.src).toBe("banner.png"); + expect(avatar2.loadingStatus).toBe("loading"); + vi.useRealTimers(); + }); + + testWithEffect("should handle image error", () => { + const avatar = new Avatar({ src: "invalid.jpg" }); + + avatar.image.onerror?.(); + + expect(avatar.loadingStatus).toBe("error"); + }); + }); + + describe("Attributes", () => { + testWithEffect("should return correct attributes in loading state", () => { + const avatar = new Avatar({ src: "favicon.png" }); + const imageAttrs = avatar.image; + const fallbackAttrs = avatar.fallback; + + expect(imageAttrs).toHaveProperty("data-melt-avatar-image", ""); + expect(imageAttrs).toHaveProperty("src", "favicon.png"); + expect(imageAttrs.style).toContain("display: none"); + expect(imageAttrs).toHaveProperty("onload"); + expect(imageAttrs).toHaveProperty("onerror"); + + expect(fallbackAttrs).toHaveProperty("data-melt-avatar-fallback", ""); + expect(fallbackAttrs.style).toBeUndefined(); + expect(fallbackAttrs.hidden).toBeUndefined(); + }); + + testWithEffect("should return correct attributes in loaded state", () => { + vi.useFakeTimers() + const avatar = new Avatar({ src: "favicon.png" }); + + // Simulate loaded state + avatar.image.onload?.(); + vi.advanceTimersByTime(avatar.delayMs + 1); + + // Note: In actual implementation, this would be async due to setTimeout + // For testing purposes, we check the structure + const fallbackAttrs = avatar.fallback; + expect(avatar.image.style).toBeDefined(); + expect(fallbackAttrs.style).toContain("display: none"); + expect(fallbackAttrs.hidden).toBe(true); + + vi.useRealTimers(); + }); + + testWithEffect("should return correct attributes in error state", async () => { + const avatar = new Avatar(); + avatar.image.onerror?.(); + await tick(); + + const fallbackAttrs = avatar.fallback; + const imageAttrs = avatar.image; + + expect(imageAttrs.style).toContain("display: none"); + expect(fallbackAttrs.style).toBeUndefined(); + expect(fallbackAttrs.hidden).toBeUndefined(); + }); + }); + + describe("Callbacks", () => { + testWithEffect("should call onLoadingStatusChange when status changes", () => { + const onLoadingStatusChange = vi.fn(); + const _ = new Avatar({ onLoadingStatusChange }); + + // Initial call should happen + expect(onLoadingStatusChange).toHaveBeenCalledWith("loading"); + }); + + testWithEffect("should call onLoadingStatusChange on error", async () => { + const onLoadingStatusChange = vi.fn(); + const avatar = new Avatar({ onLoadingStatusChange }); + + onLoadingStatusChange.mockClear(); + avatar.image.onerror?.(); + await tick(); + + expect(onLoadingStatusChange).toHaveBeenCalledWith("error"); + }); + + testWithEffect("should call onLoadingStatusChange on load", async () => { + vi.useFakeTimers(); + const onLoadingStatusChange = vi.fn(); + const avatar = new Avatar({ onLoadingStatusChange }); + + onLoadingStatusChange.mockClear(); + avatar.image.onload?.(); + vi.advanceTimersByTime(avatar.delayMs + 1); + await tick(); + + expect(onLoadingStatusChange).toHaveBeenCalledWith("loaded"); + vi.useRealTimers(); + }); + }); + + describe("Delay Functionality", () => { + testWithEffect("should respect delayMs for image loading", async () => { + vi.useFakeTimers(); + + const avatar = new Avatar({ + src: "favicon.png", + delayMs: 1000 + }); + + // Simulate image load + const cleanup = avatar.image.onload?.(); + + // Should not be loaded yet + expect(avatar.loadingStatus).toBe("loading"); + + // Fast forward time + vi.advanceTimersByTime(1000); + await tick(); + + expect(avatar.loadingStatus).toBe("loaded"); + + + // Now should be loaded (in real implementation) + // Note: This test structure depends on actual implementation details + // Cleanup + if (typeof cleanup === 'function') { + cleanup(); + } + + vi.useRealTimers(); + }); + }); +}); + +// Browser-specific user interaction tests +describe("Avatar User Interactions (Browser)", () => { + const alt = "Blue box"; + + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + testWithEffect("should clean up timers properly", () => { + const { container, unmount } = render(AvatarTest, { + src: "favicon.png", + delayMs: 1000 + }); + + const image = container.querySelector("[data-melt-avatar-image]") as HTMLImageElement; + + // Simulate image load to start timer + const loadEvent = new Event("load"); + image.dispatchEvent(loadEvent); + + // Unmount component before timer completes + unmount(); + + // Wait longer than delay + vi.advanceTimersByTime(1000); + + // If cleanup function ran, we would not reach here + // as manipulating the dom wiil throw an error for unmounted component + expect(true).toBe(true); // Test passes if no errors thrown + + }); + + testWithEffect("should call onLoadingStatusChange callback", async () => { + const onLoadingStatusChange = vi.fn(); + + render(AvatarTest, { + src: "favicon.png", + onLoadingStatusChange + }); + + // Should be called initially with "loading" + expect(onLoadingStatusChange).toHaveBeenNthCalledWith(1, "loading"); + + + await vi.waitFor(() => { + expect(onLoadingStatusChange).toHaveBeenNthCalledWith(2, "loaded"); + }) + }); + + testWithEffect("should display image when src loads successfully", async () => { + const onLoadingStatusChange = vi.fn(); + const { getByAltText, getByText } = render(AvatarTest, { + alt, + onLoadingStatusChange, + src: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iIzAwZiIvPjwvc3ZnPg==", + }); + + const image = getByAltText(alt); + const fallback = getByText("fallback"); + + await vi.waitFor(() => { + expect(onLoadingStatusChange).toHaveBeenNthCalledWith(2, 'loaded'); + }) + + expect(image).toBeVisible() + expect(fallback).not.toBeVisible(); + }); + + testWithEffect("flaky: should display fallback when image fails to load:", async () => { + const { getByAltText, getByText } = render(AvatarTest, { + alt, + src: "invalid.jpg" + }); + + const image = getByAltText(alt); + const fallback = getByText("fallback"); + const errorText = getByText("Loading error"); + + expect(image).toBeInTheDocument(); + expect(fallback).toBeInTheDocument(); + expect(errorText).not.toBeVisible(); + + + await vi.waitFor(() => { + // Fallback should still be visible + expect(errorText).toBeVisible(); + expect(image).not.toBeVisible(); + expect(fallback).toBeVisible(); + }, { + // Flaky chromium CI + timeout: 4000, + interval: 500 + }); + + }, { + timeout: 5000, + }); + + testWithEffect("should handle delay before showing image", async () => { + const { getByAltText, getByText } = render(AvatarTest, { + alt, + delayMs: 200, + src: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iIzAwZiIvPjwvc3ZnPg==", + }); + + const image = getByAltText(alt); + const fallback = getByText("fallback"); + + expect(image).not.toBeVisible(); + expect(fallback).toBeVisible(); + + vi.advanceTimersByTime(200); + + await vi.waitFor(() => { + expect(image).toBeVisible(); + expect(fallback).not.toBeVisible(); + }) + }); + + testWithEffect("should handle src changes", async () => { + const props = { + alt, + src: "favicon.png", + }; + const { getByAltText, rerender } = render(AvatarTest, props); + + const image = getByAltText(alt); + expect(image).toHaveAttribute("src", "favicon.png"); + + props.src = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iIzAwZiIvPjwvc3ZnPg=="; + await rerender(props); + + expect(image).toHaveAttribute("src", props.src); + }); + + testWithEffect("should handle missing src gracefully", async () => { + const { container } = render(AvatarTest, { + src: undefined + }); + + const image = container.querySelector("[data-melt-avatar-image]") as HTMLImageElement; + const fallback = container.querySelector("[data-melt-avatar-fallback]"); + + expect(image.src).toBe(""); + expect(fallback).toBeVisible(); + }); + + testWithEffect("should maintain proper accessibility attributes", async () => { + const { container } = render(AvatarTest, { + src: "favicon.png", + alt: "User avatar" + }); + + const image = container.querySelector("[data-melt-avatar-image]") as HTMLImageElement; + + // Should have proper alt text for accessibility + if (image.hasAttribute("alt")) { + expect(image).toHaveAttribute("alt", "User avatar"); + } + }); +}); \ No newline at end of file diff --git a/packages/melt/src/lib/builders/tests/AvatarTest.svelte b/packages/melt/src/lib/builders/tests/AvatarTest.svelte new file mode 100644 index 00000000..73f033a7 --- /dev/null +++ b/packages/melt/src/lib/builders/tests/AvatarTest.svelte @@ -0,0 +1,43 @@ + + + + {#snippet children(avatar)} + {alt} +
fallback
+
+ Loading error +
+ {/snippet} +
\ No newline at end of file diff --git a/packages/melt/src/lib/components/Avatar.svelte b/packages/melt/src/lib/components/Avatar.svelte index 7d539850..dd74b66b 100644 --- a/packages/melt/src/lib/components/Avatar.svelte +++ b/packages/melt/src/lib/components/Avatar.svelte @@ -15,7 +15,10 @@ let { children, onLoadingStatusChange, ...rest }: Props = $props(); - export const avatar = new Builder(getters({ ...rest })); + export const avatar = new Builder({ + onLoadingStatusChange, + ...getters({ ...rest }), + }); -{@render children(avatar)} +{@render children(avatar)} \ No newline at end of file diff --git a/packages/melt/src/lib/utils/test.svelte.ts b/packages/melt/src/lib/utils/test.svelte.ts index a34db2ff..126ca0ed 100644 --- a/packages/melt/src/lib/utils/test.svelte.ts +++ b/packages/melt/src/lib/utils/test.svelte.ts @@ -1,9 +1,11 @@ import { flushSync } from "svelte"; -import { test, vi } from "vitest"; +import { test, vi, type TestOptions } from "vitest"; +import { kbd } from "./keyboard"; export function testWithEffect( name: string, fn: () => void | Promise, + options?: number | Omit ): ReturnType { return test(name, async () => { let promise: void | Promise; @@ -16,7 +18,7 @@ export function testWithEffect( } finally { cleanup(); } - }); + }, options); } export function vitestSetTimeoutWrapper(fn: () => void, timeout: number) { @@ -32,3 +34,22 @@ export function focus(node: HTMLElement | null | undefined) { flushSync(() => node.focus()); } } + +type KbdKeys = keyof typeof kbd; +/** + * A wrapper around the internal kbd object to make it easier to use + * in tests which require the key names to be wrapped in curly braces. + */ +export const testKbd: Record = Object.entries(kbd).reduce((acc, [key, value]) => { + acc[key as KbdKeys] = `{${value}}`; + return acc; +}, {} as Record); + +export function exists(get: (id: string) => HTMLElement, testId: string) { + try { + get(testId); + return true; + } catch { + return false; + } +} diff --git a/packages/melt/svelte.config.js b/packages/melt/svelte.config.js index 8f452a50..ed5381fb 100644 --- a/packages/melt/svelte.config.js +++ b/packages/melt/svelte.config.js @@ -5,7 +5,9 @@ const config = { // Consult https://kit.svelte.dev/docs/integrations#preprocessors // for more information about preprocessors preprocess: vitePreprocess(), - + compilerOptions: { + runes: true, + }, kit: {}, }; diff --git a/packages/melt/vitest-setup-client.ts b/packages/melt/vitest-setup-client.ts index 570b9f0e..9a2e5429 100644 --- a/packages/melt/vitest-setup-client.ts +++ b/packages/melt/vitest-setup-client.ts @@ -1,2 +1,3 @@ /// /// +/// diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16d28373..646d3abd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -178,12 +178,12 @@ importers: focus-trap: specifier: ^7.6.5 version: 7.6.5 - jest-axe: - specifier: ^9.0.0 - version: 9.0.0 runed: specifier: ^0.23.3 version: 0.23.3(svelte@5.34.8) + vitest-axe: + specifier: 1.0.0-pre.3 + version: 1.0.0-pre.3(vitest@3.2.3) devDependencies: '@sveltejs/kit': specifier: ^2.15.2 @@ -194,9 +194,6 @@ importers: '@sveltejs/vite-plugin-svelte': specifier: ^5.0.3 version: 5.0.3(svelte@5.34.8)(vite@6.0.11(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.7.0)) - jsdom: - specifier: ^24.1.0 - version: 24.1.0 publint: specifier: ^0.1.9 version: 0.1.16 @@ -1170,10 +1167,6 @@ packages: resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} engines: {node: '>=8'} - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -1604,9 +1597,6 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - '@sveltejs/acorn-typescript@1.0.5': resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} peerDependencies: @@ -2522,10 +2512,6 @@ packages: dfa@1.2.0: resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==} - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - diff@5.2.0: resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} @@ -3243,22 +3229,6 @@ packages: resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} engines: {node: '>=14'} - jest-axe@9.0.0: - resolution: {integrity: sha512-Xt7O0+wIpW31lv0SO1wQZUTyJE7DEmnDEZeTt9/S9L5WUywxrv8BrgvTuQEqujtfaQOcJ70p4wg7UUgK1E2F5g==} - engines: {node: '>= 16.0.0'} - - jest-diff@29.7.0: - resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-get-type@29.6.3: - resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-matcher-utils@29.2.2: - resolution: {integrity: sha512-4DkJ1sDPT+UX2MR7Y3od6KtvRi9Im1ZGLGgdLFLm4lPexbTaCgJW5NN3IOXlQHF7NSHY/VHhflQ+WoKtD/vyCw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jiti@1.21.6: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true @@ -3447,6 +3417,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -4193,10 +4166,6 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - prismjs@1.29.0: resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} engines: {node: '>=6'} @@ -4249,9 +4218,6 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} @@ -5219,6 +5185,11 @@ packages: vite: optional: true + vitest-axe@1.0.0-pre.3: + resolution: {integrity: sha512-vrsyixV225vMe0vGZV0aZjOYez2Pan5MxIx2RqnYnpbbRrUN2lJpQS9ong6dfF5a7BfQenR0LOD6hei3IQIPSw==} + peerDependencies: + vitest: '>=0.31.0' + vitest-browser-svelte@0.1.0: resolution: {integrity: sha512-YB6ZUZZQNqU1T9NzvTEDpwpPv35Ng1NZMPBh81zDrLEdOgROGE6nJb79NWb1Eu/a8VkHifqArpOZfJfALge6xQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -6552,10 +6523,6 @@ snapshots: '@istanbuljs/schema@0.1.3': {} - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.8 - '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 @@ -6920,8 +6887,6 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} - '@sinclair/typebox@0.27.8': {} - '@sveltejs/acorn-typescript@1.0.5(acorn@8.15.0)': dependencies: acorn: 8.15.0 @@ -7301,6 +7266,26 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@vitest/browser@3.2.3(playwright@1.52.0)(vite@6.0.11(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.7.0))(vitest@3.2.3)': + dependencies: + '@testing-library/dom': 10.4.0 + '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.0) + '@vitest/mocker': 3.2.3(vite@6.0.11(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.7.0)) + '@vitest/utils': 3.2.3 + magic-string: 0.30.17 + sirv: 3.0.1 + tinyrainbow: 2.0.0 + vitest: 3.2.3(@types/debug@4.1.12)(@vitest/browser@3.2.3)(jiti@2.4.2)(jsdom@24.1.0)(lightningcss@1.30.1)(yaml@2.7.0) + ws: 8.18.2 + optionalDependencies: + playwright: 1.52.0 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + optional: true + '@vitest/browser@3.2.3(playwright@1.52.0)(vite@6.3.5(jiti@1.21.6)(lightningcss@1.30.1)(yaml@2.7.0))(vitest@3.2.3)': dependencies: '@testing-library/dom': 10.4.0 @@ -7349,6 +7334,15 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 + '@vitest/mocker@3.2.3(vite@6.0.11(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.7.0))': + dependencies: + '@vitest/spy': 3.2.3 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.0.11(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.7.0) + optional: true + '@vitest/mocker@3.2.3(vite@6.3.5(jiti@1.21.6)(lightningcss@1.30.1)(yaml@2.7.0))': dependencies: '@vitest/spy': 3.2.3 @@ -7357,6 +7351,14 @@ snapshots: optionalDependencies: vite: 6.3.5(jiti@1.21.6)(lightningcss@1.30.1)(yaml@2.7.0) + '@vitest/mocker@3.2.3(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.7.0))': + dependencies: + '@vitest/spy': 3.2.3 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.3.5(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.7.0) + '@vitest/pretty-format@3.2.3': dependencies: tinyrainbow: 2.0.0 @@ -7452,6 +7454,7 @@ snapshots: debug: 4.4.1 transitivePeerDependencies: - supports-color + optional: true ajv@6.12.6: dependencies: @@ -7653,7 +7656,8 @@ snapshots: '@astrojs/compiler': 2.12.0 synckit: 0.9.0 - asynckit@0.4.0: {} + asynckit@0.4.0: + optional: true autoprefixer@10.4.21(postcss@8.5.3): dependencies: @@ -7887,6 +7891,7 @@ snapshots: combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 + optional: true comma-separated-tokens@2.0.3: {} @@ -7953,11 +7958,13 @@ snapshots: cssstyle@4.0.1: dependencies: rrweb-cssom: 0.6.0 + optional: true data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 whatwg-url: 14.0.0 + optional: true dataloader@1.4.0: {} @@ -7977,7 +7984,8 @@ snapshots: dependencies: ms: 2.1.3 - decimal.js@10.4.3: {} + decimal.js@10.4.3: + optional: true decode-named-character-reference@1.0.2: dependencies: @@ -7999,7 +8007,8 @@ snapshots: defu@6.1.4: {} - delayed-stream@1.0.0: {} + delayed-stream@1.0.0: + optional: true dequal@2.0.3: {} @@ -8023,8 +8032,6 @@ snapshots: dfa@1.2.0: {} - diff-sequences@29.6.3: {} - diff@5.2.0: {} dir-glob@3.0.1: @@ -8488,6 +8495,7 @@ snapshots: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 + optional: true fraction.js@4.3.7: {} @@ -8785,6 +8793,7 @@ snapshots: html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 + optional: true html-escaper@2.0.2: {} @@ -8799,16 +8808,18 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color + optional: true https-proxy-agent@7.0.4: dependencies: agent-base: 7.1.1 - debug: 4.4.0 + debug: 4.4.1 transitivePeerDependencies: - supports-color + optional: true human-id@1.0.2: {} @@ -8825,6 +8836,7 @@ snapshots: iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 + optional: true ieee754@1.2.1: {} @@ -8899,7 +8911,8 @@ snapshots: is-plain-obj@4.1.0: {} - is-potential-custom-element-name@1.0.1: {} + is-potential-custom-element-name@1.0.1: + optional: true is-reference@3.0.3: dependencies: @@ -8948,29 +8961,6 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jest-axe@9.0.0: - dependencies: - axe-core: 4.9.1 - chalk: 4.1.2 - jest-matcher-utils: 29.2.2 - lodash.merge: 4.6.2 - - jest-diff@29.7.0: - dependencies: - chalk: 4.1.2 - diff-sequences: 29.6.3 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - jest-get-type@29.6.3: {} - - jest-matcher-utils@29.2.2: - dependencies: - chalk: 4.1.2 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - jiti@1.21.6: {} jiti@2.4.2: {} @@ -9017,6 +9007,7 @@ snapshots: - bufferutil - supports-color - utf-8-validate + optional: true jsesc@2.5.2: optional: true @@ -9128,6 +9119,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash-es@4.17.21: {} + lodash.merge@4.6.2: {} lodash.sortby@4.7.0: {} @@ -9653,11 +9646,13 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 - mime-db@1.52.0: {} + mime-db@1.52.0: + optional: true mime-types@2.1.35: dependencies: mime-db: 1.52.0 + optional: true mimic-fn@2.1.0: {} @@ -9787,7 +9782,8 @@ snapshots: dependencies: esm-env: 1.2.2 - nwsapi@2.2.10: {} + nwsapi@2.2.10: + optional: true object-assign@4.1.1: {} @@ -10065,12 +10061,6 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 - pretty-format@29.7.0: - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 - prismjs@1.29.0: {} prismjs@1.30.0: {} @@ -10084,7 +10074,8 @@ snapshots: property-information@7.1.0: {} - psl@1.9.0: {} + psl@1.9.0: + optional: true publint@0.1.16: dependencies: @@ -10099,7 +10090,8 @@ snapshots: punycode@2.3.1: {} - querystringify@2.2.0: {} + querystringify@2.2.0: + optional: true queue-microtask@1.2.3: {} @@ -10116,8 +10108,6 @@ snapshots: react-is@17.0.2: {} - react-is@18.3.1: {} - read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -10313,7 +10303,8 @@ snapshots: require-from-string@2.0.2: {} - requires-port@1.0.0: {} + requires-port@1.0.0: + optional: true resolve-from@4.0.0: {} @@ -10425,9 +10416,11 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.41.0 fsevents: 2.3.3 - rrweb-cssom@0.6.0: {} + rrweb-cssom@0.6.0: + optional: true - rrweb-cssom@0.7.1: {} + rrweb-cssom@0.7.1: + optional: true run-parallel@1.2.0: dependencies: @@ -10473,6 +10466,7 @@ snapshots: saxes@6.0.0: dependencies: xmlchars: 2.2.0 + optional: true semver@6.3.1: optional: true @@ -10811,7 +10805,8 @@ snapshots: magic-string: 0.30.17 zimmerframe: 1.1.2 - symbol-tree@3.2.4: {} + symbol-tree@3.2.4: + optional: true synckit@0.9.0: dependencies: @@ -10911,6 +10906,7 @@ snapshots: punycode: 2.3.1 universalify: 0.2.0 url-parse: 1.5.10 + optional: true tr46@0.0.3: {} @@ -10921,6 +10917,7 @@ snapshots: tr46@5.0.0: dependencies: punycode: 2.3.1 + optional: true tree-kill@1.2.2: {} @@ -11089,7 +11086,8 @@ snapshots: universalify@0.1.2: {} - universalify@0.2.0: {} + universalify@0.2.0: + optional: true unplugin-icons@0.19.0: dependencies: @@ -11135,6 +11133,7 @@ snapshots: dependencies: querystringify: 2.2.0 requires-port: 1.0.0 + optional: true util-deprecate@1.0.2: {} @@ -11174,6 +11173,27 @@ snapshots: - tsx - yaml + vite-node@3.2.3(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.7.0): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.3.5(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.7.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vite@6.0.11(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.7.0): dependencies: esbuild: 0.24.2 @@ -11225,6 +11245,13 @@ snapshots: optionalDependencies: vite: 6.3.5(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.7.0) + vitest-axe@1.0.0-pre.3(vitest@3.2.3): + dependencies: + axe-core: 4.9.1 + chalk: 5.3.0 + lodash-es: 4.17.21 + vitest: 3.2.3(@types/debug@4.1.12)(@vitest/browser@3.2.3)(jiti@2.4.2)(jsdom@24.1.0)(lightningcss@1.30.1)(yaml@2.7.0) + vitest-browser-svelte@0.1.0(@vitest/browser@3.2.3)(svelte@5.34.8)(vitest@3.2.3): dependencies: '@vitest/browser': 3.2.3(playwright@1.52.0)(vite@6.3.5(jiti@1.21.6)(lightningcss@1.30.1)(yaml@2.7.0))(vitest@3.2.3) @@ -11274,6 +11301,49 @@ snapshots: - tsx - yaml + vitest@3.2.3(@types/debug@4.1.12)(@vitest/browser@3.2.3)(jiti@2.4.2)(jsdom@24.1.0)(lightningcss@1.30.1)(yaml@2.7.0): + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.3 + '@vitest/mocker': 3.2.3(vite@6.3.5(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.7.0)) + '@vitest/pretty-format': 3.2.3 + '@vitest/runner': 3.2.3 + '@vitest/snapshot': 3.2.3 + '@vitest/spy': 3.2.3 + '@vitest/utils': 3.2.3 + chai: 5.2.0 + debug: 4.4.1 + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.0 + tinyrainbow: 2.0.0 + vite: 6.3.5(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.7.0) + vite-node: 3.2.3(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.7.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@vitest/browser': 3.2.3(playwright@1.52.0)(vite@6.0.11(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.7.0))(vitest@3.2.3) + jsdom: 24.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + volar-service-css@0.0.62(@volar/language-service@2.4.11): dependencies: vscode-css-languageservice: 6.3.0 @@ -11389,6 +11459,7 @@ snapshots: w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 + optional: true web-namespaces@2.0.1: {} @@ -11396,7 +11467,8 @@ snapshots: webidl-conversions@4.0.2: {} - webidl-conversions@7.0.0: {} + webidl-conversions@7.0.0: + optional: true webpack-sources@3.2.3: {} @@ -11405,13 +11477,16 @@ snapshots: whatwg-encoding@3.1.1: dependencies: iconv-lite: 0.6.3 + optional: true - whatwg-mimetype@4.0.0: {} + whatwg-mimetype@4.0.0: + optional: true whatwg-url@14.0.0: dependencies: tr46: 5.0.0 webidl-conversions: 7.0.0 + optional: true whatwg-url@5.0.0: dependencies: @@ -11480,9 +11555,11 @@ snapshots: ws@8.18.2: {} - xml-name-validator@5.0.0: {} + xml-name-validator@5.0.0: + optional: true - xmlchars@2.2.0: {} + xmlchars@2.2.0: + optional: true xxhash-wasm@1.1.0: {}