@gjsify/assert 0.0.4 → 0.1.1

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/src/index.ts CHANGED
@@ -1,3 +1,569 @@
1
- export * from '@gjsify/deno_std/node/assert';
2
- import assert from '@gjsify/deno_std/node/assert';
3
- export default assert;
1
+ // Reference: Node.js lib/assert.js
2
+ // Reimplemented for GJS
3
+
4
+ import { AssertionError } from './assertion-error.js';
5
+ import { isDeepEqual, isDeepStrictEqual } from './deep-equal.js';
6
+
7
+ export { AssertionError };
8
+
9
+ // ---- Helpers ----
10
+
11
+ function innerFail(obj: {
12
+ actual?: unknown;
13
+ expected?: unknown;
14
+ message?: string | Error;
15
+ operator?: string;
16
+ stackStartFn?: Function;
17
+ }): never {
18
+ if (obj.message instanceof Error) throw obj.message;
19
+ throw new AssertionError({
20
+ actual: obj.actual,
21
+ expected: obj.expected,
22
+ message: obj.message as string | undefined,
23
+ operator: obj.operator,
24
+ stackStartFn: obj.stackStartFn,
25
+ });
26
+ }
27
+
28
+ function isPromiseLike(val: unknown): val is PromiseLike<unknown> {
29
+ return val !== null && typeof val === 'object' && typeof (val as { then?: unknown }).then === 'function';
30
+ }
31
+
32
+ // ---- Core functions ----
33
+
34
+ function ok(value: unknown, message?: string | Error): void {
35
+ if (!value) {
36
+ innerFail({
37
+ actual: value,
38
+ expected: true,
39
+ message,
40
+ operator: '==',
41
+ stackStartFn: ok,
42
+ });
43
+ }
44
+ }
45
+
46
+ function equal(actual: unknown, expected: unknown, message?: string | Error): void {
47
+ // eslint-disable-next-line eqeqeq
48
+ if (actual != expected) {
49
+ innerFail({
50
+ actual,
51
+ expected,
52
+ message,
53
+ operator: '==',
54
+ stackStartFn: equal,
55
+ });
56
+ }
57
+ }
58
+
59
+ function notEqual(actual: unknown, expected: unknown, message?: string | Error): void {
60
+ // eslint-disable-next-line eqeqeq
61
+ if (actual == expected) {
62
+ innerFail({
63
+ actual,
64
+ expected,
65
+ message,
66
+ operator: '!=',
67
+ stackStartFn: notEqual,
68
+ });
69
+ }
70
+ }
71
+
72
+ function strictEqual(actual: unknown, expected: unknown, message?: string | Error): void {
73
+ if (!Object.is(actual, expected)) {
74
+ innerFail({
75
+ actual,
76
+ expected,
77
+ message,
78
+ operator: 'strictEqual',
79
+ stackStartFn: strictEqual,
80
+ });
81
+ }
82
+ }
83
+
84
+ function notStrictEqual(actual: unknown, expected: unknown, message?: string | Error): void {
85
+ if (Object.is(actual, expected)) {
86
+ innerFail({
87
+ actual,
88
+ expected,
89
+ message,
90
+ operator: 'notStrictEqual',
91
+ stackStartFn: notStrictEqual,
92
+ });
93
+ }
94
+ }
95
+
96
+ function deepEqual(actual: unknown, expected: unknown, message?: string | Error): void {
97
+ if (!isDeepEqual(actual, expected)) {
98
+ innerFail({
99
+ actual,
100
+ expected,
101
+ message,
102
+ operator: 'deepEqual',
103
+ stackStartFn: deepEqual,
104
+ });
105
+ }
106
+ }
107
+
108
+ function notDeepEqual(actual: unknown, expected: unknown, message?: string | Error): void {
109
+ if (isDeepEqual(actual, expected)) {
110
+ innerFail({
111
+ actual,
112
+ expected,
113
+ message,
114
+ operator: 'notDeepEqual',
115
+ stackStartFn: notDeepEqual,
116
+ });
117
+ }
118
+ }
119
+
120
+ function deepStrictEqual(actual: unknown, expected: unknown, message?: string | Error): void {
121
+ if (!isDeepStrictEqual(actual, expected)) {
122
+ innerFail({
123
+ actual,
124
+ expected,
125
+ message,
126
+ operator: 'deepStrictEqual',
127
+ stackStartFn: deepStrictEqual,
128
+ });
129
+ }
130
+ }
131
+
132
+ function notDeepStrictEqual(actual: unknown, expected: unknown, message?: string | Error): void {
133
+ if (isDeepStrictEqual(actual, expected)) {
134
+ innerFail({
135
+ actual,
136
+ expected,
137
+ message,
138
+ operator: 'notDeepStrictEqual',
139
+ stackStartFn: notDeepStrictEqual,
140
+ });
141
+ }
142
+ }
143
+
144
+ // ---- throws / doesNotThrow ----
145
+
146
+ type ErrorPredicate = RegExp | Function | ((err: unknown) => boolean) | object | Error;
147
+
148
+ function getActual(fn: () => unknown): unknown {
149
+ const NO_EXCEPTION = Symbol('NO_EXCEPTION');
150
+ try {
151
+ fn();
152
+ } catch (e) {
153
+ return e;
154
+ }
155
+ return NO_EXCEPTION;
156
+ }
157
+
158
+ async function getActualAsync(fn: (() => Promise<unknown>) | Promise<unknown>): Promise<unknown> {
159
+ const NO_EXCEPTION = Symbol('NO_EXCEPTION');
160
+ try {
161
+ if (typeof fn === 'function') {
162
+ const result = fn();
163
+ if (isPromiseLike(result)) {
164
+ await result;
165
+ }
166
+ } else {
167
+ await fn;
168
+ }
169
+ } catch (e) {
170
+ return e;
171
+ }
172
+ return NO_EXCEPTION;
173
+ }
174
+
175
+ function expectedException(
176
+ actual: unknown,
177
+ expected: ErrorPredicate,
178
+ message: string | Error | undefined,
179
+ fn: Function,
180
+ ): boolean {
181
+ if (expected === undefined) return true;
182
+
183
+ // RegExp validation
184
+ if (expected instanceof RegExp) {
185
+ const str = String(actual);
186
+ if (expected.test(str)) return true;
187
+ throw new AssertionError({
188
+ actual,
189
+ expected,
190
+ message: message as string | undefined,
191
+ operator: fn.name,
192
+ stackStartFn: fn,
193
+ });
194
+ }
195
+
196
+ // Validation function
197
+ if (typeof expected === 'function') {
198
+ // Error class constructor
199
+ if (expected.prototype !== undefined && actual instanceof (expected as new (...args: unknown[]) => unknown)) {
200
+ return true;
201
+ }
202
+ // Error class but not instance
203
+ if (Error.isPrototypeOf(expected as Function)) {
204
+ return false;
205
+ }
206
+ // Validator function
207
+ const result = (expected as Function).call({}, actual);
208
+ if (result !== true) {
209
+ throw new AssertionError({
210
+ actual,
211
+ expected,
212
+ message: message as string | undefined,
213
+ operator: fn.name,
214
+ stackStartFn: fn,
215
+ });
216
+ }
217
+ return true;
218
+ }
219
+
220
+ // Object validation (check properties)
221
+ if (typeof expected === 'object' && expected !== null) {
222
+ const keys = Object.keys(expected);
223
+ for (const key of keys) {
224
+ const expectedObj = expected as Record<string, unknown>;
225
+ const actualObj = actual as Record<string, unknown>;
226
+ if (typeof actualObj[key] === 'string' && expectedObj[key] instanceof RegExp) {
227
+ if (!(expectedObj[key] as RegExp).test(actualObj[key] as string)) {
228
+ throw new AssertionError({
229
+ actual,
230
+ expected,
231
+ message: message as string | undefined,
232
+ operator: fn.name,
233
+ stackStartFn: fn,
234
+ });
235
+ }
236
+ } else if (!isDeepStrictEqual(actualObj[key], expectedObj[key])) {
237
+ throw new AssertionError({
238
+ actual,
239
+ expected,
240
+ message: message as string | undefined,
241
+ operator: fn.name,
242
+ stackStartFn: fn,
243
+ });
244
+ }
245
+ }
246
+ return true;
247
+ }
248
+
249
+ return true;
250
+ }
251
+
252
+ function throws(
253
+ fn: () => unknown,
254
+ errorOrMessage?: ErrorPredicate | string,
255
+ message?: string | Error,
256
+ ): void {
257
+ if (typeof fn !== 'function') {
258
+ throw new TypeError('The "fn" argument must be of type function.');
259
+ }
260
+
261
+ let expected: ErrorPredicate | undefined;
262
+ if (typeof errorOrMessage === 'string') {
263
+ message = errorOrMessage;
264
+ expected = undefined;
265
+ } else {
266
+ expected = errorOrMessage;
267
+ }
268
+
269
+ const actual = getActual(fn);
270
+ if (typeof actual === 'symbol') {
271
+ // NO_EXCEPTION sentinel
272
+ innerFail({
273
+ actual: undefined,
274
+ expected,
275
+ message: message || 'Missing expected exception.',
276
+ operator: 'throws',
277
+ stackStartFn: throws,
278
+ });
279
+ }
280
+
281
+ if (expected !== undefined) {
282
+ expectedException(actual, expected, message, throws);
283
+ }
284
+ }
285
+
286
+ function doesNotThrow(
287
+ fn: () => unknown,
288
+ errorOrMessage?: ErrorPredicate | string,
289
+ message?: string | Error,
290
+ ): void {
291
+ if (typeof fn !== 'function') {
292
+ throw new TypeError('The "fn" argument must be of type function.');
293
+ }
294
+
295
+ let expected: ErrorPredicate | undefined;
296
+ if (typeof errorOrMessage === 'string') {
297
+ message = errorOrMessage;
298
+ expected = undefined;
299
+ } else {
300
+ expected = errorOrMessage;
301
+ }
302
+
303
+ const actual = getActual(fn);
304
+ if (typeof actual === 'symbol') {
305
+ // NO_EXCEPTION — good
306
+ return;
307
+ }
308
+
309
+ // If an expected error type was given, only re-throw if it matches
310
+ if (expected !== undefined && typeof expected === 'function' && expected.prototype !== undefined && actual instanceof expected) {
311
+ innerFail({
312
+ actual,
313
+ expected,
314
+ message: message || `Got unwanted exception.\n${actual && (actual as Error).message ? (actual as Error).message : ''}`,
315
+ operator: 'doesNotThrow',
316
+ stackStartFn: doesNotThrow,
317
+ });
318
+ }
319
+
320
+ if (expected === undefined || (typeof expected === 'function' && expected.prototype !== undefined && actual instanceof expected)) {
321
+ innerFail({
322
+ actual,
323
+ expected,
324
+ message: message || `Got unwanted exception.\n${actual && (actual as Error).message ? (actual as Error).message : ''}`,
325
+ operator: 'doesNotThrow',
326
+ stackStartFn: doesNotThrow,
327
+ });
328
+ }
329
+
330
+ throw actual;
331
+ }
332
+
333
+ async function rejects(
334
+ asyncFn: (() => Promise<unknown>) | Promise<unknown>,
335
+ errorOrMessage?: ErrorPredicate | string,
336
+ message?: string | Error,
337
+ ): Promise<void> {
338
+ let expected: ErrorPredicate | undefined;
339
+ if (typeof errorOrMessage === 'string') {
340
+ message = errorOrMessage;
341
+ expected = undefined;
342
+ } else {
343
+ expected = errorOrMessage;
344
+ }
345
+
346
+ const actual = await getActualAsync(asyncFn);
347
+ if (typeof actual === 'symbol') {
348
+ innerFail({
349
+ actual: undefined,
350
+ expected,
351
+ message: message || 'Missing expected rejection.',
352
+ operator: 'rejects',
353
+ stackStartFn: rejects,
354
+ });
355
+ }
356
+
357
+ if (expected !== undefined) {
358
+ expectedException(actual, expected, message, rejects);
359
+ }
360
+ }
361
+
362
+ async function doesNotReject(
363
+ asyncFn: (() => Promise<unknown>) | Promise<unknown>,
364
+ errorOrMessage?: ErrorPredicate | string,
365
+ message?: string | Error,
366
+ ): Promise<void> {
367
+ let expected: ErrorPredicate | undefined;
368
+ if (typeof errorOrMessage === 'string') {
369
+ message = errorOrMessage;
370
+ expected = undefined;
371
+ } else {
372
+ expected = errorOrMessage;
373
+ }
374
+
375
+ const actual = await getActualAsync(asyncFn);
376
+ if (typeof actual !== 'symbol') {
377
+ innerFail({
378
+ actual,
379
+ expected,
380
+ message: message || `Got unwanted rejection.\n${actual && (actual as Error).message ? (actual as Error).message : ''}`,
381
+ operator: 'doesNotReject',
382
+ stackStartFn: doesNotReject,
383
+ });
384
+ }
385
+ }
386
+
387
+ // ---- fail ----
388
+
389
+ function fail(message?: string | Error): never;
390
+ function fail(actual: unknown, expected: unknown, message?: string | Error, operator?: string, stackStartFn?: Function): never;
391
+ function fail(
392
+ actualOrMessage?: unknown,
393
+ expected?: unknown,
394
+ message?: string | Error,
395
+ operator?: string,
396
+ stackStartFn?: Function,
397
+ ): never {
398
+ // Single-argument form: fail(message)
399
+ if (arguments.length === 0 || arguments.length === 1) {
400
+ const msg = arguments.length === 0
401
+ ? 'Failed'
402
+ : (typeof actualOrMessage === 'string' ? actualOrMessage : undefined);
403
+ if (actualOrMessage instanceof Error) throw actualOrMessage;
404
+ throw new AssertionError({
405
+ message: msg || 'Failed',
406
+ operator: 'fail',
407
+ stackStartFn: fail,
408
+ });
409
+ }
410
+
411
+ // Legacy multi-argument form
412
+ throw new AssertionError({
413
+ actual: actualOrMessage,
414
+ expected,
415
+ message: message as string | undefined,
416
+ operator: operator || 'fail',
417
+ stackStartFn: stackStartFn || fail,
418
+ });
419
+ }
420
+
421
+ // ---- ifError ----
422
+
423
+ function ifError(value: unknown): void {
424
+ if (value !== null && value !== undefined) {
425
+ let message = 'ifError got unwanted exception: ';
426
+ if (typeof value === 'object' && typeof (value as Error).message === 'string') {
427
+ if ((value as Error).message.length === 0 && (value as Error).constructor) {
428
+ message += (value as Error).constructor.name;
429
+ } else {
430
+ message += (value as Error).message;
431
+ }
432
+ } else {
433
+ message += String(value);
434
+ }
435
+
436
+ const err = new AssertionError({
437
+ actual: value,
438
+ expected: null,
439
+ message,
440
+ operator: 'ifError',
441
+ stackStartFn: ifError,
442
+ });
443
+
444
+ // Attach original error info
445
+ const origStack = value instanceof Error ? value.stack : undefined;
446
+ if (origStack) {
447
+ (err as Error & { origStack?: string }).origStack = origStack;
448
+ }
449
+
450
+ throw err;
451
+ }
452
+ }
453
+
454
+ // ---- match / doesNotMatch ----
455
+
456
+ function match(actual: string, expected: RegExp, message?: string | Error): void {
457
+ if (typeof actual !== 'string') {
458
+ throw new TypeError('The "actual" argument must be of type string.');
459
+ }
460
+ if (!(expected instanceof RegExp)) {
461
+ throw new TypeError('The "expected" argument must be an instance of RegExp.');
462
+ }
463
+ if (!expected.test(actual)) {
464
+ innerFail({
465
+ actual,
466
+ expected,
467
+ message: message || `The input did not match the regular expression ${expected}. Input:\n\n'${actual}'\n`,
468
+ operator: 'match',
469
+ stackStartFn: match,
470
+ });
471
+ }
472
+ }
473
+
474
+ function doesNotMatch(actual: string, expected: RegExp, message?: string | Error): void {
475
+ if (typeof actual !== 'string') {
476
+ throw new TypeError('The "actual" argument must be of type string.');
477
+ }
478
+ if (!(expected instanceof RegExp)) {
479
+ throw new TypeError('The "expected" argument must be an instance of RegExp.');
480
+ }
481
+ if (expected.test(actual)) {
482
+ innerFail({
483
+ actual,
484
+ expected,
485
+ message: message || `The input was expected to not match the regular expression ${expected}. Input:\n\n'${actual}'\n`,
486
+ operator: 'doesNotMatch',
487
+ stackStartFn: doesNotMatch,
488
+ });
489
+ }
490
+ }
491
+
492
+ // ---- strict namespace ----
493
+
494
+ const strict = Object.assign(
495
+ function strict(value: unknown, message?: string | Error) { ok(value, message); },
496
+ {
497
+ AssertionError,
498
+ ok,
499
+ equal: strictEqual,
500
+ notEqual: notStrictEqual,
501
+ deepEqual: deepStrictEqual,
502
+ notDeepEqual: notDeepStrictEqual,
503
+ deepStrictEqual,
504
+ notDeepStrictEqual,
505
+ strictEqual,
506
+ notStrictEqual,
507
+ throws,
508
+ doesNotThrow,
509
+ rejects,
510
+ doesNotReject,
511
+ fail,
512
+ ifError,
513
+ match,
514
+ doesNotMatch,
515
+ strict: undefined as unknown as typeof strict,
516
+ },
517
+ );
518
+ strict.strict = strict;
519
+
520
+ // ---- Default export: assert function with all methods ----
521
+
522
+ const assert = Object.assign(
523
+ function assert(value: unknown, message?: string | Error) { ok(value, message); },
524
+ {
525
+ AssertionError,
526
+ ok,
527
+ equal,
528
+ notEqual,
529
+ strictEqual,
530
+ notStrictEqual,
531
+ deepEqual,
532
+ notDeepEqual,
533
+ deepStrictEqual,
534
+ notDeepStrictEqual,
535
+ throws,
536
+ doesNotThrow,
537
+ rejects,
538
+ doesNotReject,
539
+ fail,
540
+ ifError,
541
+ match,
542
+ doesNotMatch,
543
+ strict,
544
+ },
545
+ );
546
+
547
+ // Named exports
548
+ export {
549
+ ok,
550
+ equal,
551
+ notEqual,
552
+ strictEqual,
553
+ notStrictEqual,
554
+ deepEqual,
555
+ notDeepEqual,
556
+ deepStrictEqual,
557
+ notDeepStrictEqual,
558
+ throws,
559
+ doesNotThrow,
560
+ rejects,
561
+ doesNotReject,
562
+ fail,
563
+ ifError,
564
+ match,
565
+ doesNotMatch,
566
+ strict,
567
+ };
568
+
569
+ export default assert;
@@ -0,0 +1,101 @@
1
+ // Minimal inspect fallback for GJS — original implementation
2
+
3
+ /**
4
+ * Minimal value-to-string converter for assertion error messages.
5
+ * This avoids depending on util.inspect (which itself depends on @gjsify/deno_std).
6
+ * Can be replaced with util.inspect once @gjsify/util is migrated.
7
+ */
8
+
9
+ const MAX_DEPTH = 3;
10
+ const MAX_ARRAY_LENGTH = 10;
11
+ const MAX_STRING_LENGTH = 128;
12
+
13
+ export function safeInspect(value: unknown, depth: number = MAX_DEPTH): string {
14
+ if (value === null) return 'null';
15
+ if (value === undefined) return 'undefined';
16
+
17
+ switch (typeof value) {
18
+ case 'string':
19
+ if (value.length > MAX_STRING_LENGTH) {
20
+ return `'${value.slice(0, MAX_STRING_LENGTH)}...'`;
21
+ }
22
+ return `'${value}'`;
23
+ case 'number':
24
+ case 'boolean':
25
+ case 'bigint':
26
+ return String(value);
27
+ case 'symbol':
28
+ return value.toString();
29
+ case 'function':
30
+ return `[Function: ${value.name || 'anonymous'}]`;
31
+ case 'object':
32
+ return inspectObject(value, depth);
33
+ }
34
+
35
+ return String(value);
36
+ }
37
+
38
+ function inspectObject(obj: object, depth: number, seen: WeakSet<object> = new WeakSet()): string {
39
+ if (seen.has(obj)) return '[Circular]';
40
+ seen.add(obj);
41
+
42
+ if (obj instanceof Date) return obj.toISOString();
43
+ if (obj instanceof RegExp) return obj.toString();
44
+ if (obj instanceof Error) return `[${obj.constructor.name}: ${obj.message}]`;
45
+
46
+ if (obj instanceof Map) {
47
+ if (depth <= 0) return `Map(${obj.size}) { ... }`;
48
+ const entries = [...obj.entries()]
49
+ .slice(0, MAX_ARRAY_LENGTH)
50
+ .map(([k, v]) => `${inspectInner(k, depth - 1, seen)} => ${inspectInner(v, depth - 1, seen)}`);
51
+ const suffix = obj.size > MAX_ARRAY_LENGTH ? ', ...' : '';
52
+ return `Map(${obj.size}) { ${entries.join(', ')}${suffix} }`;
53
+ }
54
+
55
+ if (obj instanceof Set) {
56
+ if (depth <= 0) return `Set(${obj.size}) { ... }`;
57
+ const entries = [...obj.values()]
58
+ .slice(0, MAX_ARRAY_LENGTH)
59
+ .map(v => inspectInner(v, depth - 1, seen));
60
+ const suffix = obj.size > MAX_ARRAY_LENGTH ? ', ...' : '';
61
+ return `Set(${obj.size}) { ${entries.join(', ')}${suffix} }`;
62
+ }
63
+
64
+ if (ArrayBuffer.isView(obj)) {
65
+ const typedName = obj.constructor.name;
66
+ const arr = obj instanceof DataView
67
+ ? new Uint8Array(obj.buffer, obj.byteOffset, obj.byteLength)
68
+ : obj as unknown as ArrayLike<number>;
69
+ const len = 'length' in arr ? (arr as ArrayLike<number>).length : 0;
70
+ const shown = Math.min(len, MAX_ARRAY_LENGTH);
71
+ const items: string[] = [];
72
+ for (let i = 0; i < shown; i++) items.push(String((arr as ArrayLike<number>)[i]));
73
+ const suffix = len > MAX_ARRAY_LENGTH ? ', ...' : '';
74
+ return `${typedName}(${len}) [ ${items.join(', ')}${suffix} ]`;
75
+ }
76
+
77
+ if (Array.isArray(obj)) {
78
+ if (depth <= 0) return `[ ... ]`;
79
+ const shown = obj.slice(0, MAX_ARRAY_LENGTH)
80
+ .map(v => inspectInner(v, depth - 1, seen));
81
+ const suffix = obj.length > MAX_ARRAY_LENGTH ? ', ...' : '';
82
+ return `[ ${shown.join(', ')}${suffix} ]`;
83
+ }
84
+
85
+ // Plain object
86
+ if (depth <= 0) return '{ ... }';
87
+ const keys = Object.keys(obj);
88
+ const entries = keys.slice(0, MAX_ARRAY_LENGTH)
89
+ .map(k => `${k}: ${inspectInner((obj as Record<string, unknown>)[k], depth - 1, seen)}`);
90
+ const suffix = keys.length > MAX_ARRAY_LENGTH ? ', ...' : '';
91
+ const prefix = obj.constructor && obj.constructor.name !== 'Object'
92
+ ? `${obj.constructor.name} ` : '';
93
+ return `${prefix}{ ${entries.join(', ')}${suffix} }`;
94
+ }
95
+
96
+ function inspectInner(value: unknown, depth: number, seen: WeakSet<object>): string {
97
+ if (value === null) return 'null';
98
+ if (value === undefined) return 'undefined';
99
+ if (typeof value === 'object') return inspectObject(value, depth, seen);
100
+ return safeInspect(value, depth);
101
+ }
@@ -1,3 +1,21 @@
1
- export * from '@gjsify/deno_std/node/assert/strict';
2
- import assert from '@gjsify/deno_std/node/assert/strict';
3
- export default assert;
1
+ export { strict as default, strict } from '../index.js';
2
+ export {
3
+ AssertionError,
4
+ ok,
5
+ fail,
6
+ ifError,
7
+ match,
8
+ doesNotMatch,
9
+ throws,
10
+ doesNotThrow,
11
+ rejects,
12
+ doesNotReject,
13
+ strictEqual,
14
+ notStrictEqual,
15
+ deepStrictEqual,
16
+ notDeepStrictEqual,
17
+ strictEqual as equal,
18
+ notStrictEqual as notEqual,
19
+ deepStrictEqual as deepEqual,
20
+ notDeepStrictEqual as notDeepEqual,
21
+ } from '../index.js';