mirror of
https://github.com/bretzel-app/crumbs.git
synced 2026-10-12 20:06:57 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6cd5baadb | ||
|
|
f9d29a192a | ||
|
|
dd100683c9 | ||
|
|
e0eca4228b | ||
|
|
b33d3d3f72 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
crumbs:
|
||||
image: ghcr.io/bretzel-app/crumbs:0.22.3
|
||||
image: ghcr.io/bretzel-app/crumbs:0.23.0
|
||||
container_name: crumbs
|
||||
ports:
|
||||
- "3000:3000"
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "crumbs",
|
||||
"private": true,
|
||||
"version": "0.23.0",
|
||||
"version": "0.24.0",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"author": "Bretzel (https://bretzel.app)",
|
||||
|
||||
@@ -73,9 +73,12 @@
|
||||
items.splice(afterIndex + 1, 0, newItem);
|
||||
items = [...items];
|
||||
emitChange();
|
||||
// Focus by unique ID — positional indexing breaks when checked items
|
||||
// are interleaved in items[] (e.g. reopened notes) because the DOM
|
||||
// only renders active items as inputs
|
||||
const newId = newItem.id;
|
||||
setTimeout(() => {
|
||||
const inputs = document.querySelectorAll<HTMLInputElement>('[data-testid="checklist-input"]');
|
||||
inputs[afterIndex + 1]?.focus();
|
||||
document.querySelector<HTMLInputElement>(`[data-item-id="${newId}"]`)?.focus();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@@ -99,6 +102,14 @@
|
||||
const inputs = document.querySelectorAll<HTMLInputElement>('[data-testid="checklist-input"]');
|
||||
inputs[Math.max(0, index - 1)]?.focus();
|
||||
}, 0);
|
||||
} else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
||||
const inputs = Array.from(document.querySelectorAll<HTMLInputElement>('[data-testid="checklist-input"]'));
|
||||
const current = inputs.findIndex((el) => el.dataset.itemId === id);
|
||||
const target = e.key === 'ArrowUp' ? current - 1 : current + 1;
|
||||
if (target >= 0 && target < inputs.length) {
|
||||
e.preventDefault();
|
||||
inputs[target].focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +151,7 @@
|
||||
class="flex-1 min-w-0 bg-transparent text-sm outline-none {item.checked ? 'text-[var(--text-muted)] line-through' : 'text-[var(--text)]'}"
|
||||
placeholder="List item"
|
||||
data-testid="checklist-input"
|
||||
data-item-id={item.id}
|
||||
/>
|
||||
<button
|
||||
onclick={() => removeItem(item.id)}
|
||||
|
||||
@@ -73,6 +73,62 @@ test.describe('Checklist', () => {
|
||||
await expect(page.getByTestId('checklist-input')).toHaveCount(2);
|
||||
});
|
||||
|
||||
test('Scenario: Enter key focuses new item on reopened note with checked items before active ones', async ({ authenticatedPage: page }) => {
|
||||
// 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, 'Groceries', ['Milk', 'Eggs', 'Bread', 'Butter']);
|
||||
|
||||
// Reopen and check items 1 and 2 so saved content has checked items first
|
||||
await noteCard(page, 'Groceries').click();
|
||||
await page.getByTestId('checklist-checkbox').nth(0).click(); // Milk → done
|
||||
await page.getByTestId('checklist-checkbox').nth(0).click(); // Eggs (now first active) → 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, 'Groceries').click();
|
||||
await expect(page.getByTestId('checklist-input')).toHaveCount(2); // Bread, Butter active
|
||||
|
||||
// When the user presses Enter on the first active item (Bread)
|
||||
await page.getByTestId('checklist-input').first().press('Enter');
|
||||
|
||||
// Then focus moves to the newly created empty item
|
||||
await expect(page.getByTestId('checklist-input')).toHaveCount(3);
|
||||
await expect(page.getByTestId('checklist-input').nth(1)).toBeFocused();
|
||||
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();
|
||||
|
||||
+1
-1
@@ -292,7 +292,7 @@
|
||||
<li><a href="https://github.com/bretzel-app/crumbs/blob/main/docs/DEPLOYMENT.md" target="_blank" rel="noopener">Docs</a></li>
|
||||
<li><a href="https://github.com/bretzel-app/crumbs/blob/main/LICENSE" target="_blank" rel="noopener">License</a></li>
|
||||
</ul>
|
||||
<span class="footer-version">v0.23.0</span>
|
||||
<span class="footer-version">v0.24.0</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user