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
Original file line number Diff line number Diff line change
@@ -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));
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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.