mirror of
https://github.com/nexu-io/open-design.git
synced 2026-09-20 06:15:06 +08:00
61dba52422
Follow-ups on the refreshed entry surfaces, all driven by product review of the running build. Team badge (RecentProjectsStrip / recent-projects.css) - The glyph is now remixicon `swap-2-fill` (added to the inlined path table) instead of the drawn group-of-people mark. - The badge hangs off the CARD, not the cover. The cover scales 1.03 on hover, so a badge nested in it grew and slid outward at the very moment it faded in, landing flush with the card edge while ⋯ stayed 8px in. It now shares the ⋯ anchor's positioned ancestor and its 8px inset, so both hover affordances sit on one line, one per edge. - Restyled to match ⋯: 26px tall, plain panel ground, accent-coloured 12px label, 13px glyph, no stroke and no green wash. Tokens rather than literal hex, so the chip still inverts under the dark theme instead of sitting white beside a dark ⋯. Community card byline (CommunityView / CommunityTemplatePreview) - Cards carry who published the template again, from the catalogue only: a manifest author wins, otherwise the SOURCE answers it — bundled and marketplace records are Open Design's, a locally authored plugin is the signed-in person's own. No invented handles, and the view/remix counts that were removed with the placeholder byline stay out until a source exists. Onboarding "local agent" step (EntryShell / entry-layout.css / AgentIcon) - Continue moves up beside Back on one row above the panel, one control per edge, instead of sitting alone under a panel whose height changes with the chosen runtime. - The CLI grid goes 2 → 3 per row, and BYOK closes it on a row of its own. The span is CSS; the panel also sorts BYOK last, so the scan order can never leave a hole above it. - Chips left-align their content. The button primitive centers it, which left every logo floating mid-tile instead of leading its name. - BYOK wears a key glyph (`key-2-fill`) rather than the initial-letter tile the fallback gave it: there is no vendor behind it — the runtime is whatever provider key the user brings. Account menu (EntryNavRail / analytics contract / locales) - The account menu gains a 账单 row that opens the workspace billing console, tracked as its own element (distinct from the hover panel's credits row), with `entry.accountBilling` / `entry.accountGithubHelp` across the locale files and a test covering the row. Also carried along: rail recent-row, run-status icon and home-hero spacing tweaks, and the e2e AMR/visual-navigation helper updates that go with them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019DPHofFYG9h3DgjgCDWLYh
178 lines
7.4 KiB
TypeScript
178 lines
7.4 KiB
TypeScript
import { expect, test } from '@/playwright/suite';
|
|
import { ensureRailOpen } from '@/playwright/rail';
|
|
import { openSettingsDialog } from '@/playwright/amr';
|
|
import {
|
|
captureVisual,
|
|
configureVisualPage,
|
|
gotoVisualHome,
|
|
waitForVisualFonts,
|
|
} from '@/playwright/visual';
|
|
|
|
test('[P2] captures the projects page surface', async ({ page }) => {
|
|
await configureVisualPage(page);
|
|
await gotoVisualHome(page);
|
|
|
|
await ensureRailOpen(page);
|
|
const legacyProjectsNav = page.getByTestId('entry-nav-projects');
|
|
const teamProjectsNav = page.getByTestId('entry-nav-all-projects');
|
|
if (await legacyProjectsNav.isVisible().catch(() => false)) {
|
|
await legacyProjectsNav.click();
|
|
await expect(page).toHaveURL(/\/projects$/);
|
|
const projects = page.getByTestId('entry-view-projects');
|
|
await expect(projects.getByRole('heading', { name: 'Projects' })).toBeVisible();
|
|
await expect(projects.getByText('Launchpad dashboard').first()).toBeVisible();
|
|
} else {
|
|
if (await teamProjectsNav.isVisible().catch(() => false)) {
|
|
await teamProjectsNav.click();
|
|
await expect(page.getByRole('heading', { name: /all projects|全部项目/i })).toBeVisible();
|
|
} else {
|
|
await page.getByTestId('entry-nav-drafts').click();
|
|
await expect(page.getByTestId('recent-projects-strip')).toBeVisible();
|
|
await expect(page.getByText('Launchpad dashboard').first()).toBeVisible();
|
|
}
|
|
}
|
|
await waitForVisualFonts(page);
|
|
|
|
await captureVisual(page, 'visual-projects');
|
|
});
|
|
|
|
test('[P2] captures the projects kanban surface', async ({ page }) => {
|
|
await configureVisualPage(page);
|
|
await gotoVisualHome(page);
|
|
|
|
await ensureRailOpen(page);
|
|
const legacyProjectsNav = page.getByTestId('entry-nav-projects');
|
|
const teamProjectsNav = page.getByTestId('entry-nav-all-projects');
|
|
if (await legacyProjectsNav.isVisible().catch(() => false)) {
|
|
await legacyProjectsNav.click();
|
|
const projects = page.getByTestId('entry-view-projects');
|
|
await projects.getByTestId('designs-view-kanban').click();
|
|
await expect(projects.getByTestId('designs-view-kanban')).toHaveAttribute('aria-pressed', 'true');
|
|
await expect(projects.getByText('Launchpad dashboard').first()).toBeVisible();
|
|
} else {
|
|
if (await teamProjectsNav.isVisible().catch(() => false)) {
|
|
await teamProjectsNav.click();
|
|
await expect(page.getByRole('heading', { name: /all projects|全部项目/i })).toBeVisible();
|
|
} else {
|
|
await page.getByTestId('entry-nav-drafts').click();
|
|
await expect(page.getByTestId('recent-projects-strip')).toBeVisible();
|
|
await expect(page.getByText('Launchpad dashboard').first()).toBeVisible();
|
|
}
|
|
}
|
|
await waitForVisualFonts(page);
|
|
|
|
await captureVisual(page, 'visual-projects-kanban');
|
|
});
|
|
|
|
test('[P2] captures the design systems page surface', async ({ page }) => {
|
|
await configureVisualPage(page);
|
|
await gotoVisualHome(page);
|
|
|
|
await ensureRailOpen(page);
|
|
await page.getByTestId('entry-nav-design-systems').click();
|
|
await expect(page).toHaveURL(/\/design-systems$/);
|
|
await expect(page.getByTestId('design-systems-tab')).toBeVisible();
|
|
await page.getByRole('tab', { name: 'Official presets' }).click();
|
|
await expect(page.getByTestId('design-system-card-agentic')).toBeVisible();
|
|
await expect(page.getByTestId('design-system-card-airbnb')).toBeVisible();
|
|
await waitForVisualFonts(page);
|
|
|
|
await captureVisual(page, 'visual-design-systems');
|
|
});
|
|
|
|
test('[P2] captures the design system detail preview surface', async ({ page }) => {
|
|
await configureVisualPage(page);
|
|
await gotoVisualHome(page);
|
|
|
|
await ensureRailOpen(page);
|
|
await page.getByTestId('entry-nav-design-systems').click();
|
|
await page.getByRole('tab', { name: 'Official presets' }).click();
|
|
await page.getByTestId('design-system-card-agentic').click();
|
|
const detail = page.getByTestId('design-system-detail-agentic');
|
|
await expect(detail).toBeVisible();
|
|
await expect(detail.getByTestId('design-kit-view-agentic')).toBeVisible();
|
|
await expect(detail.getByTestId('design-kit-logo-section')).toBeVisible();
|
|
await waitForVisualFonts(page);
|
|
|
|
await captureVisual(page, 'visual-design-system-detail');
|
|
});
|
|
|
|
test('[P2] captures the plugins page surface', async ({ page }) => {
|
|
await configureVisualPage(page);
|
|
await gotoVisualHome(page);
|
|
|
|
await ensureRailOpen(page);
|
|
await page.getByTestId('entry-nav-plugins').click();
|
|
await expect(page).toHaveURL(/\/plugins$/);
|
|
const plugins = page.getByTestId('entry-view-plugins');
|
|
// The view renders `entry.navPlugins`: #5517 briefly called this surface
|
|
// 扩展/Extensions, then reverted to 插件/Plugins to match the @-mention picker.
|
|
await expect(plugins.getByRole('heading', { name: 'Plugins', exact: true })).toBeVisible();
|
|
await expect(plugins.getByTestId('plugins-tab-installed')).toBeVisible();
|
|
// The marketplace opens on the 官方 scope, fed by `/api/marketplaces` — empty
|
|
// in this harness. The fixture plugins are user-installed, so switch to 个人.
|
|
await plugins.getByTestId('plugins-tab-installed').click();
|
|
await expect(plugins.getByText('Prototype Starter').first()).toBeVisible();
|
|
await waitForVisualFonts(page);
|
|
|
|
await captureVisual(page, 'visual-plugins');
|
|
});
|
|
|
|
test('[P2] captures the integrations page surface', async ({ page }) => {
|
|
await configureVisualPage(page);
|
|
await gotoVisualHome(page);
|
|
|
|
// Navigated directly: the composer "+" menu no longer carries a connectors
|
|
// row to reach this page through.
|
|
await page.goto('/integrations', { waitUntil: 'domcontentloaded' });
|
|
await expect(page).toHaveURL(/\/integrations$/);
|
|
await expect(page.getByTestId('integrations-tab-connectors')).toHaveAttribute(
|
|
'aria-selected',
|
|
'true',
|
|
);
|
|
await expect(page.getByTestId('connector-grid-wrap')).toBeVisible();
|
|
await waitForVisualFonts(page);
|
|
|
|
await captureVisual(page, 'visual-integrations');
|
|
});
|
|
|
|
test('[P2] captures the integrations use everywhere surface', async ({ page }) => {
|
|
await configureVisualPage(page);
|
|
await gotoVisualHome(page);
|
|
|
|
const dialog = await openSettingsSection(page, 'settings-nav-execution');
|
|
await expect(dialog.getByRole('tablist', { name: /Execution mode/i })).toBeVisible();
|
|
await waitForVisualFonts(page);
|
|
|
|
await captureVisual(page, 'visual-integrations-use-everywhere');
|
|
});
|
|
|
|
test('[P2] captures the integrations MCP surface', async ({ page }) => {
|
|
await configureVisualPage(page);
|
|
await gotoVisualHome(page);
|
|
|
|
// Navigated directly: the composer "+" menu no longer carries an MCP row to
|
|
// reach this page through.
|
|
await page.goto('/integrations', { waitUntil: 'domcontentloaded' });
|
|
await page.getByTestId('integrations-tab-mcp').click();
|
|
await expect(page).toHaveURL(/\/integrations$/);
|
|
await expect(page.getByTestId('integrations-tab-mcp')).toHaveAttribute(
|
|
'aria-selected',
|
|
'true',
|
|
);
|
|
await expect(page.getByRole('heading', { name: 'External MCP servers' })).toBeVisible();
|
|
await waitForVisualFonts(page);
|
|
|
|
await captureVisual(page, 'visual-integrations-mcp');
|
|
});
|
|
|
|
async function openSettingsSection(page: import('@playwright/test').Page, testId: string) {
|
|
// #5971 deleted the rail-footer settings chip (`entry-settings-button`).
|
|
// `openSettingsDialog` owns every remaining entry point — chiefly the rail's
|
|
// own item under 插件, which now renders in both identity states — including
|
|
// the rail-open handling this used to do by hand.
|
|
const dialog = await openSettingsDialog(page);
|
|
await dialog.getByTestId(testId).click();
|
|
return dialog;
|
|
}
|