Skip to content
Open
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
143 changes: 143 additions & 0 deletions apps/desktop/e2e/workhub-color-filter-layout.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { expect, test } from '@playwright/test';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const e2eDir = dirname(fileURLToPath(import.meta.url));

test('message color filters stay centered just outside both message edges', async ({ page }) => {
await page.setContent(`
<div id="user" class="workhub-message workhub-work-message" data-sender="user">
<div class="chat-message-inner">
<button class="workhub-work-color-filter workhub-work-color-filter-user"></button>
<div class="maka-chat-message-bubble-user">User</div>
</div>
</div>
<div id="assistant" class="workhub-message workhub-work-message" data-sender="assistant">
<div class="chat-message-inner">
<button class="workhub-work-color-filter workhub-work-color-filter-assistant"></button>
<div class="maka-chat-message-bubble-assistant">Assistant</div>
</div>
</div>
`);
await page.addStyleTag({ path: resolve(e2eDir, '../src/renderer/maka-tokens.css') });
await page.addStyleTag({ path: resolve(e2eDir, '../src/renderer/styles/workhub.css') });
await page.addStyleTag({ content: `
#user, #assistant { width: 680px; height: 80px; margin: 32px; }
` });

const geometry = await page.evaluate(() => {
const measure = (messageId: string) => {
const message = document.querySelector<HTMLElement>(`#${messageId}`)!;
const filter = message.querySelector<HTMLElement>('.workhub-work-color-filter')!;
const messageRect = message.getBoundingClientRect();
const filterRect = filter.getBoundingClientRect();
return {
center: filterRect.left + filterRect.width / 2,
left: messageRect.left,
right: messageRect.right,
};
};
return { user: measure('user'), assistant: measure('assistant') };
});

expect(geometry.user.center - geometry.user.right).toBeCloseTo(6, 0);
expect(geometry.assistant.center - geometry.assistant.left).toBeCloseTo(-6, 0);
});

test('WorkHub anchor rail reveals one Work color across its grouped ticks', async ({ page }) => {
await page.setContent(`
<nav class="maka-prompt-rail">
<button
id="first"
class="maka-prompt-rail-tick"
data-has-accent="true"
data-group-highlighted="true"
style="--maka-prompt-rail-accent:#b91c1c"
>
<span class="maka-prompt-rail-tick-bar"></span>
</button>
<button
id="same-work"
class="maka-prompt-rail-tick"
data-has-accent="true"
data-group-highlighted="true"
style="--maka-prompt-rail-accent:#b91c1c"
><span class="maka-prompt-rail-tick-bar"></span></button>
<button
id="other-work"
class="maka-prompt-rail-tick"
data-has-accent="true"
style="--maka-prompt-rail-accent:#2563eb"
><span class="maka-prompt-rail-tick-bar"></span></button>
</nav>
<span class="maka-prompt-rail-preview" style="--maka-prompt-rail-accent:#b91c1c">
<span class="maka-prompt-rail-preview-context">登录稳定性</span>
<span class="maka-prompt-rail-preview-prompt">排查登录超时</span>
</span>
`);
await page.addStyleTag({ path: resolve(e2eDir, '../src/renderer/maka-tokens.css') });
await page.addStyleTag({ path: resolve(e2eDir, '../src/renderer/styles/prompt-rail.css') });

const tick = page.locator('#first');
const before = await tick.evaluate((element) => getComputedStyle(element).color);
await tick.hover();
await expect(tick).toHaveCSS('color', 'rgb(185, 28, 28)');
await expect(page.locator('#same-work')).toHaveCSS('color', 'rgb(185, 28, 28)');
await expect(page.locator('#other-work')).not.toHaveCSS('color', 'rgb(185, 28, 28)');
await expect(page.locator('.maka-prompt-rail-preview-context'))
.toHaveCSS('color', 'rgb(185, 28, 28)');
expect(before).not.toBe('rgb(185, 28, 28)');
});

test('WorkHub question choices make selection and motion unmistakable', async ({ page }) => {
await page.setContent(`
<div class="workhub-interaction-card" style="--workhub-accent:#b91c1c">
<fieldset>
<legend>支付回调的真实代码在哪里?</legend>
<div class="workhub-question-options">
<button id="selected" type="button" data-selected="true">
<span>在其它仓库/路径</span>
<small>请给出绝对路径</small>
</button>
<button id="idle" type="button">
<span>还没实现,先做设计评审</span>
<small>基于风险清单继续</small>
</button>
</div>
</fieldset>
</div>
`);
await page.addStyleTag({ path: resolve(e2eDir, '../src/renderer/maka-tokens.css') });
await page.addStyleTag({ path: resolve(e2eDir, '../src/renderer/styles/workhub.css') });
await page.addStyleTag({ content: `
:root {
--color-background-surface: #fff;
--color-background-subtle: #f6f6f6;
--color-border-subtle: #ddd;
--color-border-emphasized: #999;
--color-text-primary: #111;
--color-text-secondary: #555;
}
` });

const selected = page.locator('#selected');
const idle = page.locator('#idle');
const selectedBackground = await selected.evaluate((element) => getComputedStyle(element).backgroundColor);
const idleBackground = await idle.evaluate((element) => getComputedStyle(element).backgroundColor);
const indicator = await selected.evaluate((element) => {
const style = getComputedStyle(element, '::before');
return { content: style.content, opacity: style.opacity, transform: style.transform };
});

expect(selectedBackground).not.toBe(idleBackground);
expect(indicator.content).not.toBe('none');
expect(indicator.opacity).toBe('1');
expect(indicator.transform).not.toBe('matrix(0, 0, 0, 0, 0, 0)');
await expect(selected).not.toHaveCSS('transition-duration', '0s');

await page.emulateMedia({ reducedMotion: 'reduce' });
const reducedDuration = await selected.evaluate((element) =>
Number.parseFloat(getComputedStyle(element).transitionDuration),
);
expect(reducedDuration).toBeLessThanOrEqual(0.01);
});
73 changes: 73 additions & 0 deletions apps/desktop/e2e/workhub-sidebar-identity-layout.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { expect, test } from '@playwright/test';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const e2eDir = dirname(fileURLToPath(import.meta.url));

test('Session rail uses a vertical Work identity line distinct from status in both themes', async ({ page }) => {
await page.setContent(`
<aside class="maka-session-panel">
<span class="maka-session-row-signals">
<span
class="maka-session-row-work-identity"
style="--maka-session-work-identity: var(--workhub-identity-3)"
aria-hidden="true"
></span>
<span class="runtime-status"></span>
</span>
</aside>
`);
await page.addStyleTag({ path: resolve(e2eDir, '../src/renderer/maka-tokens.css') });
await page.addStyleTag({ path: resolve(e2eDir, '../src/renderer/styles/workhub.css') });
await page.addStyleTag({ path: resolve(e2eDir, '../src/renderer/styles/sidebar.css') });
await page.addStyleTag({ content: `
.runtime-status { width: 8px; height: 8px; border-radius: 50%; background: black; }
` });

const marker = page.locator('.maka-session-row-work-identity');
const signals = page.locator('.maka-session-row-signals');
await expect(marker).toHaveCSS('width', '2px');
await expect(marker).toHaveCSS('height', '14px');
await expect(marker).toHaveCSS('background-color', 'rgb(124, 58, 237)');
await expect(marker).toHaveCSS('opacity', '0.82');
await expect(signals.locator(':scope > *')).toHaveCount(2);

await page.locator('html').evaluate((element) => element.setAttribute('data-theme', 'dark'));
await expect(marker).toHaveCSS('background-color', 'rgb(167, 139, 250)');
});

test('Session composer dock exposes an equal-width WorkHub return bar', async ({ page }) => {
await page.setContent(`
<main style="width: 100vw">
<button class="maka-workhub-composer-return" aria-label="Return to WorkHub">
<span class="maka-workhub-composer-return__destination">
<svg class="maka-workhub-composer-return__arrow" viewBox="0 0 16 16" aria-hidden="true"></svg>
<span>Return to WorkHub</span>
</span>
<span class="maka-workhub-composer-return__context" aria-hidden="true">
<span class="maka-workhub-composer-return__identity"></span>
<span>maka-workhub-mainline / Review routing</span>
</span>
</button>
<div class="composer">
<div class="maka-composer-astryx">Composer</div>
</div>
</main>
`);
await page.addStyleTag({ path: resolve(e2eDir, '../src/renderer/maka-tokens.css') });
await page.addStyleTag({ path: resolve(e2eDir, '../src/renderer/styles/composer.css') });

const destination = page.getByRole('button', { name: 'Return to WorkHub' });
const composer = page.locator('.maka-composer-astryx');
await expect(destination).toHaveCSS('min-height', '32px');
await expect(destination).toHaveCSS('width', '680px');
await expect(composer).toHaveCSS('width', '680px');

await destination.hover();
await expect(destination.locator('.maka-workhub-composer-return__arrow'))
.toHaveCSS('transform', 'matrix(1, 0, 0, 1, -2, 0)');

await page.setViewportSize({ width: 480, height: 720 });
await expect(destination).toHaveCSS('width', '432px');
await expect(composer).toHaveCSS('width', '432px');
});
7 changes: 6 additions & 1 deletion apps/desktop/src/main/__tests__/notifications-policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,21 @@ it('gates native notifications through every required condition', () => {
});

it('recognizes terminal kinds and keeps distinct localized fallback copy', () => {
for (const value of ['completed', 'errored']) assert.equal(isRunNotificationKind(value), true);
for (const value of ['completed', 'errored', 'waiting_for_user']) {
assert.equal(isRunNotificationKind(value), true);
}
for (const value of ['complete', 'error', 'aborted', '', undefined, null, 1, {}]) {
assert.equal(isRunNotificationKind(value), false);
}

const completed = runNotificationCopy('completed', 'zh');
const errored = runNotificationCopy('errored', 'zh');
const waiting = runNotificationCopy('waiting_for_user', 'zh');
assert.ok(completed.title && completed.body);
assert.ok(errored.title && errored.body);
assert.ok(waiting.title && waiting.body);
assert.notEqual(completed.title, errored.title);
assert.notEqual(completed.title, waiting.title);
});

it('sanitizes renderer content, caps it, and falls back per field', () => {
Expand Down
Loading