Skip to content

Commit 82c98a7

Browse files
committed
tests: fuzz fumbled keystrokes against key confidence
Occasionally mistypes-then-corrects the displayed character instead of typing it cleanly, mirroring a human fumble.
1 parent 408f4b3 commit 82c98a7

1 file changed

Lines changed: 56 additions & 1 deletion

File tree

e2e/guided-lesson/alphabet-unlock.spec.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ import {
1010
roundTextSignature,
1111
sendChar,
1212
snapshotKeys,
13+
SPACE_GLYPHS,
1314
} from "./helpers.ts";
1415

1516
const CURRENT_KEY_SELECTOR = '[id*="currentKey"] [data-code-point]';
17+
const CURRENT_KEY_DETAILS_SELECTOR = '[id*="currentKey"] [class*="keyDetails"]';
18+
const FUMBLE_PROBABILITY = 0.15;
1619

1720
test("typing through the full American English alphabet never regresses an unlocked key", async ({
1821
page,
@@ -68,10 +71,31 @@ test("typing through the full American English alphabet never regresses an unloc
6871
.locator(CURSOR_SELECTOR)
6972
.first()
7073
.textContent();
71-
await sendChar(page, displayedChar ?? "");
74+
const targetBeforeChar = await currentKeyCodePoint(page);
75+
const detailsBeforeChar =
76+
targetBeforeChar != null ? await currentKeyDetailsText(page) : null;
77+
const fumbled = random() < FUMBLE_PROBABILITY;
78+
if (fumbled) {
79+
await fumbleThenCorrect(page, displayedChar ?? "", random);
80+
} else {
81+
await sendChar(page, displayedChar ?? "");
82+
}
7283
keystrokes++;
7384
await page.waitForTimeout(delay);
7485

86+
if (fumbled && targetBeforeChar != null) {
87+
const targetAfterChar = await currentKeyCodePoint(page);
88+
if (targetAfterChar === targetBeforeChar) {
89+
// Histogram.from() skips a typo'd step's timing sample, so a
90+
// fumble must not move this key's confidence.
91+
const detailsAfterChar = await currentKeyDetailsText(page);
92+
expect(
93+
detailsAfterChar,
94+
`expected fumbling ${targetBeforeChar} not to change its reported confidence (a miss records no timing sample), but it went from "${detailsBeforeChar}" to "${detailsAfterChar}" after ${keystrokes} keystrokes (seed ${seed})`,
95+
).toEqual(detailsBeforeChar);
96+
}
97+
}
98+
7599
const signature = await roundTextSignature(page);
76100
if (signature === roundSignature) {
77101
continue;
@@ -187,3 +211,34 @@ async function currentKeyCodePoint(page: Page): Promise<string | null> {
187211
}
188212
return locator.first().getAttribute("data-code-point");
189213
}
214+
215+
async function currentKeyDetailsText(page: Page): Promise<string | null> {
216+
const locator = page.locator(CURRENT_KEY_DETAILS_SELECTOR);
217+
if ((await locator.count()) === 0) {
218+
return null;
219+
}
220+
return locator.first().textContent();
221+
}
222+
223+
const ALPHANUMERIC_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
224+
225+
function randomAlphanumericChar(random: () => number, exclude: string): string {
226+
let char: string;
227+
do {
228+
char = ALPHANUMERIC_CHARS[Math.floor(random() * ALPHANUMERIC_CHARS.length)];
229+
} while (char === exclude);
230+
return char;
231+
}
232+
233+
// TextInput.appendChar() never turns a wrong char into a step; only the
234+
// following correct char does, tagged Attr.Miss.
235+
async function fumbleThenCorrect(
236+
page: Page,
237+
displayed: string,
238+
random: () => number,
239+
): Promise<void> {
240+
const correct = SPACE_GLYPHS.has(displayed) ? " " : displayed;
241+
const wrong = randomAlphanumericChar(random, correct);
242+
await page.keyboard.type(wrong);
243+
await sendChar(page, displayed);
244+
}

0 commit comments

Comments
 (0)