@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,862 @@
1
+ /* eslint-disable no-useless-escape */
2
+ // @flow
3
+ import * as i18n from "@khanacademy/wonder-blocks-i18n";
4
+ import $ from "jquery";
5
+ import KAS from "kas";
6
+ import _ from "underscore";
7
+
8
+ import {Errors} from "../logging/log.js";
9
+ import {PerseusError} from "../perseus-error.js";
10
+
11
+ import KhanMath from "./math.js";
12
+
13
+ const MAXERROR_EPSILON = Math.pow(2, -42);
14
+
15
+ type Guess = $FlowFixMe;
16
+ type Predicate = $FlowFixMe;
17
+
18
+ // TOOD(kevinb): Figure out how this relates to KEScore in
19
+ // perseus-all-package/types.js and see if there's a way to
20
+ // unify these types.
21
+ type Score = {|
22
+ empty: boolean,
23
+ correct: boolean,
24
+ message: ?string,
25
+ suppressAlmostThere?: ?boolean,
26
+ guess: Guess,
27
+ // It would be nice if we could ungraded required
28
+ ungraded?: boolean,
29
+ |};
30
+
31
+ export const errors = {
32
+ APPROXIMATED_PI_ERROR: (i18n._(
33
+ "Your answer is close, but you may " +
34
+ "have approximated pi. Enter your " +
35
+ "answer as a multiple of pi, like " +
36
+ "<code>12\\ \\text{pi}</code> or " +
37
+ "<code>2/3\\ \\text{pi}</code>",
38
+ ): string),
39
+ EXTRA_SYMBOLS_ERROR: (i18n._(
40
+ "We could not understand your " +
41
+ "answer. Please check your answer for extra " +
42
+ "text or symbols.",
43
+ ): string),
44
+ NEEDS_TO_BE_SIMPLFIED_ERROR: (i18n._(
45
+ "Your answer is almost correct, " + "but it needs to be simplified.",
46
+ ): string),
47
+ MISSING_PERCENT_ERROR: (i18n._(
48
+ "Your answer is almost correct, " +
49
+ "but it is missing a " +
50
+ "<code>\\%</code> at the end.",
51
+ ): string),
52
+ MULTIPLICATION_SIGN_ERROR: (i18n._(
53
+ "I'm a computer. I only understand " +
54
+ "multiplication if you use an asterisk " +
55
+ "(*) as the multiplication sign.",
56
+ ): string),
57
+ WRONG_CASE_ERROR: (i18n._(
58
+ "Your answer includes use of a variable with the wrong case.",
59
+ ): string),
60
+ WRONG_LETTER_ERROR: (i18n._(
61
+ "Your answer includes a wrong variable letter.",
62
+ ): string),
63
+ };
64
+
65
+ /*
66
+ * Answer types
67
+ *
68
+ * Utility for creating answerable questions displayed in exercises
69
+ *
70
+ * Different answer types produce different kinds of input displays, and do
71
+ * different kinds of checking on the solutions.
72
+ *
73
+ * Each of the objects contain two functions, setup and createValidator.
74
+ *
75
+ * The setup function takes a solutionarea and solution, and performs setup
76
+ * within the solutionarea, and then returns an object which contains:
77
+ *
78
+ * answer: a function which, when called, will retrieve the current answer from
79
+ * the solutionarea, which can then be validated using the validator
80
+ * function
81
+ * validator: a function returned from the createValidator function (defined
82
+ * below)
83
+ * solution: the correct answer to the problem
84
+ * showGuess: a function which, when given a guess, shows the guess within the
85
+ * provided solutionarea
86
+ * showGuessCustom: a function which displays parts of a guess that are not
87
+ * within the solutionarea; currently only used for custom
88
+ * answers
89
+ *
90
+ * The createValidator function only takes a solution, and it returns a
91
+ * function which can be used to validate an answer.
92
+ *
93
+ * The resulting validator function returns:
94
+ * - true: if the answer is fully correct
95
+ * - false: if the answer is incorrect
96
+ * - "" (the empty string): if no answer has been provided (e.g. the answer box
97
+ * is left unfilled)
98
+ * - a string: if there is some slight error
99
+ *
100
+ * In most cases, setup and createValidator don't really need the solution DOM
101
+ * element so we have setupFunctional and createValidatorFunctional for them
102
+ * which take only $solution.text() and $solution.data(). This makes it easier
103
+ * to reuse specific answer types.
104
+ *
105
+ * TODO(alpert): Think of a less-absurd name for createValidatorFunctional.
106
+ *
107
+ */
108
+
109
+ const KhanAnswerTypes: $FlowFixMe = {
110
+ /*
111
+ * predicate answer type
112
+ *
113
+ * performs simple predicate-based checking of a numeric solution, with
114
+ * different kinds of number formats
115
+ *
116
+ * Uses the data-forms option on the solution to choose which number formats
117
+ * are acceptable. Available data-forms:
118
+ *
119
+ * - integer: 3
120
+ * - proper: 3/5
121
+ * - improper: 5/3
122
+ * - pi: 3 pi
123
+ * - log: log(5)
124
+ * - percent: 15%
125
+ * - mixed: 1 1/3
126
+ * - decimal: 1.7
127
+ *
128
+ * The solution should be a predicate of the form:
129
+ *
130
+ * function(guess, maxError) {
131
+ * return abs(guess - 3) < maxError;
132
+ * }
133
+ *
134
+ */
135
+ predicate: {
136
+ defaultForms: "integer, proper, improper, mixed, decimal",
137
+ createValidatorFunctional: function (
138
+ predicate: Predicate,
139
+ options: $FlowFixMe,
140
+ ): (Guess) => Score {
141
+ // Extract the options from the given solution object
142
+ options = _.extend(
143
+ {
144
+ simplify: "required",
145
+ ratio: false,
146
+ forms: KhanAnswerTypes.predicate.defaultForms,
147
+ },
148
+ options,
149
+ );
150
+ let acceptableForms;
151
+ // this is maintaining backwards compatibility
152
+ // TODO(merlob) fix all places that depend on this, then delete
153
+ if (!_.isArray(options.forms)) {
154
+ acceptableForms = options.forms.split(/\s*,\s*/);
155
+ } else {
156
+ acceptableForms = options.forms;
157
+ }
158
+
159
+ // TODO(jack): remove options.inexact in favor of options.maxError
160
+ if (options.inexact === undefined) {
161
+ // If we aren't allowing inexact, ensure that we don't have a
162
+ // large maxError as well.
163
+ options.maxError = 0;
164
+ }
165
+ // Allow a small tolerance on maxError, to avoid numerical
166
+ // representation issues (2.3 should be correct for a solution of
167
+ // 2.45 with maxError=0.15).
168
+ options.maxError = +options.maxError + MAXERROR_EPSILON;
169
+
170
+ // If percent is an acceptable form, make sure it's the last one
171
+ // in the list so we don't prematurely complain about not having
172
+ // a percent sign when the user entered the correct answer in a
173
+ // different form (such as a decimal or fraction)
174
+ if (_.contains(acceptableForms, "percent")) {
175
+ acceptableForms = _.without(acceptableForms, "percent");
176
+ acceptableForms.push("percent");
177
+ }
178
+
179
+ // Take text looking like a fraction, and turn it into a number
180
+ const fractionTransformer = function (text) {
181
+ text = text
182
+ // Replace unicode minus sign with hyphen
183
+ .replace(/\u2212/, "-")
184
+ // Remove space after +, -
185
+ .replace(/([+-])\s+/g, "$1")
186
+ // Remove leading/trailing whitespace
187
+ .replace(/(^\s*)|(\s*$)/gi, "");
188
+
189
+ // Extract numerator and denominator
190
+ const match = text.match(/^([+-]?\d+)\s*\/\s*([+-]?\d+)$/);
191
+ // Fractions are represented as "-\frac{numerator}{denominator}"
192
+ // in Mobile device input instead of "numerator/denominator" as
193
+ // in web-browser.
194
+ const mobileDeviceMatch = text.match(
195
+ /^([+-]?)\\frac\{([+-]?\d+)\}\{([+-]?\d+)\}$/,
196
+ );
197
+ const parsedInt = parseInt(text, 10);
198
+ if (match || mobileDeviceMatch) {
199
+ let num;
200
+ let denom;
201
+ let simplified = true;
202
+ if (match) {
203
+ num = parseFloat(match[1]);
204
+ denom = parseFloat(match[2]);
205
+ } else {
206
+ num = parseFloat(mobileDeviceMatch[2]);
207
+ if (mobileDeviceMatch[1] === "-") {
208
+ if (num < 0) {
209
+ simplified = false;
210
+ }
211
+ num = -num;
212
+ }
213
+ denom = parseFloat(mobileDeviceMatch[3]);
214
+ }
215
+
216
+ simplified =
217
+ simplified &&
218
+ denom > 0 &&
219
+ (options.ratio || denom !== 1) &&
220
+ KhanMath.getGCD(num, denom) === 1;
221
+ return [
222
+ {
223
+ value: num / denom,
224
+ exact: simplified,
225
+ },
226
+ ];
227
+ }
228
+ if (!isNaN(parsedInt) && "" + parsedInt === text) {
229
+ return [
230
+ {
231
+ value: parsedInt,
232
+ exact: true,
233
+ },
234
+ ];
235
+ }
236
+
237
+ return [];
238
+ };
239
+
240
+ /*
241
+ * Different forms of numbers
242
+ *
243
+ * Each function returns a list of objects of the form:
244
+ *
245
+ * {
246
+ * value: numerical value,
247
+ * exact: true/false
248
+ * }
249
+ */
250
+ const forms = {
251
+ // integer, which is encompassed by decimal
252
+ integer: function (text) {
253
+ // Compare the decimal form to the decimal form rounded to
254
+ // an integer. Only accept if the user actually entered an
255
+ // integer.
256
+ const decimal = forms.decimal(text);
257
+ const rounded = forms.decimal(text, 1);
258
+ if (
259
+ (decimal[0].value != null &&
260
+ decimal[0].value === rounded[0].value) ||
261
+ (decimal[1].value != null &&
262
+ decimal[1].value === rounded[1].value)
263
+ ) {
264
+ return decimal;
265
+ }
266
+ return [];
267
+ },
268
+
269
+ // A proper fraction
270
+ proper: function (text) {
271
+ return $.map(fractionTransformer(text), function (o) {
272
+ // All fractions that are less than 1
273
+ if (Math.abs(o.value) < 1) {
274
+ return [o];
275
+ }
276
+ return [];
277
+ });
278
+ },
279
+
280
+ // an improper fraction
281
+ improper: function (text) {
282
+ return $.map(fractionTransformer(text), function (o) {
283
+ // All fractions that are greater than 1
284
+ if (Math.abs(o.value) >= 1) {
285
+ return [o];
286
+ }
287
+ return [];
288
+ });
289
+ },
290
+
291
+ // pi-like numbers
292
+ pi: function (text) {
293
+ let match;
294
+ let possibilities = [];
295
+
296
+ // Replace unicode minus sign with hyphen
297
+ text = text.replace(/\u2212/, "-");
298
+
299
+ // - pi
300
+ // (Note: we also support \pi (for TeX), p, tau (and \tau,
301
+ // and t), pau.)
302
+ if (
303
+ (match = text.match(
304
+ /^([+-]?)\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i,
305
+ ))
306
+ ) {
307
+ possibilities = [
308
+ {
309
+ value: parseFloat(match[1] + "1"),
310
+ exact: true,
311
+ },
312
+ ];
313
+
314
+ // 5 / 6 pi
315
+ } else if (
316
+ (match = text.match(
317
+ /^([+-]?\s*\d+\s*(?:\/\s*[+-]?\s*\d+)?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i,
318
+ ))
319
+ ) {
320
+ possibilities = fractionTransformer(match[1]);
321
+
322
+ // 4 5 / 6 pi
323
+ } else if (
324
+ (match = text.match(
325
+ /^([+-]?)\s*(\d+)\s*([+-]?\d+)\s*\/\s*([+-]?\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i,
326
+ ))
327
+ ) {
328
+ const sign = parseFloat(match[1] + "1");
329
+ const integ = parseFloat(match[2]);
330
+ const num = parseFloat(match[3]);
331
+ const denom = parseFloat(match[4]);
332
+ const simplified =
333
+ num < denom && KhanMath.getGCD(num, denom) === 1;
334
+
335
+ possibilities = [
336
+ {
337
+ value: sign * (integ + num / denom),
338
+ exact: simplified,
339
+ },
340
+ ];
341
+
342
+ // 5 pi / 6
343
+ } else if (
344
+ (match = text.match(
345
+ /^([+-]?\s*\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\s*\d+))?$/i,
346
+ ))
347
+ ) {
348
+ possibilities = fractionTransformer(
349
+ match[1] + "/" + match[3],
350
+ );
351
+
352
+ // - pi / 4
353
+ } else if (
354
+ (match = text.match(
355
+ /^([+-]?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\d+))?$/i,
356
+ ))
357
+ ) {
358
+ possibilities = fractionTransformer(
359
+ match[1] + "1/" + match[3],
360
+ );
361
+
362
+ // 0
363
+ } else if (text === "0") {
364
+ possibilities = [{value: 0, exact: true}];
365
+
366
+ // 0.5 pi (fallback)
367
+ } else if (
368
+ (match = text.match(
369
+ /^(.+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i,
370
+ ))
371
+ ) {
372
+ possibilities = forms.decimal(match[1]);
373
+ } else {
374
+ possibilities = _.reduce(
375
+ KhanAnswerTypes.predicate.defaultForms.split(
376
+ /\s*,\s*/,
377
+ ),
378
+ function (memo, form) {
379
+ return memo.concat(forms[form](text));
380
+ },
381
+ [],
382
+ );
383
+
384
+ // If the answer is a floating point number that's
385
+ // near a multiple of pi, mark is as being possibly
386
+ // an approximation of pi. We actually check if
387
+ // it's a plausible approximation of pi/12, since
388
+ // sometimes the correct answer is like pi/3 or pi/4.
389
+ // We also say it's a pi-approximation if it involves
390
+ // x/7 (since 22/7 is an approximation of pi.)
391
+ // Never mark an integer as being an approximation
392
+ // of pi.
393
+ let approximatesPi = false;
394
+ const number = parseFloat(text);
395
+ if (!isNaN(number) && number !== parseInt(text)) {
396
+ const piMult = Math.PI / 12;
397
+ const roundedNumber =
398
+ piMult * Math.round(number / piMult);
399
+ if (Math.abs(number - roundedNumber) < 0.01) {
400
+ approximatesPi = true;
401
+ }
402
+ } else if (text.match(/\/\s*7/)) {
403
+ approximatesPi = true;
404
+ }
405
+ if (approximatesPi) {
406
+ _.each(possibilities, function (possibility) {
407
+ possibility.piApprox = true;
408
+ });
409
+ }
410
+ return possibilities;
411
+ }
412
+
413
+ let multiplier = Math.PI;
414
+ if (text.match(/\\?tau|t|\u03c4/)) {
415
+ multiplier = Math.PI * 2;
416
+ }
417
+
418
+ // We're taking an early stand along side xkcd in the
419
+ // inevitable ti vs. pau debate... http://xkcd.com/1292
420
+ if (text.match(/pau/)) {
421
+ multiplier = Math.PI * 1.5;
422
+ }
423
+
424
+ $.each(possibilities, function (ix, possibility) {
425
+ possibility.value *= multiplier;
426
+ });
427
+ return possibilities;
428
+ },
429
+
430
+ // Converts '' to 1 and '-' to -1 so you can write "[___] x"
431
+ // and accept sane things
432
+ coefficient: function (text) {
433
+ let possibilities = [];
434
+
435
+ // Replace unicode minus sign with hyphen
436
+ text = text.replace(/\u2212/, "-");
437
+
438
+ if (text === "") {
439
+ possibilities = [{value: 1, exact: true}];
440
+ } else if (text === "-") {
441
+ possibilities = [{value: -1, exact: true}];
442
+ }
443
+ return possibilities;
444
+ },
445
+
446
+ // simple log(c) form
447
+ log: function (text) {
448
+ let match;
449
+ let possibilities = [];
450
+
451
+ // Replace unicode minus sign with hyphen
452
+ text = text.replace(/\u2212/, "-");
453
+ text = text.replace(/[ \(\)]/g, "");
454
+
455
+ if ((match = text.match(/^log\s*(\S+)\s*$/i))) {
456
+ possibilities = forms.decimal(match[1]);
457
+ } else if (text === "0") {
458
+ possibilities = [{value: 0, exact: true}];
459
+ }
460
+ return possibilities;
461
+ },
462
+
463
+ // Numbers with percent signs
464
+ percent: function (text) {
465
+ text = $.trim(text);
466
+ // store whether or not there is a percent sign
467
+ let hasPercentSign = false;
468
+
469
+ if (text.indexOf("%") === text.length - 1) {
470
+ text = $.trim(text.substring(0, text.length - 1));
471
+ hasPercentSign = true;
472
+ }
473
+
474
+ const transformed = forms.decimal(text);
475
+ $.each(transformed, function (ix, t) {
476
+ t.exact = hasPercentSign;
477
+ t.value = t.value / 100;
478
+ });
479
+ return transformed;
480
+ },
481
+
482
+ // Mixed numbers, like 1 3/4
483
+ mixed: function (text) {
484
+ const match = text
485
+ // Replace unicode minus sign with hyphen
486
+ .replace(/\u2212/, "-")
487
+ // Remove space after +, -
488
+ .replace(/([+-])\s+/g, "$1")
489
+ // Extract integer, numerator and denominator
490
+ .match(/^([+-]?)(\d+)\s+(\d+)\s*\/\s*(\d+)$/);
491
+
492
+ if (match) {
493
+ const sign = parseFloat(match[1] + "1");
494
+ const integ = parseFloat(match[2]);
495
+ const num = parseFloat(match[3]);
496
+ const denom = parseFloat(match[4]);
497
+ const simplified =
498
+ num < denom && KhanMath.getGCD(num, denom) === 1;
499
+
500
+ return [
501
+ {
502
+ value: sign * (integ + num / denom),
503
+ exact: simplified,
504
+ },
505
+ ];
506
+ }
507
+
508
+ return [];
509
+ },
510
+
511
+ // Decimal numbers -- compare entered text rounded to
512
+ // 'precision' Reciprical of the precision against the correct
513
+ // answer. We round to 1/1e10 by default, which is healthily
514
+ // less than machine epsilon but should be more than any real
515
+ // decimal answer would use. (The 'integer' answer type uses
516
+ // precision == 1.)
517
+ decimal: function (text: string, precision: number = 1e10) {
518
+ const normal = function (text) {
519
+ text = $.trim(text);
520
+
521
+ const match = text
522
+ // Replace unicode minus sign with hyphen
523
+ .replace(/\u2212/, "-")
524
+ // Remove space after +, -
525
+ .replace(/([+-])\s+/g, "$1")
526
+ // Extract integer, numerator and denominator. If
527
+ // commas or spaces are used, they must be in the
528
+ // "correct" places
529
+ .match(
530
+ /^([+-]?(?:\d{1,3}(?:[, ]?\d{3})*\.?|\d{0,3}(?:[, ]?\d{3})*\.(?:\d{3}[, ]?)*\d{1,3}))$/,
531
+ );
532
+
533
+ // You can't start a number with `0,`, to prevent us
534
+ // interpeting '0.342' as correct for '342'
535
+ const badLeadingZero = text.match(/^0[0,]*,/);
536
+
537
+ if (match && !badLeadingZero) {
538
+ let x = parseFloat(match[1].replace(/[, ]/g, ""));
539
+
540
+ if (options.inexact === undefined) {
541
+ x = Math.round(x * precision) / precision;
542
+ }
543
+
544
+ return x;
545
+ }
546
+ };
547
+
548
+ const commas = function (text) {
549
+ text = text.replace(/([\.,])/g, function (_, c) {
550
+ return c === "." ? "," : ".";
551
+ });
552
+ return normal(text);
553
+ };
554
+
555
+ return [
556
+ {value: normal(text), exact: true},
557
+ {value: commas(text), exact: true},
558
+ ];
559
+ },
560
+ };
561
+
562
+ // validator function
563
+ return function (guess: Guess): Score {
564
+ // The fallback variable is used in place of the answer, if no
565
+ // answer is provided (i.e. the field is left blank)
566
+ const fallback =
567
+ options.fallback != null ? "" + options.fallback : "";
568
+
569
+ guess = $.trim(guess) || fallback;
570
+
571
+ const score: Score = {
572
+ empty: guess === "",
573
+ correct: false,
574
+ message: (null: ?string),
575
+ guess: guess,
576
+ };
577
+
578
+ // iterate over all the acceptable forms, and if one of the
579
+ // answers is correct, return true
580
+ $.each(acceptableForms, function (i, form) {
581
+ const transformed = forms[form](guess);
582
+ for (let j = 0, l = transformed.length; j < l; j++) {
583
+ const val = transformed[j].value;
584
+ const exact = transformed[j].exact;
585
+ const piApprox = transformed[j].piApprox;
586
+ // If a string was returned, and it exactly matches,
587
+ // return true
588
+ if (predicate(val, options.maxError)) {
589
+ // If the exact correct number was returned,
590
+ // return true
591
+ if (exact || options.simplify === "optional") {
592
+ score.correct = true;
593
+ score.message = options.message || null;
594
+ // If the answer is correct, don't say it's
595
+ // empty. This happens, for example, with the
596
+ // coefficient type where guess === "" but is
597
+ // interpreted as "1" which is correct.
598
+ score.empty = false;
599
+ } else if (form === "percent") {
600
+ // Otherwise, an error was returned
601
+ score.empty = true;
602
+ score.message = errors.MISSING_PERCENT_ERROR;
603
+ } else {
604
+ if (options.simplify !== "enforced") {
605
+ score.empty = true;
606
+ }
607
+ score.message =
608
+ errors.NEEDS_TO_BE_SIMPLFIED_ERROR;
609
+ }
610
+ // The return false below stops the looping of the
611
+ // callback since predicate check succeeded.
612
+ // No more forms to look to verify the user guess.
613
+ return false;
614
+ }
615
+ if (piApprox && predicate(val, Math.abs(val * 0.001))) {
616
+ score.empty = true;
617
+ score.message = errors.APPROXIMATED_PI_ERROR;
618
+ }
619
+ }
620
+ });
621
+
622
+ if (score.correct === false) {
623
+ let interpretedGuess = false;
624
+ _.each(forms, function (form) {
625
+ const anyAreNaN = _.any(form(guess), function (t) {
626
+ return t.value != null && !_.isNaN(t.value);
627
+ });
628
+
629
+ if (anyAreNaN) {
630
+ interpretedGuess = true;
631
+ }
632
+ });
633
+ if (!interpretedGuess) {
634
+ score.empty = true;
635
+ score.message = errors.EXTRA_SYMBOLS_ERROR;
636
+ return score;
637
+ }
638
+ }
639
+
640
+ return score;
641
+ };
642
+ },
643
+ },
644
+
645
+ /*
646
+ * number answer type
647
+ *
648
+ * wraps the predicate answer type to performs simple number-based checking
649
+ * of a solution
650
+ */
651
+ number: {
652
+ convertToPredicate: function (
653
+ correctAnswer: string,
654
+ options: $FlowFixMe,
655
+ ): Predicate {
656
+ // TODO(alpert): Don't think this $.trim is necessary
657
+ const correctFloat = parseFloat($.trim(correctAnswer));
658
+
659
+ return [
660
+ function (guess, maxError) {
661
+ return Math.abs(guess - correctFloat) < maxError;
662
+ },
663
+ $.extend({}, options, {type: "predicate"}),
664
+ ];
665
+ },
666
+ createValidatorFunctional: function (
667
+ correctAnswer: string,
668
+ options: $FlowFixMe,
669
+ ): (Guess) => Score {
670
+ return KhanAnswerTypes.predicate.createValidatorFunctional(
671
+ ...KhanAnswerTypes.number.convertToPredicate(
672
+ correctAnswer,
673
+ options,
674
+ ),
675
+ );
676
+ },
677
+ },
678
+
679
+ /*
680
+ * The expression answer type parses a given expression or equation
681
+ * and semantically compares it to the solution. In addition, instant
682
+ * feedback is provided by rendering the last answer that fully parsed.
683
+ *
684
+ * Parsing options:
685
+ * functions (e.g. data-functions="f g h")
686
+ * A space or comma separated list of single-letter variables that
687
+ * should be interpreted as functions. Case sensitive. "e" and "i"
688
+ * are reserved.
689
+ *
690
+ * no functions specified: f(x+y) == fx + fy
691
+ * with "f" as a function: f(x+y) != fx + fy
692
+ *
693
+ * Comparison options:
694
+ * same-form (e.g. data-same-form)
695
+ * If present, the answer must match the solution's structure in
696
+ * addition to evaluating the same. Commutativity and excess negation
697
+ * are ignored, but all other changes will trigger a rejection. Useful
698
+ * for requiring a particular form of an equation, or if the answer
699
+ * must be factored.
700
+ *
701
+ * example question: Factor x^2 + x - 2
702
+ * example solution: (x-1)(x+2)
703
+ * accepted answers: (x-1)(x+2), (x+2)(x-1), ---(-x-2)(-1+x), etc.
704
+ * rejected answers: x^2+x-2, x*x+x-2, x(x+1)-2, (x-1)(x+2)^1, etc.
705
+ * rejection message: Your answer is not in the correct form
706
+ *
707
+ * simplify (e.g. data-simplify)
708
+ * If present, the answer must be fully expanded and simplified. Use
709
+ * carefully - simplification is hard and there may be bugs, or you
710
+ * might not agree on the definition of "simplified" used. You will
711
+ * get an error if the provided solution is not itself fully expanded
712
+ * and simplified.
713
+ *
714
+ * example question: Simplify ((n*x^5)^5) / (n^(-2)*x^2)^-3
715
+ * example solution: x^31 / n
716
+ * accepted answers: x^31 / n, x^31 / n^1, x^31 * n^(-1), etc.
717
+ * rejected answers: (x^25 * n^5) / (x^(-6) * n^6), etc.
718
+ * rejection message: Your answer is not fully expanded and simplified
719
+ *
720
+ * Rendering options:
721
+ * times (e.g. data-times)
722
+ * If present, explicit multiplication (such as between numbers) will
723
+ * be rendered with a cross/x symbol (TeX: \times) instead of the usual
724
+ * center dot (TeX: \cdot).
725
+ *
726
+ * normal rendering: 2 * 3^x -> 2 \cdot 3^{x}
727
+ * but with "times": 2 * 3^x -> 2 \times 3^{x}
728
+ */
729
+ expression: {
730
+ parseSolution: function (
731
+ solutionString: string,
732
+ options: $FlowFixMe,
733
+ ): $FlowFixMe {
734
+ let solution = KAS.parse(solutionString, options);
735
+ if (!solution.parsed) {
736
+ throw new PerseusError(
737
+ "The provided solution (" +
738
+ solutionString +
739
+ ") didn't parse.",
740
+ Errors.InvalidInput,
741
+ );
742
+ } else if (options.simplified && !solution.expr.isSimplified()) {
743
+ throw new PerseusError(
744
+ "The provided solution (" +
745
+ solutionString +
746
+ ") isn't fully expanded and simplified.",
747
+ Errors.InvalidInput,
748
+ );
749
+ } else {
750
+ solution = solution.expr;
751
+ }
752
+ return solution;
753
+ },
754
+
755
+ createValidatorFunctional: function (
756
+ solution: $FlowFixMe,
757
+ options: $FlowFixMe,
758
+ ): (Guess) => Score {
759
+ return function (guess: Guess): Score {
760
+ const score = {
761
+ empty: false,
762
+ correct: false,
763
+ message: (null: ?string),
764
+ guess: guess,
765
+ // Setting `ungraded` to true indicates that if the
766
+ // guess doesn't match any of the solutions, the guess
767
+ // shouldn't be marked as incorrect; instead, `message`
768
+ // should be shown to the user. This is different from
769
+ // setting `empty` to true, since the behavior of `empty`
770
+ // is that `message` only will be shown if the guess is
771
+ // graded as empty for every solution.
772
+ ungraded: false,
773
+ };
774
+
775
+ // Don't bother parsing an empty input
776
+ if (!guess) {
777
+ score.empty = true;
778
+ return score;
779
+ }
780
+
781
+ const answer = KAS.parse(guess, options);
782
+
783
+ // An unsuccessful parse doesn't count as wrong
784
+ if (!answer.parsed) {
785
+ score.empty = true;
786
+ return score;
787
+ }
788
+
789
+ // Solution will need to be parsed again if we're creating
790
+ // this from a multiple question type
791
+ if (typeof solution === "string") {
792
+ solution = KhanAnswerTypes.expression.parseSolution(
793
+ solution,
794
+ options,
795
+ );
796
+ }
797
+
798
+ const result = KAS.compare(answer.expr, solution, options);
799
+
800
+ if (result.equal) {
801
+ // Correct answer
802
+ score.correct = true;
803
+ } else if (
804
+ result.wrongVariableNames ||
805
+ result.wrongVariableCase
806
+ ) {
807
+ // We don't want to give people an error for getting the
808
+ // variable names or the variable case wrong.
809
+ // TODO(aasmund): This should ideally have been handled
810
+ // under the `result.message` condition, but the
811
+ // KAS messages currently aren't translatable.
812
+ score.ungraded = true;
813
+ score.message = result.wrongVariableCase
814
+ ? errors.WRONG_CASE_ERROR
815
+ : errors.WRONG_LETTER_ERROR;
816
+ // Don't tell the use they're "almost there" in this case, that may not be true and isn't helpful.
817
+ score.suppressAlmostThere = true;
818
+ } else if (result.message) {
819
+ // Nearly correct answer
820
+ // TODO(aasmund): This message also isn't translatable;
821
+ // need to fix that in KAS
822
+ score.message = result.message;
823
+ } else {
824
+ // Replace x with * and see if it would have been correct
825
+ // TODO(aasmund): I think this branch is effectively dead,
826
+ // because the replacement will only work in situations
827
+ // where the variables are wrong (except if the variable
828
+ // is x, in which case the replacement won't work either),
829
+ // which is handled by another branch. When we implement a
830
+ // more sophisticated variable check, revive this or
831
+ // remove it completely if it will never come into play.
832
+ const answerX = KAS.parse(
833
+ guess.replace(/[xX]/g, "*"),
834
+ options,
835
+ );
836
+ if (answerX.parsed) {
837
+ const resultX = KAS.compare(
838
+ answerX.expr,
839
+ solution,
840
+ options,
841
+ );
842
+ if (resultX.equal) {
843
+ score.ungraded = true;
844
+ score.message = errors.MULTIPLICATION_SIGN_ERROR;
845
+ } else if (resultX.message) {
846
+ // TODO(aasmund): I18nize `score.message`
847
+ score.message =
848
+ resultX.message +
849
+ " Also, I'm a computer. I only understand " +
850
+ "multiplication if you use an " +
851
+ "asterisk (*) as the multiplication " +
852
+ "sign.";
853
+ }
854
+ }
855
+ }
856
+ return score;
857
+ };
858
+ },
859
+ },
860
+ };
861
+
862
+ export default KhanAnswerTypes;