fix: dismiss teacher welcome on consultation entry

This commit is contained in:
鲨鱼辣椒
2026-09-28 16:08:29 +08:00
parent 4495345fb0
commit c96bb45d09
5 changed files with 167 additions and 2 deletions

View File

@@ -2028,6 +2028,41 @@ test('settled prompt releases the composer without a client receipt and keeps th
} finally { await releaseSnapshot(electronApp); }
});
test('teacher avatar consumes the welcome bubble through close, reopen and reload without a model request', async ({ launchElectronApp }) => {
const electronApp = await launchElectronApp({ skipSetup: true });
let page = await getStableWindow(electronApp);
const connection = await page.evaluate(async () => ({ token: await window.electron.ipcRenderer.invoke('hostapi:token') as string, baseUrl: await window.electron.ipcRenderer.invoke('hostapi:base-url') as string }));
await installCodingFirstChatHost(electronApp, connection, true);
await settleSnapshot(electronApp);
await disableCodingEventSource(page);
try {
await page.reload(); page = await getStableWindow(electronApp);
await page.getByTestId('ai-module-option-programming').click();
await page.evaluate(() => { window.location.hash = '/chat'; });
const teacherButton = page.getByRole('button', { name: '与代码智能体聊天', exact: true });
const invitation = page.getByTestId('teacher-invitation');
const teacher = page.getByTestId('teacher-chat-panel');
await expect(invitation).toHaveAttribute('data-bubble-kind', 'welcome');
await teacherButton.click();
await expect(teacher).toBeVisible();
await expect(teacher.getByText('一起理解代码', { exact: true })).toBeVisible();
await expect(invitation).toHaveCount(0);
await page.screenshot({ path: test.info().outputPath('teacher-avatar-consumed-welcome.png') });
await teacherButton.click();
await expect(teacher).toHaveCount(0);
await expect(invitation).toHaveCount(0);
await teacherButton.click();
await expect(teacher).toBeVisible();
await expect(invitation).toHaveCount(0);
await teacherButton.click();
await expect(teacher).toHaveCount(0);
await page.reload();
await expect(teacherButton).toBeVisible();
await expect(invitation).toHaveCount(0);
expect((await readState(electronApp)).captured.filter(item => /\/(agent-topics|messages|teacher-check-in|prompt)$/.test(item.path) && item.method === 'POST')).toHaveLength(0);
} finally { await releaseSnapshot(electronApp); }
});
test('teacher stays in the top-right header and proactively checks in without taking over the student draft', async ({ launchElectronApp }) => {
const electronApp = await launchElectronApp({ skipSetup: true });
let page = await getStableWindow(electronApp);
@@ -2121,6 +2156,12 @@ test('teacher stays in the top-right header and proactively checks in without ta
await expect(composer).toHaveValue('我还在写自己的想法');
await page.clock.fastForward(300_000);
expect((await readState(electronApp)).captured.filter((item) => item.path.endsWith('/teacher-check-in'))).toHaveLength(1);
await teacherButton.click();
await expect(page.getByTestId('teacher-chat-panel')).toBeVisible();
await expect(invitation).toHaveAttribute('data-bubble-kind', 'check-in');
await teacherButton.click();
await expect(page.getByTestId('teacher-chat-panel')).toHaveCount(0);
await expect(invitation).toHaveAttribute('data-bubble-kind', 'check-in');
await invitation.getByRole('button', { name: '和智能体聊聊' }).click();
await expect(invitation).toHaveCount(0);
await expect(presence).toHaveCount(0);

View File

@@ -423,6 +423,73 @@ describe('teacher presence and periodic guidance', () => {
expect(screen.queryByTestId('teacher-invitation')).not.toBeInTheDocument();
});
it('consumes the static greeting when consultation opens through the avatar and remembers it for the project', async () => {
const greeting = '有什么编程问题想一起讨论?';
api.config.mockResolvedValue({ enabled: true, definition: { ...definition, welcome_message: greeting } });
const view = await mount();
expect(screen.getByTestId('teacher-invitation')).toHaveAttribute('data-bubble-kind', 'welcome');
await act(async () => fireEvent.click(screen.getByRole('button', { name: '智能体', exact: true })));
expect(within(screen.getByTestId('teacher-chat-panel')).getByText(greeting)).toBeVisible();
expect(screen.queryByTestId('teacher-invitation')).not.toBeInTheDocument();
await act(async () => fireEvent.click(screen.getByRole('button', { name: '智能体', exact: true })));
expect(screen.queryByTestId('teacher-chat-panel')).not.toBeInTheDocument();
expect(screen.queryByTestId('teacher-invitation')).not.toBeInTheDocument();
await act(async () => fireEvent.click(screen.getByRole('button', { name: '智能体', exact: true })));
expect(screen.getByTestId('teacher-chat-panel')).toBeVisible();
expect(screen.queryByTestId('teacher-invitation')).not.toBeInTheDocument();
view.unmount();
const next = await mount();
expect(screen.queryByTestId('teacher-invitation')).not.toBeInTheDocument();
await act(async () => next.rerender(<Harness projectId="other" />));
expect(screen.getByTestId('teacher-invitation')).toHaveAttribute('data-bubble-kind', 'welcome');
expect(api.create).not.toHaveBeenCalled();
expect(api.send).not.toHaveBeenCalled();
expect(api.checkIn).not.toHaveBeenCalled();
});
it.each([false, true])('consumes the static greeting when consultation opens before config loads (closed before load: %s)', async (closeBeforeLoad) => {
const config = { enabled: true, definition: { ...definition, welcome_message: '欢迎一起创作' } };
let resolve!: (value: typeof config) => void;
api.config.mockImplementationOnce(() => new Promise<typeof config>((done) => { resolve = done; }));
api.config.mockResolvedValue(config);
const view = await mount();
expect(screen.queryByTestId('teacher-invitation')).not.toBeInTheDocument();
await act(async () => fireEvent.click(screen.getByRole('button', { name: '智能体', exact: true })));
if (closeBeforeLoad) {
await act(async () => fireEvent.click(screen.getByRole('button', { name: '智能体', exact: true })));
}
await act(async () => resolve(config));
expect(screen.queryByTestId('teacher-invitation')).not.toBeInTheDocument();
if (!closeBeforeLoad) {
await act(async () => fireEvent.click(screen.getByRole('button', { name: '智能体', exact: true })));
}
expect(screen.queryByTestId('teacher-chat-panel')).not.toBeInTheDocument();
expect(screen.queryByTestId('teacher-invitation')).not.toBeInTheDocument();
view.unmount();
await mount();
expect(screen.queryByTestId('teacher-invitation')).not.toBeInTheDocument();
expect(api.create).not.toHaveBeenCalled();
expect(api.send).not.toHaveBeenCalled();
expect(api.checkIn).not.toHaveBeenCalled();
});
it('consumes only the static greeting on avatar entry and leaves a real unread check-in until explicitly viewed', async () => {
api.config.mockResolvedValue({ enabled: true, definition: { ...definition, welcome_message: '欢迎一起创作' } });
api.list.mockResolvedValue({ items: [{ id: 'topic-project' }], lastSelectedTopicId: 'topic-project' });
api.read.mockResolvedValue(checkInTopic());
await mount();
await act(async () => fireEvent.click(screen.getByRole('button', { name: '智能体', exact: true })));
expect(screen.getByTestId('teacher-invitation')).toHaveAttribute('data-bubble-kind', 'check-in');
expect(screen.getByTestId('teacher-invitation')).toHaveTextContent('排行榜');
await act(async () => fireEvent.click(screen.getByRole('button', { name: '看看智能体说的' })));
expect(screen.queryByTestId('teacher-invitation')).not.toBeInTheDocument();
await act(async () => fireEvent.click(screen.getByRole('button', { name: '智能体', exact: true })));
expect(screen.queryByTestId('teacher-invitation')).not.toBeInTheDocument();
expect(api.create).not.toHaveBeenCalled();
expect(api.send).not.toHaveBeenCalled();
expect(api.checkIn).not.toHaveBeenCalled();
});
it('keeps the elapsed interval when the Renderer reloads instead of starting another five-minute wait', async () => {
const first = await mount();
await advance(4 * 60_000);