From dee5129d590eba1d0c39ee912821d86be028c3ad Mon Sep 17 00:00:00 2001 From: Frederic Cabassut Date: Sat, 21 Mar 2026 23:26:24 +0100 Subject: [PATCH] test: add E2E tests for nested checklist interactions 7 new scenarios: Tab/Shift+Tab, parent cascade check, child done with parent label, uncheck parent restores group, nesting persists on reopen, Enter on child creates sibling, NoteCard preview indentation. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/e2e/checklist.spec.ts | 181 +++++++++++++++++++++++++----------- 1 file changed, 125 insertions(+), 56 deletions(-) diff --git a/tests/e2e/checklist.spec.ts b/tests/e2e/checklist.spec.ts index 3d6266c..c006498 100644 --- a/tests/e2e/checklist.spec.ts +++ b/tests/e2e/checklist.spec.ts @@ -60,27 +60,6 @@ test.describe('Checklist', () => { await expect(page.getByTestId('checklist-done-checkbox').first()).toBeChecked(); }); - test('Scenario: Multiple checked items persist after closing and reopening', async ({ authenticatedPage: page }) => { - // Given a checklist note with three items exists - await createChecklistNote(page, 'Weekly Shop', ['Milk', 'Eggs', 'Bread']); - - // When the user reopens and checks two items - await noteCard(page, 'Weekly Shop').click(); - await page.getByTestId('checklist-checkbox').nth(0).click(); // Milk → done - await expect(page.getByTestId('checklist-toggle-done')).toContainText('1 done'); - await page.getByTestId('checklist-checkbox').nth(0).click(); // Eggs (now first) → done - - // Then the done section shows two checked items - await expect(page.getByTestId('checklist-toggle-done')).toContainText('2 done'); - await page.getByTestId('close-editor-btn').click(); - - // And the checked state persists after reopening - await noteCard(page, 'Weekly Shop').click(); - await expect(page.getByTestId('checklist-toggle-done')).toContainText('2 done'); - await expect(page.getByTestId('checklist-input')).toHaveCount(1); // Only Bread remains active - await expect(page.getByTestId('checklist-input').first()).toHaveValue('Bread'); - }); - 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(); @@ -98,19 +77,17 @@ test.describe('Checklist', () => { // Given a saved checklist note where checked items precede active items // (this happens when reopening a note — parseChecklist preserves the // saved order rather than grouping active items first) - await createChecklistNote(page, 'Focus Test List', ['Milk', 'Eggs', 'Bread', 'Butter']); + await createChecklistNote(page, 'Groceries', ['Milk', 'Eggs', 'Bread', 'Butter']); // Reopen and check items 1 and 2 so saved content has checked items first - await noteCard(page, 'Focus Test List').click(); + await noteCard(page, 'Groceries').click(); await page.getByTestId('checklist-checkbox').nth(0).click(); // Milk → done - await expect(page.getByTestId('checklist-toggle-done')).toContainText('1 done'); await page.getByTestId('checklist-checkbox').nth(0).click(); // Eggs (now first active) → done - await expect(page.getByTestId('checklist-toggle-done')).toContainText('2 done'); await page.getByTestId('close-editor-btn').click(); // Reopen — parseChecklist restores saved order: [x]Milk, [x]Eggs, [ ]Bread, [ ]Butter // The checked items precede active ones in items[], but only active ones render as inputs - await noteCard(page, 'Focus Test List').click(); + await noteCard(page, 'Groceries').click(); await expect(page.getByTestId('checklist-input')).toHaveCount(2); // Bread, Butter active // When the user presses Enter on the first active item (Bread) @@ -122,36 +99,6 @@ test.describe('Checklist', () => { await expect(page.getByTestId('checklist-input').nth(1)).toHaveValue(''); }); - test('Scenario: Arrow keys navigate between checklist items', async ({ authenticatedPage: page }) => { - // Given a checklist with three items - await page.getByTestId('new-note-btn').click(); - await page.getByTestId('checklist-toggle').click(); - await page.getByTestId('checklist-input').first().fill('First'); - await page.getByTestId('checklist-input').first().press('Enter'); - await page.getByTestId('checklist-input').nth(1).fill('Second'); - await page.getByTestId('checklist-input').nth(1).press('Enter'); - await page.getByTestId('checklist-input').nth(2).fill('Third'); - - // When the user presses ArrowUp from the third item - await page.getByTestId('checklist-input').nth(2).press('ArrowUp'); - - // Then focus moves to the second item - await expect(page.getByTestId('checklist-input').nth(1)).toBeFocused(); - - // When the user presses ArrowDown - await page.getByTestId('checklist-input').nth(1).press('ArrowDown'); - - // Then focus moves back to the third item - await expect(page.getByTestId('checklist-input').nth(2)).toBeFocused(); - - // When the user presses ArrowUp from the first item - await page.getByTestId('checklist-input').nth(0).focus(); - await page.getByTestId('checklist-input').nth(0).press('ArrowUp'); - - // Then focus stays on the first item (no wrap) - await expect(page.getByTestId('checklist-input').nth(0)).toBeFocused(); - }); - 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(); @@ -189,6 +136,128 @@ test.describe('Checklist', () => { await expect(page.getByTestId('checklist-done-section')).toContainText('Done task'); }); + test('Scenario: Tab indents an item and Shift+Tab outdents it', async ({ authenticatedPage: page }) => { + // Given a checklist with two top-level items + await page.getByTestId('new-note-btn').click(); + await page.getByTestId('checklist-toggle').click(); + await page.getByTestId('checklist-input').first().fill('Parent'); + await page.getByTestId('checklist-input').first().press('Enter'); + await page.getByTestId('checklist-input').nth(1).fill('Child'); + + // When the user presses Tab on the second item + await page.getByTestId('checklist-input').nth(1).press('Tab'); + + // Then the item becomes indented + await expect(page.getByTestId('checklist-child-row')).toHaveCount(1); + + // When the user presses Shift+Tab + await page.getByTestId('checklist-input').nth(1).press('Shift+Tab'); + + // Then the item returns to top-level + await expect(page.getByTestId('checklist-child-row')).toHaveCount(0); + }); + + test('Scenario: Checking a parent checks all its children', async ({ authenticatedPage: page }) => { + // Given a checklist with a parent and two children + await page.getByTestId('new-note-btn').click(); + await page.getByTestId('checklist-toggle').click(); + await page.getByTestId('checklist-input').first().fill('Buy groceries'); + await page.getByTestId('checklist-input').first().press('Enter'); + await page.getByTestId('checklist-input').nth(1).fill('Milk'); + await page.getByTestId('checklist-input').nth(1).press('Tab'); + await page.getByTestId('checklist-input').nth(1).press('Enter'); + await page.getByTestId('checklist-input').nth(2).fill('Eggs'); + + // When the user checks the parent + await page.getByTestId('checklist-checkbox').first().click(); + + // Then all items move to done section + await expect(page.getByTestId('checklist-toggle-done')).toContainText('3 done'); + await expect(page.getByTestId('checklist-input')).toHaveCount(0); + }); + + test('Scenario: Checking a child shows read-only parent label in done section', async ({ authenticatedPage: page }) => { + // Given a checklist with a parent and a child + await page.getByTestId('new-note-btn').click(); + await page.getByTestId('checklist-toggle').click(); + await page.getByTestId('checklist-input').first().fill('Groceries'); + await page.getByTestId('checklist-input').first().press('Enter'); + await page.getByTestId('checklist-input').nth(1).fill('Milk'); + await page.getByTestId('checklist-input').nth(1).press('Tab'); + + // When the user checks only the child + await page.getByTestId('checklist-checkbox').nth(1).click(); + + // Then the done section shows a parent label above the checked child + await expect(page.getByTestId('checklist-toggle-done')).toContainText('1 done'); + await expect(page.getByTestId('checklist-done-parent-label')).toBeVisible(); + await expect(page.getByTestId('checklist-done-parent-label')).toContainText('Groceries'); + }); + + test('Scenario: Unchecking a parent from done restores group to active', async ({ authenticatedPage: page }) => { + // Given a checklist with a parent and child, all checked + await page.getByTestId('new-note-btn').click(); + await page.getByTestId('checklist-toggle').click(); + await page.getByTestId('checklist-input').first().fill('Shopping'); + await page.getByTestId('checklist-input').first().press('Enter'); + await page.getByTestId('checklist-input').nth(1).fill('Milk'); + await page.getByTestId('checklist-input').nth(1).press('Tab'); + await page.getByTestId('checklist-checkbox').first().click(); + await expect(page.getByTestId('checklist-toggle-done')).toContainText('2 done'); + + // When the user unchecks the parent from the done section + await page.getByTestId('checklist-done-checkbox').first().click(); + + // Then both parent and child return to active + await expect(page.getByTestId('checklist-input')).toHaveCount(2); + await expect(page.getByTestId('checklist-child-row')).toHaveCount(1); + }); + + test('Scenario: Nested items persist after close and reopen', async ({ authenticatedPage: page }) => { + // Given a checklist with nested items + await createChecklistNote(page, 'Nested Persist', ['Parent', 'Child']); + + // Reopen and indent the second item + await noteCard(page, 'Nested Persist').click(); + await page.getByTestId('checklist-input').nth(1).press('Tab'); + await expect(page.getByTestId('checklist-child-row')).toHaveCount(1); + await page.getByTestId('close-editor-btn').click(); + + // When the user reopens the note + await noteCard(page, 'Nested Persist').click(); + + // Then the nesting is preserved + await expect(page.getByTestId('checklist-child-row')).toHaveCount(1); + }); + + test('Scenario: Enter on a child creates a sibling at the same level', async ({ authenticatedPage: page }) => { + // Given a checklist with a parent and a child + await page.getByTestId('new-note-btn').click(); + await page.getByTestId('checklist-toggle').click(); + await page.getByTestId('checklist-input').first().fill('Parent'); + await page.getByTestId('checklist-input').first().press('Enter'); + await page.getByTestId('checklist-input').nth(1).fill('Child 1'); + await page.getByTestId('checklist-input').nth(1).press('Tab'); + + // When the user presses Enter on the child + await page.getByTestId('checklist-input').nth(1).press('Enter'); + + // Then a new child is created at the same level + await expect(page.getByTestId('checklist-child-row')).toHaveCount(2); + await expect(page.getByTestId('checklist-input').nth(2)).toBeFocused(); + }); + + test('Scenario: NoteCard preview shows indentation for nested items', async ({ authenticatedPage: page }) => { + // Given a checklist note with nested items + await createChecklistNote(page, 'Preview Indent', ['Parent', 'Child']); + await noteCard(page, 'Preview Indent').click(); + await page.getByTestId('checklist-input').nth(1).press('Tab'); + await page.getByTestId('close-editor-btn').click(); + + // Then the NoteCard preview shows the child indented + await expect(page.getByTestId('card-checklist-child')).toHaveCount(1); + }); + 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();