Skip to content

Commit 2769890

Browse files
esmcelroyCopilot
andcommitted
chore: add testing infrastructure, CI/CD, and copilot instructions
- Add .github/copilot-instructions.md with project context and guidelines - Set up Vitest with jsdom for unit testing (imageUtils, useLocalStorage) - Set up Playwright for E2E testing (smoke tests) - Add CI workflow: lint, typecheck, unit tests, Playwright UI tests - Add GitHub Pages deploy workflow (actions/deploy-pages) - Configure vite base path for GitHub Pages (/squarify/) - Update package.json with test scripts - Update .gitignore for test artifacts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 167943a commit 2769890

14 files changed

Lines changed: 1651 additions & 70 deletions

.github/copilot-instructions.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
## Project context
2+
- Squarify: browser-based photo padding tool that normalizes aspect ratios
3+
- Fully client-side React + TypeScript app — no backend, no auth, no routing
4+
- Images never leave the user's device
5+
6+
## Tech Stack
7+
- React 19 + TypeScript
8+
- Vite 8 with `@tailwindcss/vite` (Tailwind CSS v4)
9+
- `lucide-react` for icons
10+
- `jszip` for batch ZIP download
11+
- Vitest + React Testing Library for unit/integration tests
12+
- Playwright for E2E UI tests
13+
14+
## Project Structure
15+
```
16+
src/
17+
App.tsx – Main app component (upload → process → download flow)
18+
main.tsx – React entry point
19+
types.ts – Shared TypeScript types (UploadedPhoto, PaddingSettings)
20+
index.css – Tailwind CSS entry
21+
components/
22+
PhotoUpload.tsx – Drag-and-drop / file picker upload zone
23+
PhotoGrid.tsx – Grid display of uploaded/processed photos
24+
PaddingSettingsPanel.tsx – Settings UI (color/image fill, process button)
25+
hooks/
26+
useLocalStorage.ts – Generic localStorage-backed state hook
27+
lib/
28+
imageUtils.ts – Pure image processing logic (aspect ratio, canvas padding)
29+
e2e/ – Playwright E2E test specs
30+
```
31+
32+
## Development Principles
33+
- Test-Driven Development: Red-Green-Refactor cycle
34+
- Incremental Changes: Small, testable modifications
35+
- Systematic Debugging: Use test failures as guides
36+
- Validation Before Commit: All tests pass, no lint errors
37+
38+
## Testing Scope
39+
- **Unit tests (Vitest)**: Pure logic in `lib/imageUtils.ts`, hooks, utility functions
40+
- **E2E tests (Playwright)**: Critical user journeys — upload, process, download
41+
- Keep unit tests fast and DOM-independent where possible
42+
- E2E tests use chromium only for CI speed
43+
44+
## Testing Commands
45+
```bash
46+
npm test # Run Vitest unit tests
47+
npm run test:ui # Run Playwright E2E tests
48+
npm run test:ui:install # Install Playwright browsers
49+
```
50+
51+
## Git Workflow
52+
- Use conventional commits: `feat:`, `fix:`, `chore:`, `docs:`, `test:`, etc.
53+
- Feature branches: `feature/<descriptive-name>`
54+
- Always stage all changes before committing: `git add .`
55+
- Push to the correct branch: `git push origin <branch-name>`
56+
57+
## CI/CD
58+
- GitHub Actions CI: lint, typecheck, unit tests, Playwright UI tests
59+
- GitHub Pages deployment on push to `main`
60+
- Vite `base` is configured for GitHub Pages
61+
62+
## Key Patterns
63+
- All image processing happens on `<canvas>` elements in the browser
64+
- Photos are stored as data URLs in React state (not persisted to disk/server)
65+
- Settings are persisted via `localStorage` using the `useLocalStorage` hook
66+
- The `findMaxAspectRatio` function determines the target aspect ratio from uploaded photos
67+
- `padImageToAspectRatio` creates a new canvas at the target ratio and centers the original image

.github/workflows/ci.yml

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
lint:
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
- uses: actions/setup-node@v4
20+
with:
21+
node-version: 20
22+
- uses: actions/cache@v4
23+
with:
24+
path: ~/.npm
25+
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
26+
restore-keys: |
27+
${{ runner.os }}-node-
28+
- run: npm ci
29+
- run: npm run lint
30+
31+
typecheck:
32+
runs-on: ubuntu-latest
33+
steps:
34+
- uses: actions/checkout@v4
35+
- uses: actions/setup-node@v4
36+
with:
37+
node-version: 20
38+
- uses: actions/cache@v4
39+
with:
40+
path: ~/.npm
41+
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
42+
restore-keys: |
43+
${{ runner.os }}-node-
44+
- run: npm ci
45+
- run: npx tsc --noEmit
46+
47+
test:
48+
runs-on: ubuntu-latest
49+
steps:
50+
- uses: actions/checkout@v4
51+
- uses: actions/setup-node@v4
52+
with:
53+
node-version: 20
54+
- uses: actions/cache@v4
55+
with:
56+
path: ~/.npm
57+
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
58+
restore-keys: |
59+
${{ runner.os }}-node-
60+
- run: npm ci
61+
- run: npm test
62+
- name: Upload coverage artifact
63+
if: always()
64+
uses: actions/upload-artifact@v4
65+
with:
66+
name: coverage
67+
path: coverage
68+
if-no-files-found: ignore
69+
70+
ui-test:
71+
if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref == 'refs/heads/main')
72+
runs-on: ubuntu-latest
73+
permissions:
74+
contents: read
75+
pull-requests: write
76+
steps:
77+
- uses: actions/checkout@v4
78+
- uses: actions/setup-node@v4
79+
with:
80+
node-version: 20
81+
- uses: actions/cache@v4
82+
with:
83+
path: ~/.npm
84+
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
85+
restore-keys: |
86+
${{ runner.os }}-node-
87+
- run: npm ci
88+
- name: Install Playwright browsers
89+
run: npx playwright install --with-deps chromium
90+
- name: Run UI tests
91+
id: ui-tests
92+
run: npm run test:ui
93+
continue-on-error: true
94+
- name: Upload Playwright report
95+
if: always()
96+
uses: actions/upload-artifact@v4
97+
with:
98+
name: playwright-report
99+
path: |
100+
playwright-report/
101+
test-results/
102+
if-no-files-found: ignore
103+
- name: Post Playwright results to PR
104+
if: github.event_name == 'pull_request'
105+
uses: actions/github-script@v7
106+
with:
107+
github-token: ${{ secrets.GITHUB_TOKEN }}
108+
script: |
109+
const passed = '${{ steps.ui-tests.outcome }}' === 'success';
110+
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
111+
const status = passed ? '✅ All UI tests passed' : '❌ Some UI tests failed';
112+
const body = `## 🎭 Playwright UI Test Results\n\n${status}\n\n[View full report →](${runUrl})`;
113+
await github.rest.issues.createComment({
114+
issue_number: context.issue.number,
115+
owner: context.repo.owner,
116+
repo: context.repo.repo,
117+
body,
118+
});
119+
- name: Fail job if UI tests failed
120+
if: steps.ui-tests.outcome == 'failure'
121+
run: exit 1

.github/workflows/deploy.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
permissions:
9+
contents: read
10+
pages: write
11+
id-token: write
12+
13+
concurrency:
14+
group: pages
15+
cancel-in-progress: true
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/checkout@v4
22+
- uses: actions/setup-node@v4
23+
with:
24+
node-version: 20
25+
- uses: actions/cache@v4
26+
with:
27+
path: ~/.npm
28+
key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
29+
restore-keys: |
30+
${{ runner.os }}-node-
31+
- run: npm ci
32+
- run: npm run build
33+
- uses: actions/upload-pages-artifact@v3
34+
with:
35+
path: dist
36+
37+
deploy:
38+
needs: build
39+
runs-on: ubuntu-latest
40+
environment:
41+
name: github-pages
42+
url: ${{ steps.deployment.outputs.page_url }}
43+
steps:
44+
- id: deployment
45+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,8 @@ dist
137137
# Vite logs files
138138
vite.config.js.timestamp-*
139139
vite.config.ts.timestamp-*
140+
141+
# Test artifacts
142+
test-results/
143+
playwright-report/
144+
coverage/

e2e/smoke.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { test, expect } from '@playwright/test'
2+
3+
test('app loads and shows header', async ({ page }) => {
4+
await page.goto('/')
5+
await expect(page.locator('h1')).toHaveText('Squarify')
6+
await expect(page.getByText('Pad photos to a uniform aspect ratio')).toBeVisible()
7+
})
8+
9+
test('upload zone is visible', async ({ page }) => {
10+
await page.goto('/')
11+
await expect(page.getByText('Drop photos here or click to browse')).toBeVisible()
12+
})
13+
14+
test('padding settings panel is visible', async ({ page }) => {
15+
await page.goto('/')
16+
await expect(page.getByText('Padding Settings')).toBeVisible()
17+
await expect(page.getByText('Solid Color')).toBeVisible()
18+
await expect(page.getByText('Background Image')).toBeVisible()
19+
})

0 commit comments

Comments
 (0)