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