Skip to content

Commit

Permalink
unify usage of sleepAsync and add tests
Browse files Browse the repository at this point in the history
The tests mock JS setTimeout API. However promise.resolve() is not working without flushing the promise queue (which could be done just by awaiting Promise.resolve()), similar issue has been discussed in jestjs/jest#2157.
  • Loading branch information
undergroundwires committed May 4, 2021
1 parent 131a984 commit 039ad9f
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 4 deletions.
5 changes: 5 additions & 0 deletions src/infrastructure/Threading/AsyncSleep.ts
@@ -0,0 +1,5 @@
export type SchedulerType = (callback: (...args: any[]) => void, ms: number) => void;

export function sleepAsync(time: number, scheduler: SchedulerType = setTimeout) {
return new Promise((resolve) => scheduler(() => resolve(undefined), time));
}
Expand Up @@ -22,7 +22,8 @@ import LiquorTree from 'liquor-tree';
import Node from './Node/Node.vue';
import { INode } from './Node/INode';
import { convertExistingToNode, toNewLiquorTreeNode } from './LiquorTree/NodeWrapper/NodeTranslator';
import { INodeSelectedEvent } from './/INodeSelectedEvent';
import { INodeSelectedEvent } from './INodeSelectedEvent';
import { sleepAsync } from '@/infrastructure/Threading/AsyncSleep';
import { getNewState } from './LiquorTree/NodeWrapper/NodeStateUpdater';
import { LiquorTreeOptions } from './LiquorTree/LiquorTreeOptions';
import { FilterPredicate, NodePredicateFilter } from './LiquorTree/NodeWrapper/NodePredicateFilter';
Expand Down Expand Up @@ -121,7 +122,6 @@ function recurseDown(
async function tryUntilDefinedAsync<T>(
accessor: () => T | undefined,
delayInMs: number, maxTries: number): Promise<T | undefined> {
const sleepAsync = () => new Promise(((resolve) => setTimeout(resolve, delayInMs)));
let triesLeft = maxTries;
let value: T;
while (triesLeft !== 0) {
Expand All @@ -130,7 +130,7 @@ async function tryUntilDefinedAsync<T>(
return value;
}
triesLeft--;
await sleepAsync();
await sleepAsync(delayInMs);
}
return value;
}
Expand Down
@@ -1,6 +1,7 @@
import 'mocha';
import { expect } from 'chai';
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
import { sleepAsync } from '@/infrastructure/Threading/AsyncSleep';

describe('AsyncLazy', () => {
it('returns value from lambda', async () => {
Expand Down Expand Up @@ -33,7 +34,6 @@ describe('AsyncLazy', () => {
});
it('when running long-running task in parallel', async () => {
// act
const sleepAsync = (time: number) => new Promise(((resolve) => setTimeout(resolve, time)));
const sut = new AsyncLazy(async () => {
await sleepAsync(100);
totalExecuted++;
Expand Down
79 changes: 79 additions & 0 deletions tests/unit/infrastructure/Threading/AsyncSleep.spec.ts
@@ -0,0 +1,79 @@
import 'mocha';
import { expect } from 'chai';
import { sleepAsync, SchedulerType } from '@/infrastructure/Threading/AsyncSleep';

describe('AsyncSleep', () => {
it('fulfills after delay', async () => {
// arrange
const delayInMs = 10;
const scheduler = new SchedulerMock();
// act
const sleep = sleepAsync(delayInMs, scheduler.mock);
const promiseState = watchPromiseState(sleep);
scheduler.tickNext(delayInMs);
await flushPromiseResolutionQueue();
// assert
const actual = promiseState.isFulfilled();
expect(actual).to.equal(true);
});
it('pending before delay', async () => {
// arrange
const delayInMs = 10;
const scheduler = new SchedulerMock();
// act
const sleep = sleepAsync(delayInMs, scheduler.mock);
const promiseState = watchPromiseState(sleep);
scheduler.tickNext(delayInMs / 5);
await flushPromiseResolutionQueue();
// assert
const actual = promiseState.isPending();
expect(actual).to.equal(true);
});
});

function flushPromiseResolutionQueue() {
return Promise.resolve();
}

class SchedulerMock {
public readonly mock: SchedulerType;
private currentTime = 0;
private scheduledActions = new Array<{time: number, action: (...args: any[]) => void}>();
constructor() {
this.mock = (callback: (...args: any[]) => void, ms: number) => {
this.scheduledActions.push({ time: this.currentTime + ms, action: callback });
};
}
public tickNext(ms: number) {
const newTime = this.currentTime + ms;
let newActions = this.scheduledActions;
for (const action of this.scheduledActions) {
if (newTime >= action.time) {
newActions = newActions.filter((a) => a !== action);
action.action();
}
}
this.scheduledActions = newActions;
}
}

function watchPromiseState<T>(promise: Promise<T>) {
let isPending = true;
let isRejected = false;
let isFulfilled = false;
promise.then(
() => {
isFulfilled = true;
isPending = false;
},
() => {
isRejected = true;
isPending = false;
},
);
return {
isFulfilled: () => isFulfilled,
isPending: () => isPending,
isRejected: () => isRejected,
};
}

0 comments on commit 039ad9f

Please sign in to comment.