@@ -10,9 +10,12 @@ import {
1010 roundTextSignature ,
1111 sendChar ,
1212 snapshotKeys ,
13+ SPACE_GLYPHS ,
1314} from "./helpers.ts" ;
1415
1516const 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
1720test ( "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