Files
crumbs/tests/e2e/checklist.spec.ts
T
Frederic CabassutandClaude Opus 4.6 fc391d675c docs: add E2E BDD testing best practices and improve test suite
- Add docs/TESTING.md with 10 BDD testing best practices
- Add createNote() helper to fixtures to DRY up state setup
- Add createChecklistNote() helper in checklist tests
- Rename scenario titles to describe outcomes, not actions
- Replace imperative Given steps with createNote() helper
- Fix fragile conditional in tag filtering test (silent pass)
- Remove implementation details from Gherkin comments
- Improve Given/When/Then rhythm consistency across all tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 23:39:29 +01:00

127 lines
5.5 KiB
TypeScript

import { test, expect, noteCard } from './helpers/fixtures.js';
import type { Page } from '@playwright/test';
/** Create a checklist note via UI. Leaves the editor closed. */
async function createChecklistNote(page: Page, title: string, items: string[]) {
await page.getByTestId('new-note-btn').click();
await page.getByTestId('note-title-input').fill(title);
await page.getByTestId('checklist-toggle').click();
for (let i = 0; i < items.length; i++) {
await page.getByTestId('checklist-input').nth(i).fill(items[i]);
if (i < items.length - 1) {
await page.getByTestId('checklist-input').nth(i).press('Enter');
}
}
await page.getByTestId('close-editor-btn').click();
await expect(noteCard(page, title)).toBeVisible();
}
test.describe('Checklist', () => {
test('Scenario: Checklist replaces the rich text editor when enabled', async ({ authenticatedPage: page }) => {
// Given the user is creating a new note
await page.getByTestId('new-note-btn').click();
// When the user enables checklist mode
await page.getByTestId('checklist-toggle').click();
// Then the checklist component is displayed
await expect(page.getByTestId('checklist')).toBeVisible();
// And the rich text editor is hidden
await expect(page.getByTestId('tiptap-editor')).not.toBeVisible();
});
test('Scenario: Checklist item persists after closing and reopening the note', async ({ authenticatedPage: page }) => {
// Given a checklist note with an item "Buy milk" exists
await createChecklistNote(page, 'Shopping List', ['Buy milk']);
// When the user reopens the note
await noteCard(page, 'Shopping List').click();
// Then the checklist is displayed with the saved item
await expect(page.getByTestId('checklist')).toBeVisible();
await expect(page.getByTestId('checklist-input').first()).toHaveValue('Buy milk');
});
test('Scenario: Checked item moves to the done section', async ({ authenticatedPage: page }) => {
// Given a checklist note with an item "Buy milk" exists
await createChecklistNote(page, 'Tasks', ['Buy milk']);
// When the user reopens the note and checks the item
await noteCard(page, 'Tasks').click();
await page.getByTestId('checklist-checkbox').first().click();
// Then the item moves to the done section
await expect(page.getByTestId('checklist-toggle-done')).toContainText('1 done');
await page.getByTestId('close-editor-btn').click();
// And the done state persists after reopening
await noteCard(page, 'Tasks').click();
await expect(page.getByTestId('checklist-done-checkbox').first()).toBeChecked();
});
test('Scenario: Enter key adds a new checklist item', async ({ authenticatedPage: page }) => {
// Given a checklist with one item
await page.getByTestId('new-note-btn').click();
await page.getByTestId('checklist-toggle').click();
await page.getByTestId('checklist-input').first().fill('First item');
// When the user presses Enter
await page.getByTestId('checklist-input').first().press('Enter');
// Then a second checklist item appears
await expect(page.getByTestId('checklist-input')).toHaveCount(2);
});
test('Scenario: Backspace on empty item removes it from the list', async ({ authenticatedPage: page }) => {
// Given a checklist with two items where the second is empty
await page.getByTestId('new-note-btn').click();
await page.getByTestId('checklist-toggle').click();
await page.getByTestId('checklist-input').first().fill('First item');
await page.getByTestId('checklist-input').first().press('Enter');
await expect(page.getByTestId('checklist-input')).toHaveCount(2);
// When the user presses Backspace on the empty item
await page.getByTestId('checklist-input').nth(1).press('Backspace');
// Then only the first item remains
await expect(page.getByTestId('checklist-input')).toHaveCount(1);
});
test('Scenario: Completed items are separated into a done section', async ({ authenticatedPage: page }) => {
// Given a checklist note with two items
await page.getByTestId('new-note-btn').click();
await page.getByTestId('note-title-input').fill('Hide Done Test');
await page.getByTestId('checklist-toggle').click();
await page.getByTestId('checklist-input').first().fill('Done task');
await page.getByTestId('checklist-input').first().press('Enter');
await page.getByTestId('checklist-input').nth(1).fill('Pending task');
// When the user completes the first item
await page.getByTestId('checklist-checkbox').first().click();
// Then only the pending item remains in the active list
await expect(page.getByTestId('checklist-input')).toHaveCount(1);
await expect(page.getByTestId('checklist-input').first()).toHaveValue('Pending task');
// And the done section shows the completed item
await expect(page.getByTestId('checklist-toggle-done')).toContainText('1 done');
await expect(page.getByTestId('checklist-done-section')).toBeVisible();
await expect(page.getByTestId('checklist-done-section')).toContainText('Done task');
});
test('Scenario: Disabling checklist mode restores the rich text editor', async ({ authenticatedPage: page }) => {
// Given checklist mode is enabled on a new note
await page.getByTestId('new-note-btn').click();
await page.getByTestId('checklist-toggle').click();
await expect(page.getByTestId('checklist')).toBeVisible();
// When the user disables checklist mode
await page.getByTestId('checklist-toggle').click();
// Then the rich text editor is displayed again
await expect(page.getByTestId('tiptap-editor')).toBeVisible();
await expect(page.getByTestId('checklist')).not.toBeVisible();
});
});