| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- import assert from 'node:assert/strict'
- import { readFile } from 'node:fs/promises'
- const read = path => readFile(new URL(`../${path}`, import.meta.url), 'utf8')
- const frameEditor = await read('src/views/components/element/FrameElement/index.vue')
- const frameBase = await read('src/views/components/element/FrameElement/BaseFrameElement.vue')
- const operate = await read('src/views/Editor/Canvas/Operate/index.vue')
- const canvasTool = await read('src/views/Editor/CanvasTool/index2.vue')
- const canvas = await read('src/views/Editor/Canvas/index.vue')
- const toolbars = await Promise.all([
- read('src/components/CollapsibleToolbar/index.vue'),
- read('src/components/CollapsibleToolbar/index2.vue'),
- read('src/components/CollapsibleToolbar/index22.vue'),
- ])
- // 编辑态与只读/学生态都必须把 772 路由到文章预览
- assert.match(frameEditor, /ArticleReadingPreview/)
- assert.match(frameEditor, /isArticleReadingTool/)
- assert.match(frameBase, /ArticleReadingPreview/)
- assert.match(frameBase, /isArticleReadingTool/)
- assert.match(frameBase, /ARTICLE_READING_TOOL_TYPE/)
- // 锁定、重置、滚轮放行
- assert.match(operate, /isArticleReadingTool/)
- assert.match(canvasTool, /ARTICLE_READING_TOOL_TYPE/)
- assert.match(canvasTool, /articleStore\.requestResetPreview/)
- assert.match(canvas, /article-reading-preview/)
- // 三个工具栏名称映射
- for (const toolbar of toolbars) {
- assert.match(toolbar, /ARTICLE_READING_TOOL_TYPE/)
- assert.match(toolbar, /ssArticleReadingTool/)
- }
- // 编号只能来自命名常量:代码里不得出现 772 字面量(注释中说明编号是允许的)
- function stripComments(source) {
- return source
- .replace(/<!--[\s\S]*?-->/g, '')
- .replace(/\/\*[\s\S]*?\*\//g, '')
- .replace(/(^|[^:])\/\/.*$/gm, '$1')
- }
- const codeFiles = {
- frameEditor, frameBase, operate, canvasTool, canvas,
- toolbar0: toolbars[0], toolbar1: toolbars[1], toolbar2: toolbars[2],
- }
- for (const [name, source] of Object.entries(codeFiles)) {
- assert.doesNotMatch(
- stripComments(source),
- /\b772\b/,
- `${name} must reference ARTICLE_READING_TOOL_TYPE, not the literal 772`,
- )
- }
- // 话题讨论与句子跟读的既有编号不得被改写
- assert.match(frameBase, /77: 'ssEnglishSpeakingTool'/)
- assert.match(frameBase, /81: 'ssH5Page'/)
- // 文章分支不得回落到话题讨论组件
- const articleBranch = frameEditor.slice(frameEditor.indexOf('ArticleReadingPreview'))
- assert.doesNotMatch(articleBranch.slice(0, 200), /TopicDiscussionPreview/)
- // 老师端 Student runtime 按工具类型分流
- const studentView = await read('src/views/Student/index.vue')
- const articleClassComposable = await read(
- 'src/views/Student/components/ArticleReadingClassPanel/useArticleClassSummary.ts',
- )
- assert.match(studentView, /ArticleReadingClassPanel/)
- assert.match(studentView, /ARTICLE_READING_TOOL_TYPE/)
- assert.match(studentView, /TOPIC_DISCUSSION_TOOL_TYPE/)
- assert.match(articleClassComposable, /listArticleSessionsByConfig/)
- assert.match(articleClassComposable, /generateArticleClassSummary/)
- // 作业记录必须带真实工具号,不能写死 77
- assert.match(studentView, /atool: String\(activityToolType\)/)
- assert.doesNotMatch(studentView, /atool: '77'/)
- // 通用结果弹窗/提交按钮对文章与话题一视同仁地抑制
- assert.doesNotMatch(studentView, /currentSlideToolType !== 77/)
- assert.match(studentView, /articleReadingPanelRef\.value\?\.scheduleRefetch/)
- // 三个语言包都要有工具名与班级面板文案
- for (const loc of ['cn', 'en', 'hk']) {
- const pack = JSON.parse(await read(`src/views/lang/${loc}.json`))
- for (const key of [
- 'ssArticleReadingTool', 'ssArticleReading', 'ssArticleClassSummary',
- 'ssArticleFilterCompleted', 'ssArticleFilterReading',
- ]) {
- assert.ok(pack[key], `${loc}.json is missing ${key}`)
- }
- }
- // ---- 编辑态老师预览不进统计(spec §4.5):三道门都要在 ArticleReadingPreview 里 ----
- const articlePreview = await read('src/views/Editor/EnglishSpeaking/preview/ArticleReadingPreview.vue')
- // 门 1:runtime 判定不能只看 userid —— 编辑器 URL 里也有 userid。
- // 身份判据用项目既有的 type(Student/index.vue:625:老师端 1 / 学生端 2)。
- assert.match(articlePreview, /isStudentRuntime/)
- assert.match(articlePreview, /params\.get\('type'\)/)
- assert.match(articlePreview, /role === '2'/)
- // 门 2:编辑态把 null 传给引擎,不是空串
- assert.match(articlePreview, /sessionUserId/)
- // 门 3:resume 探针与进度广播只在学生态跑
- assert.match(articlePreview, /if \(!isStudentRuntime\.value\) return/)
- // 反向:不得再出现「只要有 userid 就当学生」的旧判据
- assert.doesNotMatch(articlePreview, /startDisabled = computed\(\(\) => !hasArticle\.value \|\| !runtimeUserId\.value\)/)
- // 反向:不得拿 mode 当身份 —— mode=student 是视图,老师上课时也在这个视图里,
- // 用它会把上课页的老师当成学生(编辑态那个 bug 换个地方复发),还得再去和
- // App.vue 的 localStorage viewMode 对账。type 是身份,与渲染哪个视图无关。
- assert.doesNotMatch(articlePreview, /'student'/)
- assert.doesNotMatch(articlePreview, /currentViewMode/)
- assert.doesNotMatch(await read('src/App.vue'), /currentViewMode/)
- // ---- 报告的词级形状必须跟住后端契约(spec §6):段落下面是**扁平的 words**。
- // 后端 c62d35a「flatten scoring/report to paragraph→words」把句层删了,前端的类型和
- // 取值路径没跟着改,于是任何一张已完成的段落卡片一展开就 `paragraph.sentences` 是
- // undefined、`.flatMap` 抛异常。类型说了谎,所以 type-check 也发现不了 —— 只能在这里钉。
- const articleTypes = await read('src/types/articleReading.ts')
- assert.match(articleTypes, /words: ArticleWordResult\[\]/)
- assert.doesNotMatch(articleTypes, /sentences/)
- assert.doesNotMatch(articleTypes, /ArticleSentenceResult/)
- const paragraphCard = await read('src/views/Editor/EnglishSpeaking/preview/ArticleParagraphCard.vue')
- assert.match(paragraphCard, /props\.paragraph\.words/)
- assert.doesNotMatch(paragraphCard, /flattenParagraphWords/)
- assert.doesNotMatch(
- await read('src/views/Editor/EnglishSpeaking/preview/articleReadingModel.ts'),
- /sentences/,
- )
- console.log('article reading routing: all assertions passed')
|