Skip to content

test: add E2E and Storybook coverage for SPEC.md gaps#159

Merged
ryota-murakami merged 3 commits intomainfrom
test/e2e-storybook-coverage-gaps
Feb 27, 2026
Merged

test: add E2E and Storybook coverage for SPEC.md gaps#159
ryota-murakami merged 3 commits intomainfrom
test/e2e-storybook-coverage-gaps

Conversation

@ryota-murakami
Copy link
Contributor

@ryota-murakami ryota-murakami commented Feb 27, 2026

Summary

  • Add 6 new E2E spec files (~30 tests) covering previously untested SPEC.md features: public board access, account profile/stats, command palette, keyboard shortcuts, maintenance CRUD, and maintenance view toggle
  • Add 2 new Storybook story files (~8 stories) for AccountClient and PublicBoardClient components
  • Fix 3 existing flaky tests in add-repository-pagination.spec.ts by adding resetRepoCards() in beforeEach for proper test isolation
  • Add db-query helpers (resetBoardPublicState, resetMaintenanceItems) and update seed.sql with public board + maintenance seed data

New E2E Files

File Tests Coverage
public-board-happy-path.spec.ts 5 Public board read-only access, invalid slug
account-profile.spec.ts 4 Account page, stats, deletion flow
command-palette.spec.ts 5 Cmd+K open/close, search, navigation
keyboard-shortcuts.spec.ts 5 ? help, . overflow, Enter open card
maintenance-crud.spec.ts 5 Maintenance list, delete, GitHub links
maintenance-view-toggle.spec.ts 3 Grid/List toggle, persistence

New Storybook Stories

File Stories
AccountClient.stories.tsx 4 (default, delete dialog, loading, empty)
PublicBoardClient.stories.tsx 4 (default, many cards, not found, empty)

Existing Test Fixes

  • add-repository-pagination.spec.ts: Added resetRepoCards() in beforeEach to ensure combobox shows available repos (previously failed due to leftover cards from prior tests)

Test plan

  • pnpm lint — 0 warnings, 0 errors
  • pnpm typecheck — 0 errors
  • pnpm test — 1290 passed
  • pnpm build — success
  • pnpm e2e:parallel — 402 passed, 0 failed (12/12 shards)

Summary by CodeRabbit

  • New Features
    • Move cards between boards
    • Public board sharing with a read-only view for unauthenticated visitors
    • Account profile page with management and deletion flows
    • Keyboard shortcuts for faster navigation (?, ., Enter)
    • Command palette for quick access (Cmd/Ctrl+K)
    • Maintenance mode with grid/list view toggle and search

…tate

- Fix all path references to use src/ prefix (app/ → src/app/, lib/ → src/lib/)
- Add missing routes: boards/favorites, boards/new, public/[slug], (landing)
- Add missing server actions: public-board.ts, user-settings.ts
- Update MSW handlers.ts → handlers/ directory structure
- Remove withAuth() from auth guard docs (eliminated in #150)
- Add "Move to Another Board" feature to SPEC and README
- Update CDP helper list and E2E test table
- Bump SPEC version to v1.5 (2026-02-27)
- Add 6 new E2E spec files: public-board, account-profile, command-palette,
  keyboard-shortcuts, maintenance-crud, maintenance-view-toggle
- Add 2 Storybook story files: AccountClient, PublicBoardClient
- Fix existing add-repository-pagination tests (test isolation via resetRepoCards)
- Add db-query helpers: resetBoardPublicState, resetMaintenanceItems
- Update seed.sql with public board and maintenance seed data
@vercel
Copy link
Contributor

vercel bot commented Feb 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gitbox Ready Ready Preview, Comment Feb 27, 2026 0:25am

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

📝 Walkthrough

Walkthrough

Moves project code from lib/ to src/, reorganizes MSW/mocks and proxy paths, adds public action modules and Storybook stories, updates seed data for public boards, and introduces multiple new E2E test suites and test helpers for maintenance and public-board flows.

Changes

Cohort / File(s) Summary
Project structure & MSW
CLAUDE.md, SPEC.md, src/..., src/lib/..., src/app/..., mocks/...
Relocated code and docs references from lib/ and top-level app/ to src/ paths; reorganized MSW handlers into mocks/; updated middleware → proxy.ts references.
Public actions & API path updates
src/lib/actions/...
Added new public action modules (public-board.ts, user-settings.ts) and moved auth-guard/types (ActionResult) to src/lib/actions/* without signature changes.
E2E test helpers & seed data
e2e/helpers/db-query.ts, supabase/seed.sql
Added MAINTENANCE_IDS, PUBLIC_BOARD_SLUG, extended PROJECT_INFO_IDS, and new helpers resetBoardPublicState() and resetMaintenanceItems(); updated seed to include is_public/share_slug.
E2E tests (authenticated)
e2e/logged-in/account-profile.spec.ts, e2e/logged-in/command-palette.spec.ts, e2e/logged-in/keyboard-shortcuts.spec.ts, e2e/logged-in/maintenance-crud.spec.ts, e2e/logged-in/maintenance-view-toggle.spec.ts, e2e/logged-in/add-repository-pagination.spec.ts
Added multiple authenticated test suites covering account profile, command palette, keyboard shortcuts, maintenance CRUD/view toggling, and refactored repository pagination tests to use reset helpers and consistent locators.
E2E tests (unauthenticated / public)
e2e/unauthenticated/public-board-happy-path.spec.ts
New unauthenticated public-board smoke/happy-path tests verifying read-only UI, absence of edit controls, footer links, and metadata.
Storybook stories
src/app/account/AccountClient.stories.tsx, src/app/public/[slug]/PublicBoardClient.stories.tsx
Added stories for AccountClient and PublicBoardClient (Default, variants) with play functions asserting UI text and elements.
Docs / README
README.md, CLAUDE.md, SPEC.md
Updated documentation paths and wording to reflect src/ layout; added "Move cards between boards" to feature list.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

📦 From lib/ to src/ the files parade,
Tests and stories in neat rows laid,
Public boards now share their light,
Maintenance guards keep things polite,
One tidy refactor—code cascade ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding E2E and Storybook coverage for gaps in SPEC.md.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch test/e2e-storybook-coverage-gaps

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (6)
e2e/logged-in/keyboard-shortcuts.spec.ts (1)

79-82: Replace fixed timeout with explicit wait.

waitForTimeout(300) is a flaky pattern. Prefer waiting for a specific condition.

💡 Suggested fix
     // Dismiss any open modals first
     await page.keyboard.press('Escape')
-    await page.waitForTimeout(300)
+    // Wait for any modal to close before proceeding
+    await page.waitForLoadState('networkidle')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/logged-in/keyboard-shortcuts.spec.ts` around lines 79 - 82, After
pressing Escape with page.keyboard.press('Escape'), remove the flaky await
page.waitForTimeout(300) and replace it with an explicit wait for the modal to
be dismissed — e.g. identify the modal/dialog selector used in the app (such as
'[role="dialog"]', '.modal', or '.modal-backdrop') and call await
page.waitForSelector('<modal-selector>', { state: 'hidden' }) or use await
page.locator('<modal-selector>').waitFor({ state: 'hidden' }) so the test
proceeds only after the modal is gone.
e2e/logged-in/account-profile.spec.ts (1)

50-59: CSS class selector is fragile.

.text-2xl.font-bold ties the test to Tailwind utility classes. If the design changes, this test will fail even though the functionality works. Consider adding data-testid attributes to stat values in the component.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/logged-in/account-profile.spec.ts` around lines 50 - 59, The test is
using the fragile Tailwind selector '.text-2xl.font-bold' (statValues via
main.locator), so update the component to add stable attributes (e.g.,
data-testid="stat-value" on each stat value) and change the test to locate with
main.locator('[data-testid="stat-value"]') (keep the existing count and loop
assertions against statValues.nth(i).textContent()). Ensure the data-testid
string matches exactly between component and test.
src/app/account/AccountClient.stories.tsx (1)

119-124: Consider scoping the zero-count assertion.

getAllByText('0') could match unrelated text. Scoping to the stats section would make this more resilient.

💡 Suggested refinement
     // All three stat counts should display "0"
-    const zeros = canvas.getAllByText('0')
-    expect(zeros.length).toBe(3)
+    // Stats are rendered with .text-2xl.font-bold class
+    const statValues = canvasElement.querySelectorAll('.text-2xl.font-bold')
+    expect(statValues.length).toBe(3)
+    for (const el of statValues) {
+      expect(el.textContent).toBe('0')
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/account/AccountClient.stories.tsx` around lines 119 - 124, The test
assertion using canvas.getAllByText('0') is too broad; narrow it to the stats
container so unrelated "0" text won't be matched. Update the story test to first
locate the stats section (e.g., using canvas.getByTestId('account-stats') or
canvas.getByRole/... for the stats element) and then call
within(statsElement).getAllByText('0') and assert length === 3; if the component
lacks a stable selector, add a data-testid like "account-stats" to the
AccountClient stats container so the test can target it reliably.
e2e/unauthenticated/public-board-happy-path.spec.ts (1)

12-16: Unused import: BOARD_IDS is imported but never used.

Only PUBLIC_BOARD_SLUG and resetBoardPublicState are used in this file.

🧹 Remove unused import
 import {
   PUBLIC_BOARD_SLUG,
-  BOARD_IDS,
   resetBoardPublicState,
 } from '../helpers/db-query'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/unauthenticated/public-board-happy-path.spec.ts` around lines 12 - 16,
Remove the unused import BOARD_IDS from the import list; in the import statement
that currently reads "PUBLIC_BOARD_SLUG, BOARD_IDS, resetBoardPublicState" keep
only PUBLIC_BOARD_SLUG and resetBoardPublicState so the file imports the two
symbols actually used.
e2e/helpers/db-query.ts (1)

831-842: Hardcoded projectinfo ID should be a constant.

The ID '00000000-0000-0000-0000-000000000601' is used inline but not defined in PROJECT_INFO_IDS. For consistency with the rest of the file, consider adding it as a named constant.

🧹 Add constant for maintenance projectinfo ID
 /** Project info UUIDs */
 export const PROJECT_INFO_IDS = {
   projinfo1: '00000000-0000-0000-0000-000000000401',
   projinfo2: '00000000-0000-0000-0000-000000000402',
   projinfo3: '00000000-0000-0000-0000-000000000403',
   projinfo4: '00000000-0000-0000-0000-000000000404',
+  maintenanceProjinfo1: '00000000-0000-0000-0000-000000000601',
 } as const

Then reference it in resetMaintenanceItems:

   const { error: piError } = await supabase.from('projectinfo').upsert(
     {
-      id: '00000000-0000-0000-0000-000000000601',
+      id: PROJECT_INFO_IDS.maintenanceProjinfo1,
       maintenance_id: MAINTENANCE_IDS.maintenance1,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/helpers/db-query.ts` around lines 831 - 842, Hardcoded projectinfo ID
string in the upsert inside resetMaintenanceItems should be moved to a named
constant in PROJECT_INFO_IDS for consistency; add a new constant (e.g.
PROJECT_INFO_IDS.maintenance1ProjectInfo) alongside existing PROJECT_INFO_IDS
entries and replace the inline literal in the upsert call within
resetMaintenanceItems so the upsert uses that constant instead of the hardcoded
'00000000-0000-0000-0000-000000000601'.
e2e/logged-in/maintenance-view-toggle.spec.ts (1)

53-55: CSS selector for grid container may be brittle.

The selector .grid.gap-6 relies on specific Tailwind classes. If the styling changes, this test could break. Consider using a data-testid or checking for the grid button's aria-pressed state instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/logged-in/maintenance-view-toggle.spec.ts` around lines 53 - 55, The test
uses a brittle CSS selector via page.locator('.grid.gap-6') (bound to
gridContainer); replace this with a more resilient check — either target a
data-testid attribute on the grid container or, better, assert the grid view
toggle button's state (e.g., the grid toggle element's aria-pressed or selected
state) rather than relying on Tailwind classes; update the spec to locate the
toggle button and assert its aria-pressed value (and only fall back to a
data-testid on the container if necessary).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@e2e/helpers/db-query.ts`:
- Around line 831-842: Hardcoded projectinfo ID string in the upsert inside
resetMaintenanceItems should be moved to a named constant in PROJECT_INFO_IDS
for consistency; add a new constant (e.g.
PROJECT_INFO_IDS.maintenance1ProjectInfo) alongside existing PROJECT_INFO_IDS
entries and replace the inline literal in the upsert call within
resetMaintenanceItems so the upsert uses that constant instead of the hardcoded
'00000000-0000-0000-0000-000000000601'.

In `@e2e/logged-in/account-profile.spec.ts`:
- Around line 50-59: The test is using the fragile Tailwind selector
'.text-2xl.font-bold' (statValues via main.locator), so update the component to
add stable attributes (e.g., data-testid="stat-value" on each stat value) and
change the test to locate with main.locator('[data-testid="stat-value"]') (keep
the existing count and loop assertions against statValues.nth(i).textContent()).
Ensure the data-testid string matches exactly between component and test.

In `@e2e/logged-in/keyboard-shortcuts.spec.ts`:
- Around line 79-82: After pressing Escape with page.keyboard.press('Escape'),
remove the flaky await page.waitForTimeout(300) and replace it with an explicit
wait for the modal to be dismissed — e.g. identify the modal/dialog selector
used in the app (such as '[role="dialog"]', '.modal', or '.modal-backdrop') and
call await page.waitForSelector('<modal-selector>', { state: 'hidden' }) or use
await page.locator('<modal-selector>').waitFor({ state: 'hidden' }) so the test
proceeds only after the modal is gone.

In `@e2e/logged-in/maintenance-view-toggle.spec.ts`:
- Around line 53-55: The test uses a brittle CSS selector via
page.locator('.grid.gap-6') (bound to gridContainer); replace this with a more
resilient check — either target a data-testid attribute on the grid container
or, better, assert the grid view toggle button's state (e.g., the grid toggle
element's aria-pressed or selected state) rather than relying on Tailwind
classes; update the spec to locate the toggle button and assert its aria-pressed
value (and only fall back to a data-testid on the container if necessary).

In `@e2e/unauthenticated/public-board-happy-path.spec.ts`:
- Around line 12-16: Remove the unused import BOARD_IDS from the import list; in
the import statement that currently reads "PUBLIC_BOARD_SLUG, BOARD_IDS,
resetBoardPublicState" keep only PUBLIC_BOARD_SLUG and resetBoardPublicState so
the file imports the two symbols actually used.

In `@src/app/account/AccountClient.stories.tsx`:
- Around line 119-124: The test assertion using canvas.getAllByText('0') is too
broad; narrow it to the stats container so unrelated "0" text won't be matched.
Update the story test to first locate the stats section (e.g., using
canvas.getByTestId('account-stats') or canvas.getByRole/... for the stats
element) and then call within(statsElement).getAllByText('0') and assert length
=== 3; if the component lacks a stable selector, add a data-testid like
"account-stats" to the AccountClient stats container so the test can target it
reliably.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d0ed43a and 03f3eff.

📒 Files selected for processing (14)
  • CLAUDE.md
  • README.md
  • SPEC.md
  • e2e/helpers/db-query.ts
  • e2e/logged-in/account-profile.spec.ts
  • e2e/logged-in/add-repository-pagination.spec.ts
  • e2e/logged-in/command-palette.spec.ts
  • e2e/logged-in/keyboard-shortcuts.spec.ts
  • e2e/logged-in/maintenance-crud.spec.ts
  • e2e/logged-in/maintenance-view-toggle.spec.ts
  • e2e/unauthenticated/public-board-happy-path.spec.ts
  • src/app/account/AccountClient.stories.tsx
  • src/app/public/[slug]/PublicBoardClient.stories.tsx
  • supabase/seed.sql

@github-actions
Copy link

github-actions bot commented Feb 27, 2026

🧪 E2E Coverage Report (Sharded: 12 parallel jobs)

Metric Coverage
Lines 93.72%
Functions 18.88%
Branches 18.32%
Statements 31.65%

📊 Full report available in workflow artifacts

- Replace waitForTimeout(300) with explicit dialog wait (keyboard-shortcuts)
- Remove unused BOARD_IDS import (public-board-happy-path)
- Extract hardcoded projectinfo ID to PROJECT_INFO_IDS constant (db-query)
- Skipped: CSS class selectors (.text-2xl.font-bold, .grid.gap-6) — would
  require component changes beyond PR scope
- Skipped: getAllByText('0') scoping — MSW-mocked story has controlled data
@ryota-murakami
Copy link
Contributor Author

@coderabbitai resolve

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
e2e/logged-in/keyboard-shortcuts.spec.ts (1)

81-83: Don’t swallow assertion failures in setup steps.

At Line 81-83, .catch(() => {}) can hide real failures and make this test silently flaky.

Suggested fix
-    await expect(page.getByRole('dialog'))
-      .not.toBeVisible({ timeout: 5000 })
-      .catch(() => {})
+    await expect(page.locator('[role="dialog"]:visible')).toHaveCount(0)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/logged-in/keyboard-shortcuts.spec.ts` around lines 81 - 83, The test is
swallowing failures by appending .catch(() => {}) to the assertion on the dialog
locator (page.getByRole('dialog')), which hides real test errors; remove the
.catch(() => {}) so the await expect(page.getByRole('dialog')).not.toBeVisible({
timeout: 5000 }) can fail loudly, or if the dialog may or may not exist, replace
this pattern with an explicit conditional check using the locator's
isVisible()/count() and assert the expected state instead (e.g., await
locator.isVisible() and then expect(...) or assert count === 0) to avoid
suppressing assertion failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e/helpers/db-query.ts`:
- Around line 803-849: resetMaintenanceItems currently uses upsert which leaves
any extra maintenance rows created by prior tests; instead make the helper
deterministically reset state by first deleting all rows in the maintenance
table (e.g., supabase.from('maintenance').delete(...)) and then inserting
exactly the seedItems (use insert, not upsert) so only the expected rows exist;
likewise, delete any projectinfo rows linked to those maintenance entries (or
all projectinfo rows if appropriate) before re-inserting the specific
PROJECT_INFO_IDS. Update the logic in resetMaintenanceItems to perform
delete-then-insert for both maintenance and projectinfo (and wrap in a
transaction if supported) so tests get an exact, reproducible seed.

In `@e2e/logged-in/keyboard-shortcuts.spec.ts`:
- Around line 100-109: The negative assertion for the shortcuts modal is
performed after pressing 'Escape', risking a false pass; ensure you check that
pressing '?' does not open the shortcuts modal before closing any dialogs by
calling page.keyboard.press('?') then immediately asserting on the
shortcutsModal locator (const shortcutsModal =
page.locator('[data-testid="shortcuts-help-modal"]')) with await
expect(shortcutsModal).not.toBeVisible() and only after that press 'Escape' to
close the settings dialog, so reorder the steps to press '?' → assert
shortcutsModal not visible → press 'Escape' → assert dialog closed.

---

Nitpick comments:
In `@e2e/logged-in/keyboard-shortcuts.spec.ts`:
- Around line 81-83: The test is swallowing failures by appending .catch(() =>
{}) to the assertion on the dialog locator (page.getByRole('dialog')), which
hides real test errors; remove the .catch(() => {}) so the await
expect(page.getByRole('dialog')).not.toBeVisible({ timeout: 5000 }) can fail
loudly, or if the dialog may or may not exist, replace this pattern with an
explicit conditional check using the locator's isVisible()/count() and assert
the expected state instead (e.g., await locator.isVisible() and then expect(...)
or assert count === 0) to avoid suppressing assertion failures.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03f3eff and 92766db.

📒 Files selected for processing (3)
  • e2e/helpers/db-query.ts
  • e2e/logged-in/keyboard-shortcuts.spec.ts
  • e2e/unauthenticated/public-board-happy-path.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e/unauthenticated/public-board-happy-path.spec.ts

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

✅ Actions performed

Comments resolved. Auto-approval is disabled; enable reviews.request_changes_workflow to approve automatically.

@ryota-murakami ryota-murakami merged commit c4ae093 into main Feb 27, 2026
20 checks passed
@codecov-commenter
Copy link

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.37%. Comparing base (d0ed43a) to head (92766db).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #159      +/-   ##
==========================================
+ Coverage   69.06%   69.37%   +0.31%     
==========================================
  Files         153      155       +2     
  Lines        4636     4696      +60     
  Branches     1239     1259      +20     
==========================================
+ Hits         3202     3258      +56     
- Misses       1411     1415       +4     
  Partials       23       23              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ryota-murakami ryota-murakami deleted the test/e2e-storybook-coverage-gaps branch February 27, 2026 06:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants