Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions apps/desktop/e2e/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ export const test = base.extend<{
sandboxBoundaryWindow: Page;
readOnlyBoundaryWindow: Page;
sessionWorkbarWindow: Page;
artifactPaneWindow: Page;
botSettingsWindow: Page;
invocableSkillsWindow: Page;
settingsProjectsWindow: Page;
Expand Down Expand Up @@ -389,6 +390,17 @@ export const test = base.extend<{
use,
);
},
artifactPaneWindow: async ({}, use) => {
await withE2eWindow(
{
seed: false,
readinessSelector: '.maka-artifact-list',
e2eFixtureScenario: 'artifact-pane',
locale: 'zh',
},
use,
);
},
// Remote access: uses the e2e-fixture workspace so Settings opens on the
// channel catalog and main injects deterministic IM onboarding adapters.
// The renderer still talks through the real preload/IPC/session authority.
Expand Down
36 changes: 33 additions & 3 deletions apps/desktop/e2e/session-workbar.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
import { test, expect } from './fixtures';

test('generated files use list-to-preview navigation with a compact action menu', async ({ artifactPaneWindow: page }) => {
const pane = page.getByRole('region', { name: '生成文件预览面板' });
const list = pane.getByRole('listbox', { name: '生成文件列表' });
const notes = list.getByRole('option', { name: 'notes.md' });

await expect(list).toBeVisible();
await notes.click();

await expect(list).toHaveCount(0);
await expect(pane.getByRole('region', { name: '预览 notes.md' })).toBeVisible();
await expect(pane.getByRole('button', { name: '返回生成文件列表' })).toBeVisible();

await pane.getByRole('button', { name: 'notes.md 的更多操作' }).click();
await expect(page.getByRole('menuitem', { name: '在 Finder 中打开' })).toBeVisible();
await expect(page.getByRole('menuitem', { name: '另存为' })).toBeVisible();
await expect(page.getByRole('menuitem', { name: '复制' })).toBeVisible();
await expect(page.getByRole('menuitem', { name: '删除' })).toBeVisible();
await page.keyboard.press('Escape');

await pane.getByRole('button', { name: '返回生成文件列表' }).click();
await expect(list).toBeVisible();
await expect(list).toBeFocused();

await list.press('Enter');
await expect(list).toHaveCount(0);
await page.keyboard.press('Escape');
await expect(list).toBeVisible();
await expect(list).toBeFocused();
});

// Workbar product journey. Narrow max-height and "toggle unmounted without a
// session" are pinned in chat-shell-layout-contract (CSS + chrome actions source).
test('session tools share one user-controlled workbar that stays mounted across repeated collapse', async ({ sessionWorkbarWindow: page }) => {
Expand All @@ -13,8 +43,8 @@ test('session tools share one user-controlled workbar that stays mounted across
await expect(tabs.getByRole('tab', { name: /浏览器/ })).toHaveCount(0);
const launcher = workbar.getByRole('button', { name: '打开工作栏标签' });
await launcher.click();
await expect(page.getByRole('menuitem', { name: '文件' })).toBeEnabled();
await page.getByRole('menuitem', { name: '任务' }).click();
await expect(page.getByRole('menuitem', { name: '生成文件' })).toBeEnabled();
await page.keyboard.press('Escape');
await expect(
page
.getByLabel('活跃会话任务')
Expand Down Expand Up @@ -122,7 +152,7 @@ test('session tools share one user-controlled workbar that stays mounted across
await expect(rightWorkbar).toHaveCSS('width', '511px');

await rightWorkbar.getByRole('button', { name: '打开工作栏标签' }).click();
await page.getByRole('menuitem', { name: '文件' }).click();
await page.getByRole('menuitem', { name: '生成文件' }).click();
await expect(page.getByText('暂无生成文件')).toBeVisible();

// The record-file row is a fact about the workspace, not the session: it
Expand Down
8 changes: 4 additions & 4 deletions apps/desktop/src/main/e2e-fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,8 @@ const E2E_FIXTURE_SCENARIOS = new Set<E2eFixtureScenario>([
'turn-control-branch-orphan',
// PR-UI-RENDER-3a-smoke: registry-driven artifact preview fixtures.
// Each shares the standard chat seed + same ARTIFACT_SESSION_ID but
// writes a different single artifact so the ArtifactPane default
// selection deterministically shows the artifact we want.
// writes a different single artifact so opening the only list row reaches
// the intended preview deterministically.
'artifact-preview-image',
'artifact-preview-unsupported',
'artifact-preview-oversize',
Expand Down Expand Up @@ -462,8 +462,8 @@ function buildE2eFixtureState(fixture: E2eFixture | null): E2eFixtureState | nul
case 'artifact-errors':
// PR-UI-RENDER-3a-smoke: each preview scenario shares the same
// chat session as `artifact-pane`; only the on-disk artifact
// varies. The ArtifactPane selects records[0] by default so the
// single seeded artifact is what gets rendered.
// varies. Each list contains exactly one row so the intended preview is
// deterministic after the normal list-to-preview interaction.
case 'artifact-preview-image':
case 'artifact-preview-unsupported':
case 'artifact-preview-oversize':
Expand Down
5 changes: 2 additions & 3 deletions apps/desktop/src/main/e2e-fixture/scenarios-artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,8 @@ export function artifactMessages(now: number): StoredMessage[] {
export async function writeArtifacts(workspaceRoot: string, now: number, scenario: E2eFixtureScenario): Promise<void> {
const root = join(workspaceRoot, 'artifacts');
// PR-UI-RENDER-3a-smoke: dedicated preview scenarios get their
// own short artifact list (single artifact each) so the
// ArtifactPane default selection deterministically picks the one
// the fixture is meant to render. The `sizeBytesOverride`
// own short artifact list (single artifact each) so the normal list row
// deterministically opens the preview the fixture is meant to render. The `sizeBytesOverride`
// field bypasses the post-write `stat().size` overwrite so we
// can claim 3MB in metadata without writing 3MB to disk.
type ArtifactSpec = {
Expand Down
5 changes: 3 additions & 2 deletions apps/desktop/src/renderer/artifact-list-keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
* a11y review on PR108b: the artifact list MUST be a single tab stop with
* ArrowUp/Down navigation + Enter to focus the preview area. Without this
* helper the list would either (a) tab through every artifact row, or (b)
* miss arrow-key navigation entirely.
* miss arrow-key navigation entirely. Activation opens the selected file in
* the pane's preview state; the helper stays independent of that view model.
*
* Each helper takes only the data needed to compute the transition. The
* caller is responsible for `event.preventDefault()` so the keys don't
Expand All @@ -28,7 +29,7 @@ export type ArtifactListAction =
*
* - ArrowDown / ArrowUp / Home / End → `select` (wraps at ends; if no
* selection yet, ArrowDown starts at 0 and ArrowUp at the last item)
* - Enter / Space on a selected row → `activate` (caller focuses preview)
* - Enter / Space on a selected row → `activate` (caller opens preview)
* - Escape → `dismiss` (caller closes pane or returns focus to chat)
* - Any other key → `noop` (do not preventDefault)
*
Expand Down
Loading
Loading