@gjsify/unit 0.0.2

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/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # @gjsify/unit
2
+
3
+ A BDD-style testing framework for Gjs, Deno and Node.js, forked from [gjsunit](https://github.com/philipphoffmann/gjsunit).
4
+
5
+ ## What it is
6
+ unit is a BDD-style testing framework for the gjs Javascript binding for Gnome 4x which can be used to write Gnome 4x applications and extensions. It's syntax is totally stolen from [Jasmine](http://jasmine.github.io/) ;-).
7
+
8
+ ## How to use it
9
+
10
+ First you need to install the package together with `@gjsify/cli`:
11
+
12
+ ```bash
13
+ yarn install @gjsify/cli @gjsify/unit -D
14
+ ```
15
+
16
+ After that you can build and run your tests:
17
+
18
+ ```bash
19
+ gjsify build test-runner.ts --platform gjs --outfile test.gjs.js
20
+ gjs -m test.gjs.js
21
+ ```
22
+
23
+ ## Writing test suites
24
+
25
+ In your test directory you can have as many subdirectories and test files as you wish. unit will just run all of them.
26
+ A test suite could look like this:
27
+
28
+ ```js
29
+ // my-module.spec.ts
30
+
31
+ import { describe, it, expect } from '@gjsify/unit';
32
+ import MyModule from './my-module.js';
33
+
34
+ export default async () => {
35
+
36
+ await describe('MyModule', async () => {
37
+ await it('should say hello', async () => {
38
+ var module = new MyModule();
39
+
40
+ expect(module.hello()).toEqual('hello');
41
+ expect(module.hello()).not.toEqual('hi');
42
+ });
43
+ });
44
+
45
+ }
46
+ ```
47
+
48
+ ```js
49
+ // test-runner.ts
50
+
51
+ import { run } from '@gjsify/unit';
52
+ import myTestSuite from './my-module.spec.ts';
53
+
54
+ run({myTestSuite});
55
+ ```
56
+
57
+
58
+ Your test files must expose a function. This is the function you will call in your test runner. In your test suite you can use `describe` and `it` to cluster your test suite. You can then use `expect` to capture the value you want to express expectations on. The available methods for expressing expectations are:
59
+ - `toBe(value)` (checks using ===)
60
+ - `toEqual(value)` (checks using ==)
61
+ - `toMatch(regex)` (checks using String.prototype.match and a regular expression)
62
+ - `toBeDefined()` (checks whether the actual value is defined)
63
+ - `toBeUndefined()` (opposite of the above)
64
+ - `toBeNull()` (checks whether the actual value is null)
65
+ - `toBeTruthy()` (checks whether the actual value is castable to true)
66
+ - `toBeFalsy()` (checks whether the actual value is castable to false)
67
+ - `toContain(needle)` (checks whether an array contains the needle value)
68
+ - `toBeLessThan(value)`
69
+ - `toBeGreaterThan(value)`
70
+ - `toBeCloseTo(value, precision)` (can check float values until a given precision)
71
+ - `to(callback)` (checks the value using the provided callback (which gets passed the actual value as first parameter))
72
+
73
+ There is also a `spy` method with which the call of methods can be checked, this is forked from [mysticatea/spy](https://github.com/mysticatea/spy).
74
+
75
+ ```js
76
+ // spy.spec.ts
77
+
78
+ import { describe, it, expect, spy } from '@gjsify/unit';
79
+
80
+ export default async () => {
81
+ await describe("'spy' function", async () => {
82
+ await it("should have a calls length of 1 after called one time.", async () => {
83
+ const f = spy()
84
+ f()
85
+
86
+ expect(f.calls.length).toBe(1)
87
+ })
88
+ })
89
+ }
90
+ ```
91
+
92
+ I recommend looking at the test suite for examples.
93
+
94
+ Happy testing!
95
+
96
+
@@ -0,0 +1,390 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var src_exports = {};
30
+ __export(src_exports, {
31
+ afterEach: () => afterEach,
32
+ assert: () => assert,
33
+ beforeEach: () => beforeEach,
34
+ default: () => src_default,
35
+ describe: () => describe,
36
+ expect: () => expect,
37
+ it: () => it,
38
+ on: () => on,
39
+ print: () => print,
40
+ run: () => run
41
+ });
42
+ module.exports = __toCommonJS(src_exports);
43
+ var import_gjs = require("@girs/gjs");
44
+ __reExport(src_exports, require("./spy.js"), module.exports);
45
+ var import_assert = __toESM(require("assert"), 1);
46
+ const mainloop = globalThis?.imports?.mainloop;
47
+ let countTestsOverall = 0;
48
+ let countTestsFailed = 0;
49
+ let countTestsIgnored = 0;
50
+ let runtime = "";
51
+ const RED = "\x1B[31m";
52
+ const GREEN = "\x1B[32m";
53
+ const BLUE = "\x1B[34m";
54
+ const GRAY = "\x1B[90m";
55
+ const RESET = "\x1B[39m";
56
+ const print = globalThis.print || console.log;
57
+ class MatcherFactory {
58
+ constructor(actualValue, positive, negated) {
59
+ this.actualValue = actualValue;
60
+ this.positive = positive;
61
+ if (negated) {
62
+ this.not = negated;
63
+ } else {
64
+ this.not = new MatcherFactory(actualValue, !positive, this);
65
+ }
66
+ }
67
+ not;
68
+ triggerResult(success, msg) {
69
+ if (success && !this.positive || !success && this.positive) {
70
+ ++countTestsFailed;
71
+ throw new Error(msg);
72
+ }
73
+ }
74
+ to(callback) {
75
+ this.triggerResult(
76
+ callback(this.actualValue),
77
+ ` Expected callback to validate`
78
+ );
79
+ }
80
+ toBe(expectedValue) {
81
+ this.triggerResult(
82
+ this.actualValue === expectedValue,
83
+ ` Expected values to match using ===
84
+ Expected: ${expectedValue} (${typeof expectedValue})
85
+ Actual: ${this.actualValue} (${typeof this.actualValue})`
86
+ );
87
+ }
88
+ toEqual(expectedValue) {
89
+ this.triggerResult(
90
+ this.actualValue == expectedValue,
91
+ ` Expected values to match using ==
92
+ Expected: ${expectedValue} (${typeof expectedValue})
93
+ Actual: ${this.actualValue} (${typeof this.actualValue})`
94
+ );
95
+ }
96
+ toEqualArray(expectedValue) {
97
+ let success = Array.isArray(this.actualValue) && Array.isArray(expectedValue) && this.actualValue.length === expectedValue.length;
98
+ for (let i = 0; i < this.actualValue.length; i++) {
99
+ const actualVal = this.actualValue[i];
100
+ const expectedVal = expectedValue[i];
101
+ success = actualVal == expectedVal;
102
+ if (!success)
103
+ break;
104
+ }
105
+ this.triggerResult(
106
+ success,
107
+ ` Expected array items to match using ==
108
+ Expected: ${expectedValue} (${typeof expectedValue})
109
+ Actual: ${this.actualValue} (${typeof this.actualValue})`
110
+ );
111
+ }
112
+ toMatch(expectedValue) {
113
+ if (typeof this.actualValue.match !== "function") {
114
+ throw new Error(`You can not use toMatch on type ${typeof this.actualValue}`);
115
+ }
116
+ this.triggerResult(
117
+ !!this.actualValue.match(expectedValue),
118
+ " Expected values to match using regular expression\n Expression: " + expectedValue + "\n Actual: " + this.actualValue
119
+ );
120
+ }
121
+ toBeDefined() {
122
+ this.triggerResult(
123
+ typeof this.actualValue !== "undefined",
124
+ ` Expected value to be defined`
125
+ );
126
+ }
127
+ toBeUndefined() {
128
+ this.triggerResult(
129
+ typeof this.actualValue === "undefined",
130
+ ` Expected value to be undefined`
131
+ );
132
+ }
133
+ toBeNull() {
134
+ this.triggerResult(
135
+ this.actualValue === null,
136
+ ` Expected value to be null`
137
+ );
138
+ }
139
+ toBeTruthy() {
140
+ this.triggerResult(
141
+ this.actualValue,
142
+ ` Expected value to be truthy`
143
+ );
144
+ }
145
+ toBeFalsy() {
146
+ this.triggerResult(
147
+ !this.actualValue,
148
+ ` Expected value to be falsy`
149
+ );
150
+ }
151
+ toContain(needle) {
152
+ this.triggerResult(
153
+ this.actualValue instanceof Array && this.actualValue.indexOf(needle) !== -1,
154
+ ` Expected ` + this.actualValue + ` to contain ` + needle
155
+ );
156
+ }
157
+ toBeLessThan(greaterValue) {
158
+ this.triggerResult(
159
+ this.actualValue < greaterValue,
160
+ ` Expected ` + this.actualValue + ` to be less than ` + greaterValue
161
+ );
162
+ }
163
+ toBeGreaterThan(smallerValue) {
164
+ this.triggerResult(
165
+ this.actualValue > smallerValue,
166
+ ` Expected ` + this.actualValue + ` to be greater than ` + smallerValue
167
+ );
168
+ }
169
+ toBeCloseTo(expectedValue, precision) {
170
+ const shiftHelper = Math.pow(10, precision);
171
+ this.triggerResult(
172
+ Math.round(this.actualValue * shiftHelper) / shiftHelper === Math.round(expectedValue * shiftHelper) / shiftHelper,
173
+ ` Expected ` + this.actualValue + ` with precision ` + precision + ` to be close to ` + expectedValue
174
+ );
175
+ }
176
+ toThrow(ErrorType) {
177
+ let errorMessage = "";
178
+ let didThrow = false;
179
+ let typeMatch = true;
180
+ try {
181
+ this.actualValue();
182
+ didThrow = false;
183
+ } catch (e) {
184
+ errorMessage = e.message || "";
185
+ didThrow = true;
186
+ if (ErrorType) {
187
+ typeMatch = e instanceof ErrorType;
188
+ }
189
+ }
190
+ const functionName = this.actualValue.name || typeof this.actualValue === "function" ? "[anonymous function]" : this.actualValue.toString();
191
+ this.triggerResult(
192
+ didThrow,
193
+ ` Expected ${functionName} to ${this.positive ? "throw" : "not throw"} an exception ${!this.positive && errorMessage ? `, but an error with the message "${errorMessage}" was thrown` : ""}`
194
+ );
195
+ if (ErrorType) {
196
+ this.triggerResult(
197
+ typeMatch,
198
+ ` Expected Error type '${ErrorType.name}', but the error is not an instance of it`
199
+ );
200
+ }
201
+ }
202
+ }
203
+ const describe = async function(moduleName, callback) {
204
+ print("\n" + moduleName);
205
+ await callback();
206
+ beforeEachCb = null;
207
+ afterEachCb = null;
208
+ };
209
+ const runtimeMatch = async function(onRuntime, version) {
210
+ const currRuntime = await getRuntime();
211
+ const foundRuntime = onRuntime.find((r) => currRuntime.includes(r));
212
+ if (!foundRuntime) {
213
+ return {
214
+ matched: false
215
+ };
216
+ }
217
+ if (typeof version === "string") {
218
+ if (!currRuntime.includes(version)) {
219
+ return {
220
+ matched: false
221
+ };
222
+ }
223
+ }
224
+ return {
225
+ matched: true,
226
+ runtime: foundRuntime,
227
+ version
228
+ };
229
+ };
230
+ const on = async function(onRuntime, version, callback) {
231
+ if (typeof onRuntime === "string") {
232
+ onRuntime = [onRuntime];
233
+ }
234
+ if (typeof version === "function") {
235
+ callback = version;
236
+ version = void 0;
237
+ }
238
+ const { matched } = await runtimeMatch(onRuntime, version);
239
+ if (!matched) {
240
+ ++countTestsIgnored;
241
+ return;
242
+ }
243
+ print(`
244
+ On ${onRuntime.join(", ")}${version ? " " + version : ""}`);
245
+ await callback();
246
+ };
247
+ let beforeEachCb;
248
+ let afterEachCb;
249
+ const beforeEach = function(callback) {
250
+ beforeEachCb = callback;
251
+ };
252
+ const afterEach = function(callback) {
253
+ afterEachCb = callback;
254
+ };
255
+ const it = async function(expectation, callback) {
256
+ try {
257
+ if (typeof beforeEachCb === "function") {
258
+ await beforeEachCb();
259
+ }
260
+ await callback();
261
+ if (typeof afterEachCb === "function") {
262
+ await afterEachCb();
263
+ }
264
+ print(` ${GREEN}\u2714${RESET} ${GRAY}${expectation}${RESET}`);
265
+ } catch (e) {
266
+ print(` ${RED}\u274C${RESET} ${GRAY}${expectation}${RESET}`);
267
+ print(`${RED}${e.message}${RESET}`);
268
+ if (e.stack)
269
+ print(e.stack);
270
+ }
271
+ };
272
+ const expect = function(actualValue) {
273
+ ++countTestsOverall;
274
+ const expecter = new MatcherFactory(actualValue, true);
275
+ return expecter;
276
+ };
277
+ const assert = function(success, message) {
278
+ ++countTestsOverall;
279
+ if (!success) {
280
+ ++countTestsFailed;
281
+ }
282
+ (0, import_assert.default)(success, message);
283
+ };
284
+ assert.strictEqual = function(actual, expected, message) {
285
+ ++countTestsOverall;
286
+ try {
287
+ import_assert.default.strictEqual(actual, expected, message);
288
+ } catch (error) {
289
+ ++countTestsFailed;
290
+ throw error;
291
+ }
292
+ };
293
+ assert.throws = function(promiseFn, ...args) {
294
+ ++countTestsOverall;
295
+ let error;
296
+ try {
297
+ promiseFn();
298
+ } catch (e) {
299
+ error = e;
300
+ }
301
+ if (!error)
302
+ ++countTestsFailed;
303
+ import_assert.default.throws(() => {
304
+ if (error)
305
+ throw error;
306
+ }, args[0], args[1]);
307
+ };
308
+ assert.deepStrictEqual = function(actual, expected, message) {
309
+ ++countTestsOverall;
310
+ try {
311
+ import_assert.default.deepStrictEqual(actual, expected, message);
312
+ } catch (error) {
313
+ ++countTestsFailed;
314
+ throw error;
315
+ }
316
+ };
317
+ const runTests = async function(namespaces) {
318
+ for (const subNamespace in namespaces) {
319
+ const namespace = namespaces[subNamespace];
320
+ if (typeof namespace === "function") {
321
+ await namespace();
322
+ } else if (typeof namespace === "object") {
323
+ await runTests(namespace);
324
+ }
325
+ }
326
+ };
327
+ const printResult = () => {
328
+ if (countTestsIgnored) {
329
+ print(`
330
+ ${BLUE}\u2714 ${countTestsIgnored} ignored test${countTestsIgnored > 1 ? "s" : ""}${RESET}`);
331
+ }
332
+ if (countTestsFailed) {
333
+ print(`
334
+ ${RED}\u274C ${countTestsFailed} of ${countTestsOverall} tests failed${RESET}`);
335
+ } else {
336
+ print(`
337
+ ${GREEN}\u2714 ${countTestsOverall} completed${RESET}`);
338
+ }
339
+ };
340
+ const getRuntime = async () => {
341
+ if (runtime && runtime !== "Unknown") {
342
+ return runtime;
343
+ }
344
+ if (globalThis.Deno?.version?.deno) {
345
+ return "Deno " + globalThis.Deno?.version?.deno;
346
+ } else {
347
+ let process = globalThis.process;
348
+ if (!process) {
349
+ try {
350
+ process = await import("process");
351
+ } catch (error) {
352
+ console.error(error);
353
+ console.warn(error.message);
354
+ runtime = "Unknown";
355
+ }
356
+ }
357
+ if (process?.versions?.gjs) {
358
+ runtime = "Gjs " + process.versions.gjs;
359
+ } else if (process?.versions?.node) {
360
+ runtime = "Node.js " + process.versions.node;
361
+ }
362
+ }
363
+ return runtime || "Unknown";
364
+ };
365
+ const printRuntime = async () => {
366
+ const runtime2 = await getRuntime();
367
+ print(`
368
+ Running on ${runtime2}`);
369
+ };
370
+ const run = async (namespaces) => {
371
+ printRuntime().then(async () => {
372
+ return runTests(namespaces).then(() => {
373
+ printResult();
374
+ print();
375
+ mainloop?.quit();
376
+ });
377
+ });
378
+ mainloop?.run();
379
+ };
380
+ var src_default = {
381
+ run,
382
+ assert,
383
+ expect,
384
+ it,
385
+ afterEach,
386
+ beforeEach,
387
+ on,
388
+ describe,
389
+ print
390
+ };
package/lib/cjs/spy.js ADDED
@@ -0,0 +1,158 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+ var spy_exports = {};
19
+ __export(spy_exports, {
20
+ spy: () => spy
21
+ });
22
+ module.exports = __toCommonJS(spy_exports);
23
+ function spy(f) {
24
+ const calls = [];
25
+ function spy2(...args) {
26
+ let retv;
27
+ try {
28
+ retv = f ? f.apply(this, args) : void 0;
29
+ } catch (error) {
30
+ calls.push({
31
+ type: "throw",
32
+ this: this,
33
+ arguments: args,
34
+ throw: error
35
+ });
36
+ throw error;
37
+ }
38
+ calls.push({
39
+ type: "return",
40
+ this: this,
41
+ arguments: args,
42
+ return: retv
43
+ });
44
+ return retv;
45
+ }
46
+ Object.defineProperties(spy2, {
47
+ calls: descriptors.calls(calls),
48
+ returnedCalls: descriptors.returnedCalls,
49
+ thrownCalls: descriptors.thrownCalls,
50
+ firstCall: descriptors.firstCall,
51
+ lastCall: descriptors.lastCall,
52
+ firstReturnedCall: descriptors.firstReturnedCall,
53
+ lastReturnedCall: descriptors.lastReturnedCall,
54
+ firstThrownCall: descriptors.firstThrownCall,
55
+ lastThrownCall: descriptors.lastThrownCall,
56
+ reset: descriptors.reset,
57
+ toString: descriptors.toString(f)
58
+ });
59
+ return spy2;
60
+ }
61
+ const descriptors = {
62
+ calls(value) {
63
+ return { value, configurable: true };
64
+ },
65
+ returnedCalls: {
66
+ get: function returnedCalls() {
67
+ return this.calls.filter(isReturned);
68
+ },
69
+ configurable: true
70
+ },
71
+ thrownCalls: {
72
+ get: function thrownCalls() {
73
+ return this.calls.filter(isThrown);
74
+ },
75
+ configurable: true
76
+ },
77
+ firstCall: {
78
+ get: function firstCall() {
79
+ return this.calls[0] || null;
80
+ },
81
+ configurable: true
82
+ },
83
+ lastCall: {
84
+ get: function lastCall() {
85
+ return this.calls[this.calls.length - 1] || null;
86
+ },
87
+ configurable: true
88
+ },
89
+ firstReturnedCall: {
90
+ get: function firstReturnedCall() {
91
+ for (let i = 0; i < this.calls.length; ++i) {
92
+ const call = this.calls[i];
93
+ if (isReturned(call)) {
94
+ return call;
95
+ }
96
+ }
97
+ return null;
98
+ },
99
+ configurable: true
100
+ },
101
+ lastReturnedCall: {
102
+ get: function lastReturnedCall() {
103
+ for (let i = this.calls.length - 1; i >= 0; --i) {
104
+ const call = this.calls[i];
105
+ if (isReturned(call)) {
106
+ return call;
107
+ }
108
+ }
109
+ return null;
110
+ },
111
+ configurable: true
112
+ },
113
+ firstThrownCall: {
114
+ get: function firstThrownCall() {
115
+ for (let i = 0; i < this.calls.length; ++i) {
116
+ const call = this.calls[i];
117
+ if (isThrown(call)) {
118
+ return call;
119
+ }
120
+ }
121
+ return null;
122
+ },
123
+ configurable: true
124
+ },
125
+ lastThrownCall: {
126
+ get: function lastThrownCall() {
127
+ for (let i = this.calls.length - 1; i >= 0; --i) {
128
+ const call = this.calls[i];
129
+ if (isThrown(call)) {
130
+ return call;
131
+ }
132
+ }
133
+ return null;
134
+ },
135
+ configurable: true
136
+ },
137
+ reset: {
138
+ value: function reset() {
139
+ ;
140
+ this.calls.length = 0;
141
+ },
142
+ configurable: true
143
+ },
144
+ toString(f) {
145
+ return {
146
+ value: function toString() {
147
+ return `/* The spy of */ ${f ? f.toString() : "function(){}"}`;
148
+ },
149
+ configurable: true
150
+ };
151
+ }
152
+ };
153
+ function isReturned(call) {
154
+ return call.type === "return";
155
+ }
156
+ function isThrown(call) {
157
+ return call.type === "throw";
158
+ }