merge: integrate remote learning module safely
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled

This commit is contained in:
brother7 committed 2026-08-17 01:05:49 +08:00
commit f7171a471a
125 files changed
+11451 -12132

No files matched your search

+153 -20
View File
@@ -32,6 +32,7 @@ function projectStatus(buildStatus: string, buildErrorCode: string | null = null
app_id: 'space-cleaner',
title: 'space-cleaner',
summary: 'A project',
status: 'draft',
},
latest_version: {
id: 'version-1',
@@ -62,6 +63,26 @@ async function advancePoll() {
});
}
async function submitProjectMetadata() {
fetchCurrentWorksProjectStatusMock.mockRejectedValueOnce(Object.assign(
new Error('project missing'),
{ statusCode: 404 },
));
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: '一键提交审核' }));
await Promise.resolve();
});
fireEvent.change(screen.getByLabelText(/发布者姓名/), { target: { value: '小明' } });
fireEvent.change(screen.getByLabelText(/发布者年龄/), { target: { value: '12' } });
fireEvent.change(screen.getByLabelText(/项目简介/), { target: { value: '这是一个太空清洁小游戏。' } });
fireEvent.click(screen.getByRole('button', { name: '提交审核' }));
await act(async () => {
await Promise.resolve();
await Promise.resolve();
await Promise.resolve();
});
}
describe('ProjectPublishAction', () => {
beforeEach(() => {
vi.useFakeTimers();
@@ -74,16 +95,16 @@ describe('ProjectPublishAction', () => {
vi.useRealTimers();
});
it('submits automatic metadata and locks after Builder succeeds', async () => {
it('creates a new project without a cover and locks after Builder succeeds', async () => {
fetchCurrentWorksProjectStatusMock.mockResolvedValue(projectStatus('succeeded'));
render(<ProjectPublishAction project={project} projectType="mini_game" />);
fireEvent.click(screen.getByRole('button', { name: '一键提交审核' }));
expect(screen.getByRole('button', { name: '正在生成本次构建结果…' })).toBeDisabled();
await submitProjectMetadata();
expect(screen.getByRole('button', { name: '正在等待云端检查…' })).toBeDisabled();
await flushSubmission();
expect(screen.getByTestId('project-publish-status')).toHaveTextContent(
'本次构建结果已通过本地预览检查并上传,正在等待平台校验。',
'无封面作品与本次构建结果已提交,正在等待平台校验。',
);
expect(publishWorksProjectSourceMock).toHaveBeenCalledWith({
@@ -91,22 +112,28 @@ describe('ProjectPublishAction', () => {
project: {
app_id: 'makelore-prj-space-cleaner',
title: 'space-cleaner',
summary: 'space-cleaner,由 Makelore 创建的作品。',
summary: '这是一个太空清洁小游戏。',
description: null,
cover_url: null,
creator_name: '小明',
creator_age: 12,
category: 'mini_game',
age_band: null,
difficulty: null,
},
});
expect(fetchCurrentWorksProjectStatusMock).not.toHaveBeenCalled();
expect(screen.queryByLabelText(/项目封面/)).not.toBeInTheDocument();
expect(publishWorksProjectSourceMock.mock.calls[0]?.[0]).not.toHaveProperty('cover');
expect(fetchCurrentWorksProjectStatusMock).toHaveBeenCalledOnce();
await advancePoll();
expect(fetchCurrentWorksProjectStatusMock).toHaveBeenCalledWith(
expect(fetchCurrentWorksProjectStatusMock).toHaveBeenLastCalledWith(
'makelore-prj-space-cleaner',
);
expect(fetchCurrentWorksProjectStatusMock).toHaveBeenCalledTimes(2);
expect(screen.getByRole('button', { name: '已提交,等待运营审核' })).toBeDisabled();
expect(screen.getByTestId('project-publish-status')).toHaveTextContent('已提交,等待运营审核');
expect(screen.getByTestId('project-publish-status')).toHaveTextContent('无封面作品已提交');
});
it('shows a friendly Builder failure without raw details', async () => {
@@ -115,7 +142,7 @@ describe('ProjectPublishAction', () => {
);
render(<ProjectPublishAction project={project} projectType="mini_game" />);
fireEvent.click(screen.getByRole('button', { name: '一键提交审核' }));
await submitProjectMetadata();
await flushSubmission();
await advancePoll();
@@ -134,7 +161,7 @@ describe('ProjectPublishAction', () => {
);
render(<ProjectPublishAction project={project} projectType="mini_game" />);
fireEvent.click(screen.getByRole('button', { name: '一键提交审核' }));
await submitProjectMetadata();
await flushSubmission();
await advancePoll();
@@ -157,7 +184,7 @@ describe('ProjectPublishAction', () => {
fetchCurrentWorksProjectStatusMock.mockResolvedValue(projectStatus('succeeded'));
render(<ProjectPublishAction project={project} projectType="mini_game" />);
fireEvent.click(screen.getByRole('button', { name: '一键提交审核' }));
await submitProjectMetadata();
await flushSubmission();
expect(screen.getByTestId('project-publish-binding-warning')).toHaveTextContent(
@@ -167,7 +194,7 @@ describe('ProjectPublishAction', () => {
await advancePoll();
expect(fetchCurrentWorksProjectStatusMock).toHaveBeenCalledOnce();
expect(fetchCurrentWorksProjectStatusMock).toHaveBeenCalledTimes(2);
expect(screen.getByRole('button', { name: '已提交,等待运营审核' })).toBeDisabled();
expect(screen.getByTestId('project-publish-binding-warning')).toBeVisible();
});
@@ -178,7 +205,7 @@ describe('ProjectPublishAction', () => {
fetchCurrentWorksProjectStatusMock.mockResolvedValue(status);
render(<ProjectPublishAction project={project} projectType="mini_game" />);
fireEvent.click(screen.getByRole('button', { name: '一键提交审核' }));
await submitProjectMetadata();
await flushSubmission();
await act(async () => {
await vi.runAllTimersAsync();
@@ -188,7 +215,7 @@ describe('ProjectPublishAction', () => {
expect(failure).toHaveTextContent('构建时间比平时更长');
expect(failure).toHaveTextContent('不需要重复提交');
expect(screen.getByRole('button', { name: '已提交,请稍后查看' })).toBeDisabled();
expect(fetchCurrentWorksProjectStatusMock).toHaveBeenCalledTimes(300);
expect(fetchCurrentWorksProjectStatusMock).toHaveBeenCalledTimes(301);
});
it('sanitizes local and HTTP failures before rendering', async () => {
@@ -198,14 +225,31 @@ describe('ProjectPublishAction', () => {
));
render(<ProjectPublishAction project={project} projectType="mini_game" />);
fireEvent.click(screen.getByRole('button', { name: '一键提交审核' }));
await submitProjectMetadata();
await flushSubmission();
const failure = screen.getByTestId('project-publish-failure');
expect(failure).toHaveTextContent('发布服务暂时不可用');
expect(failure).not.toHaveTextContent('Traceback');
expect(failure).not.toHaveTextContent('INTERNAL_DATABASE_FAILURE');
expect(fetchCurrentWorksProjectStatusMock).not.toHaveBeenCalled();
expect(fetchCurrentWorksProjectStatusMock).toHaveBeenCalledOnce();
});
it('explains a first-create metadata race without claiming that existing metadata was saved', async () => {
publishWorksProjectSourceMock.mockRejectedValue(Object.assign(
new Error('private current metadata token=secret'),
{ code: 'PROJECT_METADATA_CONFLICT', statusCode: 409 },
));
render(<ProjectPublishAction project={project} projectType="mini_game" />);
await submitProjectMetadata();
await flushSubmission();
const failure = screen.getByTestId('project-publish-failure');
expect(failure).toHaveTextContent('作品资料已发生变化');
expect(failure).toHaveTextContent('本次没有提交版本');
expect(failure).toHaveTextContent('重新点击提交审核');
expect(failure).not.toHaveTextContent('token=secret');
});
it('turns a local package error into instructions for the user', async () => {
@@ -215,7 +259,7 @@ describe('ProjectPublishAction', () => {
));
render(<ProjectPublishAction project={project} projectType="mini_game" />);
fireEvent.click(screen.getByRole('button', { name: '一键提交审核' }));
await submitProjectMetadata();
await flushSubmission();
const failure = screen.getByTestId('project-publish-failure');
@@ -223,20 +267,109 @@ describe('ProjectPublishAction', () => {
expect(failure).toHaveTextContent('请让开发助手修复当前项目模板');
expect(failure).not.toHaveTextContent('D:/repo');
expect(failure).not.toHaveTextContent('PROJECT_FILE_MISSING');
expect(fetchCurrentWorksProjectStatusMock).not.toHaveBeenCalled();
expect(fetchCurrentWorksProjectStatusMock).toHaveBeenCalledOnce();
});
it('keeps generated metadata within the server limits', async () => {
const longName = `作品-${'x'.repeat(250)}`;
render(<ProjectPublishAction project={{ ...project, name: longName }} projectType="mini_program" />);
fireEvent.click(screen.getByRole('button', { name: '一键提交审核' }));
await submitProjectMetadata();
await flushSubmission();
const input = publishWorksProjectSourceMock.mock.calls[0]?.[0];
expect(input.project.app_id).toBe('makelore-prj-space-cleaner');
expect(input.project.title).toHaveLength(160);
expect(input.project.summary).toBe(`${input.project.title},由 Makelore 创建的作品。`);
expect(input.project.summary).toBe('这是一个太空清洁小游戏。');
expect(input.project.creator_name).toBe('小明');
expect(input.project.creator_age).toBe(12);
expect(input.project.category).toBe('mini_program');
});
it('submits an existing draft as version-only without editable metadata or replacement cover', async () => {
const draftStatus = projectStatus('queued');
draftStatus.project.title = '平台上的草稿标题';
draftStatus.project.summary = '平台上的草稿简介';
Object.assign(draftStatus.project, {
cover_url: 'coverimage/draft.jpg',
creator_name: '草稿作者',
creator_age: 10,
category: 'mini_game',
});
fetchCurrentWorksProjectStatusMock.mockResolvedValue(draftStatus);
render(<ProjectPublishAction project={project} projectType="mini_game" />);
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: '一键提交审核' }));
await Promise.resolve();
});
expect(screen.getByRole('heading', { name: '提交已有草稿的新版本' })).toBeVisible();
expect(screen.getByText(/当前发布契约不支持安全修改已有作品资料/)).toBeVisible();
expect(screen.getByText(/沿用平台现有作品资料与封面/)).toBeVisible();
expect(screen.queryByLabelText(/项目名称/)).not.toBeInTheDocument();
expect(screen.queryByLabelText(/项目封面/)).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '提交新版本' }));
await flushSubmission();
expect(publishWorksProjectSourceMock).toHaveBeenCalledWith({
projectId: project.id,
project: expect.objectContaining({
app_id: 'space-cleaner',
title: '平台上的草稿标题',
summary: '平台上的草稿简介',
cover_url: 'coverimage/draft.jpg',
creator_name: '草稿作者',
status: 'draft',
}),
});
expect(publishWorksProjectSourceMock.mock.calls[0]?.[0]).not.toHaveProperty('cover');
expect(screen.getByTestId('project-publish-status')).toHaveTextContent(
'现有作品资料与封面保持不变',
);
});
it('submits a published project as a version without requesting replacement metadata or cover', async () => {
const publishedStatus = projectStatus('succeeded');
publishedStatus.project.status = 'published';
publishedStatus.project.title = '已发布的太空清洁队';
publishedStatus.project.summary = '平台上现有的简介';
Object.assign(publishedStatus.project, {
description: '平台上现有的详细介绍',
cover_url: 'coverimage/existing.jpg',
creator_name: '原作者',
creator_age: 11,
category: 'mini_game',
age_band: '6-12岁',
difficulty: '入门',
});
fetchCurrentWorksProjectStatusMock.mockResolvedValue(publishedStatus);
render(<ProjectPublishAction project={project} projectType="mini_game" />);
await act(async () => {
fireEvent.click(screen.getByRole('button', { name: '一键提交审核' }));
await Promise.resolve();
});
expect(screen.getByRole('heading', { name: '提交已发布作品的新版本' })).toBeVisible();
expect(screen.getByText(/现有名称、简介、作者信息和封面都会保持不变/)).toBeVisible();
expect(screen.queryByLabelText(/项目封面/)).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '提交新版本' }));
await flushSubmission();
expect(publishWorksProjectSourceMock).toHaveBeenCalledWith({
projectId: project.id,
project: expect.objectContaining({
app_id: 'space-cleaner',
title: '已发布的太空清洁队',
summary: '平台上现有的简介',
cover_url: 'coverimage/existing.jpg',
creator_name: '原作者',
}),
});
expect(publishWorksProjectSourceMock.mock.calls[0]?.[0]).not.toHaveProperty('cover');
expect(screen.getByTestId('project-publish-status')).toHaveTextContent(
'现有作品资料与封面保持不变',
);
});
});