@khanacademy/math-input 12.1.1 → 13.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/components/keypad/keypad.d.ts +1 -1
  3. package/dist/components/keypad/mobile-keypad.d.ts +2 -1
  4. package/dist/components/keypad/shared-keys.d.ts +1 -1
  5. package/dist/es/index.js +30 -10
  6. package/dist/es/index.js.map +1 -1
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +30 -10
  9. package/dist/index.js.map +1 -1
  10. package/dist/types.d.ts +1 -0
  11. package/package.json +1 -1
  12. package/src/components/__tests__/integration.test.tsx +287 -0
  13. package/src/components/keypad/__tests__/__snapshots__/keypad.test.tsx.snap +84 -26
  14. package/src/components/keypad/__tests__/keypad-v2-mathquill.test.tsx +1 -1
  15. package/src/components/keypad/__tests__/keypad.test.tsx +30 -0
  16. package/src/components/keypad/keypad-button.stories.tsx +2 -1
  17. package/src/components/keypad/keypad-button.tsx +3 -0
  18. package/src/components/keypad/keypad-mathquill.stories.tsx +2 -2
  19. package/src/components/keypad/keypad-pages/fractions-page.tsx +1 -1
  20. package/src/components/keypad/keypad-pages/keypad-pages.stories.tsx +1 -1
  21. package/src/components/keypad/keypad.tsx +3 -3
  22. package/src/components/keypad/mobile-keypad.tsx +21 -4
  23. package/src/components/keypad/navigation-pad.stories.tsx +1 -1
  24. package/src/components/keypad/shared-keys.tsx +4 -4
  25. package/src/components/tabbar/tabbar.stories.tsx +4 -1
  26. package/src/{components/keypad/keypad.stories.tsx → full-keypad.stories.tsx} +6 -6
  27. package/src/{full-math-input.stories.tsx → full-mobile-input.stories.tsx} +15 -3
  28. package/src/index.ts +1 -1
  29. package/src/types.ts +1 -0
  30. package/tsconfig-build.tsbuildinfo +1 -1
package/dist/types.d.ts CHANGED
@@ -41,6 +41,7 @@ export type KeyConfig = NonManyKeyConfig | ManyKeyConfig;
41
41
  export type KeypadConfiguration = {
42
42
  keypadType: KeypadType;
43
43
  extraKeys?: ReadonlyArray<Key>;
44
+ times?: boolean;
44
45
  };
45
46
  export type KeyHandler = (key: Key) => Cursor;
46
47
  export type Cursor = {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Khan Academy's new expression editor for the mobile web.",
4
4
  "author": "Khan Academy",
5
5
  "license": "MIT",
6
- "version": "12.1.1",
6
+ "version": "13.1.0",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -0,0 +1,287 @@
1
+ import "@testing-library/jest-dom";
2
+ import {
3
+ screen,
4
+ render,
5
+ fireEvent,
6
+ within,
7
+ waitFor,
8
+ } from "@testing-library/react";
9
+ import userEvent from "@testing-library/user-event";
10
+ import MathQuill from "mathquill";
11
+ import React, {useState} from "react";
12
+
13
+ import {KeypadType} from "../../enums";
14
+ import MathInput from "../input/math-input";
15
+ import KeypadContext from "../keypad-context";
16
+ import KeypadSwitch from "../keypad-switch";
17
+
18
+ import type {KeypadAPI, KeypadConfiguration} from "../../types";
19
+
20
+ const MQ = MathQuill.getInterface(2);
21
+
22
+ const defaultConfiguration: KeypadConfiguration = {
23
+ keypadType: KeypadType.FRACTION,
24
+ };
25
+
26
+ function InputWithContext({keypadConfiguration}) {
27
+ const [value, setValue] = useState<string>("");
28
+
29
+ return (
30
+ <KeypadContext.Consumer>
31
+ {({keypadElement}) => {
32
+ return (
33
+ <MathInput
34
+ keypadElement={keypadElement as any}
35
+ value={value}
36
+ onChange={(nextValue, cb) => {
37
+ setValue(nextValue);
38
+ cb();
39
+ }}
40
+ onFocus={() => {
41
+ keypadElement?.configure(
42
+ keypadConfiguration,
43
+ () => {
44
+ keypadElement?.activate();
45
+ },
46
+ );
47
+ }}
48
+ onBlur={() => {
49
+ keypadElement?.dismiss();
50
+ }}
51
+ />
52
+ );
53
+ }}
54
+ </KeypadContext.Consumer>
55
+ );
56
+ }
57
+
58
+ function KeypadWithContext() {
59
+ return (
60
+ <KeypadContext.Consumer>
61
+ {({setKeypadElement}) => {
62
+ return (
63
+ <KeypadSwitch
64
+ onElementMounted={setKeypadElement}
65
+ onDismiss={() => {}}
66
+ onAnalyticsEvent={async () => {}}
67
+ useV2Keypad
68
+ />
69
+ );
70
+ }}
71
+ </KeypadContext.Consumer>
72
+ );
73
+ }
74
+
75
+ function ConnectedMathInput({keypadConfiguration = defaultConfiguration}) {
76
+ const [keypadElement, setKeypadElement] = useState<KeypadAPI | null>();
77
+ const [renderer, setRenderer] = useState<any>(null);
78
+ const [scrollableElement, setScrollableElement] =
79
+ useState<HTMLElement | null>();
80
+
81
+ return (
82
+ <KeypadContext.Provider
83
+ value={{
84
+ setKeypadElement,
85
+ keypadElement,
86
+ setRenderer,
87
+ renderer,
88
+ setScrollableElement,
89
+ scrollableElement,
90
+ }}
91
+ >
92
+ <InputWithContext keypadConfiguration={keypadConfiguration} />
93
+ <KeypadWithContext />
94
+ </KeypadContext.Provider>
95
+ );
96
+ }
97
+
98
+ describe("math input integration", () => {
99
+ it("renders", () => {
100
+ render(<ConnectedMathInput />);
101
+
102
+ expect(
103
+ screen.getByLabelText(
104
+ "Math input box Tap with one or two fingers to open keyboard",
105
+ ),
106
+ ).toBeInTheDocument();
107
+ });
108
+
109
+ it("doesn't show the keypad initially", () => {
110
+ render(<ConnectedMathInput />);
111
+
112
+ expect(
113
+ screen.queryByRole("button", {name: "1"}),
114
+ ).not.toBeInTheDocument();
115
+ });
116
+
117
+ it("shows the keypad after input interaction", async () => {
118
+ render(<ConnectedMathInput />);
119
+
120
+ const input = screen.getByLabelText(
121
+ "Math input box Tap with one or two fingers to open keyboard",
122
+ );
123
+
124
+ fireEvent.touchStart(input);
125
+
126
+ await waitFor(() => {
127
+ expect(screen.getByRole("button", {name: "4"})).toBeVisible();
128
+ });
129
+
130
+ expect(screen.getByRole("button", {name: "1"})).toBeVisible();
131
+ });
132
+
133
+ it("updates input when using keypad", async () => {
134
+ render(<ConnectedMathInput />);
135
+
136
+ const input = screen.getByLabelText(
137
+ "Math input box Tap with one or two fingers to open keyboard",
138
+ );
139
+
140
+ fireEvent.touchStart(input);
141
+
142
+ await waitFor(() => {
143
+ expect(screen.getByRole("button", {name: "4"})).toBeVisible();
144
+ });
145
+
146
+ userEvent.click(screen.getByRole("button", {name: "1"}));
147
+
148
+ // MathQuill is problematic,
149
+ // this is the only way I know how to test the "input"
150
+ const mathquillInput =
151
+ // eslint-disable-next-line testing-library/no-node-access
152
+ document.getElementsByClassName("mq-root-block")[0] as HTMLElement;
153
+ const span1 = within(mathquillInput).getByText("1");
154
+
155
+ expect(span1).toBeVisible();
156
+ });
157
+
158
+ it("updates input when pressing many numbers", async () => {
159
+ render(<ConnectedMathInput />);
160
+
161
+ const input = screen.getByLabelText(
162
+ "Math input box Tap with one or two fingers to open keyboard",
163
+ );
164
+
165
+ fireEvent.touchStart(input);
166
+
167
+ await waitFor(() => {
168
+ expect(screen.getByRole("button", {name: "4"})).toBeVisible();
169
+ });
170
+
171
+ const testNumbers = [8, 6, 7, 5, 3, 0, 9];
172
+ testNumbers.forEach((num) => {
173
+ userEvent.click(screen.getByRole("button", {name: `${num}`}));
174
+ });
175
+
176
+ // MathQuill is problematic,
177
+ // this is how to get the value of the input directly from MQ
178
+ const mathquillInstance =
179
+ // eslint-disable-next-line testing-library/no-node-access
180
+ MQ(document.getElementsByClassName("mq-editable-field")[0]);
181
+
182
+ expect(mathquillInstance.latex()).toBe("8675309");
183
+ });
184
+
185
+ it("can handle symbols", async () => {
186
+ render(<ConnectedMathInput />);
187
+
188
+ const input = screen.getByLabelText(
189
+ "Math input box Tap with one or two fingers to open keyboard",
190
+ );
191
+
192
+ fireEvent.touchStart(input);
193
+
194
+ await waitFor(() => {
195
+ expect(screen.getByRole("button", {name: "4"})).toBeVisible();
196
+ });
197
+
198
+ userEvent.click(screen.getByRole("button", {name: "4"}));
199
+ userEvent.click(screen.getByRole("button", {name: "2"}));
200
+ userEvent.click(screen.getByRole("button", {name: "Percent"}));
201
+
202
+ // MathQuill is problematic,
203
+ // this is how to get the value of the input directly from MQ
204
+ const mathquillInstance =
205
+ // eslint-disable-next-line testing-library/no-node-access
206
+ MQ(document.getElementsByClassName("mq-editable-field")[0]);
207
+
208
+ expect(mathquillInstance.latex()).toBe("42\\%");
209
+ });
210
+
211
+ it("handles fractions correctly in expression", async () => {
212
+ const keypadConfiguration = {
213
+ keypadType: KeypadType.EXPRESSION,
214
+ };
215
+ render(
216
+ <ConnectedMathInput keypadConfiguration={keypadConfiguration} />,
217
+ );
218
+
219
+ const input = screen.getByLabelText(
220
+ "Math input box Tap with one or two fingers to open keyboard",
221
+ );
222
+
223
+ fireEvent.touchStart(input);
224
+
225
+ await waitFor(() => {
226
+ expect(screen.getByRole("button", {name: "4"})).toBeVisible();
227
+ });
228
+
229
+ userEvent.click(screen.getByRole("button", {name: "1"}));
230
+ userEvent.click(
231
+ screen.getByRole("button", {
232
+ name: "Fraction, excluding the current expression",
233
+ }),
234
+ );
235
+ userEvent.click(screen.getByRole("button", {name: "4"}));
236
+ userEvent.click(
237
+ screen.getByRole("button", {
238
+ name: "Navigate right out of the numerator and into the denominator",
239
+ }),
240
+ );
241
+ userEvent.click(screen.getByRole("button", {name: "2"}));
242
+
243
+ // MathQuill is problematic,
244
+ // this is how to get the value of the input directly from MQ
245
+ const mathquillInstance =
246
+ // eslint-disable-next-line testing-library/no-node-access
247
+ MQ(document.getElementsByClassName("mq-editable-field")[0]);
248
+
249
+ expect(mathquillInstance.latex()).toBe("1\\frac{4}{2}");
250
+ });
251
+
252
+ it("handles fractions correctly in fraction", async () => {
253
+ render(<ConnectedMathInput />);
254
+
255
+ const input = screen.getByLabelText(
256
+ "Math input box Tap with one or two fingers to open keyboard",
257
+ );
258
+
259
+ fireEvent.touchStart(input);
260
+
261
+ await waitFor(() => {
262
+ expect(screen.getByRole("button", {name: "4"})).toBeVisible();
263
+ });
264
+
265
+ userEvent.click(screen.getByRole("button", {name: "1"}));
266
+ userEvent.click(
267
+ screen.getByRole("button", {
268
+ name: "Fraction, excluding the current expression",
269
+ }),
270
+ );
271
+ userEvent.click(screen.getByRole("button", {name: "4"}));
272
+ userEvent.click(
273
+ screen.getByRole("button", {
274
+ name: "Navigate right out of the numerator and into the denominator",
275
+ }),
276
+ );
277
+ userEvent.click(screen.getByRole("button", {name: "2"}));
278
+
279
+ // MathQuill is problematic,
280
+ // this is how to get the value of the input directly from MQ
281
+ const mathquillInstance =
282
+ // eslint-disable-next-line testing-library/no-node-access
283
+ MQ(document.getElementsByClassName("mq-editable-field")[0]);
284
+
285
+ expect(mathquillInstance.latex()).toBe("1\\frac{4}{2}");
286
+ });
287
+ });