@remit/ui 0.0.118 → 0.0.119
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/package.json
CHANGED
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
* A language the build has no dictionary for is not a failure: the provider
|
|
9
9
|
* answers null and the browser's own checking is switched back on, so the
|
|
10
10
|
* writer never faces a surface that has silently stopped marking anything.
|
|
11
|
+
*
|
|
12
|
+
* A checker that was supposed to run and is not is the case that has to be
|
|
13
|
+
* loud, in either of the two ways it happens — one that never opens, and one
|
|
14
|
+
* that opens and then stops. Both say so through `onStatus`, both hand the
|
|
15
|
+
* message back to the browser, and the second takes its own marks down on the
|
|
16
|
+
* way out so nothing is left underlined by a checker that is gone.
|
|
11
17
|
*/
|
|
12
18
|
|
|
13
19
|
import assert from "node:assert/strict";
|
|
@@ -21,6 +27,7 @@ import type { Root, createRoot as reactCreateRoot } from "react-dom/client";
|
|
|
21
27
|
import type { ComposeBody as ComposeBodyType } from "./compose-body.js";
|
|
22
28
|
import type {
|
|
23
29
|
CheckRequest,
|
|
30
|
+
ProviderStatus,
|
|
24
31
|
SpellcheckOptions,
|
|
25
32
|
SpellProvider,
|
|
26
33
|
SuggestRequest,
|
|
@@ -87,6 +94,84 @@ const recordingSpellcheck = (): {
|
|
|
87
94
|
return { options, asked, closed };
|
|
88
95
|
};
|
|
89
96
|
|
|
97
|
+
/** A checker whose engine never loads, so nothing was ever opened to stop. */
|
|
98
|
+
const brokenSpellcheck = (): {
|
|
99
|
+
options: SpellcheckOptions;
|
|
100
|
+
statuses: ProviderStatus[];
|
|
101
|
+
} => {
|
|
102
|
+
const statuses: ProviderStatus[] = [];
|
|
103
|
+
return {
|
|
104
|
+
options: {
|
|
105
|
+
provider: () => Promise.reject(new Error("boom")),
|
|
106
|
+
onStatus: (status) => statuses.push(status),
|
|
107
|
+
},
|
|
108
|
+
statuses,
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
/** A checker that comes up, marks the message, and then stops running. */
|
|
113
|
+
const stoppingSpellcheck = (): {
|
|
114
|
+
options: SpellcheckOptions;
|
|
115
|
+
statuses: ProviderStatus[];
|
|
116
|
+
stop: () => void;
|
|
117
|
+
} => {
|
|
118
|
+
const statuses: ProviderStatus[] = [];
|
|
119
|
+
const listeners: ((status: ProviderStatus) => void)[] = [];
|
|
120
|
+
const options: SpellcheckOptions = {
|
|
121
|
+
provider: (language) => {
|
|
122
|
+
const provider: SpellProvider = {
|
|
123
|
+
language,
|
|
124
|
+
onStatus: (listener) => {
|
|
125
|
+
listeners.push(listener);
|
|
126
|
+
listener({ state: "ready", language });
|
|
127
|
+
return () => {};
|
|
128
|
+
},
|
|
129
|
+
check: (request: CheckRequest) =>
|
|
130
|
+
Promise.resolve({
|
|
131
|
+
requestId: request.requestId,
|
|
132
|
+
revision: request.revision,
|
|
133
|
+
findings: request.spans.map((span) => ({
|
|
134
|
+
spanId: span.spanId,
|
|
135
|
+
start: 0,
|
|
136
|
+
end: 3,
|
|
137
|
+
kind: "spelling" as const,
|
|
138
|
+
suggestions: ["This"],
|
|
139
|
+
})),
|
|
140
|
+
}),
|
|
141
|
+
suggest: (request: SuggestRequest) =>
|
|
142
|
+
Promise.resolve({
|
|
143
|
+
requestId: request.requestId,
|
|
144
|
+
word: request.word,
|
|
145
|
+
suggestions: [],
|
|
146
|
+
}),
|
|
147
|
+
close: () => {},
|
|
148
|
+
};
|
|
149
|
+
return Promise.resolve(provider);
|
|
150
|
+
},
|
|
151
|
+
onStatus: (status) => statuses.push(status),
|
|
152
|
+
};
|
|
153
|
+
return {
|
|
154
|
+
options,
|
|
155
|
+
statuses,
|
|
156
|
+
stop: () => {
|
|
157
|
+
for (const listener of listeners)
|
|
158
|
+
listener({
|
|
159
|
+
state: "failed",
|
|
160
|
+
language: "en",
|
|
161
|
+
reason: "worker",
|
|
162
|
+
detail: "the worker went away",
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const marked = (): [number, number][] =>
|
|
169
|
+
(
|
|
170
|
+
(
|
|
171
|
+
globalThis as unknown as { CSS: { highlights: Map<string, Marks> } }
|
|
172
|
+
).CSS.highlights.get("spell-error")?.ranges ?? []
|
|
173
|
+
).map((range) => [range.startOffset, range.endOffset]);
|
|
174
|
+
|
|
90
175
|
const settle = async (): Promise<void> => {
|
|
91
176
|
await act(async () => {
|
|
92
177
|
await Promise.resolve();
|
|
@@ -96,6 +181,14 @@ const settle = async (): Promise<void> => {
|
|
|
96
181
|
});
|
|
97
182
|
};
|
|
98
183
|
|
|
184
|
+
/** Long enough for the editor's idle to expire and the pass to come back. */
|
|
185
|
+
const checked = async (): Promise<void> => {
|
|
186
|
+
await act(async () => {
|
|
187
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
188
|
+
});
|
|
189
|
+
await settle();
|
|
190
|
+
};
|
|
191
|
+
|
|
99
192
|
const editable = (): HTMLElement => {
|
|
100
193
|
const surface = container.querySelector<HTMLElement>(
|
|
101
194
|
"[data-testid=compose-body]",
|
|
@@ -248,6 +341,52 @@ describe("the composer's spellchecker", () => {
|
|
|
248
341
|
);
|
|
249
342
|
});
|
|
250
343
|
|
|
344
|
+
it("names a checker that never came up, and hands the message back", async () => {
|
|
345
|
+
const { options, statuses } = brokenSpellcheck();
|
|
346
|
+
|
|
347
|
+
await mount(options);
|
|
348
|
+
|
|
349
|
+
assert.deepEqual(
|
|
350
|
+
statuses,
|
|
351
|
+
[{ state: "failed", language: "en", reason: "worker", detail: "boom" }],
|
|
352
|
+
"the composer hears why checking is not happening, in the shape it reports",
|
|
353
|
+
);
|
|
354
|
+
assert.equal(
|
|
355
|
+
editable().getAttribute("spellcheck"),
|
|
356
|
+
"true",
|
|
357
|
+
"a checker that never started leaves the browser's own checking on",
|
|
358
|
+
);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it("takes its marks down with a checker that stops mid-message", async () => {
|
|
362
|
+
const { options, statuses, stop } = stoppingSpellcheck();
|
|
363
|
+
await mount(options);
|
|
364
|
+
await checked();
|
|
365
|
+
|
|
366
|
+
assert.deepEqual(
|
|
367
|
+
marked(),
|
|
368
|
+
[[0, 3]],
|
|
369
|
+
"the misspelling is marked while the checker is running",
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
await act(async () => {
|
|
373
|
+
stop();
|
|
374
|
+
});
|
|
375
|
+
await settle();
|
|
376
|
+
|
|
377
|
+
assert.deepEqual(
|
|
378
|
+
statuses.map((status) => status.state),
|
|
379
|
+
["ready", "failed"],
|
|
380
|
+
"the stop is reported, not swallowed",
|
|
381
|
+
);
|
|
382
|
+
assert.deepEqual(marked(), [], "no mark outlives the checker that drew it");
|
|
383
|
+
assert.equal(
|
|
384
|
+
editable().getAttribute("spellcheck"),
|
|
385
|
+
"true",
|
|
386
|
+
"the browser takes the message back the moment ours stops",
|
|
387
|
+
);
|
|
388
|
+
});
|
|
389
|
+
|
|
251
390
|
it("leaves the browser to it when the composer is handed no checker", async () => {
|
|
252
391
|
await act(async () => {
|
|
253
392
|
root = createRoot(container);
|