diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7f69505 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,30 @@ +## ✨ 작업 배경 [#](#background) + + + + + + + + +✔️ Related issues # + + + + + + +## 💻 작업 내용 [#](#work_detail) + + + + + + +## 🏃 기능 동작 시연 [#](#prove) + + + +## 💬 코멘트 [#](#comments) + + diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz index 9377de1..bd4aae6 100644 Binary files a/.yarn/install-state.gz and b/.yarn/install-state.gz differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a2795e2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 dhlab-fe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/package.json b/package.json index 9e20e10..458bc81 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "types": "dist/index.d.ts", "files": [ "dist", - "README.md" + "README.md", + "LICENSE" ], "scripts": { "build": "rollup -c", @@ -37,8 +38,7 @@ "peerDependencies": { "@tanstack/react-query": "^4.0.0 || ^5.0.0", "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0", - "react-error-boundary": "^4.0.0" + "react-dom": "^17.0.0 || ^18.0.0" }, "peerDependenciesMeta": { "axios": { @@ -49,6 +49,7 @@ } }, "dependencies": { + "react-error-boundary": "^4.0.0", "ts-pattern": "^5.0.0" }, "devDependencies": { @@ -63,12 +64,12 @@ "@testing-library/user-event": "^14.6.1", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", + "@vitest/coverage-v8": "^3.2.4", "axios": "^1.10.0", "jsdom": "^26.1.0", "ky": "^1.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-error-boundary": "^4.0.0", "rollup": "^4.0.0", "rollup-plugin-peer-deps-external": "^2.2.4", "tslib": "^2.8.1", diff --git a/src/components/__tests__/api-error-boundary.integration.test.tsx b/src/components/__tests__/api-error-boundary.integration.test.tsx index e9d83bb..bada966 100644 --- a/src/components/__tests__/api-error-boundary.integration.test.tsx +++ b/src/components/__tests__/api-error-boundary.integration.test.tsx @@ -12,7 +12,7 @@ import { it, vi, } from "vitest"; -import type { PartialErrorConfig } from "../../types/api-error"; +import type { TPartialErrorConfig } from "../../types/api-error"; import { ApiErrorBoundary } from "../api-error-boundary"; // Mock HTTPError (실제 HTTPError를 사용하되 Response 객체 생성) @@ -196,7 +196,7 @@ describe("ApiErrorBoundary Integration Tests", () => { describe("커스텀 설정", () => { it("커스텀 fallback 컴포넌트를 사용해야 함", async () => { - const customConfig: PartialErrorConfig = { + const customConfig: TPartialErrorConfig = { 404: { type: "custom", fallback: ( @@ -218,7 +218,7 @@ describe("ApiErrorBoundary Integration Tests", () => { }); it("커스텀 fallback 함수를 사용해야 함", async () => { - const customConfig: PartialErrorConfig = { + const customConfig: TPartialErrorConfig = { 404: { type: "custom", fallback: (error, resetErrorBoundary) => ( @@ -307,7 +307,7 @@ describe("ApiErrorBoundary Integration Tests", () => { describe("onError 콜백", () => { it("에러 발생 시 onError 콜백이 호출되어야 함", async () => { const onErrorSpy = vi.fn(); - const customConfig: PartialErrorConfig = { + const customConfig: TPartialErrorConfig = { 404: { type: "default", message: "오류가 발생했습니다.", diff --git a/src/components/__tests__/error-boundary.test.tsx b/src/components/__tests__/error-boundary.test.tsx new file mode 100644 index 0000000..d400ae5 --- /dev/null +++ b/src/components/__tests__/error-boundary.test.tsx @@ -0,0 +1,359 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + it, + vi, +} from "vitest"; +import { ErrorBoundary } from "../error-boundary"; + +// Mock 컴포넌트들 +const NormalComponent = () =>
정상적인 컴포넌트
; + +const ErrorComponent = ({ message = "Test error" }: { message?: string }) => { + throw new Error(message); +}; + +const NetworkErrorComponent = () => { + const error = new Error("Network connection failed"); + error.name = "NetworkError"; + throw error; +}; + +const CustomErrorComponent = ({ code = 404 }: { code?: number }) => { + const error = new Error(`Error ${code}`); + error.name = code.toString(); + throw error; +}; + +// Custom Fallback Component +const CustomFallbackComponent = ({ error, resetErrorBoundary }: any) => ( +
+

Custom Error: {error.message}

+ +
+); + +describe("ErrorBoundary", () => { + const originalConsoleError = console.error; + + beforeAll(() => { + console.error = vi.fn(); + }); + + afterAll(() => { + console.error = originalConsoleError; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe("정상 동작", () => { + it("에러가 없을 때 자식 컴포넌트가 정상적으로 렌더링되어야 함", () => { + render( +
Error: {error.message}
} + > + +
, + ); + + expect(screen.getByText("정상적인 컴포넌트")).toBeInTheDocument(); + }); + }); + + describe("에러 처리", () => { + it("기본 에러를 처리하고 fallbackRender를 사용해야 함", async () => { + render( + ( +
+

에러 발생: {error.message}

+ +
+ )} + > + +
, + ); + + await waitFor(() => { + expect( + screen.getByText("에러 발생: Test error occurred"), + ).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: /다시 시도/i }), + ).toBeInTheDocument(); + }); + }); + + it("FallbackComponent를 사용해야 함", async () => { + render( + + + , + ); + + await waitFor(() => { + expect( + screen.getByText("Custom Error: Custom component error"), + ).toBeInTheDocument(); + expect( + screen.getByRole("button", { name: /Reset Error/i }), + ).toBeInTheDocument(); + }); + }); + + it("fallback ReactNode를 사용해야 함", async () => { + render( + Simple fallback}> + + , + ); + + await waitFor(() => { + expect(screen.getByText("Simple fallback")).toBeInTheDocument(); + }); + }); + + it("onError 콜백이 호출되어야 함", async () => { + const onError = vi.fn(); + + render( +
Error: {error.message}
} + > + +
, + ); + + await waitFor(() => { + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + message: "Callback test error", + }), + expect.any(Object), + ); + }); + }); + }); + + describe("ignoreError 기능", () => { + it("문자열 패턴으로 에러를 무시해야 함", () => { + expect(() => { + render( +
Error: {error.message}
} + > + +
, + ); + }).toThrow("Network connection failed"); + }); + + it("숫자 패턴으로 에러를 무시해야 함", () => { + expect(() => { + render( +
Error: {error.message}
} + > + +
, + ); + }).toThrow("Error 404"); + }); + + it("함수 패턴으로 에러를 무시해야 함", () => { + const ignoreFunction = vi.fn().mockReturnValue(true); + + expect(() => { + render( +
Error: {error.message}
} + > + +
, + ); + }).toThrow("Function test error"); + }); + + it("무시하지 않는 에러는 정상적으로 처리해야 함", async () => { + const onError = vi.fn(); + + render( +
Error: {error.message}
} + > + +
, + ); + + await waitFor(() => { + expect(screen.getByText("Error: Different error")).toBeInTheDocument(); + expect(onError).toHaveBeenCalledWith( + expect.objectContaining({ + message: "Different error", + }), + expect.any(Object), + ); + }); + }); + + it("여러 무시 조건을 함께 사용할 수 있어야 함", () => { + expect(() => { + render( + error.message.includes("timeout"), + ]} + fallbackRender={({ error }) =>
Error: {error.message}
} + > + +
, + ); + }).toThrow("Request timeout occurred"); + }); + }); + + describe("에러 경계 리셋", () => { + it("resetErrorBoundary 호출 시 에러 상태가 초기화되어야 함", async () => { + const user = userEvent.setup(); + let hasError = true; + + const ConditionalErrorComponent = () => { + if (hasError) { + throw new Error("Conditional error"); + } + return
No error now
; + }; + + render( + ( +
+

Error: {error.message}

+ +
+ )} + > + +
, + ); + + // 에러 상태 확인 + await waitFor(() => { + expect( + screen.getByText("Error: Conditional error"), + ).toBeInTheDocument(); + }); + + // 리셋 버튼 클릭 + await user.click(screen.getByRole("button", { name: /Reset/i })); + + // 에러가 해결되어 정상 컴포넌트가 렌더링되어야 함 + await waitFor(() => { + expect(screen.getByText("No error now")).toBeInTheDocument(); + }); + }); + + it("resetKeys 변경 시 에러 상태가 초기화되어야 함", async () => { + let hasError = true; + const ConditionalErrorComponent = () => { + if (hasError) { + throw new Error("Reset keys test error"); + } + return
Reset successful
; + }; + + const { rerender } = render( +
Error: {error.message}
} + > + +
, + ); + + // 에러 상태 확인 + await waitFor(() => { + expect( + screen.getByText("Error: Reset keys test error"), + ).toBeInTheDocument(); + }); + + // 에러 조건 해제 후 resetKeys 변경 + hasError = false; + rerender( +
Error: {error.message}
} + > + +
, + ); + + // 에러가 해결되어 정상 컴포넌트가 렌더링되어야 함 + await waitFor(() => { + expect(screen.getByText("Reset successful")).toBeInTheDocument(); + }); + }); + + it("onReset 콜백이 호출되어야 함", async () => { + const user = userEvent.setup(); + const onReset = vi.fn(); + + render( + ( +
+

Error: {error.message}

+ +
+ )} + > + +
, + ); + + await waitFor(() => { + expect( + screen.getByText("Error: Reset callback test"), + ).toBeInTheDocument(); + }); + + await user.click(screen.getByRole("button", { name: /Reset/i })); + + await waitFor(() => { + expect(onReset).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/src/components/api-error-boundary.tsx b/src/components/api-error-boundary.tsx index 31cf224..5f59632 100644 --- a/src/components/api-error-boundary.tsx +++ b/src/components/api-error-boundary.tsx @@ -10,10 +10,10 @@ import { type FallbackProps, } from "react-error-boundary"; import { P, match } from "ts-pattern"; -import type { PartialErrorConfig } from "../types/api-error"; +import type { TPartialErrorConfig } from "../types/api-error"; import { getErrorConfig, isApiError } from "../utils/api-error"; -type IgnoreErrorType = +export type TIgnoreErrorType = | string | number | ((error: HTTPError | AxiosError) => boolean); @@ -28,13 +28,13 @@ const DefaultButton = (props: React.ComponentProps<"button">) => ( ); -type ApiErrorBoundaryProps = { +export type TApiErrorBoundaryProps = { children: React.ReactNode; FallbackContainer?: React.ComponentType<{ children: React.ReactNode }>; Button?: React.ComponentType>; - overrideConfig?: PartialErrorConfig; + overrideConfig?: TPartialErrorConfig; resetKeys?: ErrorBoundaryProps["resetKeys"]; - ignoreError?: IgnoreErrorType[]; + ignoreError?: TIgnoreErrorType[]; }; export function ApiErrorBoundary({ @@ -46,7 +46,7 @@ export function ApiErrorBoundary({ overrideConfig, resetKeys, ignoreError = [], -}: ApiErrorBoundaryProps) { +}: TApiErrorBoundaryProps) { const handleError: ErrorBoundaryProps["onError"] = (error, info) => { if (!isApiError(error)) { throw error; @@ -125,7 +125,7 @@ export function ApiErrorBoundary({ type ApiErrorFallbackProps = { error: HTTPError | AxiosError; resetErrorBoundary: FallbackProps["resetErrorBoundary"]; - overrideConfig?: PartialErrorConfig; + overrideConfig?: TPartialErrorConfig; Button: React.ComponentType>; }; diff --git a/src/components/error-boundary.tsx b/src/components/error-boundary.tsx new file mode 100644 index 0000000..7dc46fb --- /dev/null +++ b/src/components/error-boundary.tsx @@ -0,0 +1,45 @@ +"use client"; + +import type React from "react"; +import type { ErrorInfo } from "react"; +import { ErrorBoundary as ReactErrorBoundary } from "react-error-boundary"; + +export type TErrorBoundaryProps = React.ComponentProps< + typeof ReactErrorBoundary +> & { + ignoreError?: Array boolean)>; +}; + +export function ErrorBoundary({ + children, + ignoreError = [], + onError, + ...rest +}: TErrorBoundaryProps) { + const handleError = (error: Error, errorInfo: ErrorInfo) => { + const shouldIgnore = ignoreError.some((ignore) => { + if (typeof ignore === "string") { + return error.message.includes(ignore); + } + if (typeof ignore === "number") { + return error.name === ignore.toString(); + } + if (typeof ignore === "function") { + return ignore(error); + } + return false; + }); + + if (shouldIgnore) { + throw error; + } + + onError?.(error, errorInfo); + }; + + return ( + + {children} + + ); +} diff --git a/src/index.ts b/src/index.ts index faf139e..7648a98 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,20 @@ // Components -export { ApiErrorBoundary } from "./components/api-error-boundary"; +export { + ApiErrorBoundary, + type TApiErrorBoundaryProps, +} from "./components/api-error-boundary"; +export { + ErrorBoundary, + type TErrorBoundaryProps, +} from "./components/error-boundary"; + +export * from "react-error-boundary"; +export { ErrorBoundary as BaseErrorBoundary } from "react-error-boundary"; // Types export type { - PartialErrorConfig, - ErrorConfigElementType, + TPartialErrorConfig as PartialErrorConfig, + TErrorConfigElementType as ErrorConfigElementType, } from "./types/api-error"; // Utils diff --git a/src/types/api-error.ts b/src/types/api-error.ts index fbfb110..0b7a058 100644 --- a/src/types/api-error.ts +++ b/src/types/api-error.ts @@ -3,28 +3,28 @@ import type { HTTPError } from "ky"; import type { ErrorInfo } from "react"; import type { HTTP_ERROR_CONFIG } from "../constants/http-error-message"; -export type ErrorConfigElementType = +export type TErrorConfigElementType = (typeof HTTP_ERROR_CONFIG)[keyof typeof HTTP_ERROR_CONFIG]; -type OnErrorCallback = ( +type TOnErrorCallback = ( error: HTTPError | AxiosError, info: ErrorInfo, statusCode: number, ) => void; -type DefaultErrorConfigType = { +type TDefaultErrorConfigType = { type: "default"; - onError?: OnErrorCallback; + onError?: TOnErrorCallback; } & Partial< - Omit & { - action: Partial; + Omit & { + action: Partial; message: string; } >; -type CustomErrorConfigType = { +type TCustomErrorConfigType = { type: "custom"; - onError?: OnErrorCallback; + onError?: TOnErrorCallback; fallback: | React.ReactNode | (( @@ -33,8 +33,8 @@ type CustomErrorConfigType = { ) => React.ReactNode); }; -export type PartialErrorConfig = { +export type TPartialErrorConfig = { [K in keyof typeof HTTP_ERROR_CONFIG]?: - | ({ type: "default" } & DefaultErrorConfigType) - | ({ type: "custom" } & CustomErrorConfigType); + | ({ type: "default" } & TDefaultErrorConfigType) + | ({ type: "custom" } & TCustomErrorConfigType); }; diff --git a/src/utils/api-error.ts b/src/utils/api-error.ts index 2d7fb32..9c403f1 100644 --- a/src/utils/api-error.ts +++ b/src/utils/api-error.ts @@ -1,11 +1,11 @@ import type { AxiosError } from "axios"; import type { HTTPError } from "ky"; import { HTTP_ERROR_CONFIG } from "../constants/http-error-message"; -import type { PartialErrorConfig } from "../types/api-error"; +import type { TPartialErrorConfig } from "../types/api-error"; export const getErrorConfig = ( error: HTTPError | AxiosError, - overrideConfig?: PartialErrorConfig, + overrideConfig?: TPartialErrorConfig, ) => { const statusCode = Object.keys(HTTP_ERROR_CONFIG).includes( error.response?.status?.toString() ?? "", diff --git a/yarn.lock b/yarn.lock index 945f65a..185e910 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,16 @@ __metadata: languageName: node linkType: hard +"@ampproject/remapping@npm:^2.3.0": + version: 2.3.0 + resolution: "@ampproject/remapping@npm:2.3.0" + dependencies: + "@jridgewell/gen-mapping": "npm:^0.3.5" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/81d63cca5443e0f0c72ae18b544cc28c7c0ec2cea46e7cb888bb0e0f411a1191d0d6b7af798d54e30777d8d1488b2ec0732aac2be342d3d7d3ffd271c6f489ed + languageName: node + linkType: hard + "@asamuzakjp/css-color@npm:^3.2.0": version: 3.2.0 resolution: "@asamuzakjp/css-color@npm:3.2.0" @@ -36,6 +46,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.27.1": version: 7.27.1 resolution: "@babel/helper-validator-identifier@npm:7.27.1" @@ -43,6 +60,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.25.4": + version: 7.28.0 + resolution: "@babel/parser@npm:7.28.0" + dependencies: + "@babel/types": "npm:^7.28.0" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/c2ef81d598990fa949d1d388429df327420357cb5200271d0d0a2784f1e6d54afc8301eb8bdf96d8f6c77781e402da93c7dc07980fcc136ac5b9d5f1fce701b5 + languageName: node + linkType: hard + "@babel/runtime@npm:^7.12.5": version: 7.27.6 resolution: "@babel/runtime@npm:7.27.6" @@ -50,6 +78,23 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.25.4, @babel/types@npm:^7.28.0": + version: 7.28.0 + resolution: "@babel/types@npm:7.28.0" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.27.1" + checksum: 10c0/7ca8521bf5e2d2ed4db31176efaaf94463a6b7a4d16dcc60e34e963b3596c2ecadb85457bebed13a9ee9a5829ef5f515d05b55a991b6a8f3b835451843482e39 + languageName: node + linkType: hard + +"@bcoe/v8-coverage@npm:^1.0.2": + version: 1.0.2 + resolution: "@bcoe/v8-coverage@npm:1.0.2" + checksum: 10c0/1eb1dc93cc17fb7abdcef21a6e7b867d6aa99a7ec88ec8207402b23d9083ab22a8011213f04b2cf26d535f1d22dc26139b7929e6c2134c254bd1e14ba5e678c3 + languageName: node + linkType: hard + "@biomejs/biome@npm:^1.8.0": version: 1.9.4 resolution: "@biomejs/biome@npm:1.9.4" @@ -202,6 +247,7 @@ __metadata: "@testing-library/user-event": "npm:^14.6.1" "@types/react": "npm:^18.2.0" "@types/react-dom": "npm:^18.2.0" + "@vitest/coverage-v8": "npm:^3.2.4" axios: "npm:^1.10.0" jsdom: "npm:^26.1.0" ky: "npm:^1.8.1" @@ -219,7 +265,6 @@ __metadata: "@tanstack/react-query": ^4.0.0 || ^5.0.0 react: ^17.0.0 || ^18.0.0 react-dom: ^17.0.0 || ^18.0.0 - react-error-boundary: ^4.0.0 peerDependenciesMeta: axios: optional: true @@ -426,13 +471,47 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.5.0": +"@istanbuljs/schema@npm:^0.1.2": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 10c0/61c5286771676c9ca3eb2bd8a7310a9c063fb6e0e9712225c8471c582d157392c88f5353581c8c9adbe0dff98892317d2fdfc56c3499aa42e0194405206a963a + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.5": + version: 0.3.12 + resolution: "@jridgewell/gen-mapping@npm:0.3.12" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.0" + "@jridgewell/trace-mapping": "npm:^0.3.24" + checksum: 10c0/32f771ae2467e4d440be609581f7338d786d3d621bac3469e943b9d6d116c23c4becb36f84898a92bbf2f3c0511365c54a945a3b86a83141547a2a360a5ec0c7 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:^3.1.0": + version: 3.1.2 + resolution: "@jridgewell/resolve-uri@npm:3.1.2" + checksum: 10c0/d502e6fb516b35032331406d4e962c21fe77cdf1cbdb49c6142bcbd9e30507094b18972778a6e27cbad756209cfe34b1a27729e6fa08a2eb92b33943f680cf1e + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:^1.4.14, @jridgewell/sourcemap-codec@npm:^1.5.0": version: 1.5.4 resolution: "@jridgewell/sourcemap-codec@npm:1.5.4" checksum: 10c0/c5aab3e6362a8dd94ad80ab90845730c825fc4c8d9cf07ebca7a2eb8a832d155d62558800fc41d42785f989ddbb21db6df004d1786e8ecb65e428ab8dff71309 languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:^0.3.23, @jridgewell/trace-mapping@npm:^0.3.24, @jridgewell/trace-mapping@npm:^0.3.25": + version: 0.3.29 + resolution: "@jridgewell/trace-mapping@npm:0.3.29" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/fb547ba31658c4d74eb17e7389f4908bf7c44cef47acb4c5baa57289daf68e6fe53c639f41f751b3923aca67010501264f70e7b49978ad1f040294b22c37b333 + languageName: node + linkType: hard + "@npmcli/agent@npm:^3.0.0": version: 3.0.0 resolution: "@npmcli/agent@npm:3.0.0" @@ -815,6 +894,33 @@ __metadata: languageName: node linkType: hard +"@vitest/coverage-v8@npm:^3.2.4": + version: 3.2.4 + resolution: "@vitest/coverage-v8@npm:3.2.4" + dependencies: + "@ampproject/remapping": "npm:^2.3.0" + "@bcoe/v8-coverage": "npm:^1.0.2" + ast-v8-to-istanbul: "npm:^0.3.3" + debug: "npm:^4.4.1" + istanbul-lib-coverage: "npm:^3.2.2" + istanbul-lib-report: "npm:^3.0.1" + istanbul-lib-source-maps: "npm:^5.0.6" + istanbul-reports: "npm:^3.1.7" + magic-string: "npm:^0.30.17" + magicast: "npm:^0.3.5" + std-env: "npm:^3.9.0" + test-exclude: "npm:^7.0.1" + tinyrainbow: "npm:^2.0.0" + peerDependencies: + "@vitest/browser": 3.2.4 + vitest: 3.2.4 + peerDependenciesMeta: + "@vitest/browser": + optional: true + checksum: 10c0/cae3e58d81d56e7e1cdecd7b5baab7edd0ad9dee8dec9353c52796e390e452377d3f04174d40b6986b17c73241a5e773e422931eaa8102dcba0605ff24b25193 + languageName: node + linkType: hard + "@vitest/expect@npm:3.2.4": version: 3.2.4 resolution: "@vitest/expect@npm:3.2.4" @@ -972,6 +1078,17 @@ __metadata: languageName: node linkType: hard +"ast-v8-to-istanbul@npm:^0.3.3": + version: 0.3.3 + resolution: "ast-v8-to-istanbul@npm:0.3.3" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.25" + estree-walker: "npm:^3.0.3" + js-tokens: "npm:^9.0.1" + checksum: 10c0/ffc39bc3ab4b8c1f7aea945960ce6b1e518bab3da7c800277eab2da07d397eeae4a2cb8a5a5f817225646c8ea495c1e4434fbe082c84bae8042abddef53f50b2 + languageName: node + linkType: hard + "asynckit@npm:^0.4.0": version: 0.4.0 resolution: "asynckit@npm:0.4.0" @@ -1167,7 +1284,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.3.4, debug@npm:^4.4.1": +"debug@npm:4, debug@npm:^4.1.1, debug@npm:^4.3.4, debug@npm:^4.4.1": version: 4.4.1 resolution: "debug@npm:4.4.1" dependencies: @@ -1563,7 +1680,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.2.2": +"glob@npm:^10.2.2, glob@npm:^10.4.1": version: 10.4.5 resolution: "glob@npm:10.4.5" dependencies: @@ -1647,6 +1764,13 @@ __metadata: languageName: node linkType: hard +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + "http-cache-semantics@npm:^4.1.1": version: 4.2.0 resolution: "http-cache-semantics@npm:4.2.0" @@ -1777,6 +1901,45 @@ __metadata: languageName: node linkType: hard +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.2": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^4.0.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^5.0.6": + version: 5.0.6 + resolution: "istanbul-lib-source-maps@npm:5.0.6" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.23" + debug: "npm:^4.1.1" + istanbul-lib-coverage: "npm:^3.0.0" + checksum: 10c0/ffe75d70b303a3621ee4671554f306e0831b16f39ab7f4ab52e54d356a5d33e534d97563e318f1333a6aae1d42f91ec49c76b6cd3f3fb378addcb5c81da0255f + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.1.7": + version: 3.1.7 + resolution: "istanbul-reports@npm:3.1.7" + dependencies: + html-escaper: "npm:^2.0.0" + istanbul-lib-report: "npm:^3.0.0" + checksum: 10c0/a379fadf9cf8dc5dfe25568115721d4a7eb82fbd50b005a6672aff9c6989b20cc9312d7865814e0859cd8df58cbf664482e1d3604be0afde1f7fc3ccc1394a51 + languageName: node + linkType: hard + "jackspeak@npm:^3.1.2": version: 3.4.3 resolution: "jackspeak@npm:3.4.3" @@ -1901,6 +2064,26 @@ __metadata: languageName: node linkType: hard +"magicast@npm:^0.3.5": + version: 0.3.5 + resolution: "magicast@npm:0.3.5" + dependencies: + "@babel/parser": "npm:^7.25.4" + "@babel/types": "npm:^7.25.4" + source-map-js: "npm:^1.2.0" + checksum: 10c0/a6cacc0a848af84f03e3f5bda7b0de75e4d0aa9ddce5517fd23ed0f31b5ddd51b2d0ff0b7e09b51f7de0f4053c7a1107117edda6b0732dca3e9e39e6c5a68c64 + languageName: node + linkType: hard + +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 + languageName: node + linkType: hard + "make-fetch-happen@npm:^14.0.3": version: 14.0.3 resolution: "make-fetch-happen@npm:14.0.3" @@ -2449,7 +2632,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.5": +"semver@npm:^7.3.5, semver@npm:^7.5.3": version: 7.7.2 resolution: "semver@npm:7.7.2" bin: @@ -2516,7 +2699,7 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:^1.2.1": +"source-map-js@npm:^1.2.0, source-map-js@npm:^1.2.1": version: 1.2.1 resolution: "source-map-js@npm:1.2.1" checksum: 10c0/7bda1fc4c197e3c6ff17de1b8b2c20e60af81b63a52cb32ec5a5d67a20a7d42651e2cb34ebe93833c5a2a084377e17455854fee3e21e7925c64a51b6a52b0faf @@ -2648,6 +2831,17 @@ __metadata: languageName: node linkType: hard +"test-exclude@npm:^7.0.1": + version: 7.0.1 + resolution: "test-exclude@npm:7.0.1" + dependencies: + "@istanbuljs/schema": "npm:^0.1.2" + glob: "npm:^10.4.1" + minimatch: "npm:^9.0.4" + checksum: 10c0/6d67b9af4336a2e12b26a68c83308c7863534c65f27ed4ff7068a56f5a58f7ac703e8fc80f698a19bb154fd8f705cdf7ec347d9512b2c522c737269507e7b263 + languageName: node + linkType: hard + "tinybench@npm:^2.9.0": version: 2.9.0 resolution: "tinybench@npm:2.9.0"