forked from mirror/crumbs-original
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) <noreply@anthropic.com>
This commit is contained in:
committed by
Fred Cabassut
co-authored by
Claude Opus 4.6
parent
087fe8c713
commit
dee5129d59
+125
-56
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user