fix: float teacher introductions above a centered welcome

This commit is contained in:
鲨鱼辣椒
2026-09-29 16:29:19 +08:00
parent 521e1ee167
commit 5de8ea823d
6 changed files with 53 additions and 28 deletions

View File

@@ -48,17 +48,19 @@ test.beforeEach(async ({ page }) => {
});
for (const width of [375, 768, 1280, 1470]) {
test(`welcome, composer and community link clear teacher introductions at ${width}px`, async ({ page }) => {
test(`welcome and bottom controls remain usable with floating teachers at ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 850 });
await page.emulateMedia({ reducedMotion: 'reduce' });
await page.goto(baseUrl + '?teachers');
const heading = page.getByTestId('coding-start-heading');
const teachers = page.getByTestId('teacher-welcomes');
await expect(teachers.locator('.teacher-welcome-card')).toHaveCount(2);
await expect.poll(async () => {
const a = await heading.boundingBox(), b = await teachers.boundingBox();
return !!a && !!b && (a.x + a.width <= b.x || a.y >= b.y + b.height);
}).toBe(true);
if (width < 900) {
await expect.poll(async () => {
const a = await heading.boundingBox(), b = await teachers.boundingBox();
return !!a && !!b && a.y >= b.y + b.height;
}).toBe(true);
}
await expect(page.locator('.coding-start-mark')).toHaveCSS('animation-name', 'none');
await page.getByTestId('community-invitation').scrollIntoViewIfNeeded();
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width);
@@ -88,7 +90,7 @@ test('a short window scrolls the welcome while keeping the composer at the botto
});
for (const width of [1280, 1470]) {
test(`the bottom input is centered between the project sidebar and fixed teacher panel at ${width}px`, async ({ page }) => {
test(`floating teachers leave the welcome and bottom input centered in the page at ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 850 });
await page.emulateMedia({ reducedMotion: 'reduce' });
await page.goto(baseUrl + '?teachers&sidebar');
@@ -96,27 +98,37 @@ for (const width of [1280, 1470]) {
const composer = page.getByTestId('coding-message-composer');
await expect(teachers).toHaveCSS('width', '340px');
await expect.poll(async () => {
const [side, teacher, input] = await Promise.all([
page.getByTestId('project-sidebar').boundingBox(), teachers.boundingBox(), composer.boundingBox(),
const [side, input] = await Promise.all([
page.getByTestId('project-sidebar').boundingBox(), composer.boundingBox(),
]);
return Math.abs((input!.x + input!.width / 2) - (side!.x + side!.width + teacher!.x) / 2);
return Math.abs(input!.x + input!.width / 2 - (side!.x + side!.width + width) / 2);
}).toBeLessThan(2);
const input = (await composer.boundingBox())!;
const invitation = (await page.getByTestId('community-invitation').boundingBox())!;
expect(invitation.x).toBeCloseTo(input.x, 0);
expect(invitation.width).toBeCloseTo(input.width, 0);
expect(input.y - invitation.y - invitation.height).toBeCloseTo(12, 0);
await page.screenshot({ path: test.info().outputPath(`composer-between-panels-${width}.png`) });
const heading = page.getByTestId('coding-start-heading');
await expect.poll(async () => {
const [title, content] = await Promise.all([heading.boundingBox(), page.locator('.classroom-chat-view').boundingBox()]);
return Math.abs(title!.y + title!.height / 2 - content!.y - content!.height / 2);
}).toBeLessThan(2);
expect((await heading.boundingBox())!.x + (await heading.boundingBox())!.width / 2).toBeCloseTo(input.x + input.width / 2, 0);
await page.screenshot({ path: test.info().outputPath(`floating-teachers-centered-welcome-${width}.png`) });
await composer.getByRole('textbox').fill('保留这份草稿');
await page.getByTestId('project-sidebar').evaluate(el => { el.style.width = '60px'; });
await expect.poll(async () => {
const [side, teacher, input] = await Promise.all([
page.getByTestId('project-sidebar').boundingBox(), teachers.boundingBox(), composer.boundingBox(),
const [side, input] = await Promise.all([
page.getByTestId('project-sidebar').boundingBox(), composer.boundingBox(),
]);
return Math.abs(input!.x + input!.width / 2 - (side!.x + side!.width + teacher!.x) / 2);
return Math.abs(input!.x + input!.width / 2 - (side!.x + side!.width + width) / 2);
}).toBeLessThan(2);
const inputBeforeDismiss = await composer.boundingBox();
const headingBeforeDismiss = await heading.boundingBox();
await teachers.getByRole('button', { name: '先自己试试' }).click();
await expect(composer.getByRole('textbox')).toHaveValue('保留这份草稿');
expect(await composer.boundingBox()).toEqual(inputBeforeDismiss);
expect(await heading.boundingBox()).toEqual(headingBeforeDismiss);
await expect.poll(async () => {
const [side, input] = await Promise.all([page.getByTestId('project-sidebar').boundingBox(), composer.boundingBox()]);
return Math.abs(input!.x + input!.width / 2 - (side!.x + side!.width + width) / 2);

View File

@@ -1113,6 +1113,11 @@ test('first PI Conversation is editable under 500 ms and submits before runtime
await expect(page.getByTestId('coding-message-composer')).toBeInViewport({ ratio: 1 });
expect(await page.getByTestId('coding-message-composer').evaluate(el => innerHeight - el.getBoundingClientRect().bottom)).toBeLessThan(40);
await expect(page.getByTestId('community-invitation')).toBeVisible();
expect(await page.getByTestId('coding-message-composer').evaluate(el => {
const body = document.querySelector('.coding-chat-body')!.getBoundingClientRect();
const input = el.getBoundingClientRect();
return Math.abs(input.left + input.width / 2 - body.left - body.width / 2);
})).toBeLessThan(2);
expect(await page.getByTestId('community-invitation').evaluate(el => {
const composer = document.querySelector('[data-testid="coding-message-composer"]')!;
return composer.getBoundingClientRect().top - el.getBoundingClientRect().bottom;