Skip to content

Commit

Permalink
test: async
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelKreil committed Feb 22, 2024
1 parent 08fa7f2 commit dbc7a7d
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 15 deletions.
22 changes: 22 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@
"@versatiles/release-tool": "^1.2.2",
"eslint": "^8.56.0",
"jest": "^29.7.0",
"jest-extended": "^4.0.2",
"npm-check-updates": "^16.14.15",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2"
}
}
}
4 changes: 2 additions & 2 deletions src/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ progress.setHeader('Building Release');
cleanupFolder(dstFolder);

// Run the main build tasks sequentially: fetch assets, compress files, and generate frontends.
await Pf.runSequential(
await Pf.run(Pf.sequential(
getAssets(fileSystem),
compressFiles(),
generateFrontends(fileSystem, projectFolder, dstFolder),
);
));

// Save release notes in the destination folder.
notes.save(resolve(dstFolder, 'notes.md'));
Expand Down
54 changes: 52 additions & 2 deletions src/lib/__mocks__/progress.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,53 @@
import progress from '../progress';
/* eslint-disable @typescript-eslint/naming-convention */
import { jest } from '@jest/globals';
import type { ProgressLabel as ProgressLabelType, Progress as ProgressType } from '../progress';

progress.disable();
const originalModule = await import('../progress');
originalModule.default.disable();

function mockProgressLabel(progressLabel: ProgressLabelType): ProgressLabelType {
jest.spyOn(progressLabel, 'updateLabel');
jest.spyOn(progressLabel, 'start');
jest.spyOn(progressLabel, 'end');
jest.spyOn(progressLabel, 'getOutputAnsi');
jest.spyOn(progressLabel, 'getOutputText');
return progressLabel;
}

jest.unstable_mockModule('./progress', () => {
const ProgressLabel = jest.fn((progress: ProgressType, label: string, indent: number) => {
return mockProgressLabel(new originalModule.ProgressLabel(progress, label, indent));
});

const Progress = jest.fn(() => {
const progress = new originalModule.Progress();
jest.spyOn(progress, 'disableAnsi');
jest.spyOn(progress, 'disable');
jest.spyOn(progress, 'setHeader');
jest.spyOn(progress, 'finish');
jest.spyOn(progress, 'redraw');
jest.spyOn(progress, 'write');

// @ts-expect-error too lazy
// eslint-disable-next-line @typescript-eslint/unbound-method
progress._add = progress.add;
jest.spyOn(progress, 'add').mockImplementation((name: string, indent = 0): ProgressLabelType => {
// @ts-expect-error too lazy
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call
return mockProgressLabel(progress._add(name, indent));
});
return progress;
});

const progress = new Progress();
progress.disable();

return { ProgressLabel, Progress, default: progress };
});


const { default: progress, ProgressLabel, Progress } = await import('../progress');


export default progress;
export { ProgressLabel, Progress };
133 changes: 133 additions & 0 deletions src/lib/async.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/* eslint-disable @typescript-eslint/unbound-method */
import { jest } from '@jest/globals';
import { toHaveBeenCalledBefore } from 'jest-extended';
import type { ProgressLabel } from './progress';
expect.extend({ toHaveBeenCalledBefore });

const progress = (await import('./__mocks__/progress')).default;
// eslint-disable-next-line @typescript-eslint/naming-convention
const Pf = (await import('./async')).default;

function getAsyncMock(): jest.Mock<() => Promise<void>> {

return jest.fn(async (): Promise<void> => {
await new Promise(res => setTimeout(res, Math.random() * 50));
});
}

describe('PromiseFunction', () => {
describe('single', () => {
it('creates and runs a single async operation', async () => {
const mockInit = getAsyncMock();
const mockRun = getAsyncMock();

await Pf.run(Pf.single(mockInit, mockRun));

// Verify, that every function was executed once
expect(mockInit).toHaveBeenCalledTimes(1);
expect(mockRun).toHaveBeenCalledTimes(1);

// Verify the order of execution
expect(mockInit).toHaveBeenCalledBefore(mockRun);
});
});

describe('parallel', () => {
it('runs multiple PromiseFunctions in parallel', async () => {
const mockInit1 = getAsyncMock();
const mockRun1 = getAsyncMock();
const mockInit2 = getAsyncMock();
const mockRun2 = getAsyncMock();

await Pf.run(Pf.parallel(
Pf.single(mockInit1, mockRun1),
Pf.single(mockInit2, mockRun2),
));

// Verify, that every function was executed once
expect(mockInit1).toHaveBeenCalledTimes(1);
expect(mockRun1).toHaveBeenCalledTimes(1);
expect(mockInit2).toHaveBeenCalledTimes(1);
expect(mockRun2).toHaveBeenCalledTimes(1);

// Verify the order of execution
expect(mockInit1).toHaveBeenCalledBefore(mockInit2);
expect(mockInit1).toHaveBeenCalledBefore(mockRun1);
expect(mockInit2).toHaveBeenCalledBefore(mockRun2);
});
});

describe('sequential', () => {
it('runs multiple PromiseFunctions sequentially', async () => {
const mockInit1 = getAsyncMock();
const mockRun1 = getAsyncMock();
const mockInit2 = getAsyncMock();
const mockRun2 = getAsyncMock();

await Pf.run(Pf.sequential(
Pf.single(mockInit1, mockRun1),
Pf.single(mockInit2, mockRun2),
));

// Verify, that every function was executed once
expect(mockInit1).toHaveBeenCalledTimes(1);
expect(mockRun1).toHaveBeenCalledTimes(1);
expect(mockInit2).toHaveBeenCalledTimes(1);
expect(mockRun2).toHaveBeenCalledTimes(1);

// Verify the order of execution
expect(mockInit1).toHaveBeenCalledBefore(mockInit2);
expect(mockInit2).toHaveBeenCalledBefore(mockRun1);
expect(mockRun1).toHaveBeenCalledBefore(mockRun2);
});
});

describe('wrapProgress', () => {
it('wraps a PromiseFunction with progress tracking', async () => {
jest.clearAllMocks();

const mockInit = getAsyncMock();
const mockRun = getAsyncMock();

await Pf.run(Pf.wrapProgress('Test Progress', Pf.single(mockInit, mockRun)));

const progressLabel = jest.mocked(progress.add).mock.results[0].value as ProgressLabel;

expect(progress.add).toHaveBeenCalledTimes(1);
expect(mockInit).toHaveBeenCalledTimes(1);
expect(mockRun).toHaveBeenCalledTimes(1);
expect(progressLabel.start).toHaveBeenCalledTimes(1);
expect(progressLabel.end).toHaveBeenCalledTimes(1);

expect(progress.add).toHaveBeenCalledWith('Test Progress');

expect(progress.add).toHaveBeenCalledBefore(mockInit);
expect(mockInit).toHaveBeenCalledBefore(jest.mocked(progressLabel.start));
expect(progressLabel.start).toHaveBeenCalledBefore(mockRun);
expect(mockRun).toHaveBeenCalledBefore(jest.mocked(progressLabel.end));
});
});

describe('wrapAsync', () => {
it('wraps a async function with progress tracking', async () => {
jest.clearAllMocks();

const mockAsync = getAsyncMock();

await Pf.run(Pf.wrapAsync('Test Progress', 3, mockAsync));

const progressLabel = jest.mocked(progress.add).mock.results[0].value as ProgressLabel;

expect(progress.add).toHaveBeenCalledTimes(1);
expect(mockAsync).toHaveBeenCalledTimes(1);
expect(progressLabel.start).toHaveBeenCalledTimes(1);
expect(progressLabel.end).toHaveBeenCalledTimes(1);

expect(progress.add).toHaveBeenCalledWith('Test Progress', 3);

expect(progress.add).toHaveBeenCalledBefore(jest.mocked(progressLabel.start));
expect(progressLabel.start).toHaveBeenCalledBefore(mockAsync);
expect(mockAsync).toHaveBeenCalledBefore(jest.mocked(progressLabel.end));
});
});
});
10 changes: 0 additions & 10 deletions src/lib/async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,6 @@ export default class PromiseFunction {
);
}

/**
* Executes multiple PromiseFunctions sequentially.
*
* @param pfs - An array of PromiseFunction instances to execute.
*/
public static async runSequential(...pfs: PromiseFunction[]): Promise<void> {
for (const pf of pfs) await pf.init();
for (const pf of pfs) await pf.run();
}

/**
* Initializes and then runs a given PromiseFunction.
*
Expand Down

0 comments on commit dbc7a7d

Please sign in to comment.