Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components/CherryBlossom.accessibility.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import CherryBlossom from './CherryBlossom';
import type React from 'react';

vi.mock('framer-motion', () => ({
useReducedMotion: vi.fn(() => false),
motion: {
div: ({
children,
Expand Down
1 change: 1 addition & 0 deletions components/CherryBlossom.empty-fallback.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import CherryBlossom from './CherryBlossom';
// Mock framer-motion to avoid animation DOM complexity
vi.mock('framer-motion', () => {
return {
useReducedMotion: vi.fn(() => false),
motion: {
div: ({ children }: { children?: React.ReactNode }) => <div>{children}</div>,
},
Expand Down
1 change: 1 addition & 0 deletions components/CherryBlossom.error-resilience.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const motionRuntime = vi.hoisted(() => ({
}));

vi.mock('framer-motion', () => ({
useReducedMotion: vi.fn(() => false),
motion: {
div: ({ children, ...props }: React.HTMLAttributes<HTMLDivElement>) => {
if (motionRuntime.shouldThrow) {
Expand Down
1 change: 1 addition & 0 deletions components/CherryBlossom.mock-integrations.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import CherryBlossom from './CherryBlossom';
// 1. Mock standard asynchronous imports and databases using stubs
// We mock framer-motion to execute synchronously to avoid async animation timeouts
vi.mock('framer-motion', () => ({
useReducedMotion: vi.fn(() => false),
motion: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
div: ({ children, className, ...props }: any) => (
Expand Down
1 change: 1 addition & 0 deletions components/CherryBlossom.mouse-interactivity.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import '@testing-library/jest-dom';

// Mock framer-motion to keep the tests simple and stable
vi.mock('framer-motion', () => ({
useReducedMotion: vi.fn(() => false),
motion: {
div: ({
children,
Expand Down
1 change: 1 addition & 0 deletions components/CherryBlossom.responsive-breakpoints.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import '@testing-library/jest-dom';

// Mock framer-motion to inspect the props passed to the animated elements
vi.mock('framer-motion', () => ({
useReducedMotion: vi.fn(() => false),
motion: {
div: ({
children,
Expand Down
31 changes: 31 additions & 0 deletions components/CherryBlossom.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import CherryBlossom from './CherryBlossom';
import { useReducedMotion } from 'framer-motion';
import type React from 'react';
import '@testing-library/jest-dom';

// Mock framer-motion
vi.mock('framer-motion', () => ({
motion: {
Expand All @@ -15,11 +17,13 @@ vi.mock('framer-motion', () => ({
</div>
),
},
useReducedMotion: vi.fn(() => false), // default: motion is allowed
}));

describe('CherryBlossom', () => {
beforeEach(() => {
vi.restoreAllMocks();
vi.mocked(useReducedMotion).mockReturnValue(false);
});

it('renders without crashing after mount', async () => {
Expand Down Expand Up @@ -68,3 +72,30 @@ describe('CherryBlossom', () => {
expect(() => unmount()).not.toThrow();
});
});

describe('CherryBlossom - Reduced Motion Accessibility', () => {
beforeEach(() => {
vi.restoreAllMocks();
vi.mocked(useReducedMotion).mockReturnValue(false);
});

it('renders nothing when the user prefers reduced motion', async () => {
vi.mocked(useReducedMotion).mockReturnValue(true);

const { container } = render(<CherryBlossom />);

await waitFor(() => {
expect(container.firstChild).toBeNull();
});
});

it('still renders the falling petals when reduced motion is not requested', async () => {
vi.mocked(useReducedMotion).mockReturnValue(false);

const { container } = render(<CherryBlossom />);

await waitFor(() => {
expect(container.querySelector('.fixed.inset-0')).toBeInTheDocument();
});
});
});
1 change: 1 addition & 0 deletions components/CherryBlossom.theme-contrast.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import '@testing-library/jest-dom';

// Mock framer-motion to inspect the props passed to the animated elements
vi.mock('framer-motion', () => ({
useReducedMotion: vi.fn(() => false),
motion: {
div: ({
children,
Expand Down
6 changes: 5 additions & 1 deletion components/CherryBlossom.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { motion } from 'framer-motion';
import { motion, useReducedMotion } from 'framer-motion';
import { useEffect, useState } from 'react';

const generatePetals = (count: number) => {
Expand Down Expand Up @@ -32,6 +32,7 @@
export default function CherryBlossom() {
const [petals] = useState<Petal[]>(() => generatePetals(25));
const [mounted, setMounted] = useState(false);
const prefersReducedMotion = useReducedMotion();

Check failure on line 35 in components/CherryBlossom.tsx

View workflow job for this annotation

GitHub Actions / Format · Lint · Typecheck · Test

components/CherryBlossom.timezone-boundaries.test.tsx > CherryBlossom Timezone Boundaries > remains stable during daylight saving transition timestamps

Error: [vitest] No "useReducedMotion" export is defined on the "framer-motion" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("framer-motion"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ CherryBlossom components/CherryBlossom.tsx:35:32 ❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ beginWork node_modules/react-dom/cjs/react-dom-client.development.js:11778:18 ❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/react-dom/cjs/react-dom-client.development.js:17469:41

Check failure on line 35 in components/CherryBlossom.tsx

View workflow job for this annotation

GitHub Actions / Format · Lint · Typecheck · Test

components/CherryBlossom.timezone-boundaries.test.tsx > CherryBlossom Timezone Boundaries > renders consistently on leap year boundary dates

Error: [vitest] No "useReducedMotion" export is defined on the "framer-motion" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("framer-motion"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ CherryBlossom components/CherryBlossom.tsx:35:32 ❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ beginWork node_modules/react-dom/cjs/react-dom-client.development.js:11778:18 ❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/react-dom/cjs/react-dom-client.development.js:17469:41

Check failure on line 35 in components/CherryBlossom.tsx

View workflow job for this annotation

GitHub Actions / Format · Lint · Typecheck · Test

components/CherryBlossom.timezone-boundaries.test.tsx > CherryBlossom Timezone Boundaries > maintains viewport based animation coordinates across timezone boundaries

Error: [vitest] No "useReducedMotion" export is defined on the "framer-motion" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("framer-motion"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ CherryBlossom components/CherryBlossom.tsx:35:32 ❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ beginWork node_modules/react-dom/cjs/react-dom-client.development.js:11778:18 ❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/react-dom/cjs/react-dom-client.development.js:17469:41

Check failure on line 35 in components/CherryBlossom.tsx

View workflow job for this annotation

GitHub Actions / Format · Lint · Typecheck · Test

components/CherryBlossom.timezone-boundaries.test.tsx > CherryBlossom Timezone Boundaries > preserves petal rendering near EST calendar boundaries

Error: [vitest] No "useReducedMotion" export is defined on the "framer-motion" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("framer-motion"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ CherryBlossom components/CherryBlossom.tsx:35:32 ❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ beginWork node_modules/react-dom/cjs/react-dom-client.development.js:11778:18 ❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/react-dom/cjs/react-dom-client.development.js:17469:41

Check failure on line 35 in components/CherryBlossom.tsx

View workflow job for this annotation

GitHub Actions / Format · Lint · Typecheck · Test

components/CherryBlossom.timezone-boundaries.test.tsx > CherryBlossom Timezone Boundaries > renders correctly at a UTC midnight boundary

Error: [vitest] No "useReducedMotion" export is defined on the "framer-motion" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("framer-motion"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ CherryBlossom components/CherryBlossom.tsx:35:32 ❯ Object.react_stack_bottom_frame node_modules/react-dom/cjs/react-dom-client.development.js:25904:20 ❯ renderWithHooks node_modules/react-dom/cjs/react-dom-client.development.js:7662:22 ❯ updateFunctionComponent node_modules/react-dom/cjs/react-dom-client.development.js:10166:19 ❯ beginWork node_modules/react-dom/cjs/react-dom-client.development.js:11778:18 ❯ runWithFiberInDEV node_modules/react-dom/cjs/react-dom-client.development.js:874:13 ❯ performUnitOfWork node_modules/react-dom/cjs/react-dom-client.development.js:17641:22 ❯ workLoopSync node_modules/react-dom/cjs/react-dom-client.development.js:17469:41

// SSR hydration guard: the petal animations use framer-motion values derived
// from Math.random() at component initialisation time (via useState initialiser).
Expand All @@ -43,6 +44,9 @@
}, []);

if (!mounted) return null;
if (prefersReducedMotion) {
return null;
}

return (
<div className="fixed inset-0 pointer-events-none z-10 overflow-hidden">
Expand Down
14 changes: 6 additions & 8 deletions lib/validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,14 +299,12 @@ const baseStreakParamsSchema = z.object({
grace: z
.string()
.optional()
.refine(
(val) => {
if (val === undefined || val === '') return true;
return /^\d+$/.test(val) && Number(val) >= 0 && Number(val) <= 7;
},
{ message: 'grace must be an integer between 0 and 7' }
)
.transform((val) => (val === undefined || val === '' ? 1 : Number(val)))
.transform((val) => {
if (val === undefined || val === '') return 1;
const n = Number(val);
if (isNaN(n) || !Number.isInteger(n)) return 1;
return Math.min(7, Math.max(0, n));
})
.default(1),

mode: z.enum(['commits', 'loc']).catch('commits').default('commits'),
Expand Down
Loading