mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-10 02:51:35 +08:00
Add runtime tests
This commit is contained in:
parent
95b4bfc253
commit
52f1b595e8
@ -28,6 +28,8 @@
|
||||
"./dist/drag.js"
|
||||
],
|
||||
"scripts": {
|
||||
"check": "npx tsc --noEmit",
|
||||
"test": "npx vitest run",
|
||||
"clean": "npx rimraf ./dist ./docs ./types ./tsconfig.tsbuildinfo",
|
||||
"generate:events": "task generate:events",
|
||||
"generate": "npm run generate:events",
|
||||
@ -39,11 +41,21 @@
|
||||
"prepack": "npm run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"happy-dom": "^17.1.1",
|
||||
"promises-aplus-tests": "2.1.2",
|
||||
"rimraf": "^5.0.5",
|
||||
"typedoc": "^0.27.7",
|
||||
"typedoc-plugin-markdown": "^4.4.2",
|
||||
"typedoc-plugin-mdn-links": "^4.0.13",
|
||||
"typedoc-plugin-missing-exports": "^3.1.0",
|
||||
"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, dispatchWailsEvent, eventListeners, Once } from './events';
|
||||
|
||||
import { On, Off, OffAll, OnMultiple, WailsEvent, Once } from './events';
|
||||
import { eventListeners } from "./listener";
|
||||
import { expect, describe, it, vi, afterEach, beforeEach } from 'vitest';
|
||||
|
||||
const dispatchWailsEvent = window._wails.dispatchWailsEvent;
|
||||
|
||||
afterEach(() => {
|
||||
OffAll();
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('OnMultiple', () => {
|
||||
let testEvent = new WailsEvent('a', {});
|
||||
describe("OnMultiple", () => {
|
||||
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', () => {
|
||||
const cb = vi.fn();
|
||||
it("should dispatch a properly initialised WailsEvent", () => {
|
||||
OnMultiple('a', cb, 5);
|
||||
dispatchWailsEvent(testEvent);
|
||||
expect(cb).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should stop after the specified number of times", () => {
|
||||
OnMultiple('a', cb, 5);
|
||||
dispatchWailsEvent(testEvent);
|
||||
dispatchWailsEvent(testEvent);
|
||||
@ -20,77 +30,122 @@ describe('OnMultiple', () => {
|
||||
dispatchWailsEvent(testEvent);
|
||||
dispatchWailsEvent(testEvent);
|
||||
dispatchWailsEvent(testEvent);
|
||||
expect(cb).toBeCalledTimes(5);
|
||||
expect(cb).toHaveBeenCalledTimes(5);
|
||||
});
|
||||
|
||||
it('should return a cancel fn', () => {
|
||||
const cb = vi.fn()
|
||||
const cancel = OnMultiple('a', cb, 5)
|
||||
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', () => {})
|
||||
it("should return a cancel fn", () => {
|
||||
const cancel = OnMultiple('a', cb, 5);
|
||||
dispatchWailsEvent(testEvent);
|
||||
dispatchWailsEvent(testEvent);
|
||||
cancel();
|
||||
})
|
||||
})
|
||||
dispatchWailsEvent(testEvent);
|
||||
dispatchWailsEvent(testEvent);
|
||||
expect(cb).toBeCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Once', () => {
|
||||
it('should create a listener with a count of 1', () => {
|
||||
Once('a', () => {})
|
||||
expect(eventListeners.get("a")[0].maxCallbacks).toBe(1)
|
||||
})
|
||||
describe("On", () => {
|
||||
let testEvent = { name: 'a', data: ["hello", "events"], sender: "window" };
|
||||
const cb = vi.fn((ev) => {
|
||||
expect(ev).toBeInstanceOf(WailsEvent);
|
||||
expect(ev).toMatchObject(testEvent);
|
||||
});
|
||||
|
||||
it('should return a cancel fn', () => {
|
||||
const cancel = EventsOn('a', () => {})
|
||||
it("should dispatch a properly initialised WailsEvent", () => {
|
||||
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();
|
||||
})
|
||||
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(() => {
|
||||
On('a', () => {})
|
||||
On('a', () => {})
|
||||
On('a', () => {})
|
||||
On('b', () => {})
|
||||
On('c', () => {})
|
||||
})
|
||||
On('a', cba);
|
||||
On('a', cba);
|
||||
On('a', cba);
|
||||
On('b', cbb);
|
||||
On('c', cbc);
|
||||
On('c', cbc);
|
||||
});
|
||||
|
||||
it('should cancel all event listeners for a single type', () => {
|
||||
Off('a')
|
||||
expect(eventListeners.get('a')).toBeUndefined()
|
||||
expect(eventListeners.get('b')).not.toBeUndefined()
|
||||
expect(eventListeners.get('c')).not.toBeUndefined()
|
||||
})
|
||||
it("should cancel all event listeners for a single type", () => {
|
||||
Off('a');
|
||||
dispatchWailsEvent({ name: 'a' });
|
||||
dispatchWailsEvent({ name: 'b' });
|
||||
dispatchWailsEvent({ name: 'c' });
|
||||
expect(cba).not.toHaveBeenCalled();
|
||||
expect(cbb).toHaveBeenCalledTimes(1);
|
||||
expect(cbc).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should cancel all event listeners for multiple types', () => {
|
||||
Off('a', 'b')
|
||||
expect(eventListeners.get('a')).toBeUndefined()
|
||||
expect(eventListeners.get('b')).toBeUndefined()
|
||||
expect(eventListeners.get('c')).not.toBeUndefined()
|
||||
})
|
||||
})
|
||||
it("should cancel all event listeners for multiple types", () => {
|
||||
Off('a', 'c')
|
||||
dispatchWailsEvent({ name: 'a' });
|
||||
dispatchWailsEvent({ name: 'b' });
|
||||
dispatchWailsEvent({ name: 'c' });
|
||||
expect(cba).not.toHaveBeenCalled();
|
||||
expect(cbb).toHaveBeenCalledTimes(1);
|
||||
expect(cbc).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('OffAll', () => {
|
||||
it('should cancel all event listeners', () => {
|
||||
On('a', () => {})
|
||||
On('a', () => {})
|
||||
On('a', () => {})
|
||||
On('b', () => {})
|
||||
On('c', () => {})
|
||||
OffAll()
|
||||
expect(eventListeners.size).toBe(0)
|
||||
})
|
||||
})
|
||||
describe("OffAll", () => {
|
||||
it("should cancel all event listeners", () => {
|
||||
const cba = vi.fn(), cbb = vi.fn(), cbc = vi.fn();
|
||||
On('a', cba);
|
||||
On('a', cba);
|
||||
On('a', cba);
|
||||
On('b', cbb);
|
||||
On('c', cbc);
|
||||
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