-
Matiss Janis Aboltins authored
*
(typescript) fixing strictNullChecks=true issues * Patch test-helpers * Strict patchMatiss Janis Aboltins authored*
(typescript) fixing strictNullChecks=true issues * Patch test-helpers * Strict patch
test-helpers.ts 4.01 KiB
export let tracer: null | ReturnType<typeof execTracer> = null;
function timeout<T extends Promise<unknown>>(promise: T, n: number) {
let resolve: (response: string) => void;
const timeoutPromise = new Promise<string>(_ => (resolve = _));
const timer = setTimeout(() => resolve(`timeout(${n})`), n);
return Promise.race([
promise.then(res => {
clearTimeout(timer);
return res;
}),
timeoutPromise,
]);
}
export function resetTracer() {
tracer = execTracer();
}
export function execTracer<T>() {
const queue: Array<{ name: string; data?: T }> = [];
let hasStarted = false;
let waitingFor: null | {
name: string;
reject: (error: Error) => void;
resolve: (data?: T) => void;
} = null;
let ended = false;
const log = false;
return {
event(name: string, data?: T) {
if (!hasStarted) {
return;
} else if (log) {
console.log(`--- event(${name}, ${JSON.stringify(data)}) ---`);
}
if (ended) {
throw new Error(
`Tracer received event but didn’t expect it: ${name} with data: ${JSON.stringify(
data,
)}`,
);
} else if (waitingFor) {
if (waitingFor.name !== name) {
waitingFor.reject(
new Error(
`Event traced “${name}” but expected “${waitingFor.name}”`,
),
);
} else {
waitingFor.resolve(data);
}
waitingFor = null;
} else {
queue.push({ name, data });
}
},
wait(name: string) {
if (waitingFor) {
throw new Error(
`Already waiting for ${waitingFor.name}, cannot wait for multiple events`,
);
}
return new Promise((resolve, reject) => {
waitingFor = { resolve, reject, name };
});
},
expectWait(name: string, data?: T) {
if (!hasStarted) {
throw new Error(`Expected “${name}” but tracer hasn’t started yet`);
} else if (log) {
console.log(`--- expectWait(${name}) ---`);
}
const promise = this.wait(name);
if (data === undefined) {
// We want to ignore the result
return expect(
timeout(
promise.then(() => true),
1000,
),
).resolves.toEqual(true);
}
if (typeof data === 'function') {
return expect(timeout(promise, 1000))
.resolves.toBeTruthy()
.then(() => promise)
.then(res => data(res));
} else {
// We use this form because it tracks the right location in the
// test when it fails. It's annoying to always write this in the
// test though, so this provides a clean API. The right line
// number in the test will show up in the stack.
return expect(timeout(promise, 1000)).resolves.toEqual(data);
}
},
expectNow(name: string, data?: T) {
if (!hasStarted) {
throw new Error(`Expected “${name}” but tracer hasn’t started yet`);
} else if (log) {
console.log(`--- expectNow(${name}) ---`);
}
const entry = queue.shift();
if (!entry) {
throw new Error(
`Expected event “${name}” but none found - has it happened yet?`,
);
} else if (entry.name === name) {
if (typeof data === 'function') {
data(entry.data);
} else {
expect(entry.data).toEqual(data);
}
} else {
throw new Error(
`Event traced “${queue[0].name}” but expected “${name}”`,
);
}
},
expect(name: string, data?: T) {
if (queue.length === 0) {
return this.expectWait(name, data);
}
return this.expectNow(name, data);
},
start() {
hasStarted = true;
},
end() {
if (hasStarted && queue.length !== 0) {
const str = queue.map(x => JSON.stringify(x));
throw new Error(
'Event tracer ended with existing events: ' + str.join('\n\n'),
);
}
ended = true;
},
};
}