@khanacademy/perseus 0.0.1 → 0.2.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 (507) hide show
  1. package/.babelrc.js +1 -1
  2. package/CHANGELOG.md +29 -0
  3. package/dist/es/index.css +4361 -0
  4. package/dist/es/index.js +7 -1
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.css +4361 -0
  7. package/dist/index.js +7 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/strings.js +148 -46
  10. package/package.json +50 -4
  11. package/src/README.md +35 -0
  12. package/src/__stories__/item-renderer.stories.jsx +27 -0
  13. package/src/__stories__/server-item-renderer.stories.jsx +27 -0
  14. package/src/__testdata__/item-renderer_testdata.js +161 -0
  15. package/src/__testdata__/renderer_testdata.js +123 -0
  16. package/src/__tests__/__snapshots__/item-renderer_test.jsx.snap +233 -0
  17. package/src/__tests__/__snapshots__/perseus-markdown_test.jsx.snap +516 -0
  18. package/src/__tests__/__snapshots__/pure-markdown_test.js.snap +113 -0
  19. package/src/__tests__/__snapshots__/renderer_test.jsx.snap +576 -0
  20. package/src/__tests__/__snapshots__/server-item-renderer_test.jsx.snap +306 -0
  21. package/src/__tests__/a11y_test.jsx +304 -0
  22. package/src/__tests__/item-renderer_test.jsx +892 -0
  23. package/src/__tests__/mock-asset-loading-widget.jsx +57 -0
  24. package/src/__tests__/mock-widget.jsx +35 -0
  25. package/src/__tests__/perseus-markdown_test.jsx +747 -0
  26. package/src/__tests__/pure-markdown_test.js +552 -0
  27. package/src/__tests__/renderability_test.jsx +481 -0
  28. package/src/__tests__/renderer-api_test.jsx +333 -0
  29. package/src/__tests__/renderer_test.jsx +1792 -0
  30. package/src/__tests__/server-item-renderer_test.jsx +516 -0
  31. package/src/__tests__/test-items/image-item.js +25 -0
  32. package/src/__tests__/test-items/input-number-1-item.js +27 -0
  33. package/src/__tests__/test-items/input-number-2-item.js +39 -0
  34. package/src/__tests__/test-items/table-item.js +30 -0
  35. package/src/__tests__/tex-wrangler_test.js +83 -0
  36. package/src/__tests__/util_test.js +41 -0
  37. package/src/a11y.js +46 -0
  38. package/src/all-widgets.js +9 -0
  39. package/src/article-renderer.jsx +215 -0
  40. package/src/asset-context.js +30 -0
  41. package/src/basic-widgets.js +13 -0
  42. package/src/components/__stories__/button-group.stories.jsx +61 -0
  43. package/src/components/__stories__/fixed-to-responsive.stories.jsx +132 -0
  44. package/src/components/__stories__/graph.stories.jsx +27 -0
  45. package/src/components/__stories__/graphie.stories.jsx +28 -0
  46. package/src/components/__stories__/hud.stories.jsx +36 -0
  47. package/src/components/__stories__/icon.stories.jsx +56 -0
  48. package/src/components/__stories__/image-loader.stories.jsx +60 -0
  49. package/src/components/__stories__/info-tip.stories.jsx +33 -0
  50. package/src/components/__stories__/inline-icon.stories.jsx +39 -0
  51. package/src/components/__stories__/input-with-examples.stories.jsx +51 -0
  52. package/src/components/__stories__/lint.stories.jsx +40 -0
  53. package/src/components/__stories__/math-input.stories.jsx +29 -0
  54. package/src/components/__stories__/math-output.stories.jsx +24 -0
  55. package/src/components/__stories__/multi-button-group.stories.jsx +69 -0
  56. package/src/components/__stories__/number-input.stories.jsx +42 -0
  57. package/src/components/__stories__/possible-answers.stories.jsx +22 -0
  58. package/src/components/__stories__/prop-check-box.stories.jsx +62 -0
  59. package/src/components/__stories__/range-input.stories.jsx +28 -0
  60. package/src/components/__stories__/simple-keypad-input.stories.jsx +28 -0
  61. package/src/components/__stories__/sortable.stories.jsx +73 -0
  62. package/src/components/__stories__/stub-tag-editor.stories.jsx +44 -0
  63. package/src/components/__stories__/svg-image.stories.jsx +60 -0
  64. package/src/components/__stories__/tex.stories.jsx +18 -0
  65. package/src/components/__stories__/text-buttons.stories.jsx +51 -0
  66. package/src/components/__stories__/text-input.stories.jsx +34 -0
  67. package/src/components/__stories__/text-list-editor.stories.jsx +33 -0
  68. package/src/components/__stories__/zoomable-tex.stories.jsx +39 -0
  69. package/src/components/__stories__/zoomable.stories.jsx +40 -0
  70. package/src/components/__testdata__/sorter_testdata.js +20 -0
  71. package/src/components/__tests__/__snapshots__/sortable_test.jsx.snap +79 -0
  72. package/src/components/__tests__/__snapshots__/sorter_test.jsx.snap +297 -0
  73. package/src/components/__tests__/number-input_test.jsx +112 -0
  74. package/src/components/__tests__/sortable_test.jsx +234 -0
  75. package/src/components/__tests__/sorter_test.jsx +111 -0
  76. package/src/components/__tests__/zoomable_test.jsx +388 -0
  77. package/src/components/button-group.jsx +131 -0
  78. package/src/components/constants.js +15 -0
  79. package/src/components/fixed-to-responsive.jsx +164 -0
  80. package/src/components/graph.jsx +415 -0
  81. package/src/components/graphie-classes.jsx +148 -0
  82. package/src/components/graphie-movables.jsx +342 -0
  83. package/src/components/graphie.jsx +353 -0
  84. package/src/components/highlighting/highlightable-content.jsx +245 -0
  85. package/src/components/highlighting/highlights.js +208 -0
  86. package/src/components/highlighting/ranges.js +299 -0
  87. package/src/components/highlighting/types.js +73 -0
  88. package/src/components/highlighting/ui/highlight-renderer.jsx +179 -0
  89. package/src/components/highlighting/ui/highlight-set-renderer.jsx +190 -0
  90. package/src/components/highlighting/ui/highlight-tooltip.jsx +153 -0
  91. package/src/components/highlighting/ui/highlighting-ui.jsx +163 -0
  92. package/src/components/highlighting/ui/selection-tracker.jsx +195 -0
  93. package/src/components/highlighting/ui/types.js +21 -0
  94. package/src/components/highlighting/ui/util.js +166 -0
  95. package/src/components/highlighting/word-indexer.jsx +155 -0
  96. package/src/components/hud.jsx +179 -0
  97. package/src/components/icon.jsx +149 -0
  98. package/src/components/image-loader.jsx +175 -0
  99. package/src/components/info-tip/info-tip.jsx +146 -0
  100. package/src/components/info-tip.jsx +38 -0
  101. package/src/components/inline-icon.jsx +81 -0
  102. package/src/components/input-with-examples.jsx +231 -0
  103. package/src/components/lint.jsx +454 -0
  104. package/src/components/math-input.jsx +282 -0
  105. package/src/components/math-output.jsx +128 -0
  106. package/src/components/multi-button-group.jsx +137 -0
  107. package/src/components/new-tooltip/README.md +110 -0
  108. package/src/components/new-tooltip/__stories__/flexible-position.stories.jsx +437 -0
  109. package/src/components/new-tooltip/__stories__/new-tooltip.stories.jsx +665 -0
  110. package/src/components/new-tooltip/__stories__/tooltip-arrow.stories.jsx +70 -0
  111. package/src/components/new-tooltip/__stories__/tooltip-bubble.stories.jsx +98 -0
  112. package/src/components/new-tooltip/__tests__/style-constants_test.js +8 -0
  113. package/src/components/new-tooltip/__tests__/util_test.js +47 -0
  114. package/src/components/new-tooltip/flexible-position.jsx +182 -0
  115. package/src/components/new-tooltip/new-tooltip.jsx +399 -0
  116. package/src/components/new-tooltip/style-constants.js +13 -0
  117. package/src/components/new-tooltip/tooltip-arrow.jsx +184 -0
  118. package/src/components/new-tooltip/tooltip-bubble.jsx +223 -0
  119. package/src/components/new-tooltip/tooltip-portal.jsx +101 -0
  120. package/src/components/new-tooltip/tooltip-positioner.jsx +529 -0
  121. package/src/components/new-tooltip/types.js +183 -0
  122. package/src/components/new-tooltip/util.js +27 -0
  123. package/src/components/number-input.jsx +253 -0
  124. package/src/components/possible-answers.jsx +36 -0
  125. package/src/components/prop-check-box.jsx +80 -0
  126. package/src/components/range-input.jsx +61 -0
  127. package/src/components/simple-keypad-input.jsx +91 -0
  128. package/src/components/sortable.jsx +873 -0
  129. package/src/components/stub-tag-editor.jsx +54 -0
  130. package/src/components/svg-image.jsx +762 -0
  131. package/src/components/tex-buttons.jsx +322 -0
  132. package/src/components/tex.jsx +70 -0
  133. package/src/components/text-input.jsx +82 -0
  134. package/src/components/text-list-editor.jsx +157 -0
  135. package/src/components/tooltip.jsx +395 -0
  136. package/src/components/visibility-observer/__stories__/visibility-observer.stories.jsx +140 -0
  137. package/src/components/visibility-observer/get-scroll-parent.js +45 -0
  138. package/src/components/visibility-observer/visibility-observer.js +109 -0
  139. package/src/components/visibility-observer/visibility-observer.jsx +123 -0
  140. package/src/components/zoomable-tex.jsx +70 -0
  141. package/src/components/zoomable.jsx +304 -0
  142. package/src/components.js +17 -0
  143. package/src/dependencies.js +22 -0
  144. package/src/error-boundary.jsx +48 -0
  145. package/src/extra-widgets.js +75 -0
  146. package/src/gorgon/README +40 -0
  147. package/src/gorgon/__tests__/matcher_test.js +497 -0
  148. package/src/gorgon/__tests__/rule_test.js +101 -0
  149. package/src/gorgon/__tests__/rules_test.js +487 -0
  150. package/src/gorgon/__tests__/selector-parser_test.js +52 -0
  151. package/src/gorgon/__tests__/tree-transformer_test.js +446 -0
  152. package/src/gorgon/gorgon.js +290 -0
  153. package/src/gorgon/proptypes.js +29 -0
  154. package/src/gorgon/rule.js +413 -0
  155. package/src/gorgon/rules/absolute-url.js +24 -0
  156. package/src/gorgon/rules/all-rules.js +72 -0
  157. package/src/gorgon/rules/blockquoted-math.js +10 -0
  158. package/src/gorgon/rules/blockquoted-widget.js +10 -0
  159. package/src/gorgon/rules/double-spacing-after-terminal.js +12 -0
  160. package/src/gorgon/rules/extra-content-spacing.js +12 -0
  161. package/src/gorgon/rules/heading-level-1.js +14 -0
  162. package/src/gorgon/rules/heading-level-skip.js +20 -0
  163. package/src/gorgon/rules/heading-sentence-case.js +11 -0
  164. package/src/gorgon/rules/heading-title-case.js +63 -0
  165. package/src/gorgon/rules/image-alt-text.js +21 -0
  166. package/src/gorgon/rules/image-in-table.js +10 -0
  167. package/src/gorgon/rules/image-spaces-around-urls.js +35 -0
  168. package/src/gorgon/rules/image-widget.js +50 -0
  169. package/src/gorgon/rules/link-click-here.js +11 -0
  170. package/src/gorgon/rules/lint-utils.js +48 -0
  171. package/src/gorgon/rules/long-paragraph.js +14 -0
  172. package/src/gorgon/rules/math-adjacent.js +10 -0
  173. package/src/gorgon/rules/math-align-extra-break.js +11 -0
  174. package/src/gorgon/rules/math-align-linebreaks.js +43 -0
  175. package/src/gorgon/rules/math-empty.js +10 -0
  176. package/src/gorgon/rules/math-font-size.js +12 -0
  177. package/src/gorgon/rules/math-frac.js +10 -0
  178. package/src/gorgon/rules/math-nested.js +11 -0
  179. package/src/gorgon/rules/math-starts-with-space.js +12 -0
  180. package/src/gorgon/rules/math-text-empty.js +10 -0
  181. package/src/gorgon/rules/math-without-dollars.js +14 -0
  182. package/src/gorgon/rules/nested-lists.js +11 -0
  183. package/src/gorgon/rules/profanity.js +10 -0
  184. package/src/gorgon/rules/table-missing-cells.js +20 -0
  185. package/src/gorgon/rules/unbalanced-code-delimiters.js +14 -0
  186. package/src/gorgon/rules/unescaped-dollar.js +10 -0
  187. package/src/gorgon/rules/widget-in-table.js +10 -0
  188. package/src/gorgon/selector.js +506 -0
  189. package/src/gorgon/tree-transformer.js +588 -0
  190. package/src/hint-renderer.jsx +159 -0
  191. package/src/hints-renderer.jsx +359 -0
  192. package/src/icon-paths.js +120 -0
  193. package/src/index.js +149 -2
  194. package/src/init.js +49 -0
  195. package/src/interaction-tracker.js +60 -0
  196. package/src/interactive2/__tests__/movable-mock.js +48 -0
  197. package/src/interactive2/__tests__/movable-point_test.js +146 -0
  198. package/src/interactive2/interactive-util.js +152 -0
  199. package/src/interactive2/movable-helper-methods.js +160 -0
  200. package/src/interactive2/movable-line-options.js +392 -0
  201. package/src/interactive2/movable-line.js +322 -0
  202. package/src/interactive2/movable-point-options.js +190 -0
  203. package/src/interactive2/movable-point.jsx +603 -0
  204. package/src/interactive2/movable-polygon-options.js +351 -0
  205. package/src/interactive2/movable-polygon.js +368 -0
  206. package/src/interactive2/movable.js +273 -0
  207. package/src/interactive2/objective_.js +79 -0
  208. package/src/interactive2/types.js +3 -0
  209. package/src/interactive2/wrapped-defaults.js +86 -0
  210. package/src/interactive2/wrapped-ellipse.js +87 -0
  211. package/src/interactive2/wrapped-line.js +109 -0
  212. package/src/interactive2/wrapped-path.js +47 -0
  213. package/src/interactive2.js +91 -0
  214. package/src/item-renderer.jsx +518 -0
  215. package/src/item-version.js +12 -0
  216. package/src/jipt-paragraphs.js +44 -0
  217. package/src/jquery.mobile.vmouse.js +545 -0
  218. package/src/keypad-context.js +33 -0
  219. package/src/loading-context.js +20 -0
  220. package/src/logging/log.js +107 -0
  221. package/src/logging/log_test.js +41 -0
  222. package/src/mixins/changeable.jsx +115 -0
  223. package/src/mixins/editor-jsonify.jsx +13 -0
  224. package/src/mixins/provide-keypad.jsx +129 -0
  225. package/src/mixins/widget-jsonify-deprecated.jsx +23 -0
  226. package/src/mixins/widget-prop-blacklist.js +36 -0
  227. package/src/multi-items/__stories__/multi-renderer.stories.js +88 -0
  228. package/src/multi-items/__testdata__/multi-renderer_testdata.js +174 -0
  229. package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +994 -0
  230. package/src/multi-items/__tests__/items_test.js +245 -0
  231. package/src/multi-items/__tests__/multi-renderer_test.js +903 -0
  232. package/src/multi-items/__tests__/prop-type-builders_test.js +134 -0
  233. package/src/multi-items/__tests__/shapes_test.js +44 -0
  234. package/src/multi-items/__tests__/trees_test.js +316 -0
  235. package/src/multi-items/item-types.js +55 -0
  236. package/src/multi-items/items.js +176 -0
  237. package/src/multi-items/multi-renderer.jsx +543 -0
  238. package/src/multi-items/prop-type-builders.js +105 -0
  239. package/src/multi-items/shape-types.js +47 -0
  240. package/src/multi-items/shapes.js +53 -0
  241. package/src/multi-items/tree-types.js +42 -0
  242. package/src/multi-items/trees.js +345 -0
  243. package/src/multi-items.js +76 -0
  244. package/src/not-gorgon.js +129 -0
  245. package/src/perseus-api.jsx +270 -0
  246. package/src/perseus-env.js +26 -0
  247. package/src/perseus-error.js +19 -0
  248. package/src/perseus-markdown.jsx +433 -0
  249. package/src/perseus-types.js +1597 -0
  250. package/src/pure-markdown.js +347 -0
  251. package/src/question-paragraph.jsx +30 -0
  252. package/src/renderability.jsx +108 -0
  253. package/src/renderer.jsx +1932 -0
  254. package/src/server-item-renderer.jsx +467 -0
  255. package/src/sigfigs.jsx +492 -0
  256. package/src/styles/articles.less +748 -0
  257. package/src/styles/constants.js +119 -0
  258. package/src/styles/global-constants.js +350 -0
  259. package/src/styles/global-styles.js +307 -0
  260. package/src/styles/khan-exercise.css +1304 -0
  261. package/src/styles/media-queries.js +48 -0
  262. package/src/styles/perseus-renderer.less +1007 -0
  263. package/src/styles/shared.js +132 -0
  264. package/src/styles/util.less +47 -0
  265. package/src/styles/variables.boostrap.less +301 -0
  266. package/src/styles/variables.less +68 -0
  267. package/src/styles/variables.shared.less +356 -0
  268. package/src/styles/widgets/categorizer.less +39 -0
  269. package/src/styles/widgets/definition.less +86 -0
  270. package/src/styles/widgets/dropdown.less +3 -0
  271. package/src/styles/widgets/expression.less +147 -0
  272. package/src/styles/widgets/graded-group.less +36 -0
  273. package/src/styles/widgets/grapher.less +11 -0
  274. package/src/styles/widgets/group.less +19 -0
  275. package/src/styles/widgets/image.less +29 -0
  276. package/src/styles/widgets/interactive-graph.less +61 -0
  277. package/src/styles/widgets/label-image.less +41 -0
  278. package/src/styles/widgets/matcher.less +8 -0
  279. package/src/styles/widgets/matrix.less +120 -0
  280. package/src/styles/widgets/measurer.less +20 -0
  281. package/src/styles/widgets/orderer.less +97 -0
  282. package/src/styles/widgets/passage.less +151 -0
  283. package/src/styles/widgets/plotter.less +33 -0
  284. package/src/styles/widgets/radio.less +52 -0
  285. package/src/styles/widgets/reaction-diagram.less +35 -0
  286. package/src/styles/widgets/sequence.less +5 -0
  287. package/src/styles/widgets/sortable.less +127 -0
  288. package/src/styles/widgets/table.less +56 -0
  289. package/src/styles/widgets/transformer.less +28 -0
  290. package/src/styles/widgets/unit.less +14 -0
  291. package/src/tex-wrangler.js +176 -0
  292. package/src/traversal.jsx +141 -0
  293. package/src/types.js +538 -0
  294. package/src/util/a11y.js +24 -0
  295. package/src/util/answer-types.js +862 -0
  296. package/src/util/answer-types_test.js +115 -0
  297. package/src/util/colors.js +94 -0
  298. package/src/util/fix-passage-refs.jsx +122 -0
  299. package/src/util/graph-utils.js +16 -0
  300. package/src/util/graphie.js +1586 -0
  301. package/src/util/interactive.js +4078 -0
  302. package/src/util/katex-preprocess.js +16 -0
  303. package/src/util/math.js +296 -0
  304. package/src/util/mobile-native-utils.js +14 -0
  305. package/src/util/mobile-native-utils_test.js +24 -0
  306. package/src/util/register-all-widgets-for-testing.js +12 -0
  307. package/src/util/scroll-utils.js +162 -0
  308. package/src/util/sizing-utils.js +46 -0
  309. package/src/util/tex.js +203 -0
  310. package/src/util/url-utils.js +36 -0
  311. package/src/util.js +990 -0
  312. package/src/version.js +11 -0
  313. package/src/widget-container.jsx +174 -0
  314. package/src/widgets/__stories__/categorizer.stories.jsx +15 -0
  315. package/src/widgets/__stories__/cs-program.stories.jsx +15 -0
  316. package/src/widgets/__stories__/definition.stories.jsx +38 -0
  317. package/src/widgets/__stories__/dropdown.stories.jsx +15 -0
  318. package/src/widgets/__stories__/explanation.stories.jsx +19 -0
  319. package/src/widgets/__stories__/expression.stories.jsx +26 -0
  320. package/src/widgets/__stories__/graded-group-set.stories.jsx +32 -0
  321. package/src/widgets/__stories__/graded-group.stories.jsx +32 -0
  322. package/src/widgets/__stories__/grapher.stories.jsx +47 -0
  323. package/src/widgets/__stories__/group.stories.jsx +15 -0
  324. package/src/widgets/__stories__/iframe.stories.jsx +15 -0
  325. package/src/widgets/__stories__/image.stories.jsx +62 -0
  326. package/src/widgets/__stories__/input-number.stories.jsx +90 -0
  327. package/src/widgets/__stories__/interaction.stories.jsx +15 -0
  328. package/src/widgets/__stories__/interactive-graph.stories.jsx +61 -0
  329. package/src/widgets/__stories__/matcher.stories.jsx +15 -0
  330. package/src/widgets/__stories__/matrix.stories.jsx +15 -0
  331. package/src/widgets/__stories__/number-line.stories.jsx +15 -0
  332. package/src/widgets/__stories__/numeric-input.stories.jsx +19 -0
  333. package/src/widgets/__stories__/orderer.stories.jsx +15 -0
  334. package/src/widgets/__stories__/passage-ref.stories.jsx +19 -0
  335. package/src/widgets/__stories__/passage.stories.jsx +19 -0
  336. package/src/widgets/__stories__/radio.stories.jsx +82 -0
  337. package/src/widgets/__stories__/sorter.stories.jsx +15 -0
  338. package/src/widgets/__stories__/transformer.stories.jsx +15 -0
  339. package/src/widgets/__stories__/unit.stories.jsx +45 -0
  340. package/src/widgets/__stories__/video-transcript-link.stories.jsx +24 -0
  341. package/src/widgets/__stories__/video.stories.jsx +15 -0
  342. package/src/widgets/__testdata__/categorizer_testdata.js +50 -0
  343. package/src/widgets/__testdata__/cs-program_testdata.js +28 -0
  344. package/src/widgets/__testdata__/dropdown_testdata.js +34 -0
  345. package/src/widgets/__testdata__/explanation_testdata.js +56 -0
  346. package/src/widgets/__testdata__/expression_testdata.js +95 -0
  347. package/src/widgets/__testdata__/graded-group-set_testdata.js +358 -0
  348. package/src/widgets/__testdata__/graded-group_testdata.js +79 -0
  349. package/src/widgets/__testdata__/grapher_testdata.js +376 -0
  350. package/src/widgets/__testdata__/group_testdata.js +156 -0
  351. package/src/widgets/__testdata__/iframe_testdata.js +36 -0
  352. package/src/widgets/__testdata__/image_testdata.js +38 -0
  353. package/src/widgets/__testdata__/input-number_testdata.js +73 -0
  354. package/src/widgets/__testdata__/interaction_testdata.js +264 -0
  355. package/src/widgets/__testdata__/interactive-graph_testdata.js +646 -0
  356. package/src/widgets/__testdata__/matcher_testdata.js +34 -0
  357. package/src/widgets/__testdata__/matrix_testdata.js +29 -0
  358. package/src/widgets/__testdata__/number-line_testdata.js +31 -0
  359. package/src/widgets/__testdata__/numeric-input_testdata.js +266 -0
  360. package/src/widgets/__testdata__/orderer_testdata.js +58 -0
  361. package/src/widgets/__testdata__/passage-ref_testdata.js +41 -0
  362. package/src/widgets/__testdata__/passage_testdata.js +71 -0
  363. package/src/widgets/__testdata__/radio_testdata.js +210 -0
  364. package/src/widgets/__testdata__/sorter_testdata.js +20 -0
  365. package/src/widgets/__testdata__/transformer_testdata.js +109 -0
  366. package/src/widgets/__testdata__/video_testdata.js +22 -0
  367. package/src/widgets/__tests__/__snapshots__/categorizer_test.jsx.snap +928 -0
  368. package/src/widgets/__tests__/__snapshots__/cs-program_test.jsx.snap +75 -0
  369. package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +99 -0
  370. package/src/widgets/__tests__/__snapshots__/dropdown_test.jsx.snap +143 -0
  371. package/src/widgets/__tests__/__snapshots__/explanation_test.js.snap +473 -0
  372. package/src/widgets/__tests__/__snapshots__/expression_test.jsx.snap +38 -0
  373. package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt_test.jsx.snap +754 -0
  374. package/src/widgets/__tests__/__snapshots__/graded-group-set_test.jsx.snap +310 -0
  375. package/src/widgets/__tests__/__snapshots__/graded-group_test.jsx.snap +863 -0
  376. package/src/widgets/__tests__/__snapshots__/grapher_test.jsx.snap +2880 -0
  377. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +1154 -0
  378. package/src/widgets/__tests__/__snapshots__/iframe_test.jsx.snap +69 -0
  379. package/src/widgets/__tests__/__snapshots__/image_test.jsx.snap +388 -0
  380. package/src/widgets/__tests__/__snapshots__/input-number_test.jsx.snap +978 -0
  381. package/src/widgets/__tests__/__snapshots__/interaction_test.jsx.snap +311 -0
  382. package/src/widgets/__tests__/__snapshots__/interactive-graph_test.jsx.snap +2915 -0
  383. package/src/widgets/__tests__/__snapshots__/matcher_test.jsx.snap +1039 -0
  384. package/src/widgets/__tests__/__snapshots__/matrix_test.jsx.snap +450 -0
  385. package/src/widgets/__tests__/__snapshots__/number-line_test.jsx.snap +888 -0
  386. package/src/widgets/__tests__/__snapshots__/numeric-input_test.jsx.snap +930 -0
  387. package/src/widgets/__tests__/__snapshots__/orderer_test.jsx.snap +275 -0
  388. package/src/widgets/__tests__/__snapshots__/passage-ref_test.jsx.snap +446 -0
  389. package/src/widgets/__tests__/__snapshots__/passage_test.jsx.snap +1988 -0
  390. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +7107 -0
  391. package/src/widgets/__tests__/__snapshots__/video_test.jsx.snap +163 -0
  392. package/src/widgets/__tests__/categorizer_test.jsx +205 -0
  393. package/src/widgets/__tests__/cs-program_test.jsx +45 -0
  394. package/src/widgets/__tests__/definition_test.js +152 -0
  395. package/src/widgets/__tests__/dropdown_test.jsx +106 -0
  396. package/src/widgets/__tests__/explanation_test.js +199 -0
  397. package/src/widgets/__tests__/expression_test.jsx +439 -0
  398. package/src/widgets/__tests__/graded-group-set-jipt_test.jsx +33 -0
  399. package/src/widgets/__tests__/graded-group-set_test.jsx +270 -0
  400. package/src/widgets/__tests__/graded-group_test.jsx +233 -0
  401. package/src/widgets/__tests__/grapher.cypress.jsx +486 -0
  402. package/src/widgets/__tests__/grapher_test.jsx +35 -0
  403. package/src/widgets/__tests__/group_test.jsx +512 -0
  404. package/src/widgets/__tests__/iframe_test.jsx +47 -0
  405. package/src/widgets/__tests__/image_test.jsx +65 -0
  406. package/src/widgets/__tests__/input-number_test.jsx +360 -0
  407. package/src/widgets/__tests__/interaction_test.jsx +33 -0
  408. package/src/widgets/__tests__/interactive-graph.cypress.jsx +697 -0
  409. package/src/widgets/__tests__/interactive-graph_test.jsx +105 -0
  410. package/src/widgets/__tests__/matcher_test.jsx +157 -0
  411. package/src/widgets/__tests__/matrix_test.jsx +84 -0
  412. package/src/widgets/__tests__/number-line_test.jsx +81 -0
  413. package/src/widgets/__tests__/numeric-input_test.jsx +574 -0
  414. package/src/widgets/__tests__/orderer_test.jsx +75 -0
  415. package/src/widgets/__tests__/passage-ref_test.jsx +166 -0
  416. package/src/widgets/__tests__/passage_test.jsx +133 -0
  417. package/src/widgets/__tests__/radio/base-radio_test.jsx +82 -0
  418. package/src/widgets/__tests__/radio_test.jsx +651 -0
  419. package/src/widgets/__tests__/renderQuestion.jsx +66 -0
  420. package/src/widgets/__tests__/unit_test.jsx +144 -0
  421. package/src/widgets/__tests__/video_test.jsx +55 -0
  422. package/src/widgets/categorizer.jsx +338 -0
  423. package/src/widgets/cs-program.jsx +227 -0
  424. package/src/widgets/definition.jsx +141 -0
  425. package/src/widgets/dropdown.jsx +143 -0
  426. package/src/widgets/explanation.jsx +315 -0
  427. package/src/widgets/expression.jsx +652 -0
  428. package/src/widgets/graded-group-answer-bar.jsx +152 -0
  429. package/src/widgets/graded-group-set.jsx +285 -0
  430. package/src/widgets/graded-group.jsx +448 -0
  431. package/src/widgets/grapher/util.jsx +806 -0
  432. package/src/widgets/grapher.jsx +663 -0
  433. package/src/widgets/group.jsx +213 -0
  434. package/src/widgets/iframe.jsx +198 -0
  435. package/src/widgets/image.jsx +310 -0
  436. package/src/widgets/input-number.jsx +411 -0
  437. package/src/widgets/interaction/arrow-picker.jsx +39 -0
  438. package/src/widgets/interaction/color-picker.jsx +69 -0
  439. package/src/widgets/interaction/dash-picker.jsx +46 -0
  440. package/src/widgets/interaction/element-container.jsx +97 -0
  441. package/src/widgets/interaction.jsx +742 -0
  442. package/src/widgets/interactive-graph.jsx +2820 -0
  443. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +93 -0
  444. package/src/widgets/label-image/__stories__/marker.stories.jsx +172 -0
  445. package/src/widgets/label-image/answer-choices.jsx +311 -0
  446. package/src/widgets/label-image/marker.jsx +365 -0
  447. package/src/widgets/label-image/types.js +33 -0
  448. package/src/widgets/label-image.jsx +841 -0
  449. package/src/widgets/label-image_test.js +840 -0
  450. package/src/widgets/lights-puzzle.jsx +356 -0
  451. package/src/widgets/matcher.jsx +271 -0
  452. package/src/widgets/matrix.jsx +616 -0
  453. package/src/widgets/measurer.jsx +215 -0
  454. package/src/widgets/molecule/__tests__/layout_test.jsx +159 -0
  455. package/src/widgets/molecule/__tests__/parser_test.jsx +142 -0
  456. package/src/widgets/molecule/molecule-drawing.jsx +175 -0
  457. package/src/widgets/molecule/molecule-layout.jsx +367 -0
  458. package/src/widgets/molecule/smiles-parser.jsx +300 -0
  459. package/src/widgets/molecule.jsx +172 -0
  460. package/src/widgets/number-line.jsx +858 -0
  461. package/src/widgets/numeric-input.jsx +622 -0
  462. package/src/widgets/orderer.jsx +753 -0
  463. package/src/widgets/passage/__tests__/passage-markdown_test.jsx +382 -0
  464. package/src/widgets/passage/get-line-height-for-node.js +10 -0
  465. package/src/widgets/passage/passage-markdown.jsx +416 -0
  466. package/src/widgets/passage-ref-target.jsx +70 -0
  467. package/src/widgets/passage-ref.jsx +198 -0
  468. package/src/widgets/passage.jsx +601 -0
  469. package/src/widgets/plotter.jsx +1198 -0
  470. package/src/widgets/radio/__stories__/base-radio.stories.jsx +40 -0
  471. package/src/widgets/radio/__stories__/choice-icon.stories.jsx +157 -0
  472. package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +26 -0
  473. package/src/widgets/radio/__stories__/cross-out-button.stories.jsx +64 -0
  474. package/src/widgets/radio/__stories__/cross-out-menu.stories.jsx +59 -0
  475. package/src/widgets/radio/__stories__/cross-out-tooltip.stories.jsx +59 -0
  476. package/src/widgets/radio/__stories__/focus-ring.stories.jsx +38 -0
  477. package/src/widgets/radio/__stories__/option-status.stories.jsx +118 -0
  478. package/src/widgets/radio/__stories__/toggleable-radio-button.stories.jsx +28 -0
  479. package/src/widgets/radio/base-radio.jsx +642 -0
  480. package/src/widgets/radio/choice-icon.jsx +468 -0
  481. package/src/widgets/radio/choice-none-above.jsx +54 -0
  482. package/src/widgets/radio/choice.jsx +692 -0
  483. package/src/widgets/radio/choice.jsx.fixture.js +64 -0
  484. package/src/widgets/radio/cross-out-button.jsx +136 -0
  485. package/src/widgets/radio/cross-out-menu-wrapper.jsx +492 -0
  486. package/src/widgets/radio/cross-out-tooltip.jsx +129 -0
  487. package/src/widgets/radio/focus-ring.jsx +51 -0
  488. package/src/widgets/radio/option-status.jsx +89 -0
  489. package/src/widgets/radio/toggleable-radio-button.jsx +133 -0
  490. package/src/widgets/radio/util.js +32 -0
  491. package/src/widgets/radio/util_test.js +19 -0
  492. package/src/widgets/radio/widget.jsx +470 -0
  493. package/src/widgets/radio.jsx +156 -0
  494. package/src/widgets/reaction-diagram.jsx +137 -0
  495. package/src/widgets/sequence.jsx +140 -0
  496. package/src/widgets/simulator.jsx +808 -0
  497. package/src/widgets/sorter.jsx +146 -0
  498. package/src/widgets/table.jsx +394 -0
  499. package/src/widgets/transformer.jsx +2876 -0
  500. package/src/widgets/unit.jsx +372 -0
  501. package/src/widgets/video-transcript-link.jsx +84 -0
  502. package/src/widgets/video-transcript-link_test.js +211 -0
  503. package/src/widgets/video.jsx +126 -0
  504. package/src/widgets.js +431 -0
  505. package/src/zoom.js +539 -0
  506. package/README.md +0 -3
  507. package/src/__tests__/index.test.js +0 -14
@@ -0,0 +1,134 @@
1
+ // @flow
2
+ import {treeToItem} from "../items.js";
3
+ import {buildPropTypeForShape} from "../prop-type-builders.js";
4
+ import shapes from "../shapes.js";
5
+
6
+ // TODO(emily): [PERSEUS_MERGE] Calling prop types as a function fails these
7
+ // tests. Rewrite them once we're using the prop-types library.
8
+ describe.skip("buildPropTypeForShape", () => {
9
+ // The value we're wrapping might not be a valid ItemTree - that's the
10
+ // point of testing this PropType - so we disable Flow checking by casting
11
+ // `value` to type `any`. The current implementation of `treeToItem` can
12
+ // handle that possibility.
13
+ const tryPropType = (propType, value: any) =>
14
+ propType({value: treeToItem(value)}, "value", "<function tryPropType>");
15
+
16
+ const assertPropTypePasses = (propType, value) =>
17
+ expect(null).toEqual(tryPropType(propType, value));
18
+
19
+ const assertPropTypeFails = (propType, value) =>
20
+ expect(tryPropType(propType, value) instanceof Error).toBeTruthy();
21
+
22
+ it("validates a content node", () => {
23
+ const propType = buildPropTypeForShape(shapes.content);
24
+
25
+ // Perseus has default values for all item fields, so all except the
26
+ // type are optional.
27
+ assertPropTypePasses(propType, {__type: "content"});
28
+ assertPropTypePasses(propType, {
29
+ content: "",
30
+ widgets: {},
31
+ images: {},
32
+ __type: "content",
33
+ });
34
+
35
+ // We also leave the full object optional by default, like the propType
36
+ // primitives.
37
+ assertPropTypePasses(propType, null);
38
+
39
+ // But specifying a bad type for any field will fail the propType.
40
+ assertPropTypeFails(propType, {content: 1, __type: "content"});
41
+ assertPropTypeFails(propType, {widgets: 1, __type: "content"});
42
+ assertPropTypeFails(propType, {images: 1, __type: "content"});
43
+ });
44
+
45
+ // TODO(mdr): Remove #LegacyContentNode support.
46
+ it("validates a legacy content node", () => {
47
+ const propType = buildPropTypeForShape(shapes.content);
48
+
49
+ // Perseus has default values for all item fields, so all except the
50
+ // type are optional.
51
+ assertPropTypePasses(propType, {__type: "item"});
52
+ assertPropTypePasses(propType, {
53
+ content: "",
54
+ widgets: {},
55
+ images: {},
56
+ __type: "item",
57
+ });
58
+
59
+ // We also leave the full object optional by default, like the propType
60
+ // primitives.
61
+ assertPropTypePasses(propType, null);
62
+
63
+ // But specifying a bad type for any field will fail the propType.
64
+ assertPropTypeFails(propType, {content: 1, __type: "item"});
65
+ assertPropTypeFails(propType, {widgets: 1, __type: "item"});
66
+ assertPropTypeFails(propType, {images: 1, __type: "item"});
67
+ });
68
+
69
+ it("validates a hint node", () => {
70
+ const propType = buildPropTypeForShape(shapes.hint);
71
+
72
+ // Perseus has default values for all hint fields, so all except the
73
+ // type are optional.
74
+ assertPropTypePasses(propType, {__type: "hint"});
75
+ assertPropTypePasses(propType, {
76
+ content: "",
77
+ widgets: {},
78
+ images: {},
79
+ replace: false,
80
+ __type: "hint",
81
+ });
82
+
83
+ // We also leave the full object optional by default, like the propType
84
+ // primitives.
85
+ assertPropTypePasses(propType, null);
86
+
87
+ // But specifying a bad type for any field will fail the propType.
88
+ assertPropTypeFails(propType, {content: 1, __type: "hint"});
89
+ assertPropTypeFails(propType, {widgets: 1, __type: "hint"});
90
+ assertPropTypeFails(propType, {images: 1, __type: "hint"});
91
+ assertPropTypeFails(propType, {replace: 1, __type: "hint"});
92
+ });
93
+
94
+ it("validates an array", () => {
95
+ const propType = buildPropTypeForShape(shapes.arrayOf(shapes.content));
96
+ const emptyItem = {__type: "content"};
97
+
98
+ assertPropTypePasses(propType, []);
99
+ assertPropTypePasses(propType, [emptyItem, emptyItem, emptyItem]);
100
+
101
+ // While the array itself is optional, its elements are required.
102
+ assertPropTypePasses(propType, null);
103
+ assertPropTypeFails(propType, [emptyItem, null, emptyItem]);
104
+ });
105
+
106
+ it("validates tags", () => {
107
+ const propType = buildPropTypeForShape(shapes.tags);
108
+
109
+ assertPropTypePasses(propType, []);
110
+ assertPropTypePasses(propType, ["a", "b", "c"]);
111
+
112
+ assertPropTypePasses(propType, null);
113
+ assertPropTypeFails(propType, ["a", null, "b"]);
114
+ assertPropTypeFails(propType, [1, 2, 3]);
115
+ });
116
+
117
+ it("validates an object", () => {
118
+ const propType = buildPropTypeForShape(
119
+ shapes.shape({
120
+ a: shapes.content,
121
+ b: shapes.content,
122
+ }),
123
+ );
124
+ const emptyItem = {__type: "content"};
125
+
126
+ assertPropTypePasses(propType, {a: emptyItem, b: emptyItem});
127
+
128
+ // While the object itself is optional, its fields are required.
129
+ assertPropTypePasses(propType, null);
130
+ assertPropTypeFails(propType, {});
131
+ assertPropTypeFails(propType, {a: emptyItem});
132
+ assertPropTypeFails(propType, {b: emptyItem});
133
+ });
134
+ });
@@ -0,0 +1,44 @@
1
+ // @flow
2
+ import shapes from "../shapes.js";
3
+
4
+ describe("shapes.content", () => {
5
+ it('has type "content"', () => {
6
+ const shape = shapes.content;
7
+ expect("content").toEqual(shape.type);
8
+ });
9
+ });
10
+
11
+ describe("shapes.hint", () => {
12
+ it('has type "hint"', () => {
13
+ const shape = shapes.hint;
14
+ expect("hint").toEqual(shape.type);
15
+ });
16
+ });
17
+
18
+ describe("shapes.arrayOf", () => {
19
+ it('has type "array"', () => {
20
+ const shape = shapes.arrayOf(shapes.content);
21
+ expect("array").toEqual(shape.type);
22
+ });
23
+
24
+ it("has the given shape as its `elementShape` property", () => {
25
+ const shape = shapes.arrayOf(shapes.content);
26
+ expect(shapes.content).toEqual(shape.elementShape);
27
+ });
28
+ });
29
+
30
+ describe("shapes.shape", () => {
31
+ it('has type "object"', () => {
32
+ const shape = shapes.shape({});
33
+ expect("object").toEqual(shape.type);
34
+ });
35
+
36
+ it("has the given shapes as its `shape` property", () => {
37
+ const shape = shapes.shape({
38
+ foo: shapes.content,
39
+ bar: shapes.hint,
40
+ });
41
+ expect(shapes.content).toEqual(shape.shape.foo);
42
+ expect(shapes.hint).toEqual(shape.shape.bar);
43
+ });
44
+ });
@@ -0,0 +1,316 @@
1
+ // @flow
2
+ import shapes from "../shapes.js";
3
+ import {buildMapper} from "../trees.js";
4
+
5
+ import type {
6
+ ItemTree,
7
+ ContentNode,
8
+ HintNode,
9
+ TagsNode,
10
+ ItemArrayNode,
11
+ } from "../item-types.js";
12
+ import type {TreeMapper} from "../trees.js";
13
+
14
+ describe("buildMapper", () => {
15
+ function content(n): ContentNode {
16
+ return {
17
+ __type: "content",
18
+ content: `content ${n}`,
19
+ };
20
+ }
21
+
22
+ function hint(n): HintNode {
23
+ return {
24
+ __type: "hint",
25
+ content: `hint ${n}`,
26
+ };
27
+ }
28
+
29
+ function tags(...elements): TagsNode {
30
+ // This function mostly exists as a shorthand way to clarify to Flow
31
+ // that this is a TagsNode, not a confused ItemArrayNode.
32
+ return elements;
33
+ }
34
+
35
+ function array(...elements): ItemArrayNode {
36
+ // This function mostly exists as a shorthand way to clarify to Flow
37
+ // that this is an ItemArrayNode, not a confused TagsNode.
38
+ /**
39
+ * TODO(somewhatabstract, JIRA-XXXX):
40
+ * The Tree types are really hard to work with properly.
41
+ */
42
+ // $FlowFixMe[incompatible-return]
43
+ return elements;
44
+ }
45
+
46
+ const shape = shapes.shape({
47
+ a: shapes.content,
48
+ b: shapes.arrayOf(shapes.content),
49
+ c: shapes.shape({
50
+ d: shapes.content,
51
+ e: shapes.hint,
52
+ }),
53
+ f: shapes.hint,
54
+ g: shapes.tags,
55
+ });
56
+
57
+ const tree: ItemTree = {
58
+ a: content(1),
59
+ b: array(content(2), content(3), content(4)),
60
+ c: {
61
+ d: content(5),
62
+ e: hint(6),
63
+ },
64
+ f: hint(7),
65
+ g: tags("foo", "bar"),
66
+ };
67
+
68
+ it("calls the content mapper for each of the content nodes", () => {
69
+ const calledWith = [];
70
+ buildMapper()
71
+ .setContentMapper((c) => calledWith.push(c))
72
+ .mapTree(tree, shape);
73
+ calledWith.sort();
74
+
75
+ expect([
76
+ content(1),
77
+ content(2),
78
+ content(3),
79
+ content(4),
80
+ content(5),
81
+ ]).toEqual(calledWith);
82
+ });
83
+
84
+ it("calls the hint mapper for each of the hint nodes", () => {
85
+ const calledWith = [];
86
+ buildMapper()
87
+ .setHintMapper((h) => calledWith.push(h))
88
+ .mapTree(tree, shape);
89
+ calledWith.sort();
90
+
91
+ expect([hint(6), hint(7)]).toEqual(calledWith);
92
+ });
93
+
94
+ it("calls the tags mapper for each of the tags nodes", () => {
95
+ const calledWith = [];
96
+ buildMapper()
97
+ .setTagsMapper((t) => calledWith.push(t))
98
+ .mapTree(tree, shape);
99
+ calledWith.sort();
100
+
101
+ expect([["foo", "bar"]]).toEqual(calledWith);
102
+ });
103
+
104
+ it("returns a mapped tree with the correct shape", () => {
105
+ const mapper: TreeMapper<
106
+ ContentNode,
107
+ string,
108
+ HintNode,
109
+ string,
110
+ TagsNode,
111
+ string,
112
+ > = buildMapper()
113
+ .setContentMapper((c) => `mapped content: ${c.content || "<none>"}`)
114
+ .setHintMapper((h) => `mapped hint: ${h.content || "<none>"}`)
115
+ .setTagsMapper((t) => `mapped tags: ${t.join(", ")}`);
116
+ const result = mapper.mapTree(tree, shape);
117
+
118
+ expect({
119
+ a: "mapped content: content 1",
120
+ b: [
121
+ "mapped content: content 2",
122
+ "mapped content: content 3",
123
+ "mapped content: content 4",
124
+ ],
125
+ c: {
126
+ d: "mapped content: content 5",
127
+ e: "mapped hint: hint 6",
128
+ },
129
+ f: "mapped hint: hint 7",
130
+ g: "mapped tags: foo, bar",
131
+ }).toEqual(result);
132
+ });
133
+
134
+ it("provides each node shape to the leaf mappers", () => {
135
+ // Return the type of the shape of the node in place of each node.
136
+ const result = buildMapper()
137
+ .setContentMapper((_, s) => s.type)
138
+ .setHintMapper((_, s) => s.type)
139
+ .setTagsMapper((_, s) => s.type)
140
+ .mapTree(tree, shape);
141
+
142
+ expect({
143
+ a: "content",
144
+ b: ["content", "content", "content"],
145
+ c: {
146
+ d: "content",
147
+ e: "hint",
148
+ },
149
+ f: "hint",
150
+ g: "tags",
151
+ }).toEqual(result);
152
+ });
153
+
154
+ it("provides each node path to the leaf mappers", () => {
155
+ // Return the path to the node in place of each node.
156
+ const result = buildMapper()
157
+ .setContentMapper((_, __, p) => p)
158
+ .setHintMapper((_, __, p) => p)
159
+ .setTagsMapper((_, __, p) => p)
160
+ .mapTree(tree, shape);
161
+
162
+ expect({
163
+ a: ["a"],
164
+ b: [
165
+ ["b", 0],
166
+ ["b", 1],
167
+ ["b", 2],
168
+ ],
169
+ c: {
170
+ d: ["c", "d"],
171
+ e: ["c", "e"],
172
+ },
173
+ f: ["f"],
174
+ g: ["g"],
175
+ }).toEqual(result);
176
+ });
177
+
178
+ it("handles recursive objects", () => {
179
+ const shape = shapes.shape({
180
+ a: shapes.shape({
181
+ b: shapes.shape({
182
+ c: shapes.content,
183
+ }),
184
+ }),
185
+ });
186
+
187
+ const tree = {a: {b: {c: content(1)}}};
188
+
189
+ const mapper: TreeMapper<
190
+ ContentNode,
191
+ ?string,
192
+ HintNode,
193
+ HintNode,
194
+ TagsNode,
195
+ TagsNode,
196
+ > = buildMapper().setContentMapper((c) => c.content);
197
+ const result = mapper.mapTree(tree, shape);
198
+
199
+ expect({a: {b: {c: "content 1"}}}).toEqual(result);
200
+ });
201
+
202
+ it("handles recursive arrays", () => {
203
+ const shape = shapes.arrayOf(
204
+ shapes.arrayOf(shapes.arrayOf(shapes.content)),
205
+ );
206
+
207
+ const tree = array(
208
+ array(array(content(0)), array(content(1))),
209
+ array(array(content(2)), array(content(3), content(4))),
210
+ );
211
+
212
+ const mapper: TreeMapper<
213
+ ContentNode,
214
+ ?string,
215
+ HintNode,
216
+ HintNode,
217
+ TagsNode,
218
+ TagsNode,
219
+ > = buildMapper().setContentMapper((c) => c.content);
220
+ const result = mapper.mapTree(tree, shape);
221
+
222
+ expect([
223
+ [["content 0"], ["content 1"]],
224
+ [["content 2"], ["content 3", "content 4"]],
225
+ ]).toEqual(result);
226
+ });
227
+
228
+ it("handles empty arrays", () => {
229
+ const shape = shapes.arrayOf(shapes.arrayOf(shapes.content));
230
+
231
+ const tree = array(array(), array(content(1)), array());
232
+
233
+ const mapper: TreeMapper<
234
+ ContentNode,
235
+ ?string,
236
+ HintNode,
237
+ HintNode,
238
+ TagsNode,
239
+ TagsNode,
240
+ > = buildMapper().setContentMapper((c) => c.content);
241
+
242
+ // Test the outer array being empty.
243
+ expect([]).toEqual(mapper.mapTree(array(), shape));
244
+
245
+ // Test inner arrays being empty.
246
+ const result = mapper.mapTree(tree, shape);
247
+ expect([[], ["content 1"], []]).toEqual(result);
248
+ });
249
+
250
+ it("calls the array mapper for arrays", () => {
251
+ const shape = shapes.arrayOf(shapes.content);
252
+ const tree = array(content(1), content(2), content(3));
253
+
254
+ let wasCalled = false;
255
+ let callArgs = {};
256
+ const mapper: TreeMapper<
257
+ ContentNode,
258
+ ?string,
259
+ HintNode,
260
+ HintNode,
261
+ TagsNode,
262
+ TagsNode,
263
+ > = buildMapper()
264
+ .setContentMapper((c) => c.content)
265
+ .setArrayMapper((mappedArray, originalArray, shape, path) => {
266
+ wasCalled = true;
267
+ callArgs = {mappedArray, originalArray, shape, path};
268
+ return mappedArray;
269
+ });
270
+ mapper.mapTree(tree, shape);
271
+
272
+ expect(wasCalled).toBeTruthy();
273
+
274
+ expect(["content 1", "content 2", "content 3"]).toEqual(
275
+ callArgs.mappedArray,
276
+ );
277
+ expect(tree).toEqual(callArgs.originalArray);
278
+ expect(shape).toEqual(callArgs.shape);
279
+ expect([]).toEqual(callArgs.path);
280
+ });
281
+
282
+ it("uses the array mapper return value to construct the new tree", () => {
283
+ const mapper = buildMapper()
284
+ // $FlowFixMe[not-a-function]
285
+ // $FlowFixMe[prop-missing]
286
+ // $FlowFixMe[incompatible-use]
287
+ .setContentMapper((c) => c.content)
288
+ // $FlowFixMe[not-a-function]
289
+ // $FlowFixMe[prop-missing]
290
+ // $FlowFixMe[incompatible-use]
291
+ .setHintMapper((h) => h.content)
292
+ // $FlowFixMe[not-a-function]
293
+ // $FlowFixMe[prop-missing]
294
+ .setTagsMapper((t) => t.join(", "))
295
+ .setArrayMapper((mappedArray, originalArray, shape, path) => {
296
+ // $FlowFixMe[prop-missing]
297
+ return mappedArray.map((child) => `${String(child)} in array`);
298
+ });
299
+ const result = mapper.mapTree(tree, shape);
300
+
301
+ expect({
302
+ a: "content 1",
303
+ b: [
304
+ "content 2 in array",
305
+ "content 3 in array",
306
+ "content 4 in array",
307
+ ],
308
+ c: {
309
+ d: "content 5",
310
+ e: "hint 6",
311
+ },
312
+ f: "hint 7",
313
+ g: "foo, bar",
314
+ }).toEqual(result);
315
+ });
316
+ });
@@ -0,0 +1,55 @@
1
+ // @flow
2
+ /**
3
+ * Type definitions for multi-item types, including:
4
+ *
5
+ * - Item: A multi-item tree wrapped in a `_multi` key, to help us recognize it
6
+ * as a multi-item in other contexts and avoid misinterpreting its
7
+ * other properties.
8
+ * - ItemTree: A multi-item without the `_multi` key. Conforms to the Tree
9
+ * interface, so it's compatible with our tree traversal functions.
10
+ * - And the various types of nodes that compose a tree.
11
+ */
12
+ import type {WidgetDict, ImageDict} from "../types.js";
13
+ import type {Tree, ArrayNode, ObjectNode} from "./tree-types.js";
14
+
15
+ export type ContentNode = {
16
+ // TODO(mdr): When we first drafted the multi-item feature, we named
17
+ // content nodes "item" nodes, and later decided the term was
18
+ // ambiguous and switched to "content". But we're temporarily keeping
19
+ // support for the "item" string when inferring item shape, so that we
20
+ // don't crash on multi-items we've already created - but all new
21
+ // content nodes will be generated with the "content" string.
22
+ //
23
+ // Code blocks that enable this legacy support are greppable with the
24
+ // keyword #LegacyContentNode.
25
+ __type: "content" | "item",
26
+ // Perseus has default values for these fields, so they're all optional.
27
+ content?: ?string,
28
+ images?: ?ImageDict,
29
+ widgets?: ?WidgetDict,
30
+ ...
31
+ };
32
+ export type HintNode = {
33
+ __type: "hint",
34
+ // Perseus has default values for these fields, so they're all optional.
35
+ content?: ?string,
36
+ images?: ?ImageDict,
37
+ widgets?: ?WidgetDict,
38
+ replace?: ?boolean,
39
+ ...
40
+ };
41
+ export type TagsNode = $ReadOnlyArray<string>;
42
+
43
+ export type ItemArrayNode = ArrayNode<ContentNode, HintNode, TagsNode>;
44
+ export type ItemObjectNode = ObjectNode<ContentNode, HintNode, TagsNode>;
45
+ export type ItemTree = Tree<ContentNode, HintNode, TagsNode>;
46
+
47
+ // TODO(jeremy): I think we could refine this root type for multi items. Right
48
+ // now a _multi's value can be _anything_ including "primitive" node types such
49
+ // as "type: content" or "type: hint". That doesn't seem to match the shapes
50
+ // that are in use to date. We could also move to a strict type at that point
51
+ // because it appears that we _never_ add other root keys to an item that
52
+ // specifies the `_multi` key. I think we'd be safe to restrict this root
53
+ // object type to something like the following:
54
+ // export type Item = {|_multi: ItemArrayNode | ItemObjectNode|};
55
+ export type Item = {_multi: ItemTree, ...};