@khanacademy/perseus 3.0.0 → 4.0.1

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 (1209) hide show
  1. package/.babelrc.js +1 -1
  2. package/.eslintrc.js +1 -0
  3. package/CHANGELOG.md +32 -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 +17390 -21159
  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 +12443 -15871
  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 +9 -9
  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__/categorizer.test.ts +203 -0
  691. package/src/widgets/__tests__/cs-program.test.ts +43 -0
  692. package/src/widgets/__tests__/definition.test.ts +136 -0
  693. package/src/widgets/__tests__/dropdown.test.ts +105 -0
  694. package/src/widgets/__tests__/explanation.test.ts +198 -0
  695. package/src/widgets/__tests__/expression.test.ts +431 -0
  696. package/src/widgets/__tests__/graded-group-set-jipt.test.ts +30 -0
  697. package/src/widgets/__tests__/graded-group-set.test.ts +288 -0
  698. package/src/widgets/__tests__/graded-group.test.ts +232 -0
  699. package/src/widgets/__tests__/grapher.cypress.ts +576 -0
  700. package/src/widgets/__tests__/grapher.test.ts +33 -0
  701. package/src/widgets/__tests__/group.test.tsx +512 -0
  702. package/src/widgets/__tests__/iframe.test.ts +45 -0
  703. package/src/widgets/__tests__/image.test.ts +62 -0
  704. package/src/widgets/__tests__/input-number.test.ts +354 -0
  705. package/src/widgets/__tests__/interaction.test.ts +31 -0
  706. package/src/widgets/__tests__/interactive-graph.cypress.ts +815 -0
  707. package/src/widgets/__tests__/interactive-graph.test.ts +106 -0
  708. package/src/widgets/__tests__/matcher.test.tsx +152 -0
  709. package/src/widgets/__tests__/matrix.test.ts +82 -0
  710. package/src/widgets/__tests__/number-line.test.ts +79 -0
  711. package/src/widgets/__tests__/numeric-input.test.ts +502 -0
  712. package/src/widgets/__tests__/orderer.test.ts +73 -0
  713. package/src/widgets/__tests__/passage-ref.test.ts +168 -0
  714. package/src/widgets/__tests__/passage.test.tsx +224 -0
  715. package/src/widgets/__tests__/radio/base-radio.test.tsx +470 -0
  716. package/src/widgets/__tests__/radio/choice-icon.test.tsx +90 -0
  717. package/src/widgets/__tests__/radio/choice.test.tsx +310 -0
  718. package/src/widgets/__tests__/radio/focus-ring.test.tsx +23 -0
  719. package/src/widgets/__tests__/radio/option-status.test.tsx +77 -0
  720. package/src/widgets/__tests__/radio.test.ts +807 -0
  721. package/src/widgets/__tests__/renderQuestion.tsx +77 -0
  722. package/src/widgets/__tests__/unit.test.tsx +168 -0
  723. package/src/widgets/__tests__/video.test.ts +53 -0
  724. package/src/widgets/categorizer.tsx +343 -0
  725. package/src/widgets/cs-program.tsx +227 -0
  726. package/src/widgets/definition.tsx +121 -0
  727. package/src/widgets/dropdown.tsx +142 -0
  728. package/src/widgets/explanation.tsx +317 -0
  729. package/src/widgets/expression.tsx +647 -0
  730. package/src/widgets/graded-group-answer-bar.tsx +142 -0
  731. package/src/widgets/graded-group-set.tsx +296 -0
  732. package/src/widgets/graded-group.tsx +466 -0
  733. package/src/widgets/grapher/util.tsx +803 -0
  734. package/src/widgets/grapher.tsx +674 -0
  735. package/src/widgets/group.tsx +196 -0
  736. package/src/widgets/iframe.tsx +195 -0
  737. package/src/widgets/image.tsx +309 -0
  738. package/src/widgets/input-number.tsx +413 -0
  739. package/src/widgets/interaction/arrow-picker.tsx +38 -0
  740. package/src/widgets/interaction/color-picker.tsx +69 -0
  741. package/src/widgets/interaction/dash-picker.tsx +45 -0
  742. package/src/widgets/interaction/element-container.tsx +101 -0
  743. package/src/widgets/interaction.tsx +826 -0
  744. package/src/widgets/interactive-graph.tsx +2921 -0
  745. package/src/widgets/label-image/__stories__/answer-choices.stories.tsx +98 -0
  746. package/src/widgets/label-image/__stories__/marker.stories.tsx +181 -0
  747. package/src/widgets/label-image/answer-choices.tsx +300 -0
  748. package/src/widgets/label-image/marker.tsx +364 -0
  749. package/src/widgets/label-image/types.ts +26 -0
  750. package/src/widgets/label-image.test.ts +840 -0
  751. package/src/widgets/label-image.tsx +849 -0
  752. package/src/widgets/lights-puzzle.tsx +359 -0
  753. package/src/widgets/matcher.tsx +278 -0
  754. package/src/widgets/matrix.tsx +637 -0
  755. package/src/widgets/measurer.tsx +217 -0
  756. package/src/widgets/molecule/__tests__/layout.test.ts +162 -0
  757. package/src/widgets/molecule/__tests__/parser.test.ts +155 -0
  758. package/src/widgets/molecule/molecule-drawing.ts +173 -0
  759. package/src/widgets/molecule/molecule-layout.ts +362 -0
  760. package/src/widgets/molecule/smiles-parser.ts +296 -0
  761. package/src/widgets/molecule.tsx +172 -0
  762. package/src/widgets/number-line.tsx +864 -0
  763. package/src/widgets/numeric-input.tsx +563 -0
  764. package/src/widgets/orderer.tsx +788 -0
  765. package/src/widgets/passage/__tests__/passage-markdown.test.ts +376 -0
  766. package/src/widgets/passage/passage-markdown.tsx +475 -0
  767. package/src/widgets/passage-ref-target.tsx +73 -0
  768. package/src/widgets/passage-ref.tsx +215 -0
  769. package/src/widgets/passage.tsx +584 -0
  770. package/src/widgets/plotter.tsx +1204 -0
  771. package/src/widgets/radio/__stories__/base-radio.stories.tsx +204 -0
  772. package/src/widgets/radio/__stories__/choice-icon.stories.tsx +168 -0
  773. package/src/widgets/radio/__stories__/choice-none-above.stories.tsx +40 -0
  774. package/src/widgets/radio/__stories__/choice.stories.tsx +137 -0
  775. package/src/widgets/radio/__stories__/focus-ring.stories.tsx +43 -0
  776. package/src/widgets/radio/__stories__/option-status.stories.tsx +105 -0
  777. package/src/widgets/radio/base-radio.tsx +509 -0
  778. package/src/widgets/radio/choice-a11y-text.ts +61 -0
  779. package/src/widgets/radio/choice-icon/choice-icon.tsx +217 -0
  780. package/src/widgets/radio/choice-icon/cross-out-line.tsx +53 -0
  781. package/src/widgets/radio/choice-icon/shared-styles.ts +15 -0
  782. package/src/widgets/radio/choice-none-above.tsx +56 -0
  783. package/src/widgets/radio/choice.jsx.fixture.ts +45 -0
  784. package/src/widgets/radio/choice.tsx +358 -0
  785. package/src/widgets/radio/focus-ring.tsx +57 -0
  786. package/src/widgets/radio/option-status.tsx +100 -0
  787. package/src/widgets/radio/util.test.ts +18 -0
  788. package/src/widgets/radio/util.ts +31 -0
  789. package/src/widgets/radio/widget.tsx +521 -0
  790. package/src/widgets/radio.ts +157 -0
  791. package/src/widgets/reaction-diagram.tsx +144 -0
  792. package/src/widgets/sequence.tsx +137 -0
  793. package/src/widgets/simulator.tsx +831 -0
  794. package/src/widgets/sorter.tsx +154 -0
  795. package/src/widgets/table.tsx +393 -0
  796. package/src/widgets/transformer.tsx +2951 -0
  797. package/src/widgets/unit.tsx +374 -0
  798. package/src/widgets/video-transcript-link.test.tsx +213 -0
  799. package/src/widgets/video-transcript-link.tsx +84 -0
  800. package/src/widgets/video.tsx +124 -0
  801. package/src/widgets.ts +444 -0
  802. package/src/zoom.ts +558 -0
  803. package/tsconfig.json +25 -0
  804. package/tsconfig.tsbuildinfo +1 -0
  805. package/src/__stories__/hints-renderer.stories.jsx +0 -60
  806. package/src/__stories__/item-renderer.stories.jsx +0 -27
  807. package/src/__stories__/server-item-renderer.stories.jsx +0 -27
  808. package/src/__testdata__/graphie_testdata.js +0 -53
  809. package/src/__testdata__/item-renderer_testdata.js +0 -161
  810. package/src/__testdata__/renderer_testdata.js +0 -123
  811. package/src/__tests__/a11y_test.jsx +0 -304
  812. package/src/__tests__/item-renderer_test.jsx +0 -892
  813. package/src/__tests__/jipt-paragraphs_test.js +0 -34
  814. package/src/__tests__/mock-asset-loading-widget.jsx +0 -57
  815. package/src/__tests__/mock-widget.jsx +0 -35
  816. package/src/__tests__/perseus-markdown_test.jsx +0 -747
  817. package/src/__tests__/renderability_test.jsx +0 -481
  818. package/src/__tests__/renderer-api_test.jsx +0 -309
  819. package/src/__tests__/renderer_test.jsx +0 -1792
  820. package/src/__tests__/server-item-renderer_test.jsx +0 -516
  821. package/src/__tests__/test-items/image-item.js +0 -25
  822. package/src/__tests__/test-items/input-number-1-item.js +0 -27
  823. package/src/__tests__/test-items/input-number-2-item.js +0 -39
  824. package/src/__tests__/test-items/table-item.js +0 -30
  825. package/src/__tests__/tex-wrangler_test.js +0 -83
  826. package/src/__tests__/util_test.js +0 -41
  827. package/src/a11y.js +0 -46
  828. package/src/all-widgets.js +0 -9
  829. package/src/article-renderer.jsx +0 -214
  830. package/src/asset-context.js +0 -30
  831. package/src/basic-widgets.js +0 -13
  832. package/src/components/__stories__/button-group.stories.jsx +0 -61
  833. package/src/components/__stories__/fixed-to-responsive.stories.jsx +0 -132
  834. package/src/components/__stories__/graph.stories.jsx +0 -27
  835. package/src/components/__stories__/graphie.stories.jsx +0 -32
  836. package/src/components/__stories__/hud.stories.jsx +0 -36
  837. package/src/components/__stories__/icon.stories.jsx +0 -56
  838. package/src/components/__stories__/image-loader.stories.jsx +0 -60
  839. package/src/components/__stories__/info-tip.stories.jsx +0 -33
  840. package/src/components/__stories__/inline-icon.stories.jsx +0 -39
  841. package/src/components/__stories__/input-with-examples.stories.jsx +0 -52
  842. package/src/components/__stories__/lint.stories.jsx +0 -40
  843. package/src/components/__stories__/math-input.stories.jsx +0 -29
  844. package/src/components/__stories__/math-output.stories.jsx +0 -24
  845. package/src/components/__stories__/multi-button-group.stories.jsx +0 -69
  846. package/src/components/__stories__/number-input.stories.jsx +0 -42
  847. package/src/components/__stories__/prop-check-box.stories.jsx +0 -62
  848. package/src/components/__stories__/range-input.stories.jsx +0 -28
  849. package/src/components/__stories__/simple-keypad-input.stories.jsx +0 -28
  850. package/src/components/__stories__/sortable.stories.jsx +0 -73
  851. package/src/components/__stories__/stub-tag-editor.stories.jsx +0 -44
  852. package/src/components/__stories__/svg-image.stories.jsx +0 -71
  853. package/src/components/__stories__/tex.stories.jsx +0 -24
  854. package/src/components/__stories__/text-buttons.stories.jsx +0 -51
  855. package/src/components/__stories__/text-input.stories.jsx +0 -34
  856. package/src/components/__stories__/text-list-editor.stories.jsx +0 -33
  857. package/src/components/__stories__/zoomable-tex.stories.jsx +0 -39
  858. package/src/components/__stories__/zoomable.stories.jsx +0 -40
  859. package/src/components/__testdata__/sorter_testdata.js +0 -20
  860. package/src/components/__tests__/number-input_test.jsx +0 -112
  861. package/src/components/__tests__/sortable_test.jsx +0 -234
  862. package/src/components/__tests__/sorter_test.jsx +0 -111
  863. package/src/components/__tests__/zoomable_test.jsx +0 -388
  864. package/src/components/button-group.jsx +0 -131
  865. package/src/components/constants.js +0 -15
  866. package/src/components/fixed-to-responsive.jsx +0 -165
  867. package/src/components/graph.jsx +0 -413
  868. package/src/components/graphie-classes.jsx +0 -148
  869. package/src/components/graphie-movables.jsx +0 -342
  870. package/src/components/graphie.jsx +0 -353
  871. package/src/components/highlighting/highlightable-content.jsx +0 -245
  872. package/src/components/highlighting/highlights.js +0 -208
  873. package/src/components/highlighting/ranges.js +0 -299
  874. package/src/components/highlighting/types.js +0 -73
  875. package/src/components/highlighting/ui/highlight-renderer.jsx +0 -179
  876. package/src/components/highlighting/ui/highlight-set-renderer.jsx +0 -190
  877. package/src/components/highlighting/ui/highlight-tooltip.jsx +0 -143
  878. package/src/components/highlighting/ui/highlighting-ui.jsx +0 -163
  879. package/src/components/highlighting/ui/selection-tracker.jsx +0 -184
  880. package/src/components/highlighting/ui/types.js +0 -21
  881. package/src/components/highlighting/ui/util.js +0 -166
  882. package/src/components/highlighting/word-indexer.jsx +0 -155
  883. package/src/components/hud.jsx +0 -179
  884. package/src/components/icon.jsx +0 -149
  885. package/src/components/image-loader.jsx +0 -175
  886. package/src/components/info-tip/info-tip.jsx +0 -146
  887. package/src/components/info-tip.jsx +0 -38
  888. package/src/components/inline-icon.jsx +0 -81
  889. package/src/components/input-with-examples.jsx +0 -248
  890. package/src/components/lint.jsx +0 -455
  891. package/src/components/math-input.jsx +0 -293
  892. package/src/components/math-output.jsx +0 -128
  893. package/src/components/multi-button-group.jsx +0 -137
  894. package/src/components/number-input.jsx +0 -253
  895. package/src/components/prop-check-box.jsx +0 -80
  896. package/src/components/range-input.jsx +0 -61
  897. package/src/components/simple-keypad-input.jsx +0 -88
  898. package/src/components/sortable.jsx +0 -872
  899. package/src/components/stub-tag-editor.jsx +0 -54
  900. package/src/components/svg-image.jsx +0 -765
  901. package/src/components/tex-buttons.jsx +0 -320
  902. package/src/components/tex.jsx +0 -70
  903. package/src/components/text-input.jsx +0 -133
  904. package/src/components/text-list-editor.jsx +0 -157
  905. package/src/components/tooltip.jsx +0 -392
  906. package/src/components/visibility-observer/__stories__/visibility-observer.stories.jsx +0 -140
  907. package/src/components/visibility-observer/get-scroll-parent.js +0 -45
  908. package/src/components/visibility-observer/visibility-observer.js +0 -109
  909. package/src/components/visibility-observer/visibility-observer.jsx +0 -123
  910. package/src/components/zoomable-tex.jsx +0 -70
  911. package/src/components/zoomable.jsx +0 -294
  912. package/src/components.js +0 -17
  913. package/src/definition-context.js +0 -59
  914. package/src/dependencies.js +0 -22
  915. package/src/error-boundary.jsx +0 -48
  916. package/src/extra-widgets.js +0 -75
  917. package/src/hint-renderer.jsx +0 -152
  918. package/src/hints-renderer.jsx +0 -367
  919. package/src/icon-paths.js +0 -120
  920. package/src/index.js +0 -159
  921. package/src/init.js +0 -49
  922. package/src/interaction-tracker.js +0 -60
  923. package/src/interactive2/__tests__/movable-mock.js +0 -48
  924. package/src/interactive2/__tests__/movable-point_test.js +0 -146
  925. package/src/interactive2/interactive-util.js +0 -152
  926. package/src/interactive2/movable-helper-methods.js +0 -160
  927. package/src/interactive2/movable-line-options.js +0 -392
  928. package/src/interactive2/movable-line.js +0 -322
  929. package/src/interactive2/movable-point-options.js +0 -190
  930. package/src/interactive2/movable-point.jsx +0 -597
  931. package/src/interactive2/movable-polygon-options.js +0 -351
  932. package/src/interactive2/movable-polygon.js +0 -368
  933. package/src/interactive2/movable.js +0 -273
  934. package/src/interactive2/objective_.js +0 -79
  935. package/src/interactive2/types.js +0 -3
  936. package/src/interactive2/wrapped-defaults.js +0 -86
  937. package/src/interactive2/wrapped-ellipse.js +0 -86
  938. package/src/interactive2/wrapped-line.js +0 -109
  939. package/src/interactive2/wrapped-path.js +0 -47
  940. package/src/interactive2.js +0 -91
  941. package/src/item-renderer.jsx +0 -514
  942. package/src/item-version.js +0 -12
  943. package/src/jipt-paragraphs.js +0 -52
  944. package/src/keypad-context.js +0 -33
  945. package/src/loading-context.js +0 -20
  946. package/src/logging/log.js +0 -107
  947. package/src/logging/log_test.js +0 -41
  948. package/src/mixins/__tests__/widget-prop-denylist_test.js +0 -23
  949. package/src/mixins/changeable.jsx +0 -115
  950. package/src/mixins/editor-jsonify.jsx +0 -13
  951. package/src/mixins/provide-keypad.jsx +0 -131
  952. package/src/mixins/widget-jsonify-deprecated.jsx +0 -23
  953. package/src/mixins/widget-prop-denylist.js +0 -49
  954. package/src/multi-items/__stories__/multi-renderer.stories.js +0 -88
  955. package/src/multi-items/__testdata__/multi-renderer_testdata.js +0 -174
  956. package/src/multi-items/__tests__/items_test.js +0 -245
  957. package/src/multi-items/__tests__/multi-renderer_test.js +0 -903
  958. package/src/multi-items/__tests__/prop-type-builders_test.js +0 -134
  959. package/src/multi-items/__tests__/shapes_test.js +0 -44
  960. package/src/multi-items/__tests__/trees_test.js +0 -316
  961. package/src/multi-items/items.js +0 -176
  962. package/src/multi-items/multi-renderer.jsx +0 -543
  963. package/src/multi-items/prop-type-builders.js +0 -105
  964. package/src/multi-items/shape-types.js +0 -47
  965. package/src/multi-items/shapes.js +0 -53
  966. package/src/multi-items/tree-types.js +0 -42
  967. package/src/multi-items/trees.js +0 -345
  968. package/src/multi-items.js +0 -76
  969. package/src/perseus-api.jsx +0 -214
  970. package/src/perseus-env.js +0 -26
  971. package/src/perseus-error.js +0 -19
  972. package/src/perseus-markdown.jsx +0 -408
  973. package/src/perseus-types.js +0 -1471
  974. package/src/question-paragraph.jsx +0 -30
  975. package/src/renderability.jsx +0 -108
  976. package/src/renderer.jsx +0 -1937
  977. package/src/server-item-renderer.jsx +0 -467
  978. package/src/sigfigs.jsx +0 -492
  979. package/src/styles/constants.js +0 -96
  980. package/src/styles/global-constants.js +0 -344
  981. package/src/styles/global-styles.js +0 -307
  982. package/src/styles/media-queries.js +0 -48
  983. package/src/styles/shared.js +0 -132
  984. package/src/tex-wrangler.js +0 -176
  985. package/src/translation-linter.js +0 -125
  986. package/src/traversal.jsx +0 -141
  987. package/src/types.js +0 -482
  988. package/src/util/a11y.js +0 -24
  989. package/src/util/answer-types.js +0 -861
  990. package/src/util/answer-types_test.js +0 -115
  991. package/src/util/colors.js +0 -94
  992. package/src/util/fix-passage-refs.jsx +0 -122
  993. package/src/util/graph-utils.js +0 -16
  994. package/src/util/graphie.js +0 -1586
  995. package/src/util/interactive.js +0 -4078
  996. package/src/util/katex-preprocess.js +0 -16
  997. package/src/util/math.js +0 -289
  998. package/src/util/mobile-native-utils.js +0 -14
  999. package/src/util/mobile-native-utils_test.js +0 -24
  1000. package/src/util/react-render.js +0 -27
  1001. package/src/util/register-all-widgets-for-testing.js +0 -12
  1002. package/src/util/scroll-utils.js +0 -162
  1003. package/src/util/sizing-utils.js +0 -46
  1004. package/src/util/tex.js +0 -203
  1005. package/src/util/url-utils.js +0 -36
  1006. package/src/util.js +0 -984
  1007. package/src/version.js +0 -11
  1008. package/src/widget-container.jsx +0 -178
  1009. package/src/widgets/__stories__/categorizer.stories.jsx +0 -15
  1010. package/src/widgets/__stories__/cs-program.stories.jsx +0 -15
  1011. package/src/widgets/__stories__/definition.stories.jsx +0 -108
  1012. package/src/widgets/__stories__/dropdown.stories.jsx +0 -15
  1013. package/src/widgets/__stories__/explanation.stories.jsx +0 -19
  1014. package/src/widgets/__stories__/expression.stories.jsx +0 -26
  1015. package/src/widgets/__stories__/graded-group-set.stories.jsx +0 -32
  1016. package/src/widgets/__stories__/graded-group.stories.jsx +0 -32
  1017. package/src/widgets/__stories__/grapher.stories.jsx +0 -47
  1018. package/src/widgets/__stories__/group.stories.jsx +0 -15
  1019. package/src/widgets/__stories__/iframe.stories.jsx +0 -15
  1020. package/src/widgets/__stories__/image.stories.jsx +0 -62
  1021. package/src/widgets/__stories__/input-number.stories.jsx +0 -102
  1022. package/src/widgets/__stories__/interaction.stories.jsx +0 -15
  1023. package/src/widgets/__stories__/interactive-graph.stories.jsx +0 -61
  1024. package/src/widgets/__stories__/matcher.stories.jsx +0 -15
  1025. package/src/widgets/__stories__/matrix.stories.jsx +0 -15
  1026. package/src/widgets/__stories__/number-line.stories.jsx +0 -15
  1027. package/src/widgets/__stories__/numeric-input.stories.jsx +0 -106
  1028. package/src/widgets/__stories__/on-screen-keypad.stories.jsx +0 -118
  1029. package/src/widgets/__stories__/orderer.stories.jsx +0 -15
  1030. package/src/widgets/__stories__/passage-ref.stories.jsx +0 -23
  1031. package/src/widgets/__stories__/passage.stories.jsx +0 -27
  1032. package/src/widgets/__stories__/radio.stories.jsx +0 -90
  1033. package/src/widgets/__stories__/sorter.stories.jsx +0 -15
  1034. package/src/widgets/__stories__/transformer.stories.jsx +0 -15
  1035. package/src/widgets/__stories__/unit.stories.jsx +0 -29
  1036. package/src/widgets/__stories__/video-transcript-link.stories.jsx +0 -24
  1037. package/src/widgets/__stories__/video.stories.jsx +0 -15
  1038. package/src/widgets/__testdata__/base-radio_testdata.js +0 -22
  1039. package/src/widgets/__testdata__/categorizer_testdata.js +0 -50
  1040. package/src/widgets/__testdata__/cs-program_testdata.js +0 -28
  1041. package/src/widgets/__testdata__/dropdown_testdata.js +0 -34
  1042. package/src/widgets/__testdata__/explanation_testdata.js +0 -56
  1043. package/src/widgets/__testdata__/expression_testdata.js +0 -95
  1044. package/src/widgets/__testdata__/graded-group-set_testdata.js +0 -358
  1045. package/src/widgets/__testdata__/graded-group_testdata.js +0 -79
  1046. package/src/widgets/__testdata__/grapher_testdata.js +0 -376
  1047. package/src/widgets/__testdata__/group_testdata.js +0 -156
  1048. package/src/widgets/__testdata__/iframe_testdata.js +0 -36
  1049. package/src/widgets/__testdata__/image_testdata.js +0 -38
  1050. package/src/widgets/__testdata__/input-number_testdata.js +0 -73
  1051. package/src/widgets/__testdata__/interaction_testdata.js +0 -264
  1052. package/src/widgets/__testdata__/interactive-graph_testdata.js +0 -646
  1053. package/src/widgets/__testdata__/matcher_testdata.js +0 -34
  1054. package/src/widgets/__testdata__/matrix_testdata.js +0 -29
  1055. package/src/widgets/__testdata__/number-line_testdata.js +0 -31
  1056. package/src/widgets/__testdata__/numeric-input_testdata.js +0 -266
  1057. package/src/widgets/__testdata__/orderer_testdata.js +0 -58
  1058. package/src/widgets/__testdata__/passage-ref_testdata.js +0 -103
  1059. package/src/widgets/__testdata__/passage_testdata.js +0 -96
  1060. package/src/widgets/__testdata__/radio_testdata.js +0 -262
  1061. package/src/widgets/__testdata__/sorter_testdata.js +0 -20
  1062. package/src/widgets/__testdata__/transformer_testdata.js +0 -109
  1063. package/src/widgets/__testdata__/video_testdata.js +0 -22
  1064. package/src/widgets/__tests__/categorizer_test.jsx +0 -205
  1065. package/src/widgets/__tests__/cs-program_test.jsx +0 -45
  1066. package/src/widgets/__tests__/definition_test.js +0 -137
  1067. package/src/widgets/__tests__/dropdown_test.jsx +0 -106
  1068. package/src/widgets/__tests__/explanation_test.js +0 -199
  1069. package/src/widgets/__tests__/expression_test.jsx +0 -429
  1070. package/src/widgets/__tests__/graded-group-set-jipt_test.jsx +0 -32
  1071. package/src/widgets/__tests__/graded-group-set_test.jsx +0 -290
  1072. package/src/widgets/__tests__/graded-group_test.jsx +0 -233
  1073. package/src/widgets/__tests__/grapher.cypress.jsx +0 -546
  1074. package/src/widgets/__tests__/grapher_test.jsx +0 -35
  1075. package/src/widgets/__tests__/group_test.jsx +0 -512
  1076. package/src/widgets/__tests__/iframe_test.jsx +0 -47
  1077. package/src/widgets/__tests__/image_test.jsx +0 -65
  1078. package/src/widgets/__tests__/input-number_test.jsx +0 -352
  1079. package/src/widgets/__tests__/interaction_test.jsx +0 -33
  1080. package/src/widgets/__tests__/interactive-graph.cypress.jsx +0 -777
  1081. package/src/widgets/__tests__/interactive-graph_test.jsx +0 -105
  1082. package/src/widgets/__tests__/matcher_test.jsx +0 -157
  1083. package/src/widgets/__tests__/matrix_test.jsx +0 -84
  1084. package/src/widgets/__tests__/number-line_test.jsx +0 -81
  1085. package/src/widgets/__tests__/numeric-input_test.jsx +0 -503
  1086. package/src/widgets/__tests__/orderer_test.jsx +0 -75
  1087. package/src/widgets/__tests__/passage-ref_test.jsx +0 -166
  1088. package/src/widgets/__tests__/passage_test.jsx +0 -212
  1089. package/src/widgets/__tests__/radio/base-radio_test.jsx +0 -443
  1090. package/src/widgets/__tests__/radio/choice-icon_test.jsx +0 -91
  1091. package/src/widgets/__tests__/radio/choice_test.jsx +0 -311
  1092. package/src/widgets/__tests__/radio/focus-ring_test.jsx +0 -23
  1093. package/src/widgets/__tests__/radio/option-status_test.jsx +0 -65
  1094. package/src/widgets/__tests__/radio_test.jsx +0 -810
  1095. package/src/widgets/__tests__/renderQuestion.jsx +0 -66
  1096. package/src/widgets/__tests__/unit_test.jsx +0 -169
  1097. package/src/widgets/__tests__/video_test.jsx +0 -55
  1098. package/src/widgets/categorizer.jsx +0 -343
  1099. package/src/widgets/cs-program.jsx +0 -227
  1100. package/src/widgets/definition.jsx +0 -121
  1101. package/src/widgets/dropdown.jsx +0 -141
  1102. package/src/widgets/explanation.jsx +0 -315
  1103. package/src/widgets/expression.jsx +0 -629
  1104. package/src/widgets/graded-group-answer-bar.jsx +0 -152
  1105. package/src/widgets/graded-group-set.jsx +0 -296
  1106. package/src/widgets/graded-group.jsx +0 -453
  1107. package/src/widgets/grapher/util.jsx +0 -806
  1108. package/src/widgets/grapher.jsx +0 -663
  1109. package/src/widgets/group.jsx +0 -195
  1110. package/src/widgets/iframe.jsx +0 -198
  1111. package/src/widgets/image.jsx +0 -310
  1112. package/src/widgets/input-number.jsx +0 -402
  1113. package/src/widgets/interaction/arrow-picker.jsx +0 -39
  1114. package/src/widgets/interaction/color-picker.jsx +0 -69
  1115. package/src/widgets/interaction/dash-picker.jsx +0 -46
  1116. package/src/widgets/interaction/element-container.jsx +0 -97
  1117. package/src/widgets/interaction.jsx +0 -742
  1118. package/src/widgets/interactive-graph.jsx +0 -2820
  1119. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +0 -93
  1120. package/src/widgets/label-image/__stories__/marker.stories.jsx +0 -172
  1121. package/src/widgets/label-image/answer-choices.jsx +0 -311
  1122. package/src/widgets/label-image/marker.jsx +0 -365
  1123. package/src/widgets/label-image/types.js +0 -33
  1124. package/src/widgets/label-image.jsx +0 -841
  1125. package/src/widgets/label-image_test.js +0 -840
  1126. package/src/widgets/lights-puzzle.jsx +0 -356
  1127. package/src/widgets/matcher.jsx +0 -274
  1128. package/src/widgets/matrix.jsx +0 -616
  1129. package/src/widgets/measurer.jsx +0 -215
  1130. package/src/widgets/molecule/__tests__/layout_test.jsx +0 -159
  1131. package/src/widgets/molecule/__tests__/parser_test.jsx +0 -142
  1132. package/src/widgets/molecule/molecule-drawing.jsx +0 -175
  1133. package/src/widgets/molecule/molecule-layout.jsx +0 -367
  1134. package/src/widgets/molecule/smiles-parser.jsx +0 -300
  1135. package/src/widgets/molecule.jsx +0 -172
  1136. package/src/widgets/number-line.jsx +0 -855
  1137. package/src/widgets/numeric-input.jsx +0 -546
  1138. package/src/widgets/orderer.jsx +0 -756
  1139. package/src/widgets/passage/__tests__/passage-markdown_test.jsx +0 -373
  1140. package/src/widgets/passage/passage-markdown.jsx +0 -472
  1141. package/src/widgets/passage-ref-target.jsx +0 -73
  1142. package/src/widgets/passage-ref.jsx +0 -214
  1143. package/src/widgets/passage.jsx +0 -576
  1144. package/src/widgets/plotter.jsx +0 -1198
  1145. package/src/widgets/radio/__stories__/base-radio.stories.jsx +0 -189
  1146. package/src/widgets/radio/__stories__/choice-icon.stories.jsx +0 -166
  1147. package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +0 -41
  1148. package/src/widgets/radio/__stories__/choice.stories.jsx +0 -138
  1149. package/src/widgets/radio/__stories__/focus-ring.stories.jsx +0 -44
  1150. package/src/widgets/radio/__stories__/option-status.stories.jsx +0 -104
  1151. package/src/widgets/radio/base-radio.jsx +0 -494
  1152. package/src/widgets/radio/choice-a11y-text.js +0 -62
  1153. package/src/widgets/radio/choice-icon/choice-icon.jsx +0 -214
  1154. package/src/widgets/radio/choice-icon/cross-out-line.jsx +0 -52
  1155. package/src/widgets/radio/choice-icon/shared-styles.js +0 -16
  1156. package/src/widgets/radio/choice-none-above.jsx +0 -63
  1157. package/src/widgets/radio/choice.jsx +0 -373
  1158. package/src/widgets/radio/choice.jsx.fixture.js +0 -46
  1159. package/src/widgets/radio/focus-ring.jsx +0 -58
  1160. package/src/widgets/radio/option-status.jsx +0 -98
  1161. package/src/widgets/radio/util.js +0 -32
  1162. package/src/widgets/radio/util_test.js +0 -19
  1163. package/src/widgets/radio/widget.jsx +0 -515
  1164. package/src/widgets/radio.jsx +0 -155
  1165. package/src/widgets/reaction-diagram.jsx +0 -137
  1166. package/src/widgets/sequence.jsx +0 -143
  1167. package/src/widgets/simulator.jsx +0 -802
  1168. package/src/widgets/sorter.jsx +0 -149
  1169. package/src/widgets/table.jsx +0 -389
  1170. package/src/widgets/transformer.jsx +0 -2872
  1171. package/src/widgets/unit.jsx +0 -357
  1172. package/src/widgets/video-transcript-link.jsx +0 -84
  1173. package/src/widgets/video-transcript-link_test.js +0 -211
  1174. package/src/widgets/video.jsx +0 -126
  1175. package/src/widgets.js +0 -431
  1176. package/src/zoom.js +0 -539
  1177. /package/{src/multi-items/item-types.js → dist/multi-items/item-types.js.flow} +0 -0
  1178. /package/src/__tests__/__snapshots__/{item-renderer_test.jsx.snap → item-renderer.test.tsx.snap} +0 -0
  1179. /package/src/__tests__/__snapshots__/{perseus-markdown_test.jsx.snap → perseus-markdown.test.ts.snap} +0 -0
  1180. /package/src/__tests__/__snapshots__/{renderer_test.jsx.snap → renderer.test.tsx.snap} +0 -0
  1181. /package/src/__tests__/__snapshots__/{server-item-renderer_test.jsx.snap → server-item-renderer.test.tsx.snap} +0 -0
  1182. /package/src/components/__tests__/__snapshots__/{sortable_test.jsx.snap → sortable.test.tsx.snap} +0 -0
  1183. /package/src/components/__tests__/__snapshots__/{sorter_test.jsx.snap → sorter.test.tsx.snap} +0 -0
  1184. /package/src/multi-items/__tests__/__snapshots__/{multi-renderer_test.js.snap → multi-renderer.test.tsx.snap} +0 -0
  1185. /package/src/widgets/__tests__/__snapshots__/{categorizer_test.jsx.snap → categorizer.test.ts.snap} +0 -0
  1186. /package/src/widgets/__tests__/__snapshots__/{cs-program_test.jsx.snap → cs-program.test.ts.snap} +0 -0
  1187. /package/src/widgets/__tests__/__snapshots__/{definition_test.js.snap → definition.test.ts.snap} +0 -0
  1188. /package/src/widgets/__tests__/__snapshots__/{dropdown_test.jsx.snap → dropdown.test.ts.snap} +0 -0
  1189. /package/src/widgets/__tests__/__snapshots__/{explanation_test.js.snap → explanation.test.ts.snap} +0 -0
  1190. /package/src/widgets/__tests__/__snapshots__/{graded-group-set-jipt_test.jsx.snap → graded-group-set-jipt.test.ts.snap} +0 -0
  1191. /package/src/widgets/__tests__/__snapshots__/{graded-group-set_test.jsx.snap → graded-group-set.test.ts.snap} +0 -0
  1192. /package/src/widgets/__tests__/__snapshots__/{graded-group_test.jsx.snap → graded-group.test.ts.snap} +0 -0
  1193. /package/src/widgets/__tests__/__snapshots__/{grapher_test.jsx.snap → grapher.test.ts.snap} +0 -0
  1194. /package/src/widgets/__tests__/__snapshots__/{group_test.jsx.snap → group.test.tsx.snap} +0 -0
  1195. /package/src/widgets/__tests__/__snapshots__/{iframe_test.jsx.snap → iframe.test.ts.snap} +0 -0
  1196. /package/src/widgets/__tests__/__snapshots__/{image_test.jsx.snap → image.test.ts.snap} +0 -0
  1197. /package/src/widgets/__tests__/__snapshots__/{input-number_test.jsx.snap → input-number.test.ts.snap} +0 -0
  1198. /package/src/widgets/__tests__/__snapshots__/{interaction_test.jsx.snap → interaction.test.ts.snap} +0 -0
  1199. /package/src/widgets/__tests__/__snapshots__/{interactive-graph_test.jsx.snap → interactive-graph.test.ts.snap} +0 -0
  1200. /package/src/widgets/__tests__/__snapshots__/{matcher_test.jsx.snap → matcher.test.tsx.snap} +0 -0
  1201. /package/src/widgets/__tests__/__snapshots__/{matrix_test.jsx.snap → matrix.test.ts.snap} +0 -0
  1202. /package/src/widgets/__tests__/__snapshots__/{number-line_test.jsx.snap → number-line.test.ts.snap} +0 -0
  1203. /package/src/widgets/__tests__/__snapshots__/{numeric-input_test.jsx.snap → numeric-input.test.ts.snap} +0 -0
  1204. /package/src/widgets/__tests__/__snapshots__/{orderer_test.jsx.snap → orderer.test.ts.snap} +0 -0
  1205. /package/src/widgets/__tests__/__snapshots__/{passage-ref_test.jsx.snap → passage-ref.test.ts.snap} +0 -0
  1206. /package/src/widgets/__tests__/__snapshots__/{passage_test.jsx.snap → passage.test.tsx.snap} +0 -0
  1207. /package/src/widgets/__tests__/__snapshots__/{radio_test.jsx.snap → radio.test.ts.snap} +0 -0
  1208. /package/src/widgets/__tests__/__snapshots__/{unit_test.jsx.snap → unit.test.tsx.snap} +0 -0
  1209. /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
+ });