Skip to content
Merged
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
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: CI

on:
pull_request:
push:
branches:
- main

concurrency:
group: quality-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
quality:
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up pnpm
uses: pnpm/action-setup@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Check formatting
run: pnpm format:check

- name: Lint
run: pnpm lint

- name: Run unit tests
run: pnpm test:run

- name: Build
run: pnpm build
6 changes: 6 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dist
coverage
node_modules
pnpm-lock.yaml
playwright-report
test-results
4 changes: 4 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"semi": false,
"singleQuote": true
}
2 changes: 2 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"recommendations": [
"streetsidesoftware.code-spell-checker",
"bradlc.vscode-tailwindcss",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"ms-playwright.playwright"
]
}
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ For the implemented product and current constraints, start with [docs/CURRENT_ST

```bash
pnpm install # install dependencies
pnpm format # apply Prettier formatting
pnpm format:check # verify formatting without changing files
pnpm lint # run ESLint
pnpm lint:fix # run ESLint with autofixes
pnpm check # run formatting, lint, tests, and build
pnpm dev # start dev server (http://localhost:5173)
pnpm build # production build
pnpm preview # preview production build
Expand Down Expand Up @@ -69,7 +74,12 @@ The dev server runs at [http://localhost:5173](http://localhost:5173) by default

## Testing

`pnpm test:run` executes the current unit and component suite. The repository does not currently include Playwright or any E2E test setup.
`pnpm test:run` executes the current unit and component suite. `pnpm test:e2e` runs the Playwright smoke test in `e2e/`.

## CI and quality gates

GitHub Actions runs formatting, linting, tests, and build checks for pull requests and pushes to `main`.
To block merges until those checks pass, configure a branch protection rule or ruleset on `main` and require the `quality` status check.

## Contributor conventions

Expand Down
94 changes: 47 additions & 47 deletions docs/MVP_TASKS_TDD.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,100 +14,100 @@ Small chunks for Test-Driven Development. Each task follows **Red** (write faili

## Phase 0: Bootstrap project

| Task | What to do | TDD note |
|------|------------|----------|
| **0.1** | Create Vite + React + TypeScript project with pnpm | No test first; scaffold only. |
| **0.2** | Add TailwindCSS and basic config | No test first. |
| Task | What to do | TDD note |
| ------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| **0.1** | Create Vite + React + TypeScript project with pnpm | No test first; scaffold only. |
| **0.2** | Add TailwindCSS and basic config | No test first. |
| **0.3** | Add Vitest, jsdom, @testing-library/react, @testing-library/user-event; config for React | **Test first:** one dummy component test that renders and asserts text; then ensure Vitest runs. |
| **0.4** | Add Playwright (optional now; use later for E2E) | Can defer. |
| **0.5** | Add Shadcn/Base UI (when first needed) | Defer until we build UI components. |
| **0.4** | Add Playwright (optional now; use later for E2E) | Can defer. |
| **0.5** | Add Shadcn/Base UI (when first needed) | Defer until we build UI components. |

---

## Phase 1: Data and constants (unit tests only)

Pure data and config—no UI. We test **logic and structure** with Vitest.

| Task | What to test (write first) | What to implement |
|------|----------------------------|--------------------|
| **1.1** | `THAI_CONSONANTS` has length 44; each item has `id`, `char`, optional `name` | Define `THAI_CONSONANTS` array (e.g. in `src/data/consonants.ts`). |
| **1.2** | `THAI_VOWELS` exists; each item has `id`, `char` (or similar); count as per PRD (~32) | Define `THAI_VOWELS` in `src/data/vowels.ts`. |
| **1.3** | Grid guide options: exactly 3 (Cross, Sandwich, Thai) | `GRID_GUIDE_OPTIONS` in `src/data/sheetOptions.ts`. |
| **1.4** | Font options: 5 fonts; default is Noto Serif Thai | `FONT_OPTIONS` and default in `sheetOptions.ts`. |
| **1.5** | Font size options: Small / Medium / Large (or 18/24/32pt); default | `FONT_SIZE_OPTIONS` and default. |
| **1.6** | Paper size: at least A4, optional Letter | `PAPER_SIZE_OPTIONS` and default. |
| **1.7** | Default sheet config object: rows per character (e.g. 2), ghost copies (e.g. 3) | `DEFAULT_SHEET_CONFIG` and type `SheetConfig`. |
| Task | What to test (write first) | What to implement |
| ------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| **1.1** | `THAI_CONSONANTS` has length 44; each item has `id`, `char`, optional `name` | Define `THAI_CONSONANTS` array (e.g. in `src/data/consonants.ts`). |
| **1.2** | `THAI_VOWELS` exists; each item has `id`, `char` (or similar); count as per PRD (~32) | Define `THAI_VOWELS` in `src/data/vowels.ts`. |
| **1.3** | Grid guide options: exactly 3 (Cross, Sandwich, Thai) | `GRID_GUIDE_OPTIONS` in `src/data/sheetOptions.ts`. |
| **1.4** | Font options: 5 fonts; default is Noto Serif Thai | `FONT_OPTIONS` and default in `sheetOptions.ts`. |
| **1.5** | Font size options: Small / Medium / Large (or 18/24/32pt); default | `FONT_SIZE_OPTIONS` and default. |
| **1.6** | Paper size: at least A4, optional Letter | `PAPER_SIZE_OPTIONS` and default. |
| **1.7** | Default sheet config object: rows per character (e.g. 2), ghost copies (e.g. 3) | `DEFAULT_SHEET_CONFIG` and type `SheetConfig`. |

---

## Phase 2: Content selection (component TDD)

Selection state and UI. We test **behavior**: what the user sees and what happens when they click.

| Task | What to test (write first) | What to implement |
|------|----------------------------|--------------------|
| **2.1** | Hook or util: initial state = no consonants/vowels selected; toggle adds/removes id | `useContentSelection` or `selectionReducer` + tests. |
| **2.2** | “Select all consonants” sets all 44; “Clear consonants” sets 0 | Extend hook/util; test in isolation (unit test). |
| **2.3** | Same for vowels: Select all / Clear | Same pattern for vowels. |
| Task | What to test (write first) | What to implement |
| ------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| **2.1** | Hook or util: initial state = no consonants/vowels selected; toggle adds/removes id | `useContentSelection` or `selectionReducer` + tests. |
| **2.2** | “Select all consonants” sets all 44; “Clear consonants” sets 0 | Extend hook/util; test in isolation (unit test). |
| **2.3** | Same for vowels: Select all / Clear | Same pattern for vowels. |
| **2.4** | Component renders section “Consonants” and list of consonant items (by role or label) | `ContentSelection.tsx`: show consonants from `THAI_CONSONANTS`. |
| **2.5** | Clicking a consonant toggles selection (visual or aria state) | Wire toggle to state; assert selection count or state. |
| **2.6** | Component shows “Vowels” and vowel items; toggle works | Add vowels to `ContentSelection`. |
| **2.7** | Button “Select all” (consonants) → summary shows “44 consonants” (or equivalent) | Add Select all button; assert summary text. |
| **2.8** | Button “Clear” (consonants) → summary shows “0 consonants” | Add Clear button for consonants. |
| **2.9** | Select all / Clear for vowels; summary shows “X consonants, Y vowels selected” | Same for vowels; unified summary. |
| **2.5** | Clicking a consonant toggles selection (visual or aria state) | Wire toggle to state; assert selection count or state. |
| **2.6** | Component shows “Vowels” and vowel items; toggle works | Add vowels to `ContentSelection`. |
| **2.7** | Button “Select all” (consonants) → summary shows “44 consonants” (or equivalent) | Add Select all button; assert summary text. |
| **2.8** | Button “Clear” (consonants) → summary shows “0 consonants” | Add Clear button for consonants. |
| **2.9** | Select all / Clear for vowels; summary shows “X consonants, Y vowels selected” | Same for vowels; unified summary. |

---

## Phase 3: Sheet options form (component TDD)

Form controls for sheet configuration. Test **that controls exist and that changing them updates state**.

| Task | What to test (write first) | What to implement |
|------|----------------------------|--------------------|
| Task | What to test (write first) | What to implement |
| ------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| **3.1** | Component receives `config` and `onChange`; changing “Rows per character” calls `onChange` with new value | `SheetOptions.tsx`: number input or select for rows. |
| **3.2** | “Ghost copies per row” control; onChange with new value | Add control; test. |
| **3.3** | Paper size dropdown: options A4, Letter; onChange | Dropdown from `PAPER_SIZE_OPTIONS`. |
| **3.4** | Grid guide dropdown: 3 options; onChange | Dropdown from `GRID_GUIDE_OPTIONS`. |
| **3.5** | Font dropdown: 5 options; default selected; onChange | Font dropdown; default from constants. |
| **3.6** | Font size dropdown: Small/Medium/Large (or pt); onChange | Font size dropdown. |
| **3.2** | “Ghost copies per row” control; onChange with new value | Add control; test. |
| **3.3** | Paper size dropdown: options A4, Letter; onChange | Dropdown from `PAPER_SIZE_OPTIONS`. |
| **3.4** | Grid guide dropdown: 3 options; onChange | Dropdown from `GRID_GUIDE_OPTIONS`. |
| **3.5** | Font dropdown: 5 options; default selected; onChange | Font dropdown; default from constants. |
| **3.6** | Font size dropdown: Small/Medium/Large (or pt); onChange | Font size dropdown. |

---

## Phase 4: Preview (component TDD)

Preview area that reflects selection and options. Test **that it receives props and re-renders**, not pixel-perfect layout.

| Task | What to test (write first) | What to implement |
|------|----------------------------|--------------------|
| Task | What to test (write first) | What to implement |
| ------- | ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| **4.1** | Preview renders a region (e.g. “Preview” or role="region") and shows something when given selected consonants | `Preview.tsx`: takes `selectedConsonants`, `selectedVowels`, `config`; render placeholder. |
| **4.2** | When `selectedConsonants` changes, preview content updates (e.g. character count or first char) | Assert content reflects new selection. |
| **4.3** | When `config` (e.g. rows per character) changes, preview reflects it (e.g. more rows) | Assert preview uses config. |
| **4.4** | Preview shows correct grid guide and font (e.g. class or data attribute from config) | Apply grid and font from config. |
| **4.2** | When `selectedConsonants` changes, preview content updates (e.g. character count or first char) | Assert content reflects new selection. |
| **4.3** | When `config` (e.g. rows per character) changes, preview reflects it (e.g. more rows) | Assert preview uses config. |
| **4.4** | Preview shows correct grid guide and font (e.g. class or data attribute from config) | Apply grid and font from config. |

---

## Phase 5: Output (Print + PDF)

Buttons and handlers; mock print and PDF in tests.

| Task | What to test (write first) | What to implement |
|------|----------------------------|--------------------|
| **5.1** | “Print” and “Download PDF” buttons are present (by role or text) | `OutputActions.tsx`: two buttons. |
| **5.2** | Click “Print” calls `window.print` (mock `window.print`, assert called) | `onPrint` handler. |
| Task | What to test (write first) | What to implement |
| ------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| **5.1** | “Print” and “Download PDF” buttons are present (by role or text) | `OutputActions.tsx`: two buttons. |
| **5.2** | Click “Print” calls `window.print` (mock `window.print`, assert called) | `onPrint` handler. |
| **5.3** | Click “Download PDF” triggers download (mock PDF lib / blob; assert download or callback) | PDF generation (e.g. jsPDF or browser print-to-PDF); mock in test. |
| **5.4** | PDF content uses current selection and sheet config (integration or unit test with mock) | Pass selection + config into PDF generator. |
| **5.4** | PDF content uses current selection and sheet config (integration or unit test with mock) | Pass selection + config into PDF generator. |

---

## Phase 6: Single page and E2E

Lift state to one place; one happy-path E2E.

| Task | What to test (write first) | What to implement |
|------|----------------------------|--------------------|
| **6.1** | App page renders ContentSelection, SheetOptions, Preview, OutputActions | `App.tsx`: compose sections; state in parent or context. |
| **6.2** | Changing selection updates preview; changing options updates preview | Integration: RTL or E2E that changes controls and asserts preview. |
| **6.3** | Playwright: open app → select some consonants → change options → see preview → click Print (mock or real) | One E2E test for critical path. |
| Task | What to test (write first) | What to implement |
| ------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| **6.1** | App page renders ContentSelection, SheetOptions, Preview, OutputActions | `App.tsx`: compose sections; state in parent or context. |
| **6.2** | Changing selection updates preview; changing options updates preview | Integration: RTL or E2E that changes controls and asserts preview. |
| **6.3** | Playwright: open app → select some consonants → change options → see preview → click Print (mock or real) | One E2E test for critical path. |

---

Expand Down
Loading
Loading