@khanacademy/perseus 19.1.5 → 19.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (489) hide show
  1. package/dist/components/graph.d.ts +0 -1
  2. package/dist/components/graphie.d.ts +0 -1
  3. package/dist/es/index.js +319 -212
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.js +338 -222
  6. package/dist/index.js.map +1 -1
  7. package/dist/interactive2/movable-point.d.ts +195 -1
  8. package/dist/interactive2/movable.d.ts +1 -1
  9. package/dist/interactive2/types.d.ts +4 -2
  10. package/dist/interactive2.d.ts +2 -1
  11. package/dist/server-item-renderer.d.ts +2 -2
  12. package/dist/util/graphie.d.ts +12 -2
  13. package/dist/widgets/expression.d.ts +6 -6
  14. package/package.json +11 -8
  15. package/.babelrc.js +0 -8
  16. package/.eslintrc.js +0 -12
  17. package/CHANGELOG.md +0 -1432
  18. package/src/__stories__/article-renderer.stories.tsx +0 -100
  19. package/src/__stories__/hints-renderer.stories.tsx +0 -55
  20. package/src/__stories__/server-item-renderer.stories.tsx +0 -44
  21. package/src/__testdata__/article-renderer.testdata.ts +0 -212
  22. package/src/__testdata__/graphie.testdata.ts +0 -51
  23. package/src/__testdata__/renderer.testdata.ts +0 -274
  24. package/src/__testdata__/server-item-renderer.testdata.ts +0 -171
  25. package/src/__tests__/__snapshots__/perseus-markdown.test.ts.snap +0 -507
  26. package/src/__tests__/__snapshots__/renderer.test.tsx.snap +0 -525
  27. package/src/__tests__/__snapshots__/server-item-renderer.test.tsx.snap +0 -285
  28. package/src/__tests__/a11y.test.ts +0 -303
  29. package/src/__tests__/article-renderer.test.tsx +0 -130
  30. package/src/__tests__/error-boundary.test.tsx +0 -76
  31. package/src/__tests__/extract-perseus-data.test.ts +0 -1027
  32. package/src/__tests__/init.test.ts +0 -10
  33. package/src/__tests__/jipt-paragraphs.test.ts +0 -32
  34. package/src/__tests__/mock-asset-loading-widget.tsx +0 -53
  35. package/src/__tests__/mock-widget.tsx +0 -33
  36. package/src/__tests__/perseus-markdown.test.ts +0 -764
  37. package/src/__tests__/renderability.test.ts +0 -480
  38. package/src/__tests__/renderer-api.test.tsx +0 -302
  39. package/src/__tests__/renderer.test.tsx +0 -1816
  40. package/src/__tests__/server-item-renderer.test.tsx +0 -582
  41. package/src/__tests__/test-items/image-item.ts +0 -24
  42. package/src/__tests__/test-items/input-number-1-item.ts +0 -26
  43. package/src/__tests__/test-items/input-number-2-item.ts +0 -38
  44. package/src/__tests__/test-items/table-item.ts +0 -29
  45. package/src/__tests__/tex-wrangler.test.ts +0 -82
  46. package/src/__tests__/util.test.ts +0 -40
  47. package/src/__tests__/widget-container.test.tsx +0 -126
  48. package/src/__tests__/widgets.test.ts +0 -91
  49. package/src/a11y.ts +0 -45
  50. package/src/all-widgets.ts +0 -9
  51. package/src/article-renderer.tsx +0 -237
  52. package/src/asset-context.ts +0 -30
  53. package/src/basic-widgets.ts +0 -16
  54. package/src/components/__stories__/button-group.stories.tsx +0 -55
  55. package/src/components/__stories__/fixed-to-responsive.stories.tsx +0 -141
  56. package/src/components/__stories__/graph.stories.tsx +0 -28
  57. package/src/components/__stories__/graphie.stories.tsx +0 -33
  58. package/src/components/__stories__/hud.stories.tsx +0 -35
  59. package/src/components/__stories__/icon.stories.tsx +0 -63
  60. package/src/components/__stories__/image-loader.stories.tsx +0 -63
  61. package/src/components/__stories__/info-tip.stories.tsx +0 -31
  62. package/src/components/__stories__/inline-icon.stories.tsx +0 -40
  63. package/src/components/__stories__/input-with-examples.stories.tsx +0 -53
  64. package/src/components/__stories__/lint.stories.tsx +0 -86
  65. package/src/components/__stories__/math-input.stories.tsx +0 -43
  66. package/src/components/__stories__/math-output.stories.tsx +0 -23
  67. package/src/components/__stories__/multi-button-group.stories.tsx +0 -66
  68. package/src/components/__stories__/number-input.stories.tsx +0 -41
  69. package/src/components/__stories__/prop-check-box.stories.tsx +0 -65
  70. package/src/components/__stories__/range-input.stories.tsx +0 -29
  71. package/src/components/__stories__/simple-keypad-input.stories.tsx +0 -27
  72. package/src/components/__stories__/sortable.stories.tsx +0 -77
  73. package/src/components/__stories__/stub-tag-editor.stories.tsx +0 -45
  74. package/src/components/__stories__/svg-image.stories.tsx +0 -74
  75. package/src/components/__stories__/tex.stories.tsx +0 -23
  76. package/src/components/__stories__/text-input.stories.tsx +0 -33
  77. package/src/components/__stories__/text-list-editor.stories.tsx +0 -32
  78. package/src/components/__stories__/tooltip.stories.tsx +0 -52
  79. package/src/components/__stories__/zoomable-tex.stories.tsx +0 -41
  80. package/src/components/__stories__/zoomable.stories.tsx +0 -49
  81. package/src/components/__testdata__/sorter.testdata.ts +0 -19
  82. package/src/components/__tests__/__snapshots__/sortable.test.tsx.snap +0 -163
  83. package/src/components/__tests__/__snapshots__/sorter.test.tsx.snap +0 -265
  84. package/src/components/__tests__/math-input.test.tsx +0 -147
  85. package/src/components/__tests__/number-input.test.tsx +0 -128
  86. package/src/components/__tests__/sortable.test.tsx +0 -269
  87. package/src/components/__tests__/sorter.test.tsx +0 -114
  88. package/src/components/__tests__/zoomable.test.tsx +0 -399
  89. package/src/components/button-group.tsx +0 -130
  90. package/src/components/constants.ts +0 -14
  91. package/src/components/fixed-to-responsive.tsx +0 -169
  92. package/src/components/graph.tsx +0 -444
  93. package/src/components/graphie-classes.ts +0 -153
  94. package/src/components/graphie-movables.ts +0 -338
  95. package/src/components/graphie.tsx +0 -400
  96. package/src/components/highlighting/highlightable-content.tsx +0 -240
  97. package/src/components/highlighting/highlights.ts +0 -207
  98. package/src/components/highlighting/ranges.ts +0 -304
  99. package/src/components/highlighting/types.ts +0 -73
  100. package/src/components/highlighting/ui/highlight-renderer.tsx +0 -172
  101. package/src/components/highlighting/ui/highlight-set-renderer.tsx +0 -185
  102. package/src/components/highlighting/ui/highlight-tooltip.tsx +0 -141
  103. package/src/components/highlighting/ui/highlighting-ui.tsx +0 -143
  104. package/src/components/highlighting/ui/selection-tracker.tsx +0 -186
  105. package/src/components/highlighting/ui/types.ts +0 -21
  106. package/src/components/highlighting/ui/util.ts +0 -165
  107. package/src/components/highlighting/word-indexer.tsx +0 -152
  108. package/src/components/hud.tsx +0 -167
  109. package/src/components/icon.tsx +0 -141
  110. package/src/components/image-loader.tsx +0 -177
  111. package/src/components/info-tip/info-tip.tsx +0 -144
  112. package/src/components/info-tip.tsx +0 -39
  113. package/src/components/inline-icon.tsx +0 -60
  114. package/src/components/input-with-examples.tsx +0 -249
  115. package/src/components/lint.tsx +0 -459
  116. package/src/components/math-input.tsx +0 -474
  117. package/src/components/math-output.tsx +0 -134
  118. package/src/components/multi-button-group.tsx +0 -135
  119. package/src/components/number-input.tsx +0 -249
  120. package/src/components/prop-check-box.tsx +0 -79
  121. package/src/components/range-input.tsx +0 -60
  122. package/src/components/simple-keypad-input.tsx +0 -88
  123. package/src/components/sortable.tsx +0 -958
  124. package/src/components/stub-tag-editor.tsx +0 -53
  125. package/src/components/svg-image.tsx +0 -779
  126. package/src/components/tex.tsx +0 -67
  127. package/src/components/text-input.tsx +0 -129
  128. package/src/components/text-list-editor.tsx +0 -166
  129. package/src/components/tooltip.tsx +0 -410
  130. package/src/components/visibility-observer/__stories__/visibility-observer.stories.tsx +0 -141
  131. package/src/components/visibility-observer/create-visibility-observer.ts +0 -108
  132. package/src/components/visibility-observer/get-scroll-parent.ts +0 -46
  133. package/src/components/visibility-observer/visibility-observer.tsx +0 -124
  134. package/src/components/zoomable-tex.tsx +0 -78
  135. package/src/components/zoomable.tsx +0 -304
  136. package/src/components.ts +0 -15
  137. package/src/definition-context.tsx +0 -59
  138. package/src/dependencies.ts +0 -40
  139. package/src/error-boundary.tsx +0 -70
  140. package/src/extra-widgets.ts +0 -75
  141. package/src/hint-renderer.tsx +0 -151
  142. package/src/hints-renderer.tsx +0 -372
  143. package/src/icon-paths.ts +0 -119
  144. package/src/index.ts +0 -171
  145. package/src/init.ts +0 -56
  146. package/src/interaction-tracker.ts +0 -62
  147. package/src/interactive2/__tests__/movable-mock.ts +0 -55
  148. package/src/interactive2/__tests__/movable-point.test.ts +0 -144
  149. package/src/interactive2/interactive-util.test.ts +0 -93
  150. package/src/interactive2/interactive-util.ts +0 -159
  151. package/src/interactive2/movable-helper-methods.ts +0 -166
  152. package/src/interactive2/movable-line-options.ts +0 -419
  153. package/src/interactive2/movable-line.ts +0 -307
  154. package/src/interactive2/movable-point-options.ts +0 -224
  155. package/src/interactive2/movable-point.tsx +0 -585
  156. package/src/interactive2/movable-polygon-options.ts +0 -389
  157. package/src/interactive2/movable-polygon.ts +0 -360
  158. package/src/interactive2/movable.test.ts +0 -249
  159. package/src/interactive2/movable.ts +0 -437
  160. package/src/interactive2/objective_.ts +0 -79
  161. package/src/interactive2/types.ts +0 -17
  162. package/src/interactive2/wrapped-defaults.ts +0 -87
  163. package/src/interactive2/wrapped-ellipse.ts +0 -94
  164. package/src/interactive2/wrapped-line.ts +0 -116
  165. package/src/interactive2/wrapped-path.ts +0 -51
  166. package/src/interactive2.ts +0 -70
  167. package/src/item-version.ts +0 -10
  168. package/src/jipt-paragraphs.ts +0 -49
  169. package/src/jquery.mobile.vmouse.js +0 -545
  170. package/src/loading-context.ts +0 -21
  171. package/src/logging/log.test.ts +0 -39
  172. package/src/logging/log.ts +0 -97
  173. package/src/mixins/__tests__/widget-prop-denylist.test.ts +0 -21
  174. package/src/mixins/changeable.ts +0 -133
  175. package/src/mixins/editor-jsonify.ts +0 -13
  176. package/src/mixins/widget-jsonify-deprecated.ts +0 -23
  177. package/src/mixins/widget-prop-denylist.ts +0 -48
  178. package/src/multi-items/__stories__/multi-renderer.stories.tsx +0 -83
  179. package/src/multi-items/__testdata__/multi-renderer.testdata.ts +0 -172
  180. package/src/multi-items/__tests__/__snapshots__/multi-renderer.test.tsx.snap +0 -1164
  181. package/src/multi-items/__tests__/items.test.ts +0 -240
  182. package/src/multi-items/__tests__/multi-renderer.test.tsx +0 -914
  183. package/src/multi-items/__tests__/prop-type-builders.test.ts +0 -133
  184. package/src/multi-items/__tests__/shapes.test.ts +0 -43
  185. package/src/multi-items/__tests__/trees.test.ts +0 -317
  186. package/src/multi-items/item-types.ts +0 -56
  187. package/src/multi-items/items.ts +0 -176
  188. package/src/multi-items/multi-renderer.tsx +0 -569
  189. package/src/multi-items/prop-type-builders.ts +0 -106
  190. package/src/multi-items/shape-types.ts +0 -52
  191. package/src/multi-items/shapes.ts +0 -52
  192. package/src/multi-items/tree-types.ts +0 -47
  193. package/src/multi-items/trees.ts +0 -360
  194. package/src/multi-items.ts +0 -75
  195. package/src/perseus-api.tsx +0 -188
  196. package/src/perseus-env.ts +0 -27
  197. package/src/perseus-error.ts +0 -17
  198. package/src/perseus-markdown.tsx +0 -405
  199. package/src/perseus-types.ts +0 -1366
  200. package/src/perseus-version.ts +0 -9
  201. package/src/question-paragraph.tsx +0 -34
  202. package/src/renderability.ts +0 -109
  203. package/src/renderer.tsx +0 -2000
  204. package/src/server-item-renderer.tsx +0 -495
  205. package/src/sigfigs.ts +0 -490
  206. package/src/styles/articles.less +0 -749
  207. package/src/styles/constants.ts +0 -94
  208. package/src/styles/global-constants.ts +0 -343
  209. package/src/styles/global-styles.ts +0 -306
  210. package/src/styles/khan-exercise.css +0 -1468
  211. package/src/styles/media-queries.ts +0 -47
  212. package/src/styles/perseus-renderer.less +0 -732
  213. package/src/styles/shared.ts +0 -130
  214. package/src/styles/util.less +0 -47
  215. package/src/styles/variables.boostrap.less +0 -277
  216. package/src/styles/variables.less +0 -56
  217. package/src/styles/variables.shared.less +0 -340
  218. package/src/styles/widgets/categorizer.less +0 -38
  219. package/src/styles/widgets/dropdown.less +0 -3
  220. package/src/styles/widgets/expression.less +0 -65
  221. package/src/styles/widgets/graded-group.less +0 -36
  222. package/src/styles/widgets/grapher.less +0 -12
  223. package/src/styles/widgets/group.less +0 -19
  224. package/src/styles/widgets/image.less +0 -54
  225. package/src/styles/widgets/interactive-graph.less +0 -64
  226. package/src/styles/widgets/label-image.less +0 -57
  227. package/src/styles/widgets/matcher.less +0 -8
  228. package/src/styles/widgets/matrix.less +0 -126
  229. package/src/styles/widgets/measurer.less +0 -20
  230. package/src/styles/widgets/orderer.less +0 -98
  231. package/src/styles/widgets/passage.less +0 -154
  232. package/src/styles/widgets/plotter.less +0 -31
  233. package/src/styles/widgets/radio.less +0 -58
  234. package/src/styles/widgets/reaction-diagram.less +0 -35
  235. package/src/styles/widgets/sequence.less +0 -5
  236. package/src/styles/widgets/sortable.less +0 -113
  237. package/src/styles/widgets/table.less +0 -57
  238. package/src/styles/widgets/unit.less +0 -15
  239. package/src/tex-wrangler.ts +0 -182
  240. package/src/translation-linter.ts +0 -126
  241. package/src/traversal.ts +0 -138
  242. package/src/types.ts +0 -513
  243. package/src/util/a11y.ts +0 -23
  244. package/src/util/answer-types.test.ts +0 -114
  245. package/src/util/answer-types.ts +0 -876
  246. package/src/util/colors.ts +0 -93
  247. package/src/util/drawing-transform.test.ts +0 -67
  248. package/src/util/drawing-transform.ts +0 -102
  249. package/src/util/extract-perseus-data.ts +0 -539
  250. package/src/util/fix-passage-refs.ts +0 -121
  251. package/src/util/geometry.test.ts +0 -75
  252. package/src/util/geometry.ts +0 -312
  253. package/src/util/graph-bounds.ts +0 -23
  254. package/src/util/graph-utils.ts +0 -15
  255. package/src/util/graphie.test.ts +0 -1277
  256. package/src/util/graphie.ts +0 -1576
  257. package/src/util/interactive.ts +0 -4174
  258. package/src/util/interval.ts +0 -4
  259. package/src/util/katex-preprocess.ts +0 -14
  260. package/src/util/math.test.ts +0 -11
  261. package/src/util/math.ts +0 -294
  262. package/src/util/mobile-native-utils.test.ts +0 -22
  263. package/src/util/mobile-native-utils.ts +0 -12
  264. package/src/util/parse-perseus-json.test.ts +0 -10
  265. package/src/util/parse-perseus-json.ts +0 -13
  266. package/src/util/react-render.tsx +0 -27
  267. package/src/util/register-all-widgets-for-testing.ts +0 -13
  268. package/src/util/scroll-utils.ts +0 -155
  269. package/src/util/sizing-utils.ts +0 -46
  270. package/src/util/tex.ts +0 -73
  271. package/src/util/url-utils.ts +0 -34
  272. package/src/util.graphie.test.ts +0 -45
  273. package/src/util.graphie.ts +0 -59
  274. package/src/util.ts +0 -966
  275. package/src/version.ts +0 -10
  276. package/src/widget-container.tsx +0 -199
  277. package/src/widgets/__stories__/categorizer.stories.tsx +0 -14
  278. package/src/widgets/__stories__/cs-program.stories.tsx +0 -14
  279. package/src/widgets/__stories__/definition.stories.tsx +0 -114
  280. package/src/widgets/__stories__/dropdown.stories.tsx +0 -14
  281. package/src/widgets/__stories__/explanation.stories.tsx +0 -18
  282. package/src/widgets/__stories__/expression.stories.tsx +0 -145
  283. package/src/widgets/__stories__/graded-group-set.stories.tsx +0 -31
  284. package/src/widgets/__stories__/graded-group.stories.tsx +0 -31
  285. package/src/widgets/__stories__/grapher.stories.tsx +0 -46
  286. package/src/widgets/__stories__/group.stories.tsx +0 -14
  287. package/src/widgets/__stories__/iframe.stories.tsx +0 -14
  288. package/src/widgets/__stories__/image.stories.tsx +0 -85
  289. package/src/widgets/__stories__/input-number.stories.tsx +0 -101
  290. package/src/widgets/__stories__/interaction.stories.tsx +0 -14
  291. package/src/widgets/__stories__/interactive-graph.stories.tsx +0 -60
  292. package/src/widgets/__stories__/label-image.stories.tsx +0 -130
  293. package/src/widgets/__stories__/matcher.stories.tsx +0 -14
  294. package/src/widgets/__stories__/matrix.stories.tsx +0 -14
  295. package/src/widgets/__stories__/number-line.stories.tsx +0 -14
  296. package/src/widgets/__stories__/numeric-input.stories.tsx +0 -105
  297. package/src/widgets/__stories__/orderer.stories.tsx +0 -18
  298. package/src/widgets/__stories__/passage-ref.stories.tsx +0 -33
  299. package/src/widgets/__stories__/passage.stories.tsx +0 -39
  300. package/src/widgets/__stories__/plotter.stories.tsx +0 -27
  301. package/src/widgets/__stories__/radio.stories.tsx +0 -86
  302. package/src/widgets/__stories__/random-widgets.stories.tsx +0 -59
  303. package/src/widgets/__stories__/sorter.stories.tsx +0 -14
  304. package/src/widgets/__stories__/test-keypad-context-wrapper.tsx +0 -66
  305. package/src/widgets/__stories__/unit.stories.tsx +0 -28
  306. package/src/widgets/__stories__/video-transcript-link.stories.tsx +0 -23
  307. package/src/widgets/__stories__/video.stories.tsx +0 -22
  308. package/src/widgets/__testdata__/base-radio.testdata.ts +0 -21
  309. package/src/widgets/__testdata__/categorizer.testdata.ts +0 -89
  310. package/src/widgets/__testdata__/cs-program.testdata.ts +0 -27
  311. package/src/widgets/__testdata__/dropdown.testdata.ts +0 -71
  312. package/src/widgets/__testdata__/explanation.testdata.ts +0 -85
  313. package/src/widgets/__testdata__/expression.testdata.ts +0 -198
  314. package/src/widgets/__testdata__/graded-group-set.testdata.ts +0 -356
  315. package/src/widgets/__testdata__/graded-group.testdata.ts +0 -77
  316. package/src/widgets/__testdata__/grapher.testdata.ts +0 -374
  317. package/src/widgets/__testdata__/group.testdata.ts +0 -153
  318. package/src/widgets/__testdata__/iframe.testdata.ts +0 -35
  319. package/src/widgets/__testdata__/image.testdata.ts +0 -36
  320. package/src/widgets/__testdata__/input-number.testdata.ts +0 -72
  321. package/src/widgets/__testdata__/interaction.testdata.ts +0 -262
  322. package/src/widgets/__testdata__/interactive-graph-random.testdata.ts +0 -269
  323. package/src/widgets/__testdata__/interactive-graph.testdata.ts +0 -644
  324. package/src/widgets/__testdata__/label-image.testdata.ts +0 -308
  325. package/src/widgets/__testdata__/matcher.testdata.ts +0 -33
  326. package/src/widgets/__testdata__/matrix.testdata.ts +0 -28
  327. package/src/widgets/__testdata__/number-line.testdata.ts +0 -30
  328. package/src/widgets/__testdata__/numeric-input.testdata.ts +0 -265
  329. package/src/widgets/__testdata__/orderer.testdata.ts +0 -123
  330. package/src/widgets/__testdata__/passage-ref.testdata.ts +0 -101
  331. package/src/widgets/__testdata__/passage.testdata.ts +0 -95
  332. package/src/widgets/__testdata__/plotter.testdata.ts +0 -36
  333. package/src/widgets/__testdata__/radio.testdata.ts +0 -324
  334. package/src/widgets/__testdata__/randomizers.ts +0 -51
  335. package/src/widgets/__testdata__/sorter.testdata.ts +0 -19
  336. package/src/widgets/__testdata__/video.testdata.ts +0 -61
  337. package/src/widgets/__tests__/__snapshots__/categorizer.test.ts.snap +0 -940
  338. package/src/widgets/__tests__/__snapshots__/cs-program.test.ts.snap +0 -67
  339. package/src/widgets/__tests__/__snapshots__/definition.test.ts.snap +0 -97
  340. package/src/widgets/__tests__/__snapshots__/dropdown.test.ts.snap +0 -133
  341. package/src/widgets/__tests__/__snapshots__/explanation.test.ts.snap +0 -425
  342. package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt.test.ts.snap +0 -729
  343. package/src/widgets/__tests__/__snapshots__/graded-group-set.test.ts.snap +0 -339
  344. package/src/widgets/__tests__/__snapshots__/graded-group.test.ts.snap +0 -801
  345. package/src/widgets/__tests__/__snapshots__/grapher.test.ts.snap +0 -2673
  346. package/src/widgets/__tests__/__snapshots__/group.test.tsx.snap +0 -1211
  347. package/src/widgets/__tests__/__snapshots__/iframe.test.ts.snap +0 -61
  348. package/src/widgets/__tests__/__snapshots__/image.test.ts.snap +0 -378
  349. package/src/widgets/__tests__/__snapshots__/input-number.test.ts.snap +0 -495
  350. package/src/widgets/__tests__/__snapshots__/interaction.test.ts.snap +0 -292
  351. package/src/widgets/__tests__/__snapshots__/interactive-graph.test.ts.snap +0 -2889
  352. package/src/widgets/__tests__/__snapshots__/matcher.test.tsx.snap +0 -883
  353. package/src/widgets/__tests__/__snapshots__/matrix.test.ts.snap +0 -514
  354. package/src/widgets/__tests__/__snapshots__/number-line.test.ts.snap +0 -814
  355. package/src/widgets/__tests__/__snapshots__/numeric-input.test.ts.snap +0 -470
  356. package/src/widgets/__tests__/__snapshots__/orderer.test.ts.snap +0 -243
  357. package/src/widgets/__tests__/__snapshots__/passage-ref.test.ts.snap +0 -434
  358. package/src/widgets/__tests__/__snapshots__/passage.test.tsx.snap +0 -1956
  359. package/src/widgets/__tests__/__snapshots__/plotter.test.tsx.snap +0 -827
  360. package/src/widgets/__tests__/__snapshots__/radio.test.ts.snap +0 -4237
  361. package/src/widgets/__tests__/__snapshots__/unit.test.tsx.snap +0 -20
  362. package/src/widgets/__tests__/__snapshots__/video.test.ts.snap +0 -153
  363. package/src/widgets/__tests__/categorizer.test.ts +0 -203
  364. package/src/widgets/__tests__/cs-program.test.ts +0 -43
  365. package/src/widgets/__tests__/definition.test.ts +0 -136
  366. package/src/widgets/__tests__/dropdown.test.ts +0 -105
  367. package/src/widgets/__tests__/explanation.test.ts +0 -198
  368. package/src/widgets/__tests__/expression-mobile.test.tsx +0 -363
  369. package/src/widgets/__tests__/expression.test.tsx +0 -484
  370. package/src/widgets/__tests__/graded-group-set-jipt.test.ts +0 -30
  371. package/src/widgets/__tests__/graded-group-set.test.ts +0 -288
  372. package/src/widgets/__tests__/graded-group.test.ts +0 -232
  373. package/src/widgets/__tests__/grapher.cypress.ts +0 -576
  374. package/src/widgets/__tests__/grapher.test.ts +0 -33
  375. package/src/widgets/__tests__/group.test.tsx +0 -510
  376. package/src/widgets/__tests__/iframe.test.ts +0 -45
  377. package/src/widgets/__tests__/image.test.ts +0 -62
  378. package/src/widgets/__tests__/input-number.test.ts +0 -354
  379. package/src/widgets/__tests__/interaction.test.ts +0 -31
  380. package/src/widgets/__tests__/interactive-graph.cypress.ts +0 -815
  381. package/src/widgets/__tests__/interactive-graph.test.ts +0 -106
  382. package/src/widgets/__tests__/matcher.test.tsx +0 -176
  383. package/src/widgets/__tests__/matrix.test.ts +0 -82
  384. package/src/widgets/__tests__/number-line.test.ts +0 -79
  385. package/src/widgets/__tests__/numeric-input.test.ts +0 -575
  386. package/src/widgets/__tests__/orderer.test.ts +0 -73
  387. package/src/widgets/__tests__/passage-ref.test.ts +0 -168
  388. package/src/widgets/__tests__/passage.test.tsx +0 -224
  389. package/src/widgets/__tests__/plotter.test.tsx +0 -69
  390. package/src/widgets/__tests__/radio/base-radio.test.tsx +0 -470
  391. package/src/widgets/__tests__/radio/choice-icon.test.tsx +0 -90
  392. package/src/widgets/__tests__/radio/choice.test.tsx +0 -310
  393. package/src/widgets/__tests__/radio/focus-ring.test.tsx +0 -22
  394. package/src/widgets/__tests__/radio/option-status.test.tsx +0 -77
  395. package/src/widgets/__tests__/radio.test.ts +0 -807
  396. package/src/widgets/__tests__/randomizers.test.ts +0 -53
  397. package/src/widgets/__tests__/renderQuestion.tsx +0 -101
  398. package/src/widgets/__tests__/unit.test.tsx +0 -168
  399. package/src/widgets/__tests__/video.test.ts +0 -69
  400. package/src/widgets/categorizer.tsx +0 -344
  401. package/src/widgets/cs-program.tsx +0 -227
  402. package/src/widgets/definition.tsx +0 -121
  403. package/src/widgets/deprecated-standin/__stories__/deprecated-standin.stories.tsx +0 -30
  404. package/src/widgets/deprecated-standin/__tests__/__snapshots__/deprecated-standin.test.ts.snap +0 -70
  405. package/src/widgets/deprecated-standin/__tests__/deprecated-standin.test.ts +0 -93
  406. package/src/widgets/deprecated-standin/index.tsx +0 -58
  407. package/src/widgets/dropdown.tsx +0 -142
  408. package/src/widgets/explanation.tsx +0 -317
  409. package/src/widgets/expression.tsx +0 -707
  410. package/src/widgets/graded-group-answer-bar.tsx +0 -142
  411. package/src/widgets/graded-group-set.tsx +0 -338
  412. package/src/widgets/graded-group.tsx +0 -471
  413. package/src/widgets/grapher/util.tsx +0 -799
  414. package/src/widgets/grapher.tsx +0 -660
  415. package/src/widgets/group.tsx +0 -195
  416. package/src/widgets/iframe.tsx +0 -195
  417. package/src/widgets/image.tsx +0 -312
  418. package/src/widgets/input-number.tsx +0 -412
  419. package/src/widgets/interaction/arrow-picker.tsx +0 -38
  420. package/src/widgets/interaction/color-picker.tsx +0 -69
  421. package/src/widgets/interaction/dash-picker.tsx +0 -45
  422. package/src/widgets/interaction/element-container.tsx +0 -100
  423. package/src/widgets/interaction.tsx +0 -826
  424. package/src/widgets/interactive-graph.tsx +0 -2597
  425. package/src/widgets/label-image/__stories__/answer-choices.stories.tsx +0 -91
  426. package/src/widgets/label-image/__stories__/answer-pill.stories.tsx +0 -62
  427. package/src/widgets/label-image/__stories__/marker.stories.tsx +0 -171
  428. package/src/widgets/label-image/__tests__/answer-pill.test.tsx +0 -75
  429. package/src/widgets/label-image/__tests__/hide-answers-toggle.test.tsx +0 -39
  430. package/src/widgets/label-image/answer-choices.tsx +0 -88
  431. package/src/widgets/label-image/answer-pill.tsx +0 -103
  432. package/src/widgets/label-image/hide-answers-toggle.tsx +0 -40
  433. package/src/widgets/label-image/marker.tsx +0 -268
  434. package/src/widgets/label-image/strings.ts +0 -5
  435. package/src/widgets/label-image/types.ts +0 -27
  436. package/src/widgets/label-image.test.ts +0 -928
  437. package/src/widgets/label-image.tsx +0 -819
  438. package/src/widgets/lights-puzzle.tsx +0 -359
  439. package/src/widgets/matcher.tsx +0 -298
  440. package/src/widgets/matrix.tsx +0 -634
  441. package/src/widgets/measurer.tsx +0 -222
  442. package/src/widgets/molecule/__tests__/layout.test.ts +0 -162
  443. package/src/widgets/molecule/__tests__/parser.test.ts +0 -155
  444. package/src/widgets/molecule/molecule-drawing.ts +0 -173
  445. package/src/widgets/molecule/molecule-layout.ts +0 -362
  446. package/src/widgets/molecule/smiles-parser.ts +0 -296
  447. package/src/widgets/molecule.tsx +0 -172
  448. package/src/widgets/number-line.tsx +0 -862
  449. package/src/widgets/numeric-input.tsx +0 -576
  450. package/src/widgets/orderer.tsx +0 -786
  451. package/src/widgets/passage/__tests__/passage-markdown.test.ts +0 -376
  452. package/src/widgets/passage/passage-markdown.tsx +0 -475
  453. package/src/widgets/passage-ref-target.tsx +0 -76
  454. package/src/widgets/passage-ref.tsx +0 -213
  455. package/src/widgets/passage.tsx +0 -582
  456. package/src/widgets/plotter.tsx +0 -1194
  457. package/src/widgets/radio/__stories__/base-radio.stories.tsx +0 -196
  458. package/src/widgets/radio/__stories__/choice-icon.stories.tsx +0 -166
  459. package/src/widgets/radio/__stories__/choice-none-above.stories.tsx +0 -40
  460. package/src/widgets/radio/__stories__/choice.stories.tsx +0 -137
  461. package/src/widgets/radio/__stories__/focus-ring.stories.tsx +0 -43
  462. package/src/widgets/radio/__stories__/option-status.stories.tsx +0 -103
  463. package/src/widgets/radio/base-radio.tsx +0 -511
  464. package/src/widgets/radio/choice-a11y-text.ts +0 -61
  465. package/src/widgets/radio/choice-icon/choice-icon.tsx +0 -218
  466. package/src/widgets/radio/choice-icon/cross-out-line.tsx +0 -51
  467. package/src/widgets/radio/choice-icon/shared-styles.ts +0 -15
  468. package/src/widgets/radio/choice-none-above.tsx +0 -56
  469. package/src/widgets/radio/choice.jsx.fixture.ts +0 -45
  470. package/src/widgets/radio/choice.tsx +0 -359
  471. package/src/widgets/radio/focus-ring.tsx +0 -57
  472. package/src/widgets/radio/option-status.tsx +0 -98
  473. package/src/widgets/radio/util.test.ts +0 -18
  474. package/src/widgets/radio/util.ts +0 -31
  475. package/src/widgets/radio/widget.tsx +0 -520
  476. package/src/widgets/radio.ts +0 -157
  477. package/src/widgets/reaction-diagram.tsx +0 -144
  478. package/src/widgets/sequence.tsx +0 -142
  479. package/src/widgets/simulator.tsx +0 -829
  480. package/src/widgets/sorter.tsx +0 -155
  481. package/src/widgets/table.tsx +0 -393
  482. package/src/widgets/unit.tsx +0 -374
  483. package/src/widgets/video-transcript-link.test.tsx +0 -213
  484. package/src/widgets/video-transcript-link.tsx +0 -84
  485. package/src/widgets/video.tsx +0 -135
  486. package/src/widgets.ts +0 -489
  487. package/src/zoom.ts +0 -555
  488. package/tsconfig-build.json +0 -26
  489. package/tsconfig-build.tsbuildinfo +0 -1
@@ -1,1816 +0,0 @@
1
- import {describe, beforeAll, beforeEach, it} from "@jest/globals";
2
- import {screen, waitFor, within} from "@testing-library/react";
3
- import userEvent from "@testing-library/user-event";
4
- import * as React from "react";
5
-
6
- import "@testing-library/jest-dom"; // Imports custom matchers
7
-
8
- import {clone} from "../../../../testing/object-utils";
9
- import {testDependencies} from "../../../../testing/test-dependencies";
10
- import {
11
- dropdownWidget,
12
- imageWidget,
13
- inputNumberWidget,
14
- question1,
15
- question2,
16
- mockedItem,
17
- mockedRandomItem,
18
- mockedShuffledRadioProps,
19
- } from "../__testdata__/renderer.testdata";
20
- import * as Dependencies from "../dependencies";
21
- import {Errors} from "../logging/log";
22
- import {registerWidget} from "../widgets";
23
- import {renderQuestion} from "../widgets/__tests__/renderQuestion";
24
- import InputNumberExport from "../widgets/input-number";
25
- import RadioWidgetExport from "../widgets/radio";
26
-
27
- import MockWidgetExport from "./mock-widget";
28
-
29
- import type {
30
- DropdownWidget,
31
- PerseusImageWidgetOptions,
32
- PerseusInputNumberWidgetOptions,
33
- } from "../perseus-types";
34
- import type {APIOptions} from "../types";
35
-
36
- // NOTE(jeremy): We can't use an automatic mock for the translation linter,
37
- // because one of it's "instance" methods is created using `debounce` and Jest
38
- // doesn't provide a mocked instance method for it (I suspect that Jest doesn't
39
- // see that symbol as an instance method).
40
- const mockRunLinter = jest.fn();
41
- const mockApplyLintErrors = jest.fn();
42
- jest.mock("../translation-linter", () => {
43
- // We mock the TranslationLinter constructor here setting things up so we
44
- // can spy/verify calls to instances of TranslationLinter
45
- return function () {
46
- return {
47
- runLinter: mockRunLinter,
48
- applyLintErrors: mockApplyLintErrors,
49
- };
50
- };
51
- });
52
-
53
- describe("renderer", () => {
54
- beforeAll(() => {
55
- registerWidget("input-number", InputNumberExport);
56
- registerWidget("radio", RadioWidgetExport);
57
- registerWidget("mock-widget", MockWidgetExport);
58
- });
59
-
60
- beforeEach(() => {
61
- jest.spyOn(Dependencies, "getDependencies").mockReturnValue(
62
- testDependencies,
63
- );
64
- });
65
-
66
- afterEach(() => {
67
- // The Renderer uses a timer to wait for widgets to complete rendering.
68
- // If we don't spin the timers here, then the timer fires in the test
69
- // _after_ and breaks it because we do setState() in the callback,
70
- // and by that point the component has been unmounted.
71
- jest.runOnlyPendingTimers();
72
- });
73
-
74
- describe("snapshots", () => {
75
- it("initial render", () => {
76
- // Arrange and Act
77
- const {container} = renderQuestion(question1);
78
-
79
- // Assert
80
- expect(container).toMatchSnapshot("initial render");
81
- });
82
-
83
- it("correct answer", () => {
84
- // Arrange
85
- const {container} = renderQuestion(question1);
86
-
87
- // Act
88
- userEvent.click(screen.getByRole("button"));
89
- userEvent.click(screen.getAllByRole("option")[2]);
90
- jest.runOnlyPendingTimers();
91
-
92
- // Assert
93
- expect(container).toMatchSnapshot("correct answer");
94
- });
95
-
96
- it("incorrect answer", () => {
97
- // Arrange
98
- const {container} = renderQuestion(question1);
99
-
100
- // Act
101
- userEvent.click(screen.getByRole("button"));
102
- userEvent.click(screen.getAllByRole("option")[1]);
103
- jest.runOnlyPendingTimers();
104
-
105
- // Assert
106
- expect(container).toMatchSnapshot("incorrect answer");
107
- });
108
- });
109
-
110
- describe("linting (TranslationLinter)", () => {
111
- const extraProps = {
112
- linterContext: {
113
- contentType: "exercise",
114
- highlightLint: true,
115
- // TODO(CP-4838): is it okay to use [] as a default?
116
- paths: [],
117
- stack: [],
118
- },
119
- } as const;
120
-
121
- it("should run the linter on mount", () => {
122
- // Arrange and Act
123
- renderQuestion(question1, {}, extraProps);
124
-
125
- // Assert
126
- expect(mockRunLinter).toHaveBeenCalledWith(
127
- "The total number of boxes the forklift can carry is [[☃ dropdown 1]] $60$.",
128
- expect.any(Function),
129
- );
130
- });
131
-
132
- it("should do nothing in linter callback if component is already unmounted", () => {
133
- // Arrange
134
- const {unmount} = renderQuestion(question1, {}, extraProps);
135
- unmount();
136
- mockApplyLintErrors.mockClear();
137
-
138
- // Act - Call the linter callback
139
- // `.calls` is an array of calls to this mock.
140
- // Each call is an array of the parameters passed to that call.
141
- // So `[0][1]` is the first call's second parameter.
142
- mockRunLinter.mock.calls[0][1]([]);
143
-
144
- // Assert
145
- expect(mockApplyLintErrors).not.toHaveBeenCalled();
146
- });
147
-
148
- it("should run linter on update", () => {
149
- // Arrange
150
- const {rerender} = renderQuestion(question1, {}, extraProps);
151
-
152
- mockRunLinter.mockClear();
153
-
154
- // Act
155
- rerender(question1, {...extraProps, problemNum: 1});
156
-
157
- // Assert
158
- expect(mockRunLinter).toHaveBeenCalled();
159
- });
160
- });
161
-
162
- describe("initial render", () => {
163
- it("should build initial state from props passed in", () => {
164
- // Arrange and Act
165
- const {renderer} = renderQuestion(question1);
166
-
167
- // Assert
168
- expect(renderer.state.jiptContent).toBeNull();
169
- expect(renderer.state.translationLintErrors).toHaveLength(0);
170
- expect(renderer.state.lastUsedWidgetId).toBeNull();
171
-
172
- expect(renderer.state.widgetInfo).toStrictEqual(question1.widgets);
173
- expect(renderer.state.widgetProps).toMatchInlineSnapshot(`
174
- {
175
- "dropdown 1": {
176
- "choices": [
177
- "greater than or equal to",
178
- "less than or equal to",
179
- ],
180
- "placeholder": "greater/less than or equal to",
181
- },
182
- }
183
- `);
184
- });
185
-
186
- it("should derive type widget ID if type missing", () => {
187
- // Arrange
188
- // Note that the types disallow this, but our Renderer handles the
189
- // case so for now, I'm adding this test. We can remove the test
190
- // if/when we clean up the code for it in _getAllWidgetsInfo().
191
- const question = {
192
- ...question1,
193
- widgets: {
194
- ...question1.widgets,
195
- // $FlowIgnore[prop-missing]
196
- // $FlowIgnore[incompatible-cast]
197
- // @ts-expect-error - TS2352 - Conversion of type '{ type: undefined; static?: boolean | undefined; graded?: boolean | undefined; alignment?: string | undefined; options: PerseusCategorizerWidgetOptions | null | undefined; key?: number | undefined; version?: Version | undefined; } | ... 38 more ... | { ...; }' to type 'DropdownWidget' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
198
- "dropdown 1": {
199
- ...question1.widgets["dropdown 1"],
200
- // $FlowIgnore[incompatible-cast]
201
- type: undefined,
202
- } as DropdownWidget,
203
- },
204
- } as const;
205
-
206
- // Act
207
- const {renderer} = renderQuestion(question);
208
-
209
- // Assert
210
- expect(renderer.state.widgetInfo["dropdown 1"]?.type).toBe(
211
- "dropdown",
212
- );
213
- });
214
-
215
- it("should default alignment if missing", () => {
216
- // Arrange/Act
217
- const {renderer} = renderQuestion({
218
- ...question1,
219
- widgets: {
220
- ...question1.widgets,
221
- "dropdown 1": {
222
- ...question1.widgets["dropdown 1"],
223
- alignment: undefined,
224
- },
225
- },
226
- });
227
-
228
- // Assert
229
- expect(renderer.state.widgetInfo["dropdown 1"]?.alignment).toBe(
230
- "default",
231
- );
232
- });
233
-
234
- it("should build default widget info if widget ID missing from widget props", () => {
235
- // Arrange
236
- const {renderer} = renderQuestion({
237
- ...question1,
238
- content:
239
- "[[☃ dropdown 1]] exists.\n\n" +
240
- "[[☃ dropdown 2]] does not!",
241
- });
242
- expect(question1.widgets["dropdown 2"]).toBeUndefined();
243
- let sawDropdown2 = false;
244
-
245
- // Act
246
- const widgets = renderer.findInternalWidgets((id, info, widget) => {
247
- // We should see the Renderer dealing with `dropdown 2` even if
248
- // no widget options/props were provided in the Perseus item.
249
- if (id === "dropdown 2") {
250
- // Dropdown 2's info is the default value (ie. it's got a
251
- // type and empty options object).
252
- expect(info.type).toBe("dropdown");
253
- expect(info.options).toStrictEqual({});
254
- expect(info.graded).toBeTrue();
255
-
256
- sawDropdown2 = true;
257
- return true;
258
- }
259
- return false;
260
- });
261
-
262
- // Assert
263
- expect(sawDropdown2).toBeTrue();
264
- // `dropdown 2` not found, however, because the Renderer doesn't
265
- // render widget's that don't have options defined.
266
- expect(widgets).toStrictEqual([null]);
267
- });
268
-
269
- it("should restore serialized state on mount if provided in prop", () => {
270
- // Arrange
271
- renderQuestion(
272
- question1,
273
- {},
274
- {
275
- serializedState: {
276
- "dropdown 1": {
277
- placeholder: "greater/less than or equal to",
278
- choices: [
279
- "greater than or equal to",
280
- "less than or equal to",
281
- ],
282
- selected: 2,
283
- },
284
- },
285
- },
286
- );
287
-
288
- // Assert
289
- expect(screen.getByRole("button")).toHaveTextContent(
290
- /^less than or equal to$/,
291
- );
292
- });
293
-
294
- it("should call the onWidgetStartProps callback if provided in apiOptions", () => {
295
- // Arrange
296
- const onWidgetStartProps = jest.fn();
297
- const apiOptions: APIOptions = {onWidgetStartProps};
298
-
299
- // Act
300
- renderQuestion(mockedRandomItem, apiOptions);
301
-
302
- // Assert
303
- expect(onWidgetStartProps).toHaveBeenCalledWith(
304
- mockedShuffledRadioProps,
305
- );
306
- });
307
- });
308
-
309
- describe("rendering", () => {
310
- const images: Array<Record<any, any>> = [];
311
- let originalImage;
312
-
313
- beforeEach(() => {
314
- originalImage = window.Image;
315
- // Mock HTML Image so we can trigger onLoad callbacks and see full
316
- // image rendering.
317
- // @ts-expect-error - TS2322 - Type 'Mock<Record<string, any>, [], any>' is not assignable to type 'new (width?: number | undefined, height?: number | undefined) => HTMLImageElement'.
318
- window.Image = jest.fn(() => {
319
- const img: Record<string, any> = {};
320
- images.push(img);
321
- return img;
322
- });
323
- });
324
-
325
- afterEach(() => {
326
- window.Image = originalImage;
327
- });
328
-
329
- // Tells the image loader 1, or all, of our images loaded
330
- const markImagesAsLoaded = (imageIndex?: number) => {
331
- if (imageIndex != null) {
332
- const img = images[imageIndex];
333
- if (img?.onload) {
334
- img.onload();
335
- }
336
- } else {
337
- images.forEach((i) => {
338
- if (i?.onload) {
339
- i.onload();
340
- }
341
- });
342
- }
343
- };
344
-
345
- it.each([true, false])(
346
- "should render a table when isMobile: %s",
347
- (isMobile: boolean) => {
348
- // Arrange
349
- const question = {
350
- content:
351
- "| Heading 1 | Heading 2 | Heading 3 |\n" +
352
- "| --------- | --------- | --------- |\n" +
353
- "| r1c1 | r1c2 | r1c3 |\n" +
354
- "| r2c1 | r2c2 | r2c3 |\n" +
355
- "| r3c1 | r3c2 | r3c3 |",
356
- images: {},
357
- widgets: {},
358
- } as const;
359
-
360
- // Act
361
- const {container} = renderQuestion(question, {
362
- isMobile,
363
- });
364
-
365
- // Assert
366
- expect(container).toMatchSnapshot();
367
- },
368
- );
369
-
370
- it("should wrap image to be responsive if not in table and dimensions provided", () => {
371
- // Arrange
372
- const question = {
373
- content:
374
- "One image with dimensions that isn't in a table.\n\n" +
375
- "![This image has dimensions](https://ka-perseus-images.s3.amazonaws.com/29e9aa32de3731b09e245e416cbf4e3fb2e89b58.svg)",
376
- images: {
377
- "https://ka-perseus-images.s3.amazonaws.com/29e9aa32de3731b09e245e416cbf4e3fb2e89b58.svg":
378
- {height: 410, width: 420},
379
- },
380
- widgets: {},
381
- } as const;
382
- renderQuestion(question);
383
-
384
- // Act
385
- markImagesAsLoaded();
386
-
387
- // Assert
388
- const imageNodes = screen.queryAllByRole("img");
389
- expect(imageNodes).toHaveLength(1);
390
-
391
- // eslint-disable-next-line testing-library/no-node-access
392
- expect(imageNodes[0].parentElement).toHaveClass(
393
- "fixed-to-responsive",
394
- );
395
- });
396
-
397
- it("should not wrap image to be responsive if not in table and no dimensions provided", () => {
398
- // Arrange
399
- const question = {
400
- content:
401
- "One image without dimensions.\n\n" +
402
- "![This image doesn't have dimensions](https://ka-perseus-images.s3.amazonaws.com/41dbe22e2d8aa0eb4356195d634c3219dc7142c9.svg)",
403
- images: {},
404
- widgets: {},
405
- } as const;
406
- renderQuestion(question);
407
-
408
- // Act
409
- markImagesAsLoaded();
410
-
411
- // Assert
412
- const imageNodes = screen.queryAllByRole("img");
413
- expect(imageNodes).toHaveLength(1);
414
-
415
- // image didn't have dimensions provided so it is not rendered as
416
- // responsive.
417
- // eslint-disable-next-line testing-library/no-node-access
418
- expect(imageNodes[0].parentElement).not.toHaveClass(
419
- "fixed-to-responsive",
420
- );
421
- });
422
-
423
- it("should add dimensions to wrapper, if provided", () => {
424
- // Arrange
425
- const question = {
426
- content:
427
- "One image with dimensions.\n\n" +
428
- "![This image has dimensions](https://ka-perseus-images.s3.amazonaws.com/29e9aa32de3731b09e245e416cbf4e3fb2e89b58.svg)",
429
- images: {
430
- "https://ka-perseus-images.s3.amazonaws.com/29e9aa32de3731b09e245e416cbf4e3fb2e89b58.svg":
431
- {height: 410, width: 420},
432
- },
433
- widgets: {},
434
- } as const;
435
- renderQuestion(question);
436
-
437
- // Act
438
- markImagesAsLoaded();
439
-
440
- // Assert
441
- expect(screen.queryAllByRole("img")).toHaveLength(1);
442
-
443
- const wrapperStyle = getComputedStyle(
444
- // $FlowIgnore[incompatible-call]
445
- // @ts-expect-error - TS2345 - Argument of type 'HTMLElement | null' is not assignable to parameter of type 'Element'.
446
- screen.getByAltText("This image has dimensions").parentElement, // eslint-disable-line testing-library/no-node-access
447
- );
448
- expect(wrapperStyle.maxWidth).toBe("420px");
449
- expect(wrapperStyle.maxHeight).toBe("410px");
450
- });
451
-
452
- it("should not add dimensions to wrapper, if not provided", () => {
453
- // Arrange
454
- const question = {
455
- content:
456
- "One image without dimensions.\n\n" +
457
- "![This image doesn't](https://ka-perseus-images.s3.amazonaws.com/41dbe22e2d8aa0eb4356195d634c3219dc7142c9.svg)",
458
- images: {},
459
- widgets: {},
460
- } as const;
461
- renderQuestion(question);
462
-
463
- // Act
464
- markImagesAsLoaded();
465
-
466
- // Assert
467
- expect(screen.queryAllByRole("img")).toHaveLength(1);
468
-
469
- const wrapperStyle = getComputedStyle(
470
- // $FlowIgnore[incompatible-call]
471
- // @ts-expect-error - TS2345 - Argument of type 'HTMLElement | null' is not assignable to parameter of type 'Element'.
472
- screen.getByAltText("This image doesn't").parentElement, // eslint-disable-line testing-library/no-node-access
473
- );
474
- expect(wrapperStyle.maxWidth).toBe("");
475
- expect(wrapperStyle.maxHeight).toBe("");
476
- });
477
-
478
- it("should never render image in responsive mode if inside table", () => {
479
- // Arrange
480
- const question = {
481
- content:
482
- "A table with images:\n\n" +
483
- "\n\n" +
484
- "| Title | Image |\n" +
485
- "| ----- |------ |\n" +
486
- "| Are you hungry for pizza? |\n" +
487
- "| ![This image has dimensions](https://ka-perseus-images.s3.amazonaws.com/29e9aa32de3731b09e245e416cbf4e3fb2e89b58.svg) ",
488
- images: {
489
- "https://ka-perseus-images.s3.amazonaws.com/29e9aa32de3731b09e245e416cbf4e3fb2e89b58.svg":
490
- {height: 410, width: 420},
491
- },
492
- widgets: {},
493
- } as const;
494
- renderQuestion(question);
495
-
496
- // Act
497
- markImagesAsLoaded();
498
-
499
- // Assert
500
- const imageNodes = screen.queryAllByRole("img");
501
- expect(imageNodes).toHaveLength(1);
502
- expect(getComputedStyle(imageNodes[0]).width).toBe("420px");
503
- expect(getComputedStyle(imageNodes[0]).height).toBe("410px");
504
-
505
- // And the parent shouldn't be a wrapper element
506
- // eslint-disable-next-line testing-library/no-node-access
507
- expect(imageNodes[0].parentElement).not.toHaveClass(
508
- "fixed-to-responsive",
509
- );
510
- });
511
-
512
- it("should render widgetPlaceholder if provided", () => {
513
- // Arrange
514
- const widgetPlaceholder = <div key="1">This is a placeholder</div>;
515
-
516
- // Act
517
- renderQuestion(question1, {
518
- widgetPlaceholder,
519
- });
520
-
521
- // Assert
522
- expect(
523
- screen.getByText("This is a placeholder"),
524
- ).toBeInTheDocument();
525
- // Make sure the 'dropdown' widget wasn't rendered!
526
- expect(screen.queryAllByRole("button")).toHaveLength(0);
527
- });
528
-
529
- it("should render columns", () => {
530
- // Arrange
531
- const question = {
532
- content:
533
- "Stuff in column 1\n\n" +
534
- "=====\n\n" +
535
- "Stuff in column 2\n\n",
536
- images: {},
537
- widgets: {},
538
- } as const;
539
-
540
- // Act
541
- const {container} = renderQuestion(question);
542
-
543
- // Assert
544
- expect(container).toMatchSnapshot();
545
- });
546
-
547
- it("should render image placeholder for links found in content that are images (for translation support)", () => {
548
- // Arrange
549
- const question = {
550
- content:
551
- "A link to an image: https://ka-perseus-images.s3.amazonaws.com/29e9aa32de3731b09e245e416cbf4e3fb2e89b58.svg",
552
- images: {},
553
- widgets: {},
554
- } as const;
555
- const imagePlaceholder = (
556
- // Without a `key`, we get React warnings
557
- <div key="placeholder">image placeholder</div>
558
- );
559
-
560
- // Act
561
- renderQuestion(question, {
562
- imagePlaceholder,
563
- });
564
-
565
- // Assert
566
- const renderedPlaceholder = screen.getByText("image placeholder");
567
- expect(renderedPlaceholder).toBeInTheDocument();
568
- // eslint-disable-next-line testing-library/no-node-access
569
- expect(renderedPlaceholder.parentElement).toMatchInlineSnapshot(`
570
- <a
571
- href="https://ka-perseus-images.s3.amazonaws.com/29e9aa32de3731b09e245e416cbf4e3fb2e89b58.svg"
572
- rel="noopener noreferrer"
573
- target="_blank"
574
- >
575
- <div>
576
- image placeholder
577
- </div>
578
- </a>
579
- `);
580
- });
581
-
582
- it("should render imagePlaceholder for all images", () => {
583
- // Arrange
584
- const imagePlaceholder = (
585
- <div key="1">This is an image placeholder</div>
586
- );
587
-
588
- // Act
589
- renderQuestion(
590
- {
591
- content:
592
- "We need more cat gifs: \n\n" +
593
- "![this isn't a cat gif :(](https://ka-perseus-images.s3.amazonaws.com/29e9aa32de3731b09e245e416cbf4e3fb2e89b58.svg)\n\n" +
594
- "![this isn't a cat gif :(](https://ka-perseus-images.s3.amazonaws.com/41dbe22e2d8aa0eb4356195d634c3219dc7142c9.svg)",
595
- images: {
596
- "https://ka-perseus-images.s3.amazonaws.com/29e9aa32de3731b09e245e416cbf4e3fb2e89b58.svg":
597
- {height: 420, width: 420},
598
- "https://ka-perseus-images.s3.amazonaws.com/41dbe22e2d8aa0eb4356195d634c3219dc7142c9.svg":
599
- {height: 216, width: 216},
600
- },
601
- widgets: {},
602
- },
603
- {
604
- imagePlaceholder,
605
- },
606
- );
607
-
608
- // Assert
609
- expect(
610
- screen.getAllByText("This is an image placeholder"),
611
- ).toHaveLength(2);
612
- });
613
-
614
- it("should render warning if same widget included multiple times in content", () => {
615
- // Arrange
616
- const question = {
617
- ...question1,
618
- content: question1.content + "\n\n[[☃ dropdown 1]]",
619
- } as const;
620
-
621
- // Act
622
- renderQuestion(question);
623
-
624
- // Assert
625
- expect(
626
- screen.getByText("Widget [[☃ dropdown 1]] already exists."),
627
- ).toBeInTheDocument();
628
- });
629
-
630
- it("should render block math", async () => {
631
- // Arrange
632
- const question = {
633
- content:
634
- "This is some block math\n\n" +
635
- "$1 + 2$\n\n" +
636
- "And a footer.",
637
- images: {},
638
- widgets: {},
639
- } as const;
640
- const apiOptions: Record<string, any> = {};
641
-
642
- // Act
643
- const {container} = renderQuestion(question, apiOptions);
644
- // KaTeX renders async, so we have to wait until it's done.
645
- await waitFor(() => {
646
- screen.getByText("1 + 2");
647
- });
648
-
649
- // Assert
650
- expect(container).toMatchSnapshot();
651
- });
652
-
653
- it("should render block math on mobile", async () => {
654
- // Arrange
655
- const question = {
656
- content:
657
- "This is some block math\n\n" +
658
- "$1 + 2$\n\n" +
659
- "And a footer.",
660
- images: {},
661
- widgets: {},
662
- } as const;
663
- const apiOptions = {isMobile: true} as const;
664
-
665
- // Act
666
- const {container} = renderQuestion(question, apiOptions);
667
- // KaTeX renders async, so we have to wait until it's done.
668
- await waitFor(() => {
669
- screen.getByText("1 + 2");
670
- });
671
-
672
- // Assert
673
- expect(container).toMatchSnapshot();
674
- });
675
-
676
- it("should render block math in a Zoomable when on mobile", async () => {
677
- // Arrange
678
- const question = {
679
- content:
680
- "This is some block math\n\n" +
681
- "$1 + 2$\n\n" +
682
- "And a footer.",
683
- images: {},
684
- widgets: {},
685
- } as const;
686
- const apiOptions = {isMobile: true} as const;
687
-
688
- // Act
689
- const {container} = renderQuestion(question, apiOptions);
690
- // KaTeX renders async, so we have to wait until it's done.
691
- await waitFor(() => {
692
- screen.getByText("1 + 2");
693
- });
694
-
695
- // Assert
696
- // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access
697
- const [zoomable] = container.querySelectorAll(
698
- ".perseus-block-math-inner span",
699
- );
700
- expect(zoomable).not.toBeUndefined();
701
- expect(zoomable).not.toBeNull();
702
- // @ts-expect-error - TS2339 - Property 'style' does not exist on type 'Element'.
703
- expect(zoomable.style.transform).not.toBe("");
704
-
705
- /* eslint-disable testing-library/no-node-access */
706
- // @ts-expect-error - TS2531 - Object is possibly 'null'.
707
- expect(zoomable.parentElement.style.transform).not.toBe("");
708
- /* eslint-enable testing-library/no-node-access */
709
- });
710
-
711
- it("should render the math", async () => {
712
- // Arrange
713
- const question = {
714
- content: "This is some inline math: $1 + 2$",
715
- images: {},
716
- widgets: {},
717
- } as const;
718
-
719
- // Act
720
- const {container} = renderQuestion(question);
721
-
722
- // Assert
723
- await waitFor(() => {
724
- screen.getByText("1 + 2");
725
- });
726
- expect(container).toMatchSnapshot();
727
- });
728
-
729
- it("should replace deprecated alignment tags in inline math", async () => {
730
- // Arrange
731
- const question = {
732
- content:
733
- "$\\begin{align}\n2\\text{HCl}(\\text{aq})+\\text{Ca}(\\text{OH})_2(\\text{aq})\\rightarrow\\text{Ca}(\\text{s})+2\\text H_2\\text O(\\text l)+\\text{Cl}_2(\\text g)\n\\end{align}$",
734
- images: {},
735
- widgets: {},
736
- } as const;
737
-
738
- // Act
739
- renderQuestion(question);
740
-
741
- // Assert
742
- // KaTeX renders async, so we use waitFor() have to wait until it's done.
743
- await waitFor(() => {
744
- screen.getByText(/\\begin\{aligned\}.*\\end\{aligned\}/);
745
- });
746
- });
747
- });
748
-
749
- // Note that we can't use `.each` here because the three props require
750
- // slightly different methods to change them for our rerender() function
751
- describe("resets widgetInfo and widgetProps when a prop in SHOULD_CLEAR_WIDGETS_PROP_LIST changes", () => {
752
- let originalWidgetInfo;
753
- let originalWidgetProps;
754
- let rerender;
755
- let renderer;
756
-
757
- beforeEach(() => {
758
- // Arrange
759
- // eslint-disable-next-line testing-library/no-render-in-setup
760
- const result = renderQuestion(question1);
761
- rerender = result.rerender;
762
- renderer = result.renderer;
763
-
764
- originalWidgetInfo = clone(renderer.state.widgetInfo);
765
- originalWidgetProps = clone(renderer.state.widgetProps);
766
-
767
- // Poke the renderer so it's not in it's initial-render state
768
- userEvent.click(screen.getByRole("button"));
769
- jest.runOnlyPendingTimers(); // There's a setTimeout to open the dropdown
770
- userEvent.click(screen.getAllByRole("option")[1]);
771
- });
772
-
773
- it("'content' prop'", () => {
774
- // Act
775
- rerender({
776
- ...question1,
777
- content:
778
- "this is a content change but still retains the widget it had: [[☃ dropdown 1]]",
779
- });
780
-
781
- // Assert
782
- expect(renderer.state.widgetInfo).toStrictEqual(originalWidgetInfo);
783
- expect(renderer.state.widgetProps).toStrictEqual(
784
- originalWidgetProps,
785
- );
786
- });
787
-
788
- it("'problemNum' prop", () => {
789
- // Act
790
- rerender(question1, {problemNum: 1});
791
-
792
- // Assert
793
- expect(renderer.state.widgetInfo).toStrictEqual(originalWidgetInfo);
794
- expect(renderer.state.widgetProps).toStrictEqual(
795
- originalWidgetProps,
796
- );
797
- });
798
-
799
- it("'widgets' prop", () => {
800
- // Act
801
- rerender({
802
- ...question1,
803
- widgets: {
804
- ...question1.widgets,
805
- // This is just a copy of the original widget, but it
806
- // causes a 'widgets' prop change
807
- "another-widget 1": question1.widgets["dropdown 1"],
808
- },
809
- });
810
-
811
- // Assert
812
- const allWidgetIds = Object.getOwnPropertyNames(originalWidgetInfo);
813
-
814
- // Note that we are only caring about equality of keys that existed
815
- // in the original state. Of course, since we made changes to the
816
- // `widgets` prop, the two widget-dependant state fields will be
817
- // changed!
818
- for (const widgetId of allWidgetIds) {
819
- expect(renderer.state.widgetInfo[widgetId]).toStrictEqual(
820
- originalWidgetInfo[widgetId],
821
- );
822
- }
823
-
824
- for (const widgetId of allWidgetIds) {
825
- expect(renderer.state.widgetProps[widgetId]).toStrictEqual(
826
- originalWidgetProps[widgetId],
827
- );
828
- }
829
- });
830
- });
831
-
832
- describe("focus management", () => {
833
- it("should focus the first focusable widget", () => {
834
- // Arrange
835
- const question = {
836
- content:
837
- "A dropdown [[☃ dropdown 1]]\nAn input [[☃ input-number 1]]\n\nAnd an image [[☃ image 1]].",
838
- images: {},
839
- widgets: {
840
- "dropdown 1": dropdownWidget,
841
- "input-number 1": inputNumberWidget,
842
- "image 1": imageWidget,
843
- },
844
- } as const;
845
- const onFocusChange = jest.fn();
846
- const {renderer} = renderQuestion(question, {
847
- onFocusChange,
848
- });
849
-
850
- // Act
851
- const focusResult = renderer.focus();
852
-
853
- // Assert
854
- expect(focusResult).toBeTrue();
855
- expect(onFocusChange).toHaveBeenCalledWith(["dropdown 1"], null);
856
- });
857
-
858
- it("should return false if no widgets are focusable", () => {
859
- // Arrange
860
- const onFocusChange = jest.fn();
861
- const {renderer} = renderQuestion(
862
- {
863
- content: "[[☃ image 1]]",
864
- images: {},
865
- widgets: {
866
- "image 1": {
867
- alignment: "block",
868
- graded: true,
869
- options: {
870
- alt: "A number line labeled 200 to 300 with tick marks at every 5 units. The tick marks at 200, 210, 220, 230, 240, 250, 260, 270, 280, 290, and 300 are labeled. A red circle labeled A is between 220 tick mark and 230 tick mark.",
871
- backgroundImage: {
872
- height: 80,
873
- url: "web+graphie://ka-perseus-graphie.s3.amazonaws.com/3351ccf19e60c28a1d08664f5c16defa76ed0348",
874
- width: 380,
875
- },
876
- } as PerseusImageWidgetOptions,
877
- static: false,
878
- type: "image",
879
- version: {major: 0, minor: 0},
880
- },
881
- },
882
- },
883
- {
884
- onFocusChange,
885
- },
886
- );
887
-
888
- // Act
889
- const focusResult = renderer.focus();
890
-
891
- // Assert
892
- expect(focusResult).toBeFalsy();
893
- expect(onFocusChange).not.toHaveBeenCalled();
894
- });
895
-
896
- it("should call onFocusChange callback when new widget receives focus", () => {
897
- // Arrange
898
- const onFocusChange = jest.fn();
899
- renderQuestion(
900
- {
901
- ...question2,
902
- content:
903
- "Enter 1 in this field: [[☃ input-number 1]].\n\n" +
904
- "Enter 2 in this field: [[☃ input-number 2]] $60$.",
905
- widgets: {
906
- "input-number 1": question2.widgets["input-number 1"],
907
- "input-number 2": question2.widgets["input-number 1"],
908
- },
909
- },
910
- {onFocusChange},
911
- );
912
-
913
- // Act
914
- userEvent.click(screen.getAllByRole("textbox")[1]);
915
-
916
- // Assert
917
- expect(onFocusChange).toHaveBeenCalledWith(
918
- /* new focus path */ ["input-number 2"],
919
- /* old focus path */ null,
920
- );
921
- });
922
-
923
- it("should call onFocusChange callback when a widget loses focus", () => {
924
- // Arrange
925
- const onFocusChange = jest.fn();
926
- renderQuestion(
927
- {
928
- ...question2,
929
- content:
930
- "Enter 1 in this field: [[☃ input-number 1]].\n\n" +
931
- "Enter 2 in this field: [[☃ input-number 2]] $60$.",
932
- widgets: {
933
- "input-number 1": question2.widgets["input-number 1"],
934
- "input-number 2": question2.widgets["input-number 1"],
935
- },
936
- },
937
- {onFocusChange},
938
- );
939
- userEvent.click(screen.getAllByRole("textbox")[1]);
940
- onFocusChange.mockClear();
941
-
942
- // Act
943
- userEvent.tab();
944
- // There's a _.defer() in the blur handling
945
- jest.runOnlyPendingTimers();
946
-
947
- // Assert
948
- expect(onFocusChange).toHaveBeenCalledWith(
949
- /* new focus path */ null,
950
- /* old focus path */ ["input-number 2"],
951
- );
952
- });
953
-
954
- it("should throw if widget provides invalid focus path", () => {
955
- // Arrange
956
- const {renderer} = renderQuestion(mockedItem);
957
- const [widget2] = renderer.findWidgets("mock-widget 2");
958
-
959
- // Act and Assert
960
- expect(() => {
961
- // TODO: Right now KAError is mocked because we're
962
- // jest.mock()ing the logging module. Figure out how to
963
- // unmock.
964
- widget2.props.onFocus("this is not an array");
965
- }).toThrow("widget props.onFocus focusPath must be an Array");
966
- });
967
-
968
- it("should focus the input at the requested FocusPath", () => {
969
- // Arrange
970
- const {renderer} = renderQuestion(question2);
971
-
972
- // Act
973
- renderer.focusPath(["input-number 1"]);
974
-
975
- // Assert
976
- expect(screen.getByRole("textbox")).toHaveFocus();
977
- });
978
-
979
- it("should do nothing if requested FocusPath is already focused", () => {
980
- // Arrange
981
- const onFocusChange = jest.fn();
982
- const {renderer} = renderQuestion(question2, {
983
- onFocusChange,
984
- });
985
- renderer.focusPath(["input-number 1"]);
986
- onFocusChange.mockClear();
987
-
988
- // Act
989
- renderer.focusPath(["input-number 1"]);
990
-
991
- // Assert
992
- expect(onFocusChange).not.toHaveBeenCalled();
993
- });
994
-
995
- it("should blur current widget when focus changes", () => {
996
- // Arrange
997
- const onFocusChange = jest.fn();
998
- const {renderer} = renderQuestion(
999
- {
1000
- ...question2,
1001
- content:
1002
- "Input 1: [[☃ input-number 1]]\n\n" +
1003
- "Input 2: [[☃ input-number 2]]",
1004
- widgets: {
1005
- ...question2.widgets,
1006
- "input-number 2": question2.widgets["input-number 1"],
1007
- },
1008
- },
1009
- {onFocusChange},
1010
- );
1011
- renderer.focusPath(["input-number 1"]);
1012
- onFocusChange.mockClear();
1013
-
1014
- // Act
1015
- renderer.focusPath(["input-number 2"]);
1016
-
1017
- // Assert
1018
- expect(onFocusChange).toHaveBeenCalledWith(
1019
- ["input-number 2"], // New focus
1020
- ["input-number 1"], // Old focus
1021
- );
1022
- });
1023
-
1024
- it("should do nothing if blurred widget is not focused", () => {
1025
- // Arrange
1026
- const onFocusChange = jest.fn();
1027
- const {renderer} = renderQuestion(
1028
- {
1029
- ...question2,
1030
- content:
1031
- "Input 1: [[☃ input-number 1]]\n\n" +
1032
- "Input 2: [[☃ input-number 2]]",
1033
- widgets: {
1034
- ...question2.widgets,
1035
- "input-number 2": question2.widgets["input-number 1"],
1036
- },
1037
- },
1038
- {onFocusChange},
1039
- );
1040
- // Focus _second_ input number widget
1041
- screen.getAllByRole("textbox")[1].focus();
1042
- onFocusChange.mockClear();
1043
-
1044
- // Act
1045
- renderer.blurPath(["input-number 1"]);
1046
-
1047
- // Assert
1048
- expect(onFocusChange).not.toHaveBeenCalled();
1049
- });
1050
-
1051
- it("should blur current widget", () => {
1052
- // Arrange
1053
- const onFocusChange = jest.fn();
1054
- const {renderer} = renderQuestion(
1055
- {
1056
- ...question2,
1057
- content:
1058
- "Input 1: [[☃ input-number 1]]\n\n" +
1059
- "Input 2: [[☃ input-number 2]]",
1060
- widgets: {
1061
- ...question2.widgets,
1062
- "input-number 2": question2.widgets["input-number 1"],
1063
- },
1064
- },
1065
- {onFocusChange},
1066
- );
1067
- // Focus _second_ input number widget
1068
- screen.getAllByRole("textbox")[1].focus();
1069
- onFocusChange.mockClear();
1070
-
1071
- // Act
1072
- renderer.blur();
1073
- jest.runOnlyPendingTimers(); // There's a _.defer() in this code path
1074
-
1075
- // Assert
1076
- expect(onFocusChange).toHaveBeenCalledWith(
1077
- null, // New focus
1078
- ["input-number 2"], // Old focus
1079
- );
1080
- });
1081
-
1082
- it("should do nothing when blur() called but no widget focused", () => {
1083
- // Arrange
1084
- const onFocusChange = jest.fn();
1085
- const {renderer} = renderQuestion(
1086
- {
1087
- ...question2,
1088
- content:
1089
- "Input 1: [[☃ input-number 1]]\n\n" +
1090
- "Input 2: [[☃ input-number 2]]",
1091
- widgets: {
1092
- ...question2.widgets,
1093
- "input-number 2": question2.widgets["input-number 1"],
1094
- },
1095
- },
1096
- {onFocusChange},
1097
- );
1098
-
1099
- // Act
1100
- renderer.blur();
1101
- jest.runOnlyPendingTimers(); // There's a _.defer() in this code path
1102
-
1103
- // Assert
1104
- expect(onFocusChange).not.toHaveBeenCalled();
1105
- });
1106
- });
1107
-
1108
- describe("state serialization", () => {
1109
- it("should request widget's serialized state if implemented", () => {
1110
- // Arrange
1111
- const {renderer} = renderQuestion(mockedItem);
1112
-
1113
- const [widget2] = renderer.findWidgets("mock-widget 2");
1114
- expect(widget2).not.toBeUndefined();
1115
- widget2.getSerializedState = jest.fn();
1116
-
1117
- // Act
1118
- renderer.getSerializedState();
1119
-
1120
- // Act
1121
- expect(widget2.getSerializedState).toHaveBeenCalled();
1122
- });
1123
-
1124
- it("should skip restoration if state's widget ID list doesn't match renderer widgets", () => {
1125
- // Arrange
1126
- const errorSpy = jest.spyOn(testDependencies.Log, "error");
1127
-
1128
- const {renderer} = renderQuestion(question1);
1129
-
1130
- // Act
1131
- renderer.restoreSerializedState({
1132
- "group 1": {},
1133
- "interactive-chart 1": {},
1134
- });
1135
-
1136
- // Assert
1137
- expect(errorSpy).toHaveBeenCalledWith(
1138
- "Refusing to restore bad serialized state:",
1139
- Errors.Internal,
1140
- {
1141
- loggedMetadata: {
1142
- currentProps: expect.anything(),
1143
- serializedState:
1144
- '{"group 1":{},"interactive-chart 1":{}}',
1145
- },
1146
- },
1147
- );
1148
- });
1149
-
1150
- it("should fire restoration callback when all widgets have restored", () => {
1151
- // Arrange
1152
- const makeRestoreSerializedStateMock = jest
1153
- .fn()
1154
- .mockImplementation((props, callback) => callback());
1155
-
1156
- const {renderer} = renderQuestion(mockedItem);
1157
- const [widget1, widget2, widget3] = renderer.findWidgets(
1158
- // @ts-expect-error - TS2367 - This condition will always return 'false' since the types '"video" | "image" | "iframe" | "table" | "radio" | "definition" | "group" | "matrix" | "categorizer" | "cs-program" | "dropdown" | "example-graphie-widget" | "example-widget" | ... 26 more ... | "unit-input"' and '"mock-widget"' have no overlap.
1159
- (_, info) => info.type === "mock-widget",
1160
- );
1161
- widget1.restoreSerializedState = makeRestoreSerializedStateMock;
1162
- widget2.restoreSerializedState = makeRestoreSerializedStateMock;
1163
- widget3.restoreSerializedState = makeRestoreSerializedStateMock;
1164
-
1165
- const restorationCallback = jest.fn();
1166
-
1167
- // Act
1168
- renderer.restoreSerializedState(
1169
- {"mock-widget 1": {}, "mock-widget 2": {}, "mock-widget 3": {}},
1170
- restorationCallback,
1171
- );
1172
- jest.runOnlyPendingTimers();
1173
-
1174
- // Assert
1175
- expect(restorationCallback).toHaveBeenCalledTimes(1);
1176
- });
1177
-
1178
- it("should return each widget's state from serialize()", () => {
1179
- // Arrange
1180
- const {renderer} = renderQuestion(mockedItem);
1181
- const widgets = renderer.findWidgets((id) =>
1182
- ["mock-widget 1", "mock-widget 2", "mock-widget 3"].includes(
1183
- id,
1184
- ),
1185
- );
1186
- widgets.forEach((w) => {
1187
- w.serialize = jest.fn(() => `State: ${w.props.widgetId}`);
1188
- });
1189
- // It takes a clock tick after rendering for widgetInfo to be
1190
- // populated (which renderer uses during serialize()).
1191
- jest.runOnlyPendingTimers();
1192
-
1193
- // Act
1194
- const state = renderer.serialize();
1195
-
1196
- // Assert
1197
- expect(state).toStrictEqual({
1198
- "mock-widget 1": "State: mock-widget 1",
1199
- "mock-widget 2": "State: mock-widget 2",
1200
- "mock-widget 3": "State: mock-widget 3",
1201
- });
1202
- });
1203
- });
1204
-
1205
- describe("finding widgets", () => {
1206
- it("should be able to find widgets by widget ID string", () => {
1207
- // Arrange
1208
- const {renderer} = renderQuestion(question1);
1209
-
1210
- // Act
1211
- const [dropdown1] = renderer.findWidgets("dropdown 1");
1212
-
1213
- // Assert
1214
- expect(dropdown1).not.toBeNull();
1215
- expect(dropdown1).not.toBeUndefined();
1216
- });
1217
-
1218
- it("should be able to find widgets by widget type string", () => {
1219
- // Arrange
1220
- const {renderer} = renderQuestion(question1);
1221
-
1222
- // Act
1223
- const [dropdown1] = renderer.findWidgets("dropdown");
1224
-
1225
- // Assert
1226
- expect(dropdown1).not.toBeNull();
1227
- expect(dropdown1).not.toBeUndefined();
1228
- });
1229
-
1230
- it("should be able to find widgets by filter function", () => {
1231
- // Arrange
1232
- const {renderer} = renderQuestion(question1);
1233
- let filterCalled = false;
1234
-
1235
- // Act and Arrange
1236
- renderer.findWidgets((id, info, widget) => {
1237
- filterCalled = true;
1238
- expect(id).toBe("dropdown 1");
1239
- expect(info.type).toBe("dropdown");
1240
- expect(widget).not.toBeNull();
1241
- expect(widget).not.toBeUndefined();
1242
- return false;
1243
- });
1244
-
1245
- expect(filterCalled).toBeTrue();
1246
- });
1247
- });
1248
-
1249
- describe("misc behaviors", () => {
1250
- it("should be able to force re-render", () => {
1251
- // Arrange
1252
- const onRender = jest.fn();
1253
- const apiOptions: Record<string, any> = {};
1254
- const extraProps = {
1255
- alwaysUpdate: true, // Switching to `false` to fails test
1256
- onRender,
1257
- } as const;
1258
- const {rerender} = renderQuestion(
1259
- question1,
1260
- apiOptions,
1261
- extraProps,
1262
- );
1263
-
1264
- // Act
1265
- rerender(question1, extraProps);
1266
-
1267
- // How do I tell it rerendered?
1268
- expect(onRender).toHaveBeenCalledTimes(2);
1269
- });
1270
-
1271
- it("should use new serializedState if getSerializedState is different", () => {
1272
- // Act
1273
- // Render with serialized state
1274
- const {rerender} = renderQuestion(
1275
- question1,
1276
- {},
1277
- {
1278
- serializedState: {
1279
- "dropdown 1": {
1280
- placeholder: "greater/less than or equal to",
1281
- choices: [
1282
- "greater than or equal to",
1283
- "less than or equal to",
1284
- ],
1285
- selected: 2, // <-- Important
1286
- },
1287
- },
1288
- },
1289
- );
1290
-
1291
- // Act
1292
- rerender(question1, {
1293
- serializedState: {
1294
- "dropdown 1": {
1295
- placeholder: "greater/less than or equal to",
1296
- choices: [
1297
- "greater than or equal to",
1298
- "less than or equal to",
1299
- ],
1300
- selected: 1, // <-- Important
1301
- },
1302
- },
1303
- });
1304
-
1305
- expect(screen.getByRole("button")).toHaveTextContent(
1306
- /greater than or equal to/,
1307
- );
1308
- });
1309
-
1310
- it("should render the widget in full width if alignment == 'fullWidth'", () => {
1311
- // Arrange/Act
1312
- renderQuestion({
1313
- ...question1,
1314
- widgets: {
1315
- ...question1.widgets,
1316
- "dropdown 1": {
1317
- ...question1.widgets["dropdown 1"],
1318
- alignment: "full-width",
1319
- },
1320
- },
1321
- });
1322
-
1323
- // Assert
1324
- let el = screen.getByRole("button");
1325
- while (el != null) {
1326
- if (el.classList.contains("widget-full-width")) {
1327
- break;
1328
- }
1329
- // eslint-disable-next-line testing-library/no-node-access
1330
- // @ts-expect-error - TS2322 - Type 'HTMLElement | null' is not assignable to type 'HTMLElement'.
1331
- // eslint-disable-next-line testing-library/no-node-access
1332
- el = el.parentElement;
1333
- }
1334
-
1335
- // Didn't find parent element that's set to full-widget
1336
- expect(el).not.toBeNull();
1337
- });
1338
-
1339
- it("should force the widget to be non-static if it has a problem number", () => {
1340
- // Arrange/Act
1341
- const {renderer} = renderQuestion(
1342
- {
1343
- ...question1,
1344
- widgets: {
1345
- ...question1.widgets,
1346
- "dropdown 1": {
1347
- ...question1.widgets["dropdown 1"],
1348
- static: true,
1349
- },
1350
- },
1351
- },
1352
- {},
1353
- {problemNum: 1},
1354
- );
1355
-
1356
- // Assert
1357
- const [dropdownWidget] = renderer.findWidgets("dropdown 1");
1358
-
1359
- // Act
1360
- expect(dropdownWidget.props.static).toBeFalse();
1361
- });
1362
-
1363
- it("should ask each widget to show rationales", () => {
1364
- // Arrange
1365
- const {renderer} = renderQuestion(mockedItem);
1366
- const widgets = renderer.findWidgets(
1367
- // @ts-expect-error - TS2367 - This condition will always return 'false' since the types '"video" | "image" | "iframe" | "table" | "radio" | "definition" | "group" | "matrix" | "categorizer" | "cs-program" | "dropdown" | "example-graphie-widget" | "example-widget" | ... 26 more ... | "unit-input"' and '"mock-widget"' have no overlap.
1368
- (_, info) => info.type === "mock-widget",
1369
- );
1370
- widgets.forEach(
1371
- (w) =>
1372
- (w.showRationalesForCurrentlySelectedChoices = jest.fn()),
1373
- );
1374
-
1375
- // Guard: If our test data were to cause this to return an empty
1376
- // array, the rest of the assertions in this test would pass.
1377
- expect(widgets.length).toBeGreaterThan(0);
1378
-
1379
- // Act
1380
- renderer.showRationalesForCurrentlySelectedChoices();
1381
-
1382
- // Assert
1383
- widgets.forEach((w) =>
1384
- expect(
1385
- w.showRationalesForCurrentlySelectedChoices,
1386
- ).toHaveBeenCalled(),
1387
- );
1388
- });
1389
-
1390
- it("should ask each widget to deselect incorrect choices", () => {
1391
- // Arrange
1392
- const {renderer} = renderQuestion(mockedItem);
1393
- const widgets = renderer.findWidgets(
1394
- // @ts-expect-error - TS2367 - This condition will always return 'false' since the types '"video" | "image" | "iframe" | "table" | "radio" | "definition" | "group" | "matrix" | "categorizer" | "cs-program" | "dropdown" | "example-graphie-widget" | "example-widget" | ... 26 more ... | "unit-input"' and '"mock-widget"' have no overlap.
1395
- (_, info) => info.type === "mock-widget",
1396
- );
1397
- widgets.forEach(
1398
- (w) => (w.deselectIncorrectSelectedChoices = jest.fn()),
1399
- );
1400
-
1401
- // Guard: If our test data were to cause this to return an empty
1402
- // array, the rest of the assertions in this test would pass.
1403
- expect(widgets.length).toBeGreaterThan(0);
1404
-
1405
- // Act
1406
- renderer.deselectIncorrectSelectedChoices();
1407
-
1408
- // Assert
1409
- widgets.forEach((w) =>
1410
- expect(w.deselectIncorrectSelectedChoices).toHaveBeenCalled(),
1411
- );
1412
- });
1413
-
1414
- it("should return user input", () => {
1415
- // Arrange
1416
- const {renderer} = renderQuestion({
1417
- ...question2,
1418
- content:
1419
- "Input 1: [[☃ input-number 1]]\n\n" +
1420
- "Input 2: [[☃ input-number 2]]\n\n" +
1421
- "A widget that doesn't implement getUserInput: [[☃ image 1]]",
1422
- widgets: {
1423
- ...question2.widgets,
1424
- "input-number 2": {
1425
- ...question2.widgets["input-number 1"],
1426
- static: true,
1427
- },
1428
- "image 1": {
1429
- type: "image",
1430
- graded: false,
1431
- options: {
1432
- backgroundImage: {
1433
- url: "https://example.com/cat.png",
1434
- width: 100,
1435
- height: 100,
1436
- },
1437
- },
1438
- },
1439
- },
1440
- });
1441
-
1442
- screen
1443
- .getAllByRole("textbox")
1444
- .forEach((el, i) => userEvent.type(el, i.toString()));
1445
-
1446
- // Act
1447
- const input = renderer.getUserInput();
1448
-
1449
- // Assert
1450
- expect(input).toStrictEqual([
1451
- {currentValue: "0"},
1452
- {currentValue: "1"},
1453
- null, // image widget doesn't implement getUserinput
1454
- ]);
1455
- });
1456
-
1457
- it("should return all widget IDs that were rendererd", () => {
1458
- // Arrange
1459
- const {renderer} = renderQuestion({
1460
- ...question2,
1461
- content:
1462
- "Input 1: [[☃ input-number 1]]\n\n" +
1463
- "Input 2: [[☃ input-number 2]]\n\n" +
1464
- "A widget that doesn't implement getUserInput: [[☃ image 1]]",
1465
- widgets: {
1466
- ...question2.widgets,
1467
- "input-number 2": {
1468
- ...question2.widgets["input-number 1"],
1469
- static: true,
1470
- },
1471
- "image 1": {
1472
- type: "image",
1473
- graded: false,
1474
- options: {
1475
- backgroundImage: {
1476
- url: "https://example.com/cat.png",
1477
- width: 100,
1478
- height: 100,
1479
- },
1480
- },
1481
- },
1482
- },
1483
- });
1484
-
1485
- // Act
1486
- const widgetIds = renderer.getWidgetIds();
1487
-
1488
- // Assert
1489
- expect(widgetIds).toStrictEqual([
1490
- "input-number 1",
1491
- "input-number 2",
1492
- "image 1",
1493
- ]);
1494
- });
1495
- });
1496
-
1497
- describe("getDOMNodeForPath", () => {
1498
- it("should return the DOM node for the widget at requested FocusPath", () => {
1499
- // Arrange
1500
- const {renderer} = renderQuestion(question1);
1501
-
1502
- // Act
1503
- const node = renderer.getDOMNodeForPath(["dropdown 1"]);
1504
-
1505
- // Assert
1506
- // "button" role is the WB dropdown's "opener" element
1507
- // @ts-expect-error - TS2345 - Argument of type 'Element | Text | null | undefined' is not assignable to parameter of type 'HTMLElement'.
1508
- expect(within(node).queryAllByRole("button")).toHaveLength(1);
1509
- });
1510
-
1511
- it("should return the widget's getDOMNodeForPath() result for the widget at requested FocusPath", () => {
1512
- // Arrange
1513
- const {renderer} = renderQuestion(mockedItem);
1514
- const widget2DOMNode = <span />;
1515
- const [widget2] = renderer.findWidgets("mock-widget 2");
1516
- widget2.getDOMNodeForPath = jest.fn(() => widget2DOMNode);
1517
-
1518
- // Act
1519
- const node = renderer.getDOMNodeForPath(["mock-widget 2"]);
1520
-
1521
- // Assert
1522
- expect(node).toBe(widget2DOMNode);
1523
- });
1524
- });
1525
-
1526
- describe("getGrammarTypeForPath", () => {
1527
- it("should return undefined if matching widget doesn't implement getGrammarTypeForPath", () => {
1528
- // Arrange
1529
- const {renderer} = renderQuestion(question1);
1530
-
1531
- // Act
1532
- const grammarType = renderer.getGrammarTypeForPath(["dropdown 1"]);
1533
-
1534
- // Assert
1535
- expect(grammarType).toBeUndefined();
1536
- });
1537
-
1538
- it("should return widget result if matching widget implements getGrammarTypeForPath", () => {
1539
- // Arrange
1540
- const {renderer} = renderQuestion(question2);
1541
-
1542
- // Act
1543
- const grammarType = renderer.getGrammarTypeForPath([
1544
- "input-number 1",
1545
- ]);
1546
-
1547
- // Assert
1548
- expect(grammarType).toBe("number");
1549
- });
1550
- });
1551
-
1552
- describe("getInputPaths", () => {
1553
- it("should return all input paths for all rendererd widgets", () => {
1554
- // Arrange
1555
- const {renderer} = renderQuestion(mockedItem);
1556
- const [mockWidget1, mockWidget2, mockWidget3] =
1557
- renderer.findWidgets((id) =>
1558
- [
1559
- "mock-widget 1",
1560
- "mock-widget 2",
1561
- "mock-widget 3",
1562
- ].includes(id),
1563
- );
1564
- mockWidget1.getInputPaths = jest.fn(() => ["input 1"]);
1565
- mockWidget2.getInputPaths = jest.fn(() => ["input 2", "input 3"]);
1566
- mockWidget3.getInputPaths = jest.fn(() => [
1567
- ["input 4", "sub-input 4.1"],
1568
- "input 5",
1569
- ]);
1570
-
1571
- // Act
1572
- const inputPaths = renderer.getInputPaths();
1573
-
1574
- // Assert
1575
- expect(inputPaths).toMatchInlineSnapshot(`
1576
- [
1577
- [
1578
- "mock-widget 1",
1579
- "input 1",
1580
- ],
1581
- [
1582
- "mock-widget 2",
1583
- "input 2",
1584
- ],
1585
- [
1586
- "mock-widget 2",
1587
- "input 3",
1588
- ],
1589
- [
1590
- "mock-widget 3",
1591
- "input 4",
1592
- "sub-input 4.1",
1593
- ],
1594
- [
1595
- "mock-widget 3",
1596
- "input 5",
1597
- ],
1598
- ]
1599
- `);
1600
- });
1601
- });
1602
-
1603
- describe("emptyWidgets", () => {
1604
- it("should return all empty widgets", () => {
1605
- // Arrange
1606
- const {renderer} = renderQuestion({
1607
- ...question2,
1608
- content:
1609
- "Input 1: [[☃ input-number 1]]\n\n" +
1610
- "Input 2: [[☃ input-number 2]]",
1611
- widgets: {
1612
- ...question2.widgets,
1613
- "input-number 2": question2.widgets["input-number 1"],
1614
- },
1615
- });
1616
- userEvent.type(screen.getAllByRole("textbox")[0], "150");
1617
-
1618
- // Act
1619
- const emptyWidgets = renderer.emptyWidgets();
1620
-
1621
- // Assert
1622
- expect(emptyWidgets).toStrictEqual(["input-number 2"]);
1623
- });
1624
-
1625
- it("should not return static widgets even if empty", () => {
1626
- // Arrange
1627
- const {renderer} = renderQuestion({
1628
- ...question2,
1629
- content:
1630
- "Input 1: [[☃ input-number 1]]\n\n" +
1631
- "Input 2: [[☃ input-number 2]]",
1632
- widgets: {
1633
- ...question2.widgets,
1634
- "input-number 2": {
1635
- ...question2.widgets["input-number 1"],
1636
- static: true,
1637
- },
1638
- },
1639
- });
1640
-
1641
- // Act
1642
- const emptyWidgets = renderer.emptyWidgets();
1643
-
1644
- // Assert
1645
- expect(emptyWidgets).toStrictEqual(["input-number 1"]);
1646
- });
1647
- });
1648
-
1649
- describe("setInputValue", () => {
1650
- it("should set the value on the requested FocusPath", () => {
1651
- // Arrange
1652
- const {renderer} = renderQuestion({
1653
- ...question2,
1654
- content:
1655
- "Input 1: [[☃ input-number 1]]\n\n" +
1656
- "Input 2: [[☃ input-number 2]]",
1657
- widgets: {
1658
- ...question2.widgets,
1659
- "input-number 2": {
1660
- ...question2.widgets["input-number 1"],
1661
- static: true,
1662
- },
1663
- },
1664
- });
1665
- const cb = jest.fn();
1666
-
1667
- // Act
1668
- renderer.setInputValue(["input-number 2"], "1000", cb);
1669
-
1670
- // Assert
1671
- expect(screen.getAllByRole("textbox")[0]).toHaveValue("");
1672
- expect(screen.getAllByRole("textbox")[1]).toHaveValue("1000");
1673
- });
1674
-
1675
- it("should call the focus callback when value is complete", () => {
1676
- // Arrange
1677
- const {renderer} = renderQuestion({
1678
- ...question2,
1679
- content:
1680
- "Input 1: [[☃ input-number 1]]\n\n" +
1681
- "Input 2: [[☃ input-number 2]]",
1682
- widgets: {
1683
- ...question2.widgets,
1684
- "input-number 2": {
1685
- ...question2.widgets["input-number 1"],
1686
- static: true,
1687
- },
1688
- },
1689
- });
1690
- const cb = jest.fn();
1691
-
1692
- // Act
1693
- renderer.setInputValue(["input-number 2"], "1000", cb);
1694
- jest.runOnlyPendingTimers();
1695
-
1696
- // Assert
1697
- expect(cb).toHaveBeenCalled();
1698
- });
1699
- });
1700
-
1701
- describe("getUserInputForWidgets", () => {
1702
- it("should return user input for all rendered widgets", () => {
1703
- // Arrange
1704
- const {renderer} = renderQuestion({
1705
- content:
1706
- "Input widget: [[\u2603 input-number 1]]\n\n" +
1707
- "Dropdown widget: [[\u2603 dropdown 1]]\n\n" +
1708
- "Image widget (won't have user input): [[\u2603 image 1]]\n\n" +
1709
- "Another input widget: [[\u2603 input-number 2]]",
1710
- widgets: {
1711
- "image 1": imageWidget,
1712
- "input-number 1": inputNumberWidget,
1713
- "input-number 2": inputNumberWidget,
1714
- "dropdown 1": dropdownWidget,
1715
- },
1716
- images: {},
1717
- });
1718
-
1719
- userEvent.type(screen.getAllByRole("textbox")[0], "100");
1720
- userEvent.type(screen.getAllByRole("textbox")[1], "200");
1721
-
1722
- // Open the dropdown and select the second (idx: 1) item
1723
- userEvent.click(screen.getByRole("button"));
1724
- jest.runOnlyPendingTimers();
1725
- userEvent.click(screen.getAllByRole("option")[1]);
1726
- jest.runOnlyPendingTimers();
1727
-
1728
- // Act
1729
- const userInput = renderer.getUserInputForWidgets();
1730
-
1731
- // Assert
1732
- expect(userInput).toMatchInlineSnapshot(`
1733
- {
1734
- "dropdown 1": {
1735
- "value": 1,
1736
- },
1737
- "image 1": null,
1738
- "input-number 1": {
1739
- "currentValue": "100",
1740
- },
1741
- "input-number 2": {
1742
- "currentValue": "200",
1743
- },
1744
- }
1745
- `);
1746
- });
1747
- });
1748
-
1749
- describe("examples", () => {
1750
- it("should return examples if all widgets return the same examples (or null)", () => {
1751
- // Arrange
1752
- const {renderer} = renderQuestion({
1753
- content:
1754
- "Input widget: [[\u2603 input-number 1]]\n\n" +
1755
- "Dropdown widget: [[\u2603 dropdown 1]]\n\n" +
1756
- "Image widget (won't have user input): [[\u2603 image 1]]\n\n" +
1757
- "Another input widget: [[\u2603 input-number 2]]",
1758
- widgets: {
1759
- "image 1": imageWidget,
1760
- "input-number 1": inputNumberWidget,
1761
- "input-number 2": inputNumberWidget,
1762
- "dropdown 1": dropdownWidget,
1763
- },
1764
- images: {},
1765
- });
1766
-
1767
- // Act
1768
- const examples = renderer.examples();
1769
-
1770
- // Assert
1771
- expect(examples).toMatchInlineSnapshot(`
1772
- [
1773
- "**Your answer should be** ",
1774
- "an integer, like $6$",
1775
- "a *proper* fraction, like $1/2$ or $6/10$",
1776
- "an *improper* fraction, like $10/7$ or $14/8$",
1777
- "a mixed number, like $1\\ 3/4$",
1778
- ]
1779
- `);
1780
- });
1781
-
1782
- it("should return nothing if widgets return the different examples", () => {
1783
- // NOTE(jeremy): I'm unsure why we don't return examples if the
1784
- // examples aren't the same, but this is current functionality so
1785
- // I'm adding this test to verify the current behaviour.
1786
-
1787
- // Arrange
1788
- const {renderer} = renderQuestion({
1789
- content:
1790
- "Input widget: [[\u2603 input-number 1]]\n\n" +
1791
- "Dropdown widget: [[\u2603 dropdown 1]]\n\n" +
1792
- "Image widget (won't have user input): [[\u2603 image 1]]\n\n" +
1793
- "Another input widget: [[\u2603 input-number 2]]",
1794
- widgets: {
1795
- "image 1": imageWidget,
1796
- "input-number 1": inputNumberWidget,
1797
- "input-number 2": {
1798
- ...inputNumberWidget,
1799
- options: {
1800
- ...inputNumberWidget.options,
1801
- answerType: "percent",
1802
- } as PerseusInputNumberWidgetOptions,
1803
- },
1804
- "dropdown 1": dropdownWidget,
1805
- },
1806
- images: {},
1807
- });
1808
-
1809
- // Act
1810
- const examples = renderer.examples();
1811
-
1812
- // Assert
1813
- expect(examples).toBeNull();
1814
- });
1815
- });
1816
- });