fix(home): hide Robot entry and balance three module cards
This commit is contained in:
1 parent
23ddce11da
commit
13336cfe12
6 files changed
+167
-81
No files matched your search
@@ -200,7 +200,7 @@ test.describe('Makelore module navigation without setup flow', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('keeps the four module entries in a proportional vertical stack while resizing the window', async ({ launchElectronApp }) => {
|
||||
test('lays out three module cards in a desktop row and a compact mobile stack', async ({ launchElectronApp }) => {
|
||||
const app = await launchElectronApp({ skipSetup: true });
|
||||
|
||||
try {
|
||||
@@ -209,17 +209,18 @@ test.describe('Makelore module navigation without setup flow', () => {
|
||||
|
||||
const moduleCards = page.locator('[data-testid^="ai-module-option-"]');
|
||||
await expect(moduleCards.locator('.module-option-card-title')).toHaveText([
|
||||
'Agents 智能体', 'Code 编程', 'Canvas 设计', 'Robot 机器',
|
||||
'Agents 智能体', 'Code 编程', 'Canvas 设计',
|
||||
]);
|
||||
await expect(page.getByTestId('ai-module-option-robot')).toHaveCount(0);
|
||||
await expect(moduleCards.first().locator('.module-option-card-description')).toHaveText('打造你想象中的AI助手');
|
||||
await expect(moduleCards.locator('img')).toHaveCount(4);
|
||||
await expect(moduleCards.locator('img')).toHaveCount(3);
|
||||
await expect.poll(async () => await moduleCards.locator('img').evaluateAll((images) =>
|
||||
images.every((image) => image.complete && image.naturalWidth > 0),
|
||||
)).toBe(true);
|
||||
await page.screenshot({ path: test.info().outputPath('module-entry-artwork.png') });
|
||||
const readCardMetrics = async () => await moduleCards.evaluateAll((elements) => elements.map((element) => {
|
||||
const bounds = element.getBoundingClientRect();
|
||||
const image = element.querySelector('.module-option-card-image')?.getBoundingClientRect();
|
||||
const content = element.querySelector('.module-option-card-content')?.getBoundingClientRect();
|
||||
return {
|
||||
left: bounds.left,
|
||||
top: bounds.top,
|
||||
@@ -227,70 +228,59 @@ test.describe('Makelore module navigation without setup flow', () => {
|
||||
height: bounds.height,
|
||||
imageWidth: image?.width ?? 0,
|
||||
imageHeight: image?.height ?? 0,
|
||||
imageLeft: image?.left ?? 0,
|
||||
imageTop: image?.top ?? 0,
|
||||
contentLeft: content?.left ?? 0,
|
||||
contentTop: content?.top ?? 0,
|
||||
};
|
||||
}));
|
||||
|
||||
const initialMetrics = await readCardMetrics();
|
||||
expect(initialMetrics).toHaveLength(4);
|
||||
await expect(moduleCards.first().locator('.module-option-card-arrow')).toHaveCount(0);
|
||||
await expect(moduleCards.first().locator('.module-option-artwork')).toHaveCount(0);
|
||||
await expect.poll(async () => await moduleCards.first().evaluate((element) => {
|
||||
const surface = element.querySelector<HTMLElement>('.module-option-card-surface');
|
||||
const frame = element.querySelector<HTMLElement>('.module-option-card-frame');
|
||||
if (!surface || !frame) return false;
|
||||
const surfaceStyle = getComputedStyle(surface);
|
||||
const frameStyle = getComputedStyle(frame);
|
||||
const surfaceInsets = [surfaceStyle.top, surfaceStyle.right, surfaceStyle.bottom, surfaceStyle.left]
|
||||
.map((value) => Number.parseFloat(value));
|
||||
return frameStyle.display === 'none'
|
||||
&& surfaceInsets.every((value) => value === 0)
|
||||
&& surfaceStyle.transform === 'none';
|
||||
})).toBe(true);
|
||||
const initialImageStyles = await moduleCards.first().evaluate((element) => {
|
||||
const image = element.querySelector<HTMLElement>('.module-option-card-image');
|
||||
const cardHeight = element.getBoundingClientRect().height;
|
||||
if (!image) return null;
|
||||
const style = getComputedStyle(image);
|
||||
return {
|
||||
height: Number.parseFloat(style.height),
|
||||
cardHeight,
|
||||
marginTop: Number.parseFloat(style.marginTop),
|
||||
marginBottom: Number.parseFloat(style.marginBottom),
|
||||
marginLeft: Number.parseFloat(style.marginLeft),
|
||||
};
|
||||
});
|
||||
expect(initialImageStyles).not.toBeNull();
|
||||
expect(initialImageStyles!.height).toBeLessThan(initialImageStyles!.cardHeight);
|
||||
expect(initialImageStyles!.marginTop).toBe(1);
|
||||
expect(initialImageStyles!.marginBottom).toBe(1);
|
||||
expect(initialImageStyles!.marginTop).toBe(initialImageStyles!.marginBottom);
|
||||
expect(initialImageStyles!.marginLeft).toBeGreaterThan(0);
|
||||
expect(initialImageStyles!.marginLeft).toBe(initialImageStyles!.marginTop);
|
||||
expect(Math.max(...initialMetrics.map((card) => card.left)) - Math.min(...initialMetrics.map((card) => card.left))).toBeLessThan(1);
|
||||
expect(initialMetrics.map((card) => card.top)).toEqual(
|
||||
[...initialMetrics].sort((top, bottom) => top.top - bottom.top).map((card) => card.top),
|
||||
);
|
||||
|
||||
const initialWidth = initialMetrics[0]?.width ?? 0;
|
||||
expect(initialMetrics[0]?.imageWidth ?? 0).toBeGreaterThan(initialWidth * 0.24);
|
||||
expect(initialMetrics[0]?.imageWidth ?? 0).toBeLessThan(initialWidth / 2);
|
||||
expect(initialMetrics[0]?.imageHeight ?? 0).toBeGreaterThan((initialMetrics[0]?.height ?? 0) * 0.75);
|
||||
|
||||
await app.evaluate(({ BrowserWindow }) => {
|
||||
BrowserWindow.getAllWindows()[0]?.setSize(1100, 700);
|
||||
BrowserWindow.getAllWindows()[0]?.setMinimumSize(360, 600);
|
||||
});
|
||||
|
||||
await expect.poll(async () => (await readCardMetrics())[0]?.width ?? 0).toBeLessThan(initialWidth - 1);
|
||||
for (const width of [1440, 1024, 768]) {
|
||||
await app.evaluate(({ BrowserWindow }, windowWidth) => BrowserWindow.getAllWindows()[0]?.setSize(windowWidth, 900), width);
|
||||
await expect.poll(() => page.evaluate(() => innerWidth)).toBe(width);
|
||||
const cards = await readCardMetrics();
|
||||
expect(cards).toHaveLength(3);
|
||||
expect(Math.max(...cards.map(card => card.top)) - Math.min(...cards.map(card => card.top))).toBeLessThan(1);
|
||||
expect(Math.max(...cards.map(card => card.width)) - Math.min(...cards.map(card => card.width))).toBeLessThan(1);
|
||||
const gaps = cards.slice(1).map((card, index) => card.left - cards[index]!.left - cards[index]!.width);
|
||||
expect(Math.min(...gaps)).toBeGreaterThan(0);
|
||||
expect(Math.abs(gaps[0]! - gaps[1]!)).toBeLessThan(1);
|
||||
for (const card of cards) {
|
||||
expect(card.left).toBeGreaterThanOrEqual(0);
|
||||
expect(card.left + card.width).toBeLessThanOrEqual(width);
|
||||
expect(card.contentTop).toBeGreaterThanOrEqual(card.imageTop + card.imageHeight - 1);
|
||||
}
|
||||
await expect(page.getByRole('heading', { name: '今天,想创作什么?' })).toHaveCSS('text-align', 'center');
|
||||
expect(await moduleCards.evaluateAll(elements => elements.every(element => {
|
||||
const bounds = element.getBoundingClientRect();
|
||||
return [[2, 2], [bounds.width - 2, bounds.height - 2], [bounds.width / 2, bounds.height / 2]].every(([x, y]) =>
|
||||
element.contains(document.elementFromPoint(bounds.left + x!, bounds.top + y!)),
|
||||
);
|
||||
}))).toBe(true);
|
||||
await page.screenshot({ path: test.info().outputPath(`module-entry-${width}.png`) });
|
||||
}
|
||||
|
||||
const resizedMetrics = await readCardMetrics();
|
||||
expect(resizedMetrics).toHaveLength(4);
|
||||
expect(Math.max(...resizedMetrics.map((card) => card.left)) - Math.min(...resizedMetrics.map((card) => card.left))).toBeLessThan(1);
|
||||
expect(resizedMetrics.map((card) => card.top)).toEqual(
|
||||
[...resizedMetrics].sort((top, bottom) => top.top - bottom.top).map((card) => card.top),
|
||||
);
|
||||
expect(resizedMetrics[0]!.imageWidth).toBeGreaterThan(resizedMetrics[0]!.width * 0.24);
|
||||
expect(resizedMetrics[0]!.imageWidth).toBeLessThan(resizedMetrics[0]!.width / 2);
|
||||
expect(resizedMetrics[0]!.imageHeight).toBeGreaterThan(resizedMetrics[0]!.height * 0.75);
|
||||
await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0]?.setSize(375, 800));
|
||||
await expect.poll(() => page.evaluate(() => innerWidth)).toBe(375);
|
||||
const compactCards = await readCardMetrics();
|
||||
expect(Math.max(...compactCards.map(card => card.left)) - Math.min(...compactCards.map(card => card.left))).toBeLessThan(1);
|
||||
for (const [index, card] of compactCards.entries()) {
|
||||
if (index > 0) expect(card.top).toBeGreaterThan(compactCards[index - 1]!.top + compactCards[index - 1]!.height);
|
||||
expect(card.imageWidth).toBeLessThan(card.width / 2);
|
||||
expect(card.imageHeight).toBeGreaterThan(card.height * 0.75);
|
||||
expect(card.contentLeft).toBeGreaterThanOrEqual(card.imageLeft + card.imageWidth - 1);
|
||||
}
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth - innerWidth)).toBeLessThanOrEqual(1);
|
||||
expect(await moduleCards.locator('.module-option-card-title, .module-option-card-description').evaluateAll(elements =>
|
||||
elements.every(element => element.scrollWidth <= element.clientWidth),
|
||||
)).toBe(true);
|
||||
await page.screenshot({ path: test.info().outputPath('module-entry-375.png') });
|
||||
|
||||
await moduleCards.first().hover();
|
||||
await moduleCards.first().focus();
|
||||
@@ -323,7 +313,7 @@ test.describe('Makelore module navigation without setup flow', () => {
|
||||
const page = await getStableWindow(app);
|
||||
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
|
||||
|
||||
await expect(page.locator('[data-testid^="ai-module-option-"]')).toHaveCount(4);
|
||||
await expect(page.locator('[data-testid^="ai-module-option-"]')).toHaveCount(3);
|
||||
await expect(page.getByTestId('ai-module-option-learning')).toHaveCount(0);
|
||||
await page.evaluate(() => {
|
||||
window.location.hash = '#/learning';
|
||||
@@ -385,7 +375,8 @@ test.describe('Makelore module navigation without setup flow', () => {
|
||||
await moduleHomeTrigger.click();
|
||||
await expect(page).toHaveURL(/\/module-select$/);
|
||||
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
|
||||
await page.getByTestId('ai-module-option-robot').click();
|
||||
await expect(page.getByTestId('ai-module-option-robot')).toHaveCount(0);
|
||||
await page.evaluate(() => { window.location.hash = '#/ai-hardware'; });
|
||||
await expect(page).toHaveURL(/\/ai-hardware$/);
|
||||
await expect(page.getByTestId('ai-hardware-page')).toBeVisible();
|
||||
await expect(page.getByTestId('sidebar-robot-navigation')).toBeVisible();
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('AI module navigation', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('shows the four supported module choices and routes Canvas from the chooser', async () => {
|
||||
it('shows Agents, Code and Canvas on the home page and routes Canvas from the chooser', async () => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/module-select']}>
|
||||
<Routes>
|
||||
@@ -64,18 +64,16 @@ describe('AI module navigation', () => {
|
||||
|
||||
expect(screen.getByTestId('ai-module-selection-page')).toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { name: '今天,想创作什么?' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('heading', { name: '今天,想创作什么?' })).toHaveClass('whitespace-nowrap');
|
||||
expect(screen.getByTestId('ai-module-options')).toHaveClass('module-selection-options');
|
||||
expect(await screen.findByText('小明,欢迎回来!')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('ai-module-option-programming')).toHaveClass('module-option-card', 'module-option-card-horizontal');
|
||||
for (const moduleId of ['programming', 'painting', 'robot', 'cloud_agents'] as const) {
|
||||
expect(screen.getByTestId('ai-module-option-programming')).toHaveClass('module-option-card');
|
||||
for (const moduleId of ['programming', 'painting', 'cloud_agents'] as const) {
|
||||
expect(screen.getByTestId(`ai-module-option-${moduleId}`).querySelector('.module-option-artwork')).toBeNull();
|
||||
}
|
||||
expect(screen.getAllByTestId(/^ai-module-option-/u).map((option) => option.dataset.testid)).toEqual([
|
||||
'ai-module-option-cloud_agents',
|
||||
'ai-module-option-programming',
|
||||
'ai-module-option-painting',
|
||||
'ai-module-option-robot',
|
||||
]);
|
||||
expect(screen.queryByTestId('ai-module-option-learning')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('先选一个入口。进入后,你还可以从左下角随时切换到其他模块。')).not.toBeInTheDocument();
|
||||
@@ -89,12 +87,7 @@ describe('AI module navigation', () => {
|
||||
expect(screen.getByText('Canvas 设计')).toBeInTheDocument();
|
||||
expect(screen.getByAltText('数位板设计创作')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('ai-module-option-painting')).toHaveTextContent('用灵感描绘色彩缤纷的世界');
|
||||
const robotOption = screen.getByTestId('ai-module-option-robot');
|
||||
expect(robotOption).toBeEnabled();
|
||||
expect(robotOption).not.toHaveClass('module-option-card-disabled');
|
||||
expect(robotOption).toHaveTextContent('Robot 机器');
|
||||
expect(robotOption).toHaveTextContent('制作你的第一个机器人小伙伴');
|
||||
expect(screen.getByAltText('青少年管理机器人硬件与设备绑定')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('ai-module-option-robot')).not.toBeInTheDocument();
|
||||
expect(screen.getByAltText('个人云智能体')).toHaveAttribute('src', expect.stringContaining('module-agents.webp'));
|
||||
expect(screen.getByAltText('个人云智能体')).toHaveClass('module-option-card-image-media', 'object-cover');
|
||||
|
||||
@@ -102,7 +95,8 @@ describe('AI module navigation', () => {
|
||||
expect(await screen.findByTestId('location-path')).toHaveTextContent('/image-canvas');
|
||||
});
|
||||
|
||||
it('routes Robot from the chooser to the AI hardware workspace', async () => {
|
||||
it('hides Robot from the chooser even when its module permission is enabled', () => {
|
||||
useAuthStore.setState({ moduleAccess: { programming: true, design: true, robot: true, cloud_agents: true } });
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/module-select']}>
|
||||
<Routes>
|
||||
@@ -112,8 +106,9 @@ describe('AI module navigation', () => {
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTestId('ai-module-option-robot'));
|
||||
expect(await screen.findByTestId('location-path')).toHaveTextContent('/ai-hardware');
|
||||
expect(screen.queryByTestId('ai-module-option-robot')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Robot 机器')).not.toBeInTheDocument();
|
||||
expect(screen.queryByAltText('青少年管理机器人硬件与设备绑定')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('greys out a disabled module and does not navigate when it is clicked', async () => {
|
||||
|
||||
Reference in new issue
Block a user