新增根 biome.json(v2.5,v2 起原生支持 monorepo,子包自动向上查找):
- formatter: 2 空格缩进、单引号、行宽 100、LF、尾逗号、箭头函数括号
(匹配现有代码风格)
- linter: recommended preset + 项目化裁剪
- useTemplate/useTemplate off(geometry.ts 故意保留字符串拼接,
且测试用精确浮点断言锁定行为)
- noNonNullAssertion off(测试与 DOM 访问里的 ! 是惯用写法)
- noConsole off(4 个 lib 用 console.error/warn 做错误上报)
- CSS 关闭 noDescendingSpecificity/noDuplicateProperties
(手写 CSS 的 fallback 模式)
- useImportType/useNodejsImportProtocol/noUnusedVariables warn
- vcs.useIgnoreFile 自动读 .gitignore;排除 public/target/node_modules/
dist/.dioxus/static + Tailwind 入口 input.css(含 @theme 等 Biome 无法
解析的 at-rules)
首次格式化覆盖 29 个源文件:import 排序、node: 协议、引号/缩进统一,
并修复 index.ts 两处 forEach 回调隐式返回值(useIterableCallbackReturn)。
90 个 vitest 用例全绿,确认无语义改动。
116 lines
4.1 KiB
TypeScript
116 lines
4.1 KiB
TypeScript
/**
|
|
* theme-transition 测试。
|
|
*
|
|
* happy-dom 不提供 document.startViewTransition,天然覆盖降级路径(瞬切 dark class)。
|
|
* 主路径通过 mock startViewTransition 验证:调用它、设 CSS 变量。
|
|
*
|
|
* 注意:startThemeTransition 只接收 (x, y),目标主题(亮/暗)从 DOM 的 dark class
|
|
* 现状推导(取反),不依赖外部传入——避免与调用方状态不同步。
|
|
*/
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import './index';
|
|
|
|
describe('startThemeTransition', () => {
|
|
beforeEach(() => {
|
|
document.documentElement.classList.remove('dark');
|
|
document.documentElement.classList.remove('is-theme-transitioning');
|
|
document.documentElement.style.cssText = '';
|
|
vi.restoreAllMocks();
|
|
});
|
|
afterEach(() => {
|
|
document.documentElement.classList.remove('dark');
|
|
document.documentElement.classList.remove('is-theme-transitioning');
|
|
document.documentElement.style.cssText = '';
|
|
});
|
|
|
|
it('降级:无 startViewTransition 时,亮→暗(无 dark class 时 add)', () => {
|
|
expect(document.documentElement.classList.contains('dark')).toBe(false);
|
|
|
|
window.__startThemeTransition(100, 200);
|
|
|
|
expect(document.documentElement.classList.contains('dark')).toBe(true);
|
|
});
|
|
|
|
it('降级:无 startViewTransition 时,暗→亮(有 dark class 时 remove)', () => {
|
|
document.documentElement.classList.add('dark');
|
|
|
|
window.__startThemeTransition(100, 200);
|
|
|
|
expect(document.documentElement.classList.contains('dark')).toBe(false);
|
|
});
|
|
|
|
it('主路径:有 startViewTransition 时调用它,注入变量,callback 切换 dark class', async () => {
|
|
const cbRef: { cb: (() => void) | null } = { cb: null };
|
|
const readyP = Promise.resolve();
|
|
const finishedP = Promise.resolve();
|
|
const startVT = vi.fn((cb: () => void) => {
|
|
cbRef.cb = cb;
|
|
return { ready: readyP, finished: finishedP, skipTransition: () => {} };
|
|
});
|
|
Object.defineProperty(document, 'startViewTransition', {
|
|
value: startVT,
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
|
|
window.__startThemeTransition(100, 200);
|
|
|
|
expect(startVT).toHaveBeenCalledTimes(1);
|
|
|
|
// CSS 变量应注入
|
|
expect(document.documentElement.style.getPropertyValue('--tt-x')).toBe('100px');
|
|
expect(document.documentElement.style.getPropertyValue('--tt-y')).toBe('200px');
|
|
expect(document.documentElement.style.getPropertyValue('--tt-r')).toMatch(/^\d+(\.\d+)?px$/);
|
|
|
|
// is-theme-transitioning 应在 VT 之前添加
|
|
expect(document.documentElement.classList.contains('is-theme-transitioning')).toBe(true);
|
|
|
|
// callback 里根据 DOM 现状(无 dark)切到 dark
|
|
cbRef.cb?.();
|
|
expect(document.documentElement.classList.contains('dark')).toBe(true);
|
|
|
|
// finished 后移除 is-theme-transitioning 和 CSS 变量
|
|
await finishedP;
|
|
await Promise.resolve();
|
|
// happy-dom microtask 可能需要额外 tick
|
|
await new Promise((r) => setTimeout(r, 0));
|
|
expect(document.documentElement.classList.contains('is-theme-transitioning')).toBe(false);
|
|
expect(document.documentElement.style.getPropertyValue('--tt-x')).toBe('');
|
|
|
|
delete (document as unknown as { startViewTransition?: unknown }).startViewTransition;
|
|
});
|
|
|
|
it('reduced-motion:即使有 startViewTransition 也走降级(瞬切)', () => {
|
|
const startVT = vi.fn(() => ({
|
|
ready: Promise.resolve(),
|
|
finished: Promise.resolve(),
|
|
skipTransition: () => {},
|
|
}));
|
|
Object.defineProperty(document, 'startViewTransition', {
|
|
value: startVT,
|
|
configurable: true,
|
|
writable: true,
|
|
});
|
|
vi.stubGlobal(
|
|
'matchMedia',
|
|
vi.fn((q: string) => ({
|
|
matches: q.includes('reduce'),
|
|
media: q,
|
|
onchange: null,
|
|
addEventListener: () => {},
|
|
removeEventListener: () => {},
|
|
addListener: () => {},
|
|
removeListener: () => {},
|
|
dispatchEvent: () => false,
|
|
})),
|
|
);
|
|
|
|
window.__startThemeTransition(0, 0);
|
|
|
|
expect(startVT).not.toHaveBeenCalled();
|
|
expect(document.documentElement.classList.contains('dark')).toBe(true);
|
|
|
|
delete (document as unknown as { startViewTransition?: unknown }).startViewTransition;
|
|
});
|
|
});
|