@khanacademy/perseus 2.1.0 → 4.0.0

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