125 lines
4.3 KiB
TypeScript
125 lines
4.3 KiB
TypeScript
import { describe, expect, test } from 'vitest';
|
||
import type { PersistedClassroomData } from '@/lib/server/classroom-storage';
|
||
import {
|
||
buildCourseModuleOutputDigest,
|
||
formatPreviousModuleContext,
|
||
} from '@/lib/course-framework/module-digest';
|
||
|
||
function classroomFixture(): PersistedClassroomData {
|
||
return {
|
||
id: 'classroom-actual-1',
|
||
createdAt: '2026-08-15T09:00:00.000Z',
|
||
stage: { id: 'stage-1', name: '实际生成的机器学习基础' } as never,
|
||
scenes: [
|
||
{
|
||
id: 'scene-slide',
|
||
stageId: 'stage-1',
|
||
order: 1,
|
||
title: '监督学习基础',
|
||
type: 'slide',
|
||
content: {
|
||
type: 'slide',
|
||
canvas: {
|
||
id: 'slide-1',
|
||
viewportSize: 1000,
|
||
viewportRatio: 0.5625,
|
||
theme: {},
|
||
elements: [
|
||
{
|
||
id: 'text-1',
|
||
type: 'text',
|
||
content: '<p>特征、标签与训练样本</p>',
|
||
},
|
||
],
|
||
},
|
||
},
|
||
actions: [{ id: 'speech-1', type: 'speech', text: '比较监督学习和无监督学习。' }],
|
||
},
|
||
{
|
||
id: 'scene-interactive',
|
||
stageId: 'stage-1',
|
||
order: 2,
|
||
title: '分类边界实验室',
|
||
type: 'interactive',
|
||
content: {
|
||
type: 'interactive',
|
||
widgetType: 'simulation',
|
||
widgetConfig: { type: 'simulation', concept: '决策边界', challenge: '调整样本分布' },
|
||
html: '<style>.noise{color:red}</style><h1>拖动数据点观察边界变化</h1><script>ignore()</script>',
|
||
},
|
||
},
|
||
{
|
||
id: 'scene-quiz',
|
||
stageId: 'stage-1',
|
||
order: 3,
|
||
title: '概念检查',
|
||
type: 'quiz',
|
||
content: {
|
||
type: 'quiz',
|
||
questions: [
|
||
{
|
||
id: 'q-1',
|
||
type: 'single',
|
||
question: '标签在监督学习中承担什么作用?',
|
||
},
|
||
],
|
||
},
|
||
},
|
||
{
|
||
id: 'scene-pbl',
|
||
stageId: 'stage-1',
|
||
order: 4,
|
||
title: '识别真实业务问题',
|
||
type: 'pbl',
|
||
content: {
|
||
type: 'pbl',
|
||
projectV2: {
|
||
title: '业务问题分类项目',
|
||
description: '判断问题适合监督学习还是无监督学习。',
|
||
learningObjective: '能够把业务目标转成学习任务。',
|
||
milestones: [{ title: '提交问题定义' }],
|
||
},
|
||
},
|
||
},
|
||
] as never,
|
||
};
|
||
}
|
||
|
||
describe('course module output digest', () => {
|
||
test('extracts bounded evidence from all four actual scene types', () => {
|
||
const digest = buildCourseModuleOutputDigest(classroomFixture());
|
||
|
||
expect(digest.sceneTypeCounts).toEqual({ slide: 1, quiz: 1, interactive: 1, pbl: 1 });
|
||
expect(digest.sceneCount).toBe(4);
|
||
expect(digest.coveredConcepts).toContain('监督学习基础');
|
||
expect(digest.coveredConcepts).toContain('特征、标签与训练样本');
|
||
expect(digest.coveredConcepts).toContain('拖动数据点观察边界变化');
|
||
expect(digest.coveredConcepts.join(' ')).not.toContain('ignore()');
|
||
expect(digest.assessments).toContain('标签在监督学习中承担什么作用?');
|
||
expect(digest.assessments).toContain('提交问题定义');
|
||
expect(digest.interactiveExperiences).toEqual([
|
||
'分类边界实验室(simulation, interactive-html)',
|
||
'识别真实业务问题(PBL)',
|
||
]);
|
||
expect(digest.semanticHash).toMatch(/^sha256:[a-f0-9]{64}$/);
|
||
expect(buildCourseModuleOutputDigest(classroomFixture()).semanticHash).toBe(
|
||
digest.semanticHash,
|
||
);
|
||
});
|
||
|
||
test('formats actual prior outputs as bounded continuity context', () => {
|
||
const digest = buildCourseModuleOutputDigest(classroomFixture());
|
||
const context = formatPreviousModuleContext([
|
||
{ index: 1, title: '基础模块', outputDigest: digest },
|
||
{ index: 2, title: '练习模块', outputDigest: { ...digest, classroomId: 'classroom-2' } },
|
||
]);
|
||
|
||
expect(context).toContain('模块 1《基础模块》');
|
||
expect(context).toContain('实际摘要');
|
||
expect(context).toContain('实际覆盖');
|
||
expect(context).toContain('实际语义哈希');
|
||
expect(context).toContain('分类边界实验室(simulation, interactive-html)');
|
||
expect(context.length).toBeLessThanOrEqual(12_000);
|
||
});
|
||
});
|