test-article-reading-routing.mjs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import assert from 'node:assert/strict'
  2. import { readFile } from 'node:fs/promises'
  3. const read = path => readFile(new URL(`../${path}`, import.meta.url), 'utf8')
  4. const frameEditor = await read('src/views/components/element/FrameElement/index.vue')
  5. const frameBase = await read('src/views/components/element/FrameElement/BaseFrameElement.vue')
  6. const operate = await read('src/views/Editor/Canvas/Operate/index.vue')
  7. const canvasTool = await read('src/views/Editor/CanvasTool/index2.vue')
  8. const canvas = await read('src/views/Editor/Canvas/index.vue')
  9. const toolbars = await Promise.all([
  10. read('src/components/CollapsibleToolbar/index.vue'),
  11. read('src/components/CollapsibleToolbar/index2.vue'),
  12. read('src/components/CollapsibleToolbar/index22.vue'),
  13. ])
  14. // 编辑态与只读/学生态都必须把 772 路由到文章预览
  15. assert.match(frameEditor, /ArticleReadingPreview/)
  16. assert.match(frameEditor, /isArticleReadingTool/)
  17. assert.match(frameBase, /ArticleReadingPreview/)
  18. assert.match(frameBase, /isArticleReadingTool/)
  19. assert.match(frameBase, /ARTICLE_READING_TOOL_TYPE/)
  20. // 锁定、重置、滚轮放行
  21. assert.match(operate, /isArticleReadingTool/)
  22. assert.match(canvasTool, /ARTICLE_READING_TOOL_TYPE/)
  23. assert.match(canvasTool, /articleStore\.requestResetPreview/)
  24. assert.match(canvas, /article-reading-preview/)
  25. // 三个工具栏名称映射
  26. for (const toolbar of toolbars) {
  27. assert.match(toolbar, /ARTICLE_READING_TOOL_TYPE/)
  28. assert.match(toolbar, /ssArticleReadingTool/)
  29. }
  30. // 编号只能来自命名常量:代码里不得出现 772 字面量(注释中说明编号是允许的)
  31. function stripComments(source) {
  32. return source
  33. .replace(/<!--[\s\S]*?-->/g, '')
  34. .replace(/\/\*[\s\S]*?\*\//g, '')
  35. .replace(/(^|[^:])\/\/.*$/gm, '$1')
  36. }
  37. const codeFiles = {
  38. frameEditor, frameBase, operate, canvasTool, canvas,
  39. toolbar0: toolbars[0], toolbar1: toolbars[1], toolbar2: toolbars[2],
  40. }
  41. for (const [name, source] of Object.entries(codeFiles)) {
  42. assert.doesNotMatch(
  43. stripComments(source),
  44. /\b772\b/,
  45. `${name} must reference ARTICLE_READING_TOOL_TYPE, not the literal 772`,
  46. )
  47. }
  48. // 话题讨论与句子跟读的既有编号不得被改写
  49. assert.match(frameBase, /77: 'ssEnglishSpeakingTool'/)
  50. assert.match(frameBase, /81: 'ssH5Page'/)
  51. // 文章分支不得回落到话题讨论组件
  52. const articleBranch = frameEditor.slice(frameEditor.indexOf('ArticleReadingPreview'))
  53. assert.doesNotMatch(articleBranch.slice(0, 200), /TopicDiscussionPreview/)
  54. // 老师端 Student runtime 按工具类型分流
  55. const studentView = await read('src/views/Student/index.vue')
  56. const articleClassComposable = await read(
  57. 'src/views/Student/components/ArticleReadingClassPanel/useArticleClassSummary.ts',
  58. )
  59. assert.match(studentView, /ArticleReadingClassPanel/)
  60. assert.match(studentView, /ARTICLE_READING_TOOL_TYPE/)
  61. assert.match(studentView, /TOPIC_DISCUSSION_TOOL_TYPE/)
  62. assert.match(articleClassComposable, /listArticleSessionsByConfig/)
  63. assert.match(articleClassComposable, /generateArticleClassSummary/)
  64. // 作业记录必须带真实工具号,不能写死 77
  65. assert.match(studentView, /atool: String\(activityToolType\)/)
  66. assert.doesNotMatch(studentView, /atool: '77'/)
  67. // 通用结果弹窗/提交按钮对文章与话题一视同仁地抑制
  68. assert.doesNotMatch(studentView, /currentSlideToolType !== 77/)
  69. assert.match(studentView, /articleReadingPanelRef\.value\?\.scheduleRefetch/)
  70. // 三个语言包都要有工具名与班级面板文案
  71. for (const loc of ['cn', 'en', 'hk']) {
  72. const pack = JSON.parse(await read(`src/views/lang/${loc}.json`))
  73. for (const key of [
  74. 'ssArticleReadingTool', 'ssArticleReading', 'ssArticleClassSummary',
  75. 'ssArticleFilterCompleted', 'ssArticleFilterReading',
  76. ]) {
  77. assert.ok(pack[key], `${loc}.json is missing ${key}`)
  78. }
  79. }
  80. // ---- 编辑态老师预览不进统计(spec §4.5):三道门都要在 ArticleReadingPreview 里 ----
  81. const articlePreview = await read('src/views/Editor/EnglishSpeaking/preview/ArticleReadingPreview.vue')
  82. // 门 1:runtime 判定不能只看 userid —— 编辑器 URL 里也有 userid。
  83. // 身份判据用项目既有的 type(Student/index.vue:625:老师端 1 / 学生端 2)。
  84. assert.match(articlePreview, /isStudentRuntime/)
  85. assert.match(articlePreview, /params\.get\('type'\)/)
  86. assert.match(articlePreview, /role === '2'/)
  87. // 门 2:编辑态把 null 传给引擎,不是空串
  88. assert.match(articlePreview, /sessionUserId/)
  89. // 门 3:resume 探针与进度广播只在学生态跑
  90. assert.match(articlePreview, /if \(!isStudentRuntime\.value\) return/)
  91. // 反向:不得再出现「只要有 userid 就当学生」的旧判据
  92. assert.doesNotMatch(articlePreview, /startDisabled = computed\(\(\) => !hasArticle\.value \|\| !runtimeUserId\.value\)/)
  93. // 反向:不得拿 mode 当身份 —— mode=student 是视图,老师上课时也在这个视图里,
  94. // 用它会把上课页的老师当成学生(编辑态那个 bug 换个地方复发),还得再去和
  95. // App.vue 的 localStorage viewMode 对账。type 是身份,与渲染哪个视图无关。
  96. assert.doesNotMatch(articlePreview, /'student'/)
  97. assert.doesNotMatch(articlePreview, /currentViewMode/)
  98. assert.doesNotMatch(await read('src/App.vue'), /currentViewMode/)
  99. // ---- 报告的词级形状必须跟住后端契约(spec §6):段落下面是**扁平的 words**。
  100. // 后端 c62d35a「flatten scoring/report to paragraph→words」把句层删了,前端的类型和
  101. // 取值路径没跟着改,于是任何一张已完成的段落卡片一展开就 `paragraph.sentences` 是
  102. // undefined、`.flatMap` 抛异常。类型说了谎,所以 type-check 也发现不了 —— 只能在这里钉。
  103. const articleTypes = await read('src/types/articleReading.ts')
  104. assert.match(articleTypes, /words: ArticleWordResult\[\]/)
  105. assert.doesNotMatch(articleTypes, /sentences/)
  106. assert.doesNotMatch(articleTypes, /ArticleSentenceResult/)
  107. const paragraphCard = await read('src/views/Editor/EnglishSpeaking/preview/ArticleParagraphCard.vue')
  108. assert.match(paragraphCard, /props\.paragraph\.words/)
  109. assert.doesNotMatch(paragraphCard, /flattenParagraphWords/)
  110. assert.doesNotMatch(
  111. await read('src/views/Editor/EnglishSpeaking/preview/articleReadingModel.ts'),
  112. /sentences/,
  113. )
  114. console.log('article reading routing: all assertions passed')