Files
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

270 lines
11 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 { COMPOSER_INPUT, ensureSidebarExpanded, expect, test } from './fixtures';
interface SettingsChunkLatchWindow extends Window {
makaE2eLatch?: {
arm(key: 'settings.chunk'): void;
release(key: 'settings.chunk'): void;
};
}
async function choiceContentGeometry(card: import('@playwright/test').Locator) {
return card.evaluate((element) => {
const content = Array.from(element.children).find((child) => child.tagName !== 'INPUT');
if (!(content instanceof HTMLElement)) {
throw new Error('SelectableCard content is missing');
}
const cardRect = element.getBoundingClientRect();
const contentRect = content.getBoundingClientRect();
return {
cardHeight: cardRect.height,
contentHeight: contentRect.height,
topGap: contentRect.top - cardRect.top,
bottomGap: cardRect.bottom - contentRect.bottom,
};
});
}
test('Settings loading surface owns unmodified Escape', async ({ window: page }) => {
const latchInstalled = await page.evaluate(() => {
const e2eLatch = (window as unknown as SettingsChunkLatchWindow).makaE2eLatch;
e2eLatch?.arm('settings.chunk');
return e2eLatch !== undefined;
});
expect(latchInstalled, 'the preload E2E latch is installed').toBe(true);
try {
await ensureSidebarExpanded(page);
await page.getByRole('button', { name: '设置' }).click();
const loadingSurface = page.locator('.maka-lazy-fallback');
await expect(loadingSurface).toBeVisible();
for (const modifier of ['ctrlKey', 'metaKey', 'altKey'] as const) {
const wasNotPrevented = await page.evaluate((key) =>
window.dispatchEvent(
new KeyboardEvent('keydown', {
key: 'Escape',
[key]: true,
bubbles: true,
cancelable: true,
}),
), modifier);
expect(wasNotPrevented).toBe(true);
await expect(loadingSurface).toBeVisible();
}
const wasNotPrevented = await page.evaluate(() =>
window.dispatchEvent(
new KeyboardEvent('keydown', {
key: 'Escape',
bubbles: true,
cancelable: true,
}),
),
);
expect(wasNotPrevented).toBe(false);
await expect(page.locator('.settingsModal')).toHaveCount(0);
} finally {
await page.evaluate(() =>
(window as unknown as SettingsChunkLatchWindow).makaE2eLatch?.release(
'settings.chunk',
),
);
}
});
test('opening settings commits an active titlebar rename', async ({ window: page }) => {
const composer = page.locator(COMPOSER_INPUT);
await composer.fill('create a session for settings rename');
await composer.press('Enter');
const identity = page.locator('[data-maka-contract="titlebar-identity"]');
await expect(identity).toBeVisible();
await page.getByRole('button', { name: '展开侧边栏' }).click();
await identity.getByRole('button', { name: /重命名任务/ }).click();
await page.getByRole('textbox', { name: '重命名任务' }).fill('renamed before settings');
// Programmatic activation preserves input focus, matching the macOS
// application-menu command that opens Settings before Chromium can blur it.
await page.getByRole('button', { name: '设置' }).evaluate((button) => button.click());
await expect(page.getByRole('main', { name: '设置内容' })).toBeVisible();
await page.keyboard.press('Escape');
await expect(identity).toContainText('renamed before settings');
});
test('settings hides expanded workbar chrome and restores it on close', async ({
window: page,
}) => {
const composer = page.locator(COMPOSER_INPUT);
await composer.fill('create a session with an expanded workbar');
await composer.press('Enter');
await page.getByRole('button', { name: '展开任务工作栏' }).click();
const workbar = page.locator('.maka-session-workbar[data-placement="right"]');
const workbarToolbar = workbar.getByRole('toolbar', { name: '任务工作栏标签' });
await expect(workbarToolbar).toBeVisible();
await expect(workbarToolbar.getByRole('button', { name: '打开工作栏标签' })).toBeVisible();
await expect(workbarToolbar.getByRole('button', { name: '收起任务工作栏' })).toBeVisible();
await page
.getByRole('button', { name: /待办.*查看和维护这个任务的待办台账/ })
.click();
const taskTab = workbarToolbar.getByRole('tab', { name: '待办' });
await expect(taskTab).toBeVisible();
await ensureSidebarExpanded(page);
await page.getByRole('button', { name: '设置' }).click();
await expect(page.getByRole('main', { name: '设置内容' })).toBeVisible();
await expect(workbar).not.toBeVisible();
await page.keyboard.press('Escape');
await expect(workbarToolbar).toBeVisible();
await expect(taskTab).toBeVisible();
});
test('wide settings gutters scroll the whole main pane', async ({ window: page }) => {
await page.setViewportSize({ width: 1600, height: 520 });
await ensureSidebarExpanded(page);
await page.getByRole('button', { name: '设置' }).click();
await page.getByRole('button', { name: '通用', exact: true }).click();
await expect(page.getByRole('textbox', { name: '助手语气偏好' })).toBeEnabled();
const pane = page.locator('.settingsMainPane');
const content = pane.locator('.settingsPageStack').first();
const geometry = await pane.evaluate((element) => {
const paneRect = element.getBoundingClientRect();
const contentElement = element.querySelector('.settingsPageStack');
const contentRect = contentElement?.getBoundingClientRect();
const layoutContent = element.querySelector('.astryx-layout-content');
if (!contentRect || !layoutContent) throw new Error('Settings layout is incomplete');
return {
blankRight: paneRect.right - contentRect.right,
clientHeight: element.clientHeight,
contentOverflowY: getComputedStyle(layoutContent).overflowY,
paneOverflowY: getComputedStyle(element).overflowY,
scrollHeight: element.scrollHeight,
wheelPoint: {
x: Math.floor((contentRect.right + paneRect.right) / 2),
y: Math.floor(Math.min(contentRect.top + 120, paneRect.bottom - 40)),
},
};
});
expect(geometry.blankRight).toBeGreaterThan(40);
expect(geometry.scrollHeight).toBeGreaterThan(geometry.clientHeight);
expect(geometry.contentOverflowY).not.toBe('auto');
expect(geometry.paneOverflowY).toBe('auto');
await page.mouse.move(geometry.wheelPoint.x, geometry.wheelPoint.y);
await page.mouse.wheel(0, 600);
await expect.poll(() => pane.evaluate((element) => element.scrollTop)).toBeGreaterThan(0);
await expect(content).toBeVisible();
});
test('appearance choice content stays vertically centered in stretched grid rows', async ({ window: page }) => {
await page.evaluate(async () => {
await window.maka.settings.update({ personalization: { uiLocale: 'en' } });
});
await page.reload();
await page.waitForSelector(COMPOSER_INPUT);
await page.setViewportSize({ width: 1650, height: 992 });
await ensureSidebarExpanded(page);
await page.getByRole('button', { name: 'Settings' }).click();
await page.getByRole('button', { name: 'Appearance', exact: true }).click();
await expect(page.getByRole('heading', { name: 'App icon' })).toBeVisible();
for (const name of ['Azure', 'Classic']) {
const card = page.getByRole('checkbox', { name, exact: true }).locator('..');
await expect(card).toBeVisible();
const geometry = await choiceContentGeometry(card);
expect(geometry.cardHeight).toBeGreaterThan(geometry.contentHeight);
expect(Math.abs(geometry.topGap - geometry.bottomGap)).toBeLessThanOrEqual(1);
}
});
test('reopening settings keeps the last-ready General page stable while refreshing', async ({
window: page,
}) => {
await ensureSidebarExpanded(page);
await page.getByRole('button', { name: '设置' }).click();
const settings = page.locator('.settingsSurface');
await page.getByRole('button', { name: '通用', exact: true }).click();
await expect(settings.getByRole('textbox', { name: '助手语气偏好' })).toBeEnabled();
await expect(settings.getByRole('button', { name: '默认模型' })).toBeEnabled();
await page.keyboard.press('Escape');
await expect(settings).toHaveCount(0);
await expect(page.getByRole('button', { name: '设置' })).toBeVisible();
await page.evaluate(() => {
const state = {
sawLoadingWarning: false,
sawGeneralWithoutReadyHostControls: false,
};
const inspect = () => {
const surface = document.querySelector('.settingsSurface');
const main = surface?.querySelector('main, [role="main"]');
if (!surface || !main) return;
state.sawLoadingWarning ||= Array.from(
surface.querySelectorAll('[role="alert"]'),
).some((banner) => banner.textContent?.includes('正在加载设置') === true);
const defaultModelReady = Array.from(main.querySelectorAll('*')).some(
(element) =>
element.children.length === 0 &&
element.textContent?.trim() === '默认模型' &&
Boolean(element.closest('.astryx-item')?.querySelector('button')),
);
state.sawGeneralWithoutReadyHostControls ||=
main.querySelector('textarea') === null || !defaultModelReady;
};
const observer = new MutationObserver(inspect);
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
Object.assign(window, {
__makaSettingsReopenProbe: {
finish() {
inspect();
observer.disconnect();
return state;
},
},
});
});
await page.getByRole('button', { name: '设置' }).click();
await expect(settings.getByRole('textbox', { name: '助手语气偏好' })).toBeEnabled();
await expect(settings.getByRole('button', { name: '默认模型' })).toBeEnabled();
const probe = await page.evaluate(() => {
const target = window as typeof window & {
__makaSettingsReopenProbe: {
finish(): {
sawLoadingWarning: boolean;
sawGeneralWithoutReadyHostControls: boolean;
};
};
};
return target.__makaSettingsReopenProbe.finish();
});
expect(probe.sawLoadingWarning).toBe(false);
expect(probe.sawGeneralWithoutReadyHostControls).toBe(false);
});