fix: align teacher and operation composer heights
This commit is contained in:
@@ -1,16 +1,36 @@
|
||||
import { useState } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { fixtureAgent } from './teacher-reply-api';
|
||||
import { CodingComposer } from '../../../src/pages/Chat/CodingComposer';
|
||||
import { TeacherChatPanel } from '../../../src/pages/Chat/TeacherChatPanel';
|
||||
import '../../../src/styles/globals.css';
|
||||
import '../../../src/pages/Chat/classroom-workspace.css';
|
||||
import 'katex/dist/katex.min.css';
|
||||
const chat = new URLSearchParams(location.search).get('chat');
|
||||
const params = new URLSearchParams(location.search);
|
||||
const chat = params.get('chat');
|
||||
const continuous = chat !== 'legacy';
|
||||
const width = Number(new URLSearchParams(location.search).get('width')) || 508;
|
||||
const width = Number(params.get('width')) || 508;
|
||||
const compareComposers = params.get('compareComposers') === '1';
|
||||
|
||||
export function OperationComposerComparison() {
|
||||
const [draft, setDraft] = useState('');
|
||||
return <main className="classroom-main flex min-h-0 min-w-0 flex-1 flex-col">
|
||||
<div className="min-h-0 flex-1 p-8"><h1 className="text-lg">我的小狗世界</h1><p className="mt-4 text-sm text-muted-foreground">操作 Agent</p></div>
|
||||
<div className="classroom-composer-view shrink-0">
|
||||
<CodingComposer value={draft} onChange={setDraft} editable canSend={Boolean(draft.trim())}
|
||||
preparing={false} recovering={false} runStatus="idle" mode="prompt" queue={{ items: [] }}
|
||||
error={null} recoverableError={false} attachments={[]} submitting={false}
|
||||
placeholder="告诉操作 Agent 你的想法" voiceScopeKey="composer-layout-fixture"
|
||||
onModeChange={() => {}} onSubmit={() => setDraft('')} onRecover={() => {}}
|
||||
onAddFiles={() => {}} onRemoveAttachment={() => {}} />
|
||||
</div>
|
||||
</main>;
|
||||
}
|
||||
|
||||
createRoot(document.getElementById('root')!).render(<div className="classroom-workspace flex h-screen flex-col bg-white">
|
||||
<header className="flex h-10 shrink-0 items-center border-b px-5 text-xs">智能体回复交互测试 · 真实页面组件 / 模拟回复</header>
|
||||
<div className="flex min-h-0 flex-1 justify-end bg-[#f5f5f1]">
|
||||
<main className="min-w-0 flex-1 p-8"><h1 className="text-lg">我的小狗世界</h1><p className="mt-4 text-sm text-muted-foreground">作品区域</p></main>
|
||||
{compareComposers ? <OperationComposerComparison /> : <main className="min-w-0 flex-1 p-8"><h1 className="text-lg">我的小狗世界</h1><p className="mt-4 text-sm text-muted-foreground">作品区域</p></main>}
|
||||
<div className="h-full shrink-0 border-l" style={{ width }}><TeacherChatPanel projectId="fixture" sourceId={chat === 'continuous' ? 'original-source' : 'source'} selectedAgent={continuous ? fixtureAgent : undefined} /></div>
|
||||
</div>
|
||||
</div>);
|
||||
|
||||
@@ -2018,6 +2018,17 @@ test('incomplete consultation replies retry only on click and preserve both draf
|
||||
const teacher = page.getByTestId('teacher-chat-panel');
|
||||
const draft = teacher.getByRole('textbox', { name: '向智能体提问' });
|
||||
await draft.fill('现在项目是什么阶段?');
|
||||
await page.getByRole('tab', { name: '操作对话', exact: true }).click();
|
||||
await expect(mainDraft).toBeVisible();
|
||||
const [operationBox, teacherBox] = await Promise.all([
|
||||
page.getByTestId('coding-message-composer').locator('.chat-composer-surface').boundingBox(),
|
||||
teacher.locator('.consultation-composer').boundingBox(),
|
||||
]);
|
||||
expect(operationBox).not.toBeNull();
|
||||
expect(teacherBox).not.toBeNull();
|
||||
expect(Math.abs(operationBox!.height - teacherBox!.height)).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(operationBox!.y + operationBox!.height - teacherBox!.y - teacherBox!.height)).toBeLessThanOrEqual(1);
|
||||
await expect(teacher.getByRole('button', { name: /添加图片|添加文件|老师模型/ })).toHaveCount(0);
|
||||
await teacher.getByRole('button', { name: '提问', exact: true }).click();
|
||||
await expect(teacher.getByRole('status')).toHaveText('这次回复未能完整显示');
|
||||
await expect(teacher.getByTestId('teacher-reply')).toHaveCount(0);
|
||||
|
||||
@@ -245,17 +245,55 @@ for (const width of [319, 508]) for (const kind of ['ordinary', 'ideas', 'struct
|
||||
});
|
||||
}
|
||||
|
||||
for (const width of [319, 508]) test(`teacher composer shows only microphone and send controls at ${width}px`, async ({ page }) => {
|
||||
for (const width of [319, 508]) test(`teacher composer matches the operation composer and shows only microphone and send controls at ${width}px`, async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1180, height: 800 });
|
||||
const savedModel = width === 508 ? 'test-second-model' : undefined;
|
||||
await page.addInitScript((model) => {
|
||||
localStorage.clear();
|
||||
if (model) localStorage.setItem('makelore-teacher-model:fixture:coding-teacher:published', model);
|
||||
}, savedModel);
|
||||
await page.goto(`${url}?kind=rich-text&width=${width}`);
|
||||
await page.route('https://example.com/teacher-preview.png', route => route.fulfill({
|
||||
contentType: 'image/svg+xml',
|
||||
body: '<svg xmlns="http://www.w3.org/2000/svg" width="900" height="250"><rect width="900" height="250" fill="#ebeedf"/></svg>',
|
||||
}));
|
||||
await page.goto(`${url}?kind=rich-text&width=${width}&compareComposers=1`);
|
||||
const panel = page.getByTestId('teacher-chat-panel');
|
||||
const input = page.getByRole('textbox', { name: '向智能体提问' });
|
||||
const operationComposer = page.getByTestId('coding-message-composer');
|
||||
const operationInput = operationComposer.getByRole('textbox');
|
||||
const teacherSurface = panel.locator('.consultation-composer');
|
||||
const operationSurface = operationComposer.locator('.chat-composer-surface');
|
||||
const heights: number[] = [];
|
||||
await expect(input).toBeEnabled();
|
||||
for (const lines of [0, 1, 3, 6, 8]) {
|
||||
const draft = Array.from({ length: lines }, (_, index) => `第 ${index + 1} 行想法`).join('\n');
|
||||
await operationInput.fill(draft);
|
||||
await input.fill(draft);
|
||||
const [teacherInputBounds, operationInputBounds, teacherBounds, operationBounds] = await Promise.all([
|
||||
input.boundingBox(), operationInput.boundingBox(), teacherSurface.boundingBox(), operationSurface.boundingBox(),
|
||||
]);
|
||||
expect(Math.abs(teacherInputBounds!.height - operationInputBounds!.height), `${lines} 行输入区域等高`).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(teacherBounds!.height - operationBounds!.height), `${lines} 行输入框外框等高`).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(teacherBounds!.y + teacherBounds!.height - operationBounds!.y - operationBounds!.height), `${lines} 行外框底边对齐`).toBeLessThanOrEqual(1);
|
||||
expect(Math.abs(teacherInputBounds!.y + teacherInputBounds!.height - operationInputBounds!.y - operationInputBounds!.height), `${lines} 行文字区域底边对齐`).toBeLessThanOrEqual(1);
|
||||
heights.push(teacherInputBounds!.height);
|
||||
for (const element of [panel, operationComposer, teacherSurface, operationSurface, input, operationInput]) {
|
||||
expect(await element.evaluate(el => el.scrollWidth <= el.clientWidth), `${lines} 行没有横向溢出`).toBe(true);
|
||||
}
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
|
||||
await page.screenshot({ path: test.info().outputPath(`teacher-operation-composers-${width}-${lines}-lines.png`) });
|
||||
}
|
||||
expect(heights[0]).toBe(heights[1]);
|
||||
expect(heights[2]).toBeGreaterThan(heights[1]);
|
||||
expect(heights[3]).toBeGreaterThan(heights[2]);
|
||||
expect(heights[4]).toBe(heights[3]);
|
||||
expect(await input.evaluate(el => el.scrollHeight > el.clientHeight)).toBe(true);
|
||||
expect(await page.evaluate(() => (window as unknown as { __teacherReplyInputs: unknown[] }).__teacherReplyInputs)).toEqual([]);
|
||||
await input.fill('保留麦克风和发送按钮');
|
||||
await expect(panel.getByRole('button', { name: /老师模型/ })).toHaveCount(0);
|
||||
await expect(panel.getByRole('button', { name: /添加图片|添加文件|添加附件/ })).toHaveCount(0);
|
||||
await expect(panel.locator('input[type="file"]')).toHaveCount(0);
|
||||
await expect(panel.getByRole('combobox')).toHaveCount(0);
|
||||
await expect(panel.getByRole('button', { name: '语音输入' })).toBeVisible();
|
||||
await expect(panel.getByRole('button', { name: '提问', exact: true })).toBeEnabled();
|
||||
expect(await panel.evaluate(el => el.scrollWidth <= el.clientWidth)).toBe(true);
|
||||
@@ -263,7 +301,8 @@ for (const width of [319, 508]) test(`teacher composer shows only microphone and
|
||||
await expect(controls.getByRole('button')).toHaveCount(2);
|
||||
await expect(controls).not.toContainText(/老师配置|模型默认|fixture|test-second-model/);
|
||||
expect(await controls.evaluate(el => el.scrollWidth <= el.clientWidth)).toBe(true);
|
||||
await panel.screenshot({ path: test.info().outputPath(`teacher-composer-${width}.png`) });
|
||||
await operationInput.fill('保留麦克风和发送按钮');
|
||||
await page.screenshot({ path: test.info().outputPath(`teacher-composer-${width}.png`) });
|
||||
await panel.getByRole('button', { name: '提问', exact: true }).click();
|
||||
expect(await page.evaluate(() => (window as unknown as { __teacherReplyInputs: Array<{text: string; modelId?: string}> }).__teacherReplyInputs.at(-1))).toMatchObject({ text: '保留麦克风和发送按钮' });
|
||||
expect(await page.evaluate(() => (window as unknown as { __teacherReplyInputs: Array<{modelId?: string}> }).__teacherReplyInputs.at(-1)?.modelId)).toBe(savedModel);
|
||||
|
||||
Reference in New Issue
Block a user