@testgorilla/tgo-typing-test 1.0.0 → 2.0.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 (134) hide show
  1. package/.eslintrc.json +46 -0
  2. package/jest.config.ts +21 -0
  3. package/ng-package.json +15 -0
  4. package/package.json +7 -22
  5. package/project.json +36 -0
  6. package/src/lib/components/tgo-typing-replay-input/tgo-typing-replay-input.component.html +30 -0
  7. package/src/lib/components/tgo-typing-replay-input/tgo-typing-replay-input.component.spec.ts +250 -0
  8. package/src/lib/components/tgo-typing-replay-input/tgo-typing-replay-input.component.ts +47 -0
  9. package/src/lib/components/tgo-typing-test/tgo-typing-test.component.html +72 -0
  10. package/src/lib/components/tgo-typing-test/tgo-typing-test.component.spec.ts +699 -0
  11. package/src/lib/components/tgo-typing-test/tgo-typing-test.component.ts +287 -0
  12. package/src/lib/helpers/config.ts +28 -0
  13. package/src/lib/helpers/constants/default-config.ts +103 -0
  14. package/src/lib/helpers/controllers/input-controller.ts +710 -0
  15. package/src/lib/helpers/controllers/quotes-controller.ts +183 -0
  16. package/src/lib/helpers/observables/banner-event.ts +18 -0
  17. package/src/lib/helpers/observables/config-event.ts +31 -0
  18. package/src/lib/helpers/observables/timer-event.ts +18 -0
  19. package/src/lib/helpers/states/active-page.ts +9 -0
  20. package/src/lib/helpers/states/composition.ts +29 -0
  21. package/src/lib/helpers/states/page-transition.ts +9 -0
  22. package/src/lib/helpers/states/slow-timer.ts +16 -0
  23. package/src/lib/helpers/states/test-active.ts +9 -0
  24. package/src/lib/helpers/states/time.ts +13 -0
  25. package/src/lib/helpers/test/caps-warning.ts +50 -0
  26. package/src/lib/helpers/test/caret.ts +92 -0
  27. package/src/lib/helpers/test/custom-text.ts +73 -0
  28. package/src/lib/helpers/test/english-punctuation.ts +38 -0
  29. package/src/lib/helpers/test/focus.ts +39 -0
  30. package/src/lib/helpers/test/manual-restart-tracker.ts +13 -0
  31. package/src/lib/helpers/test/out-of-focus.ts +19 -0
  32. package/src/lib/helpers/test/replay.ts +265 -0
  33. package/src/lib/helpers/test/test-input.ts +320 -0
  34. package/src/lib/helpers/test/test-logic.ts +1039 -0
  35. package/src/lib/helpers/test/test-state.ts +17 -0
  36. package/src/lib/helpers/test/test-stats.ts +442 -0
  37. package/src/lib/helpers/test/test-timer.ts +209 -0
  38. package/src/lib/helpers/test/test-ui.ts +370 -0
  39. package/src/lib/helpers/test/test-words.ts +72 -0
  40. package/src/lib/helpers/test/timer-progress.ts +16 -0
  41. package/src/lib/helpers/test/tts.ts +42 -0
  42. package/src/lib/helpers/test/weak-spot.ts +74 -0
  43. package/src/lib/helpers/test/wordset.ts +109 -0
  44. package/src/lib/styles/animations.scss +101 -0
  45. package/src/lib/styles/caret.scss +108 -0
  46. package/src/lib/styles/core.scss +498 -0
  47. package/src/lib/styles/index.scss +19 -0
  48. package/src/lib/styles/inputs.scss +290 -0
  49. package/src/lib/styles/popups.scss +1311 -0
  50. package/src/lib/styles/test.scss +1008 -0
  51. package/src/lib/styles/z_media-queries.scss +848 -0
  52. package/src/lib/types/types.d.ts +731 -0
  53. package/src/lib/utils/misc.ts +776 -0
  54. package/src/test-setup.ts +20 -0
  55. package/tsconfig.json +16 -0
  56. package/tsconfig.lib.json +14 -0
  57. package/tsconfig.lib.prod.json +9 -0
  58. package/tsconfig.spec.json +11 -0
  59. package/esm2022/index.mjs +0 -3
  60. package/esm2022/lib/components/tgo-typing-replay-input/tgo-typing-replay-input.component.mjs +0 -45
  61. package/esm2022/lib/components/tgo-typing-test/tgo-typing-test.component.mjs +0 -299
  62. package/esm2022/lib/helpers/config.mjs +0 -24
  63. package/esm2022/lib/helpers/constants/default-config.mjs +0 -103
  64. package/esm2022/lib/helpers/controllers/input-controller.mjs +0 -586
  65. package/esm2022/lib/helpers/controllers/quotes-controller.mjs +0 -118
  66. package/esm2022/lib/helpers/observables/config-event.mjs +0 -16
  67. package/esm2022/lib/helpers/observables/timer-event.mjs +0 -16
  68. package/esm2022/lib/helpers/states/active-page.mjs +0 -8
  69. package/esm2022/lib/helpers/states/composition.mjs +0 -20
  70. package/esm2022/lib/helpers/states/page-transition.mjs +0 -8
  71. package/esm2022/lib/helpers/states/slow-timer.mjs +0 -15
  72. package/esm2022/lib/helpers/states/test-active.mjs +0 -8
  73. package/esm2022/lib/helpers/states/time.mjs +0 -11
  74. package/esm2022/lib/helpers/test/caps-warning.mjs +0 -50
  75. package/esm2022/lib/helpers/test/caret.mjs +0 -80
  76. package/esm2022/lib/helpers/test/custom-text.mjs +0 -59
  77. package/esm2022/lib/helpers/test/english-punctuation.mjs +0 -29
  78. package/esm2022/lib/helpers/test/focus.mjs +0 -35
  79. package/esm2022/lib/helpers/test/manual-restart-tracker.mjs +0 -11
  80. package/esm2022/lib/helpers/test/out-of-focus.mjs +0 -14
  81. package/esm2022/lib/helpers/test/replay.mjs +0 -217
  82. package/esm2022/lib/helpers/test/test-input.mjs +0 -264
  83. package/esm2022/lib/helpers/test/test-logic.mjs +0 -927
  84. package/esm2022/lib/helpers/test/test-state.mjs +0 -13
  85. package/esm2022/lib/helpers/test/test-stats.mjs +0 -342
  86. package/esm2022/lib/helpers/test/test-timer.mjs +0 -207
  87. package/esm2022/lib/helpers/test/test-ui.mjs +0 -341
  88. package/esm2022/lib/helpers/test/test-words.mjs +0 -69
  89. package/esm2022/lib/helpers/test/timer-progress.mjs +0 -15
  90. package/esm2022/lib/helpers/test/weak-spot.mjs +0 -65
  91. package/esm2022/lib/helpers/test/wordset.mjs +0 -100
  92. package/esm2022/lib/utils/misc.mjs +0 -673
  93. package/esm2022/testgorilla-tgo-typing-test.mjs +0 -5
  94. package/fesm2022/testgorilla-tgo-typing-test.mjs +0 -4707
  95. package/fesm2022/testgorilla-tgo-typing-test.mjs.map +0 -1
  96. package/lib/components/tgo-typing-replay-input/tgo-typing-replay-input.component.d.ts +0 -14
  97. package/lib/components/tgo-typing-test/tgo-typing-test.component.d.ts +0 -54
  98. package/lib/helpers/config.d.ts +0 -98
  99. package/lib/helpers/constants/default-config.d.ts +0 -3
  100. package/lib/helpers/controllers/input-controller.d.ts +0 -16
  101. package/lib/helpers/controllers/quotes-controller.d.ts +0 -20
  102. package/lib/helpers/observables/config-event.d.ts +0 -5
  103. package/lib/helpers/observables/timer-event.d.ts +0 -4
  104. package/lib/helpers/states/active-page.d.ts +0 -2
  105. package/lib/helpers/states/composition.d.ts +0 -10
  106. package/lib/helpers/states/page-transition.d.ts +0 -2
  107. package/lib/helpers/states/slow-timer.d.ts +0 -3
  108. package/lib/helpers/states/test-active.d.ts +0 -2
  109. package/lib/helpers/states/time.d.ts +0 -3
  110. package/lib/helpers/test/caps-warning.d.ts +0 -5
  111. package/lib/helpers/test/caret.d.ts +0 -11
  112. package/lib/helpers/test/custom-text.d.ts +0 -16
  113. package/lib/helpers/test/english-punctuation.d.ts +0 -3
  114. package/lib/helpers/test/focus.d.ts +0 -7
  115. package/lib/helpers/test/manual-restart-tracker.d.ts +0 -3
  116. package/lib/helpers/test/out-of-focus.d.ts +0 -4
  117. package/lib/helpers/test/replay.d.ts +0 -20
  118. package/lib/helpers/test/test-input.d.ts +0 -86
  119. package/lib/helpers/test/test-logic.d.ts +0 -25
  120. package/lib/helpers/test/test-state.d.ts +0 -7
  121. package/lib/helpers/test/test-stats.d.ts +0 -92
  122. package/lib/helpers/test/test-timer.d.ts +0 -6
  123. package/lib/helpers/test/test-ui.d.ts +0 -27
  124. package/lib/helpers/test/test-words.d.ts +0 -23
  125. package/lib/helpers/test/timer-progress.d.ts +0 -3
  126. package/lib/helpers/test/weak-spot.d.ts +0 -3
  127. package/lib/helpers/test/wordset.d.ts +0 -7
  128. package/lib/utils/misc.d.ts +0 -81
  129. /package/{assets → src/assets}/typing-test-languages/english.json +0 -0
  130. /package/{assets → src/assets}/typing-test-languages/english_punctuation.json +0 -0
  131. /package/{assets → src/assets}/typing-test-languages/quotes/english_version_1.json +0 -0
  132. /package/{assets → src/assets}/typing-test-languages/quotes/english_version_2.json +0 -0
  133. /package/{assets → src/assets}/typing-test-languages/quotes/filtered_sources.json +0 -0
  134. /package/{index.d.ts → src/index.ts} +0 -0
@@ -0,0 +1,1039 @@
1
+ import { BehaviorSubject, Subject } from 'rxjs';
2
+ import Config from '../../helpers/config';
3
+ import { MonkeyTypes } from '../../types/types';
4
+ import * as Misc from '../../utils/misc';
5
+ import QuotesController from '../controllers/quotes-controller';
6
+ import * as TimerEvent from '../observables/timer-event';
7
+ import * as ActivePage from '../states/active-page';
8
+ import * as PageTransition from '../states/page-transition';
9
+ import * as TestActive from '../states/test-active';
10
+ import * as Caret from './caret';
11
+ import * as CustomText from './custom-text';
12
+ import * as EnglishPunctuation from './english-punctuation';
13
+ import * as Focus from './focus';
14
+ import * as ManualRestart from './manual-restart-tracker';
15
+ import * as OutOfFocus from './out-of-focus';
16
+ import * as Replay from './replay';
17
+ import * as TestInput from './test-input';
18
+ import * as TestState from './test-state';
19
+ import * as TestStats from './test-stats';
20
+ import * as TestTimer from './test-timer';
21
+ import * as TestUI from './test-ui';
22
+ import * as TestWords from './test-words';
23
+ import * as TimerProgress from './timer-progress';
24
+ import * as WeakSpot from './weak-spot';
25
+ import * as Wordset from './wordset';
26
+
27
+ let failReason = '';
28
+
29
+ let wordsInput: HTMLInputElement;
30
+
31
+ export const startTestSubject = new BehaviorSubject<boolean>(false);
32
+ export const resultSub = new Subject<any>();
33
+
34
+ export let notSignedInLastResult: MonkeyTypes.Result<MonkeyTypes.Mode> | null = null;
35
+
36
+ export function setWordsInputElementTestLogic(wordsElement: HTMLInputElement) {
37
+ wordsInput = wordsElement;
38
+ }
39
+
40
+ export function clearNotSignedInResult(): void {
41
+ notSignedInLastResult = null;
42
+ }
43
+
44
+ function shouldCapitalize(lastChar: string): boolean {
45
+ return /[?!.؟]/.test(lastChar);
46
+ }
47
+
48
+ function addDecimalPoint(word: string): string {
49
+ if (Math.random() < 0.8) {
50
+ const decimal = Misc.getNumbers(3, 0);
51
+ if (decimal != '') {
52
+ word = word + '.' + decimal;
53
+ }
54
+ }
55
+ return word;
56
+ }
57
+
58
+ let spanishSentenceTracker = '';
59
+ export async function punctuateWord(
60
+ previousWord: string,
61
+ currentWord: string,
62
+ index: number,
63
+ maxindex: number
64
+ ): Promise<string> {
65
+ let word = currentWord;
66
+
67
+ const currentLanguage = Config.language.split('_')[0];
68
+
69
+ const lastChar = Misc.getLastChar(previousWord);
70
+
71
+ if (Config.funbox === '58008') {
72
+ if (currentWord.length > 3) {
73
+ if (Math.random() < 0.5) {
74
+ word = Misc.setCharAt(word, Misc.randomIntFromRange(1, word.length - 2), '.');
75
+ }
76
+ if (Math.random() < 0.75) {
77
+ const index = Misc.randomIntFromRange(1, word.length - 2);
78
+ if (word[index - 1] !== '.' && word[index + 1] !== '.' && word[index + 1] !== '0') {
79
+ const special = Misc.randomElementFromArray(['/', '*', '-', '+']);
80
+ word = Misc.setCharAt(word, index, special);
81
+ }
82
+ }
83
+ }
84
+ } else if (Config.funbox === 'tenKeyMode') {
85
+ word = addDecimalPoint(word);
86
+ const special = Misc.randomElementFromArray(['/', '*', '-', '+']);
87
+ word = word + special + Misc.getNumbers(4);
88
+ // 2nd number of expression
89
+ word = addDecimalPoint(word);
90
+ } else {
91
+ if (currentLanguage != 'code' && (index == 0 || shouldCapitalize(lastChar))) {
92
+ //always capitalise the first word or if there was a dot unless using a code alphabet
93
+
94
+ word = Misc.capitalizeFirstLetterOfEachWord(word);
95
+
96
+ if (currentLanguage == 'turkish') {
97
+ word = word.replace(/I/g, 'İ');
98
+ }
99
+
100
+ if (currentLanguage == 'spanish' || currentLanguage == 'catalan') {
101
+ const rand = Math.random();
102
+ if (rand > 0.9) {
103
+ word = '¿' + word;
104
+ spanishSentenceTracker = '?';
105
+ } else if (rand > 0.8) {
106
+ word = '¡' + word;
107
+ spanishSentenceTracker = '!';
108
+ }
109
+ }
110
+ } else if (
111
+ (Math.random() < 0.1 && lastChar != '.' && lastChar != ',' && index != maxindex - 2) ||
112
+ index == maxindex - 1
113
+ ) {
114
+ if (currentLanguage == 'spanish' || currentLanguage == 'catalan') {
115
+ if (spanishSentenceTracker == '?' || spanishSentenceTracker == '!') {
116
+ word += spanishSentenceTracker;
117
+ spanishSentenceTracker = '';
118
+ }
119
+ } else {
120
+ const rand = Math.random();
121
+ if (rand <= 0.8) {
122
+ if (currentLanguage == 'kurdish') {
123
+ word += '.';
124
+ } else {
125
+ word += '.';
126
+ }
127
+ } else if (rand > 0.8 && rand < 0.9) {
128
+ if (currentLanguage == 'french') {
129
+ word = '?';
130
+ } else if (
131
+ currentLanguage == 'arabic' ||
132
+ currentLanguage == 'persian' ||
133
+ currentLanguage == 'urdu' ||
134
+ currentLanguage == 'kurdish'
135
+ ) {
136
+ word += '؟';
137
+ } else if (currentLanguage == 'greek') {
138
+ word += ';';
139
+ } else {
140
+ word += '?';
141
+ }
142
+ } else {
143
+ if (currentLanguage == 'french') {
144
+ word = '!';
145
+ } else {
146
+ word += '!';
147
+ }
148
+ }
149
+ }
150
+ } else if (
151
+ Math.random() < 0.01 &&
152
+ lastChar != ',' &&
153
+ lastChar != '.' &&
154
+ currentLanguage !== 'russian'
155
+ ) {
156
+ word = `"${word}"`;
157
+ } else if (
158
+ Math.random() < 0.011 &&
159
+ lastChar != ',' &&
160
+ lastChar != '.' &&
161
+ currentLanguage !== 'russian' &&
162
+ currentLanguage !== 'ukrainian'
163
+ ) {
164
+ word = `'${word}'`;
165
+ } else if (Math.random() < 0.012 && lastChar != ',' && lastChar != '.') {
166
+ if (currentLanguage == 'code') {
167
+ const r = Math.random();
168
+ if (r < 0.25) {
169
+ word = `(${word})`;
170
+ } else if (r < 0.5) {
171
+ word = `{${word}}`;
172
+ } else if (r < 0.75) {
173
+ word = `[${word}]`;
174
+ } else {
175
+ word = `<${word}>`;
176
+ }
177
+ } else {
178
+ word = `(${word})`;
179
+ }
180
+ } else if (
181
+ Math.random() < 0.013 &&
182
+ lastChar != ',' &&
183
+ lastChar != '.' &&
184
+ lastChar != ';' &&
185
+ lastChar != '؛' &&
186
+ lastChar != ':'
187
+ ) {
188
+ if (currentLanguage == 'french') {
189
+ word = ':';
190
+ } else if (currentLanguage == 'greek') {
191
+ word = '·';
192
+ } else {
193
+ word += ':';
194
+ }
195
+ } else if (Math.random() < 0.014 && lastChar != ',' && lastChar != '.' && previousWord != '-') {
196
+ word = '-';
197
+ } else if (
198
+ Math.random() < 0.015 &&
199
+ lastChar != ',' &&
200
+ lastChar != '.' &&
201
+ lastChar != ';' &&
202
+ lastChar != '؛' &&
203
+ lastChar != ':'
204
+ ) {
205
+ if (currentLanguage == 'french') {
206
+ word = ';';
207
+ } else if (currentLanguage == 'greek') {
208
+ word = '·';
209
+ } else if (currentLanguage == 'arabic' || currentLanguage == 'kurdish') {
210
+ word += '؛';
211
+ } else {
212
+ word += ';';
213
+ }
214
+ } else if (Math.random() < 0.2 && lastChar != ',') {
215
+ if (
216
+ currentLanguage == 'arabic' ||
217
+ currentLanguage == 'urdu' ||
218
+ currentLanguage == 'persian' ||
219
+ currentLanguage == 'kurdish'
220
+ ) {
221
+ word += '،';
222
+ } else {
223
+ word += ',';
224
+ }
225
+ } else if (Math.random() < 0.25 && currentLanguage == 'code') {
226
+ const specials = ['{', '}', '[', ']', '(', ')', ';', '=', '+', '%', '/'];
227
+
228
+ word = Misc.randomElementFromArray(specials);
229
+ } else if (
230
+ Math.random() < 0.5 &&
231
+ currentLanguage === 'english' &&
232
+ (await EnglishPunctuation.check(word))
233
+ ) {
234
+ word = await applyEnglishPunctuationToWord(word);
235
+ }
236
+ }
237
+ return word;
238
+ }
239
+
240
+ async function applyEnglishPunctuationToWord(word: string): Promise<string> {
241
+ return EnglishPunctuation.replace(word);
242
+ }
243
+
244
+ export function startTest(): boolean {
245
+ if (PageTransition.get()) {
246
+ return false;
247
+ }
248
+ startTestSubject.next(true);
249
+ TestActive.set(true);
250
+ Replay.startReplayRecording();
251
+ Replay.replayGetWordsList(TestWords.words.list);
252
+ TestInput.resetKeypressTimings();
253
+ TimerProgress.update();
254
+ TestTimer.clear();
255
+
256
+ TestStats.setStart(performance.now());
257
+ TestTimer.start();
258
+ return true;
259
+ }
260
+
261
+ interface RestartOptions {
262
+ withSameWordset?: boolean;
263
+ nosave?: boolean;
264
+ event?: { shiftKey: any };
265
+ practiseMissed?: boolean;
266
+ noAnim?: boolean;
267
+ }
268
+
269
+ export function restart(options = {} as RestartOptions): void {
270
+ const defaultOptions = {
271
+ withSameWordset: false,
272
+ practiseMissed: false,
273
+ noAnim: false,
274
+ nosave: false,
275
+ };
276
+
277
+ options = { ...defaultOptions, ...options };
278
+
279
+ if (TestUI.testRestarting || TestUI.resultCalculating) {
280
+ event?.preventDefault();
281
+ return;
282
+ }
283
+ if (ActivePage.get() == 'test' && !TestUI.resultVisible) {
284
+ if (!ManualRestart.get()) {
285
+ if (TestWords.hasTab && !options.event?.shiftKey && Config.quickRestart !== 'esc') {
286
+ return;
287
+ }
288
+ if (Config.mode !== 'zen') event?.preventDefault();
289
+ if (!Misc.canQuickRestart(Config.mode, Config.words, Config.time, CustomText)) {
290
+ return;
291
+ }
292
+ }
293
+ }
294
+ if (TestActive.get()) {
295
+ if (
296
+ Config.repeatQuotes === 'typing' &&
297
+ Config.mode === 'quote' &&
298
+ Config.language.replace(/_\d*k$/g, '') === TestWords.randomQuote.language
299
+ ) {
300
+ options.withSameWordset = true;
301
+ }
302
+ if (TestState.isRepeated) {
303
+ options.withSameWordset = true;
304
+ }
305
+
306
+ TestInput.pushKeypressesToHistory();
307
+ const testSeconds = TestStats.calculateTestSeconds(performance.now());
308
+ const afkseconds = TestStats.calculateAfkSeconds(testSeconds);
309
+ let tt = testSeconds - afkseconds;
310
+ if (tt < 0) tt = 0;
311
+ TestStats.incrementIncompleteSeconds(tt);
312
+ TestStats.incrementRestartCount();
313
+ }
314
+
315
+ let repeatWithPace = false;
316
+ if (TestUI.resultVisible && Config.repeatedPace && options.withSameWordset) {
317
+ repeatWithPace = true;
318
+ }
319
+
320
+ ManualRestart.reset();
321
+ TestTimer.clear();
322
+ TestStats.restart();
323
+ TestInput.restart();
324
+ TestInput.corrected.reset();
325
+ startTestSubject.next(false);
326
+ Caret.hide();
327
+ TestActive.set(false);
328
+ TestInput.setBailout(false);
329
+
330
+ if (ActivePage.get() == 'test' && window.scrollY > 0) {
331
+ window.scrollTo({ top: 0, behavior: 'smooth' });
332
+ }
333
+
334
+ wordsInput.value = ' ';
335
+
336
+ TestUI.reset();
337
+
338
+ TestUI.setResultVisible(false);
339
+ PageTransition.set(true);
340
+ TestUI.setTestRestarting(true);
341
+ (async () => {
342
+ if (ActivePage.get() == 'test') {
343
+ Focus.set(false);
344
+ }
345
+ TestUI.focusWords();
346
+ wordsInput.value = ' ';
347
+ let shouldQuoteRepeat = false;
348
+ if (Config.mode === 'quote' && Config.repeatQuotes === 'typing' && failReason !== '') {
349
+ shouldQuoteRepeat = true;
350
+ }
351
+
352
+ if (!options.withSameWordset && !shouldQuoteRepeat) {
353
+ TestState.setRepeated(false);
354
+ TestState.setPaceRepeat(repeatWithPace);
355
+ TestWords.setHasTab(false);
356
+ try {
357
+ await init();
358
+ } catch {
359
+ TestUI.setTestRestarting(false);
360
+ return;
361
+ }
362
+ } else {
363
+ TestState.setRepeated(true);
364
+ TestState.setPaceRepeat(repeatWithPace);
365
+ TestActive.set(false);
366
+ TestWords.words.resetCurrentIndex();
367
+ TestInput.input.reset();
368
+ TestUI.showWords();
369
+ }
370
+ failReason = '';
371
+
372
+ let fbtext = '';
373
+ if (Config.funbox !== 'none') {
374
+ fbtext = ' ' + Config.funbox;
375
+ }
376
+
377
+ TestUI.setTestRestarting(false);
378
+ TestTimer.clear();
379
+ PageTransition.set(false);
380
+
381
+ // Added by me
382
+ startTest();
383
+ })();
384
+ }
385
+
386
+ function applyFunboxesToWord(word: string, wordset?: Wordset.Wordset): string {
387
+ if (Config.funbox === 'rAnDoMcAsE') {
388
+ let randomcaseword = '';
389
+ for (let i = 0; i < word.length; i++) {
390
+ if (i % 2 != 0) {
391
+ randomcaseword += word[i].toUpperCase();
392
+ } else {
393
+ randomcaseword += word[i];
394
+ }
395
+ }
396
+ word = randomcaseword;
397
+ } else if (Config.funbox === 'capitals') {
398
+ word = Misc.capitalizeFirstLetterOfEachWord(word);
399
+ } else if (Config.funbox === 'gibberish') {
400
+ word = Misc.getGibberish();
401
+ } else if (Config.funbox === 'arrows') {
402
+ word = Misc.getArrows();
403
+ } else if (Config.funbox === 'tenKeyMode') {
404
+ word = Misc.getNumbers(Config.punctuation ? 4 : 5, Config.punctuation ? 1 : 2);
405
+ if (Config.language.split('_')[0] === 'kurdish') {
406
+ word = Misc.convertNumberToArabicIndic(word);
407
+ }
408
+ } else if (Config.funbox === '58008') {
409
+ word = Misc.getNumbers(7);
410
+ if (Config.language.split('_')[0] === 'kurdish') {
411
+ word = Misc.convertNumberToArabicIndic(word);
412
+ }
413
+ } else if (Config.funbox === 'specials') {
414
+ word = Misc.getSpecials();
415
+ } else if (Config.funbox === 'ascii') {
416
+ word = Misc.getASCII();
417
+ } else if (wordset !== undefined && Config.funbox === 'weakspot') {
418
+ word = WeakSpot.getWord(wordset);
419
+ }
420
+ return word;
421
+ }
422
+
423
+ async function getNextWord(
424
+ wordset: Wordset.Wordset,
425
+ language: MonkeyTypes.LanguageObject,
426
+ wordsBound: number
427
+ ): Promise<string> {
428
+ let randomWord = wordset.randomWord();
429
+ const previousWord = TestWords.words.get(TestWords.words.length - 1, true);
430
+ const previousWord2 = TestWords.words.get(TestWords.words.length - 2, true);
431
+
432
+ // Added by TG
433
+ if (Config.customTGQuotes) {
434
+ if (TestWords.words.length - TestWords.words.currentIndex <= 40) {
435
+ const randomQuote = QuotesController.getRandomQuote();
436
+ let rq: MonkeyTypes.Quote | undefined | null = undefined;
437
+ rq = randomQuote;
438
+
439
+ if (rq !== undefined && rq !== null) {
440
+ rq.text = rq.text.replace(/ +/gm, ' ');
441
+ rq.text = rq.text.replace(/\\\\t/gm, '\t');
442
+ rq.text = rq.text.replace(/\\\\n/gm, '\n');
443
+ rq.text = rq.text.replace(/\\t/gm, '\t');
444
+ rq.text = rq.text.replace(/\\n/gm, '\n');
445
+ rq.text = rq.text.replace(/( *(\r\n|\r|\n) *)/g, '\n ');
446
+ rq.text = rq.text.replace(/…/g, '...');
447
+ rq.text = rq.text.trim();
448
+ rq.textSplit = rq.text.split(' ');
449
+ rq.language = Config.language.replace(/_\d*k$/g, '');
450
+ TestWords.setRandomQuote(rq);
451
+ }
452
+
453
+ return TestWords.randomQuote.text;
454
+ }
455
+ } else if (Config.mode === 'quote') {
456
+ randomWord = TestWords.randomQuote.textSplit?.[TestWords.words.length] ?? '';
457
+ } else if (Config.mode == 'custom' && !CustomText.isWordRandom && !CustomText.isTimeRandom) {
458
+ randomWord = CustomText.text[TestWords.words.length];
459
+ } else {
460
+ let regenarationCount = 0; //infinite loop emergency stop button
461
+ while (
462
+ regenarationCount < 100 &&
463
+ (previousWord == randomWord ||
464
+ previousWord2 == randomWord ||
465
+ (Config.mode !== 'custom' && !Config.punctuation && randomWord == 'I') ||
466
+ (Config.mode !== 'custom' &&
467
+ !Config.punctuation &&
468
+ /[-=_+[\]{};'\\:"|,./<>?]/i.test(randomWord)))
469
+ ) {
470
+ regenarationCount++;
471
+ randomWord = wordset.randomWord();
472
+ }
473
+ }
474
+
475
+ if (randomWord === undefined) {
476
+ randomWord = wordset.randomWord();
477
+ }
478
+
479
+ randomWord = randomWord.replace(/ +/gm, ' ');
480
+ randomWord = randomWord.replace(/^ | $/gm, '');
481
+ randomWord = applyFunboxesToWord(randomWord, wordset);
482
+
483
+ if (Config.punctuation) {
484
+ randomWord = await punctuateWord(
485
+ TestWords.words.get(TestWords.words.length - 1),
486
+ randomWord,
487
+ TestWords.words.length,
488
+ wordsBound
489
+ );
490
+ }
491
+ if (Config.numbers) {
492
+ if (Math.random() < 0.1) {
493
+ randomWord = Misc.getNumbers(4);
494
+
495
+ if (Config.language.split('_')[0] === 'kurdish') {
496
+ randomWord = Misc.convertNumberToArabicIndic(randomWord);
497
+ }
498
+ }
499
+ }
500
+
501
+ return randomWord;
502
+ }
503
+
504
+ export async function init(): Promise<void> {
505
+ TestActive.set(false);
506
+ TestWords.words.reset();
507
+ TestUI.setCurrentWordElementIndex(0);
508
+
509
+ TestInput.input.resetHistory();
510
+ TestInput.input.resetCurrent();
511
+
512
+ let language;
513
+ try {
514
+ language = await Misc.getLanguage(Config.language);
515
+ } catch (e) {
516
+ Misc.emitQuoteNetworkError();
517
+ throw e;
518
+ }
519
+
520
+ let wordsBound = 100;
521
+ if (Config.showAllLines) {
522
+ if (Config.mode === 'quote') {
523
+ wordsBound = 100;
524
+ } else if (Config.mode === 'custom') {
525
+ if (CustomText.isWordRandom) {
526
+ wordsBound = CustomText.word;
527
+ } else if (CustomText.isTimeRandom) {
528
+ wordsBound = 100;
529
+ } else {
530
+ wordsBound = CustomText.text.length;
531
+ }
532
+ } else if (Config.mode != 'time') {
533
+ wordsBound = Config.words;
534
+ }
535
+ } else {
536
+ if (Config.mode === 'words' && Config.words < wordsBound) {
537
+ wordsBound = Config.words;
538
+ }
539
+ if (Config.mode == 'custom' && CustomText.isWordRandom && CustomText.word < wordsBound) {
540
+ wordsBound = CustomText.word;
541
+ }
542
+ if (Config.mode == 'custom' && CustomText.isTimeRandom) {
543
+ wordsBound = 100;
544
+ }
545
+ if (
546
+ Config.mode == 'custom' &&
547
+ !CustomText.isWordRandom &&
548
+ !CustomText.isTimeRandom &&
549
+ CustomText.text.length < wordsBound
550
+ ) {
551
+ wordsBound = CustomText.text.length;
552
+ }
553
+ }
554
+
555
+ if (
556
+ (Config.mode === 'custom' && CustomText.isWordRandom && CustomText.word == 0) ||
557
+ (Config.mode === 'custom' && CustomText.isTimeRandom && CustomText.time == 0)
558
+ ) {
559
+ wordsBound = 100;
560
+ }
561
+
562
+ if (Config.mode === 'words' && Config.words === 0) {
563
+ wordsBound = 100;
564
+ }
565
+ if (Config.funbox === 'plus_one') {
566
+ wordsBound = 2;
567
+ if (Config.mode === 'words' && Config.words < wordsBound) {
568
+ wordsBound = Config.words;
569
+ }
570
+ }
571
+ if (Config.funbox === 'plus_two') {
572
+ wordsBound = 3;
573
+ if (Config.mode === 'words' && Config.words < wordsBound) {
574
+ wordsBound = Config.words;
575
+ }
576
+ }
577
+
578
+ if (
579
+ (Config.mode == 'time' || Config.mode == 'words' || Config.mode == 'custom') &&
580
+ !Config.customTGQuotes
581
+ ) {
582
+ let wordList = language.words;
583
+ if (Config.mode == 'custom') {
584
+ wordList = CustomText.text;
585
+ }
586
+ const wordset = Wordset.withWords(wordList, Config.funbox);
587
+
588
+ if ((Config.funbox == 'wikipedia' || Config.funbox == 'poetry') && Config.mode != 'custom') {
589
+ // Skip word generation for wikipedia/poetry funboxes in non-custom mode
590
+ } else {
591
+ for (let i = 0; i < wordsBound; i++) {
592
+ const randomWord = await getNextWord(wordset, language, wordsBound);
593
+
594
+ if (/\t/g.test(randomWord)) {
595
+ TestWords.setHasTab(true);
596
+ }
597
+
598
+ const te = randomWord.replace('\n', '\n ').trim();
599
+
600
+ if (/ +/.test(te)) {
601
+ const randomList = te.split(' ');
602
+ let id = 0;
603
+ while (id < randomList.length) {
604
+ TestWords.words.push(randomList[id]);
605
+ id++;
606
+
607
+ if (
608
+ TestWords.words.length == wordsBound &&
609
+ Config.mode == 'custom' &&
610
+ CustomText.isWordRandom
611
+ ) {
612
+ break;
613
+ }
614
+ }
615
+ if (Config.mode == 'custom' && !CustomText.isWordRandom && !CustomText.isTimeRandom) {
616
+ // Custom mode with fixed text - no adjustment needed
617
+ } else {
618
+ i = TestWords.words.length - 1;
619
+ }
620
+ } else {
621
+ TestWords.words.push(randomWord);
622
+ }
623
+ }
624
+ }
625
+ }
626
+
627
+ // Added by TG
628
+ if (Config.customTGQuotes) {
629
+ let quotesCollection;
630
+
631
+ try {
632
+ quotesCollection = await QuotesController.getQuotes(
633
+ Config.language,
634
+ Config.testVersion,
635
+ Config.quoteLength
636
+ );
637
+ } catch (e) {
638
+ Misc.emitQuoteNetworkError();
639
+ throw e;
640
+ }
641
+
642
+ let rq: MonkeyTypes.Quote | undefined | null = undefined;
643
+ const randomQuote = QuotesController.getRandomQuote();
644
+ rq = randomQuote;
645
+
646
+ if (rq === undefined || rq === null) return;
647
+
648
+ rq.text = rq.text.replace(/ +/gm, ' ');
649
+ rq.text = rq.text.replace(/\\\\t/gm, '\t');
650
+ rq.text = rq.text.replace(/\\\\n/gm, '\n');
651
+ rq.text = rq.text.replace(/\\t/gm, '\t');
652
+ rq.text = rq.text.replace(/\\n/gm, '\n');
653
+ rq.text = rq.text.replace(/( *(\r\n|\r|\n) *)/g, '\n ');
654
+ rq.text = rq.text.replace(/…/g, '...');
655
+ rq.text = rq.text.trim();
656
+ rq.textSplit = rq.text.split(' ');
657
+ rq.language = Config.language.replace(/_\d*k$/g, '');
658
+
659
+ TestWords.setRandomQuote(rq);
660
+
661
+ const w = TestWords.randomQuote.textSplit;
662
+
663
+ if (w === undefined) return;
664
+
665
+ for (let i = 0; i < w.length; i++) {
666
+ if (/\t/g.test(w[i])) {
667
+ TestWords.setHasTab(true);
668
+ }
669
+
670
+ w[i] = applyFunboxesToWord(w[i]);
671
+
672
+ TestWords.words.push(w[i]);
673
+ }
674
+ }
675
+
676
+ TestUI.showWords();
677
+ }
678
+
679
+ export async function addWord(): Promise<void> {
680
+ let bound = 100;
681
+ if (Config.funbox === 'plus_one') bound = 1;
682
+ if (Config.funbox === 'plus_two') bound = 2;
683
+ if (
684
+ TestWords.words.length - TestInput.input.history.length > bound ||
685
+ (Config.mode === 'words' && TestWords.words.length >= Config.words && Config.words > 0) ||
686
+ (Config.mode === 'custom' &&
687
+ CustomText.isWordRandom &&
688
+ TestWords.words.length >= CustomText.word &&
689
+ CustomText.word != 0) ||
690
+ (Config.mode === 'custom' &&
691
+ !CustomText.isWordRandom &&
692
+ !CustomText.isTimeRandom &&
693
+ TestWords.words.length >= CustomText.text.length) ||
694
+ (Config.mode === 'quote' &&
695
+ TestWords.words.length >= (TestWords.randomQuote.textSplit?.length ?? 0))
696
+ ) {
697
+ return;
698
+ }
699
+
700
+ const language: MonkeyTypes.LanguageObject =
701
+ Config.mode !== 'custom'
702
+ ? await Misc.getCurrentLanguage(Config.language)
703
+ : {
704
+ //borrow the direction of the current language
705
+ ...(await Misc.getCurrentLanguage(Config.language)),
706
+ words: CustomText.text,
707
+ };
708
+ const wordset = Wordset.withWords(language.words, Config.funbox);
709
+
710
+ const randomWord = await getNextWord(wordset, language, bound);
711
+
712
+ const split = randomWord.split(' ');
713
+
714
+ if (!Config.customTGQuotes) {
715
+ if (split.length > 1) {
716
+ split.forEach(word => {
717
+ TestWords.words.push(word);
718
+ TestUI.addWord(word);
719
+ });
720
+ } else {
721
+ TestWords.words.push(randomWord);
722
+ TestUI.addWord(randomWord);
723
+ }
724
+ } else {
725
+ if (split.length > 1) {
726
+ split.forEach(word => {
727
+ TestWords.words.push(word);
728
+ TestUI.addWord(word);
729
+ });
730
+ }
731
+ }
732
+ }
733
+
734
+ interface CompletedEvent extends MonkeyTypes.Result<MonkeyTypes.Mode> {
735
+ keySpacing: number[] | 'toolong';
736
+ keyDuration: number[] | 'toolong';
737
+ customText: MonkeyTypes.CustomText;
738
+ smoothConsistency: number;
739
+ wpmConsistency: number;
740
+ lang: string;
741
+ challenge?: string | null;
742
+ }
743
+
744
+ type PartialCompletedEvent = Omit<Partial<CompletedEvent>, 'chartData'> & {
745
+ chartData: Partial<MonkeyTypes.ChartData>;
746
+ };
747
+
748
+ interface RetrySaving {
749
+ completedEvent: CompletedEvent | null;
750
+ canRetry: boolean;
751
+ }
752
+
753
+ const retrySaving: RetrySaving = {
754
+ completedEvent: null,
755
+ canRetry: false,
756
+ };
757
+
758
+ function buildCompletedEvent(difficultyFailed: boolean): CompletedEvent {
759
+ //build completed event object
760
+ const completedEvent: PartialCompletedEvent = {
761
+ wpm: undefined,
762
+ rawWpm: undefined,
763
+ kph: -1,
764
+ charStats: undefined,
765
+ acc: undefined,
766
+ mode: Config.mode,
767
+ mode2: undefined as never,
768
+ quoteLength: -1,
769
+ punctuation: Config.punctuation,
770
+ numbers: Config.numbers,
771
+ lazyMode: Config.lazyMode,
772
+ timestamp: Date.now(),
773
+ language: Config.language,
774
+ restartCount: TestStats.restartCount,
775
+ incompleteTestSeconds:
776
+ TestStats.incompleteSeconds < 0 ? 0 : Misc.roundTo2(TestStats.incompleteSeconds),
777
+ difficulty: Config.difficulty,
778
+ blindMode: Config.blindMode,
779
+ tags: undefined,
780
+ keySpacing: TestInput.keypressTimings.spacing.array,
781
+ keyDuration: TestInput.keypressTimings.duration.array,
782
+ consistency: undefined,
783
+ keyConsistency: undefined,
784
+ funbox: Config.funbox,
785
+ bailedOut: TestInput.bailout,
786
+ chartData: {
787
+ wpm: TestInput.wpmHistory,
788
+ raw: undefined,
789
+ err: undefined,
790
+ },
791
+ customText: undefined,
792
+ testDuration: undefined,
793
+ afkDuration: undefined,
794
+ };
795
+
796
+ // stats
797
+ const stats = TestStats.calculateStats();
798
+ if (stats.time % 1 != 0 && Config.mode !== 'time') {
799
+ TestStats.setLastSecondNotRound();
800
+ }
801
+ TestStats.setLastTestWpm(stats.wpm);
802
+ completedEvent.wpm = stats.wpm;
803
+ completedEvent.rawWpm = stats.wpmRaw;
804
+ completedEvent.charStats = [
805
+ stats.correctChars + stats.correctSpaces,
806
+ stats.incorrectChars,
807
+ stats.extraChars,
808
+ stats.missedChars,
809
+ ];
810
+ completedEvent.acc = stats.acc;
811
+ if (Config.funbox === 'tenKeyMode') {
812
+ completedEvent.kph = stats.wpm * 5 * 60;
813
+ }
814
+
815
+ // if the last second was not rounded, add another data point to the history
816
+ if (TestStats.lastSecondNotRound && !difficultyFailed) {
817
+ const wpmAndRaw = TestStats.calculateWpmAndRaw();
818
+ TestInput.pushToWpmHistory(wpmAndRaw.wpm);
819
+ TestInput.pushToRawHistory(wpmAndRaw.raw);
820
+ TestInput.pushKeypressesToHistory();
821
+ }
822
+
823
+ //consistency
824
+ const rawPerSecond = TestInput.keypressPerSecond.map(f => Math.round((f.count / 5) * 60));
825
+ const stddev = Misc.stdDev(rawPerSecond);
826
+ const avg = Misc.mean(rawPerSecond);
827
+ let consistency = Misc.roundTo2(Misc.kogasa(stddev / avg));
828
+ let keyConsistencyArray =
829
+ TestInput.keypressTimings.spacing.array === 'toolong'
830
+ ? []
831
+ : TestInput.keypressTimings.spacing.array.slice();
832
+ if (keyConsistencyArray.length > 0) {
833
+ keyConsistencyArray = keyConsistencyArray.slice(0, keyConsistencyArray.length - 1);
834
+ }
835
+ let keyConsistency = Misc.roundTo2(
836
+ Misc.kogasa(Misc.stdDev(keyConsistencyArray) / Misc.mean(keyConsistencyArray))
837
+ );
838
+ if (!consistency || isNaN(consistency)) {
839
+ consistency = 0;
840
+ }
841
+ if (!keyConsistency || isNaN(keyConsistency)) {
842
+ keyConsistency = 0;
843
+ }
844
+ completedEvent.keyConsistency = keyConsistency;
845
+ completedEvent.consistency = consistency;
846
+ const smoothedraw = Misc.smooth(rawPerSecond, 1);
847
+ completedEvent.chartData.raw = smoothedraw;
848
+ completedEvent.chartData.unsmoothedRaw = rawPerSecond;
849
+
850
+ //smoothed consistency
851
+ const stddev2 = Misc.stdDev(smoothedraw);
852
+ const avg2 = Misc.mean(smoothedraw);
853
+ const smoothConsistency = Misc.roundTo2(Misc.kogasa(stddev2 / avg2));
854
+ completedEvent.smoothConsistency = smoothConsistency;
855
+
856
+ //wpm consistency
857
+ const stddev3 = Misc.stdDev(completedEvent.chartData.wpm ?? []);
858
+ const avg3 = Misc.mean(completedEvent.chartData.wpm ?? []);
859
+ const wpmConsistency = Misc.roundTo2(Misc.kogasa(stddev3 / avg3));
860
+ completedEvent.wpmConsistency = wpmConsistency;
861
+
862
+ completedEvent.testDuration = parseFloat(stats.time.toString());
863
+ completedEvent.afkDuration = TestStats.calculateAfkSeconds(completedEvent.testDuration);
864
+
865
+ completedEvent.chartData.err = [];
866
+ for (let i = 0; i < TestInput.keypressPerSecond.length; i++) {
867
+ completedEvent.chartData.err.push(TestInput.keypressPerSecond[i].errors);
868
+ }
869
+
870
+ if (Config.mode === 'quote') {
871
+ completedEvent.quoteLength = TestWords.randomQuote.group;
872
+ completedEvent.language = Config.language.replace(/_\d*k$/g, '');
873
+ } else {
874
+ delete completedEvent.quoteLength;
875
+ }
876
+
877
+ // @ts-expect-error TODO fix this
878
+ completedEvent.mode2 = Misc.getMode2(Config, TestWords.randomQuote);
879
+
880
+ if (Config.mode === 'custom') {
881
+ completedEvent.customText = <MonkeyTypes.CustomText>{};
882
+ completedEvent.customText.textLen = CustomText.text.length;
883
+ completedEvent.customText.isWordRandom = CustomText.isWordRandom;
884
+ completedEvent.customText.isTimeRandom = CustomText.isTimeRandom;
885
+ completedEvent.customText.word = CustomText.word;
886
+ completedEvent.customText.time = CustomText.time;
887
+ } else {
888
+ delete completedEvent.customText;
889
+ }
890
+
891
+ //tags
892
+ const activeTagsIds: string[] = [];
893
+ completedEvent.tags = activeTagsIds;
894
+
895
+ if (completedEvent.mode != 'custom') delete completedEvent.customText;
896
+
897
+ return <CompletedEvent>completedEvent;
898
+ }
899
+
900
+ export function stopTest(): void {
901
+ TestActive.set(false);
902
+ Focus.set(false);
903
+ Caret.hide();
904
+ OutOfFocus.hide();
905
+ TestTimer.clear();
906
+ }
907
+
908
+ export async function finish(difficultyFailed = false): Promise<void> {
909
+ if (!TestActive.get()) return;
910
+
911
+ startTestSubject.next(false);
912
+
913
+ TestInput.recordKeypressSpacing(); //this is needed in case there is afk time at the end - to make sure test duration makes sense
914
+
915
+ TestUI.setResultCalculating(true);
916
+ TestUI.setResultVisible(true);
917
+ TestStats.setEnd(performance.now());
918
+ TestActive.set(false);
919
+ Focus.set(false);
920
+ Caret.hide();
921
+ OutOfFocus.hide();
922
+ TestTimer.clear();
923
+
924
+ if (TestInput.burstHistory.length !== TestInput.input.getHistory().length) {
925
+ const burst = TestStats.calculateBurst();
926
+ TestInput.pushBurstToHistory(burst);
927
+ }
928
+
929
+ if (Config.mode == 'zen' || TestInput.bailout) {
930
+ TestStats.removeAfkData();
931
+ }
932
+
933
+ const completedEvent = buildCompletedEvent(difficultyFailed);
934
+
935
+ function countUndefined(input: unknown): number {
936
+ if (typeof input === 'undefined') {
937
+ return 1;
938
+ } else if (typeof input === 'object' && input !== null) {
939
+ return Object.values(input).reduce((a, b) => a + countUndefined(b), 0) as number;
940
+ } else {
941
+ return 0;
942
+ }
943
+ }
944
+
945
+ if (countUndefined(completedEvent) > 0) {
946
+ console.log(completedEvent);
947
+ return;
948
+ }
949
+
950
+ const kps = TestInput.keypressPerSecond.slice(-5);
951
+ let afkDetected = kps.every(second => second.afk);
952
+ if (TestInput.bailout) afkDetected = false;
953
+
954
+ let tooShort = false;
955
+ let dontSave = false;
956
+ if (difficultyFailed) {
957
+ dontSave = true;
958
+ } else if (afkDetected) {
959
+ dontSave = true;
960
+ } else if (TestState.isRepeated) {
961
+ dontSave = true;
962
+ } else if (
963
+ (Config.mode === 'time' && completedEvent.mode2 < 15 && completedEvent.mode2 > 0) ||
964
+ (Config.mode === 'time' && completedEvent.mode2 == 0 && completedEvent.testDuration < 15) ||
965
+ (Config.mode === 'words' && completedEvent.mode2 < 10 && completedEvent.mode2 > 0) ||
966
+ (Config.mode === 'words' && completedEvent.mode2 == 0 && completedEvent.testDuration < 15) ||
967
+ (Config.mode === 'custom' &&
968
+ !CustomText.isWordRandom &&
969
+ !CustomText.isTimeRandom &&
970
+ CustomText.text.length < 10) ||
971
+ (Config.mode === 'custom' &&
972
+ CustomText.isWordRandom &&
973
+ !CustomText.isTimeRandom &&
974
+ CustomText.word < 10) ||
975
+ (Config.mode === 'custom' &&
976
+ !CustomText.isWordRandom &&
977
+ CustomText.isTimeRandom &&
978
+ CustomText.time < 15) ||
979
+ (Config.mode === 'zen' && completedEvent.testDuration < 15)
980
+ ) {
981
+ tooShort = true;
982
+ dontSave = true;
983
+ } else if (completedEvent.wpm < 0 || completedEvent.wpm > 350) {
984
+ TestStats.setInvalid();
985
+ dontSave = true;
986
+ } else if (completedEvent.acc < 75 || completedEvent.acc > 100) {
987
+ TestStats.setInvalid();
988
+ dontSave = true;
989
+ }
990
+
991
+ TestStats.setLastResult(completedEvent);
992
+
993
+ resultSub.next({
994
+ completedEvent: completedEvent,
995
+ difficultyFailed: difficultyFailed,
996
+ failReason: failReason,
997
+ afkDetected: afkDetected,
998
+ isRepeated: TestState.isRepeated,
999
+ tooShort: tooShort,
1000
+ randomQuote: TestWords.randomQuote,
1001
+ dontSave: dontSave,
1002
+ wpmHistory: TestInput.wpmHistory,
1003
+ });
1004
+
1005
+ if (completedEvent.chartData !== 'toolong') {
1006
+ delete completedEvent.chartData.unsmoothedRaw;
1007
+ }
1008
+
1009
+ if (completedEvent.testDuration > 122) {
1010
+ completedEvent.chartData = 'toolong';
1011
+ completedEvent.keySpacing = 'toolong';
1012
+ completedEvent.keyDuration = 'toolong';
1013
+ TestInput.setKeypressTimingsTooLong();
1014
+ }
1015
+
1016
+ if (
1017
+ Config.difficulty == 'normal' ||
1018
+ ((Config.difficulty == 'master' || Config.difficulty == 'expert') && !difficultyFailed)
1019
+ ) {
1020
+ TestStats.resetIncomplete();
1021
+ }
1022
+ }
1023
+
1024
+ export function fail(reason: string): void {
1025
+ failReason = reason;
1026
+ TestInput.pushKeypressesToHistory();
1027
+ finish(true);
1028
+ const testSeconds = TestStats.calculateTestSeconds(performance.now());
1029
+ const afkseconds = TestStats.calculateAfkSeconds(testSeconds);
1030
+ let tt = testSeconds - afkseconds;
1031
+ if (tt < 0) tt = 0;
1032
+ TestStats.incrementIncompleteSeconds(tt);
1033
+ TestStats.incrementRestartCount();
1034
+ }
1035
+
1036
+ TimerEvent.subscribe((eventKey, eventValue) => {
1037
+ if (eventKey === 'fail' && eventValue !== undefined) fail(eventValue);
1038
+ if (eventKey === 'finish') finish();
1039
+ });