Files
maka/apps/desktop/e2e/composer-undo.spec.ts
T
agent-radar 7be535059e
Dependency audit / audit (push) Canceled after 0s
CI / plan (push) Canceled after 0s
Gitoxide helper admission / macos-latest (push) Canceled after 0s
Gitoxide helper admission / ubuntu-latest (push) Canceled after 0s
Gitoxide helper admission / windows-latest (push) Canceled after 0s
Runtime Host peer admission / quality (push) Canceled after 0s
Windows recovery / windows_recovery (push) Canceled after 0s
CI / heavy (push) Canceled after 0s
CI / test (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 19:05:14 +08:00

144 lines
5.0 KiB
TypeScript

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import type { Locator } from '@playwright/test';
import { expect, test, COMPOSER_INPUT } from './fixtures';
const TYPED = 'x';
const PASTED = '中文 <tag>& "quoted"\r\nhttps://example.test/path?x=1&y=2\n第二行 <>&';
const PASTED_AS_PLAIN_TEXT = PASTED.replace(/\r\n/g, '\n');
const UNDO_SHORTCUT = process.platform === 'darwin' ? 'Meta+z' : 'Control+z';
async function expectComposerText(
composer: Locator,
expected: string,
): Promise<void> {
await expect
.poll(() => composer.evaluate((element) => element.innerText.replace(/\r\n/g, '\n')))
.toBe(expected);
}
async function pastePlainText(composer: Locator, pasted: string): Promise<void> {
await composer.evaluate((element, text) => {
const transfer = new DataTransfer();
transfer.setData('text/plain', text);
element.dispatchEvent(
new ClipboardEvent('paste', {
bubbles: true,
cancelable: true,
clipboardData: transfer,
}),
);
}, pasted);
}
test('plain-text paste is undone separately from prior typing', async ({ window: page }) => {
const composer = page.locator(COMPOSER_INPUT);
await composer.click();
await page.keyboard.type(TYPED);
await expectComposerText(composer, TYPED);
await pastePlainText(composer, PASTED);
await expectComposerText(composer, `${TYPED}${PASTED_AS_PLAIN_TEXT}`);
await page.keyboard.press(UNDO_SHORTCUT);
await expectComposerText(composer, TYPED);
await page.keyboard.press(UNDO_SHORTCUT);
await expectComposerText(composer, '');
await page.keyboard.press(UNDO_SHORTCUT);
await expectComposerText(composer, '');
});
test('pasted absolute path sends on the first Enter without opening the slash menu', async ({
window: page,
}) => {
const composer = page.locator(COMPOSER_INPUT);
const pasted = '/Users/me/notes.txt';
await expect(page.locator('button.maka-workspace-picker')).toBeEnabled();
await composer.click();
await pastePlainText(composer, pasted);
await expect(composer).toHaveAttribute('aria-expanded', 'false');
await composer.press('Enter');
await expect(page.getByText(`Fake backend received: ${pasted}`)).toBeVisible();
});
test('pasted mention-looking text does not open the file menu', async ({ window: page }) => {
const composer = page.locator(COMPOSER_INPUT);
await expect(page.locator('button.maka-workspace-picker')).toBeEnabled();
await composer.click();
await pastePlainText(composer, 'review @name');
await expect(composer).toHaveAttribute('aria-expanded', 'false');
await expect(page.getByRole('listbox')).toHaveCount(0);
});
test('plain-text paste closes an existing trigger menu before the first Enter', async ({
window: page,
}) => {
const composer = page.locator(COMPOSER_INPUT);
const pasted = 'Users/me/notes.txt';
await expect(page.locator('button.maka-workspace-picker')).toBeEnabled();
await composer.click();
await composer.pressSequentially('/');
await expect(composer).toHaveAttribute('aria-expanded', 'true');
await pastePlainText(composer, pasted);
await expect(composer).toHaveAttribute('aria-expanded', 'false');
await composer.press('Enter');
await expect(page.getByText(`Fake backend received: /${pasted}`)).toBeVisible();
});
test('plain-text paste synchronizes the controlled draft once', async ({ window: page }) => {
const composer = page.locator(COMPOSER_INPUT);
await expect(page.locator('button.maka-workspace-picker')).toBeEnabled();
await page.evaluate(() => {
const storageKey = 'maka-new-task-reload-intent-v1';
sessionStorage.setItem(storageKey, JSON.stringify({ draft: '' }));
const originalSetItem = Storage.prototype.setItem;
let writes = 0;
Storage.prototype.setItem = function setItem(key: string, value: string): void {
if (key === storageKey) writes += 1;
originalSetItem.call(this, key, value);
};
Object.defineProperty(globalThis, '__makaComposerDraftWrites', {
configurable: true,
get: () => writes,
});
});
await composer.click();
await pastePlainText(composer, 'one controlled change');
const writes = await page.evaluate(
() =>
(globalThis as typeof globalThis & { __makaComposerDraftWrites?: number })
.__makaComposerDraftWrites ?? 0,
);
expect(writes).toBe(1);
});