Skip to content

Commit 41b1b0b

Browse files
authored
Merge branch 'main' into copilot/expand-global-search-feature
2 parents 6f9262b + 7536d9c commit 41b1b0b

100 files changed

Lines changed: 2898 additions & 2783 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
name: 'Copilot Setup Steps'
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
paths:
7+
- .github/workflows/copilot-setup-steps.yml
8+
pull_request:
9+
paths:
10+
- .github/workflows/copilot-setup-steps.yml
11+
12+
jobs:
13+
copilot-setup-steps:
14+
runs-on: ubuntu-latest
15+
timeout-minutes: 10
16+
17+
permissions:
18+
contents: read
19+
20+
steps:
21+
- name: Checkout code
22+
uses: actions/checkout@v4
23+
24+
- name: Setup Node.js
25+
uses: actions/setup-node@v4
26+
with:
27+
node-version: '24'
28+
29+
- name: Setup pnpm
30+
uses: pnpm/action-setup@v4
31+
with:
32+
version: 10.24.0
33+
34+
- name: Get pnpm store directory
35+
shell: bash
36+
run: |
37+
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
38+
39+
- name: Setup pnpm cache
40+
uses: actions/cache@v4
41+
with:
42+
path: ${{ env.STORE_PATH }}
43+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
44+
restore-keys: |
45+
${{ runner.os }}-pnpm-store-
46+
47+
- name: Install dependencies
48+
run: pnpm install --frozen-lockfile
49+
50+
- name: Install Playwright browsers
51+
run: pnpm exec playwright install --with-deps chromium

.storybook/main.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ const config: StorybookConfig = {
4444
},
4545
typescript: {
4646
check: false,
47-
reactDocgen: 'react-docgen-typescript',
48-
reactDocgenTypescriptOptions: {
49-
shouldExtractLiteralValuesFromEnum: true,
50-
propFilter: (prop) =>
51-
prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
47+
reactDocgen: 'react-docgen',
48+
},
49+
build: {
50+
test: {
51+
disabledAddons: ['@storybook/addon-docs'],
5252
},
5353
},
5454
core: {

AGENTS.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,42 @@ The project uses Chromatic for visual regression testing. PRs automatically trig
180180
2. PRs will show visual diff status checks
181181
3. Changes to main are auto-accepted as the new baseline
182182

183+
**Deterministic Date Rendering:**
184+
185+
Stories that display relative dates (e.g. "5 days ago", "Pending (14d)") must mock `globalThis.Date` in a `beforeEach` hook to pin `Date.now()` to a fixed timestamp. Without this, Chromatic detects a visual change every day as the rendered text shifts. Use the pattern from `PaymentDetailsModal.stories.tsx`:
186+
187+
```typescript
188+
const FIXED_NOW = new Date('2026-02-15T12:00:00Z')
189+
190+
const meta = {
191+
// ...
192+
beforeEach: () => {
193+
const OriginalDate = globalThis.Date
194+
const fixedTime = FIXED_NOW.getTime()
195+
196+
const MockDate: any = function (...args: any[]) {
197+
if (args.length === 0) return new OriginalDate(fixedTime)
198+
return new (Function.prototype.bind.apply(OriginalDate, [
199+
null,
200+
...args,
201+
]) as typeof OriginalDate)()
202+
}
203+
Object.setPrototypeOf(MockDate, OriginalDate)
204+
MockDate.prototype = Object.create(OriginalDate.prototype)
205+
MockDate.now = () => fixedTime
206+
MockDate.parse = OriginalDate.parse.bind(OriginalDate)
207+
MockDate.UTC = OriginalDate.UTC.bind(OriginalDate)
208+
globalThis.Date = MockDate
209+
210+
return () => {
211+
globalThis.Date = OriginalDate
212+
}
213+
},
214+
} satisfies Meta<typeof MyComponent>
215+
```
216+
217+
This applies to any component using `formatDistanceToNow`, `getDaysPending`, or other relative date calculations.
218+
183219
### Privacy and GDPR Compliance
184220

185221
- **User Data Protection:** Always abide by GDPR regulations when handling any user data, including but not limited to:
@@ -342,3 +378,90 @@ When refactoring code:
342378
- Document any complex or non-obvious props
343379

344380
This structure ensures the codebase remains maintainable, testable, and scalable as the project grows.
381+
382+
## Testing Guidelines
383+
384+
The project uses **Vitest** with `@testing-library/react` for unit and integration tests, and **Storybook interaction tests** for component behavior.
385+
386+
### Test Philosophy
387+
388+
- **Test behavior, not implementation.** Assert on observable outcomes (return values, rendered output, side effects) rather than internal state or private methods.
389+
- **Every test must exercise real source code.** A test that only asserts on values defined within the test file itself is worthless — delete it. If a test doesn&apos;t import from `src/`, it&apos;s not testing the application.
390+
- **Prefer integration tests over isolated unit tests.** Test modules working together through their public APIs. Reserve heavy mocking for true external boundaries (network, database, third-party services).
391+
- **Tests are documentation.** A reader should understand what the code does by reading the test names and assertions.
392+
393+
### What to Test
394+
395+
- **Domain logic and data transformations** (e.g., validation functions, formatters, business rules).
396+
- **API route handlers** — test request/response contracts with realistic payloads.
397+
- **React components** — test user-visible behavior: rendering, interactions, conditional display. Use `@testing-library/react` queries (`getByRole`, `getByText`) over implementation details like CSS classes or component internals.
398+
- **Error handling paths** — verify that errors produce correct user-facing messages or status codes.
399+
- **Edge cases and boundary conditions** — empty arrays, null values, missing optional fields, maximum lengths.
400+
401+
### What NOT to Test
402+
403+
- **Type definitions and interfaces** — TypeScript already validates these at compile time.
404+
- **Simple pass-through functions** — if a function just calls another with the same arguments, the value of testing it is near zero.
405+
- **Third-party library internals** — trust that Sanity, NextAuth, tRPC work correctly. Mock them at the boundary and test your integration logic.
406+
- **Styling and layout** — use Storybook visual testing and Chromatic for visual regression instead.
407+
408+
### Writing Tests
409+
410+
```typescript
411+
// Test file location: __tests__/{path matching src/}
412+
// e.g., src/lib/proposal/validation.ts → __tests__/lib/proposal/validation.test.ts
413+
414+
// Use descriptive test names that explain the scenario and expected outcome
415+
describe('validateProposal', () => {
416+
it('should reject proposals without a title', () => { ... })
417+
it('should accept proposals with all required fields', () => { ... })
418+
})
419+
```
420+
421+
- **File naming:** `{module}.test.ts` or `{Component}.test.tsx`, mirroring the source path under `__tests__/`.
422+
- **Structure:** Use `describe` blocks to group by function or feature, `it` blocks for individual scenarios.
423+
- **Setup:** Use factory functions or test fixtures over complex `beforeEach` setup. Keep test data close to where it&apos;s used.
424+
- **Assertions:** Be specific. Prefer `toEqual` over `toBeTruthy`. Assert on error messages, not just that an error was thrown.
425+
- **Async:** Always `await` async operations. Never use `done` callbacks.
426+
427+
### Mocking
428+
429+
- **Mock at boundaries, not internally.** Mock external services (Sanity client, email provider, Slack API), not internal utility functions.
430+
- **Use `vi.mock()` for module-level mocks.** Vitest auto-hoists these, so standard ESM `import` works — no need for `require()` workarounds.
431+
- **Prefer dependency injection** where possible over module mocking.
432+
- **Avoid `Object.defineProperty` on `process.env`** — Vitest makes env properties non-configurable. Use direct assignment: `process.env.MY_VAR = 'value'`.
433+
- **Clean up mocks** with `vi.restoreAllMocks()` in `afterEach` or `beforeEach` to prevent test pollution.
434+
435+
### Environment Directives
436+
437+
Tests run in `node` environment by default. For component tests that need DOM APIs, add a docblock at the top of the file:
438+
439+
```typescript
440+
/**
441+
* @vitest-environment jsdom
442+
*/
443+
```
444+
445+
### Storybook Interaction Tests
446+
447+
For component behavior testing in context, prefer Storybook `play` functions using `storybook/test`:
448+
449+
```typescript
450+
import { expect, fn, userEvent, within } from 'storybook/test'
451+
452+
export const ClickTest: Story = {
453+
args: { onClick: fn() },
454+
play: async ({ canvasElement }) => {
455+
const canvas = within(canvasElement)
456+
await userEvent.click(canvas.getByRole('button'))
457+
},
458+
}
459+
```
460+
461+
These run in CI via `pnpm run storybook:test-ci` and complement unit tests by verifying components in a realistic rendering context.
462+
463+
### Performance
464+
465+
- **Avoid creating large data structures in tests.** Use `new Uint8Array(size)` instead of `new Array(size).fill('a').join('')` for large binary blobs — the latter is orders of magnitude slower.
466+
- **Keep tests fast.** The full suite should run in under 15 seconds. If a test needs more than 5 seconds, reconsider the approach.
467+
- **Use `vi.resetModules()` + dynamic `import()` for tests that need fresh module state** (e.g., environment variable–dependent config). Avoid `vi.isolateModules` which doesn&apos;t exist in Vitest.

__tests__/api/badge/badge-e2e.test.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { describe, it, expect } from '@jest/globals'
21
import { generateBadgeCredential } from '@/lib/badge/generator'
32
import { createTestConfiguration } from '@/lib/badge/config'
43
import { generateBadgeSVG } from '@/lib/badge/svg'

__tests__/api/cron/cleanup-orphaned-blobs.test.ts

Lines changed: 0 additions & 147 deletions
This file was deleted.

0 commit comments

Comments
 (0)