mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-18 00:46:45 +00:00
Android/mobile soft keyboards hide the Enter key or deliver it as a composing keydown that onKeydown filters out (to protect CJK candidate confirmation), so the inline sub-task draft could not be committed at all: neither Enter, tapping away, nor any on-screen control saved the text. - Add an always-visible submit button beside the input as an accessible, discoverable commit target (mouse-clickable, screen-reader labelled). Its mousedown is preventDefaulted so a desktop click keeps input focus, commits, and keeps the field open for rapid entry — mirroring the main add-task bar. On touch the tap commits via the blur path below. - Commit the draft on blur, but only on touch (isTouchActive): the natural mobile "done" gesture is tapping away, and the soft-keyboard Enter is unreliable there (#8791). Desktop keeps click-away-to-cancel — Enter and the button are the reliable commit paths — so blur no longer silently creates a task, and a user can move to the button without the draft being committed out from under them. Escape still discards on all devices. Both commit paths read the live input value, so IME/predictive-text buffering no longer strands the text. Closes #8856. Supersedes the standalone #8791 commit-on-blur branch. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
137 lines
4.8 KiB
TypeScript
137 lines
4.8 KiB
TypeScript
import { test, expect } from '../../fixtures/test.fixture';
|
|
|
|
test.describe('Subtask inline input', () => {
|
|
test('keeps the draft input focused for rapid subtask creation', async ({
|
|
page,
|
|
workViewPage,
|
|
taskPage,
|
|
}) => {
|
|
await workViewPage.waitForTaskList();
|
|
await workViewPage.addTask('Parent task');
|
|
|
|
const parentTask = taskPage.getTaskByText('Parent task');
|
|
await expect(parentTask).toBeVisible();
|
|
|
|
await parentTask.focus();
|
|
await page.keyboard.press('a');
|
|
|
|
const draftInput = parentTask.locator('.e2e-add-subtask-input');
|
|
await expect(draftInput).toBeVisible();
|
|
await expect(draftInput).toBeFocused();
|
|
|
|
await draftInput.fill('1 subtask');
|
|
await page.keyboard.press('Enter');
|
|
|
|
await expect(parentTask.locator('.sub-tasks task')).toHaveCount(1);
|
|
await expect(parentTask.locator('.sub-tasks task task-title')).toContainText([
|
|
'1 subtask',
|
|
]);
|
|
await expect(draftInput).toBeVisible();
|
|
await expect(draftInput).toBeFocused();
|
|
await expect(draftInput).toHaveValue('');
|
|
|
|
await draftInput.fill('2 subtask');
|
|
await page.keyboard.press('Enter');
|
|
|
|
await expect(parentTask.locator('.sub-tasks task')).toHaveCount(2);
|
|
await expect(parentTask.locator('.sub-tasks task task-title')).toContainText([
|
|
'1 subtask',
|
|
'2 subtask',
|
|
]);
|
|
await expect(draftInput).toBeFocused();
|
|
|
|
await page.keyboard.press('Escape');
|
|
|
|
await expect(parentTask.locator('.e2e-add-subtask-input')).toHaveCount(0);
|
|
await expect(parentTask.locator('.sub-tasks task')).toHaveCount(2);
|
|
});
|
|
|
|
test('cancels a typed draft subtask on Escape', async ({
|
|
page,
|
|
workViewPage,
|
|
taskPage,
|
|
}) => {
|
|
await workViewPage.waitForTaskList();
|
|
await workViewPage.addTask('Parent task');
|
|
|
|
const parentTask = taskPage.getTaskByText('Parent task');
|
|
await expect(parentTask).toBeVisible();
|
|
|
|
await parentTask.focus();
|
|
await page.keyboard.press('a');
|
|
|
|
const draftInput = parentTask.locator('.e2e-add-subtask-input');
|
|
await expect(draftInput).toBeFocused();
|
|
|
|
await draftInput.fill('Canceled subtask');
|
|
await page.keyboard.press('Escape');
|
|
|
|
await expect(parentTask.locator('.e2e-add-subtask-input')).toHaveCount(0);
|
|
await expect(parentTask.locator('.sub-tasks task')).toHaveCount(0);
|
|
await expect(parentTask).not.toContainText('Canceled subtask');
|
|
// Escape returns focus to the task row so keyboard navigation continues.
|
|
await expect(parentTask).toBeFocused();
|
|
});
|
|
|
|
test('returns focus to the originating subtask on Escape', async ({
|
|
page,
|
|
workViewPage,
|
|
taskPage,
|
|
}) => {
|
|
await workViewPage.waitForTaskList();
|
|
await workViewPage.addTask('Parent task');
|
|
|
|
const parentTask = taskPage.getTaskByText('Parent task');
|
|
await expect(parentTask).toBeVisible();
|
|
|
|
// Create one subtask, then close the draft input.
|
|
await parentTask.focus();
|
|
await page.keyboard.press('a');
|
|
const draftInput = parentTask.locator('.e2e-add-subtask-input');
|
|
await draftInput.fill('First subtask');
|
|
await page.keyboard.press('Enter');
|
|
await expect(parentTask.locator('.sub-tasks task')).toHaveCount(1);
|
|
await page.keyboard.press('Escape');
|
|
await expect(parentTask.locator('.e2e-add-subtask-input')).toHaveCount(0);
|
|
|
|
// Now open the draft from the subtask, then cancel: focus must return to the
|
|
// subtask it was opened from, not the parent row.
|
|
const subTask = parentTask.locator('.sub-tasks task').first();
|
|
await subTask.focus();
|
|
await page.keyboard.press('a');
|
|
await expect(parentTask.locator('.e2e-add-subtask-input')).toBeFocused();
|
|
|
|
await page.keyboard.press('Escape');
|
|
|
|
await expect(parentTask.locator('.e2e-add-subtask-input')).toHaveCount(0);
|
|
await expect(subTask).toBeFocused();
|
|
});
|
|
|
|
// On desktop (this suite runs mouse-primary Chrome), click-away cancels the
|
|
// draft — commit-on-blur is scoped to touch (#8791/#8856), where the
|
|
// soft-keyboard Enter is unreliable. Desktop commits via Enter or the button.
|
|
test('discards a typed draft when it loses focus on desktop', async ({
|
|
page,
|
|
workViewPage,
|
|
taskPage,
|
|
}) => {
|
|
await workViewPage.waitForTaskList();
|
|
await workViewPage.addTask('Parent task');
|
|
|
|
const parentTask = taskPage.getTaskByText('Parent task');
|
|
await expect(parentTask).toBeVisible();
|
|
|
|
await parentTask.focus();
|
|
await page.keyboard.press('a');
|
|
|
|
const draftInput = parentTask.locator('.e2e-add-subtask-input');
|
|
await expect(draftInput).toBeFocused();
|
|
|
|
await draftInput.fill('Blurred subtask');
|
|
await page.locator('body').click({ position: { x: 10, y: 10 } });
|
|
|
|
await expect(parentTask.locator('.e2e-add-subtask-input')).toHaveCount(0);
|
|
await expect(parentTask.locator('.sub-tasks task')).toHaveCount(0);
|
|
await expect(parentTask).not.toContainText('Blurred subtask');
|
|
});
|
|
});
|