Files
maka/apps/desktop/e2e/send-message.spec.ts
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

73 lines
3.4 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 { test, expect, COMPOSER_INPUT } from './fixtures';
/**
* Enter commits a candidate in a CJK IME; nothing else may act on it. Both the
* composer's send and ChatComposerInput's trigger menu read Enter, and the
* component runs its menu handling before the `onKeyDown` we pass it — so the
* guard is a native capture on the composer root that takes the key away from
* React entirely.
*/
/**
* Core chat loop: type a message, send it, see the deterministic fake backend
* stream a reply back into the transcript. Depends on the E2E seam: the
* fixture's MAKA_E2E=1 forces sessions:create onto the fake backend, and the
* seeded 'e2e' connection clears onboarding so the composer is usable.
*/
test('Enter mid-IME commits the candidate, then an ordinary send streams a reply', async ({
window: page,
}) => {
const composer = page.locator(COMPOSER_INPUT);
await composer.fill('中文草稿');
await composer.evaluate((element) => {
element.dispatchEvent(new CompositionEvent('compositionstart', { bubbles: true }));
// `isComposing: false` on purpose: only the composition we track ourselves
// can stop this one, so a passing test can't be crediting the component's
// own `nativeEvent.isComposing` check.
element.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true }),
);
element.dispatchEvent(new CompositionEvent('compositionend', { bubbles: true }));
});
// A leaked send is asynchronous, so `toHaveCount(0)` can pass before it
// lands, and a second send of the same text would hide it. Send something
// different and pin the total instead.
await composer.fill('中文草稿 已提交');
await composer.press('Enter');
await expect(page.getByRole('log').getByText(/Fake backend received: 中文草稿 已提交/)).toBeVisible();
await expect(page.getByLabel('你发送的消息')).toHaveCount(1);
// Settle before the ordinary send: an Enter during a streaming turn would
// become steering instead of a second message.
await expect(page.getByRole('button', { name: '重新生成' })).toHaveCount(1, { timeout: 20_000 });
// #1433: the deleted first-run panel had its own input, and the spec that
// covered the handoff between the two asserted this accessible name. With
// one composer left, the name is what a screen-reader user has to find the
// send target by — assert it on the path that exercises it.
await expect(composer).toHaveAttribute('aria-label', '消息输入框');
await composer.fill('hello e2e');
await composer.press('Enter');
await expect(page.getByRole('log').getByText(/Fake backend received: hello e2e/)).toBeVisible();
});