@khanacademy/perseus 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (503) hide show
  1. package/.babelrc.js +1 -1
  2. package/CHANGELOG.md +7 -0
  3. package/dist/es/index.js +15 -1
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.css +11934 -0
  6. package/dist/index.js +15 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/strings.js +148 -1
  9. package/package.json +40 -4
  10. package/src/README.md +7 -0
  11. package/src/__stories__/item-renderer.stories.jsx +27 -0
  12. package/src/__stories__/server-item-renderer.stories.jsx +27 -0
  13. package/src/__testdata__/item-renderer_testdata.js +161 -0
  14. package/src/__testdata__/renderer_testdata.js +123 -0
  15. package/src/__tests__/__snapshots__/item-renderer_test.jsx.snap +233 -0
  16. package/src/__tests__/__snapshots__/perseus-markdown_test.jsx.snap +516 -0
  17. package/src/__tests__/__snapshots__/pure-markdown_test.js.snap +113 -0
  18. package/src/__tests__/__snapshots__/renderer_test.jsx.snap +576 -0
  19. package/src/__tests__/__snapshots__/server-item-renderer_test.jsx.snap +306 -0
  20. package/src/__tests__/a11y_test.jsx +304 -0
  21. package/src/__tests__/item-renderer_test.jsx +892 -0
  22. package/src/__tests__/mock-asset-loading-widget.jsx +57 -0
  23. package/src/__tests__/mock-widget.jsx +35 -0
  24. package/src/__tests__/perseus-markdown_test.jsx +747 -0
  25. package/src/__tests__/pure-markdown_test.js +552 -0
  26. package/src/__tests__/renderability_test.jsx +481 -0
  27. package/src/__tests__/renderer-api_test.jsx +333 -0
  28. package/src/__tests__/renderer_test.jsx +1792 -0
  29. package/src/__tests__/server-item-renderer_test.jsx +516 -0
  30. package/src/__tests__/test-items/image-item.js +25 -0
  31. package/src/__tests__/test-items/input-number-1-item.js +27 -0
  32. package/src/__tests__/test-items/input-number-2-item.js +39 -0
  33. package/src/__tests__/test-items/table-item.js +30 -0
  34. package/src/__tests__/tex-wrangler_test.js +83 -0
  35. package/src/__tests__/util_test.js +41 -0
  36. package/src/a11y.js +46 -0
  37. package/src/all-widgets.js +9 -0
  38. package/src/article-renderer.jsx +215 -0
  39. package/src/asset-context.js +30 -0
  40. package/src/basic-widgets.js +13 -0
  41. package/src/components/__stories__/button-group.stories.jsx +61 -0
  42. package/src/components/__stories__/fixed-to-responsive.stories.jsx +132 -0
  43. package/src/components/__stories__/graph.stories.jsx +27 -0
  44. package/src/components/__stories__/graphie.stories.jsx +28 -0
  45. package/src/components/__stories__/hud.stories.jsx +36 -0
  46. package/src/components/__stories__/icon.stories.jsx +56 -0
  47. package/src/components/__stories__/image-loader.stories.jsx +60 -0
  48. package/src/components/__stories__/info-tip.stories.jsx +33 -0
  49. package/src/components/__stories__/inline-icon.stories.jsx +39 -0
  50. package/src/components/__stories__/input-with-examples.stories.jsx +51 -0
  51. package/src/components/__stories__/lint.stories.jsx +40 -0
  52. package/src/components/__stories__/math-input.stories.jsx +29 -0
  53. package/src/components/__stories__/math-output.stories.jsx +24 -0
  54. package/src/components/__stories__/multi-button-group.stories.jsx +69 -0
  55. package/src/components/__stories__/number-input.stories.jsx +42 -0
  56. package/src/components/__stories__/possible-answers.stories.jsx +22 -0
  57. package/src/components/__stories__/prop-check-box.stories.jsx +62 -0
  58. package/src/components/__stories__/range-input.stories.jsx +28 -0
  59. package/src/components/__stories__/simple-keypad-input.stories.jsx +28 -0
  60. package/src/components/__stories__/sortable.stories.jsx +73 -0
  61. package/src/components/__stories__/stub-tag-editor.stories.jsx +44 -0
  62. package/src/components/__stories__/svg-image.stories.jsx +60 -0
  63. package/src/components/__stories__/tex.stories.jsx +18 -0
  64. package/src/components/__stories__/text-buttons.stories.jsx +51 -0
  65. package/src/components/__stories__/text-input.stories.jsx +34 -0
  66. package/src/components/__stories__/text-list-editor.stories.jsx +33 -0
  67. package/src/components/__stories__/zoomable-tex.stories.jsx +39 -0
  68. package/src/components/__stories__/zoomable.stories.jsx +40 -0
  69. package/src/components/__testdata__/sorter_testdata.js +20 -0
  70. package/src/components/__tests__/__snapshots__/sortable_test.jsx.snap +79 -0
  71. package/src/components/__tests__/__snapshots__/sorter_test.jsx.snap +297 -0
  72. package/src/components/__tests__/number-input_test.jsx +112 -0
  73. package/src/components/__tests__/sortable_test.jsx +234 -0
  74. package/src/components/__tests__/sorter_test.jsx +111 -0
  75. package/src/components/__tests__/zoomable_test.jsx +388 -0
  76. package/src/components/button-group.jsx +131 -0
  77. package/src/components/constants.js +15 -0
  78. package/src/components/fixed-to-responsive.jsx +163 -0
  79. package/src/components/graph.jsx +414 -0
  80. package/src/components/graphie-classes.jsx +148 -0
  81. package/src/components/graphie-movables.jsx +342 -0
  82. package/src/components/graphie.jsx +353 -0
  83. package/src/components/highlighting/highlightable-content.jsx +245 -0
  84. package/src/components/highlighting/highlights.js +208 -0
  85. package/src/components/highlighting/ranges.js +299 -0
  86. package/src/components/highlighting/types.js +73 -0
  87. package/src/components/highlighting/ui/highlight-renderer.jsx +179 -0
  88. package/src/components/highlighting/ui/highlight-set-renderer.jsx +190 -0
  89. package/src/components/highlighting/ui/highlight-tooltip.jsx +153 -0
  90. package/src/components/highlighting/ui/highlighting-ui.jsx +163 -0
  91. package/src/components/highlighting/ui/selection-tracker.jsx +195 -0
  92. package/src/components/highlighting/ui/types.js +21 -0
  93. package/src/components/highlighting/ui/util.js +166 -0
  94. package/src/components/highlighting/word-indexer.jsx +155 -0
  95. package/src/components/hud.jsx +179 -0
  96. package/src/components/icon.jsx +149 -0
  97. package/src/components/image-loader.jsx +175 -0
  98. package/src/components/info-tip/info-tip.jsx +149 -0
  99. package/src/components/info-tip.jsx +38 -0
  100. package/src/components/inline-icon.jsx +81 -0
  101. package/src/components/input-with-examples.jsx +231 -0
  102. package/src/components/lint.jsx +456 -0
  103. package/src/components/math-input.jsx +282 -0
  104. package/src/components/math-output.jsx +128 -0
  105. package/src/components/multi-button-group.jsx +137 -0
  106. package/src/components/new-tooltip/README.md +110 -0
  107. package/src/components/new-tooltip/__stories__/flexible-position.stories.jsx +437 -0
  108. package/src/components/new-tooltip/__stories__/new-tooltip.stories.jsx +665 -0
  109. package/src/components/new-tooltip/__stories__/tooltip-arrow.stories.jsx +70 -0
  110. package/src/components/new-tooltip/__stories__/tooltip-bubble.stories.jsx +98 -0
  111. package/src/components/new-tooltip/__tests__/style-constants_test.js +8 -0
  112. package/src/components/new-tooltip/__tests__/util_test.js +47 -0
  113. package/src/components/new-tooltip/flexible-position.jsx +182 -0
  114. package/src/components/new-tooltip/new-tooltip.jsx +399 -0
  115. package/src/components/new-tooltip/style-constants.js +13 -0
  116. package/src/components/new-tooltip/tooltip-arrow.jsx +184 -0
  117. package/src/components/new-tooltip/tooltip-bubble.jsx +223 -0
  118. package/src/components/new-tooltip/tooltip-portal.jsx +101 -0
  119. package/src/components/new-tooltip/tooltip-positioner.jsx +529 -0
  120. package/src/components/new-tooltip/types.js +183 -0
  121. package/src/components/new-tooltip/util.js +27 -0
  122. package/src/components/number-input.jsx +253 -0
  123. package/src/components/possible-answers.jsx +36 -0
  124. package/src/components/prop-check-box.jsx +78 -0
  125. package/src/components/range-input.jsx +61 -0
  126. package/src/components/simple-keypad-input.jsx +91 -0
  127. package/src/components/sortable.jsx +872 -0
  128. package/src/components/stub-tag-editor.jsx +53 -0
  129. package/src/components/svg-image.jsx +762 -0
  130. package/src/components/tex-buttons.jsx +322 -0
  131. package/src/components/tex.jsx +70 -0
  132. package/src/components/text-input.jsx +82 -0
  133. package/src/components/text-list-editor.jsx +157 -0
  134. package/src/components/tooltip.jsx +395 -0
  135. package/src/components/visibility-observer/__stories__/visibility-observer.stories.jsx +140 -0
  136. package/src/components/visibility-observer/get-scroll-parent.js +45 -0
  137. package/src/components/visibility-observer/visibility-observer.js +109 -0
  138. package/src/components/visibility-observer/visibility-observer.jsx +123 -0
  139. package/src/components/zoomable-tex.jsx +70 -0
  140. package/src/components/zoomable.jsx +304 -0
  141. package/src/components.js +17 -0
  142. package/src/dependencies.js +22 -0
  143. package/src/error-boundary.jsx +48 -0
  144. package/src/extra-widgets.js +75 -0
  145. package/src/gorgon/README +40 -0
  146. package/src/gorgon/__tests__/matcher_test.js +497 -0
  147. package/src/gorgon/__tests__/rule_test.js +101 -0
  148. package/src/gorgon/__tests__/rules_test.js +487 -0
  149. package/src/gorgon/__tests__/selector-parser_test.js +52 -0
  150. package/src/gorgon/__tests__/tree-transformer_test.js +446 -0
  151. package/src/gorgon/gorgon.js +290 -0
  152. package/src/gorgon/proptypes.js +29 -0
  153. package/src/gorgon/rule.js +413 -0
  154. package/src/gorgon/rules/absolute-url.js +24 -0
  155. package/src/gorgon/rules/all-rules.js +72 -0
  156. package/src/gorgon/rules/blockquoted-math.js +10 -0
  157. package/src/gorgon/rules/blockquoted-widget.js +10 -0
  158. package/src/gorgon/rules/double-spacing-after-terminal.js +12 -0
  159. package/src/gorgon/rules/extra-content-spacing.js +12 -0
  160. package/src/gorgon/rules/heading-level-1.js +14 -0
  161. package/src/gorgon/rules/heading-level-skip.js +20 -0
  162. package/src/gorgon/rules/heading-sentence-case.js +11 -0
  163. package/src/gorgon/rules/heading-title-case.js +63 -0
  164. package/src/gorgon/rules/image-alt-text.js +21 -0
  165. package/src/gorgon/rules/image-in-table.js +10 -0
  166. package/src/gorgon/rules/image-spaces-around-urls.js +35 -0
  167. package/src/gorgon/rules/image-widget.js +50 -0
  168. package/src/gorgon/rules/link-click-here.js +11 -0
  169. package/src/gorgon/rules/lint-utils.js +48 -0
  170. package/src/gorgon/rules/long-paragraph.js +14 -0
  171. package/src/gorgon/rules/math-adjacent.js +10 -0
  172. package/src/gorgon/rules/math-align-extra-break.js +11 -0
  173. package/src/gorgon/rules/math-align-linebreaks.js +43 -0
  174. package/src/gorgon/rules/math-empty.js +10 -0
  175. package/src/gorgon/rules/math-font-size.js +12 -0
  176. package/src/gorgon/rules/math-frac.js +10 -0
  177. package/src/gorgon/rules/math-nested.js +11 -0
  178. package/src/gorgon/rules/math-starts-with-space.js +12 -0
  179. package/src/gorgon/rules/math-text-empty.js +10 -0
  180. package/src/gorgon/rules/math-without-dollars.js +14 -0
  181. package/src/gorgon/rules/nested-lists.js +11 -0
  182. package/src/gorgon/rules/profanity.js +10 -0
  183. package/src/gorgon/rules/table-missing-cells.js +20 -0
  184. package/src/gorgon/rules/unbalanced-code-delimiters.js +14 -0
  185. package/src/gorgon/rules/unescaped-dollar.js +10 -0
  186. package/src/gorgon/rules/widget-in-table.js +10 -0
  187. package/src/gorgon/selector.js +506 -0
  188. package/src/gorgon/tree-transformer.js +588 -0
  189. package/src/hint-renderer.jsx +159 -0
  190. package/src/hints-renderer.jsx +359 -0
  191. package/src/icon-paths.js +120 -0
  192. package/src/index.js +149 -2
  193. package/src/init.js +49 -0
  194. package/src/interaction-tracker.js +60 -0
  195. package/src/interactive2/__tests__/movable-mock.js +48 -0
  196. package/src/interactive2/__tests__/movable-point_test.js +146 -0
  197. package/src/interactive2/interactive-util.js +152 -0
  198. package/src/interactive2/movable-helper-methods.js +160 -0
  199. package/src/interactive2/movable-line-options.js +392 -0
  200. package/src/interactive2/movable-line.js +322 -0
  201. package/src/interactive2/movable-point-options.js +190 -0
  202. package/src/interactive2/movable-point.jsx +604 -0
  203. package/src/interactive2/movable-polygon-options.js +351 -0
  204. package/src/interactive2/movable-polygon.js +367 -0
  205. package/src/interactive2/movable.js +273 -0
  206. package/src/interactive2/objective_.js +79 -0
  207. package/src/interactive2/types.js +3 -0
  208. package/src/interactive2/wrapped-defaults.js +86 -0
  209. package/src/interactive2/wrapped-ellipse.js +87 -0
  210. package/src/interactive2/wrapped-line.js +109 -0
  211. package/src/interactive2/wrapped-path.js +47 -0
  212. package/src/interactive2.js +91 -0
  213. package/src/item-renderer.jsx +518 -0
  214. package/src/item-version.js +12 -0
  215. package/src/jipt-paragraphs.js +44 -0
  216. package/src/jquery.mobile.vmouse.js +545 -0
  217. package/src/keypad-context.js +33 -0
  218. package/src/loading-context.js +20 -0
  219. package/src/logging/log.js +107 -0
  220. package/src/logging/log_test.js +41 -0
  221. package/src/mixins/changeable.jsx +115 -0
  222. package/src/mixins/editor-jsonify.jsx +13 -0
  223. package/src/mixins/provide-keypad.jsx +131 -0
  224. package/src/mixins/widget-jsonify-deprecated.jsx +23 -0
  225. package/src/mixins/widget-prop-blacklist.js +36 -0
  226. package/src/multi-items/__stories__/multi-renderer.stories.js +88 -0
  227. package/src/multi-items/__testdata__/multi-renderer_testdata.js +174 -0
  228. package/src/multi-items/__tests__/__snapshots__/multi-renderer_test.js.snap +994 -0
  229. package/src/multi-items/__tests__/items_test.js +245 -0
  230. package/src/multi-items/__tests__/multi-renderer_test.js +903 -0
  231. package/src/multi-items/__tests__/prop-type-builders_test.js +134 -0
  232. package/src/multi-items/__tests__/shapes_test.js +44 -0
  233. package/src/multi-items/__tests__/trees_test.js +316 -0
  234. package/src/multi-items/item-types.js +55 -0
  235. package/src/multi-items/items.js +176 -0
  236. package/src/multi-items/multi-renderer.jsx +542 -0
  237. package/src/multi-items/prop-type-builders.js +105 -0
  238. package/src/multi-items/shape-types.js +47 -0
  239. package/src/multi-items/shapes.js +53 -0
  240. package/src/multi-items/tree-types.js +42 -0
  241. package/src/multi-items/trees.js +345 -0
  242. package/src/multi-items.js +76 -0
  243. package/src/not-gorgon.js +129 -0
  244. package/src/perseus-api.jsx +270 -0
  245. package/src/perseus-env.js +26 -0
  246. package/src/perseus-error.js +19 -0
  247. package/src/perseus-markdown.jsx +434 -0
  248. package/src/perseus-types.js +1597 -0
  249. package/src/pure-markdown.js +346 -0
  250. package/src/question-paragraph.jsx +30 -0
  251. package/src/renderability.jsx +108 -0
  252. package/src/renderer.jsx +1931 -0
  253. package/src/server-item-renderer.jsx +467 -0
  254. package/src/sigfigs.jsx +492 -0
  255. package/src/styles/articles.less +748 -0
  256. package/src/styles/constants.js +119 -0
  257. package/src/styles/global-constants.js +350 -0
  258. package/src/styles/global-styles.js +307 -0
  259. package/src/styles/khan-exercise.css +1304 -0
  260. package/src/styles/media-queries.js +48 -0
  261. package/src/styles/perseus-renderer.less +1008 -0
  262. package/src/styles/shared.js +132 -0
  263. package/src/styles/util.less +47 -0
  264. package/src/styles/variables.boostrap.less +301 -0
  265. package/src/styles/variables.less +68 -0
  266. package/src/styles/variables.shared.less +356 -0
  267. package/src/styles/widgets/categorizer.less +39 -0
  268. package/src/styles/widgets/definition.less +86 -0
  269. package/src/styles/widgets/dropdown.less +3 -0
  270. package/src/styles/widgets/expression.less +147 -0
  271. package/src/styles/widgets/graded-group.less +36 -0
  272. package/src/styles/widgets/grapher.less +11 -0
  273. package/src/styles/widgets/group.less +19 -0
  274. package/src/styles/widgets/image.less +29 -0
  275. package/src/styles/widgets/interactive-graph.less +61 -0
  276. package/src/styles/widgets/label-image.less +41 -0
  277. package/src/styles/widgets/matcher.less +8 -0
  278. package/src/styles/widgets/matrix.less +120 -0
  279. package/src/styles/widgets/measurer.less +20 -0
  280. package/src/styles/widgets/orderer.less +97 -0
  281. package/src/styles/widgets/passage.less +151 -0
  282. package/src/styles/widgets/plotter.less +33 -0
  283. package/src/styles/widgets/radio.less +52 -0
  284. package/src/styles/widgets/reaction-diagram.less +35 -0
  285. package/src/styles/widgets/sequence.less +5 -0
  286. package/src/styles/widgets/sortable.less +127 -0
  287. package/src/styles/widgets/table.less +56 -0
  288. package/src/styles/widgets/transformer.less +28 -0
  289. package/src/styles/widgets/unit.less +14 -0
  290. package/src/tex-wrangler.js +176 -0
  291. package/src/traversal.jsx +141 -0
  292. package/src/types.js +538 -0
  293. package/src/util/a11y.js +24 -0
  294. package/src/util/answer-types.js +862 -0
  295. package/src/util/answer-types_test.js +115 -0
  296. package/src/util/colors.js +94 -0
  297. package/src/util/fix-passage-refs.jsx +122 -0
  298. package/src/util/graph-utils.js +16 -0
  299. package/src/util/graphie.js +1585 -0
  300. package/src/util/interactive.js +4071 -0
  301. package/src/util/katex-preprocess.js +16 -0
  302. package/src/util/math.js +296 -0
  303. package/src/util/mobile-native-utils.js +14 -0
  304. package/src/util/mobile-native-utils_test.js +24 -0
  305. package/src/util/register-all-widgets-for-testing.js +12 -0
  306. package/src/util/scroll-utils.js +162 -0
  307. package/src/util/sizing-utils.js +46 -0
  308. package/src/util/tex.js +203 -0
  309. package/src/util/url-utils.js +36 -0
  310. package/src/util.js +990 -0
  311. package/src/version.js +11 -0
  312. package/src/widget-container.jsx +174 -0
  313. package/src/widgets/__stories__/categorizer.stories.jsx +15 -0
  314. package/src/widgets/__stories__/cs-program.stories.jsx +15 -0
  315. package/src/widgets/__stories__/definition.stories.jsx +38 -0
  316. package/src/widgets/__stories__/dropdown.stories.jsx +15 -0
  317. package/src/widgets/__stories__/explanation.stories.jsx +19 -0
  318. package/src/widgets/__stories__/expression.stories.jsx +26 -0
  319. package/src/widgets/__stories__/graded-group-set.stories.jsx +32 -0
  320. package/src/widgets/__stories__/graded-group.stories.jsx +32 -0
  321. package/src/widgets/__stories__/grapher.stories.jsx +47 -0
  322. package/src/widgets/__stories__/group.stories.jsx +15 -0
  323. package/src/widgets/__stories__/iframe.stories.jsx +15 -0
  324. package/src/widgets/__stories__/image.stories.jsx +62 -0
  325. package/src/widgets/__stories__/input-number.stories.jsx +90 -0
  326. package/src/widgets/__stories__/interaction.stories.jsx +15 -0
  327. package/src/widgets/__stories__/interactive-graph.stories.jsx +61 -0
  328. package/src/widgets/__stories__/matcher.stories.jsx +15 -0
  329. package/src/widgets/__stories__/matrix.stories.jsx +15 -0
  330. package/src/widgets/__stories__/number-line.stories.jsx +15 -0
  331. package/src/widgets/__stories__/numeric-input.stories.jsx +19 -0
  332. package/src/widgets/__stories__/orderer.stories.jsx +15 -0
  333. package/src/widgets/__stories__/passage-ref.stories.jsx +19 -0
  334. package/src/widgets/__stories__/passage.stories.jsx +19 -0
  335. package/src/widgets/__stories__/radio.stories.jsx +82 -0
  336. package/src/widgets/__stories__/sorter.stories.jsx +15 -0
  337. package/src/widgets/__stories__/transformer.stories.jsx +15 -0
  338. package/src/widgets/__stories__/video.stories.jsx +15 -0
  339. package/src/widgets/__testdata__/categorizer_testdata.js +50 -0
  340. package/src/widgets/__testdata__/cs-program_testdata.js +28 -0
  341. package/src/widgets/__testdata__/dropdown_testdata.js +34 -0
  342. package/src/widgets/__testdata__/explanation_testdata.js +56 -0
  343. package/src/widgets/__testdata__/expression_testdata.js +95 -0
  344. package/src/widgets/__testdata__/graded-group-set_testdata.js +358 -0
  345. package/src/widgets/__testdata__/graded-group_testdata.js +79 -0
  346. package/src/widgets/__testdata__/grapher_testdata.js +376 -0
  347. package/src/widgets/__testdata__/group_testdata.js +156 -0
  348. package/src/widgets/__testdata__/iframe_testdata.js +36 -0
  349. package/src/widgets/__testdata__/image_testdata.js +38 -0
  350. package/src/widgets/__testdata__/input-number_testdata.js +73 -0
  351. package/src/widgets/__testdata__/interaction_testdata.js +264 -0
  352. package/src/widgets/__testdata__/interactive-graph_testdata.js +646 -0
  353. package/src/widgets/__testdata__/matcher_testdata.js +34 -0
  354. package/src/widgets/__testdata__/matrix_testdata.js +29 -0
  355. package/src/widgets/__testdata__/number-line_testdata.js +31 -0
  356. package/src/widgets/__testdata__/numeric-input_testdata.js +266 -0
  357. package/src/widgets/__testdata__/orderer_testdata.js +58 -0
  358. package/src/widgets/__testdata__/passage-ref_testdata.js +41 -0
  359. package/src/widgets/__testdata__/passage_testdata.js +71 -0
  360. package/src/widgets/__testdata__/radio_testdata.js +210 -0
  361. package/src/widgets/__testdata__/sorter_testdata.js +20 -0
  362. package/src/widgets/__testdata__/transformer_testdata.js +109 -0
  363. package/src/widgets/__testdata__/video_testdata.js +22 -0
  364. package/src/widgets/__tests__/__snapshots__/categorizer_test.jsx.snap +928 -0
  365. package/src/widgets/__tests__/__snapshots__/cs-program_test.jsx.snap +75 -0
  366. package/src/widgets/__tests__/__snapshots__/definition_test.js.snap +99 -0
  367. package/src/widgets/__tests__/__snapshots__/dropdown_test.jsx.snap +143 -0
  368. package/src/widgets/__tests__/__snapshots__/explanation_test.js.snap +473 -0
  369. package/src/widgets/__tests__/__snapshots__/expression_test.jsx.snap +38 -0
  370. package/src/widgets/__tests__/__snapshots__/graded-group-set-jipt_test.jsx.snap +754 -0
  371. package/src/widgets/__tests__/__snapshots__/graded-group-set_test.jsx.snap +310 -0
  372. package/src/widgets/__tests__/__snapshots__/graded-group_test.jsx.snap +863 -0
  373. package/src/widgets/__tests__/__snapshots__/grapher_test.jsx.snap +2880 -0
  374. package/src/widgets/__tests__/__snapshots__/group_test.jsx.snap +1154 -0
  375. package/src/widgets/__tests__/__snapshots__/iframe_test.jsx.snap +69 -0
  376. package/src/widgets/__tests__/__snapshots__/image_test.jsx.snap +388 -0
  377. package/src/widgets/__tests__/__snapshots__/input-number_test.jsx.snap +978 -0
  378. package/src/widgets/__tests__/__snapshots__/interaction_test.jsx.snap +311 -0
  379. package/src/widgets/__tests__/__snapshots__/interactive-graph_test.jsx.snap +2915 -0
  380. package/src/widgets/__tests__/__snapshots__/matcher_test.jsx.snap +1039 -0
  381. package/src/widgets/__tests__/__snapshots__/matrix_test.jsx.snap +450 -0
  382. package/src/widgets/__tests__/__snapshots__/number-line_test.jsx.snap +888 -0
  383. package/src/widgets/__tests__/__snapshots__/numeric-input_test.jsx.snap +930 -0
  384. package/src/widgets/__tests__/__snapshots__/orderer_test.jsx.snap +275 -0
  385. package/src/widgets/__tests__/__snapshots__/passage-ref_test.jsx.snap +446 -0
  386. package/src/widgets/__tests__/__snapshots__/passage_test.jsx.snap +1988 -0
  387. package/src/widgets/__tests__/__snapshots__/radio_test.jsx.snap +7107 -0
  388. package/src/widgets/__tests__/__snapshots__/video_test.jsx.snap +163 -0
  389. package/src/widgets/__tests__/categorizer_test.jsx +205 -0
  390. package/src/widgets/__tests__/cs-program_test.jsx +45 -0
  391. package/src/widgets/__tests__/definition_test.js +152 -0
  392. package/src/widgets/__tests__/dropdown_test.jsx +106 -0
  393. package/src/widgets/__tests__/explanation_test.js +199 -0
  394. package/src/widgets/__tests__/expression_test.jsx +439 -0
  395. package/src/widgets/__tests__/graded-group-set-jipt_test.jsx +33 -0
  396. package/src/widgets/__tests__/graded-group-set_test.jsx +270 -0
  397. package/src/widgets/__tests__/graded-group_test.jsx +233 -0
  398. package/src/widgets/__tests__/grapher.cypress.jsx +486 -0
  399. package/src/widgets/__tests__/grapher_test.jsx +35 -0
  400. package/src/widgets/__tests__/group_test.jsx +512 -0
  401. package/src/widgets/__tests__/iframe_test.jsx +47 -0
  402. package/src/widgets/__tests__/image_test.jsx +65 -0
  403. package/src/widgets/__tests__/input-number_test.jsx +360 -0
  404. package/src/widgets/__tests__/interaction_test.jsx +33 -0
  405. package/src/widgets/__tests__/interactive-graph.cypress.jsx +697 -0
  406. package/src/widgets/__tests__/interactive-graph_test.jsx +105 -0
  407. package/src/widgets/__tests__/matcher_test.jsx +157 -0
  408. package/src/widgets/__tests__/matrix_test.jsx +84 -0
  409. package/src/widgets/__tests__/number-line_test.jsx +81 -0
  410. package/src/widgets/__tests__/numeric-input_test.jsx +574 -0
  411. package/src/widgets/__tests__/orderer_test.jsx +75 -0
  412. package/src/widgets/__tests__/passage-ref_test.jsx +166 -0
  413. package/src/widgets/__tests__/passage_test.jsx +133 -0
  414. package/src/widgets/__tests__/radio/base-radio_test.jsx +82 -0
  415. package/src/widgets/__tests__/radio_test.jsx +651 -0
  416. package/src/widgets/__tests__/renderQuestion.jsx +65 -0
  417. package/src/widgets/__tests__/unit_test.jsx +144 -0
  418. package/src/widgets/__tests__/video_test.jsx +55 -0
  419. package/src/widgets/categorizer.jsx +338 -0
  420. package/src/widgets/cs-program.jsx +228 -0
  421. package/src/widgets/definition.jsx +141 -0
  422. package/src/widgets/dropdown.jsx +143 -0
  423. package/src/widgets/explanation.jsx +315 -0
  424. package/src/widgets/expression.jsx +654 -0
  425. package/src/widgets/graded-group-answer-bar.jsx +152 -0
  426. package/src/widgets/graded-group-set.jsx +285 -0
  427. package/src/widgets/graded-group.jsx +448 -0
  428. package/src/widgets/grapher/util.jsx +819 -0
  429. package/src/widgets/grapher.jsx +658 -0
  430. package/src/widgets/group.jsx +213 -0
  431. package/src/widgets/iframe.jsx +199 -0
  432. package/src/widgets/image.jsx +309 -0
  433. package/src/widgets/input-number.jsx +410 -0
  434. package/src/widgets/interaction/arrow-picker.jsx +39 -0
  435. package/src/widgets/interaction/color-picker.jsx +69 -0
  436. package/src/widgets/interaction/dash-picker.jsx +46 -0
  437. package/src/widgets/interaction/element-container.jsx +97 -0
  438. package/src/widgets/interaction.jsx +742 -0
  439. package/src/widgets/interactive-graph.jsx +2821 -0
  440. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +93 -0
  441. package/src/widgets/label-image/__stories__/marker.stories.jsx +172 -0
  442. package/src/widgets/label-image/answer-choices.jsx +311 -0
  443. package/src/widgets/label-image/marker.jsx +365 -0
  444. package/src/widgets/label-image/types.js +33 -0
  445. package/src/widgets/label-image.jsx +841 -0
  446. package/src/widgets/label-image_test.js +840 -0
  447. package/src/widgets/lights-puzzle.jsx +356 -0
  448. package/src/widgets/matcher.jsx +271 -0
  449. package/src/widgets/matrix.jsx +615 -0
  450. package/src/widgets/measurer.jsx +215 -0
  451. package/src/widgets/molecule/__tests__/layout_test.jsx +159 -0
  452. package/src/widgets/molecule/__tests__/parser_test.jsx +142 -0
  453. package/src/widgets/molecule/molecule-drawing.jsx +175 -0
  454. package/src/widgets/molecule/molecule-layout.jsx +367 -0
  455. package/src/widgets/molecule/smiles-parser.jsx +300 -0
  456. package/src/widgets/molecule.jsx +172 -0
  457. package/src/widgets/number-line.jsx +858 -0
  458. package/src/widgets/numeric-input.jsx +622 -0
  459. package/src/widgets/orderer.jsx +753 -0
  460. package/src/widgets/passage/__tests__/passage-markdown_test.jsx +382 -0
  461. package/src/widgets/passage/get-line-height-for-node.js +10 -0
  462. package/src/widgets/passage/passage-markdown.jsx +416 -0
  463. package/src/widgets/passage-ref-target.jsx +70 -0
  464. package/src/widgets/passage-ref.jsx +198 -0
  465. package/src/widgets/passage.jsx +601 -0
  466. package/src/widgets/plotter.jsx +1198 -0
  467. package/src/widgets/radio/__stories__/base-radio.stories.jsx +40 -0
  468. package/src/widgets/radio/__stories__/choice-icon.stories.jsx +157 -0
  469. package/src/widgets/radio/__stories__/choice-none-above.stories.jsx +26 -0
  470. package/src/widgets/radio/__stories__/cross-out-button.stories.jsx +64 -0
  471. package/src/widgets/radio/__stories__/cross-out-menu.stories.jsx +59 -0
  472. package/src/widgets/radio/__stories__/cross-out-tooltip.stories.jsx +59 -0
  473. package/src/widgets/radio/__stories__/focus-ring.stories.jsx +38 -0
  474. package/src/widgets/radio/__stories__/option-status.stories.jsx +118 -0
  475. package/src/widgets/radio/__stories__/toggleable-radio-button.stories.jsx +28 -0
  476. package/src/widgets/radio/base-radio.jsx +642 -0
  477. package/src/widgets/radio/choice-icon.jsx +468 -0
  478. package/src/widgets/radio/choice-none-above.jsx +54 -0
  479. package/src/widgets/radio/choice.jsx +692 -0
  480. package/src/widgets/radio/choice.jsx.fixture.js +64 -0
  481. package/src/widgets/radio/cross-out-button.jsx +136 -0
  482. package/src/widgets/radio/cross-out-menu-wrapper.jsx +492 -0
  483. package/src/widgets/radio/cross-out-tooltip.jsx +129 -0
  484. package/src/widgets/radio/focus-ring.jsx +51 -0
  485. package/src/widgets/radio/option-status.jsx +89 -0
  486. package/src/widgets/radio/toggleable-radio-button.jsx +133 -0
  487. package/src/widgets/radio/util.js +32 -0
  488. package/src/widgets/radio/util_test.js +19 -0
  489. package/src/widgets/radio/widget.jsx +470 -0
  490. package/src/widgets/radio.jsx +156 -0
  491. package/src/widgets/reaction-diagram.jsx +136 -0
  492. package/src/widgets/sequence.jsx +140 -0
  493. package/src/widgets/simulator.jsx +808 -0
  494. package/src/widgets/sorter.jsx +146 -0
  495. package/src/widgets/table.jsx +395 -0
  496. package/src/widgets/transformer.jsx +2870 -0
  497. package/src/widgets/unit.jsx +371 -0
  498. package/src/widgets/video-transcript-link.jsx +84 -0
  499. package/src/widgets/video-transcript-link_test.js +211 -0
  500. package/src/widgets/video.jsx +131 -0
  501. package/src/widgets.js +431 -0
  502. package/src/zoom.js +539 -0
  503. package/src/__tests__/index.test.js +0 -14
@@ -0,0 +1,111 @@
1
+ // @flow
2
+ import getRenderA11yString from "katex/dist/contrib/render-a11y-string.js";
3
+ import * as React from "react";
4
+ import "@testing-library/jest-dom";
5
+
6
+ import {testDependencies} from "../../../../../testing/test-dependencies.js";
7
+ import {wait} from "../../../../../testing/wait.js";
8
+ import * as Dependencies from "../../dependencies.js";
9
+ import {renderQuestion} from "../../widgets/__tests__/renderQuestion.jsx";
10
+ import {question1} from "../__testdata__/sorter_testdata.js";
11
+
12
+ import type {APIOptions} from "../../types.js";
13
+
14
+ describe("sorter widget", () => {
15
+ beforeEach(() => {
16
+ /*
17
+ Sortable misbehaves and sets state after the component has been
18
+ unmounted. This is existing behavior and its safer to leave the existing
19
+ implementation and swallow the warning in tests.
20
+ */
21
+ jest.spyOn(console, "warn").mockImplementation(() => {});
22
+ jest.spyOn(console, "error").mockImplementation(() => {});
23
+
24
+ jest.spyOn(Dependencies, "getDependencies").mockReturnValue({
25
+ ...testDependencies,
26
+ getKaTeX: () => {
27
+ return Promise.resolve({});
28
+ },
29
+ TeX: ({children}: {|children: React.Node|}) => (
30
+ <span className="tex-mock">{children}</span>
31
+ ),
32
+ getRenderA11yString: Promise.resolve(getRenderA11yString),
33
+ shouldUseFutureKaTeX: (flag: boolean) => {},
34
+ });
35
+ });
36
+
37
+ it("should snapshot", async () => {
38
+ // Arrange
39
+ jest.useRealTimers();
40
+
41
+ const apiOptions: APIOptions = {
42
+ isMobile: false,
43
+ };
44
+
45
+ // Act
46
+ const {container} = renderQuestion(question1, apiOptions);
47
+ await wait();
48
+
49
+ // Assert
50
+ expect(container).toMatchSnapshot("first render");
51
+ });
52
+
53
+ it("should snapshot on mobile", async () => {
54
+ // Arrange
55
+ jest.useRealTimers();
56
+
57
+ const apiOptions: APIOptions = {
58
+ isMobile: true,
59
+ };
60
+
61
+ // Act
62
+ const {container} = renderQuestion(question1, apiOptions);
63
+ await wait();
64
+
65
+ // Assert
66
+ expect(container).toMatchSnapshot("first mobile render");
67
+ });
68
+
69
+ it("can be answered correctly", () => {
70
+ // Arrange
71
+ const apiOptions: APIOptions = {
72
+ isMobile: false,
73
+ };
74
+ const {renderer} = renderQuestion(question1, apiOptions);
75
+ const sorter = renderer.findWidgets("sorter 1")[0];
76
+
77
+ // Put the options in the correct order
78
+ ["$0.005$ kilograms", "$15$ grams", "$55$ grams"].forEach(
79
+ (option, index) => {
80
+ sorter.moveOptionToIndex(option, 3);
81
+ },
82
+ );
83
+
84
+ // Act
85
+ renderer.guessAndScore();
86
+
87
+ // assert
88
+ expect(renderer).toHaveBeenAnsweredCorrectly();
89
+ });
90
+ it("can be answered incorrectly", () => {
91
+ // Arrange
92
+ const apiOptions: APIOptions = {
93
+ isMobile: false,
94
+ };
95
+ const {renderer} = renderQuestion(question1, apiOptions);
96
+ const sorter = renderer.findWidgets("sorter 1")[0];
97
+
98
+ // Put the options in the reverse order
99
+ ["$0.005$ kilograms", "$15$ grams", "$55$ grams"].forEach(
100
+ (option, index) => {
101
+ sorter.moveOptionToIndex(option, 0);
102
+ },
103
+ );
104
+
105
+ // Act
106
+ renderer.guessAndScore();
107
+
108
+ // assert
109
+ expect(renderer).toHaveBeenAnsweredIncorrectly();
110
+ });
111
+ });
@@ -0,0 +1,388 @@
1
+ // @flow
2
+
3
+ import {fireEvent, render, screen, waitFor} from "@testing-library/react";
4
+ import userEvent from "@testing-library/user-event";
5
+ import * as React from "react";
6
+ import "@testing-library/jest-dom"; // Imports custom mathers
7
+
8
+ import Zoomable from "../zoomable.jsx";
9
+
10
+ const mockSize = (el, size) => {
11
+ jest.spyOn(el, "offsetWidth", "get").mockImplementation(() => size.width);
12
+ jest.spyOn(el, "offsetHeight", "get").mockImplementation(() => size.height);
13
+ };
14
+
15
+ describe("Zoomable", () => {
16
+ it("should snapshot", () => {
17
+ // Arrange and Act
18
+ const {container} = render(
19
+ <Zoomable>
20
+ <span>Some zoomable text</span>
21
+ </Zoomable>,
22
+ );
23
+
24
+ // Assert
25
+ expect(container).toMatchInlineSnapshot(`
26
+ <div>
27
+ <span
28
+ style="display: block; width: 100%; transform: scale(1, 1) translate(0, 8px); transform-origin: 0 0; opacity: 0;"
29
+ >
30
+ <span>
31
+ Some zoomable text
32
+ </span>
33
+ </span>
34
+ </div>
35
+ `);
36
+ });
37
+
38
+ it("should toggle to zoomed state when clicked", () => {
39
+ // Arrange
40
+ const {container} = render(
41
+ <Zoomable>
42
+ <span>Some zoomable text</span>
43
+ </Zoomable>,
44
+ );
45
+
46
+ // Act
47
+ userEvent.click(screen.getByText("Some zoomable text"));
48
+ jest.runOnlyPendingTimers();
49
+
50
+ // Assert
51
+ expect(container).toMatchInlineSnapshot(`
52
+ <div>
53
+ <span
54
+ style="display: block; width: 100%; transform: scale(0, 0) translate(0, 8px); transform-origin: 0 0; opacity: 0; height: 0px;"
55
+ >
56
+ <span>
57
+ Some zoomable text
58
+ </span>
59
+ </span>
60
+ </div>
61
+ `);
62
+ });
63
+
64
+ it("should toggle to unzoomed state when clicked while zoomed", () => {
65
+ // Arrange
66
+ const {container} = render(
67
+ <Zoomable>
68
+ <span>Some zoomable text</span>
69
+ </Zoomable>,
70
+ );
71
+
72
+ userEvent.click(screen.getByText("Some zoomable text"));
73
+ jest.runOnlyPendingTimers();
74
+
75
+ // Act
76
+ userEvent.click(screen.getByText("Some zoomable text"));
77
+ jest.runOnlyPendingTimers();
78
+
79
+ // Assert
80
+ expect(container).toMatchInlineSnapshot(`
81
+ <div>
82
+ <span
83
+ style="display: block; width: 100%; transform: scale(1, 1); transform-origin: 0 0; opacity: 1; height: 1px; transition-property: opacity transform; transition-duration: 0.3s; transition-timing-function: ease-out;"
84
+ >
85
+ <span>
86
+ Some zoomable text
87
+ </span>
88
+ </span>
89
+ </div>
90
+ `);
91
+ });
92
+
93
+ it("should use provided computeChildBounds function when zooming", () => {
94
+ // Arrange
95
+ const computeChildBounds = jest.fn(() => ({width: 1000, height: 1000}));
96
+
97
+ // Act
98
+ render(
99
+ <Zoomable computeChildBounds={computeChildBounds}>
100
+ <span>Some zoomable text</span>
101
+ </Zoomable>,
102
+ );
103
+ jest.runOnlyPendingTimers();
104
+
105
+ // Assert
106
+ expect(computeChildBounds).toHaveBeenCalled();
107
+ });
108
+
109
+ it("should scale if computeChildBounds is larger than root node size", () => {
110
+ // Arrange
111
+ const {container} = render(
112
+ <Zoomable>
113
+ <span>Some zoomable text</span>
114
+ </Zoomable>,
115
+ );
116
+
117
+ // Parent node bounds
118
+ // eslint-disable-next-line testing-library/no-node-access
119
+ const rootNode = container.firstElementChild;
120
+ mockSize(rootNode, {width: 400, height: 100});
121
+
122
+ // Child node bounds
123
+ mockSize(screen.getByText("Some zoomable text"), {
124
+ width: 800,
125
+ height: 200,
126
+ });
127
+
128
+ // Act
129
+ // The measure action uses a setState and setTimeout(0)
130
+ jest.runOnlyPendingTimers();
131
+ jest.runOnlyPendingTimers();
132
+
133
+ // Assert
134
+ expect(container).toMatchInlineSnapshot(`
135
+ <div>
136
+ <span
137
+ style="display: block; width: 100%; transform: scale(0.4993757802746567, 0.4993757802746567); transform-origin: 0 0; opacity: 1; height: 101px; transition-property: opacity transform; transition-duration: 0.3s; transition-timing-function: ease-out;"
138
+ >
139
+ <span>
140
+ Some zoomable text
141
+ </span>
142
+ </span>
143
+ </div>
144
+ `);
145
+ });
146
+
147
+ it("should not scale if computeChildBounds is smaller than root node size", () => {
148
+ // Arrange
149
+ const {container} = render(
150
+ <Zoomable>
151
+ <span>Some zoomable text</span>
152
+ </Zoomable>,
153
+ );
154
+
155
+ // Parent node bounds
156
+ // eslint-disable-next-line testing-library/no-node-access
157
+ const rootNode = container.firstElementChild;
158
+ mockSize(rootNode, {width: 400, height: 100});
159
+
160
+ // Child node bounds
161
+ mockSize(screen.getByText("Some zoomable text"), {
162
+ width: 100,
163
+ height: 100,
164
+ });
165
+
166
+ // Act
167
+ jest.runOnlyPendingTimers();
168
+
169
+ // Assert
170
+ expect(container).toMatchInlineSnapshot(`
171
+ <div>
172
+ <span
173
+ style="display: block; width: 100%; transform: scale(1, 1); transform-origin: 0 0; opacity: 1; transition-property: opacity transform; transition-duration: 0.3s; transition-timing-function: ease-out;"
174
+ >
175
+ <span>
176
+ Some zoomable text
177
+ </span>
178
+ </span>
179
+ </div>
180
+ `);
181
+ });
182
+
183
+ it("should reset to initial state on window resize", () => {
184
+ // Arrange
185
+ // Simulate window resize event
186
+ const resizeWindowTo = (width: number, height: number) => {
187
+ const resizeEvent = document.createEvent("Event");
188
+ resizeEvent.initEvent("resize", true, true);
189
+
190
+ window.innerWidth = width;
191
+ window.innerHeight = height;
192
+ window.dispatchEvent(resizeEvent);
193
+ };
194
+
195
+ render(
196
+ <Zoomable>
197
+ <span>Some zoomable text</span>
198
+ </Zoomable>,
199
+ );
200
+ // We need two cycles to get everything rendered and visible
201
+ jest.runOnlyPendingTimers();
202
+ jest.runOnlyPendingTimers();
203
+
204
+ // Act
205
+ resizeWindowTo(500, 500);
206
+
207
+ // Assert
208
+ // The children are initially displayed with an opacity of 0 to give
209
+ // the component a chance to render and measure. Once that's done, the
210
+ // content becomes visible. So intial state is invisible.
211
+ expect(screen.queryByText("Some zoomable text")).not.toBeVisible();
212
+ });
213
+
214
+ it("should toggle zoom if zoomed", () => {
215
+ // Arrange
216
+ const onClickHandler = jest.fn();
217
+ let zoomable: ?Zoomable;
218
+ render(
219
+ <Zoomable ref={(c) => (zoomable = c)}>
220
+ <span onClick={onClickHandler}>Some zoomable text</span>
221
+ </Zoomable>,
222
+ );
223
+ userEvent.click(screen.getByText("Some zoomable text"));
224
+ jest.runOnlyPendingTimers();
225
+ onClickHandler.mockClear();
226
+
227
+ // Act
228
+ userEvent.click(screen.getByText("Some zoomable text"));
229
+ jest.runOnlyPendingTimers();
230
+
231
+ // Assert
232
+ expect(onClickHandler).not.toHaveBeenCalled();
233
+ expect(zoomable?.state.zoomed).toBeTrue();
234
+ });
235
+
236
+ describe.each([
237
+ ["onTouchStartCapture", fireEvent.touchStart],
238
+ ["onTouchEndCapture", fireEvent.touchEnd],
239
+ ["onTouchCancelCapture", fireEvent.touchCancel],
240
+ ])("propogation of touch event: %s", (propName: string, eventFirer) => {
241
+ it("should not propogate event to children when zoomed", () => {
242
+ // Arrange
243
+ const props = {};
244
+ props[propName] = jest.fn();
245
+
246
+ render(
247
+ <Zoomable>
248
+ <span {...props}>Some zoomable text</span>
249
+ </Zoomable>,
250
+ );
251
+ userEvent.click(screen.getByText("Some zoomable text"));
252
+
253
+ // Act
254
+ eventFirer(screen.getByText("Some zoomable text"));
255
+ jest.runOnlyPendingTimers();
256
+
257
+ // Assert
258
+ expect(props[propName]).not.toHaveBeenCalled();
259
+ });
260
+
261
+ it("should propogate event to children when not zoomed", () => {
262
+ // Arrange
263
+ const props = {};
264
+ props[propName] = jest.fn();
265
+
266
+ render(
267
+ <Zoomable>
268
+ <span {...props}>Some zoomable text</span>
269
+ </Zoomable>,
270
+ );
271
+ jest.runOnlyPendingTimers();
272
+ userEvent.click(screen.getByText("Some zoomable text"));
273
+ jest.runOnlyPendingTimers();
274
+
275
+ // Act
276
+ eventFirer(screen.getByText("Some zoomable text"));
277
+
278
+ // Assert
279
+ expect(props[propName]).toHaveBeenCalled();
280
+ });
281
+ });
282
+
283
+ describe("child node mutations", () => {
284
+ let computeChildBounds;
285
+ let componentContainer;
286
+
287
+ beforeEach(() => {
288
+ // Arrange
289
+ const sizes = [
290
+ // First render
291
+ {width: 100, height: 100},
292
+ // Post-mutation render
293
+ {width: 1000, height: 1000},
294
+ ];
295
+ computeChildBounds = jest.fn(() => sizes.shift());
296
+
297
+ // eslint-disable-next-line testing-library/no-render-in-setup
298
+ const {container} = render(
299
+ <Zoomable computeChildBounds={computeChildBounds}>
300
+ <span>Some zoomable text</span>
301
+ </Zoomable>,
302
+ );
303
+ componentContainer = container;
304
+
305
+ // eslint-disable-next-line testing-library/no-node-access
306
+ const rootNode = container.firstElementChild;
307
+ mockSize(rootNode, {width: 200, height: 200});
308
+
309
+ jest.runOnlyPendingTimers();
310
+ });
311
+
312
+ it("should update measurements", async () => {
313
+ // Act
314
+ screen.getByText("Some zoomable text").innerHTML =
315
+ "Some more zoomable text";
316
+ await waitFor(() => {
317
+ screen.getByText("Some more zoomable text");
318
+ });
319
+
320
+ // Assert
321
+ expect(computeChildBounds).toHaveBeenCalledTimes(2);
322
+ expect(componentContainer).toMatchInlineSnapshot(`
323
+ <div>
324
+ <span
325
+ style="display: block; width: 100%; transform: scale(1, 1); transform-origin: 0 0; opacity: 1; transition-property: opacity transform; transition-duration: 0.3s; transition-timing-function: ease-out; height: 1001px;"
326
+ >
327
+ <span>
328
+ Some more zoomable text
329
+ </span>
330
+ </span>
331
+ </div>
332
+ `);
333
+ });
334
+
335
+ it("should remain zoomed", async () => {
336
+ // Arrange
337
+
338
+ // Act
339
+ screen.getByText("Some zoomable text").innerHTML =
340
+ "Some more zoomable text";
341
+ await waitFor(() => {
342
+ screen.getByText("Some more zoomable text");
343
+ });
344
+
345
+ // Assert
346
+ expect(computeChildBounds).toHaveBeenCalledTimes(2);
347
+ expect(componentContainer).toMatchInlineSnapshot(`
348
+ <div>
349
+ <span
350
+ style="display: block; width: 100%; transform: scale(1, 1); transform-origin: 0 0; opacity: 1; transition-property: opacity transform; transition-duration: 0.3s; transition-timing-function: ease-out; height: 1001px;"
351
+ >
352
+ <span>
353
+ Some more zoomable text
354
+ </span>
355
+ </span>
356
+ </div>
357
+ `);
358
+ });
359
+
360
+ it("should remain unzoomed", async () => {
361
+ // Arrange
362
+ // We default to "zoomed", so this "unzooms"
363
+ userEvent.click(screen.getByText("Some zoomable text"));
364
+ jest.runOnlyPendingTimers();
365
+
366
+ // Act
367
+ screen.getByText("Some zoomable text").innerHTML =
368
+ "Some more zoomable text";
369
+ await waitFor(() => {
370
+ screen.getByText("Some more zoomable text");
371
+ });
372
+
373
+ // Assert
374
+ expect(computeChildBounds).toHaveBeenCalledTimes(2);
375
+ expect(componentContainer).toMatchInlineSnapshot(`
376
+ <div>
377
+ <span
378
+ style="display: block; width: 100%; transform: scale(0.1998001998001998, 0.1998001998001998); transform-origin: 0 0; opacity: 1; transition-property: opacity transform; transition-duration: 0.3s; transition-timing-function: ease-out; height: 200px;"
379
+ >
380
+ <span>
381
+ Some more zoomable text
382
+ </span>
383
+ </span>
384
+ </div>
385
+ `);
386
+ });
387
+ });
388
+ });
@@ -0,0 +1,131 @@
1
+ // @flow
2
+ import {css, StyleSheet} from "aphrodite";
3
+ import * as React from "react";
4
+
5
+ type Props = {|
6
+ // the initial value of the button selected, defaults to null
7
+ value: any,
8
+ buttons: $ReadOnlyArray<{|
9
+ // the value returned when the button is selected
10
+ value: any,
11
+ // the content shown within the button, typically a string that gets
12
+ // rendered as the button's display text
13
+ content: React.Node,
14
+ // the title-text shown on hover
15
+ title?: string,
16
+ |}>,
17
+ // a function that is provided with the updated value (which it then is
18
+ // responsible for updating)
19
+ onChange: (value: any) => mixed,
20
+ // if false, exactly one button _must_ be selected;
21
+ // defaults to true and _at most_ one button (0 or 1) may be selected.
22
+ allowEmpty: boolean,
23
+ |};
24
+
25
+ type DefaultProps = {|
26
+ allowEmpty: Props["allowEmpty"],
27
+ value: Props["value"],
28
+ |};
29
+
30
+ /**
31
+ * ButtonGroup is an aesthetically pleasing group of buttons.
32
+ */
33
+ class ButtonGroup extends React.Component<Props> {
34
+ container: ?HTMLDivElement;
35
+
36
+ static defaultProps: DefaultProps = {
37
+ value: null,
38
+ allowEmpty: true,
39
+ };
40
+
41
+ componentWillUnmount() {
42
+ this.container = null;
43
+ }
44
+
45
+ focus(): void | boolean {
46
+ if (this.container) {
47
+ this.container.focus();
48
+ return true;
49
+ }
50
+ }
51
+
52
+ toggleSelect(newValue: any) {
53
+ const value = this.props.value;
54
+
55
+ if (this.props.allowEmpty) {
56
+ // Select the new button or unselect if it's already selected
57
+ this.props.onChange(value !== newValue ? newValue : null);
58
+ } else {
59
+ this.props.onChange(newValue);
60
+ }
61
+ }
62
+
63
+ render(): React.Element<"div"> {
64
+ const value = this.props.value;
65
+ const buttons = this.props.buttons.map((button, i) => {
66
+ return (
67
+ <button
68
+ title={button.title}
69
+ type="button"
70
+ id={"" + i}
71
+ ref={"button" + i}
72
+ key={"" + i}
73
+ className={css(
74
+ styles.buttonStyle,
75
+ button.value === value && styles.selectedStyle,
76
+ )}
77
+ onClick={() => this.toggleSelect(button.value)}
78
+ >
79
+ {button.content || "" + button.value}
80
+ </button>
81
+ );
82
+ });
83
+
84
+ const outerStyle = {
85
+ display: "inline-block",
86
+ };
87
+ return (
88
+ <div style={outerStyle} ref={(node) => (this.container = node)}>
89
+ {buttons}
90
+ </div>
91
+ );
92
+ }
93
+ }
94
+
95
+ const styles = StyleSheet.create({
96
+ buttonStyle: {
97
+ backgroundColor: "white",
98
+ border: "1px solid #ccc",
99
+ borderLeft: "0",
100
+ cursor: "pointer",
101
+ margin: "0",
102
+ padding: "5px 10px",
103
+ position: "relative", // for hover
104
+
105
+ ":first-child": {
106
+ borderLeft: "1px solid #ccc",
107
+ borderTopLeftRadius: "3px",
108
+ borderBottomLeftRadius: "3px",
109
+ },
110
+
111
+ ":last-child": {
112
+ borderRight: "1px solid #ccc",
113
+ borderTopRightRadius: "3px",
114
+ borderBottomRightRadius: "3px",
115
+ },
116
+
117
+ ":hover": {
118
+ backgroundColor: "#ccc",
119
+ },
120
+
121
+ ":focus": {
122
+ zIndex: 2,
123
+ },
124
+ },
125
+
126
+ selectedStyle: {
127
+ backgroundColor: "#ddd",
128
+ },
129
+ });
130
+
131
+ export default ButtonGroup;
@@ -0,0 +1,15 @@
1
+ // @flow
2
+ export const devices = {
3
+ PHONE: ("phone": "phone"),
4
+ TABLET: ("tablet": "tablet"),
5
+ DESKTOP: ("desktop": "desktop"),
6
+ };
7
+
8
+ // How many pixels do we reserve on the right-hand side of a preview
9
+ // for displaying lint indicators? This space needs to be reserved
10
+ // in DeviceFramer, but it is actually allocated in PerseusFrame
11
+ export const lintGutterWidth = 36;
12
+
13
+ // How wide a border does PerseusFrame draw? We need to allocate enough
14
+ // space for it in DeviceFramer.
15
+ export const perseusFrameBorderWidth = 1;