@khanacademy/perseus-editor 0.0.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/.babelrc.js +1 -1
  2. package/CHANGELOG.md +26 -0
  3. package/dist/es/index.css +1020 -0
  4. package/dist/es/index.js +9 -1
  5. package/dist/es/index.js.map +1 -1
  6. package/dist/index.css +1020 -0
  7. package/dist/index.js +9 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/strings.js +6 -1
  10. package/package.json +16 -4
  11. package/src/README.md +7 -0
  12. package/src/__stories__/editor.stories.js +33 -0
  13. package/src/__stories__/section-control-button.stories.jsx +27 -0
  14. package/src/__testdata__/input-number_testdata.js +28 -0
  15. package/src/__tests__/editor_test.jsx +123 -0
  16. package/src/__tests__/i18n_test.jsx +585 -0
  17. package/src/__tests__/traversal_test.jsx +378 -0
  18. package/src/all-editors.js +85 -0
  19. package/src/all-editors_test.js +17 -0
  20. package/src/all-widgets.js +10 -0
  21. package/src/article-editor.jsx +437 -0
  22. package/src/components/__stories__/blur-input.stories.jsx +24 -0
  23. package/src/components/blur-input.jsx +62 -0
  24. package/src/components/checkbox.jsx +194 -0
  25. package/src/components/device-framer.jsx +91 -0
  26. package/src/components/drag-target.jsx +88 -0
  27. package/src/components/dropdown-option.jsx +318 -0
  28. package/src/components/form-wrapped-text-field.jsx +248 -0
  29. package/src/components/graph-settings.jsx +631 -0
  30. package/src/components/hover-behavior.jsx +178 -0
  31. package/src/components/json-editor.jsx +121 -0
  32. package/src/components/link.jsx +173 -0
  33. package/src/components/simple-button.jsx +86 -0
  34. package/src/components/sortable.jsx +186 -0
  35. package/src/components/util.js +39 -0
  36. package/src/components/viewport-resizer.jsx +73 -0
  37. package/src/diffs/__stories__/perseus-diff-wrapper.jsx +8 -0
  38. package/src/diffs/__stories__/structured-item-diff.stories.jsx +855 -0
  39. package/src/diffs/__stories__/tags-diff.stories.jsx +38 -0
  40. package/src/diffs/__stories__/text-diff.stories.jsx +37 -0
  41. package/src/diffs/__tests__/split-diff_test.js +34 -0
  42. package/src/diffs/__tests__/string-array-diff_test.js +74 -0
  43. package/src/diffs/__tests__/structured-item-diff_test.js +432 -0
  44. package/src/diffs/__tests__/widget-diff-performer_test.js +44 -0
  45. package/src/diffs/article-diff.jsx +70 -0
  46. package/src/diffs/item-diff.jsx +77 -0
  47. package/src/diffs/renderer-diff.jsx +143 -0
  48. package/src/diffs/split-diff.jsx +30 -0
  49. package/src/diffs/string-array-diff.jsx +75 -0
  50. package/src/diffs/structured-item-diff.jsx +269 -0
  51. package/src/diffs/tags-diff.jsx +50 -0
  52. package/src/diffs/text-diff.jsx +194 -0
  53. package/src/diffs/widget-diff-performer.jsx +74 -0
  54. package/src/diffs/widget-diff.jsx +291 -0
  55. package/src/editor-page.jsx +351 -0
  56. package/src/editor.jsx +1364 -0
  57. package/src/hint-editor.jsx +447 -0
  58. package/src/i18n.jsx +139 -0
  59. package/src/iframe-content-renderer.jsx +201 -0
  60. package/src/index.js +27 -8
  61. package/src/item-editor.jsx +159 -0
  62. package/src/item-extras-editor.jsx +150 -0
  63. package/src/katex-error-view.jsx +87 -0
  64. package/src/multirenderer-editor.jsx +1074 -0
  65. package/src/section-control-button.jsx +53 -0
  66. package/src/stateful-article-editor.jsx +176 -0
  67. package/src/stateful-editor-page.jsx +90 -0
  68. package/src/styles/perseus-editor.less +1339 -0
  69. package/src/testing-widgets.js +12 -0
  70. package/src/util/register-all-widgets-and-editors-for-testing.js +15 -0
  71. package/src/util/register-all-widgets-and-editors-for-testing_test.js +18 -0
  72. package/src/widgets/__stories__/label-image-editor.stories.jsx +97 -0
  73. package/src/widgets/categorizer-editor.jsx +103 -0
  74. package/src/widgets/cs-program-editor.jsx +258 -0
  75. package/src/widgets/definition-editor.jsx +75 -0
  76. package/src/widgets/dropdown-editor.jsx +201 -0
  77. package/src/widgets/example-graphie-widget-editor.jsx +70 -0
  78. package/src/widgets/example-graphie-widget.jsx +175 -0
  79. package/src/widgets/example-widget-editor.jsx +60 -0
  80. package/src/widgets/example-widget.jsx +153 -0
  81. package/src/widgets/explanation-editor.jsx +89 -0
  82. package/src/widgets/expression-editor.jsx +560 -0
  83. package/src/widgets/graded-group-editor.jsx +162 -0
  84. package/src/widgets/graded-group-set-editor.jsx +101 -0
  85. package/src/widgets/grapher-editor.jsx +154 -0
  86. package/src/widgets/group-editor.jsx +83 -0
  87. package/src/widgets/iframe-editor.jsx +200 -0
  88. package/src/widgets/image-editor.jsx +326 -0
  89. package/src/widgets/input-number-editor.jsx +269 -0
  90. package/src/widgets/interaction/constraint-editor.jsx +147 -0
  91. package/src/widgets/interaction-editor.jsx +1389 -0
  92. package/src/widgets/interactive-graph-editor.jsx +322 -0
  93. package/src/widgets/label-image/__stories__/answer-choices.stories.jsx +81 -0
  94. package/src/widgets/label-image/__stories__/behavior.stories.jsx +32 -0
  95. package/src/widgets/label-image/__stories__/marker.stories.jsx +76 -0
  96. package/src/widgets/label-image/__stories__/question-markers.stories.jsx +106 -0
  97. package/src/widgets/label-image/__stories__/select-image.stories.jsx +60 -0
  98. package/src/widgets/label-image/answer-choices.jsx +231 -0
  99. package/src/widgets/label-image/behavior.jsx +92 -0
  100. package/src/widgets/label-image/marker.jsx +292 -0
  101. package/src/widgets/label-image/question-markers.jsx +181 -0
  102. package/src/widgets/label-image/select-image.jsx +86 -0
  103. package/src/widgets/label-image-editor.jsx +257 -0
  104. package/src/widgets/lights-puzzle-editor.jsx +267 -0
  105. package/src/widgets/matcher-editor.jsx +147 -0
  106. package/src/widgets/matrix-editor.jsx +155 -0
  107. package/src/widgets/measurer-editor.jsx +245 -0
  108. package/src/widgets/molecule-editor.jsx +63 -0
  109. package/src/widgets/number-line-editor.jsx +498 -0
  110. package/src/widgets/numeric-input-editor.jsx +554 -0
  111. package/src/widgets/orderer-editor.jsx +166 -0
  112. package/src/widgets/passage-editor.jsx +115 -0
  113. package/src/widgets/passage-ref-editor.jsx +77 -0
  114. package/src/widgets/passage-ref-target-editor.jsx +47 -0
  115. package/src/widgets/plotter-editor.jsx +537 -0
  116. package/src/widgets/radio/editor.jsx +398 -0
  117. package/src/widgets/reaction-diagram-editor.jsx +127 -0
  118. package/src/widgets/sequence-editor.jsx +205 -0
  119. package/src/widgets/simple-markdown-tester-editor.jsx +84 -0
  120. package/src/widgets/simple-markdown-tester.jsx +91 -0
  121. package/src/widgets/simulator-editor.jsx +144 -0
  122. package/src/widgets/sorter-editor.jsx +100 -0
  123. package/src/widgets/table-editor.jsx +164 -0
  124. package/src/widgets/transformer-editor.jsx +1006 -0
  125. package/src/widgets/unit-editor.jsx +293 -0
  126. package/src/widgets/video-editor.jsx +74 -0
  127. package/src/__tests__/index.test.js +0 -15
@@ -0,0 +1,38 @@
1
+ // @flow
2
+
3
+ import * as React from "react";
4
+
5
+ import TagsDiff from "../tags-diff.jsx";
6
+
7
+ import Wrapper from "./perseus-diff-wrapper.jsx";
8
+
9
+ type StoryArgs = {||};
10
+
11
+ type Story = {|
12
+ title: string,
13
+ decorators: $ReadOnlyArray<
14
+ (StoryComponent: typeof React.Component) => React.Node,
15
+ >,
16
+ |};
17
+
18
+ export default ({
19
+ title: "Perseus/Editor/Diffs/Tags Diff",
20
+ decorators: [
21
+ (StoryComponent) => (
22
+ <Wrapper>
23
+ <StoryComponent />
24
+ </Wrapper>
25
+ ),
26
+ ],
27
+ }: Story);
28
+
29
+ export const Example = (args: StoryArgs): React.Node => {
30
+ return (
31
+ <TagsDiff
32
+ title="tags"
33
+ beforeOnly={["Math", "Biology", "History"]}
34
+ afterOnly={["World War I"]}
35
+ intersection={["Physics", "Chemistry"]}
36
+ />
37
+ );
38
+ };
@@ -0,0 +1,37 @@
1
+ // @flow
2
+
3
+ import * as React from "react";
4
+
5
+ import TextDiff from "../text-diff.jsx";
6
+
7
+ import Wrapper from "./perseus-diff-wrapper.jsx";
8
+
9
+ type StoryArgs = {||};
10
+
11
+ type Story = {|
12
+ title: string,
13
+ decorators: $ReadOnlyArray<
14
+ (StoryComponent: typeof React.Component) => React.Node,
15
+ >,
16
+ |};
17
+
18
+ export default ({
19
+ title: "Perseus/Editor/Diffs/Text Diff",
20
+ decorators: [
21
+ (StoryComponent) => (
22
+ <Wrapper>
23
+ <StoryComponent />
24
+ </Wrapper>
25
+ ),
26
+ ],
27
+ }: Story);
28
+
29
+ export const Example = (args: StoryArgs): React.Node => {
30
+ return (
31
+ <TextDiff
32
+ title="A day in the life of a text diff"
33
+ before="🥱 Hello world!"
34
+ after="😴 Goodbye world!"
35
+ />
36
+ );
37
+ };
@@ -0,0 +1,34 @@
1
+ // @flow
2
+ import splitDiff from "../split-diff.jsx";
3
+
4
+ describe("split diff", function () {
5
+ it("splits an empty diff", function () {
6
+ expect(splitDiff([])).toEqual([]);
7
+ });
8
+
9
+ it("does not split a one line string", function () {
10
+ expect(splitDiff([{value: "hello"}])).toEqual([[{value: "hello"}]]);
11
+ });
12
+
13
+ it("splits a multiline string into two parts", function () {
14
+ expect(splitDiff([{value: "he\nllo"}])).toEqual([
15
+ [{value: "he"}],
16
+ [{value: "llo"}],
17
+ ]);
18
+ });
19
+
20
+ it("keeps things on the same line when splitting a changed value", () => {
21
+ expect(
22
+ splitDiff([{value: "hello"}, {value: "goodbye", removed: true}]),
23
+ ).toEqual([[{value: "hello"}, {value: "goodbye", removed: true}]]);
24
+ });
25
+
26
+ it("splits a string with a change and a newline", function () {
27
+ expect(
28
+ splitDiff([{value: "hello"}, {value: "good\nbye", removed: true}]),
29
+ ).toEqual([
30
+ [{value: "hello"}, {value: "good", removed: true}],
31
+ [{value: "bye", removed: true}],
32
+ ]);
33
+ });
34
+ });
@@ -0,0 +1,74 @@
1
+ // @flow
2
+ import stringArrayDiff from "../string-array-diff.jsx";
3
+
4
+ describe("string array diff", function () {
5
+ it("diffs an empty diff", function () {
6
+ expect(stringArrayDiff([], [])).toEqual({
7
+ before: [],
8
+ after: [],
9
+ });
10
+ });
11
+
12
+ it("diffs the same values", function () {
13
+ expect(stringArrayDiff([1], [1])).toEqual({
14
+ before: [
15
+ {
16
+ status: "unchanged",
17
+ value: 1,
18
+ },
19
+ ],
20
+ after: [
21
+ {
22
+ status: "unchanged",
23
+ value: 1,
24
+ },
25
+ ],
26
+ });
27
+ });
28
+
29
+ it("diffs an added value", function () {
30
+ expect(stringArrayDiff([1], [1, 2])).toEqual({
31
+ before: [
32
+ {
33
+ status: "unchanged",
34
+ value: 1,
35
+ },
36
+ ],
37
+ after: [
38
+ {
39
+ status: "unchanged",
40
+ value: 1,
41
+ },
42
+ {
43
+ status: "added",
44
+ value: 2,
45
+ },
46
+ ],
47
+ });
48
+ });
49
+
50
+ it("diffs a removed value", function () {
51
+ expect(stringArrayDiff([1, 2, 3], [1])).toEqual({
52
+ before: [
53
+ {
54
+ status: "unchanged",
55
+ value: 1,
56
+ },
57
+ {
58
+ status: "removed",
59
+ value: 2,
60
+ },
61
+ {
62
+ status: "removed",
63
+ value: 3,
64
+ },
65
+ ],
66
+ after: [
67
+ {
68
+ status: "unchanged",
69
+ value: 1,
70
+ },
71
+ ],
72
+ });
73
+ });
74
+ });
@@ -0,0 +1,432 @@
1
+ // @flow
2
+ import {buildEmptyItemTreeForShape, shapes} from "@khanacademy/perseus";
3
+
4
+ import StructuredItemDiff from "../structured-item-diff.jsx";
5
+
6
+ const gtpPassageShape = shapes.shape({
7
+ directions: shapes.content,
8
+ passage: shapes.content,
9
+ overview: shapes.content,
10
+ hints: shapes.hints,
11
+
12
+ questions: shapes.arrayOf(
13
+ shapes.shape({
14
+ tags: shapes.tags,
15
+ question: shapes.content,
16
+ keepInMind: shapes.content,
17
+ overview: shapes.content,
18
+ hints: shapes.hints,
19
+ }),
20
+ ),
21
+ });
22
+
23
+ const gtpSingleQuestionShape = shapes.shape({
24
+ blurb: shapes.content,
25
+ question: shapes.content,
26
+ hints: shapes.hints,
27
+ });
28
+
29
+ const emptyContent = buildEmptyItemTreeForShape(shapes.content);
30
+ const emptyHint = buildEmptyItemTreeForShape(shapes.hint);
31
+
32
+ describe("StructuredItemDiff", function () {
33
+ /**
34
+ * Testing adding all empty content items,
35
+ * and then testing removing by reversing the order.
36
+ */
37
+ it("test adding empty items, removing empty items", function () {
38
+ const beforeList = [
39
+ [emptyContent, ["directions"]],
40
+ [emptyContent, ["passage"]],
41
+ [emptyContent, ["overview"]],
42
+ ];
43
+
44
+ const afterList = [
45
+ [emptyContent, ["directions"]],
46
+ [emptyContent, ["passage"]],
47
+ [emptyContent, ["overview"]],
48
+ [emptyHint, ["hints", 0]],
49
+ [emptyHint, ["hints", 1]],
50
+ [emptyContent, ["questions", 0, "tags"]],
51
+ [emptyContent, ["questions", 0, "question"]],
52
+ [emptyContent, ["questions", 0, "keepInMind"]],
53
+ [emptyContent, ["questions", 0, "overview"]],
54
+ [emptyContent, ["questions", 1, "tags"]],
55
+ [emptyContent, ["questions", 1, "question"]],
56
+ [emptyContent, ["questions", 1, "keepInMind"]],
57
+ [emptyContent, ["questions", 1, "overview"]],
58
+ [emptyHint, ["questions", 1, "hints", 0]],
59
+ [emptyHint, ["questions", 1, "hints", 1]],
60
+ ];
61
+
62
+ const addingResult = [];
63
+ StructuredItemDiff.generateCompletePathsList(
64
+ beforeList.slice(),
65
+ afterList.slice(),
66
+ addingResult,
67
+ gtpPassageShape,
68
+ [],
69
+ );
70
+
71
+ const removingResult = [];
72
+ StructuredItemDiff.generateCompletePathsList(
73
+ afterList,
74
+ beforeList,
75
+ removingResult,
76
+ gtpPassageShape,
77
+ [],
78
+ );
79
+
80
+ expect(addingResult).toEqual([
81
+ ["directions"],
82
+ ["passage"],
83
+ ["overview"],
84
+ ["hints", 0],
85
+ ["hints", 1],
86
+ ["questions", 0, "tags"],
87
+ ["questions", 0, "question"],
88
+ ["questions", 0, "keepInMind"],
89
+ ["questions", 0, "overview"],
90
+ ["questions", 1, "tags"],
91
+ ["questions", 1, "question"],
92
+ ["questions", 1, "keepInMind"],
93
+ ["questions", 1, "overview"],
94
+ ["questions", 1, "hints", 0],
95
+ ["questions", 1, "hints", 1],
96
+ ]);
97
+
98
+ expect(removingResult).toEqual([
99
+ ["directions"],
100
+ ["passage"],
101
+ ["overview"],
102
+ ["hints", 0],
103
+ ["hints", 1],
104
+ ["questions", 0, "tags"],
105
+ ["questions", 0, "question"],
106
+ ["questions", 0, "keepInMind"],
107
+ ["questions", 0, "overview"],
108
+ ["questions", 1, "tags"],
109
+ ["questions", 1, "question"],
110
+ ["questions", 1, "keepInMind"],
111
+ ["questions", 1, "overview"],
112
+ ["questions", 1, "hints", 0],
113
+ ["questions", 1, "hints", 1],
114
+ ]);
115
+ });
116
+
117
+ /**
118
+ * Testing editing content of items,
119
+ * including top level items, as well as hints,
120
+ * and items within questions.
121
+ */
122
+ it("test editing items", function () {
123
+ const passage = buildEmptyItemTreeForShape(shapes.content);
124
+ // $FlowFixMe[prop-missing]
125
+ passage.content = "passage";
126
+ const firstQuestion = buildEmptyItemTreeForShape(shapes.content);
127
+ // $FlowFixMe[prop-missing]
128
+ firstQuestion.content = "question 1";
129
+ const firstHint = buildEmptyItemTreeForShape(shapes.hint);
130
+ // $FlowFixMe[prop-missing]
131
+ firstHint.content = "hint 1";
132
+ const secondQuestionFirstHint = buildEmptyItemTreeForShape(shapes.hint);
133
+ // $FlowFixMe[prop-missing]
134
+ secondQuestionFirstHint.content = "question 1 hint 1";
135
+ const beforeList = [
136
+ [emptyContent, ["directions"]],
137
+ [passage, ["passage"]],
138
+ [emptyContent, ["overview"]],
139
+ [firstHint, ["hints", 0]],
140
+ [emptyHint, ["hints", 1]],
141
+ [emptyContent, ["questions", 0, "tags"]],
142
+ [firstQuestion, ["questions", 0, "question"]],
143
+ [emptyContent, ["questions", 0, "keepInMind"]],
144
+ [emptyContent, ["questions", 0, "overview"]],
145
+ [emptyContent, ["questions", 1, "tags"]],
146
+ [emptyContent, ["questions", 1, "question"]],
147
+ [emptyContent, ["questions", 1, "keepInMind"]],
148
+ [emptyContent, ["questions", 1, "overview"]],
149
+ [secondQuestionFirstHint, ["questions", 1, "hints", 0]],
150
+ [emptyHint, ["questions", 1, "hints", 1]],
151
+ ];
152
+
153
+ const passageEdited = buildEmptyItemTreeForShape(shapes.content);
154
+ // $FlowFixMe[prop-missing]
155
+ passageEdited.content = "passage edited";
156
+ const firstQuestionEdited = buildEmptyItemTreeForShape(shapes.content);
157
+ // $FlowFixMe[prop-missing]
158
+ firstQuestionEdited.content = "question edited 1";
159
+ const firstHintEdited = buildEmptyItemTreeForShape(shapes.hint);
160
+ // $FlowFixMe[prop-missing]
161
+ firstHintEdited.content = "edited hint 1";
162
+ const secondQuestionFirstHintEdited = buildEmptyItemTreeForShape(
163
+ shapes.hint,
164
+ );
165
+ // $FlowFixMe[prop-missing]
166
+ secondQuestionFirstHintEdited.content = "question 1 hint 1 edited";
167
+ const afterList = [
168
+ [emptyContent, ["directions"]],
169
+ [passageEdited, ["passage"]],
170
+ [emptyContent, ["overview"]],
171
+ [firstHintEdited, ["hints", 0]],
172
+ [emptyHint, ["hints", 1]],
173
+ [emptyContent, ["questions", 0, "tags"]],
174
+ [firstQuestionEdited, ["questions", 0, "question"]],
175
+ [emptyContent, ["questions", 0, "keepInMind"]],
176
+ [emptyContent, ["questions", 0, "overview"]],
177
+ [emptyContent, ["questions", 1, "tags"]],
178
+ [emptyContent, ["questions", 1, "question"]],
179
+ [emptyContent, ["questions", 1, "keepInMind"]],
180
+ [emptyContent, ["questions", 1, "overview"]],
181
+ [secondQuestionFirstHintEdited, ["questions", 1, "hints", 0]],
182
+ [emptyHint, ["questions", 1, "hints", 1]],
183
+ ];
184
+
185
+ const firstResult = [];
186
+ StructuredItemDiff.generateCompletePathsList(
187
+ beforeList.slice(),
188
+ afterList.slice(),
189
+ firstResult,
190
+ gtpPassageShape,
191
+ [],
192
+ );
193
+
194
+ const secondResult = [];
195
+ StructuredItemDiff.generateCompletePathsList(
196
+ afterList,
197
+ beforeList,
198
+ secondResult,
199
+ gtpPassageShape,
200
+ [],
201
+ );
202
+
203
+ expect(firstResult).toEqual([
204
+ ["directions"],
205
+ ["passage"],
206
+ ["overview"],
207
+ ["hints", 0],
208
+ ["hints", 1],
209
+ ["questions", 0, "tags"],
210
+ ["questions", 0, "question"],
211
+ ["questions", 0, "keepInMind"],
212
+ ["questions", 0, "overview"],
213
+ ["questions", 1, "tags"],
214
+ ["questions", 1, "question"],
215
+ ["questions", 1, "keepInMind"],
216
+ ["questions", 1, "overview"],
217
+ ["questions", 1, "hints", 0],
218
+ ["questions", 1, "hints", 1],
219
+ ]);
220
+
221
+ expect(secondResult).toEqual([
222
+ ["directions"],
223
+ ["passage"],
224
+ ["overview"],
225
+ ["hints", 0],
226
+ ["hints", 1],
227
+ ["questions", 0, "tags"],
228
+ ["questions", 0, "question"],
229
+ ["questions", 0, "keepInMind"],
230
+ ["questions", 0, "overview"],
231
+ ["questions", 1, "tags"],
232
+ ["questions", 1, "question"],
233
+ ["questions", 1, "keepInMind"],
234
+ ["questions", 1, "overview"],
235
+ ["questions", 1, "hints", 0],
236
+ ["questions", 1, "hints", 1],
237
+ ]);
238
+ });
239
+
240
+ /**
241
+ * Testing these cases:
242
+ * (1) adding/removing overall hints
243
+ * (2) adding/removing question hints of a middle question
244
+ * (question is surrounded by other questions)
245
+ * (3) adding/removing question hints of the last question
246
+ * (4) adding/removing questions entirely
247
+ *
248
+ * This test is done with empty items, since we've tested for edits,
249
+ * and generateCompletePathLists() doesn't look at the contents.
250
+ */
251
+ it("testing adding and removing in special cases", function () {
252
+ const beforeList = [
253
+ [emptyContent, ["directions"]],
254
+ [emptyContent, ["passage"]],
255
+ [emptyContent, ["overview"]],
256
+ [emptyContent, ["questions", 0, "tags"]],
257
+ [emptyContent, ["questions", 0, "question"]],
258
+ [emptyContent, ["questions", 0, "keepInMind"]],
259
+ [emptyContent, ["questions", 0, "overview"]],
260
+ [emptyContent, ["questions", 1, "tags"]],
261
+ [emptyContent, ["questions", 1, "question"]],
262
+ [emptyContent, ["questions", 1, "keepInMind"]],
263
+ [emptyContent, ["questions", 1, "overview"]],
264
+ [emptyHint, ["questions", 1, "hints", 0]],
265
+ [emptyHint, ["questions", 1, "hints", 1]],
266
+ [emptyContent, ["questions", 2, "tags"]],
267
+ [emptyContent, ["questions", 2, "question"]],
268
+ [emptyContent, ["questions", 2, "keepInMind"]],
269
+ [emptyContent, ["questions", 2, "overview"]],
270
+ [emptyHint, ["questions", 2, "hints", 0]],
271
+ [emptyHint, ["questions", 2, "hints", 1]],
272
+ [emptyContent, ["questions", 3, "tags"]],
273
+ [emptyContent, ["questions", 3, "question"]],
274
+ [emptyContent, ["questions", 3, "keepInMind"]],
275
+ [emptyContent, ["questions", 3, "overview"]],
276
+ ];
277
+
278
+ const afterList = [
279
+ [emptyContent, ["directions"]],
280
+ [emptyContent, ["passage"]],
281
+ [emptyContent, ["overview"]],
282
+ [emptyHint, ["hints", 0]], // add
283
+ [emptyHint, ["hints", 1]], // add
284
+ [emptyContent, ["questions", 0, "tags"]],
285
+ [emptyContent, ["questions", 0, "question"]],
286
+ [emptyContent, ["questions", 0, "keepInMind"]],
287
+ [emptyContent, ["questions", 0, "overview"]],
288
+ [emptyHint, ["questions", 0, "hints", 0]], // add
289
+ [emptyHint, ["questions", 0, "hints", 1]], // add
290
+ [emptyContent, ["questions", 1, "tags"]],
291
+ [emptyContent, ["questions", 1, "question"]],
292
+ [emptyContent, ["questions", 1, "keepInMind"]],
293
+ [emptyContent, ["questions", 1, "overview"]],
294
+ [emptyHint, ["questions", 1, "hints", 0]],
295
+ // remove second hint of question 1
296
+ [emptyContent, ["questions", 2, "tags"]],
297
+ [emptyContent, ["questions", 2, "question"]],
298
+ [emptyContent, ["questions", 2, "keepInMind"]],
299
+ [emptyContent, ["questions", 2, "overview"]],
300
+ [emptyHint, ["questions", 2, "hints", 0]],
301
+ [emptyHint, ["questions", 2, "hints", 1]],
302
+ // removed question 2, added two hints to question 3
303
+ ];
304
+
305
+ const firstResult = [];
306
+ StructuredItemDiff.generateCompletePathsList(
307
+ beforeList.slice(),
308
+ afterList.slice(),
309
+ firstResult,
310
+ gtpPassageShape,
311
+ [],
312
+ );
313
+
314
+ const secondResult = [];
315
+ StructuredItemDiff.generateCompletePathsList(
316
+ afterList,
317
+ beforeList,
318
+ secondResult,
319
+ gtpPassageShape,
320
+ [],
321
+ );
322
+
323
+ expect(firstResult).toEqual([
324
+ ["directions"],
325
+ ["passage"],
326
+ ["overview"],
327
+ ["hints", 0],
328
+ ["hints", 1],
329
+ ["questions", 0, "tags"],
330
+ ["questions", 0, "question"],
331
+ ["questions", 0, "keepInMind"],
332
+ ["questions", 0, "overview"],
333
+ ["questions", 0, "hints", 0],
334
+ ["questions", 0, "hints", 1],
335
+ ["questions", 1, "tags"],
336
+ ["questions", 1, "question"],
337
+ ["questions", 1, "keepInMind"],
338
+ ["questions", 1, "overview"],
339
+ ["questions", 1, "hints", 0],
340
+ ["questions", 1, "hints", 1],
341
+ ["questions", 2, "tags"],
342
+ ["questions", 2, "question"],
343
+ ["questions", 2, "keepInMind"],
344
+ ["questions", 2, "overview"],
345
+ ["questions", 2, "hints", 0],
346
+ ["questions", 2, "hints", 1],
347
+ ["questions", 3, "tags"],
348
+ ["questions", 3, "question"],
349
+ ["questions", 3, "keepInMind"],
350
+ ["questions", 3, "overview"],
351
+ ]);
352
+
353
+ expect(secondResult).toEqual([
354
+ ["directions"],
355
+ ["passage"],
356
+ ["overview"],
357
+ ["hints", 0],
358
+ ["hints", 1],
359
+ ["questions", 0, "tags"],
360
+ ["questions", 0, "question"],
361
+ ["questions", 0, "keepInMind"],
362
+ ["questions", 0, "overview"],
363
+ ["questions", 0, "hints", 0],
364
+ ["questions", 0, "hints", 1],
365
+ ["questions", 1, "tags"],
366
+ ["questions", 1, "question"],
367
+ ["questions", 1, "keepInMind"],
368
+ ["questions", 1, "overview"],
369
+ ["questions", 1, "hints", 0],
370
+ ["questions", 1, "hints", 1],
371
+ ["questions", 2, "tags"],
372
+ ["questions", 2, "question"],
373
+ ["questions", 2, "keepInMind"],
374
+ ["questions", 2, "overview"],
375
+ ["questions", 2, "hints", 0],
376
+ ["questions", 2, "hints", 1],
377
+ ["questions", 3, "tags"],
378
+ ["questions", 3, "question"],
379
+ ["questions", 3, "keepInMind"],
380
+ ["questions", 3, "overview"],
381
+ ]);
382
+ });
383
+
384
+ /**
385
+ * Testing single question layout shape.
386
+ */
387
+ it("testing single question layout", function () {
388
+ const beforeList = [
389
+ [emptyContent, ["blurb"]],
390
+ [emptyContent, ["question"]],
391
+ ];
392
+
393
+ const afterList = [
394
+ [emptyContent, ["blurb"]],
395
+ [emptyContent, ["question"]],
396
+ [emptyHint, ["hints", 0]],
397
+ [emptyHint, ["hints", 1]],
398
+ ];
399
+
400
+ const firstResult = [];
401
+ StructuredItemDiff.generateCompletePathsList(
402
+ beforeList.slice(),
403
+ afterList.slice(),
404
+ firstResult,
405
+ gtpSingleQuestionShape,
406
+ [],
407
+ );
408
+
409
+ const secondResult = [];
410
+ StructuredItemDiff.generateCompletePathsList(
411
+ afterList,
412
+ beforeList,
413
+ secondResult,
414
+ gtpSingleQuestionShape,
415
+ [],
416
+ );
417
+
418
+ expect(firstResult).toEqual([
419
+ ["blurb"],
420
+ ["question"],
421
+ ["hints", 0],
422
+ ["hints", 1],
423
+ ]);
424
+
425
+ expect(secondResult).toEqual([
426
+ ["blurb"],
427
+ ["question"],
428
+ ["hints", 0],
429
+ ["hints", 1],
430
+ ]);
431
+ });
432
+ });
@@ -0,0 +1,44 @@
1
+ // @flow
2
+ import diff from "../widget-diff-performer.jsx";
3
+
4
+ describe("widget-diff-performer", function () {
5
+ it("diffs a same single value", function () {
6
+ const sameValue = diff("a", "a");
7
+ expect(sameValue.status).toEqual("unchanged");
8
+ });
9
+
10
+ it("diffs a different single value", function () {
11
+ const differentValue = diff("a", "b");
12
+ expect(differentValue.status).toEqual("changed");
13
+ });
14
+
15
+ it("diffs two identical objects", function () {
16
+ const objSame = diff({a: "b"}, {a: "b"});
17
+ expect(objSame.status).toEqual("unchanged");
18
+
19
+ expect(objSame.children).toHaveLength(1);
20
+
21
+ const child = objSame.children[0];
22
+ expect(child.status).toEqual("unchanged");
23
+ });
24
+
25
+ it("diffs two different objects", function () {
26
+ const objDiff = diff({a: "b"}, {a: "c"});
27
+ expect(objDiff.status).toEqual("changed");
28
+
29
+ expect(objDiff.children).toHaveLength(1);
30
+
31
+ const diffChild = objDiff.children[0];
32
+ expect(diffChild.status).toEqual("changed");
33
+ });
34
+
35
+ it("diffs a removed object", function () {
36
+ const removedObj = diff({a: "b"}, {});
37
+ expect(removedObj.children[0].status).toEqual("removed");
38
+ });
39
+
40
+ it("diffs an added object", function () {
41
+ const addedObj = diff({}, {a: "c"});
42
+ expect(addedObj.children[0].status).toEqual("added");
43
+ });
44
+ });