From 04e4c5055d950b283d438ef36da8de3d1c8f6e16 Mon Sep 17 00:00:00 2001 From: ding113 Date: Tue, 1 Sep 2026 16:30:01 +0800 Subject: [PATCH] fix(test): isolate motion animations in happy-dom --- messages/ja/dashboard.json | 2 +- tests/framer-motion.mock.tsx | 49 +++++++++++++++++++ .../unit/auth/login-page-site-title.test.tsx | 1 + .../login/login-footer-system-name.test.tsx | 1 + .../unit/login/login-footer-version.test.tsx | 1 + tests/unit/login/login-loading-state.test.tsx | 1 + tests/unit/login/login-overlay-a11y.test.tsx | 1 + tests/unit/login/login-ui-redesign.test.tsx | 1 + .../login/login-visual-regression.test.tsx | 1 + .../provider-form-endpoint-pool.test.tsx | 1 + .../provider-form-total-limit-ui.test.tsx | 1 + 11 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/framer-motion.mock.tsx diff --git a/messages/ja/dashboard.json b/messages/ja/dashboard.json index f821d2955..e3747624c 100644 --- a/messages/ja/dashboard.json +++ b/messages/ja/dashboard.json @@ -522,7 +522,7 @@ "configTotalTimeout": "全体タイムアウト", "configStickyCooldown": "Sticky タイムアウトのクールダウン", "configStickyBindingTtl": "Sticky バインドの有効期間 (SESSION_TTL)", - "slotSaturation": "Hedge スロット飽和({count} 件)", + "slotSaturation": "Hedge スロット飽和 ({count} 件)", "slotSaturationEvent": "{provider} · {active}/{cap} 件がアクティブ · 経過 {elapsed}ms", "attemptDetails": { "providerId": "Provider ID", diff --git a/tests/framer-motion.mock.tsx b/tests/framer-motion.mock.tsx new file mode 100644 index 000000000..85ef5352b --- /dev/null +++ b/tests/framer-motion.mock.tsx @@ -0,0 +1,49 @@ +import { createElement, type ComponentType, type ReactNode } from "react"; +import { vi } from "vitest"; + +type MotionProps = { + children?: ReactNode; + [key: string]: unknown; +}; + +const motionOnlyProps = new Set([ + "animate", + "custom", + "exit", + "initial", + "layout", + "layoutId", + "onAnimationComplete", + "onAnimationStart", + "transition", + "variants", + "whileFocus", + "whileHover", + "whileInView", + "whileTap", +]); + +function createMotionComponent(tag: string): ComponentType { + return ({ children, ...props }) => { + const domProps = Object.fromEntries( + Object.entries(props).filter(([name]) => !motionOnlyProps.has(name)) + ); + return createElement(tag, domProps, children); + }; +} + +const motionComponents = new Map>(); +const motion = new Proxy({} as Record>, { + get: (_target, property: string | symbol) => { + if (typeof property !== "string") return undefined; + + const existing = motionComponents.get(property); + if (existing) return existing; + + const component = createMotionComponent(property); + motionComponents.set(property, component); + return component; + }, +}); + +vi.mock("framer-motion", () => ({ motion })); diff --git a/tests/unit/auth/login-page-site-title.test.tsx b/tests/unit/auth/login-page-site-title.test.tsx index e22d4f7a4..e5fbd9b19 100644 --- a/tests/unit/auth/login-page-site-title.test.tsx +++ b/tests/unit/auth/login-page-site-title.test.tsx @@ -5,6 +5,7 @@ import { act } from "react"; import { createRoot } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import "../../framer-motion.mock"; import LoginPage from "../../../src/app/[locale]/login/page"; const mockPush = vi.hoisted(() => vi.fn()); diff --git a/tests/unit/login/login-footer-system-name.test.tsx b/tests/unit/login/login-footer-system-name.test.tsx index 92df59138..4dd320da6 100644 --- a/tests/unit/login/login-footer-system-name.test.tsx +++ b/tests/unit/login/login-footer-system-name.test.tsx @@ -1,6 +1,7 @@ import { act } from "react"; import { createRoot } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import "../../framer-motion.mock"; import LoginPage from "@/app/[locale]/login/page"; import { DEFAULT_SITE_TITLE } from "@/lib/site-title"; diff --git a/tests/unit/login/login-footer-version.test.tsx b/tests/unit/login/login-footer-version.test.tsx index 349a57a32..ef7817b5e 100644 --- a/tests/unit/login/login-footer-version.test.tsx +++ b/tests/unit/login/login-footer-version.test.tsx @@ -1,6 +1,7 @@ import { act } from "react"; import { createRoot } from "react-dom/client"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import "../../framer-motion.mock"; import LoginPage from "@/app/[locale]/login/page"; const mockPush = vi.hoisted(() => vi.fn()); diff --git a/tests/unit/login/login-loading-state.test.tsx b/tests/unit/login/login-loading-state.test.tsx index 00d7314e5..83da6ba8a 100644 --- a/tests/unit/login/login-loading-state.test.tsx +++ b/tests/unit/login/login-loading-state.test.tsx @@ -1,6 +1,7 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; import { createRoot } from "react-dom/client"; import { act } from "react"; +import "../../framer-motion.mock"; import LoginPage from "@/app/[locale]/login/page"; const mockPush = vi.hoisted(() => vi.fn()); diff --git a/tests/unit/login/login-overlay-a11y.test.tsx b/tests/unit/login/login-overlay-a11y.test.tsx index 8e9311a4d..ca642d763 100644 --- a/tests/unit/login/login-overlay-a11y.test.tsx +++ b/tests/unit/login/login-overlay-a11y.test.tsx @@ -1,6 +1,7 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; import { createRoot } from "react-dom/client"; import { act } from "react"; +import "../../framer-motion.mock"; import LoginPage from "@/app/[locale]/login/page"; const mockPush = vi.hoisted(() => vi.fn()); diff --git a/tests/unit/login/login-ui-redesign.test.tsx b/tests/unit/login/login-ui-redesign.test.tsx index d374a2aaf..1869cdb51 100644 --- a/tests/unit/login/login-ui-redesign.test.tsx +++ b/tests/unit/login/login-ui-redesign.test.tsx @@ -1,6 +1,7 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; import { createRoot } from "react-dom/client"; import { act } from "react"; +import "../../framer-motion.mock"; import LoginPage from "@/app/[locale]/login/page"; const mockPush = vi.hoisted(() => vi.fn()); diff --git a/tests/unit/login/login-visual-regression.test.tsx b/tests/unit/login/login-visual-regression.test.tsx index c251a552f..94bd61511 100644 --- a/tests/unit/login/login-visual-regression.test.tsx +++ b/tests/unit/login/login-visual-regression.test.tsx @@ -1,6 +1,7 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; import { createRoot } from "react-dom/client"; import { act } from "react"; +import "../../framer-motion.mock"; import LoginPage from "@/app/[locale]/login/page"; // Mocks diff --git a/tests/unit/settings/providers/provider-form-endpoint-pool.test.tsx b/tests/unit/settings/providers/provider-form-endpoint-pool.test.tsx index 918159969..bbe465352 100644 --- a/tests/unit/settings/providers/provider-form-endpoint-pool.test.tsx +++ b/tests/unit/settings/providers/provider-form-endpoint-pool.test.tsx @@ -8,6 +8,7 @@ import { act } from "react"; import { createRoot } from "react-dom/client"; import { NextIntlClientProvider } from "next-intl"; import { beforeEach, describe, expect, test, vi } from "vitest"; +import "../../../framer-motion.mock"; import { ProviderForm } from "../../../../src/app/[locale]/settings/providers/_components/forms/provider-form"; import { Dialog } from "../../../../src/components/ui/dialog"; import enMessages from "../../../../messages/en"; diff --git a/tests/unit/settings/providers/provider-form-total-limit-ui.test.tsx b/tests/unit/settings/providers/provider-form-total-limit-ui.test.tsx index 03b1ef8a7..ed3597b8d 100644 --- a/tests/unit/settings/providers/provider-form-total-limit-ui.test.tsx +++ b/tests/unit/settings/providers/provider-form-total-limit-ui.test.tsx @@ -8,6 +8,7 @@ import { act } from "react"; import { createRoot } from "react-dom/client"; import { NextIntlClientProvider } from "next-intl"; import { beforeEach, describe, expect, test, vi } from "vitest"; +import "../../../framer-motion.mock"; import { ProviderForm } from "../../../../src/app/[locale]/settings/providers/_components/forms/provider-form"; import { Dialog } from "../../../../src/components/ui/dialog"; import enMessages from "../../../../messages/en";