workbench.test.mjs 3.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import assert from 'node:assert/strict'
  2. import { test } from 'node:test'
  3. import {
  4. addRecord,
  5. advanceMockResults,
  6. createInitialState,
  7. formatTime,
  8. markRead,
  9. recognizePhotos,
  10. students,
  11. validateUpload
  12. } from '../src/data/workbench.ts'
  13. const photo = (id) => ({ id, path: '', name: id, demo: true })
  14. const metadata = { name: '测试卷', year: '2026', paperType: '模拟', province: '浙江省' }
  15. test('appending and deleting photos retains question edits and renumbers pages', () => {
  16. const first = recognizePhotos([photo('a'), photo('b')])
  17. first[0].content = '修改后的题干'
  18. first[0].options[0] = '修改后的选项'
  19. first[0].isWrong = true
  20. first[0].tags.push('几何')
  21. const appended = recognizePhotos([photo('a'), photo('b'), photo('c')], first)
  22. assert.equal(appended.length, 9)
  23. assert.equal(appended[0].content, '修改后的题干')
  24. assert.equal(appended[0].options[0], '修改后的选项')
  25. assert.equal(appended[0].isWrong, true)
  26. assert.ok(appended[0].tags.includes('几何'))
  27. const removed = recognizePhotos([photo('b'), photo('c')], appended)
  28. assert.equal(removed.length, 6)
  29. assert.equal(removed[0].page, 1)
  30. assert.equal(removed[3].page, 2)
  31. assert.deepEqual(
  32. removed.map((q) => q.num),
  33. [1, 2, 3, 4, 5, 6]
  34. )
  35. })
  36. test('required fields and student identity work across different classes with identical names', () => {
  37. assert.notEqual(students[0].id, students[3].id)
  38. assert.equal(students[0].name, students[3].name)
  39. assert.equal(validateUpload('mistakes', [photo('a')], [], metadata), '请先关联学生')
  40. assert.equal(validateUpload('mistakes', [photo('a')], [students[0], students[3]], metadata), '')
  41. for (const field of ['name', 'year', 'paperType', 'province'])
  42. assert.ok(validateUpload('papers', [photo('a')], [], { ...metadata, [field]: '' }))
  43. assert.equal(validateUpload('answers', [], [], metadata), '请先添加照片')
  44. })
  45. test('submission, approval and explanation completion are idempotent and survive serialization', () => {
  46. const now = Date.now()
  47. let state = createInitialState()
  48. const record = {
  49. id: 'new-paper',
  50. kind: 'papers',
  51. ...metadata,
  52. photos: [photo('a')],
  53. questions: recognizePhotos([photo('a')]),
  54. students: [],
  55. submittedAt: new Date(now).toISOString(),
  56. reviewStatus: 'pending'
  57. }
  58. state = addRecord(state, record)
  59. assert.equal(addRecord(state, record), state)
  60. assert.equal(state.records[0].reviewStatus, 'pending')
  61. state = JSON.parse(JSON.stringify(state))
  62. const complete = advanceMockResults(state, now + 21000)
  63. assert.equal(complete.records[0].reviewStatus, 'approved')
  64. assert.equal(complete.notices.filter((n) => n.id === 'approved-new-paper').length, 1)
  65. assert.equal(
  66. complete.explanations.find((v) => v.status === 'waiting'),
  67. undefined
  68. )
  69. assert.equal(advanceMockResults(complete, now + 30000), complete)
  70. assert.equal(state.records[0].reviewStatus, 'pending', 'previous state must remain immutable')
  71. })
  72. test('opening an explanation synchronizes its message badge, while unrelated notices stay unread', () => {
  73. const initial = createInitialState()
  74. const read = markRead(initial, initial.explanations[0].id)
  75. assert.equal(read.explanations[0].unread, false)
  76. assert.equal(read.notices[0].unread, false)
  77. assert.equal(read.notices[1].unread, true)
  78. const all = markRead(read)
  79. assert.ok(all.notices.every((n) => !n.unread))
  80. assert.ok(all.explanations.every((v) => !v.unread))
  81. })
  82. test('China time display stays stable across device timezones', () => {
  83. assert.equal(formatTime('2026-10-26T01:05:00Z'), '2026/10/26 09:05')
  84. assert.equal(formatTime('invalid'), '待同步')
  85. })