fix: 修复Woodpecker CI配置文件中的linter错误
ci/woodpecker/manual/woodpecker Pipeline was successful

- 移除未使用的YAML锚点定义
- 替换commands字段中的锚点引用为实际值
- 移除有问题的通知步骤
- 修复测试文件中的问题
- 添加新的测试用例和配置文件
This commit is contained in:
张翔
2026-03-28 09:42:45 +08:00
parent a5ee6489a1
commit ebaa7f3c50
53 changed files with 4564 additions and 818 deletions
+97
View File
@@ -0,0 +1,97 @@
import { renderHook, act } from '@testing-library/react';
import { useReducedMotion } from '@/hooks/use-reduced-motion';
describe('useReducedMotion', () => {
const originalMatchMedia = window.matchMedia;
beforeEach(() => {
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: query === '(prefers-reduced-motion: reduce)',
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
});
afterEach(() => {
window.matchMedia = originalMatchMedia;
});
it('should return false when user prefers motion', () => {
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
const { result } = renderHook(() => useReducedMotion());
expect(result.current).toBe(false);
});
it('should return true when user prefers reduced motion', () => {
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: query === '(prefers-reduced-motion: reduce)',
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
const { result } = renderHook(() => useReducedMotion());
expect(result.current).toBe(true);
});
it('should update when preference changes', () => {
const listeners: Array<(event: MediaQueryListEvent) => void> = [];
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn((event, listener) => {
if (event === 'change') {
listeners.push(listener);
}
}),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
const { result } = renderHook(() => useReducedMotion());
expect(result.current).toBe(false);
act(() => {
listeners.forEach(listener => {
listener({ matches: true } as MediaQueryListEvent);
});
});
expect(result.current).toBe(true);
});
});
+55
View File
@@ -0,0 +1,55 @@
import { useEffect, useState } from 'react';
export function useReducedMotion() {
const [shouldReduceMotion, setShouldReduceMotion] = useState(false);
useEffect(() => {
if (typeof window === 'undefined') {
return;
}
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
setShouldReduceMotion(mediaQuery.matches);
const handleChange = (event: MediaQueryListEvent) => {
setShouldReduceMotion(event.matches);
};
mediaQuery.addEventListener('change', handleChange);
return () => {
mediaQuery.removeEventListener('change', handleChange);
};
}, []);
return shouldReduceMotion;
}
export function getAnimationConfig(
shouldReduceMotion: boolean,
normalConfig: { duration?: number; delay?: number; ease?: any },
reducedConfig?: { duration?: number; delay?: number; ease?: any }
) {
if (shouldReduceMotion) {
return {
duration: reducedConfig?.duration ?? 0,
delay: reducedConfig?.delay ?? 0,
ease: reducedConfig?.ease ?? 'linear',
};
}
return normalConfig;
}
export function getAnimationVariants(
shouldReduceMotion: boolean,
normalVariants: any
) {
if (shouldReduceMotion) {
return {
initial: {},
animate: {},
exit: {},
};
}
return normalVariants;
}