as-test 0.5.1 → 0.5.3

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/assembly/index.ts CHANGED
@@ -8,20 +8,38 @@ import {
8
8
  __ALL_POINTS,
9
9
  CoverPoint,
10
10
  } from "as-test/assembly/coverage";
11
- import { JSON } from "json-as";
12
11
  import { Log } from "./src/log";
13
12
  import { sendFileEnd, sendFileStart, sendReport } from "./util/wipc";
13
+ import { quote } from "./util/json";
14
14
 
15
15
  let entrySuites: Suite[] = [];
16
16
 
17
17
  // @ts-ignore
18
18
  const FILE = isDefined(ENTRY_FILE) ? ENTRY_FILE : "unknown";
19
+
20
+ class ImportSnapshot {
21
+ hasValue: bool = false;
22
+ value: u32 = 0;
23
+ }
24
+
25
+ const DEFAULT_IMPORT_SNAPSHOT_VERSION = "default";
26
+
19
27
  // Globals
20
28
  // @ts-ignore
21
29
  @global let __mock_global: Map<string, u32> = new Map<string, u32>();
22
30
  // @ts-ignore
23
31
  @global let __mock_import: Map<string, u32> = new Map<string, u32>();
24
32
  // @ts-ignore
33
+ @global let __mock_import_snapshots: Map<string, ImportSnapshot> = new Map<
34
+ string,
35
+ ImportSnapshot
36
+ >();
37
+ // @ts-ignore
38
+ @global let __mock_import_target_by_index: Map<u32, string> = new Map<
39
+ u32,
40
+ string
41
+ >();
42
+ // @ts-ignore
25
43
  @global let suites: Suite[] = [];
26
44
  // @ts-ignore
27
45
  @global let depth: i32 = -1;
@@ -231,13 +249,63 @@ export function afterEach(callback: () => void): void {
231
249
  * @param {Function} oldFn - name of function to mock
232
250
  * @param {Function} newFn - the function to substitute it with
233
251
  */
234
- export function mockFn(oldFn: Function, newFn: Function): void {}
252
+ export function mockFn<T extends Function, U extends Function>(
253
+ oldFn: T,
254
+ newFn: U,
255
+ ): void {}
256
+
257
+ /**
258
+ * Restore references previously mocked with `mockFn`.
259
+ * This applies to calls that appear after `unmockFn` in source order.
260
+ */
261
+ export function unmockFn<T extends Function>(oldFn: T): void {}
235
262
 
236
263
  export function mockImport<T extends Function>(oldFn: string, newFn: T): void {
237
264
  __mock_import.set(oldFn, newFn.index);
238
265
  // mocks.set(oldFn, new MockFn(oldFn, newFn).enable());
239
266
  }
240
267
 
268
+ export function unmockImport(oldFn: string): void {
269
+ __mock_import.delete(oldFn);
270
+ }
271
+
272
+ /**
273
+ * Save a single import mock value for the given version.
274
+ *
275
+ * Accepts either:
276
+ * - `snapshotImport(importOrPath, version)`
277
+ * - `snapshotImport(importOrPath, () => { ... })` (uses default version)
278
+ *
279
+ * `imp` accepts either a string import path (e.g. "mock.foo") or the imported function.
280
+ */
281
+ export function snapshotImport<T, V>(imp: T, versionOrCapture: V): void {
282
+ const importKey = resolveImportKey<T>(imp);
283
+ if (isFunction<V>(versionOrCapture)) {
284
+ // @ts-ignore
285
+ versionOrCapture();
286
+ saveImportSnapshot(importKey, DEFAULT_IMPORT_SNAPSHOT_VERSION);
287
+ return;
288
+ }
289
+ saveImportSnapshot(importKey, versionKey<V>(versionOrCapture));
290
+ }
291
+
292
+ /**
293
+ * Restore a single import mock value for the given version.
294
+ *
295
+ * Accepts either a string import path (e.g. "mock.foo") or the imported function.
296
+ */
297
+ export function restoreImport<T, V>(imp: T, version: V): void {
298
+ const importKey = resolveImportKey<T>(imp);
299
+ const snapshotKey = importSnapshotKey(importKey, versionKey<V>(version));
300
+ if (!__mock_import_snapshots.has(snapshotKey)) return;
301
+ const snapshot = __mock_import_snapshots.get(snapshotKey);
302
+ if (snapshot.hasValue) {
303
+ __mock_import.set(importKey, snapshot.value);
304
+ } else {
305
+ __mock_import.delete(importKey);
306
+ }
307
+ }
308
+
241
309
  /**
242
310
  * Class defining options that can be passed to the `run` function.
243
311
  *
@@ -303,7 +371,7 @@ export function run(options: RunOptions = new RunOptions()): void {
303
371
  const report = new FileReport();
304
372
  report.suites = entrySuites;
305
373
  report.coverage = collectCoverage();
306
- sendReport(JSON.stringify(report));
374
+ sendReport(report.serialize());
307
375
  }
308
376
 
309
377
  function registerSuite(
@@ -328,18 +396,30 @@ function registerSuite(
328
396
  suites.push(suite);
329
397
  }
330
398
 
331
-
332
- @json
333
399
  class CoverageReport {
334
400
  total: i32 = 0;
335
401
  covered: i32 = 0;
336
402
  uncovered: i32 = 0;
337
403
  percent: f64 = 100.0;
338
404
  points: CoveragePointReport[] = [];
339
- }
340
405
 
406
+ serialize(): string {
407
+ return (
408
+ '{"total":' +
409
+ this.total.toString() +
410
+ ',"covered":' +
411
+ this.covered.toString() +
412
+ ',"uncovered":' +
413
+ this.uncovered.toString() +
414
+ ',"percent":' +
415
+ this.percent.toString() +
416
+ ',"points":' +
417
+ serializeCoveragePoints(this.points) +
418
+ "}"
419
+ );
420
+ }
421
+ }
341
422
 
342
- @json
343
423
  class CoveragePointReport {
344
424
  hash: string = "";
345
425
  file: string = "";
@@ -347,13 +427,61 @@ class CoveragePointReport {
347
427
  column: i32 = 0;
348
428
  type: string = "";
349
429
  executed: bool = false;
350
- }
351
430
 
431
+ serialize(): string {
432
+ return (
433
+ '{"hash":' +
434
+ quote(this.hash) +
435
+ ',"file":' +
436
+ quote(this.file) +
437
+ ',"line":' +
438
+ this.line.toString() +
439
+ ',"column":' +
440
+ this.column.toString() +
441
+ ',"type":' +
442
+ quote(this.type) +
443
+ ',"executed":' +
444
+ (this.executed ? "true" : "false") +
445
+ "}"
446
+ );
447
+ }
448
+ }
352
449
 
353
- @json
354
450
  class FileReport {
355
451
  suites: Suite[] = [];
356
452
  coverage: CoverageReport = new CoverageReport();
453
+
454
+ serialize(): string {
455
+ return (
456
+ '{"suites":' +
457
+ serializeSuites(this.suites) +
458
+ ',"coverage":' +
459
+ this.coverage.serialize() +
460
+ "}"
461
+ );
462
+ }
463
+ }
464
+
465
+ function serializeSuites(values: Suite[]): string {
466
+ if (!values.length) return "[]";
467
+ let out = "[";
468
+ for (let i = 0; i < values.length; i++) {
469
+ if (i) out += ",";
470
+ out += unchecked(values[i]).serialize();
471
+ }
472
+ out += "]";
473
+ return out;
474
+ }
475
+
476
+ function serializeCoveragePoints(values: CoveragePointReport[]): string {
477
+ if (!values.length) return "[]";
478
+ let out = "[";
479
+ for (let i = 0; i < values.length; i++) {
480
+ if (i) out += ",";
481
+ out += unchecked(values[i]).serialize();
482
+ }
483
+ out += "]";
484
+ return out;
357
485
  }
358
486
 
359
487
  function collectCoverage(): CoverageReport {
@@ -399,6 +527,46 @@ function snapshotKey(): string {
399
527
  return FILE + "::" + path + "::" + suite.tests.length.toString();
400
528
  }
401
529
 
530
+ function resolveImportKey<T>(imp: T): string {
531
+ if (isString<T>()) {
532
+ // @ts-ignore
533
+ return imp as string;
534
+ }
535
+ // @ts-ignore
536
+ const index = imp.index as u32;
537
+ if (__mock_import_target_by_index.has(index)) {
538
+ return __mock_import_target_by_index.get(index);
539
+ }
540
+ return index.toString();
541
+ }
542
+
543
+ function importSnapshotKey(importKey: string, version: string): string {
544
+ return importKey + "::" + version;
545
+ }
546
+
547
+ function versionKey<V>(version: V): string {
548
+ if (isString<V>()) {
549
+ // @ts-ignore
550
+ return version as string;
551
+ }
552
+ if (isInteger<V>()) {
553
+ // @ts-ignore
554
+ return (<i64>version).toString();
555
+ }
556
+ ERROR("snapshot/restore version must be string or integer");
557
+ return "";
558
+ }
559
+
560
+ function saveImportSnapshot(importKey: string, version: string): void {
561
+ const snapshotKey = importSnapshotKey(importKey, version);
562
+ const snapshot = new ImportSnapshot();
563
+ if (__mock_import.has(importKey)) {
564
+ snapshot.hasValue = true;
565
+ snapshot.value = __mock_import.get(importKey);
566
+ }
567
+ __mock_import_snapshots.set(snapshotKey, snapshot);
568
+ }
569
+
402
570
  export class Result {
403
571
  public name: string;
404
572
  public arg1: i32;
@@ -420,18 +588,34 @@ export class Result {
420
588
  return out;
421
589
  }
422
590
  serialize(): string {
423
- return JSON.stringify(this);
591
+ return (
592
+ '{"name":' +
593
+ quote(this.name) +
594
+ ',"arg1":' +
595
+ this.arg1.toString() +
596
+ ',"arg2":' +
597
+ this.arg2.toString() +
598
+ "}"
599
+ );
424
600
  }
425
601
  }
426
602
 
427
-
428
- @json
429
603
  export class Time {
430
604
  start: f64 = 0;
431
605
  end: f64 = 0;
432
606
  format(): string {
433
607
  return formatTime(this.end - this.start);
434
608
  }
609
+
610
+ serialize(): string {
611
+ return (
612
+ '{"start":' +
613
+ this.start.toString() +
614
+ ',"end":' +
615
+ this.end.toString() +
616
+ "}"
617
+ );
618
+ }
435
619
  }
436
620
 
437
621
  class Unit {
@@ -1,6 +1,6 @@
1
1
  import { visualize } from "../util/helpers";
2
2
  import { Tests } from "./tests";
3
- import { JSON } from "json-as";
3
+ import { quote, stringifyValue } from "../util/json";
4
4
  import {
5
5
  sendAssertionFailure,
6
6
  sendWarning,
@@ -9,41 +9,26 @@ import {
9
9
 
10
10
  let warnedToThrowDisabled = false;
11
11
 
12
-
13
- @json
14
12
  export class Expectation<T> extends Tests {
15
13
  public verdict: string = "none";
16
- public right: JSON.Raw = JSON.Raw.from("");
17
- public left: JSON.Raw = JSON.Raw.from("");
18
-
14
+ public right: string = "null";
15
+ public left: string = "null";
19
16
 
20
- @omit
21
17
  private _left: T;
22
18
 
23
-
24
- @omit
25
19
  // @ts-ignore
26
20
  private _right: u64 = 0;
27
21
 
28
-
29
- @omit
30
22
  // @ts-ignore
31
23
  private _not: boolean = false;
32
24
 
33
- @omit
34
25
  // @ts-ignore
35
26
  private _skip: boolean = false;
36
27
 
37
-
38
- @omit
39
28
  private _message: string = "";
40
29
 
41
-
42
- @omit
43
30
  private _snapshotKey: string = "";
44
31
 
45
-
46
- @omit
47
32
  private _location: string = "";
48
33
 
49
34
  constructor(
@@ -79,8 +64,8 @@ export class Expectation<T> extends Tests {
79
64
  if (this._skip) {
80
65
  this.verdict = "skip";
81
66
  this.instr = instr;
82
- this.left.set(left);
83
- this.right.set(right);
67
+ this.left = left;
68
+ this.right = right;
84
69
  this.message = "";
85
70
  this._not = false;
86
71
  return;
@@ -88,8 +73,8 @@ export class Expectation<T> extends Tests {
88
73
  const isFail = this._not ? passed : !passed;
89
74
  this.verdict = isFail ? "fail" : "ok";
90
75
  this.instr = instr;
91
- this.left.set(left);
92
- this.right.set(right);
76
+ this.left = left;
77
+ this.right = right;
93
78
  this.message = isFail ? this._message : "";
94
79
  if (isFail) {
95
80
  sendAssertionFailure(this._snapshotKey, instr, left, right, this.message);
@@ -317,7 +302,8 @@ export class Expectation<T> extends Tests {
317
302
  * Tests if a string starts with the provided prefix.
318
303
  */
319
304
  toStartWith(value: string): void {
320
- if (!isString<T>()) ERROR("toStartWith() can only be used on string types!");
305
+ if (!isString<T>())
306
+ ERROR("toStartWith() can only be used on string types!");
321
307
  // @ts-ignore
322
308
  const left = this._left as string;
323
309
  const passed = left.indexOf(value) == 0;
@@ -369,7 +355,7 @@ export class Expectation<T> extends Tests {
369
355
  let key = this._snapshotKey;
370
356
  if (name.length) key += "::" + name;
371
357
 
372
- const actual = JSON.stringify<T>(this._left);
358
+ const actual = stringifyValue<T>(this._left);
373
359
  const res = snapshotAssert(key, actual);
374
360
  this._resolve(res.ok, "toMatchSnapshot", actual, res.expected);
375
361
  }
@@ -417,21 +403,21 @@ export class Expectation<T> extends Tests {
417
403
  passed = this._left === equals;
418
404
  } else {
419
405
  // Fallback for reference/value types where strict equality is not enough.
420
- passed = JSON.stringify<T>(this._left) == JSON.stringify<T>(equals);
406
+ passed = stringifyValue<T>(this._left) == stringifyValue<T>(equals);
421
407
  }
422
408
 
423
409
  this._resolve(
424
410
  passed,
425
411
  "toBe",
426
- JSON.stringify<T>(this._left),
427
- JSON.stringify<T>(equals),
412
+ stringifyValue<T>(this._left),
413
+ stringifyValue<T>(equals),
428
414
  );
429
415
  }
430
416
  }
431
417
 
432
418
  function arrayEquals<T extends any[]>(a: T, b: T): boolean {
433
419
  if (a.length != b.length) return false;
434
- return JSON.stringify(a) == JSON.stringify(b);
420
+ return stringifyValue(a) == stringifyValue(b);
435
421
  }
436
422
 
437
423
  function isTruthy<T>(value: T): bool {
@@ -455,5 +441,5 @@ function isTruthy<T>(value: T): bool {
455
441
  }
456
442
 
457
443
  function q(value: string): string {
458
- return JSON.stringify<string>(value);
444
+ return quote(value);
459
445
  }
@@ -1,5 +1,5 @@
1
+ import { quote } from "../util/json";
1
2
 
2
- @json
3
3
  export class Log {
4
4
  public order: i32 = 0;
5
5
  public depth: i32 = 0;
@@ -8,4 +8,16 @@ export class Log {
8
8
  this.text = text;
9
9
  }
10
10
  display(): void {}
11
+
12
+ serialize(): string {
13
+ return (
14
+ '{"order":' +
15
+ this.order.toString() +
16
+ ',"depth":' +
17
+ this.depth.toString() +
18
+ ',"text":' +
19
+ quote(this.text) +
20
+ "}"
21
+ );
22
+ }
11
23
  }
@@ -4,12 +4,9 @@ import { Tests } from "./tests";
4
4
  import { Log } from "./log";
5
5
  import { after_each_callback, before_each_callback } from "..";
6
6
  import { sendSuiteEnd, sendSuiteStart } from "../util/wipc";
7
+ import { quote } from "../util/json";
7
8
 
8
-
9
- @json
10
9
  export class Suite {
11
-
12
- @omitif((self: Suite) => self.depth > 0)
13
10
  public file: string = "unknown";
14
11
  public order: i32 = 0;
15
12
  public time: Time = new Time();
@@ -20,8 +17,6 @@ export class Suite {
20
17
  public logs: Log[] = [];
21
18
  public kind: string;
22
19
 
23
-
24
- @omit
25
20
  public parent: Suite | null = null;
26
21
 
27
22
  public verdict: string = "none";
@@ -59,9 +54,7 @@ export class Suite {
59
54
  this.time.start = performance.now();
60
55
  sendSuiteStart(this.file, this.depth, this.kind, this.description);
61
56
  const isSkippedCase =
62
- this.kind == "xdescribe" ||
63
- this.kind == "xtest" ||
64
- this.kind == "xit";
57
+ this.kind == "xdescribe" || this.kind == "xtest" || this.kind == "xit";
65
58
  const isTestCase =
66
59
  this.kind == "test" ||
67
60
  this.kind == "it" ||
@@ -134,4 +127,55 @@ export class Suite {
134
127
  this.verdict,
135
128
  );
136
129
  }
130
+
131
+ serialize(): string {
132
+ let out = "{";
133
+ if (this.depth <= 0) {
134
+ out += '"file":' + quote(this.file) + ",";
135
+ }
136
+ out += '"order":' + this.order.toString();
137
+ out += ',"time":' + this.time.serialize();
138
+ out += ',"description":' + quote(this.description);
139
+ out += ',"depth":' + this.depth.toString();
140
+ out += ',"suites":' + serializeSuites(this.suites);
141
+ out += ',"tests":' + serializeTests(this.tests);
142
+ out += ',"logs":' + serializeLogs(this.logs);
143
+ out += ',"kind":' + quote(this.kind);
144
+ out += ',"verdict":' + quote(this.verdict);
145
+ out += "}";
146
+ return out;
147
+ }
148
+ }
149
+
150
+ function serializeSuites(values: Suite[]): string {
151
+ if (!values.length) return "[]";
152
+ let out = "[";
153
+ for (let i = 0; i < values.length; i++) {
154
+ if (i) out += ",";
155
+ out += unchecked(values[i]).serialize();
156
+ }
157
+ out += "]";
158
+ return out;
159
+ }
160
+
161
+ function serializeTests(values: Tests[]): string {
162
+ if (!values.length) return "[]";
163
+ let out = "[";
164
+ for (let i = 0; i < values.length; i++) {
165
+ if (i) out += ",";
166
+ out += unchecked(values[i]).serialize();
167
+ }
168
+ out += "]";
169
+ return out;
170
+ }
171
+
172
+ function serializeLogs(values: Log[]): string {
173
+ if (!values.length) return "[]";
174
+ let out = "[";
175
+ for (let i = 0; i < values.length; i++) {
176
+ if (i) out += ",";
177
+ out += unchecked(values[i]).serialize();
178
+ }
179
+ out += "]";
180
+ return out;
137
181
  }
@@ -1,14 +1,34 @@
1
- import { JSON } from "json-as";
1
+ import { quote, rawOrNull } from "../util/json";
2
2
 
3
-
4
- @json
5
3
  export class Tests {
6
4
  public order: i32 = 0;
7
5
  public type: string = "";
8
6
  public verdict: string = "none";
9
- public left: JSON.Raw = JSON.Raw.from("");
10
- public right: JSON.Raw = JSON.Raw.from("");
7
+ public left: string = "null";
8
+ public right: string = "null";
11
9
  public instr: string = "";
12
10
  public message: string = "";
13
11
  public location: string = "";
12
+
13
+ serialize(): string {
14
+ return (
15
+ '{"order":' +
16
+ this.order.toString() +
17
+ ',"type":' +
18
+ quote(this.type) +
19
+ ',"verdict":' +
20
+ quote(this.verdict) +
21
+ ',"left":' +
22
+ rawOrNull(this.left) +
23
+ ',"right":' +
24
+ rawOrNull(this.right) +
25
+ ',"instr":' +
26
+ quote(this.instr) +
27
+ ',"message":' +
28
+ quote(this.message) +
29
+ ',"location":' +
30
+ quote(this.location) +
31
+ "}"
32
+ );
33
+ }
14
34
  }
@@ -1,5 +1,4 @@
1
1
  import { rainbow } from "as-rainbow";
2
- import { JSON } from "json-as";
3
2
 
4
3
  export function visualize<T>(value: T): string {
5
4
  if (isNullable<T>() && changetype<usize>(value) == <usize>0) {
@@ -0,0 +1,78 @@
1
+ import { stringify } from "as-console/stringify";
2
+
3
+ export function quote(value: string): string {
4
+ return '"' + escape(value) + '"';
5
+ }
6
+
7
+ export function rawOrNull(value: string): string {
8
+ return value.length ? value : "null";
9
+ }
10
+
11
+ export function stringifyValue<T>(value: T): string {
12
+ if (isNullable<T>() && changetype<usize>(value) == <usize>0) {
13
+ return "null";
14
+ }
15
+
16
+ if (isBoolean<T>()) {
17
+ return (value as bool) ? "true" : "false";
18
+ }
19
+
20
+ if (isInteger<T>() || isFloat<T>()) {
21
+ // @ts-expect-error: type
22
+ return value.toString();
23
+ }
24
+
25
+ if (isString<T>()) {
26
+ return quote(value as string);
27
+ }
28
+
29
+ if (isArray<T>()) {
30
+ // @ts-expect-error: type
31
+ return stringifyArray<valueof<T>>(value as valueof<T>[]);
32
+ }
33
+
34
+ const formatted = stringify<T>(value);
35
+ if (formatted != "none") {
36
+ return quote(formatted);
37
+ }
38
+
39
+ return quote(nameof<T>());
40
+ }
41
+
42
+ function stringifyArray<T>(values: T[]): string {
43
+ if (!values.length) return "[]";
44
+
45
+ let out = "[";
46
+ for (let i = 0; i < values.length; i++) {
47
+ if (i) out += ",";
48
+ out += stringifyValue<T>(unchecked(values[i]));
49
+ }
50
+ out += "]";
51
+ return out;
52
+ }
53
+
54
+ function escape(value: string): string {
55
+ let out = "";
56
+ for (let i = 0; i < value.length; i++) {
57
+ const ch = value.charCodeAt(i);
58
+ if (ch == 34) {
59
+ out += '\\"';
60
+ } else if (ch == 92) {
61
+ out += "\\\\";
62
+ } else if (ch == 10) {
63
+ out += "\\n";
64
+ } else if (ch == 13) {
65
+ out += "\\r";
66
+ } else if (ch == 9) {
67
+ out += "\\t";
68
+ } else if (ch < 32) {
69
+ out += "\\u00";
70
+ const hex = ch.toString(16);
71
+ if (hex.length < 2) out += "0";
72
+ out += hex;
73
+ } else {
74
+ out += value.charAt(i);
75
+ }
76
+ }
77
+ return out;
78
+ }