@pie-lib/math-input-mathlive 1.1.5-beta.9 → 2.0.1-next.20261109072207

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 (195) hide show
  1. package/README.md +84 -109
  2. package/dist/_virtual/_rolldown/runtime.js +11 -0
  3. package/dist/horizontal-keypad.d.ts +22 -0
  4. package/dist/horizontal-keypad.js +54 -0
  5. package/dist/index.d.ts +20 -0
  6. package/dist/index.js +12 -0
  7. package/dist/keypad/index.d.ts +40 -0
  8. package/dist/keypad/index.js +245 -0
  9. package/dist/keypad/keys-layout.d.ts +7 -0
  10. package/dist/keypad/keys-layout.js +5 -0
  11. package/dist/keys/basic-operators.d.ts +5 -0
  12. package/dist/keys/basic-operators.js +30 -0
  13. package/dist/keys/chars.d.ts +5 -0
  14. package/dist/keys/comparison.d.ts +4 -0
  15. package/dist/keys/comparison.js +32 -0
  16. package/dist/keys/constants.d.ts +4 -0
  17. package/dist/keys/constants.js +35 -0
  18. package/dist/keys/digits.d.ts +15 -0
  19. package/dist/keys/digits.js +34 -0
  20. package/dist/keys/edit.d.ts +6 -0
  21. package/dist/keys/edit.js +9 -0
  22. package/dist/keys/exponent.d.ts +4 -0
  23. package/dist/keys/exponent.js +28 -0
  24. package/dist/keys/fractions.d.ts +3 -0
  25. package/dist/keys/fractions.js +27 -0
  26. package/dist/keys/geometry.d.ts +23 -0
  27. package/dist/keys/geometry.js +127 -0
  28. package/dist/keys/grades.d.ts +9 -0
  29. package/dist/keys/grades.js +414 -0
  30. package/dist/keys/index.d.ts +6 -0
  31. package/dist/keys/index.js +50 -0
  32. package/dist/keys/log.d.ts +3 -0
  33. package/dist/keys/log.js +25 -0
  34. package/dist/keys/logic.d.ts +2 -0
  35. package/dist/keys/logic.js +13 -0
  36. package/dist/keys/matrices.d.ts +2 -0
  37. package/dist/keys/matrices.js +17 -0
  38. package/dist/keys/misc.d.ts +10 -0
  39. package/dist/keys/misc.js +60 -0
  40. package/dist/keys/navigation.d.ts +2 -0
  41. package/dist/keys/navigation.js +13 -0
  42. package/dist/keys/operators.d.ts +1 -0
  43. package/dist/keys/operators.js +11 -0
  44. package/dist/keys/statistics.d.ts +5 -0
  45. package/dist/keys/statistics.js +38 -0
  46. package/dist/keys/sub-sup.d.ts +2 -0
  47. package/dist/keys/sub-sup.js +17 -0
  48. package/dist/keys/trigonometry.d.ts +6 -0
  49. package/dist/keys/trigonometry.js +43 -0
  50. package/dist/keys/utils.d.ts +5 -0
  51. package/dist/keys/utils.js +24 -0
  52. package/dist/keys/vars.d.ts +3 -0
  53. package/dist/keys/vars.js +22 -0
  54. package/dist/latex-bridge.d.ts +197 -0
  55. package/dist/latex-bridge.js +210 -0
  56. package/dist/longdiv.d.ts +24 -0
  57. package/dist/longdiv.js +43 -0
  58. package/dist/math-input.d.ts +26 -0
  59. package/dist/math-input.js +74 -0
  60. package/dist/mathlive-instance.d.ts +221 -0
  61. package/dist/mathlive-instance.js +159 -0
  62. package/dist/mf/common-styles.d.ts +594 -0
  63. package/dist/mf/common-styles.js +80 -0
  64. package/dist/mf/field-label.d.ts +16 -0
  65. package/dist/mf/field-label.js +13 -0
  66. package/dist/mf/index.d.ts +4 -0
  67. package/dist/mf/index.js +21 -0
  68. package/dist/mf/input.d.ts +94 -0
  69. package/dist/mf/input.js +127 -0
  70. package/dist/mf/static.d.ts +143 -0
  71. package/dist/mf/static.js +230 -0
  72. package/dist/selection.d.ts +43 -0
  73. package/dist/selection.js +39 -0
  74. package/package.json +32 -18
  75. package/lib/__tests__/composites.test.js +0 -255
  76. package/lib/__tests__/drop-in-compat.test.js +0 -133
  77. package/lib/__tests__/embedded-fonts.test.js +0 -121
  78. package/lib/__tests__/index.test.js +0 -118
  79. package/lib/__tests__/input-imperative.test.js +0 -155
  80. package/lib/__tests__/keypad-label.test.js +0 -231
  81. package/lib/__tests__/keypad-render.test.js +0 -206
  82. package/lib/__tests__/latex-bridge.test.js +0 -386
  83. package/lib/__tests__/mathlive-instance.test.js +0 -419
  84. package/lib/__tests__/mount-race.test.js +0 -218
  85. package/lib/__tests__/static-prompts.test.js +0 -405
  86. package/lib/__tests__/tmp-markup.test.js +0 -26
  87. package/lib/horizontal-keypad.js +0 -101
  88. package/lib/horizontal-keypad.js.map +0 -1
  89. package/lib/index.js +0 -143
  90. package/lib/index.js.map +0 -1
  91. package/lib/keypad/index.js +0 -431
  92. package/lib/keypad/index.js.map +0 -1
  93. package/lib/keypad/keys-layout.js +0 -22
  94. package/lib/keypad/keys-layout.js.map +0 -1
  95. package/lib/keys/basic-operators.js +0 -33
  96. package/lib/keys/basic-operators.js.map +0 -1
  97. package/lib/keys/chars.js +0 -12
  98. package/lib/keys/chars.js.map +0 -1
  99. package/lib/keys/comparison.js +0 -39
  100. package/lib/keys/comparison.js.map +0 -1
  101. package/lib/keys/constants.js +0 -37
  102. package/lib/keys/constants.js.map +0 -1
  103. package/lib/keys/digits.js +0 -46
  104. package/lib/keys/digits.js.map +0 -1
  105. package/lib/keys/edit.js +0 -14
  106. package/lib/keys/edit.js.map +0 -1
  107. package/lib/keys/exponent.js +0 -30
  108. package/lib/keys/exponent.js.map +0 -1
  109. package/lib/keys/fractions.js +0 -29
  110. package/lib/keys/fractions.js.map +0 -1
  111. package/lib/keys/geometry.js +0 -143
  112. package/lib/keys/geometry.js.map +0 -1
  113. package/lib/keys/grades.js +0 -259
  114. package/lib/keys/grades.js.map +0 -1
  115. package/lib/keys/index.js +0 -35
  116. package/lib/keys/index.js.map +0 -1
  117. package/lib/keys/log.js +0 -27
  118. package/lib/keys/log.js.map +0 -1
  119. package/lib/keys/logic.js +0 -19
  120. package/lib/keys/logic.js.map +0 -1
  121. package/lib/keys/matrices.js +0 -19
  122. package/lib/keys/matrices.js.map +0 -1
  123. package/lib/keys/misc.js +0 -69
  124. package/lib/keys/misc.js.map +0 -1
  125. package/lib/keys/navigation.js +0 -20
  126. package/lib/keys/navigation.js.map +0 -1
  127. package/lib/keys/operators.js +0 -15
  128. package/lib/keys/operators.js.map +0 -1
  129. package/lib/keys/statistics.js +0 -40
  130. package/lib/keys/statistics.js.map +0 -1
  131. package/lib/keys/sub-sup.js +0 -19
  132. package/lib/keys/sub-sup.js.map +0 -1
  133. package/lib/keys/trigonometry.js +0 -45
  134. package/lib/keys/trigonometry.js.map +0 -1
  135. package/lib/keys/utils.js +0 -87
  136. package/lib/keys/utils.js.map +0 -1
  137. package/lib/keys/vars.js +0 -24
  138. package/lib/keys/vars.js.map +0 -1
  139. package/lib/latex-bridge.js +0 -508
  140. package/lib/latex-bridge.js.map +0 -1
  141. package/lib/math-input.js +0 -143
  142. package/lib/math-input.js.map +0 -1
  143. package/lib/mathlive-instance.js +0 -585
  144. package/lib/mathlive-instance.js.map +0 -1
  145. package/lib/mf/common-styles.js +0 -203
  146. package/lib/mf/common-styles.js.map +0 -1
  147. package/lib/mf/index.js +0 -84
  148. package/lib/mf/index.js.map +0 -1
  149. package/lib/mf/input.js +0 -364
  150. package/lib/mf/input.js.map +0 -1
  151. package/lib/mf/static.js +0 -500
  152. package/lib/mf/static.js.map +0 -1
  153. package/src/__tests__/composites.test.jsx +0 -220
  154. package/src/__tests__/drop-in-compat.test.jsx +0 -112
  155. package/src/__tests__/index.test.jsx +0 -109
  156. package/src/__tests__/input-imperative.test.jsx +0 -144
  157. package/src/__tests__/keypad-label.test.jsx +0 -283
  158. package/src/__tests__/keypad-render.test.jsx +0 -123
  159. package/src/__tests__/latex-bridge.test.js +0 -389
  160. package/src/__tests__/mathlive-instance.test.js +0 -418
  161. package/src/__tests__/mount-race.test.jsx +0 -142
  162. package/src/__tests__/static-prompts.test.jsx +0 -364
  163. package/src/horizontal-keypad.jsx +0 -73
  164. package/src/index.jsx +0 -63
  165. package/src/keypad/index.jsx +0 -386
  166. package/src/keypad/keys-layout.js +0 -16
  167. package/src/keys/basic-operators.js +0 -32
  168. package/src/keys/chars.js +0 -5
  169. package/src/keys/comparison.js +0 -28
  170. package/src/keys/constants.js +0 -35
  171. package/src/keys/digits.js +0 -40
  172. package/src/keys/edit.js +0 -3
  173. package/src/keys/exponent.js +0 -28
  174. package/src/keys/fractions.js +0 -26
  175. package/src/keys/geometry.js +0 -149
  176. package/src/keys/grades.js +0 -367
  177. package/src/keys/index.js +0 -20
  178. package/src/keys/log.js +0 -22
  179. package/src/keys/logic.js +0 -15
  180. package/src/keys/matrices.js +0 -15
  181. package/src/keys/misc.js +0 -71
  182. package/src/keys/navigation.js +0 -8
  183. package/src/keys/operators.js +0 -10
  184. package/src/keys/statistics.js +0 -38
  185. package/src/keys/sub-sup.js +0 -15
  186. package/src/keys/trigonometry.js +0 -15
  187. package/src/keys/utils.js +0 -66
  188. package/src/keys/vars.js +0 -19
  189. package/src/latex-bridge.js +0 -524
  190. package/src/math-input.jsx +0 -115
  191. package/src/mathlive-instance.js +0 -582
  192. package/src/mf/common-styles.js +0 -210
  193. package/src/mf/index.js +0 -30
  194. package/src/mf/input.jsx +0 -319
  195. package/src/mf/static.jsx +0 -479
@@ -1,283 +0,0 @@
1
- import { withVisibleEmptySlots as latexForDisplay } from '../latex-bridge';
2
- import { keyToAction, toMathLive } from '../latex-bridge';
3
-
4
- /**
5
- * Keys use empty groups to show a command's shape (`\frac{}{}`, `x^{}`).
6
- * MathLive renders `{}` as nothing, so labels need a visible stand-in - but the
7
- * value that gets *inserted* must stay untouched.
8
- */
9
- describe('keypad label display normalisation', () => {
10
- it('gives empty groups a visible glyph', () => {
11
- const out = latexForDisplay('\\frac{}{}');
12
-
13
- expect(out).not.toEqual('\\frac{}{}');
14
- // a \rule, not a glyph: font metrics for a substituted glyph do not match
15
- // MathLive's, which left stretchy accents narrower than the box
16
- expect(out).toContain('\\rule');
17
- expect(out).toContain('pie-empty');
18
- });
19
-
20
- it.each([
21
- ['absolute value', '\\abs{}', '|', '|'],
22
- ['parenthesis', '\\left(\\right)', '(', ')'],
23
- ['brackets', '\\left[\\right]', '[', ']'],
24
- ])('gives an empty %s a tall, filled slot (the same muted tag as other slots)', (_name, latex, open, close) => {
25
- const out = latexForDisplay(toMathLive(latex));
26
-
27
- expect(out).toContain('pie-empty=1');
28
- expect(out).toContain('\\rule[-0.25em]{0.55em}{1em}');
29
- expect(out).not.toContain('\\boxed');
30
- // the strut that keeps the fences tall while the box is centred on the axis
31
- expect(out).toContain('\\rule{0em}{1em}');
32
- expect(out.startsWith(`\\left${open}`)).toBe(true);
33
- expect(out.endsWith(`\\right${close}`)).toBe(true);
34
- });
35
-
36
- it('lifts the bar of an empty segment so its box lines up with the ray and line keys', () => {
37
- const out = latexForDisplay('\\overline{}');
38
-
39
- expect(out).toContain('\\overline{\\rule{0em}{1.08em}');
40
- expect(out).toContain('pie-empty');
41
- // the ray and line keys are left as they were
42
- expect(latexForDisplay('\\overrightarrow{}')).not.toContain('\\rule{0em}');
43
- expect(latexForDisplay('\\overleftrightarrow{}')).not.toContain('\\rule{0em}');
44
- // a filled bar is untouched
45
- expect(latexForDisplay('\\overline{AB}')).toEqual('\\overline{AB}');
46
- });
47
-
48
- it('leaves a filled pair of fences alone', () => {
49
- expect(latexForDisplay(toMathLive('\\abs{x}'))).toEqual('\\left|x\\right|');
50
- expect(latexForDisplay('\\left(x\\right)')).toEqual('\\left(x\\right)');
51
- });
52
-
53
- it('handles every empty-group key shape', () => {
54
- [
55
- '\\frac{}{}',
56
- '\\overline{}',
57
- 'x^{}',
58
- 'x_{}',
59
- '\\sqrt{}',
60
- '\\sqrt[{}]{}',
61
- '\\abs{}',
62
- '\\log_{}',
63
- '\\longdiv{}',
64
- ].forEach((latex) => {
65
- expect(latexForDisplay(latex)).not.toContain('{}');
66
- });
67
- });
68
-
69
- it('treats a whitespace-only group as empty', () => {
70
- // e.g. \frac{x}{ }
71
- expect(latexForDisplay('\\frac{x}{ }')).toContain('pie-empty');
72
- });
73
-
74
- // MathQuill needed a nested \overline{} to draw the empty box. MathLive
75
- // renders that as a real line, so those labels showed a spurious bar (a line
76
- // under the arrows, and a straight line instead of the arc).
77
- it('accent keys do not carry the nested-overline box hack', () => {
78
- // eslint-disable-next-line global-require
79
- const geometry = require('../keys/geometry');
80
-
81
- Object.values(geometry).forEach((key) => {
82
- if (key && typeof key.latex === 'string') {
83
- expect(key.latex).not.toContain('\\overline{}}');
84
- }
85
- });
86
- });
87
-
88
- it('an accent over an empty slot still gets a visible box', () => {
89
- ['\\overleftrightarrow{}', '\\overarc{}'].forEach((latex) => {
90
- const out = latexForDisplay(latex);
91
-
92
- expect(out).toContain('pie-empty');
93
- expect(out).not.toContain('\\overline');
94
- });
95
- });
96
-
97
- // A glyph's width comes from font metrics; U+25AB is not in the KaTeX fonts,
98
- // so the browser substituted a wider one and stretchy accents came out
99
- // narrower than the box. A rule has an explicit width.
100
- it('the empty slot has an explicit width, not font-dependent metrics', () => {
101
- const out = latexForDisplay('\\overarc{}');
102
-
103
- expect(out).toContain('\\rule');
104
- expect(out).toMatch(/rule\{[\d.]+em\}\{[\d.]+em\}/);
105
- expect(out).not.toContain('unicode');
106
- });
107
-
108
- // Measured in the browser: the accent's immediate wrapper computes to 0px
109
- // (its .ML__stretchy child is absolutely positioned), so the svg width has to
110
- // resolve against .ML__base - whose width equals the content. Without pinning
111
- // that, the arc collapsed to a spike and sat right of the box.
112
- it('pins the accent containing block and drops the centering shift', () => {
113
- // eslint-disable-next-line global-require
114
- const { placeholderStyles } = require('../mf/common-styles');
115
-
116
- expect(placeholderStyles['& .ML__base'].position).toEqual('relative');
117
- expect(placeholderStyles['& .ML__stretchy']).toEqual({ position: 'absolute', left: 0, width: '100%' });
118
- // must be !important: MathLive writes margin-left as an inline style, and a
119
- // class cannot override that. Leaving it un-important also halved the arc's
120
- // width, because .ML__center is the stretchy's containing block.
121
- expect(placeholderStyles['& .ML__center:has(.ML__stretchy)'].marginLeft).toEqual('0 !important');
122
- });
123
-
124
- // MathLive sets the index of \sqrt[n]{x} in scriptscriptstyle - an inline
125
- // `font-size: 50%` - which is too small to read on the nth-root key and in
126
- // authored radicals. It is scaled up to 80%.
127
- describe('radical index size', () => {
128
- // Real MathLive 0.110 output for `\sqrt[1]{2}`, trimmed to the index. The
129
- // selector walks this exact chain, so this fixture is what pins it down.
130
- const INDEX_MARKUP = `
131
- <span class="ML__base">
132
- <span class="ML__sqrt-index">
133
- <span class="ML__vlist-t"><span class="ML__vlist-r"><span class="ML__vlist" style="height:0.78em">
134
- <span style="top:-3.45em">
135
- <span class="ML__pstrut" style="height:3em"></span>
136
- <span id="index-size" style="height:0.33em;display:inline-block;font-size: 50%">
137
- <span class="ML__cmr">1</span>
138
- <span id="nested-size" style="font-size: 50%">nested</span>
139
- </span>
140
- </span>
141
- </span></span></span>
142
- </span>
143
- <span class="ML__sqrt-sign"><span class="ML__delim-size1">&#8730;</span></span>
144
- </span>`;
145
-
146
- const rule = () => {
147
- // eslint-disable-next-line global-require
148
- const { rootIndexStyles } = require('../mf/common-styles');
149
- const selectors = Object.keys(rootIndexStyles);
150
-
151
- expect(selectors).toHaveLength(1);
152
-
153
- return { selector: selectors[0], value: rootIndexStyles[selectors[0]].fontSize };
154
- };
155
-
156
- it("scales the index glyph to 80%, up from MathLive's 50%", () => {
157
- // !important: the 50% is an inline style, which a class cannot override.
158
- expect(rule().value).toEqual('80% !important');
159
- });
160
-
161
- it('targets the span MathLive puts the inline font-size on', () => {
162
- const holder = document.createElement('div');
163
-
164
- holder.innerHTML = INDEX_MARKUP;
165
-
166
- const matched = holder.querySelectorAll(rule().selector.replace(/^&\s*/, ''));
167
-
168
- expect(Array.from(matched).map((el) => el.id)).toEqual(['index-size']);
169
- });
170
-
171
- it('leaves the radical index box itself unscaled', () => {
172
- // Scaling `.ML__sqrt-index` would scale the em-based top/height offsets
173
- // that tuck the index into the radical, lifting it clear of the crook.
174
- const { selector } = rule();
175
-
176
- expect(selector).not.toMatch(/\.ML__sqrt-index\s*\{?$/);
177
- expect(selector.startsWith('& .ML__sqrt-index >')).toBe(true);
178
- });
179
-
180
- it('applies wherever math renders - keypad labels, static math, mathfield', () => {
181
- // eslint-disable-next-line global-require
182
- const cs = require('../mf/common-styles');
183
- const { selector } = rule();
184
-
185
- ['commonKeyboardStyles', 'commonMathLiveStyles', 'supsubStyles'].forEach((name) => {
186
- expect(cs[name][selector]).toBeDefined();
187
- });
188
-
189
- // The live mathfield renders in a shadow root, which host styles cannot
190
- // reach, so the same rule ships as plain CSS for `applyShadowStyles`.
191
- expect(cs.ROOT_INDEX_CSS).toContain('.ML__sqrt-index >');
192
- // Derived, so the size lives in one place: the two copies cannot drift.
193
- expect(cs.ROOT_INDEX_CSS).toContain(`font-size:${rule().value}`);
194
- });
195
- });
196
-
197
- it('applies those rules wherever static math renders', () => {
198
- // eslint-disable-next-line global-require
199
- const cs = require('../mf/common-styles');
200
-
201
- ['commonKeyboardStyles', 'commonMathLiveStyles'].forEach((name) => {
202
- expect(cs[name]['& .ML__stretchy']).toBeDefined();
203
- expect(cs[name]['& .ML__base']).toBeDefined();
204
- });
205
- });
206
-
207
- // A bare `& svg` rule re-parents every svg in the subtree; MathLive's own rule
208
- // is descendant-scoped to .ML__latex .ML__stretchy svg.
209
- it('does not use a blanket svg rule', () => {
210
- // eslint-disable-next-line global-require
211
- const cs = require('../mf/common-styles');
212
-
213
- Object.values(cs).forEach((style) => {
214
- if (style && typeof style === 'object') {
215
- expect(Object.keys(style)).not.toContain('& svg');
216
- }
217
- });
218
- });
219
-
220
- // The Percent key showed as a blank button on the Advanced Algebra keypad
221
- // (row 1, position 6). MathQuill defined `LatexCmds['%']` as a symbol that
222
- // serialised to `\\%`, so its bare `%` label parsed fine; MathLive parses real
223
- // LaTeX, where `%` opens a comment - the label rendered nothing, and pressing
224
- // the key inserted a comment that swallowed the rest of the expression.
225
- it('the percent key is escaped, not a latex comment', () => {
226
- // eslint-disable-next-line global-require
227
- const { percentage } = require('../keys/misc');
228
-
229
- expect(percentage.latex).toEqual('\\%');
230
- expect(keyToAction(percentage).value).toEqual('\\%');
231
- });
232
-
233
- // Any bare `%` in a key would fail the same way, silently.
234
- it('no keypad key carries an unescaped %', () => {
235
- // eslint-disable-next-line global-require
236
- const { baseSet } = require('../keys');
237
- // eslint-disable-next-line global-require
238
- const { gradeSets } = require('../keys/grades');
239
-
240
- const every = [...baseSet, ...gradeSets.map((g) => g.set)];
241
-
242
- const walk = (node) => {
243
- if (Array.isArray(node)) {
244
- node.forEach(walk);
245
- return;
246
- }
247
-
248
- if (!node) {
249
- return;
250
- }
251
-
252
- ['latex', 'write', 'command'].forEach((field) => {
253
- const value = node[field];
254
-
255
- if (typeof value === 'string') {
256
- // a `%` is only safe when preceded by an odd run of backslashes
257
- expect(value.replace(/\\./g, '')).not.toContain('%');
258
- }
259
- });
260
- };
261
-
262
- walk(every);
263
- });
264
-
265
- it('leaves latex without empty groups alone', () => {
266
- ['\\pi', '\\sin', 'x^2', '\\overline{AB}'].forEach((latex) => {
267
- expect(latexForDisplay(latex)).toEqual(latex);
268
- });
269
- });
270
-
271
- it('handles nullish input', () => {
272
- expect(latexForDisplay(undefined)).toEqual('');
273
- expect(latexForDisplay('')).toEqual('');
274
- });
275
-
276
- it('does NOT affect what gets inserted into the field', () => {
277
- // the action must use the key's own latex, never the display form
278
- const key = { name: 'frac', latex: '\\frac{}{}' };
279
-
280
- expect(keyToAction(key).value).toEqual('\\frac{}{}');
281
- expect(keyToAction(key).value).not.toContain('pie-empty');
282
- });
283
- });
@@ -1,123 +0,0 @@
1
- import React from 'react';
2
- import { render } from '@testing-library/react';
3
-
4
- import KeyPad, { LatexLabel } from '../keypad';
5
-
6
- /**
7
- * Keypad behaviour that is new in this package: labels are static markup from
8
- * convertLatexToMarkup (the MathQuill version created a live StaticMath per
9
- * button), with a raw-latex fallback when the engine is unavailable.
10
- */
11
- describe('LatexLabel', () => {
12
- it('falls back to raw latex when MathLive is unavailable', () => {
13
- // jsdom cannot load MathLive, so latexToMarkup returns '' - the button must
14
- // still show something rather than render blank.
15
- const { container } = render(<LatexLabel latex={'\\pi'} />);
16
-
17
- expect(container.textContent).toContain('\\pi');
18
- });
19
-
20
- it('re-renders when the latex prop changes', () => {
21
- const { container, rerender } = render(<LatexLabel latex={'\\pi'} />);
22
-
23
- rerender(<LatexLabel latex={'\\theta'} />);
24
-
25
- expect(container.textContent).toContain('\\theta');
26
- });
27
-
28
- it('does not warn more than once about a missing engine', async () => {
29
- LatexLabel.warned = false;
30
-
31
- const a = new LatexLabel({ latex: '\\pi' });
32
- const b = new LatexLabel({ latex: '\\theta' });
33
-
34
- a.state = { markup: '' };
35
- b.state = { markup: '' };
36
- a.setState = () => {};
37
- b.setState = () => {};
38
-
39
- await a.componentDidMount();
40
- await b.componentDidMount();
41
-
42
- expect(LatexLabel.warned).toBe(true);
43
- });
44
- });
45
-
46
- describe('KeyPad', () => {
47
- const key = (over = {}) => ({ name: 'x', label: 'x', write: 'x', category: 'vars', ...over });
48
-
49
- it('renders a button per key', () => {
50
- const { container } = render(<KeyPad onPress={() => {}} baseSet={[[key(), key({ name: 'y', label: 'y' })]]} />);
51
-
52
- expect(container.querySelectorAll('button').length).toBe(2);
53
- });
54
-
55
- it('calls onPress with the whole key definition', () => {
56
- const onPress = jest.fn();
57
- const k = key();
58
- const { container } = render(<KeyPad onPress={onPress} baseSet={[[k]]} />);
59
-
60
- container.querySelector('button').click();
61
-
62
- expect(onPress).toHaveBeenCalledWith(k);
63
- });
64
-
65
- it('renders a latex key through LatexLabel', () => {
66
- const { container } = render(<KeyPad onPress={() => {}} baseSet={[[{ name: 'pi', latex: '\\pi' }]]} />);
67
-
68
- expect(container.textContent).toContain('\\pi');
69
- });
70
-
71
- it('disables the decimal separators when noDecimal is set', () => {
72
- const dot = key({ name: '.', label: '.', write: '.' });
73
- const comma = key({ name: ',', label: ',', write: ',' });
74
- const { container } = render(<KeyPad onPress={() => {}} noDecimal baseSet={[[dot, comma]]} />);
75
-
76
- const disabled = Array.from(container.querySelectorAll('button')).filter((b) => b.disabled);
77
-
78
- expect(disabled.length).toBe(2);
79
- });
80
-
81
- it('leaves the separators enabled otherwise', () => {
82
- const dot = key({ name: '.', label: '.', write: '.' });
83
- const { container } = render(<KeyPad onPress={() => {}} baseSet={[[dot]]} />);
84
-
85
- expect(container.querySelector('button').disabled).toBe(false);
86
- });
87
-
88
- it('renders a placeholder for empty grid slots', () => {
89
- expect(() => render(<KeyPad onPress={() => {}} baseSet={[[null]]} />)).not.toThrow();
90
- });
91
-
92
- it('notifies setKeypadInteraction on pointer interaction', () => {
93
- const setKeypadInteraction = jest.fn();
94
- const { container } = render(
95
- <KeyPad onPress={() => {}} setKeypadInteraction={setKeypadInteraction} baseSet={[[key()]]} />,
96
- );
97
-
98
- container.firstChild.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
99
-
100
- expect(setKeypadInteraction).toHaveBeenCalledWith(true);
101
- });
102
-
103
- it('uses the mode as a class so mode-specific styling applies', () => {
104
- const { container } = render(<KeyPad onPress={() => {}} mode="language" baseSet={[[key()]]} />);
105
-
106
- expect(container.firstChild.className).toContain('language');
107
- });
108
-
109
- it('drops the base set for the modes that supply their own keys', () => {
110
- const { container } = render(
111
- <KeyPad onPress={() => {}} mode="language" baseSet={[[key(), key()]]} additionalKeys={[[key()]]} />,
112
- );
113
-
114
- // base set ignored, only the additional key renders
115
- expect(container.querySelectorAll('button').length).toBe(1);
116
- });
117
-
118
- it('unmounts without throwing', () => {
119
- const { unmount } = render(<KeyPad onPress={() => {}} controlledKeypadMode baseSet={[[key()]]} />);
120
-
121
- expect(() => unmount()).not.toThrow();
122
- });
123
- });