Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 4 additions & 4 deletions apps/web/e2e/layers-activation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1803,12 +1803,12 @@ test.describe('mouse header controls', () => {
};
}),
).toEqual({
height: collapsed ? 40 : 48,
height: collapsed ? 40 : 44,
paddingLeft: collapsed ? '8px' : '12px',
paddingRight: collapsed ? '8px' : '12px',
paddingTop: collapsed ? '0px' : '4px',
paddingBottom: collapsed ? '0px' : '4px',
titleTopInset: collapsed ? 6 : 10,
paddingTop: '0px',
paddingBottom: '0px',
titleTopInset: collapsed ? 6 : 8,
gap: '2px',
logoWidth: 24,
logoHeight: 24,
Expand Down
9 changes: 6 additions & 3 deletions apps/web/e2e/menu-styles.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ test('shared menus keep default metrics, states and behaviors without toolbar ov
await expect(row).toHaveCSS('font-size', '13px');
await expect(row).toHaveCSS('line-height', '20px');
await expect(row).toHaveCSS('font-weight', '400');
await expect(row).toHaveCSS('padding', '6px 8px');
await expect(row).toHaveCSS('padding', '4px 8px');
await expect(row).toHaveCSS('border-radius', '6px');
await expect(row).toHaveCSS('min-height', '32px');
await expect(row).toHaveCSS('min-height', '28px');
const panel = row.locator('xpath=ancestor::*[@data-floating-chrome][1]');
await expect(panel).toHaveCSS('padding', '6px 4px');
await expect(panel).toHaveCSS('padding', '4px');
await expect(panel).toHaveCSS('border-radius', '8px');
for (const icon of await row.locator('svg').all()) {
await expect(icon).toHaveCSS('width', '14px');
Expand All @@ -34,6 +34,7 @@ test('shared menus keep default metrics, states and behaviors without toolbar ov
await page.getByRole('button', { name: 'Select sample' }).click();
const selected = page.getByRole('option', { name: /First option/ });
await checkRow(selected);
await expect(selected).toHaveCSS('height', '28px');
await expect(selected).toHaveAttribute('aria-selected', 'true');
const label = page.getByText('New destination', { exact: true });
await expect(label).toHaveCSS('font-size', '12px');
Expand Down Expand Up @@ -72,6 +73,8 @@ test('shared menus keep default metrics, states and behaviors without toolbar ov
await page.getByRole('button', { name: 'Menu sample' }).click();
const action = page.getByRole('menuitem', { name: 'Action Ctrl+2' });
await checkRow(action);
await expect(action).toHaveCSS('height', '28px');
await expect(action.getByText('Ctrl+2')).toHaveCSS('margin-left', '8px');
await checkRow(page.getByRole('menuitem', { name: 'Link', exact: true }));
await expect(
page.getByRole('menuitem', { name: 'Link', exact: true }),
Expand Down
36 changes: 36 additions & 0 deletions apps/web/e2e/overlay-panels.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,42 @@ test('toolbar stays visible without overlap and updates while a panel remains fo
await expect(toolbar).toBeVisible();
});

test('fullscreen rail aligns its layer control with Preview header controls', async ({
page,
}) => {
await openNewCanvas(page);
await page.keyboard.press('Escape');
await createChatPreview(page);
await settlePanels(page);
const fullscreen = page.getByTestId('toggle-preview-fullscreen');
await fullscreen.click();
const rail = page.locator('[data-fullscreen-header-rail]');
const toggle = rail.getByRole('button', {
name: 'Show layers panel',
exact: true,
});
await expect(toggle).toBeVisible();
await expect(page.locator('[data-canvas-root]')).toHaveCount(0);
const left = await toggle.boundingBox();
const right = await fullscreen.boundingBox();
if (!left || !right)
throw new Error('Fullscreen header controls are missing');
expect(left.height).toBe(28);
expect(left.height).toBe(right.height);
expect(left.y).toBeCloseTo(right.y, 1);
const railBounds = await rail.boundingBox();
if (!railBounds) throw new Error('Fullscreen rail is missing');
expect(left.y + left.height / 2 - railBounds.y).toBe(22);
await toggle.click();
await page
.getByRole('button', { name: 'Collapse layers panel', exact: true })
.click();
await expect(toggle).toBeVisible();
await fullscreen.click();
await expect(rail).toHaveCount(0);
await expect(page.locator('[data-canvas-root]')).toBeVisible();
});

test('narrow layouts keep a full-size Canvas and bounded panels', async ({
page,
}, testInfo) => {
Expand Down
185 changes: 185 additions & 0 deletions apps/web/e2e/preview-groups.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { expect, test } from '@playwright/test';

import { openNewCanvas } from './helpers';

import type * as WorkspaceStore from '../src/store/previewWorkspace/store';

test.use({ hasTouch: false, viewport: { width: 1600, height: 1000 } });

test('empty splits persist and canvas double-clicks follow the last interacted group', async ({
page,
}) => {
await page.addInitScript(() => {
localStorage.setItem(
'huabu-sketch-tools',
JSON.stringify({ state: { inputModePreference: 'mouse' }, version: 0 }),
);
});
await openNewCanvas(page);
await page.keyboard.press('Escape');
await page.getByRole('button', { name: 'Select (S)', exact: true }).click();
const canvasId = new URL(page.url()).pathname.split('/').pop();
const positions = await page
.locator('.react-flow__viewport')
.evaluate((viewport) => {
const pane = document.querySelector('.react-flow__pane');
if (!pane) throw new Error('Canvas pane is missing');
const bounds = pane.getBoundingClientRect();
const transform = new DOMMatrix(getComputedStyle(viewport).transform);
return [180, 550].map((y) => ({
x: (250 - bounds.left - transform.e) / transform.a,
y: (y - bounds.top - transform.f) / transform.d,
}));
});
const response = await page.request.post(`/api/canvas/${canvasId}/execute`, {
data: {
commands: [
{
type: 'CREATE_NODES',
nodes: positions.map((position, index) => ({
nodeType: 'note',
data: {
label: index === 0 ? 'Alpha' : 'Beta',
content: index === 0 ? 'Alpha' : 'Beta',
},
position,
size: { width: 300, height: 200 },
})),
},
],
originator: { source: 'agent', threadId: 'e2e-preview-groups' },
},
});
expect(response.ok(), await response.text()).toBe(true);
const nodes = page.locator('.react-flow__node-note');
await expect(nodes).toHaveCount(2);
const firstId = await nodes.nth(0).getAttribute('data-id');
const secondId = await nodes.nth(1).getAttribute('data-id');
if (!firstId || !secondId) throw new Error('Notes must have stable IDs');
const snapshot = () =>
page.evaluate(async () => {
const path = '/src/store/previewWorkspace/store.ts';
const { usePreviewWorkspaceStore } = (await import(
path
)) as typeof WorkspaceStore;
return usePreviewWorkspaceStore.getState().workspace;
});

await nodes.nth(0).dblclick({ position: { x: 15, y: 15 } });
await expect(page.getByRole('tab')).toHaveCount(1);
await page
.getByRole('button', {
name: 'Split: create an empty group on the right',
exact: true,
})
.click();
const strips = page.getByRole('tablist', { name: 'Open previews' });
await expect(strips).toHaveCount(2);
await expect(strips.nth(0).getByRole('tab')).toHaveCount(1);
await expect(strips.nth(1).getByRole('tab')).toHaveCount(0);
const split = await snapshot();
expect(split.activeGroupId).toBe(split.groups[1].id);

await page.reload();
await expect(strips).toHaveCount(2);
await expect(strips.nth(1).getByRole('tab')).toHaveCount(0);
expect((await snapshot()).activeGroupId).toBe(split.groups[1].id);
await page
.getByRole('button', { name: 'Close empty group', exact: true })
.click();
await expect(strips).toHaveCount(1);
await expect(strips.getByRole('tab')).toHaveCount(1);
expect((await snapshot()).groups[0].id).toBe(split.groups[0].id);
await page
.getByRole('button', {
name: 'Split: create an empty group on the right',
exact: true,
})
.click();
await expect(strips).toHaveCount(2);
await page
.locator(`.react-flow__node[data-id="${secondId}"]`)
.dblclick({ position: { x: 15, y: 15 } });
await expect(strips.nth(1).getByRole('tab')).toHaveCount(1);
let workspace = await snapshot();
expect(workspace.tabs[workspace.groups[1].tabIds[0]].target).toMatchObject({
nodeId: secondId,
});

await page
.locator(`.react-flow__node[data-id="${firstId}"]`)
.dblclick({ position: { x: 15, y: 15 } });
workspace = await snapshot();
expect(workspace.activeGroupId).toBe(workspace.groups[0].id);
expect(Object.keys(workspace.tabs)).toHaveLength(2);
await strips.nth(1).getByRole('tab').click({ button: 'right' });
await page
.getByRole('menuitem', {
name: 'Close all tabs in this group',
exact: true,
})
.click();
await expect(strips).toHaveCount(1);
await page
.getByRole('button', {
name: 'Split: create an empty group on the right',
exact: true,
})
.click();
await expect(strips).toHaveCount(2);
await strips.nth(0).getByRole('tab').focus();
await page.keyboard.press('Space');
await expect(page.getByTestId('preview-tab-drag-overlay')).toBeVisible();
await expect(
page
.getByRole('status')
.filter({ hasText: 'was moved over droppable area' }),
).toHaveCount(1);
await page.keyboard.press('ArrowRight');
await expect(page.getByTestId('preview-tab-append-indicator')).toBeVisible();
await page.keyboard.press('Space');
await expect(strips.nth(0).getByRole('tab')).toHaveCount(0);
await expect(strips.nth(1).getByRole('tab')).toHaveCount(1);
const survivingId = (await snapshot()).groups[1].id;
const closeEmpty = page.getByRole('button', {
name: 'Close empty group',
exact: true,
});
await closeEmpty.focus();
await closeEmpty.press('Enter');
await expect(strips).toHaveCount(1);
expect((await snapshot()).groups[0].id).toBe(survivingId);
await expect(
page.getByRole('button', { name: 'Close empty group', exact: true }),
).toHaveCount(0);
await page
.getByRole('button', {
name: 'Split: create an empty group on the right',
exact: true,
})
.click();
await expect(strips).toHaveCount(2);
await expect(
page.getByRole('button', {
name: 'Move tab to the other group',
exact: true,
}),
).toHaveCount(0);
await expect(
page.getByRole('button', { name: 'Merge groups', exact: true }),
).toHaveCount(0);
await page.getByRole('tabpanel').nth(1).click();
await page
.locator(`.react-flow__node[data-id="${secondId}"]`)
.dblclick({ position: { x: 15, y: 15 } });
await expect(strips.nth(1).getByRole('tab')).toHaveCount(1);
await strips
.nth(1)
.getByRole('button', { name: /^Close / })
.click();
await expect(strips).toHaveCount(1);
await expect(strips.getByRole('tab')).toHaveCount(1);
});
Loading
Loading