Integrate project classroom with teacher and friend consultations
This commit is contained in:
@@ -15,7 +15,7 @@ const api = vi.hoisted(() => ({
|
||||
}));
|
||||
vi.mock('@/lib/coding-teacher', () => ({
|
||||
teacherApi: api,
|
||||
teacherTopicsPath: (p: string, s: string) => p + '/' + s,
|
||||
teacherTopicsPath: (p: string, s: string, role?: string) => p + '/' + (role ?? s),
|
||||
}));
|
||||
const definition: TeacherDefinition = {
|
||||
schema_version: 1,
|
||||
@@ -50,6 +50,7 @@ const first = topic('first'),
|
||||
let streams: Map<string, EventTarget & { close: ReturnType<typeof vi.fn> }>;
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
localStorage.clear();
|
||||
streams = new Map();
|
||||
api.config.mockResolvedValue({ enabled: true, published_version: 1, revision: 1, definition });
|
||||
api.list.mockResolvedValue({
|
||||
@@ -142,4 +143,113 @@ describe('teacher side chat', () => {
|
||||
expect(screen.getByRole('button', { name: '提问', exact: true })).toBeDisabled();
|
||||
expect(api.read).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
['teacher', '老师', '我还没想好下一步做什么', '提问'],
|
||||
['friend', '朋友', '想听听你对作品的第一印象', '发送给朋友'],
|
||||
] as const)('only sends a %s suggestion after the student submits it', async (role, label, suggestion, sendLabel) => {
|
||||
api.list.mockResolvedValue({ items: [], lastSelectedTopicId: null });
|
||||
api.send.mockImplementation(async (_base, _id, input) => ({ ...first, requests: [{
|
||||
id: input.requestId, text: input.text, references: [], createdAt: 'now',
|
||||
sourceCursor: { workerGeneration: 1, seq: 1 }, sourceCapturedAt: 'now',
|
||||
includedSourceMessageIds: [], omittedMessages: 0, status: 'completed', response: '一起再想一想。',
|
||||
}] }));
|
||||
const view = render(<TeacherChatPanel projectId="p" sourceId="c" role={role} />);
|
||||
const sendButton = screen.getByRole('button', { name: sendLabel, exact: true });
|
||||
await waitFor(() => expect(screen.getByLabelText(`向${label}提问`)).toBeEnabled());
|
||||
fireEvent.click(screen.getByRole('button', { name: suggestion }));
|
||||
expect(screen.getByLabelText(`向${label}提问`)).toHaveValue(suggestion);
|
||||
expect(api.create).not.toHaveBeenCalled();
|
||||
expect(api.send).not.toHaveBeenCalled();
|
||||
|
||||
fireEvent.click(sendButton);
|
||||
await screen.findByText('一起再想一想。');
|
||||
expect(api.config).toHaveBeenCalledWith(role);
|
||||
expect(api.send).toHaveBeenCalledWith(`p/${role}`, 'first', expect.objectContaining({
|
||||
text: suggestion, sourceConversationId: 'c', references: [],
|
||||
}));
|
||||
expect(screen.getByLabelText(`向${label}提问`)).toHaveValue('');
|
||||
view.unmount();
|
||||
render(<TeacherChatPanel projectId="p" sourceId="c" role={role} />);
|
||||
expect(screen.getByLabelText(`向${label}提问`)).toHaveValue('');
|
||||
});
|
||||
|
||||
it('restores a closed draft and keeps teacher, friend and project drafts separate', async () => {
|
||||
api.list.mockResolvedValue({ items: [], lastSelectedTopicId: null });
|
||||
const closed = vi.fn();
|
||||
const view = render(<TeacherChatPanel key="p-teacher" projectId="p" sourceId="c" role="teacher" onClose={closed} />);
|
||||
await waitFor(() => expect(screen.getByLabelText('向老师提问')).toBeEnabled());
|
||||
fireEvent.change(screen.getByLabelText('向老师提问'), { target: { value: '我还没想清楚规则' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '关闭老师' }));
|
||||
expect(closed).toHaveBeenCalledOnce();
|
||||
view.rerender(<></>);
|
||||
view.rerender(<TeacherChatPanel key="p-teacher" projectId="p" sourceId="another-operation-chat" role="teacher" />);
|
||||
expect(screen.getByLabelText('向老师提问')).toHaveValue('我还没想清楚规则');
|
||||
|
||||
view.rerender(<TeacherChatPanel key="p-friend" projectId="p" sourceId="c" role="friend" />);
|
||||
await waitFor(() => expect(screen.getByLabelText('向朋友提问')).toBeEnabled());
|
||||
expect(screen.getByLabelText('向朋友提问')).toHaveValue('');
|
||||
fireEvent.change(screen.getByLabelText('向朋友提问'), { target: { value: '想听听你的感受' } });
|
||||
view.rerender(<TeacherChatPanel key="another-teacher" projectId="another-project" sourceId="c" role="teacher" />);
|
||||
await waitFor(() => expect(screen.getByLabelText('向老师提问')).toBeEnabled());
|
||||
expect(screen.getByLabelText('向老师提问')).toHaveValue('');
|
||||
fireEvent.change(screen.getByLabelText('向老师提问'), { target: { value: '这个项目的另一个问题' } });
|
||||
|
||||
view.rerender(<TeacherChatPanel key="p-teacher" projectId="p" sourceId="c" role="teacher" />);
|
||||
expect(screen.getByLabelText('向老师提问')).toHaveValue('我还没想清楚规则');
|
||||
view.rerender(<TeacherChatPanel key="p-friend" projectId="p" sourceId="c" role="friend" />);
|
||||
expect(screen.getByLabelText('向朋友提问')).toHaveValue('想听听你的感受');
|
||||
expect(api.send).not.toHaveBeenCalled();
|
||||
expect(api.cancel).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps a failed question and its request id when the consultation is reopened', async () => {
|
||||
api.send.mockRejectedValue(new Error('网络暂不可用'));
|
||||
const view = render(<TeacherChatPanel projectId="p" sourceId="c" role="teacher" />);
|
||||
await waitFor(() => expect(screen.getByLabelText('向老师提问')).toBeEnabled());
|
||||
fireEvent.change(screen.getByLabelText('向老师提问'), { target: { value: '我该怎样观察?' } });
|
||||
fireEvent.click(screen.getByRole('button', { name: '提问', exact: true }));
|
||||
await screen.findByText('网络暂不可用');
|
||||
const originalRequest = api.send.mock.calls[0][2];
|
||||
view.unmount();
|
||||
|
||||
render(<TeacherChatPanel projectId="p" sourceId="c" role="teacher" />);
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '提问', exact: true })).toBeEnabled());
|
||||
expect(screen.getByLabelText('向老师提问')).toHaveValue('我该怎样观察?');
|
||||
expect(api.send).toHaveBeenCalledTimes(1);
|
||||
fireEvent.click(screen.getByRole('button', { name: '提问', exact: true }));
|
||||
await screen.findByText('网络暂不可用');
|
||||
expect(api.send.mock.calls[1][2]).toEqual(originalRequest);
|
||||
});
|
||||
|
||||
it('returns to the operation chat without importing advice or offering a notes workflow', async () => {
|
||||
const returnToWork = vi.fn();
|
||||
const bringBack = vi.fn();
|
||||
api.read.mockResolvedValue({ ...first, requests: [{
|
||||
id: 'reply', text: '游戏不好玩', references: [], createdAt: 'now',
|
||||
sourceCursor: { workerGeneration: 1, seq: 1 }, sourceCapturedAt: 'now',
|
||||
includedSourceMessageIds: [], omittedMessages: 0, status: 'completed', response: '先观察别人在哪儿停下来。',
|
||||
}] });
|
||||
render(<TeacherChatPanel projectId="p" sourceId="c" role="teacher" onReturnToWork={returnToWork} onBringBack={bringBack} />);
|
||||
await screen.findByText('先观察别人在哪儿停下来。');
|
||||
expect(screen.queryByRole('button', { name: '带回主会话草稿' })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/记一下|项目共识|记入共识|待办/)).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '我去试一试' }));
|
||||
expect(returnToWork).toHaveBeenCalledOnce();
|
||||
expect(bringBack).not.toHaveBeenCalled();
|
||||
expect(api.send).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('keeps operations preview available without a consultation role', async () => {
|
||||
api.preview.mockResolvedValue({ payload: definition, draft_revision: 7 });
|
||||
api.list.mockResolvedValue({ items: [], lastSelectedTopicId: null });
|
||||
api.create.mockResolvedValue({ ...first, draftRevision: 7 });
|
||||
render(<TeacherChatPanel projectId="preview" sourceId="sample" draftRevision={7} sampleContext="学生的练习代码" />);
|
||||
await waitFor(() => expect(screen.getByRole('button', { name: '老师新话题' })).toBeEnabled());
|
||||
expect(screen.getByText('运营草稿试聊')).toBeInTheDocument();
|
||||
expect(api.preview).toHaveBeenCalledWith(7);
|
||||
expect(api.config).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole('button', { name: '老师新话题' }));
|
||||
await waitFor(() => expect(api.create).toHaveBeenCalledWith('preview/sample', 7, '学生的练习代码'));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user