mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-11 14:39:30 +08:00
Add runtime tests
This commit is contained in:
parent
95b4bfc253
commit
52f1b595e8
@ -28,6 +28,8 @@
|
|||||||
"./dist/drag.js"
|
"./dist/drag.js"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"check": "npx tsc --noEmit",
|
||||||
|
"test": "npx vitest run",
|
||||||
"clean": "npx rimraf ./dist ./docs ./types ./tsconfig.tsbuildinfo",
|
"clean": "npx rimraf ./dist ./docs ./types ./tsconfig.tsbuildinfo",
|
||||||
"generate:events": "task generate:events",
|
"generate:events": "task generate:events",
|
||||||
"generate": "npm run generate:events",
|
"generate": "npm run generate:events",
|
||||||
@ -39,11 +41,21 @@
|
|||||||
"prepack": "npm run build"
|
"prepack": "npm run build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"happy-dom": "^17.1.1",
|
||||||
|
"promises-aplus-tests": "2.1.2",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"typedoc": "^0.27.7",
|
"typedoc": "^0.27.7",
|
||||||
"typedoc-plugin-markdown": "^4.4.2",
|
"typedoc-plugin-markdown": "^4.4.2",
|
||||||
"typedoc-plugin-mdn-links": "^4.0.13",
|
"typedoc-plugin-mdn-links": "^4.0.13",
|
||||||
"typedoc-plugin-missing-exports": "^3.1.0",
|
"typedoc-plugin-missing-exports": "^3.1.0",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
|
"vitest": "^3.0.6"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"promises-aplus-tests": {
|
||||||
|
"mocha": "^11.1.0",
|
||||||
|
"sinon": "^19.0.2",
|
||||||
|
"underscore": "^1.13.7"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,430 @@
|
|||||||
|
import * as util from "node:util";
|
||||||
|
import { describe, it, beforeEach, afterEach, assert, expect, vi } from "vitest";
|
||||||
|
import { CancelError, CancellablePromise, CancelledRejectionError } from "./cancellable";
|
||||||
|
|
||||||
|
// TODO: In order of importance:
|
||||||
|
// TODO: test cancellation of subpromises the main promise resolves to.
|
||||||
|
// TODO: test cancellation of promise chains built by calling then() and friends:
|
||||||
|
// - all promises up the chain should be cancelled;
|
||||||
|
// - rejection handlers should be always executed with the CancelError of their parent promise in the chain;
|
||||||
|
// - promises returned from rejection handlers should be cancelled too;
|
||||||
|
// - if a rejection handler throws or returns a promise that ultimately rejects,
|
||||||
|
// it should be reported as an unhandled rejection,
|
||||||
|
// - unless it is a CancelError with the same reason given for cancelling the returned promise.
|
||||||
|
// TODO: test multiple calls to cancel() (second and later should have no effect).
|
||||||
|
|
||||||
|
let expectedUnhandled = new Map();
|
||||||
|
|
||||||
|
process.on('unhandledRejection', function (error, promise) {
|
||||||
|
let reason = error;
|
||||||
|
if (reason instanceof CancelledRejectionError) {
|
||||||
|
promise = reason.promise;
|
||||||
|
reason = reason.cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
let reasons = expectedUnhandled.get(promise);
|
||||||
|
const callbacks = reasons?.get(reason);
|
||||||
|
if (callbacks) {
|
||||||
|
for (const cb of callbacks) {
|
||||||
|
try {
|
||||||
|
cb(reason, promise);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Exception in unhandled rejection callback.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reasons.delete(reason);
|
||||||
|
if (reasons.size === 0) {
|
||||||
|
expectedUnhandled.delete(promise);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(util.format("Unhandled rejection.\nReason: %o\nPromise: %o", reason, promise));
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
|
function ignoreUnhandled(reason, promise) {
|
||||||
|
expectUnhandled(reason, promise, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectUnhandled(reason, promise, cb) {
|
||||||
|
let reasons = expectedUnhandled.get(promise);
|
||||||
|
if (!reasons) {
|
||||||
|
reasons = new Map();
|
||||||
|
expectedUnhandled.set(promise, reasons);
|
||||||
|
}
|
||||||
|
let callbacks = reasons.get(reason);
|
||||||
|
if (!callbacks) {
|
||||||
|
callbacks = [];
|
||||||
|
reasons.set(reason, callbacks);
|
||||||
|
}
|
||||||
|
if (cb) {
|
||||||
|
callbacks.push(cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.resetAllMocks();
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const dummyValue = { value: "value" };
|
||||||
|
const dummyCause = { dummy: "dummy" };
|
||||||
|
const dummyError = new Error("dummy");
|
||||||
|
const oncancelled = vi.fn().mockName("oncancelled");
|
||||||
|
const sentinel = vi.fn().mockName("sentinel");
|
||||||
|
const unhandled = vi.fn().mockName("unhandled");
|
||||||
|
|
||||||
|
const resolutionPatterns = [
|
||||||
|
["forever", "pending", (test, value, { cls = CancellablePromise, cb = oncancelled } = {}) => test(
|
||||||
|
new cls(() => {}, cb)
|
||||||
|
)],
|
||||||
|
["already", "fulfilled", (test, value, { cls = CancellablePromise, cb = oncancelled } = {}) => {
|
||||||
|
const prw = cls.withResolvers();
|
||||||
|
prw.oncancelled = cb;
|
||||||
|
prw.resolve(value ?? dummyValue);
|
||||||
|
return test(prw.promise);
|
||||||
|
}],
|
||||||
|
["immediately", "fulfilled", (test, value, { cls = CancellablePromise, cb = oncancelled } = {}) => {
|
||||||
|
const prw = cls.withResolvers();
|
||||||
|
prw.oncancelled = cb;
|
||||||
|
const tp = test(prw.promise);
|
||||||
|
prw.resolve(value ?? dummyValue);
|
||||||
|
return tp;
|
||||||
|
}],
|
||||||
|
["eventually", "fulfilled", async (test, value, { cls = CancellablePromise, cb = oncancelled } = {}) => {
|
||||||
|
const prw = cls.withResolvers();
|
||||||
|
prw.oncancelled = cb;
|
||||||
|
const tp = test(prw.promise);
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
prw.resolve(value ?? dummyValue);
|
||||||
|
resolve();
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
return tp;
|
||||||
|
}],
|
||||||
|
["already", "rejected", (test, reason, { cls = CancellablePromise, cb = oncancelled } = {}) => {
|
||||||
|
const prw = cls.withResolvers();
|
||||||
|
prw.oncancelled = cb;
|
||||||
|
prw.reject(reason ?? dummyError);
|
||||||
|
return test(prw.promise);
|
||||||
|
}],
|
||||||
|
["immediately", "rejected", (test, reason, { cls = CancellablePromise, cb = oncancelled } = {}) => {
|
||||||
|
const prw = cls.withResolvers();
|
||||||
|
prw.oncancelled = cb;
|
||||||
|
const tp = test(prw.promise);
|
||||||
|
prw.reject(reason ?? dummyError);
|
||||||
|
return tp;
|
||||||
|
}],
|
||||||
|
["eventually", "rejected", async (test, reason, { cls = CancellablePromise, cb = oncancelled } = {}) => {
|
||||||
|
const prw = cls.withResolvers();
|
||||||
|
prw.oncancelled = cb;
|
||||||
|
const tp = test(prw.promise);
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
prw.reject(reason ?? dummyError);
|
||||||
|
resolve();
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
|
return tp;
|
||||||
|
}],
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("CancellablePromise.cancel", ()=> {
|
||||||
|
it("should suppress its own unhandled cancellation error", async () => {
|
||||||
|
const p = new CancellablePromise(() => {});
|
||||||
|
p.cancel();
|
||||||
|
|
||||||
|
process.on('unhandledRejection', sentinel);
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
process.off('unhandledRejection', sentinel);
|
||||||
|
|
||||||
|
expect(sentinel).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.for([
|
||||||
|
["rejections", dummyError],
|
||||||
|
["cancellation errors", new CancelError("dummy", { cause: dummyCause })],
|
||||||
|
])("should not suppress arbitrary unhandled %s", async ([kind, err]) => {
|
||||||
|
const p = new CancellablePromise(() => { throw err; });
|
||||||
|
p.cancel();
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
expectUnhandled(err, p, unhandled);
|
||||||
|
expectUnhandled(err, p, resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(unhandled).toHaveBeenCalledExactlyOnceWith(err, p);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.for(resolutionPatterns)("when applied to %s %s promises", ([time, state, test]) => {
|
||||||
|
if (time === "already") {
|
||||||
|
it("should have no effect", () => test(async (promise) => {
|
||||||
|
promise.then(sentinel, sentinel);
|
||||||
|
|
||||||
|
let reason;
|
||||||
|
try {
|
||||||
|
promise.cancel();
|
||||||
|
await promise;
|
||||||
|
assert(state === "fulfilled", "Promise fulfilled unexpectedly");
|
||||||
|
} catch (err) {
|
||||||
|
reason = err;
|
||||||
|
assert(state === "rejected", "Promise rejected unexpectedly");
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(sentinel).toHaveBeenCalled();
|
||||||
|
expect(oncancelled).not.toHaveBeenCalled();
|
||||||
|
expect(reason).not.toBeInstanceOf(CancelError);
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
if (state === "rejected") {
|
||||||
|
it("should report late rejections as unhandled", () => test(async (promise) => {
|
||||||
|
promise.cancel();
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
expectUnhandled(dummyError, promise, unhandled);
|
||||||
|
expectUnhandled(dummyError, promise, resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(unhandled).toHaveBeenCalledExactlyOnceWith(dummyError, promise);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should reject with a CancelError", () => test(async (promise) => {
|
||||||
|
// Ignore the unhandled rejection from the test promise.
|
||||||
|
if (state === "rejected") { ignoreUnhandled(dummyError, promise); }
|
||||||
|
|
||||||
|
let reason;
|
||||||
|
try {
|
||||||
|
promise.cancel();
|
||||||
|
await promise;
|
||||||
|
} catch (err) {
|
||||||
|
reason = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(reason).toBeInstanceOf(CancelError);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should call the oncancelled callback synchronously", () => test(async (promise) => {
|
||||||
|
// Ignore the unhandled rejection from the test promise.
|
||||||
|
if (state === "rejected") { ignoreUnhandled(dummyError, promise); }
|
||||||
|
|
||||||
|
try {
|
||||||
|
promise.cancel();
|
||||||
|
sentinel();
|
||||||
|
await promise;
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
expect(oncancelled).toHaveBeenCalledBefore(sentinel);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("should propagate the given cause", () => test(async (promise) => {
|
||||||
|
// Ignore the unhandled rejection from the test promise.
|
||||||
|
if (state === "rejected") { ignoreUnhandled(dummyError, promise); }
|
||||||
|
|
||||||
|
let reason;
|
||||||
|
try {
|
||||||
|
promise.cancel(dummyCause);
|
||||||
|
await promise;
|
||||||
|
} catch (err) {
|
||||||
|
reason = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(reason).toBeInstanceOf(CancelError);
|
||||||
|
expect(reason).toHaveProperty('cause', dummyCause);
|
||||||
|
expect(oncancelled).toHaveBeenCalledWith(reason.cause);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const onabort = vi.fn().mockName("abort");
|
||||||
|
|
||||||
|
const abortPatterns = [
|
||||||
|
["never", "standalone", (test) => {
|
||||||
|
const signal = new AbortSignal();
|
||||||
|
signal.addEventListener('abort', onabort, { capture: true });
|
||||||
|
return test(signal);
|
||||||
|
}],
|
||||||
|
["already", "standalone", (test) => {
|
||||||
|
const signal = AbortSignal.abort(dummyCause);
|
||||||
|
onabort();
|
||||||
|
return test(signal);
|
||||||
|
}],
|
||||||
|
["eventually", "standalone", (test) => {
|
||||||
|
const signal = AbortSignal.timeout(25);
|
||||||
|
signal.addEventListener('abort', onabort, { capture: true });
|
||||||
|
return test(signal);
|
||||||
|
}],
|
||||||
|
["never", "controller-bound", (test) => {
|
||||||
|
const signal = new AbortController().signal;
|
||||||
|
signal.addEventListener('abort', onabort, { capture: true });
|
||||||
|
return test(signal);
|
||||||
|
}],
|
||||||
|
["already", " controller-bound", (test) => {
|
||||||
|
const ctrl = new AbortController();
|
||||||
|
ctrl.signal.addEventListener('abort', onabort, { capture: true });
|
||||||
|
ctrl.abort(dummyCause);
|
||||||
|
return test(ctrl.signal);
|
||||||
|
}],
|
||||||
|
["immediately", "controller-bound", (test) => {
|
||||||
|
const ctrl = new AbortController();
|
||||||
|
ctrl.signal.addEventListener('abort', onabort, { capture: true });
|
||||||
|
const tp = test(ctrl.signal);
|
||||||
|
ctrl.abort(dummyCause);
|
||||||
|
return tp;
|
||||||
|
}],
|
||||||
|
["eventually", "controller-bound", (test) => {
|
||||||
|
const ctrl = new AbortController();
|
||||||
|
ctrl.signal.addEventListener('abort', onabort, { capture: true });
|
||||||
|
const tp = test(ctrl.signal);
|
||||||
|
setTimeout(() => ctrl.abort(dummyCause), 25);
|
||||||
|
return tp;
|
||||||
|
}]
|
||||||
|
];
|
||||||
|
|
||||||
|
describe("CancellablePromise.cancelOn", ()=> {
|
||||||
|
it("should return the target promise for chaining", () => {
|
||||||
|
const p = new CancellablePromise(() => {});
|
||||||
|
expect(p.cancelOn(AbortSignal.abort())).toBe(p);
|
||||||
|
});
|
||||||
|
|
||||||
|
function tests(abortTime, mode, testSignal, resolveTime, state, testPromise) {
|
||||||
|
if (abortTime !== "never") {
|
||||||
|
it(`should call CancellablePromise.cancel ${abortTime === "already" ? "immediately" : "on abort"} with the abort reason as cause`, () => testSignal((signal) => testPromise(async (promise) => {
|
||||||
|
// Ignore the unhandled rejection from the test promise.
|
||||||
|
if (state === "rejected") { ignoreUnhandled(dummyError, promise); }
|
||||||
|
|
||||||
|
const cancelSpy = vi.spyOn(promise, 'cancel');
|
||||||
|
|
||||||
|
promise.catch(() => {});
|
||||||
|
promise.cancelOn(signal);
|
||||||
|
|
||||||
|
if (signal.aborted) {
|
||||||
|
sentinel();
|
||||||
|
} else {
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
signal.onabort = () => {
|
||||||
|
sentinel();
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(cancelSpy).toHaveBeenCalledAfter(onabort);
|
||||||
|
expect(cancelSpy).toHaveBeenCalledBefore(sentinel);
|
||||||
|
expect(cancelSpy).toHaveBeenCalledExactlyOnceWith(signal.reason);
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
resolveTime === "already"
|
||||||
|
|| abortTime === "never"
|
||||||
|
|| (
|
||||||
|
["immediately", "eventually"].includes(abortTime)
|
||||||
|
&& ["already", "immediately"].includes(resolveTime)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
it("should have no effect", () => testSignal((signal) => testPromise(async (promise) => {
|
||||||
|
promise.then(sentinel, sentinel);
|
||||||
|
|
||||||
|
let reason;
|
||||||
|
try {
|
||||||
|
if (resolveTime !== "forever") {
|
||||||
|
await promise.cancelOn(signal);
|
||||||
|
assert(state === "fulfilled", "Promise fulfilled unexpectedly");
|
||||||
|
} else {
|
||||||
|
await Promise.race([promise, new Promise((resolve) => setTimeout(resolve, 100))]).then(sentinel);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reason = err;
|
||||||
|
assert(state === "rejected", "Promise rejected unexpectedly");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abortTime !== "never" && !signal.aborted) {
|
||||||
|
// Wait for the AbortSignal to have actually aborted.
|
||||||
|
await new Promise((resolve) => signal.onabort = resolve);
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(sentinel).toHaveBeenCalled();
|
||||||
|
expect(oncancelled).not.toHaveBeenCalled();
|
||||||
|
expect(reason).not.toBeInstanceOf(CancelError);
|
||||||
|
})));
|
||||||
|
} else {
|
||||||
|
if (state === "rejected") {
|
||||||
|
it("should report late rejections as unhandled", () => testSignal((signal) => testPromise(async (promise) => {
|
||||||
|
promise.cancelOn(signal);
|
||||||
|
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
expectUnhandled(dummyError, promise, unhandled);
|
||||||
|
expectUnhandled(dummyError, promise, resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(unhandled).toHaveBeenCalledExactlyOnceWith(dummyError, promise);
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should reject with a CancelError", () => testSignal((signal) => testPromise(async (promise)=> {
|
||||||
|
// Ignore the unhandled rejection from the test promise.
|
||||||
|
if (state === "rejected") { ignoreUnhandled(dummyError, promise); }
|
||||||
|
|
||||||
|
let reason;
|
||||||
|
try {
|
||||||
|
await promise.cancelOn(signal);
|
||||||
|
} catch (err) {
|
||||||
|
reason = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(reason).toBeInstanceOf(CancelError);
|
||||||
|
})));
|
||||||
|
|
||||||
|
it(`should call the oncancelled callback ${abortTime === "already" ? "" : "a"}synchronously`, () => testSignal((signal) => testPromise(async (promise) => {
|
||||||
|
// Ignore the unhandled rejection from the test promise.
|
||||||
|
if (state === "rejected") { ignoreUnhandled(dummyError, promise); }
|
||||||
|
|
||||||
|
try {
|
||||||
|
promise.cancelOn(signal);
|
||||||
|
sentinel();
|
||||||
|
await promise;
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
expect(oncancelled).toHaveBeenCalledAfter(onabort);
|
||||||
|
if (abortTime === "already") {
|
||||||
|
expect(oncancelled).toHaveBeenCalledBefore(sentinel);
|
||||||
|
} else {
|
||||||
|
expect(oncancelled).toHaveBeenCalledAfter(sentinel);
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
|
||||||
|
it("should propagate the abort reason as cause", () => testSignal((signal) => testPromise(async (promise) => {
|
||||||
|
// Ignore the unhandled rejection from the test promise.
|
||||||
|
if (state === "rejected") { ignoreUnhandled(dummyError, promise); }
|
||||||
|
|
||||||
|
let reason;
|
||||||
|
try {
|
||||||
|
await promise.cancelOn(signal);
|
||||||
|
} catch (err) {
|
||||||
|
reason = err;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(reason).toBeInstanceOf(CancelError);
|
||||||
|
expect(reason).toHaveProperty('cause', signal.reason);
|
||||||
|
expect(oncancelled).toHaveBeenCalledWith(signal.reason);
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe.for(abortPatterns)("when called with %s aborted %s signals", ([abortTime, mode, testSignal]) => {
|
||||||
|
describe.for(resolutionPatterns)("when applied to %s %s promises", ([resolveTime, state, testPromise]) => {
|
||||||
|
tests(abortTime, mode, testSignal, resolveTime, state, testPromise);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe.for(resolutionPatterns)("when applied to %s %s promises", ([resolveTime, state, testPromise]) => {
|
||||||
|
describe.for(abortPatterns)("when called with %s aborted %s signals", ([abortTime, mode, testSignal]) => {
|
||||||
|
tests(abortTime, mode, testSignal, resolveTime, state, testPromise);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,18 +1,28 @@
|
|||||||
|
import { On, Off, OffAll, OnMultiple, WailsEvent, Once } from './events';
|
||||||
import { On, Off, OffAll, OnMultiple, WailsEvent, dispatchWailsEvent, eventListeners, Once } from './events';
|
import { eventListeners } from "./listener";
|
||||||
|
|
||||||
import { expect, describe, it, vi, afterEach, beforeEach } from 'vitest';
|
import { expect, describe, it, vi, afterEach, beforeEach } from 'vitest';
|
||||||
|
|
||||||
|
const dispatchWailsEvent = window._wails.dispatchWailsEvent;
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
OffAll();
|
OffAll();
|
||||||
vi.resetAllMocks();
|
vi.resetAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('OnMultiple', () => {
|
describe("OnMultiple", () => {
|
||||||
let testEvent = new WailsEvent('a', {});
|
const testEvent = { name: 'a', data: ["hello", "events"] };
|
||||||
|
const cb = vi.fn((ev) => {
|
||||||
|
expect(ev).toBeInstanceOf(WailsEvent);
|
||||||
|
expect(ev).toMatchObject(testEvent);
|
||||||
|
});
|
||||||
|
|
||||||
it('should stop after a specified number of times', () => {
|
it("should dispatch a properly initialised WailsEvent", () => {
|
||||||
const cb = vi.fn();
|
OnMultiple('a', cb, 5);
|
||||||
|
dispatchWailsEvent(testEvent);
|
||||||
|
expect(cb).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should stop after the specified number of times", () => {
|
||||||
OnMultiple('a', cb, 5);
|
OnMultiple('a', cb, 5);
|
||||||
dispatchWailsEvent(testEvent);
|
dispatchWailsEvent(testEvent);
|
||||||
dispatchWailsEvent(testEvent);
|
dispatchWailsEvent(testEvent);
|
||||||
@ -20,77 +30,122 @@ describe('OnMultiple', () => {
|
|||||||
dispatchWailsEvent(testEvent);
|
dispatchWailsEvent(testEvent);
|
||||||
dispatchWailsEvent(testEvent);
|
dispatchWailsEvent(testEvent);
|
||||||
dispatchWailsEvent(testEvent);
|
dispatchWailsEvent(testEvent);
|
||||||
expect(cb).toBeCalledTimes(5);
|
expect(cb).toHaveBeenCalledTimes(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a cancel fn', () => {
|
it("should return a cancel fn", () => {
|
||||||
const cb = vi.fn()
|
const cancel = OnMultiple('a', cb, 5);
|
||||||
const cancel = OnMultiple('a', cb, 5)
|
dispatchWailsEvent(testEvent);
|
||||||
dispatchWailsEvent(testEvent)
|
dispatchWailsEvent(testEvent);
|
||||||
dispatchWailsEvent(testEvent)
|
|
||||||
cancel()
|
|
||||||
dispatchWailsEvent(testEvent)
|
|
||||||
dispatchWailsEvent(testEvent)
|
|
||||||
expect(cb).toBeCalledTimes(2)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('On', () => {
|
|
||||||
it('should create a listener with a count of -1', () => {
|
|
||||||
On('a', () => {})
|
|
||||||
expect(eventListeners.get("a")[0].maxCallbacks).toBe(-1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return a cancel fn', () => {
|
|
||||||
const cancel = On('a', () => {})
|
|
||||||
cancel();
|
cancel();
|
||||||
})
|
dispatchWailsEvent(testEvent);
|
||||||
})
|
dispatchWailsEvent(testEvent);
|
||||||
|
expect(cb).toBeCalledTimes(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Once', () => {
|
describe("On", () => {
|
||||||
it('should create a listener with a count of 1', () => {
|
let testEvent = { name: 'a', data: ["hello", "events"], sender: "window" };
|
||||||
Once('a', () => {})
|
const cb = vi.fn((ev) => {
|
||||||
expect(eventListeners.get("a")[0].maxCallbacks).toBe(1)
|
expect(ev).toBeInstanceOf(WailsEvent);
|
||||||
})
|
expect(ev).toMatchObject(testEvent);
|
||||||
|
});
|
||||||
|
|
||||||
it('should return a cancel fn', () => {
|
it("should dispatch a properly initialised WailsEvent", () => {
|
||||||
const cancel = EventsOn('a', () => {})
|
On('a', cb);
|
||||||
|
dispatchWailsEvent(testEvent);
|
||||||
|
expect(cb).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should never stop", () => {
|
||||||
|
On('a', cb);
|
||||||
|
expect(eventListeners.get('a')[0].maxCallbacks).toBe(-1);
|
||||||
|
dispatchWailsEvent(testEvent);
|
||||||
|
expect(eventListeners.get('a')[0].maxCallbacks).toBe(-1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a cancel fn", () => {
|
||||||
|
const cancel = On('a', cb)
|
||||||
|
dispatchWailsEvent(testEvent);
|
||||||
cancel();
|
cancel();
|
||||||
})
|
dispatchWailsEvent(testEvent);
|
||||||
|
expect(cb).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Once", () => {
|
||||||
|
const testEvent = { name: 'a', data: ["hello", "events"] };
|
||||||
|
const cb = vi.fn((ev) => {
|
||||||
|
expect(ev).toBeInstanceOf(WailsEvent);
|
||||||
|
expect(ev).toMatchObject(testEvent);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should dispatch a properly initialised WailsEvent", () => {
|
||||||
|
Once('a', cb);
|
||||||
|
dispatchWailsEvent(testEvent);
|
||||||
|
expect(cb).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should stop after one time", () => {
|
||||||
|
Once('a', cb)
|
||||||
|
dispatchWailsEvent(testEvent);
|
||||||
|
dispatchWailsEvent(testEvent);
|
||||||
|
dispatchWailsEvent(testEvent);
|
||||||
|
expect(cb).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a cancel fn", () => {
|
||||||
|
const cancel = Once('a', cb)
|
||||||
|
cancel();
|
||||||
|
dispatchWailsEvent(testEvent);
|
||||||
|
expect(cb).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Off', () => {
|
describe("Off", () => {
|
||||||
|
const cba = vi.fn(), cbb = vi.fn(), cbc = vi.fn();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
On('a', () => {})
|
On('a', cba);
|
||||||
On('a', () => {})
|
On('a', cba);
|
||||||
On('a', () => {})
|
On('a', cba);
|
||||||
On('b', () => {})
|
On('b', cbb);
|
||||||
On('c', () => {})
|
On('c', cbc);
|
||||||
})
|
On('c', cbc);
|
||||||
|
});
|
||||||
|
|
||||||
it('should cancel all event listeners for a single type', () => {
|
it("should cancel all event listeners for a single type", () => {
|
||||||
Off('a')
|
Off('a');
|
||||||
expect(eventListeners.get('a')).toBeUndefined()
|
dispatchWailsEvent({ name: 'a' });
|
||||||
expect(eventListeners.get('b')).not.toBeUndefined()
|
dispatchWailsEvent({ name: 'b' });
|
||||||
expect(eventListeners.get('c')).not.toBeUndefined()
|
dispatchWailsEvent({ name: 'c' });
|
||||||
})
|
expect(cba).not.toHaveBeenCalled();
|
||||||
|
expect(cbb).toHaveBeenCalledTimes(1);
|
||||||
|
expect(cbc).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
it('should cancel all event listeners for multiple types', () => {
|
it("should cancel all event listeners for multiple types", () => {
|
||||||
Off('a', 'b')
|
Off('a', 'c')
|
||||||
expect(eventListeners.get('a')).toBeUndefined()
|
dispatchWailsEvent({ name: 'a' });
|
||||||
expect(eventListeners.get('b')).toBeUndefined()
|
dispatchWailsEvent({ name: 'b' });
|
||||||
expect(eventListeners.get('c')).not.toBeUndefined()
|
dispatchWailsEvent({ name: 'c' });
|
||||||
})
|
expect(cba).not.toHaveBeenCalled();
|
||||||
})
|
expect(cbb).toHaveBeenCalledTimes(1);
|
||||||
|
expect(cbc).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('OffAll', () => {
|
describe("OffAll", () => {
|
||||||
it('should cancel all event listeners', () => {
|
it("should cancel all event listeners", () => {
|
||||||
On('a', () => {})
|
const cba = vi.fn(), cbb = vi.fn(), cbc = vi.fn();
|
||||||
On('a', () => {})
|
On('a', cba);
|
||||||
On('a', () => {})
|
On('a', cba);
|
||||||
On('b', () => {})
|
On('a', cba);
|
||||||
On('c', () => {})
|
On('b', cbb);
|
||||||
OffAll()
|
On('c', cbc);
|
||||||
expect(eventListeners.size).toBe(0)
|
On('c', cbc);
|
||||||
})
|
expect(cba).not.toHaveBeenCalled();
|
||||||
})
|
expect(cbb).not.toHaveBeenCalled();
|
||||||
|
expect(cbc).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
import * as util from "util";
|
||||||
|
import * as V from "vitest";
|
||||||
|
import { CancellablePromise } from "./cancellable";
|
||||||
|
|
||||||
|
// The Promises/A+ suite handles some errors late.
|
||||||
|
process.on('rejectionHandled', function () {});
|
||||||
|
|
||||||
|
// The Promises/A+ suite leaves some errors unhandled.
|
||||||
|
process.on('unhandledRejection', function (reason, promise) {
|
||||||
|
if (promise instanceof CancellablePromise && reason != null && typeof reason === 'object') {
|
||||||
|
for (const key of ['dummy', 'other', 'sentinel']) {
|
||||||
|
if (reason[key] === key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(`Unhandled rejection at: ${util.inspect(promise)}; reason: ${util.inspect(reason)}`, { cause: reason });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emulate a minimal version of the mocha BDD API using vitest primitives.
|
||||||
|
global.context = global.describe = V.describe;
|
||||||
|
global.specify = global.it = function it(desc, fn) {
|
||||||
|
let viTestFn = fn;
|
||||||
|
if (fn && fn.length) {
|
||||||
|
viTestFn = () => new Promise((done) => fn(done));
|
||||||
|
}
|
||||||
|
V.it(desc, viTestFn);
|
||||||
|
}
|
||||||
|
global.before = function(desc, fn) { V.beforeAll(typeof desc === 'function' ? desc : fn) };
|
||||||
|
global.after = function(desc, fn) { V.afterAll(typeof desc === 'function' ? desc : fn) };
|
||||||
|
global.beforeEach = function(desc, fn) { V.beforeEach(typeof desc === 'function' ? desc : fn) };
|
||||||
|
global.afterEach = function(desc, fn) { V.afterEach(typeof desc === 'function' ? desc : fn) };
|
||||||
|
|
||||||
|
require('promises-aplus-tests').mocha({
|
||||||
|
resolved(value) {
|
||||||
|
return CancellablePromise.resolve(value);
|
||||||
|
},
|
||||||
|
rejected(reason) {
|
||||||
|
return CancellablePromise.reject(reason);
|
||||||
|
},
|
||||||
|
deferred() {
|
||||||
|
return CancellablePromise.withResolvers();
|
||||||
|
}
|
||||||
|
});
|
@ -0,0 +1,8 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: 'happy-dom',
|
||||||
|
testTimeout: 200
|
||||||
|
},
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user