From e5fc2b48ebf1a20f0131aaeede012202dee542a6 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Tue, 13 Apr 2021 23:24:02 -0700 Subject: [PATCH 01/37] fix(test): Rewrite run method to better handle function timeouts.. Update test spec file, increase file coverage to 100%. --- packages/core/src/test.ts | 30 ++++++++++++------------------ packages/core/test/test.spec.ts | 28 +++++++++++++++------------- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/packages/core/src/test.ts b/packages/core/src/test.ts index a6dff49..48714f4 100644 --- a/packages/core/src/test.ts +++ b/packages/core/src/test.ts @@ -3,7 +3,7 @@ import Runnable, { isRunnable, RunnableOptions, RunnableTypes } from './runnable import { RunOptions } from './runner'; import Suite from './suite'; -export type TestFn = () => (void | Promise); +export type TestFn = () => (void | Promise); /** * @description Checks if the passed `Runnable` value is a `Test` instance. @@ -35,33 +35,27 @@ export default class Test extends Runnable { this.doStart(); if (options && options.timeout) { - let timeoutID: NodeJS.Timeout; - const test: Promise = new Promise((resolve, reject) => { - timeoutID = setTimeout(() => { - reject(`${this.getFullDescription()} has timed out: ${options.timeout}ms`); - }, options.timeout as number); - - try { - this.fn(); - } catch (error) { - clearTimeout(timeoutID); - reject(error); - } - - clearTimeout(timeoutID); - resolve(); - }); + let timer; + const wait = (ms: number) => new Promise(resolve => { + timer = setTimeout(resolve, ms); + }); + const test = Promise.race([ + wait(options.timeout).then(() => { throw new Error(`${this.getFullDescription()} has timed out: ${options.timeout}ms`); }), + this.fn() + ]); try { await test; } catch (error) { return this.doFail(error); + } finally { + clearTimeout(timer); } return this.doPass(); } else { try { - this.fn(); + await this.fn(); } catch (error) { return this.doFail(error); } diff --git a/packages/core/test/test.spec.ts b/packages/core/test/test.spec.ts index a65481b..022d97b 100644 --- a/packages/core/test/test.spec.ts +++ b/packages/core/test/test.spec.ts @@ -6,12 +6,12 @@ import Test, { isTest } from '../src/test'; describe('Test', () => { const defaultOpts = { skip: false, todo: false }; - it('should return isDone', () => { + it('should return isDone', async () => { const fn = jest.fn(); const test = new Test('test', fn, defaultOpts, null); expect(test.isDone()).toBeFalsy(); - test.run(); + await test.run(); expect(test.isDone()).toBeTruthy(); }); @@ -78,31 +78,33 @@ describe('Test', () => { expect((await test.run()).status).toBe('skipped'); }); - it.skip('should timeout', async () => { + it('should timeout', async () => { jest.useRealTimers(); - const fn = () => { - return new Promise((resolve) => { + // Test fn should not resolve before the timeout promise + const fn = () => new Promise((resolve) => { setTimeout(() => { - resolve('Shouldn\'t resolve'); - }, 2000); + resolve('Shouldn\'t resolve first'); + }, 3000); }); - }; const test = new Test('test', fn, defaultOpts, null); - const start = jest.fn(); - test.on('start', start); - const fail = jest.fn(); - test.on('fail', fail); const end = jest.fn(); test.on('end', end); + const fail = jest.fn(); + test.on('fail', fail); + const pass = jest.fn(); + test.on('pass', pass); + const start = jest.fn(); + test.on('start', start); const result = await test.run({ timeout: 1000 }); expect(result.status).toBe('failed'); - expect(result.messages[0]).toBe(`${test.description} has timed out: 1000ms`); + expect(result.messages[0]).toBe(`Error: ${test.description} has timed out: 1000ms`); expect(start).toHaveBeenCalledTimes(1); expect(fail).toHaveBeenCalledTimes(1); expect(end).toHaveBeenCalledTimes(1); + expect(pass).toHaveBeenCalledTimes(0); }); }); From 3af00c53384d8dab9d6ce7fecec3b378efed391d Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Wed, 14 Apr 2021 00:04:18 -0700 Subject: [PATCH 02/37] test(snapshot): update snapshots. --- packages/core/test/__snapshots__/suite.spec.ts.snap | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/test/__snapshots__/suite.spec.ts.snap b/packages/core/test/__snapshots__/suite.spec.ts.snap index 29f45e1..d05b9dc 100644 --- a/packages/core/test/__snapshots__/suite.spec.ts.snap +++ b/packages/core/test/__snapshots__/suite.spec.ts.snap @@ -9,8 +9,6 @@ Array [ "beforeEach2", "beforeEach2", "afterEach1", - "afterEach1", - "afterEach2", "afterEach2", "afterAll1", "afterAll2", From 9e5440c1a11c5d13d026b8d0d0357de6dc706cc8 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Wed, 14 Apr 2021 00:15:46 -0700 Subject: [PATCH 03/37] style(test): lint fix. --- packages/core/src/suite.ts | 7 ++-- packages/core/src/test.ts | 18 ++++----- packages/core/test/runner.spec.ts | 20 +++++----- packages/core/test/test.spec.ts | 64 +++++++++++++++---------------- 4 files changed, 54 insertions(+), 55 deletions(-) diff --git a/packages/core/src/suite.ts b/packages/core/src/suite.ts index 964d316..edea42a 100644 --- a/packages/core/src/suite.ts +++ b/packages/core/src/suite.ts @@ -25,8 +25,9 @@ export interface SuiteStats { } export class BailError extends Error { + /* istanbul ignore next */ constructor(message: string) { - super(message) /* istanbul ignore next */ + super(message) this.name = 'BailError' } } @@ -109,9 +110,7 @@ export default class Suite extends Runnable { this.result.addMessages(...result.messages.map((m) => `${child.description}: ${m}`)) await this.invokeHook('afterEach') - if (result.status === Status.Failed) { - ++this.failed - } + if (result.status === Status.Failed) ++this.failed return result })()) diff --git a/packages/core/src/test.ts b/packages/core/src/test.ts index 8f02516..bbcb72d 100644 --- a/packages/core/src/test.ts +++ b/packages/core/src/test.ts @@ -3,7 +3,7 @@ import Runnable, { isRunnable, RunnableOptions, RunnableTypes } from './runnable import { RunOptions } from './runner' import Suite from './suite' -export type TestFn = () => (void | Promise); +export type TestFn = () => (void | Promise) /** * @description Checks if the passed `Runnable` value is a `Test` instance. @@ -35,27 +35,27 @@ export default class Test extends Runnable { this.doStart() if (options && options.timeout) { - let timer; + let timer const wait = (ms: number) => new Promise(resolve => { - timer = setTimeout(resolve, ms); - }); + timer = setTimeout(resolve, ms) + }) const test = Promise.race([ - wait(options.timeout).then(() => { throw new Error(`${this.getFullDescription()} has timed out: ${options.timeout}ms`); }), + wait(options.timeout).then(() => { throw new Error(`${this.getFullDescription()} has timed out: ${options.timeout}ms`) }), this.fn() - ]); + ]) try { await test } catch (error) { - return this.doFail(error); + return this.doFail(error) } finally { - clearTimeout(timer); + clearTimeout(timer) } return this.doPass() } else { try { - await this.fn(); + await this.fn() } catch (error) { return this.doFail(error) } diff --git a/packages/core/test/runner.spec.ts b/packages/core/test/runner.spec.ts index b5d73f7..76cd3dd 100644 --- a/packages/core/test/runner.spec.ts +++ b/packages/core/test/runner.spec.ts @@ -1,6 +1,6 @@ -import { Status } from '../src/result' -import Runnable from '../src/runnable' -import Runner, { normalizeRunOptions, RunOptions } from '../src/runner' +//import { Status } from '../src/result' +//import Runnable from '../src/runnable' +import Runner, { normalizeRunOptions, /*RunOptions*/ } from '../src/runner' import Suite, { rootSymbol } from '../src/suite' import Test from '../src/test' @@ -24,7 +24,7 @@ describe('runner', () => { }) // tslint:disable-next-line:max-classes-per-file - class TimeoutTestRunnable extends Runnable { + /*class TimeoutTestRunnable extends Runnable { private cb: (options?: RunOptions) => void constructor( @@ -52,10 +52,10 @@ describe('runner', () => { return this.doPass() } - } + }*/ - it('should pass run options to children', async () => { - const opts = { bail: true, timeout: 1234, sequential: true } + /*it.skip('should pass run options to children', async () => { + const opts = { bail: true, timeout: 1234, sequential: true }; const rootSuite = new Suite('root', {}, null) const childSuite = new Suite('child', {}, null) @@ -74,9 +74,9 @@ describe('runner', () => { await runner.run() - expect(cb1).toHaveBeenCalledWith(undefined) - expect(cb2).toHaveBeenCalledWith(undefined) - }) + expect(cb1).toHaveBeenCalledWith(opts); + expect(cb2).toHaveBeenCalledWith(opts); + }); */ it('should run a suite and children', async () => { const rootSuite = new Suite('root', {}, null) diff --git a/packages/core/test/test.spec.ts b/packages/core/test/test.spec.ts index 6f98e84..5c964cd 100644 --- a/packages/core/test/test.spec.ts +++ b/packages/core/test/test.spec.ts @@ -7,13 +7,13 @@ describe('Test', () => { const defaultOpts = { skip: false, todo: false } it('should return isDone', async () => { - const fn = jest.fn(); - const test = new Test('test', fn, defaultOpts, null); + const fn = jest.fn() + const test = new Test('test', fn, defaultOpts, null) - expect(test.isDone()).toBeFalsy(); - await test.run(); - expect(test.isDone()).toBeTruthy(); - }); + expect(test.isDone()).toBeFalsy() + await test.run() + expect(test.isDone()).toBeTruthy() + }) it('should check if is test', () => { const fn = jest.fn() @@ -73,32 +73,32 @@ describe('Test', () => { }) it('should timeout', async () => { - jest.useRealTimers(); + jest.useRealTimers() // Test fn should not resolve before the timeout promise const fn = () => new Promise((resolve) => { setTimeout(() => { - resolve('Shouldn\'t resolve first'); - }, 3000); - }); - - const test = new Test('test', fn, defaultOpts, null); - - const end = jest.fn(); - test.on('end', end); - const fail = jest.fn(); - test.on('fail', fail); - const pass = jest.fn(); - test.on('pass', pass); - const start = jest.fn(); - test.on('start', start); - - const result = await test.run({ timeout: 1000 }); - - expect(result.status).toBe('failed'); - expect(result.messages[0]).toBe(`Error: ${test.description} has timed out: 1000ms`); - expect(start).toHaveBeenCalledTimes(1); - expect(fail).toHaveBeenCalledTimes(1); - expect(end).toHaveBeenCalledTimes(1); - expect(pass).toHaveBeenCalledTimes(0); - }); -}); + resolve('Shouldn\'t resolve first') + }, 3000) + }) + + const test = new Test('test', fn, defaultOpts, null) + + const end = jest.fn() + test.on('end', end) + const fail = jest.fn() + test.on('fail', fail) + const pass = jest.fn() + test.on('pass', pass) + const start = jest.fn() + test.on('start', start) + + const result = await test.run({ timeout: 1000 }) + + expect(result.status).toBe('failed') + expect(result.messages[0]).toBe(`Error: ${test.description} has timed out: 1000ms`) + expect(start).toHaveBeenCalledTimes(1) + expect(fail).toHaveBeenCalledTimes(1) + expect(end).toHaveBeenCalledTimes(1) + expect(pass).toHaveBeenCalledTimes(0) + }) +}) From 7471f92862acc91f1157c50a09a7b115c4fc582d Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Wed, 14 Apr 2021 00:24:22 -0700 Subject: [PATCH 04/37] style(interface): remove non-null assertion. --- packages/core/test/interface.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/test/interface.spec.ts b/packages/core/test/interface.spec.ts index 910f364..8dd581e 100644 --- a/packages/core/test/interface.spec.ts +++ b/packages/core/test/interface.spec.ts @@ -59,7 +59,7 @@ describe('Interface', () => { }) expect(suite.children[0]).toMatchSnapshot() - expect(suite.children[0].parent!.parent).toBe(root) + expect(suite.children[0].parent.parent).toBe(root) }) it('should create skipped tests inside of suites', () => { From e42b184a4383a07a1ee6b0bdc41c14dbf3c956d7 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Wed, 14 Apr 2021 00:31:34 -0700 Subject: [PATCH 05/37] test(suite): update snapshots. --- packages/core/test/__snapshots__/suite.spec.ts.snap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/test/__snapshots__/suite.spec.ts.snap b/packages/core/test/__snapshots__/suite.spec.ts.snap index d05b9dc..29f45e1 100644 --- a/packages/core/test/__snapshots__/suite.spec.ts.snap +++ b/packages/core/test/__snapshots__/suite.spec.ts.snap @@ -9,6 +9,8 @@ Array [ "beforeEach2", "beforeEach2", "afterEach1", + "afterEach1", + "afterEach2", "afterEach2", "afterAll1", "afterAll2", From 650bfe0601162a3f4d00c397e2567ac5696e8e63 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Wed, 14 Apr 2021 00:34:26 -0700 Subject: [PATCH 06/37] test(interface): fix possibly null prop. --- packages/core/test/interface.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/test/interface.spec.ts b/packages/core/test/interface.spec.ts index 8dd581e..97dd12d 100644 --- a/packages/core/test/interface.spec.ts +++ b/packages/core/test/interface.spec.ts @@ -59,7 +59,7 @@ describe('Interface', () => { }) expect(suite.children[0]).toMatchSnapshot() - expect(suite.children[0].parent.parent).toBe(root) + expect(suite.parent).toBe(root) }) it('should create skipped tests inside of suites', () => { From 94514f4d60f8b0ae40b50d0e36fe920258f4a321 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Wed, 14 Apr 2021 00:38:59 -0700 Subject: [PATCH 07/37] refactor(suite): clean up suite class. Remove skip from bail test. --- packages/core/src/suite.ts | 5 +++-- packages/core/test/suite.spec.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/core/src/suite.ts b/packages/core/src/suite.ts index edea42a..b014f9f 100644 --- a/packages/core/src/suite.ts +++ b/packages/core/src/suite.ts @@ -121,7 +121,7 @@ export default class Suite extends Runnable { try { const result = await promise - if (options.bail && result !== undefined) { + if (options.bail && result) { throw new BailError(result.messages[0]) } } catch (error) { @@ -134,11 +134,12 @@ export default class Suite extends Runnable { await Promise.all(promises.map(async (promise) => { const result = await promise - if (options && options.bail && result !== undefined) { + if (options && options.bail && result) { throw new BailError(result.messages[0]) } })) } catch (error) { + console.log('error: ', error); await this.invokeHook('afterAll') return this.doFail(error) } diff --git a/packages/core/test/suite.spec.ts b/packages/core/test/suite.spec.ts index 226c644..ad33301 100644 --- a/packages/core/test/suite.spec.ts +++ b/packages/core/test/suite.spec.ts @@ -84,7 +84,7 @@ describe('Suite', () => { expect(end).toHaveBeenCalledTimes(1) }) - it.skip('should bail out on first failure', async () => { + it('should bail out on first failure', async () => { jest.useRealTimers() const fn = () => { From bebf8f15eb10a14bf5abc34594595fe4db4ed031 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Wed, 14 Apr 2021 00:46:10 -0700 Subject: [PATCH 08/37] fix(test): clear timeout in wait promise chain. --- packages/core/src/test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/core/src/test.ts b/packages/core/src/test.ts index bbcb72d..c4b1dbd 100644 --- a/packages/core/src/test.ts +++ b/packages/core/src/test.ts @@ -35,12 +35,15 @@ export default class Test extends Runnable { this.doStart() if (options && options.timeout) { - let timer + let timer: NodeJS.Timeout; const wait = (ms: number) => new Promise(resolve => { timer = setTimeout(resolve, ms) }) const test = Promise.race([ - wait(options.timeout).then(() => { throw new Error(`${this.getFullDescription()} has timed out: ${options.timeout}ms`) }), + wait(options.timeout).then(() => { + clearTimeout(timer); + throw new Error(`${this.getFullDescription()} has timed out: ${options.timeout}ms`) + }), this.fn() ]) @@ -48,8 +51,6 @@ export default class Test extends Runnable { await test } catch (error) { return this.doFail(error) - } finally { - clearTimeout(timer) } return this.doPass() From d9f2fb4f0a6f6d52339621fa8e760380669b95bf Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Wed, 14 Apr 2021 00:47:01 -0700 Subject: [PATCH 09/37] style(test): lint fix. --- packages/core/src/suite.ts | 1 - packages/core/src/test.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/core/src/suite.ts b/packages/core/src/suite.ts index b014f9f..7f68f48 100644 --- a/packages/core/src/suite.ts +++ b/packages/core/src/suite.ts @@ -139,7 +139,6 @@ export default class Suite extends Runnable { } })) } catch (error) { - console.log('error: ', error); await this.invokeHook('afterAll') return this.doFail(error) } diff --git a/packages/core/src/test.ts b/packages/core/src/test.ts index c4b1dbd..760a05a 100644 --- a/packages/core/src/test.ts +++ b/packages/core/src/test.ts @@ -35,13 +35,13 @@ export default class Test extends Runnable { this.doStart() if (options && options.timeout) { - let timer: NodeJS.Timeout; + let timer: NodeJS.Timeout const wait = (ms: number) => new Promise(resolve => { timer = setTimeout(resolve, ms) }) const test = Promise.race([ wait(options.timeout).then(() => { - clearTimeout(timer); + clearTimeout(timer) throw new Error(`${this.getFullDescription()} has timed out: ${options.timeout}ms`) }), this.fn() From e6a4ebf1c926899e06d6b012129c74e6b1ac59fc Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Wed, 14 Apr 2021 23:39:26 -0700 Subject: [PATCH 10/37] test(suite): split bail tests into two, one for sequential, one for non-sequential. --- packages/core/src/suite.ts | 2 +- packages/core/test/suite.spec.ts | 100 ++++++++++++++++--------------- 2 files changed, 53 insertions(+), 49 deletions(-) diff --git a/packages/core/src/suite.ts b/packages/core/src/suite.ts index 7f68f48..f0912f4 100644 --- a/packages/core/src/suite.ts +++ b/packages/core/src/suite.ts @@ -120,7 +120,7 @@ export default class Suite extends Runnable { for (const promise of promises) { try { const result = await promise - + if (options.bail && result) { throw new BailError(result.messages[0]) } diff --git a/packages/core/test/suite.spec.ts b/packages/core/test/suite.spec.ts index ad33301..eda994f 100644 --- a/packages/core/test/suite.spec.ts +++ b/packages/core/test/suite.spec.ts @@ -84,7 +84,7 @@ describe('Suite', () => { expect(end).toHaveBeenCalledTimes(1) }) - it('should bail out on first failure', async () => { + describe('should bail out on first failure', () => { jest.useRealTimers() const fn = () => { @@ -99,53 +99,57 @@ describe('Suite', () => { throw Error() } - // Non-sequential - const parent = new Suite('parent', defaultOpts, null) - const firstFail = new Test('firstFail', errorFn, defaultOpts, null) - const firstPass = new Test('firstPass', fn, defaultOpts, null) - const secondPass = new Test('secondPass', fn, defaultOpts, null) - - parent.addChildren(firstFail, firstPass, secondPass) - - const parentFail = jest.fn() - parent.on('fail', parentFail) - const parentPass = jest.fn() - parent.on('pass', parentPass) - const testFail = jest.fn() - firstFail.on('fail', testFail) - const testPass = jest.fn() - firstPass.on('pass', testPass) - - expect((await parent.run({ bail: true, sequential: false })).status).toBe(Status.Failed) - expect(parent.getStats().done).toBe(1) - expect(testPass).toHaveBeenCalledTimes(0) - expect(testFail).toHaveBeenCalledTimes(1) - expect(parentPass).toHaveBeenCalledTimes(0) - expect(parentFail).toHaveBeenCalledTimes(1) - - // Sequential - const sequentialParent = new Suite('sequentialParent', defaultOpts, null) - const secondFail = new Test('secondFail', errorFn, defaultOpts, null) - const thirdPass = new Test('thirdPass', fn, defaultOpts, null) - const fourthPass = new Test('fourthPass', fn, defaultOpts, null) - - const sequentialParentPass = jest.fn() - sequentialParent.on('pass', sequentialParentPass) - const sequentialParentFail = jest.fn() - sequentialParent.on('fail', sequentialParentFail) - const sequentialTestPass = jest.fn() - thirdPass.on('pass', sequentialTestPass) - const sequentialTestFail = jest.fn() - secondFail.on('fail', sequentialTestFail) - - sequentialParent.addChildren(thirdPass, secondFail, fourthPass) - - expect((await sequentialParent.run({ bail: true, sequential: true })).status).toBe(Status.Failed) - expect(sequentialParent.getStats().done).toBe(2) - expect(sequentialTestPass).toHaveBeenCalledTimes(1) - expect(sequentialTestFail).toHaveBeenCalledTimes(1) - expect(sequentialParentPass).toHaveBeenCalledTimes(0) - expect(sequentialParentFail).toHaveBeenCalledTimes(1) + it ('non-sequential', async () => { + // Non-sequential + const parent = new Suite('parent', defaultOpts, null) + const fail = new Test('firstFail', errorFn, defaultOpts, null) + const pass = new Test('firstPass', fn, defaultOpts, null) + const secondPass = new Test('secondPass', fn, defaultOpts, null) + + parent.addChildren(fail, pass, secondPass) + + const parentFail = jest.fn() + parent.on('fail', parentFail) + const parentPass = jest.fn() + parent.on('pass', parentPass) + const testFail = jest.fn() + fail.on('fail', testFail) + const testPass = jest.fn() + pass.on('pass', testPass) + + expect((await parent.run({ bail: true, sequential: false })).status).toBe(Status.Failed) + expect(parent.getStats().done).toBe(1) + expect(testPass).toHaveBeenCalledTimes(0) + expect(testFail).toHaveBeenCalledTimes(1) + expect(parentPass).toHaveBeenCalledTimes(0) + expect(parentFail).toHaveBeenCalledTimes(1) + }) + + it('sequential', async () => { + // Sequential + const sequentialParent = new Suite('sequentialParent', defaultOpts, null) + const fail = new Test('fail', errorFn, defaultOpts, null) + const pass = new Test('pass', fn, defaultOpts, null) + const secondPass = new Test('secondPass', fn, defaultOpts, null) + + const sequentialParentPass = jest.fn() + sequentialParent.on('pass', sequentialParentPass) + const sequentialParentFail = jest.fn() + sequentialParent.on('fail', sequentialParentFail) + const sequentialTestPass = jest.fn() + pass.on('pass', sequentialTestPass) + const sequentialTestFail = jest.fn() + fail.on('fail', sequentialTestFail) + + sequentialParent.addChildren(pass, fail, secondPass) + + expect((await sequentialParent.run({ bail: true, sequential: true })).status).toBe(Status.Failed) + expect(sequentialParent.getStats().done).toBe(2) + expect(sequentialTestPass).toHaveBeenCalledTimes(1) + expect(sequentialTestFail).toHaveBeenCalledTimes(1) + expect(sequentialParentPass).toHaveBeenCalledTimes(0) + expect(sequentialParentFail).toHaveBeenCalledTimes(1) + }) }) it('should skip', async () => { From efa4c1837122f5f457ea51a2aca61384982721b6 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Tue, 4 May 2021 01:06:51 -0700 Subject: [PATCH 11/37] chore(suite): fix spacing. --- packages/core/src/suite.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/core/src/suite.ts b/packages/core/src/suite.ts index f0912f4..6937819 100644 --- a/packages/core/src/suite.ts +++ b/packages/core/src/suite.ts @@ -105,14 +105,14 @@ export default class Suite extends Runnable { const promises: Array> = [] for (const child of this.children) { promises.push((async () => { - await this.invokeHook('beforeEach') - const result = await child.run() - this.result.addMessages(...result.messages.map((m) => `${child.description}: ${m}`)) - await this.invokeHook('afterEach') + await this.invokeHook('beforeEach') + const result = await child.run() + this.result.addMessages(...result.messages.map((m) => `${child.description}: ${m}`)) + await this.invokeHook('afterEach') - if (result.status === Status.Failed) ++this.failed + if (result.status === Status.Failed) ++this.failed - return result + return result })()) } From bcc4f4ada7578d09a86bc2d16f4e9c2337e68552 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Fri, 21 May 2021 23:17:50 -0700 Subject: [PATCH 12/37] test(test): clear timers at end of timeout test. --- packages/core/src/test.ts | 4 +++- packages/core/test/test.spec.ts | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core/src/test.ts b/packages/core/src/test.ts index 760a05a..bb1ac3d 100644 --- a/packages/core/src/test.ts +++ b/packages/core/src/test.ts @@ -39,12 +39,14 @@ export default class Test extends Runnable { const wait = (ms: number) => new Promise(resolve => { timer = setTimeout(resolve, ms) }) + const test = Promise.race([ wait(options.timeout).then(() => { clearTimeout(timer) throw new Error(`${this.getFullDescription()} has timed out: ${options.timeout}ms`) }), - this.fn() + + await this.fn() ]) try { diff --git a/packages/core/test/test.spec.ts b/packages/core/test/test.spec.ts index 5c964cd..d892427 100644 --- a/packages/core/test/test.spec.ts +++ b/packages/core/test/test.spec.ts @@ -100,5 +100,7 @@ describe('Test', () => { expect(fail).toHaveBeenCalledTimes(1) expect(end).toHaveBeenCalledTimes(1) expect(pass).toHaveBeenCalledTimes(0) + + jest.clearAllTimers() }) }) From 34d860d0f46b24200dcefbe8009dc3885a9fc706 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Fri, 21 May 2021 23:18:44 -0700 Subject: [PATCH 13/37] chore(test-spec): update comment for timeout test. --- packages/core/test/test.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/test/test.spec.ts b/packages/core/test/test.spec.ts index d892427..086b27e 100644 --- a/packages/core/test/test.spec.ts +++ b/packages/core/test/test.spec.ts @@ -74,7 +74,8 @@ describe('Test', () => { it('should timeout', async () => { jest.useRealTimers() - // Test fn should not resolve before the timeout promise + + // fn function should not resolve before the timeout promise const fn = () => new Promise((resolve) => { setTimeout(() => { resolve('Shouldn\'t resolve first') From bbfa7a0a158af04171bd56de93ebdf3b44785c36 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Fri, 21 May 2021 23:20:40 -0700 Subject: [PATCH 14/37] fix(test): remove await from fn when racing promise against timeout. --- packages/core/src/test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core/src/test.ts b/packages/core/src/test.ts index bb1ac3d..7edb2e9 100644 --- a/packages/core/src/test.ts +++ b/packages/core/src/test.ts @@ -45,8 +45,7 @@ export default class Test extends Runnable { clearTimeout(timer) throw new Error(`${this.getFullDescription()} has timed out: ${options.timeout}ms`) }), - - await this.fn() + this.fn(), ]) try { From 74f1c546e626e7ed6d3a1f45988a15931de14fe9 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Tue, 25 May 2021 21:16:48 -0700 Subject: [PATCH 15/37] refactor(runnable): make runnable an abstract class, remove events and event emitter. --- packages/core/src/runnable.ts | 84 +++++++++-------- packages/core/test/runnable.spec.ts | 134 ++++++++-------------------- 2 files changed, 83 insertions(+), 135 deletions(-) diff --git a/packages/core/src/runnable.ts b/packages/core/src/runnable.ts index e37dfc5..3aa759e 100644 --- a/packages/core/src/runnable.ts +++ b/packages/core/src/runnable.ts @@ -1,6 +1,7 @@ -import { EventEmitter } from 'events' import { performance } from 'perf_hooks' -import Result, { Status } from './result' +import { Test } from '.' +import { Hooks } from './hooks' +import { RunStatus, TestResultData } from './newResult' import Suite from './suite' export const runnableSymbol = Symbol('isRunnable') @@ -9,8 +10,8 @@ export const runnableSymbol = Symbol('isRunnable') * @description Checks if passed value is an instance of `Runnable`. */ export const isRunnable = (v: unknown): v is Runnable => { - if (typeof v === 'object' && v === null) { return false } - return (v as Runnable)[runnableSymbol] + if (v && (v as Runnable)[runnableSymbol]) { return true } + else { return false } } export enum RunnableTypes { @@ -24,7 +25,26 @@ export interface RunnableOptions { todo: boolean } -export default class Runnable extends EventEmitter { +export interface RunnableResultData extends TestResultData { + id: string + description: string + time: number +} + +const DEFAULT_RESULT_DATA: RunnableResultData = { + id: '', + description: '', + messages: [], + failures: [], + filePath: '', + hooks: {} as Hooks, + status: RunStatus.PENDING, + time: 0, + title: '', + type: RunnableTypes.Runnable, +} + +export default abstract class Runnable { /** * @description Normalize passed options object with `Runnable` default options. @@ -37,30 +57,34 @@ export default class Runnable extends EventEmitter { } } public description: string - public result: Result + public result: RunnableResultData public options: RunnableOptions public parent: Suite | null public type: RunnableTypes = RunnableTypes.Runnable public [runnableSymbol] = true public time = 0 - private start = 0 + public start = 0 /* istanbul ignore next */ constructor(description: string, options: Partial = {}, parent: Suite | null) { - super() this.description = description - this.result = new Result() + this.result = { ...DEFAULT_RESULT_DATA } this.options = Runnable.normalizeOptions(options) this.parent = parent } + /** + * @description Run a `Runnable` instance. + */ + public abstract run(): Promise + + /** * @description Sets result status to `Running` and emits a `start` event with the `Runnable` instance and timestamp. */ public doStart(): void { - this.result.status = Status.Running - this.emit('start', this) + this.result.status = RunStatus.RUNNING this.start = performance.now() } @@ -68,18 +92,16 @@ export default class Runnable extends EventEmitter { * @description Emits an `end` event with the completed `Runnable` instance and the time taken to complete. */ public doEnd() { - if (this.result.status !== Status.Skipped && this.result.status !== Status.Todo) { - this.time = performance.now() - this.start + if (this.result.status !== RunStatus.SKIPPED && this.result.status !== RunStatus.TODO) { + this.result.time = performance.now() - this.start } - this.emit('end', this, this.time) } /** * @description Emits a `pass` event with the passing `Runnable` instance. */ - public doPass(): Result { - this.result.status = Status.Passed - this.emit('pass', this) + public doPass() { + this.result.status = RunStatus.PASSED this.doEnd() return this.result @@ -88,12 +110,11 @@ export default class Runnable extends EventEmitter { /** * @description Emits a `fail` event with the failed `Runnable` instance and passed error. */ - public doFail(error?: Error | string): Result { + public doFail(error?: Error) { if (error) { - this.result.addMessages(String(error)) + this.result.failures.push(error) } - this.result.status = Status.Failed - this.emit('fail', this, error) + this.result.status = RunStatus.FAILED this.doEnd() return this.result @@ -102,33 +123,18 @@ export default class Runnable extends EventEmitter { /** * @description Emits `skip` event with the skipped `Runnable` instance. */ - public doSkip(todo = false): Result { - this.result.status = todo ? Status.Todo : Status.Skipped - this.emit('skip', this, todo) + public doSkip(todo = false) { + this.result.status = todo ? RunStatus.TODO : RunStatus.SKIPPED this.doEnd() return this.result } - /** - * @description Run a `Runnable` instance. - */ - // istanbul ignore next unimplemented - public async run(): Promise { - if (this.options.skip || this.options.todo) { - return this.doSkip(this.options.todo) - } - - this.doStart() - - return this.doSkip() // To be replaced with real run function - } - /** * @description Check that `Runnable` has completed. */ public isDone() { - return this.result.isDone() + return this.result.status !== RunStatus.PENDING && this.result.status !== RunStatus.RUNNING } /** diff --git a/packages/core/test/runnable.spec.ts b/packages/core/test/runnable.spec.ts index f3f1db1..42784a5 100644 --- a/packages/core/test/runnable.spec.ts +++ b/packages/core/test/runnable.spec.ts @@ -1,114 +1,56 @@ +import { RunStatus } from '../src/newResult' import { Status } from '../src/result' -import Runnable from '../src/runnable' +import Runnable, { isRunnable, RunnableTypes } from '../src/runnable' import Suite, { rootSymbol } from '../src/suite' +class OnyxRunnable extends Runnable { + async run() { + try { + if (this.options.skip || this.options.todo) { + return Promise.resolve(this.doSkip(true)) + } + + await this.doStart() + + return Promise.resolve(this.doPass()) + } catch(err) { + return Promise.reject(this.doFail(err)) + } + } +} + describe('Runnable', () => { const defaultOpts = { skip: false, todo: false } const defaultSuiteOpts = { skip: false, todo: false } + let parentSuite: Suite + let runnable: Runnable - it('should get full description', () => { - const parent = new Suite('parent', defaultSuiteOpts, null) - const child = new Runnable('child', defaultOpts, parent) - - expect(child.getFullDescription()).toBe('parent -> child') + beforeAll(() => { + parentSuite = new Suite('parent', defaultSuiteOpts, null) + runnable = new OnyxRunnable('runnable', defaultOpts, parentSuite) }) - it('should ignore root in full description', () => { - const parent = new Suite('parent', defaultSuiteOpts, null) - parent[rootSymbol] = true - const child = new Runnable('child', defaultOpts, parent) - expect(child.getFullDescription()).toBe('child') + it('should get full description', () => { + expect(runnable.getFullDescription()).toBe('parent -> runnable') }) - it('should run asynchronously', async () => { - const runnable = new Runnable('runnable', defaultOpts, null) - - expect((await runnable.run()).status).toBe(Status.Skipped) + it('doStart()', () => { + runnable.doStart() + expect(runnable.start).not.toBe(0) + expect(runnable.result.status).toBe(RunStatus.RUNNING) }) - describe('events', () => { - it('start', () => { - const runnable = new Runnable('runnable', defaultOpts, null) - - const fn = jest.fn() - runnable.on('start', fn) - - runnable.doStart() - expect(runnable.result.status).toBe(Status.Running) - expect(fn).toHaveBeenCalledTimes(1) - }) - - it('pass', () => { - const runnable = new Runnable('runnable', defaultOpts, null) - const fn = jest.fn() - runnable.on('pass', fn) - - const end = jest.fn() - runnable.on('end', end) - - runnable.doPass() - expect(runnable.result.status).toBe(Status.Passed) - expect(fn).toHaveBeenCalledTimes(1) - expect(end).toHaveBeenCalledTimes(1) - }) - - it('fail', () => { - const runnable = new Runnable('runnable', defaultOpts, null) - const fn = jest.fn() - runnable.on('fail', fn) - - const end = jest.fn() - runnable.on('end', end) - - runnable.doFail() - expect(runnable.result.status).toBe(Status.Failed) - expect(fn).toHaveBeenCalledTimes(1) - expect(end).toHaveBeenCalledTimes(1) - }) - - it('skip', () => { - const runnable = new Runnable('runnable', defaultOpts, null) - const fn = jest.fn() - runnable.on('skip', fn) - - const end = jest.fn() - runnable.on('end', end) - const skip = jest.fn() - runnable.on('skip', skip) - - runnable.doSkip() - expect(runnable.result.status).toBe(Status.Skipped) - expect(fn).toHaveBeenCalledTimes(1) - expect(end).toHaveBeenCalledTimes(1) - expect(skip).toHaveBeenCalledWith(runnable, false) - expect(runnable.time).toBe(0) - }) - - it('skip(todo)', () => { - const runnable = new Runnable('runnable', defaultOpts, null) - const fn = jest.fn() - runnable.on('skip', fn) - - const end = jest.fn() - runnable.on('end', end) - const skip = jest.fn() - runnable.on('skip', skip) - - runnable.doSkip(true) - expect(runnable.result.status).toBe(Status.Todo) - expect(fn).toHaveBeenCalledTimes(1) - expect(end).toHaveBeenCalledTimes(1) - expect(skip).toHaveBeenCalledWith(runnable, true) - expect(runnable.time).toBe(0) - }) + it('should ignore root in full description', () => { + parentSuite[rootSymbol] = true - it('end', () => { - const runnable = new Runnable('runnable', defaultOpts, null) - const end = jest.fn() - runnable.on('end', end) + expect(runnable.getFullDescription()).toBe('runnable') + }) - runnable.doEnd() - expect(end).toHaveBeenCalledTimes(1) + describe('isRunnable type guard', () => { + it ('should return false if not a valid Runnable', () => { + expect(isRunnable(null)).toBe(false) + expect(isRunnable('')).toBe(false) + expect(isRunnable(runnable)).toBe(true) }) }) }) From 6893b76badaac2b313c8eb8ef221c86340f471f4 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Tue, 25 May 2021 21:34:24 -0700 Subject: [PATCH 16/37] test(runnable): start testing abstract class. --- packages/core/test/runnable.spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/core/test/runnable.spec.ts b/packages/core/test/runnable.spec.ts index 42784a5..84babe9 100644 --- a/packages/core/test/runnable.spec.ts +++ b/packages/core/test/runnable.spec.ts @@ -40,9 +40,13 @@ describe('Runnable', () => { expect(runnable.result.status).toBe(RunStatus.RUNNING) }) + it('doEnd()', () => { + runnable.doEnd() + expect(runnable.result.time).not.toBe(0) + }) + it('should ignore root in full description', () => { parentSuite[rootSymbol] = true - expect(runnable.getFullDescription()).toBe('runnable') }) From 403cd68886f9f2f52d651f7ad3655635cd63769c Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Fri, 4 Jun 2021 14:15:52 -0700 Subject: [PATCH 17/37] refactor(runnable): update runnable class and tests. Update result types used. --- packages/core/src/newResult.ts | 20 ++++++++++++ packages/core/src/runnable.ts | 23 ++++++------- packages/core/test/runnable.spec.ts | 50 ++++++++++++++++++++++++++--- 3 files changed, 77 insertions(+), 16 deletions(-) create mode 100644 packages/core/src/newResult.ts diff --git a/packages/core/src/newResult.ts b/packages/core/src/newResult.ts new file mode 100644 index 0000000..43d6e01 --- /dev/null +++ b/packages/core/src/newResult.ts @@ -0,0 +1,20 @@ +import { Runnable, Suite, Test } from "." +import { Hooks } from "./hooks" +import { RunnableTypes } from "./runnable" + +export enum RunStatus { + PENDING = 'pending', + RUNNING = 'running', + PASSED = 'passed', + FAILED = 'failed', + SKIPPED = 'skipped', + TODO = 'todo', +} + +export type BaseResult = { + messages: Array + failures: Array + hooks: Hooks + status: RunStatus + fullDescription: string +} diff --git a/packages/core/src/runnable.ts b/packages/core/src/runnable.ts index 3aa759e..1b30b5a 100644 --- a/packages/core/src/runnable.ts +++ b/packages/core/src/runnable.ts @@ -1,7 +1,8 @@ import { performance } from 'perf_hooks' import { Test } from '.' import { Hooks } from './hooks' -import { RunStatus, TestResultData } from './newResult' +import { RunStatus, BaseResult } from './newResult' +import { Status } from './result' import Suite from './suite' export const runnableSymbol = Symbol('isRunnable') @@ -25,23 +26,21 @@ export interface RunnableOptions { todo: boolean } -export interface RunnableResultData extends TestResultData { +export interface RunnableResult extends BaseResult { id: string description: string time: number } -const DEFAULT_RESULT_DATA: RunnableResultData = { +const DEFAULT_RESULT: RunnableResult = { id: '', description: '', messages: [], failures: [], - filePath: '', hooks: {} as Hooks, status: RunStatus.PENDING, time: 0, - title: '', - type: RunnableTypes.Runnable, + fullDescription: '', } export default abstract class Runnable { @@ -57,9 +56,10 @@ export default abstract class Runnable { } } public description: string - public result: RunnableResultData public options: RunnableOptions public parent: Suite | null + public result: RunnableResult + public status: RunStatus public type: RunnableTypes = RunnableTypes.Runnable public [runnableSymbol] = true @@ -69,15 +69,16 @@ export default abstract class Runnable { /* istanbul ignore next */ constructor(description: string, options: Partial = {}, parent: Suite | null) { this.description = description - this.result = { ...DEFAULT_RESULT_DATA } this.options = Runnable.normalizeOptions(options) this.parent = parent + this.result = { ...DEFAULT_RESULT, description, fullDescription: this.getFullDescription() } + this.status = RunStatus.PENDING } /** * @description Run a `Runnable` instance. */ - public abstract run(): Promise + public abstract run(): Promise /** @@ -123,8 +124,8 @@ export default abstract class Runnable { /** * @description Emits `skip` event with the skipped `Runnable` instance. */ - public doSkip(todo = false) { - this.result.status = todo ? RunStatus.TODO : RunStatus.SKIPPED + public doSkip(skipOrTodo: RunStatus.SKIPPED | RunStatus.TODO) { + this.result.status = skipOrTodo this.doEnd() return this.result diff --git a/packages/core/test/runnable.spec.ts b/packages/core/test/runnable.spec.ts index 84babe9..fb2ae52 100644 --- a/packages/core/test/runnable.spec.ts +++ b/packages/core/test/runnable.spec.ts @@ -4,17 +4,19 @@ import Runnable, { isRunnable, RunnableTypes } from '../src/runnable' import Suite, { rootSymbol } from '../src/suite' class OnyxRunnable extends Runnable { - async run() { + async run(shouldThrow: boolean = false) { try { + if (shouldThrow) throw new Error('thrown') + if (this.options.skip || this.options.todo) { - return Promise.resolve(this.doSkip(true)) + return Promise.resolve(this.doSkip(this.options.skip ? RunStatus.SKIPPED : RunStatus.TODO)) } await this.doStart() return Promise.resolve(this.doPass()) } catch(err) { - return Promise.reject(this.doFail(err)) + return Promise.resolve(this.doFail(err)) } } } @@ -23,13 +25,18 @@ describe('Runnable', () => { const defaultOpts = { skip: false, todo: false } const defaultSuiteOpts = { skip: false, todo: false } let parentSuite: Suite - let runnable: Runnable + let runnable: OnyxRunnable - beforeAll(() => { + beforeEach(() => { parentSuite = new Suite('parent', defaultSuiteOpts, null) runnable = new OnyxRunnable('runnable', defaultOpts, parentSuite) }) + it('should update the result description and fullDescription when instaniated', () => { + expect(runnable.result.description).toBe('runnable') + expect(runnable.result.fullDescription).toBe('parent -> runnable') + }) + it('should get full description', () => { expect(runnable.getFullDescription()).toBe('parent -> runnable') }) @@ -50,6 +57,39 @@ describe('Runnable', () => { expect(runnable.getFullDescription()).toBe('runnable') }) + it('should return a passing result', async () => { + const result = await runnable.run() + expect(result.status).toBe(RunStatus.PASSED) + }) + + it('should return a failing result', async () => { + const result = await runnable.run(true) + expect(result.status).toBe(RunStatus.FAILED) + expect(result.failures[0].message).toBe('thrown') + }) + + it('should return a skipped result', async () => { + runnable.options.skip = true + + const result = await runnable.run() + expect(result.status).toBe(RunStatus.SKIPPED) + }) + + it('should return a todo result', async () => { + runnable.options.todo = true + + const result = await runnable.run() + expect(result.status).toBe(RunStatus.TODO) + }) + + it('should return whether the runnable has finished', async () => { + expect(runnable.isDone()).toBe(false) + + await runnable.run() + + expect(runnable.isDone()).toBe(true) + }) + describe('isRunnable type guard', () => { it ('should return false if not a valid Runnable', () => { expect(isRunnable(null)).toBe(false) From a3b3c9485ae493a52ec1d5e1ad845998ad0fe399 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Fri, 4 Jun 2021 21:31:58 -0700 Subject: [PATCH 18/37] refactor(runnable): make doFail always take an error. --- packages/core/src/runnable.ts | 6 ++---- packages/core/test/runnable.spec.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/core/src/runnable.ts b/packages/core/src/runnable.ts index 1b30b5a..09a7498 100644 --- a/packages/core/src/runnable.ts +++ b/packages/core/src/runnable.ts @@ -111,10 +111,8 @@ export default abstract class Runnable { /** * @description Emits a `fail` event with the failed `Runnable` instance and passed error. */ - public doFail(error?: Error) { - if (error) { - this.result.failures.push(error) - } + public doFail(error: Error) { + this.result.failures.push(error) this.result.status = RunStatus.FAILED this.doEnd() diff --git a/packages/core/test/runnable.spec.ts b/packages/core/test/runnable.spec.ts index fb2ae52..6267240 100644 --- a/packages/core/test/runnable.spec.ts +++ b/packages/core/test/runnable.spec.ts @@ -4,7 +4,7 @@ import Runnable, { isRunnable, RunnableTypes } from '../src/runnable' import Suite, { rootSymbol } from '../src/suite' class OnyxRunnable extends Runnable { - async run(shouldThrow: boolean = false) { + async run(shouldThrow = false) { try { if (shouldThrow) throw new Error('thrown') From df63892f0fd64078c691a06320615c1dc6b3b9c6 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Tue, 4 Jan 2022 12:21:12 -0800 Subject: [PATCH 19/37] chore(core): misc changes. --- packages/core/src/result.ts | 47 ++++++++++++++++++++++++--------- packages/core/src/runnable.ts | 6 ++++- packages/core/src/test.ts | 5 +--- packages/core/tsconfig.json | 12 +++++++-- packages/matchers/tsconfig.json | 1 + 5 files changed, 51 insertions(+), 20 deletions(-) diff --git a/packages/core/src/result.ts b/packages/core/src/result.ts index 6cd485e..3ed26e4 100644 --- a/packages/core/src/result.ts +++ b/packages/core/src/result.ts @@ -1,3 +1,5 @@ +import { RunnableTypes } from "." + export enum Status { Pending = 'pending', Running = 'running', @@ -11,29 +13,44 @@ export enum Status { * @todo Delete messages. */ export default class Result { - private internalStatus: Status - private internalMessages: string[] + private _internalErrors: Error[] + private _internalStatus: Status + private _internalMessages: string[] + + public time: number + public title: string + public description: string + public filePath: string + public type: RunnableTypes - constructor(status?: Status, messages: string | string[] = []) { - this.internalStatus = status || Status.Pending - if (!Array.isArray(messages)) { - messages = [ messages ] - } - this.internalMessages = messages + constructor(messages: string | string[] = [], options: Result, errors: Error[] | Error = [], status?: Status) { + this._internalErrors = !Array.isArray(errors) ? [ errors ] : errors + this._internalStatus = status || Status.Pending + this._internalMessages = !Array.isArray(messages) ? [ messages ] : messages + + this.description = options.description + this.filePath = options.filePath + this.time = options.time + this.title = options.title + this.type = options.type } /** * @description Checks if the internal status is 'Pending' or 'Running'. */ public isDone() { - return this.internalStatus !== Status.Pending && this.internalStatus !== Status.Running + return this._internalStatus !== Status.Pending && this._internalStatus !== Status.Running + } + + public get errors() { + return this._internalErrors } /** * @description Gets the internal status on the current `Result` instance. */ public get status() { - return this.internalStatus + return this._internalStatus } /** @@ -41,14 +58,14 @@ export default class Result { */ public set status(v: Status) { if (this.isDone()) { return } - this.internalStatus = v + this._internalStatus = v } /** * @description Gets the internal messages on the current `Result` instance. */ public get messages() { - return this.internalMessages + return this._internalMessages } /** @@ -56,6 +73,10 @@ export default class Result { */ public addMessages(...messages: string[]) { if (this.isDone()) { return } - this.internalMessages.push(...messages) + this._internalMessages.push(...messages) + } + + public addErrors(...errors: Error[]) { + this._internalErrors = [ ...this._internalErrors, ...errors ] } } diff --git a/packages/core/src/runnable.ts b/packages/core/src/runnable.ts index 09a7498..7a74b27 100644 --- a/packages/core/src/runnable.ts +++ b/packages/core/src/runnable.ts @@ -2,7 +2,7 @@ import { performance } from 'perf_hooks' import { Test } from '.' import { Hooks } from './hooks' import { RunStatus, BaseResult } from './newResult' -import { Status } from './result' +import Result, { Status } from './result' import Suite from './suite' export const runnableSymbol = Symbol('isRunnable') @@ -15,6 +15,10 @@ export const isRunnable = (v: unknown): v is Runnable => { else { return false } } +export const isRunnable2 = (v: unknown): v is R => { + return (v instanceof Runnable) || (v instanceof Test) || (v instanceof Suite) +} + export enum RunnableTypes { Runnable = 'runnable', Suite = 'suite', diff --git a/packages/core/src/test.ts b/packages/core/src/test.ts index 7edb2e9..f4db60a 100644 --- a/packages/core/src/test.ts +++ b/packages/core/src/test.ts @@ -8,10 +8,7 @@ export type TestFn = () => (void | Promise) /** * @description Checks if the passed `Runnable` value is a `Test` instance. */ -export const isTest = (v: unknown): v is Test => { - if (!isRunnable(v)) { return false } - return v.type === RunnableTypes.Test -} +export const isTest = (v: unknown): v is Test => v instanceof Test export default class Test extends Runnable { public fn: TestFn diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index fe126e4..a12a5a3 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -18,12 +18,20 @@ "sourceMap": true, "strict": true, "target": "es5", + "paths": { + "@onyx/matchers": ["../matchers"] + } }, "exclude": [ - "node_modules" + "node_modules", + "**/test/*" ], "include": [ - "src", + "src/**/*", "jest.config.js" + ], + "references": [ + { "path": "../matchers" }, + { "path": "../mock" } ] } diff --git a/packages/matchers/tsconfig.json b/packages/matchers/tsconfig.json index ff99af2..c862633 100644 --- a/packages/matchers/tsconfig.json +++ b/packages/matchers/tsconfig.json @@ -6,6 +6,7 @@ "target": "es5", "allowJs": true, "checkJs": true, + "composite": true, "esModuleInterop": true, "strict": true, "moduleResolution": "node", From 77e357c6b5183dae9f8f43a6c41a4e28efa12ba7 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 10 Jan 2022 11:08:21 -0800 Subject: [PATCH 20/37] chore(configs): update ts and jest. --- packages/core/jest.config.js | 5 +++-- packages/core/tsconfig.json | 9 +-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js index 60ddeda..2aca978 100644 --- a/packages/core/jest.config.js +++ b/packages/core/jest.config.js @@ -1,6 +1,7 @@ +/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ module.exports = { preset: 'ts-jest', + testEnvironment: 'node', roots: ['test/'], - testEnvironment: 'jsdom', - testMatch: ['**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec).[jt]s?(x)'], + // testMatch: ['**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec).[jt]s?(x)'], }; diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index a12a5a3..e913ba4 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -3,12 +3,6 @@ "esModuleInterop": true, "declaration": true, "lib": [ - "es2015", - "es2016", - "es2017", - "es2018", - "es2019", - "es2020", "esnext" ], "module": "commonjs", @@ -27,8 +21,7 @@ "**/test/*" ], "include": [ - "src/**/*", - "jest.config.js" + "src/**/*" ], "references": [ { "path": "../matchers" }, From fb7e5a0f44c791d9130cdca9dba298dfa79d04ec Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 10 Jan 2022 11:11:33 -0800 Subject: [PATCH 21/37] refactor(test): rewrite test class, runnable class, and tests. Comment out old files. --- packages/core/src/index.ts | 2 +- packages/core/src/result-spec.md | 17 + packages/core/src/result.ts | 12 +- packages/core/src/runnable.ts | 5 +- packages/core/src/runner.ts | 66 +-- packages/core/src/suite.ts | 156 +++---- packages/core/src/test.ts | 123 ++++-- packages/core/src/types.ts | 41 ++ packages/core/src/utils.ts | 18 + packages/core/src/utils/index.ts | 1 + .../core/test/__snapshots__/test.spec.ts.snap | 8 - packages/core/test/result.spec.ts | 62 +-- packages/core/test/runnable.spec.ts | 6 +- packages/core/test/runner.spec.ts | 60 +-- packages/core/test/suite.spec.ts | 390 +++++++++--------- packages/core/test/test.spec.ts | 109 ++--- 16 files changed, 590 insertions(+), 486 deletions(-) create mode 100644 packages/core/src/result-spec.md create mode 100644 packages/core/src/types.ts create mode 100644 packages/core/src/utils.ts create mode 100644 packages/core/src/utils/index.ts delete mode 100644 packages/core/test/__snapshots__/test.spec.ts.snap diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8203bcf..7b29a63 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -18,5 +18,5 @@ export { Status, default as Result } from './result' export { default as Runnable, isRunnable, RunnableTypes } from './runnable' export { default as Test, isTest } from './test' export { default as Suite, isSuite, SuiteStats, rootSymbol, BailError } from './suite' -export { default as Runner, RunOptions, runnerDefaults } from './runner' +export { RunOptions } from './runner' export { Hook, HookName, HookFn, Hooks } from './hooks' diff --git a/packages/core/src/result-spec.md b/packages/core/src/result-spec.md new file mode 100644 index 0000000..29a3d7e --- /dev/null +++ b/packages/core/src/result-spec.md @@ -0,0 +1,17 @@ +# Result + + - Returns from a Runnable instances run method. (Suite | Test) + + ```ts +// Base result interface +interface Result { + id: string + description: string + filePath: string + failures: { [key: Result[id]]: Error } + status: RunResultStatus + time: number + title: string + type: RunnableType +} + ``` diff --git a/packages/core/src/result.ts b/packages/core/src/result.ts index 3ed26e4..f801dfd 100644 --- a/packages/core/src/result.ts +++ b/packages/core/src/result.ts @@ -38,18 +38,18 @@ export default class Result { /** * @description Checks if the internal status is 'Pending' or 'Running'. */ - public isDone() { + public isDone(): boolean { return this._internalStatus !== Status.Pending && this._internalStatus !== Status.Running } - public get errors() { + public get errors(): Array { return this._internalErrors } /** * @description Gets the internal status on the current `Result` instance. */ - public get status() { + public get status(): Status { return this._internalStatus } @@ -64,19 +64,19 @@ export default class Result { /** * @description Gets the internal messages on the current `Result` instance. */ - public get messages() { + public get messages(): string[] { return this._internalMessages } /** * @description Adds messages to the internal messages if the `Runnable` has not completed. */ - public addMessages(...messages: string[]) { + public addMessages(...messages: string[]): void { if (this.isDone()) { return } this._internalMessages.push(...messages) } - public addErrors(...errors: Error[]) { + public addErrors(...errors: Error[]): void { this._internalErrors = [ ...this._internalErrors, ...errors ] } } diff --git a/packages/core/src/runnable.ts b/packages/core/src/runnable.ts index 7a74b27..8ddcbf5 100644 --- a/packages/core/src/runnable.ts +++ b/packages/core/src/runnable.ts @@ -88,9 +88,10 @@ export default abstract class Runnable { /** * @description Sets result status to `Running` and emits a `start` event with the `Runnable` instance and timestamp. */ - public doStart(): void { + public doStart(): RunnableResult { this.result.status = RunStatus.RUNNING this.start = performance.now() + return this.result } /** @@ -116,7 +117,7 @@ export default abstract class Runnable { * @description Emits a `fail` event with the failed `Runnable` instance and passed error. */ public doFail(error: Error) { - this.result.failures.push(error) + this.result.failures = [...this.result.failures, error] this.result.status = RunStatus.FAILED this.doEnd() diff --git a/packages/core/src/runner.ts b/packages/core/src/runner.ts index 12166aa..adfa0b1 100644 --- a/packages/core/src/runner.ts +++ b/packages/core/src/runner.ts @@ -1,44 +1,44 @@ -import Suite, { SuiteStats } from './suite' +// import Suite, { SuiteStats } from './suite' export interface RunOptions { bail: boolean sequential: boolean timeout: number } -/** - * @description Default runner options - */ -export const runnerDefaults: RunOptions = { - bail: false, - sequential: false, - timeout: 10000, -} +// /** +// * @description Default runner options +// */ +// export const runnerDefaults: RunOptions = { +// bail: false, +// sequential: false, +// timeout: 10000, +// } -export function normalizeRunOptions(options: Partial = {}): RunOptions { - return { - ...runnerDefaults, - ...options, - } -} +// export function normalizeRunOptions(options: Partial = {}): RunOptions { +// return { +// ...runnerDefaults, +// ...options, +// } +// } -export default class Runner { - public rootSuite: Suite - public options: RunOptions - public stats: SuiteStats +// export default class Runner { +// public rootSuite: Suite +// public options: RunOptions +// public stats: SuiteStats - constructor(suite: Suite, options: Partial = {}) { - this.options = normalizeRunOptions(options) - this.rootSuite = suite - this.stats = suite.getStats() - } +// constructor(suite: Suite, options: Partial = {}) { +// this.options = normalizeRunOptions(options) +// this.rootSuite = suite +// this.stats = suite.getStats() +// } - /** - * @description Calls run on the root suite, passing the current `Runner` instance options. - */ - public async run() { - await this.rootSuite.run(this.options) +// /** +// * @description Calls run on the root suite, passing the current `Runner` instance options. +// */ +// public async run() { +// await this.rootSuite.run(this.options) - this.stats = this.rootSuite.getStats() - return this.stats - } -} +// this.stats = this.rootSuite.getStats() +// return this.stats +// } +// } diff --git a/packages/core/src/suite.ts b/packages/core/src/suite.ts index 6937819..f9ee67f 100644 --- a/packages/core/src/suite.ts +++ b/packages/core/src/suite.ts @@ -1,16 +1,15 @@ import { HookName, Hooks } from './hooks' -import Result, { Status } from './result' -import Runnable, { isRunnable, RunnableOptions, RunnableTypes } from './runnable' -import { normalizeRunOptions, RunOptions } from './runner' +import Runnable, { isRunnable, RunnableOptions, RunnableResult, RunnableTypes } from './runnable' /** * @description Checks if passed value is a `Runnable` instance of type `Suite`. */ -export const isSuite = (v: unknown): v is Suite => { +const isSuite = (v: unknown): v is Suite => { if (!isRunnable(v)) { return false } - return v.type === RunnableTypes.Suite + else return v.type === RunnableTypes.Suite } -export const rootSymbol = Symbol('isRoot') + +const rootSymbol = Symbol('isRoot') export interface SuiteStats { total: number @@ -92,63 +91,67 @@ export default class Suite extends Runnable { /** * @description Runs a `Suite` instance. */ - public async run(options?: Partial): Promise { - options = normalizeRunOptions(options) - - if (this.options.skip || this.options.todo) { - return this.doSkip(this.options.todo) - } - - this.doStart() - await this.invokeHook('beforeAll') - - const promises: Array> = [] - for (const child of this.children) { - promises.push((async () => { - await this.invokeHook('beforeEach') - const result = await child.run() - this.result.addMessages(...result.messages.map((m) => `${child.description}: ${m}`)) - await this.invokeHook('afterEach') - - if (result.status === Status.Failed) ++this.failed - - return result - })()) - } - - if (options.sequential) { - for (const promise of promises) { - try { - const result = await promise + // public async run(options?: Partial): Promise { + // options = normalizeRunOptions(options) + + // if (this.options.skip || this.options.todo) { + // return this.doSkip(this.options.todo) + // } + + // this.doStart() + // await this.invokeHook('beforeAll') + + // const promises: Array> = [] + // for (const child of this.children) { + // promises.push((async () => { + // await this.invokeHook('beforeEach') + // const result = await child.run() + // this.result.addMessages(...result.messages.map((m) => `${child.description}: ${m}`)) + // await this.invokeHook('afterEach') + + // if (result.status === Status.Failed) ++this.failed + + // return result + // })()) + // } + + // if (options.sequential) { + // for (const promise of promises) { + // try { + // const result = await promise - if (options.bail && result) { - throw new BailError(result.messages[0]) - } - } catch (error) { - await this.invokeHook('afterAll') - return this.doFail(error) - } - } - } else { - try { - await Promise.all(promises.map(async (promise) => { - const result = await promise - - if (options && options.bail && result) { - throw new BailError(result.messages[0]) - } - })) - } catch (error) { - await this.invokeHook('afterAll') - return this.doFail(error) - } - } - - await this.invokeHook('afterAll') - if (this.failed) { - return this.doFail() - } - return this.doPass() + // if (options.bail && result) { + // throw new BailError(result.messages[0]) + // } + // } catch (error) { + // await this.invokeHook('afterAll') + // return this.doFail(error) + // } + // } + // } else { + // try { + // await Promise.all(promises.map(async (promise) => { + // const result = await promise + + // if (options && options.bail && result) { + // throw new BailError(result.messages[0]) + // } + // })) + // } catch (error) { + // await this.invokeHook('afterAll') + // return this.doFail(error) + // } + // } + + // await this.invokeHook('afterAll') + // if (this.failed) { + // return this.doFail() + // } + // return this.doPass() + // } + + public async run () { + return {} as Promise } /** @@ -156,17 +159,19 @@ export default class Suite extends Runnable { */ public getStats(): SuiteStats { const childrenList = this.flatten(this.children) - return { - done: childrenList.filter((c) => c.result.isDone()).length, - failed: childrenList.filter((c) => c.result.status === Status.Failed).length, - passed: childrenList.filter((c) => c.result.status === Status.Passed).length, - pending: childrenList.filter((c) => c.result.status === Status.Pending).length, - running: childrenList.filter((c) => c.result.status === Status.Running).length, - skipped: childrenList.filter((c) => c.result.status === Status.Skipped).length, - time: this.time, - todo: childrenList.filter((c) => c.result.status === Status.Todo).length, - total: childrenList.length, - } + // return { + // done: childrenList.filter((c) => c.result.isDone()).length, + // failed: childrenList.filter((c) => c.result.status === Status.Failed).length, + // passed: childrenList.filter((c) => c.result.status === Status.Passed).length, + // pending: childrenList.filter((c) => c.result.status === Status.Pending).length, + // running: childrenList.filter((c) => c.result.status === Status.Running).length, + // skipped: childrenList.filter((c) => c.result.status === Status.Skipped).length, + // time: this.time, + // todo: childrenList.filter((c) => c.result.status === Status.Todo).length, + // total: childrenList.length, + // } + + return {} as SuiteStats } /** @@ -185,3 +190,8 @@ export default class Suite extends Runnable { return flatTree } } + +export { + isSuite, + rootSymbol, +} diff --git a/packages/core/src/test.ts b/packages/core/src/test.ts index f4db60a..fe67793 100644 --- a/packages/core/src/test.ts +++ b/packages/core/src/test.ts @@ -1,9 +1,36 @@ -import Result from './result' -import Runnable, { isRunnable, RunnableOptions, RunnableTypes } from './runnable' +import { Status } from '.' +import { RunStatus } from './newResult' +import Runnable, { RunnableOptions, RunnableResult, RunnableTypes } from './runnable' import { RunOptions } from './runner' import Suite from './suite' -export type TestFn = () => (void | Promise) +export type TestFn = () => (void | Promise) +// const TimeoutError = Symbol('TimeoutError') + +export class TimeoutError extends Error { + public constructor(message: string) { + super(message) + this.name = 'TimeoutError' + } +} + +function promiseWithTimeout( + promise: T, + ms: number, + timeoutError = new TimeoutError('Promise timed out') +) { + let timer: NodeJS.Timeout + + // create a promise that rejects in milliseconds + const timeout = new Promise((_, reject) => { + timer = setTimeout(() => { + reject(timeoutError) + }, ms) + }) + + // returns a race between timeout and the passed promise + return Promise.race([promise(), timeout]).finally(() => clearTimeout(timer)) +} /** * @description Checks if the passed `Runnable` value is a `Test` instance. @@ -21,45 +48,79 @@ export default class Test extends Runnable { this.parent = parent } + private async _timeout( + promise: T, + ms: number, + timeoutError = new TimeoutError(`${this.getFullDescription()} has timed out: ${ms}ms`) + ): Promise { + let timer: NodeJS.Timeout + + // create a promise that rejects in milliseconds + const timeout = new Promise((_, reject) => { + timer = setTimeout(() => { + reject(timeoutError) + }, ms) + }) + + // returns a race between timeout and the passed promise + return await Promise.race([promise(), timeout]).finally(() => clearTimeout(timer)) + } + /** * @description Run a `Test` instance. */ - public async run(options?: Partial): Promise { + public async run(options?: Partial): Promise { if (this.options.skip || this.options.todo) { - return this.doSkip(this.options.todo) + return this.doSkip(this.options.todo ? RunStatus.TODO : RunStatus.SKIPPED) } this.doStart() - if (options && options.timeout) { - let timer: NodeJS.Timeout - const wait = (ms: number) => new Promise(resolve => { - timer = setTimeout(resolve, ms) - }) - - const test = Promise.race([ - wait(options.timeout).then(() => { - clearTimeout(timer) - throw new Error(`${this.getFullDescription()} has timed out: ${options.timeout}ms`) - }), - this.fn(), - ]) - - try { - await test - } catch (error) { - return this.doFail(error) - } + try { + // const result = options && options.timeout ? await promiseWithTimeout(this.fn, options.timeout) : this.fn() + const result = options && options.timeout ? await this._timeout(this.fn, options.timeout) : await this.fn() - return this.doPass() - } else { - try { - await this.fn() - } catch (error) { - return this.doFail(error) - } + // console.log('result: ', result) return this.doPass() + } catch (error: any) { + // console.log('error: ', (error as Error)) + // const err = error instanceof TimeoutError || error instanceof Error ? error : new Error(error.message) + return this.doFail(error) } } + + // public async run2() { + // if (options && options.timeout) { + // let timer: NodeJS.Timeout + // const wait = (ms: number) => new Promise(resolve => { + // timer = setTimeout(resolve, ms) + // }) + + // const test = Promise.race([ + // wait(options.timeout).then(() => { + // clearTimeout(timer) + // throw new Error(`${this.getFullDescription()} has timed out: ${options.timeout}ms`) + // }), + // this.fn(), + // ]) + + // try { + // await test + // } catch (error) { + // return this.doFail(error) + // } + + // return this.doPass() + // } else { + // try { + // await this.fn() + // } catch (error) { + // return this.doFail(error) + // } + + // return this.doPass() + // } + // return {} as Promise; + // } } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts new file mode 100644 index 0000000..e27d77f --- /dev/null +++ b/packages/core/src/types.ts @@ -0,0 +1,41 @@ +import { AnyMatchers, ExpectError } from '@onyx/matchers' + +type OmitFirstArg = A extends (x: any, ...args: infer P) => infer R ? (...args: P) => R : never + +type Expectations = { [K in keyof M]: OmitFirstArg } + +export type NegatedExpectations = Expectations & { + not: Expectations +} + + +export type ExpectResult = { + matcher: string + error?: ExpectError + status: ExpectStatus + actual: A + expected: E +} + +enum ExpectStatus { + PASS = 'Pass', + FAIL = 'Fail', +} + +const _expectFail = (fail: F): ExpectationResult => ({ _status: ExpectStatus.FAIL, fail }) +const _expectPass = (pass: P): ExpectationResult => ({ _status: ExpectStatus.PASS, pass }) + +export const expectPass: (pass: P) => ExpectationResult = _expectPass +export const expectFail: (fail: F) => ExpectationResult = _expectFail + +interface IExpectFail { + readonly _status: ExpectStatus.FAIL + readonly fail: F +} + +interface IExpectPass

{ + readonly _status: ExpectStatus.PASS + readonly pass: P +} + +type ExpectationResult = IExpectFail | IExpectPass

diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts new file mode 100644 index 0000000..e8c875d --- /dev/null +++ b/packages/core/src/utils.ts @@ -0,0 +1,18 @@ +export function promisifyTimeoutFn( + // error: Error = new Error(), + ms: number, + testFn: () => Promise, + timer: NodeJS.Timeout, +) { + const wait = new Promise(resolve => { + timer = setTimeout(resolve, ms) + }) + + return Promise.race([ + wait.then(() => { + clearTimeout(timer) + throw new Error() + }), + testFn(), + ]) +} diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts new file mode 100644 index 0000000..a7e2e6e --- /dev/null +++ b/packages/core/src/utils/index.ts @@ -0,0 +1 @@ +// export * as typeguards from './typeguards' diff --git a/packages/core/test/__snapshots__/test.spec.ts.snap b/packages/core/test/__snapshots__/test.spec.ts.snap deleted file mode 100644 index 20ece4f..0000000 --- a/packages/core/test/__snapshots__/test.spec.ts.snap +++ /dev/null @@ -1,8 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Test should run 1`] = ` -Result { - "internalMessages": Array [], - "internalStatus": "passed", -} -`; diff --git a/packages/core/test/result.spec.ts b/packages/core/test/result.spec.ts index 4dd830a..1958b89 100644 --- a/packages/core/test/result.spec.ts +++ b/packages/core/test/result.spec.ts @@ -1,42 +1,42 @@ -import Result, { Status } from '../src/result' +// import Result, { Status } from '../src/result' -describe('Result', () => { - it('should change status', () => { - const result = new Result() +// describe('Result', () => { +// it('should change status', () => { +// const result = new Result() - expect(result.status).toBe(Status.Pending) - result.status = Status.Passed - expect(result.status).toBe(Status.Passed) - }) +// expect(result.status).toBe(Status.Pending) +// result.status = Status.Passed +// expect(result.status).toBe(Status.Passed) +// }) - it('should set isDone', () => { - const result = new Result() +// it('should set isDone', () => { +// const result = new Result() - expect(result.isDone()).toBeFalsy() - result.status = Status.Passed - expect(result.isDone()).toBeTruthy() - }) +// expect(result.isDone()).toBeFalsy() +// result.status = Status.Passed +// expect(result.isDone()).toBeTruthy() +// }) - it('should work with messages', () => { - const result = new Result(Status.Pending, 'Result') +// it('should work with messages', () => { +// const result = new Result(Status.Pending, 'Result') - expect(result.messages).toHaveLength(1) - result.addMessages('Test', 'Onyx') - expect(result.messages).toHaveLength(3) - }) +// expect(result.messages).toHaveLength(1) +// result.addMessages('Test', 'Onyx') +// expect(result.messages).toHaveLength(3) +// }) - it('should lock up when done', () => { - const result = new Result() +// it('should lock up when done', () => { +// const result = new Result() - result.addMessages('Test', 'Onyx') - expect(result.messages).toHaveLength(2) +// result.addMessages('Test', 'Onyx') +// expect(result.messages).toHaveLength(2) - result.status = Status.Passed +// result.status = Status.Passed - result.addMessages('Test', 'Onyx') - expect(result.messages).toHaveLength(2) +// result.addMessages('Test', 'Onyx') +// expect(result.messages).toHaveLength(2) - result.status = Status.Pending - expect(result.status).toBe(Status.Passed) - }) -}) +// result.status = Status.Pending +// expect(result.status).toBe(Status.Passed) +// }) +// }) diff --git a/packages/core/test/runnable.spec.ts b/packages/core/test/runnable.spec.ts index 6267240..a596617 100644 --- a/packages/core/test/runnable.spec.ts +++ b/packages/core/test/runnable.spec.ts @@ -1,7 +1,5 @@ import { RunStatus } from '../src/newResult' -import { Status } from '../src/result' -import Runnable, { isRunnable, RunnableTypes } from '../src/runnable' -import Suite, { rootSymbol } from '../src/suite' +import { rootSymbol, Suite, Runnable, isRunnable } from '../src' class OnyxRunnable extends Runnable { async run(shouldThrow = false) { @@ -32,7 +30,7 @@ describe('Runnable', () => { runnable = new OnyxRunnable('runnable', defaultOpts, parentSuite) }) - it('should update the result description and fullDescription when instaniated', () => { + it('should update the result description and fullDescription when instantiated', () => { expect(runnable.result.description).toBe('runnable') expect(runnable.result.fullDescription).toBe('parent -> runnable') }) diff --git a/packages/core/test/runner.spec.ts b/packages/core/test/runner.spec.ts index 76cd3dd..b47741b 100644 --- a/packages/core/test/runner.spec.ts +++ b/packages/core/test/runner.spec.ts @@ -1,27 +1,27 @@ //import { Status } from '../src/result' //import Runnable from '../src/runnable' -import Runner, { normalizeRunOptions, /*RunOptions*/ } from '../src/runner' +// import Runner, { normalizeRunOptions, /*RunOptions*/ } from '../src' import Suite, { rootSymbol } from '../src/suite' import Test from '../src/test' const NOOP = jest.fn() -describe('runner', () => { - it('should normalize passed options', () => { - expect(normalizeRunOptions()).toMatchObject({ - bail: false, - sequential: false, - timeout: 10000, - }) - expect(normalizeRunOptions({ - bail: true, - timeout: 1000, - })).toMatchObject({ - bail: true, - sequential: false, - timeout: 1000, - }) - }) +describe.skip('runner', () => { + // it('should normalize passed options', () => { + // expect(normalizeRunOptions()).toMatchObject({ + // bail: false, + // sequential: false, + // timeout: 10000, + // }) + // expect(normalizeRunOptions({ + // bail: true, + // timeout: 1000, + // })).toMatchObject({ + // bail: true, + // sequential: false, + // timeout: 1000, + // }) + // }) // tslint:disable-next-line:max-classes-per-file /*class TimeoutTestRunnable extends Runnable { @@ -78,22 +78,22 @@ describe('runner', () => { expect(cb2).toHaveBeenCalledWith(opts); }); */ - it('should run a suite and children', async () => { - const rootSuite = new Suite('root', {}, null) - const childSuite = new Suite('child suite', {}, null) - const childTest = new Test('child test', NOOP, {}, null) - const childTestTwo = new Test('child test two', NOOP, {}, null) + // it('should run a suite and children', async () => { + // const rootSuite = new Suite('root', {}, null) + // const childSuite = new Suite('child suite', {}, null) + // const childTest = new Test('child test', NOOP, {}, null) + // const childTestTwo = new Test('child test two', NOOP, {}, null) - childSuite.addChildren(childTest, childTestTwo) - rootSuite.addChildren(childSuite) - rootSuite[rootSymbol] = true + // childSuite.addChildren(childTest, childTestTwo) + // rootSuite.addChildren(childSuite) + // rootSuite[rootSymbol] = true - const runner = new Runner(rootSuite) + // const runner = new Runner(rootSuite) - expect(runner.stats.pending).toBe(2) + // expect(runner.stats.pending).toBe(2) - expect(await runner.run()).toBe(runner.stats) + // expect(await runner.run()).toBe(runner.stats) - expect(runner.stats.passed).toBe(2) - }) + // expect(runner.stats.passed).toBe(2) + // }) }) diff --git a/packages/core/test/suite.spec.ts b/packages/core/test/suite.spec.ts index eda994f..4349b11 100644 --- a/packages/core/test/suite.spec.ts +++ b/packages/core/test/suite.spec.ts @@ -17,199 +17,199 @@ describe('Suite', () => { suite[rootSymbol] = true expect(suite.isRoot()).toBeTruthy() }) - class PassingRunnable extends Runnable { - public async run() { - this.result.addMessages('OK') - this.result.status = Status.Passed - return this.result - } - } - - it('should pass', async () => { - const child = new Test('child', jest.fn(), defaultOpts, null) - const parent = new Suite('parent', defaultOpts, null) - parent.addChildren(child) - - const start = jest.fn() - parent.on('start', start) - const pass = jest.fn() - parent.on('pass', pass) - const end = jest.fn() - parent.on('end', end) - - const promise = parent.run() - - expect(start).toHaveBeenCalledTimes(1) - - expect((await promise).status).toBe(Status.Passed) - expect(pass).toHaveBeenCalledTimes(1) - expect(end).toHaveBeenCalledTimes(1) - }) - - it('should run sequentially', async () => { - const suite = new Suite('Suite', defaultOpts, null) - const child = new PassingRunnable('desc', defaultOpts, suite) - const child1 = new PassingRunnable('desc', defaultOpts, suite) - const child2 = new PassingRunnable('desc', defaultOpts, suite) - const child3 = new PassingRunnable('desc', defaultOpts, suite) - const child4 = new PassingRunnable('desc', defaultOpts, suite) - suite.addChildren(child, child1, child2, child3, child4) - - await suite.run({ sequential: true }) - expect(suite.getStats().done).toBe(5) - }) - - it('should fail', async () => { - const fn = jest.fn() - - const err = new Error('FAIL!') - const child = new Test('child 1', () => { throw err }, defaultOpts, null) - const passingChild = new Test('child 2', fn, defaultOpts, null) - const parent = new Suite('parent', defaultOpts, null) - parent.addChildren(child) - parent.addChildren(passingChild) - - const start = jest.fn() - parent.on('start', start) - const fail = jest.fn() - parent.on('fail', fail) - const end = jest.fn() - parent.on('end', end) - - expect((await parent.run()).status).toBe(Status.Failed) - - expect(fn).toHaveBeenCalledTimes(1) - expect(start).toHaveBeenCalledTimes(1) - expect(fail).toHaveBeenCalledTimes(1) - expect(end).toHaveBeenCalledTimes(1) - }) - - describe('should bail out on first failure', () => { - jest.useRealTimers() - - const fn = () => { - return new Promise((resolve) => { - setTimeout(() => { - resolve('Shouldn\'t resolve') - }, 1500) - }) - } - - const errorFn = () => { - throw Error() - } - - it ('non-sequential', async () => { - // Non-sequential - const parent = new Suite('parent', defaultOpts, null) - const fail = new Test('firstFail', errorFn, defaultOpts, null) - const pass = new Test('firstPass', fn, defaultOpts, null) - const secondPass = new Test('secondPass', fn, defaultOpts, null) - - parent.addChildren(fail, pass, secondPass) - - const parentFail = jest.fn() - parent.on('fail', parentFail) - const parentPass = jest.fn() - parent.on('pass', parentPass) - const testFail = jest.fn() - fail.on('fail', testFail) - const testPass = jest.fn() - pass.on('pass', testPass) - - expect((await parent.run({ bail: true, sequential: false })).status).toBe(Status.Failed) - expect(parent.getStats().done).toBe(1) - expect(testPass).toHaveBeenCalledTimes(0) - expect(testFail).toHaveBeenCalledTimes(1) - expect(parentPass).toHaveBeenCalledTimes(0) - expect(parentFail).toHaveBeenCalledTimes(1) - }) - - it('sequential', async () => { - // Sequential - const sequentialParent = new Suite('sequentialParent', defaultOpts, null) - const fail = new Test('fail', errorFn, defaultOpts, null) - const pass = new Test('pass', fn, defaultOpts, null) - const secondPass = new Test('secondPass', fn, defaultOpts, null) - - const sequentialParentPass = jest.fn() - sequentialParent.on('pass', sequentialParentPass) - const sequentialParentFail = jest.fn() - sequentialParent.on('fail', sequentialParentFail) - const sequentialTestPass = jest.fn() - pass.on('pass', sequentialTestPass) - const sequentialTestFail = jest.fn() - fail.on('fail', sequentialTestFail) - - sequentialParent.addChildren(pass, fail, secondPass) - - expect((await sequentialParent.run({ bail: true, sequential: true })).status).toBe(Status.Failed) - expect(sequentialParent.getStats().done).toBe(2) - expect(sequentialTestPass).toHaveBeenCalledTimes(1) - expect(sequentialTestFail).toHaveBeenCalledTimes(1) - expect(sequentialParentPass).toHaveBeenCalledTimes(0) - expect(sequentialParentFail).toHaveBeenCalledTimes(1) - }) - }) - - it('should skip', async () => { - const parent = new Suite('parent', { skip: true }, null) - - const start = jest.fn() - parent.on('start', start) - const skip = jest.fn() - parent.on('skip', skip) - const end = jest.fn() - parent.on('end', end) - - const promise = parent.run() - - expect(skip).toHaveBeenCalledTimes(1) - expect(end).toHaveBeenCalledTimes(1) - expect((await promise).status).toBe(Status.Skipped) - }) - - it('should invoke hooks', async () => { - const error = console.error - console.error = jest.fn() - - const parent = new Suite('parent', {}, null) - parent.addChildren( - new Test('passing', jest.fn(), {}, parent), - new Test('failing', () => { throw new Error('Fail') }, {}, parent), - ) - const calls: string[] = [] - - parent.hooks.beforeAll.push( - () => calls.push('beforeAll1'), - () => calls.push('beforeAll2'), - ) - parent.hooks.beforeEach.push( - async () => await calls.push('beforeEach1'), - () => calls.push('beforeEach2'), - () => { throw new Error('beforeEach hook error') }, - ) - parent.hooks.afterEach.push( - () => calls.push('afterEach1'), - () => calls.push('afterEach2'), - ) - parent.hooks.afterAll.push( - () => calls.push('afterAll1'), - async () => await calls.push('afterAll2'), - async () => { throw new Error('afterAll hook error') }, - ) - - try { - await parent.run() - } catch { - // noop - } - expect(calls).toMatchSnapshot() - - expect(console.error).toHaveBeenCalledTimes(3) - expect(console.error).toHaveBeenCalledWith('Error in beforeEach hook: Error: beforeEach hook error') - expect(console.error).toHaveBeenCalledWith('Error in afterAll hook: Error: afterAll hook error') - - console.error = error - }) + // class PassingRunnable extends Runnable { + // public async run() { + // this.result.addMessages('OK') + // this.result.status = Status.Passed + // return this.result + // } + // } + + // it('should pass', async () => { + // const child = new Test('child', jest.fn(), defaultOpts, null) + // const parent = new Suite('parent', defaultOpts, null) + // parent.addChildren(child) + + // const start = jest.fn() + // parent.on('start', start) + // const pass = jest.fn() + // parent.on('pass', pass) + // const end = jest.fn() + // parent.on('end', end) + + // const promise = parent.run() + + // expect(start).toHaveBeenCalledTimes(1) + + // expect((await promise).status).toBe(Status.Passed) + // expect(pass).toHaveBeenCalledTimes(1) + // expect(end).toHaveBeenCalledTimes(1) + // }) + + // it('should run sequentially', async () => { + // const suite = new Suite('Suite', defaultOpts, null) + // const child = new PassingRunnable('desc', defaultOpts, suite) + // const child1 = new PassingRunnable('desc', defaultOpts, suite) + // const child2 = new PassingRunnable('desc', defaultOpts, suite) + // const child3 = new PassingRunnable('desc', defaultOpts, suite) + // const child4 = new PassingRunnable('desc', defaultOpts, suite) + // suite.addChildren(child, child1, child2, child3, child4) + + // await suite.run({ sequential: true }) + // expect(suite.getStats().done).toBe(5) + // }) + + // it('should fail', async () => { + // const fn = jest.fn() + + // const err = new Error('FAIL!') + // const child = new Test('child 1', () => { throw err }, defaultOpts, null) + // const passingChild = new Test('child 2', fn, defaultOpts, null) + // const parent = new Suite('parent', defaultOpts, null) + // parent.addChildren(child) + // parent.addChildren(passingChild) + + // const start = jest.fn() + // parent.on('start', start) + // const fail = jest.fn() + // parent.on('fail', fail) + // const end = jest.fn() + // parent.on('end', end) + + // expect((await parent.run()).status).toBe(Status.Failed) + + // expect(fn).toHaveBeenCalledTimes(1) + // expect(start).toHaveBeenCalledTimes(1) + // expect(fail).toHaveBeenCalledTimes(1) + // expect(end).toHaveBeenCalledTimes(1) + // }) + + // describe('should bail out on first failure', () => { + // jest.useRealTimers() + + // const fn = () => { + // return new Promise((resolve) => { + // setTimeout(() => { + // resolve('Shouldn\'t resolve') + // }, 1500) + // }) + // } + + // const errorFn = () => { + // throw Error() + // } + + // it ('non-sequential', async () => { + // // Non-sequential + // const parent = new Suite('parent', defaultOpts, null) + // const fail = new Test('firstFail', errorFn, defaultOpts, null) + // const pass = new Test('firstPass', fn, defaultOpts, null) + // const secondPass = new Test('secondPass', fn, defaultOpts, null) + + // parent.addChildren(fail, pass, secondPass) + + // const parentFail = jest.fn() + // parent.on('fail', parentFail) + // const parentPass = jest.fn() + // parent.on('pass', parentPass) + // const testFail = jest.fn() + // fail.on('fail', testFail) + // const testPass = jest.fn() + // pass.on('pass', testPass) + + // expect((await parent.run({ bail: true, sequential: false })).status).toBe(Status.Failed) + // expect(parent.getStats().done).toBe(1) + // expect(testPass).toHaveBeenCalledTimes(0) + // expect(testFail).toHaveBeenCalledTimes(1) + // expect(parentPass).toHaveBeenCalledTimes(0) + // expect(parentFail).toHaveBeenCalledTimes(1) + // }) + + // it('sequential', async () => { + // // Sequential + // const sequentialParent = new Suite('sequentialParent', defaultOpts, null) + // const fail = new Test('fail', errorFn, defaultOpts, null) + // const pass = new Test('pass', fn, defaultOpts, null) + // const secondPass = new Test('secondPass', fn, defaultOpts, null) + + // const sequentialParentPass = jest.fn() + // sequentialParent.on('pass', sequentialParentPass) + // const sequentialParentFail = jest.fn() + // sequentialParent.on('fail', sequentialParentFail) + // const sequentialTestPass = jest.fn() + // pass.on('pass', sequentialTestPass) + // const sequentialTestFail = jest.fn() + // fail.on('fail', sequentialTestFail) + + // sequentialParent.addChildren(pass, fail, secondPass) + + // expect((await sequentialParent.run({ bail: true, sequential: true })).status).toBe(Status.Failed) + // expect(sequentialParent.getStats().done).toBe(2) + // expect(sequentialTestPass).toHaveBeenCalledTimes(1) + // expect(sequentialTestFail).toHaveBeenCalledTimes(1) + // expect(sequentialParentPass).toHaveBeenCalledTimes(0) + // expect(sequentialParentFail).toHaveBeenCalledTimes(1) + // }) + // }) + + // it('should skip', async () => { + // const parent = new Suite('parent', { skip: true }, null) + + // const start = jest.fn() + // parent.on('start', start) + // const skip = jest.fn() + // parent.on('skip', skip) + // const end = jest.fn() + // parent.on('end', end) + + // const promise = parent.run() + + // expect(skip).toHaveBeenCalledTimes(1) + // expect(end).toHaveBeenCalledTimes(1) + // expect((await promise).status).toBe(Status.Skipped) + // }) + + // it('should invoke hooks', async () => { + // const error = console.error + // console.error = jest.fn() + + // const parent = new Suite('parent', {}, null) + // parent.addChildren( + // new Test('passing', jest.fn(), {}, parent), + // new Test('failing', () => { throw new Error('Fail') }, {}, parent), + // ) + // const calls: string[] = [] + + // parent.hooks.beforeAll.push( + // () => calls.push('beforeAll1'), + // () => calls.push('beforeAll2'), + // ) + // parent.hooks.beforeEach.push( + // async () => await calls.push('beforeEach1'), + // () => calls.push('beforeEach2'), + // () => { throw new Error('beforeEach hook error') }, + // ) + // parent.hooks.afterEach.push( + // () => calls.push('afterEach1'), + // () => calls.push('afterEach2'), + // ) + // parent.hooks.afterAll.push( + // () => calls.push('afterAll1'), + // async () => await calls.push('afterAll2'), + // async () => { throw new Error('afterAll hook error') }, + // ) + + // try { + // await parent.run() + // } catch { + // // noop + // } + // expect(calls).toMatchSnapshot() + + // expect(console.error).toHaveBeenCalledTimes(3) + // expect(console.error).toHaveBeenCalledWith('Error in beforeEach hook: Error: beforeEach hook error') + // expect(console.error).toHaveBeenCalledWith('Error in afterAll hook: Error: afterAll hook error') + + // console.error = error + // }) }) diff --git a/packages/core/test/test.spec.ts b/packages/core/test/test.spec.ts index 086b27e..3b8dbcd 100644 --- a/packages/core/test/test.spec.ts +++ b/packages/core/test/test.spec.ts @@ -1,18 +1,38 @@ import { Status } from '../src/result' -import Runnable from '../src/runnable' -import Suite from '../src/suite' -import Test, { isTest } from '../src/test' +import { Test as OnyxTest, Suite as OnyxSuite, isTest } from '../src' +import { RunStatus } from '../src/newResult' +import { TimeoutError } from '../src/test' describe('Test', () => { const defaultOpts = { skip: false, todo: false } + it('should timeout', async () => { + jest.useRealTimers() + + // fn function should not resolve before the timeout promise + const fn = () => new Promise((resolve) => { + setTimeout(() => { + resolve('Shouldn\'t resolve first') + }, 1500) + }) + + const onyxTest = new OnyxTest('test timeout', fn, defaultOpts, null) + + const timeoutResult = await onyxTest.run({ timeout: 1000 }) + + expect(timeoutResult.status).toBe(RunStatus.FAILED) + expect(timeoutResult.failures[0]).toStrictEqual(new TimeoutError(`${onyxTest.description} has timed out: 1000ms`)) + + jest.clearAllTimers() + }) + it('should return isDone', async () => { const fn = jest.fn() - const test = new Test('test', fn, defaultOpts, null) + const onyxTest = new OnyxTest('test isDone', fn, defaultOpts, null) - expect(test.isDone()).toBeFalsy() - await test.run() - expect(test.isDone()).toBeTruthy() + expect(onyxTest.isDone()).toBeFalsy() + await onyxTest.run() + expect(onyxTest.isDone()).toBeTruthy() }) it('should check if is test', () => { @@ -20,29 +40,17 @@ describe('Test', () => { expect(isTest(null)).toBeFalsy() expect(isTest({})).toBeFalsy() - expect(isTest(new Runnable('not a test', defaultOpts, null))).toBeFalsy() - expect(isTest(new Suite('not a test', defaultOpts, null))).toBeFalsy() - expect(isTest(new Test('a test', fn, defaultOpts, null))).toBeTruthy() + expect(isTest(new OnyxSuite('not a test', defaultOpts, null))).toBeFalsy() + expect(isTest(new OnyxTest('a test', fn, defaultOpts, null))).toBeTruthy() }) it('should run', async () => { const fn = jest.fn() - const test = new Test('test', fn, defaultOpts, null) - - const start = jest.fn() - test.on('start', start) - const pass = jest.fn() - test.on('pass', pass) - const end = jest.fn() - test.on('end', end) + const onyxTest = new OnyxTest('test run', fn, defaultOpts, null) expect(fn).toHaveBeenCalledTimes(0) - expect(await test.run()).toMatchSnapshot() + expect(await (await onyxTest.run()).status).toBe(Status.Passed) expect(fn).toHaveBeenCalledTimes(1) - - expect(start).toHaveBeenCalledTimes(1) - expect(pass).toHaveBeenCalledTimes(1) - expect(end).toHaveBeenCalledTimes(1) }) it('should fail', async () => { @@ -50,58 +58,15 @@ describe('Test', () => { const fn = () => { throw err } - const test = new Test('test', fn, defaultOpts, null) - - const start = jest.fn() - test.on('start', start) - const fail = jest.fn() - test.on('fail', fail) - const end = jest.fn() - test.on('end', end) - - expect((await test.run()).status).toBe(Status.Failed) - - expect(start).toHaveBeenCalledTimes(1) - expect(fail).toHaveBeenCalledTimes(1) - expect(end).toHaveBeenCalledTimes(1) + const onyxTest = new OnyxTest('test fail', fn, defaultOpts, null) + const result = await onyxTest.run() + expect(result.status).toBe(Status.Failed) + expect(result.failures[0]).toStrictEqual(new Error('Fatal error')) }) it('should skip', async () => { - const test = new Test('test', jest.fn(), { skip: true, todo: false}, null) + const onyxTest = new OnyxTest('test skip', jest.fn(), { skip: true, todo: false}, null) - expect((await test.run()).status).toBe('skipped') - }) - - it('should timeout', async () => { - jest.useRealTimers() - - // fn function should not resolve before the timeout promise - const fn = () => new Promise((resolve) => { - setTimeout(() => { - resolve('Shouldn\'t resolve first') - }, 3000) - }) - - const test = new Test('test', fn, defaultOpts, null) - - const end = jest.fn() - test.on('end', end) - const fail = jest.fn() - test.on('fail', fail) - const pass = jest.fn() - test.on('pass', pass) - const start = jest.fn() - test.on('start', start) - - const result = await test.run({ timeout: 1000 }) - - expect(result.status).toBe('failed') - expect(result.messages[0]).toBe(`Error: ${test.description} has timed out: 1000ms`) - expect(start).toHaveBeenCalledTimes(1) - expect(fail).toHaveBeenCalledTimes(1) - expect(end).toHaveBeenCalledTimes(1) - expect(pass).toHaveBeenCalledTimes(0) - - jest.clearAllTimers() + expect((await onyxTest.run()).status).toBe('skipped') }) }) From a8d77571b1910683ced364daa7e195ea75d4e39b Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 10 Jan 2022 11:13:30 -0800 Subject: [PATCH 22/37] test(core): skip files that have not been refactored. --- packages/core/test/interface.spec.ts | 2 +- packages/core/test/result.spec.ts | 62 ++++++++++++++-------------- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/core/test/interface.spec.ts b/packages/core/test/interface.spec.ts index 97dd12d..279df35 100644 --- a/packages/core/test/interface.spec.ts +++ b/packages/core/test/interface.spec.ts @@ -14,7 +14,7 @@ import Suite from '../src/suite' const noop = () => null -describe('Interface', () => { +describe.skip('Interface', () => { beforeEach(() => { root.children.length = 0 // clean up root to simplify snapshots }) diff --git a/packages/core/test/result.spec.ts b/packages/core/test/result.spec.ts index 1958b89..66f653f 100644 --- a/packages/core/test/result.spec.ts +++ b/packages/core/test/result.spec.ts @@ -1,42 +1,42 @@ -// import Result, { Status } from '../src/result' +import Result, { Status } from '../src/result' -// describe('Result', () => { -// it('should change status', () => { -// const result = new Result() +describe.skip('Result', () => { + // it('should change status', () => { + // const result = new Result() -// expect(result.status).toBe(Status.Pending) -// result.status = Status.Passed -// expect(result.status).toBe(Status.Passed) -// }) + // expect(result.status).toBe(Status.Pending) + // result.status = Status.Passed + // expect(result.status).toBe(Status.Passed) + // }) -// it('should set isDone', () => { -// const result = new Result() + // it('should set isDone', () => { + // const result = new Result() -// expect(result.isDone()).toBeFalsy() -// result.status = Status.Passed -// expect(result.isDone()).toBeTruthy() -// }) + // expect(result.isDone()).toBeFalsy() + // result.status = Status.Passed + // expect(result.isDone()).toBeTruthy() + // }) -// it('should work with messages', () => { -// const result = new Result(Status.Pending, 'Result') + // it('should work with messages', () => { + // const result = new Result(Status.Pending, 'Result') -// expect(result.messages).toHaveLength(1) -// result.addMessages('Test', 'Onyx') -// expect(result.messages).toHaveLength(3) -// }) + // expect(result.messages).toHaveLength(1) + // result.addMessages('Test', 'Onyx') + // expect(result.messages).toHaveLength(3) + // }) -// it('should lock up when done', () => { -// const result = new Result() + // it('should lock up when done', () => { + // const result = new Result() -// result.addMessages('Test', 'Onyx') -// expect(result.messages).toHaveLength(2) + // result.addMessages('Test', 'Onyx') + // expect(result.messages).toHaveLength(2) -// result.status = Status.Passed + // result.status = Status.Passed -// result.addMessages('Test', 'Onyx') -// expect(result.messages).toHaveLength(2) + // result.addMessages('Test', 'Onyx') + // expect(result.messages).toHaveLength(2) -// result.status = Status.Pending -// expect(result.status).toBe(Status.Passed) -// }) -// }) + // result.status = Status.Pending + // expect(result.status).toBe(Status.Passed) + // }) +}) From 3bb9ff905f0b40231877cfd2d043421a94d8452e Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 10 Jan 2022 20:40:47 -0800 Subject: [PATCH 23/37] refactor(test): rewrite test class. --- packages/core/jest.config.js | 2 +- packages/core/src/test.ts | 80 ++++----------------------------- packages/core/test/test.spec.ts | 9 ++-- 3 files changed, 16 insertions(+), 75 deletions(-) diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js index 2aca978..d9882dc 100644 --- a/packages/core/jest.config.js +++ b/packages/core/jest.config.js @@ -3,5 +3,5 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', roots: ['test/'], - // testMatch: ['**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec).[jt]s?(x)'], + testMatch: ['**/test/**/*.[jt]s?(x)', '**/?(*.)+(spec).[jt]s?(x)'], }; diff --git a/packages/core/src/test.ts b/packages/core/src/test.ts index fe67793..5c87b57 100644 --- a/packages/core/src/test.ts +++ b/packages/core/src/test.ts @@ -1,36 +1,13 @@ -import { Status } from '.' -import { RunStatus } from './newResult' -import Runnable, { RunnableOptions, RunnableResult, RunnableTypes } from './runnable' -import { RunOptions } from './runner' -import Suite from './suite' +// import { RunStatus } from './newResult' +import Runnable/*, { RunnableOptions, RunnableResult, RunnableTypes }*/ from './runnable' +// import { RunOptions } from './runner' +import type Suite from './suite' +import { TimeoutError } from './TimeoutError' -export type TestFn = () => (void | Promise) -// const TimeoutError = Symbol('TimeoutError') +// Types +import { RunnableOptions, RunOptions, RunnableResult, RunStatus, RunnableTypes, TestFn } from './types' -export class TimeoutError extends Error { - public constructor(message: string) { - super(message) - this.name = 'TimeoutError' - } -} - -function promiseWithTimeout( - promise: T, - ms: number, - timeoutError = new TimeoutError('Promise timed out') -) { - let timer: NodeJS.Timeout - - // create a promise that rejects in milliseconds - const timeout = new Promise((_, reject) => { - timer = setTimeout(() => { - reject(timeoutError) - }, ms) - }) - - // returns a race between timeout and the passed promise - return Promise.race([promise(), timeout]).finally(() => clearTimeout(timer)) -} +// export type TestFn = () => (void | Promise) /** * @description Checks if the passed `Runnable` value is a `Test` instance. @@ -77,50 +54,11 @@ export default class Test extends Runnable { this.doStart() try { - // const result = options && options.timeout ? await promiseWithTimeout(this.fn, options.timeout) : this.fn() - const result = options && options.timeout ? await this._timeout(this.fn, options.timeout) : await this.fn() - - // console.log('result: ', result) + options && options.timeout ? await this._timeout(this.fn, options.timeout) : await this.fn() return this.doPass() } catch (error: any) { - // console.log('error: ', (error as Error)) - // const err = error instanceof TimeoutError || error instanceof Error ? error : new Error(error.message) return this.doFail(error) } } - - // public async run2() { - // if (options && options.timeout) { - // let timer: NodeJS.Timeout - // const wait = (ms: number) => new Promise(resolve => { - // timer = setTimeout(resolve, ms) - // }) - - // const test = Promise.race([ - // wait(options.timeout).then(() => { - // clearTimeout(timer) - // throw new Error(`${this.getFullDescription()} has timed out: ${options.timeout}ms`) - // }), - // this.fn(), - // ]) - - // try { - // await test - // } catch (error) { - // return this.doFail(error) - // } - - // return this.doPass() - // } else { - // try { - // await this.fn() - // } catch (error) { - // return this.doFail(error) - // } - - // return this.doPass() - // } - // return {} as Promise; - // } } diff --git a/packages/core/test/test.spec.ts b/packages/core/test/test.spec.ts index 3b8dbcd..7591170 100644 --- a/packages/core/test/test.spec.ts +++ b/packages/core/test/test.spec.ts @@ -1,7 +1,10 @@ -import { Status } from '../src/result' +// import { Status } from '../src/result' import { Test as OnyxTest, Suite as OnyxSuite, isTest } from '../src' -import { RunStatus } from '../src/newResult' -import { TimeoutError } from '../src/test' +// import { RunStatus } from '../src/newResult' +import { TimeoutError } from '../src/TimeoutError' + +// Types +import { RunStatus, Status } from '../src/types' describe('Test', () => { const defaultOpts = { skip: false, todo: false } From 4eda7c388f0da5d0513689039f46d015d3bc35fe Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 10 Jan 2022 20:42:00 -0800 Subject: [PATCH 24/37] refactor(suite): rewrite run method. --- packages/core/src/suite.ts | 199 +++++++++++++++---------------- packages/core/test/suite.spec.ts | 9 +- 2 files changed, 101 insertions(+), 107 deletions(-) diff --git a/packages/core/src/suite.ts b/packages/core/src/suite.ts index f9ee67f..d8fb0af 100644 --- a/packages/core/src/suite.ts +++ b/packages/core/src/suite.ts @@ -1,5 +1,13 @@ import { HookName, Hooks } from './hooks' -import Runnable, { isRunnable, RunnableOptions, RunnableResult, RunnableTypes } from './runnable' +import Runnable, { isRunnable/*, RunnableOptions, RunnableResult, RunnableTypes*/ } from './runnable' +import Result from './result' + +// Utilities +import { BailError } from './BailError' +import { normalizeRunOptions } from './utils' + +// Types +import { RunnableOptions, RunOptions, RunnableResult, RunnableTypes, RunStatus, SuiteStats, Status } from './types' /** * @description Checks if passed value is a `Runnable` instance of type `Suite`. @@ -11,34 +19,24 @@ const isSuite = (v: unknown): v is Suite => { const rootSymbol = Symbol('isRoot') -export interface SuiteStats { - total: number - pending: number - running: number - done: number - passed: number - failed: number - skipped: number - todo: number - time: number -} - -export class BailError extends Error { - /* istanbul ignore next */ - constructor(message: string) { - super(message) - this.name = 'BailError' - } -} - -/* tslint:disable:max-classes-per-file */ +// export interface SuiteStats { +// total: number +// pending: number +// running: number +// done: number +// passed: number +// failed: number +// skipped: number +// todo: number +// time: number +// } export default class Suite extends Runnable { public children: Runnable[] public [rootSymbol]?: boolean public type = RunnableTypes.Suite public options: RunnableOptions public hooks: Hooks - private failed: number + private _failed: number /* istanbul ignore next */ constructor(description: string, options: Partial = {}, parent: Suite | null) { @@ -47,7 +45,6 @@ export default class Suite extends Runnable { ...Runnable.normalizeOptions(options), } this.children = [] - this.failed = 0 this.hooks = { afterAll: [], @@ -55,6 +52,47 @@ export default class Suite extends Runnable { beforeAll: [], beforeEach: [], } + this._failed = 0 + } + + private async _parallel(children: Array>, bail?: boolean): Promise { + try { + await Promise.all>( + children.map(async (promise) => { + const result = await promise + + if (bail && result) return this.doFail(new BailError(result.messages[0])) + return result + }) + ) + } catch (error: any) { + await this.invokeHook('afterAll') + return this.doFail(error) + } + } + + private async _sequential(children: Array>, bail?: boolean): Promise { + for (const promise of children) { + try { + const result = await promise + + if (bail && result) return this.doFail(new BailError(result.messages[0])) + } catch (error: any) { + return this.doFail(error) + } + } + } + + private _wrapChildren(children: Runnable[]): Array> { + return children.map((child: Runnable) => { + return (async () => { + await this.invokeHook('beforeEach') + const result = await child.run() + this.result.messages = [...this.result.messages, ...result.messages.map((msg) => `${child.description}: ${msg}`)] + await this.invokeHook('afterEach') + return result + })() + }) } /** @@ -78,7 +116,7 @@ export default class Suite extends Runnable { for (const child of children) { child.parent = this } - this.children.push(...children) + this.children = [...this.children, ...children] } /** @@ -91,67 +129,22 @@ export default class Suite extends Runnable { /** * @description Runs a `Suite` instance. */ - // public async run(options?: Partial): Promise { - // options = normalizeRunOptions(options) - - // if (this.options.skip || this.options.todo) { - // return this.doSkip(this.options.todo) - // } - - // this.doStart() - // await this.invokeHook('beforeAll') - - // const promises: Array> = [] - // for (const child of this.children) { - // promises.push((async () => { - // await this.invokeHook('beforeEach') - // const result = await child.run() - // this.result.addMessages(...result.messages.map((m) => `${child.description}: ${m}`)) - // await this.invokeHook('afterEach') - - // if (result.status === Status.Failed) ++this.failed - - // return result - // })()) - // } - - // if (options.sequential) { - // for (const promise of promises) { - // try { - // const result = await promise - - // if (options.bail && result) { - // throw new BailError(result.messages[0]) - // } - // } catch (error) { - // await this.invokeHook('afterAll') - // return this.doFail(error) - // } - // } - // } else { - // try { - // await Promise.all(promises.map(async (promise) => { - // const result = await promise - - // if (options && options.bail && result) { - // throw new BailError(result.messages[0]) - // } - // })) - // } catch (error) { - // await this.invokeHook('afterAll') - // return this.doFail(error) - // } - // } - - // await this.invokeHook('afterAll') - // if (this.failed) { - // return this.doFail() - // } - // return this.doPass() - // } - - public async run () { - return {} as Promise + public async run(options?: Partial): Promise { + options = normalizeRunOptions(options) + + if (this.options.skip || this.options.todo) return this.doSkip(this.options.skip ? RunStatus.SKIPPED : RunStatus.TODO) + + this.doStart() + await this.invokeHook('beforeAll') + + const promisifiedChildren = this._wrapChildren(this.children) + + if (options.sequential) await this._sequential(promisifiedChildren, options.bail) + else await this._parallel(promisifiedChildren, options.bail) + + await this.invokeHook('afterAll') + if (this._failed) return this.doFail(new Error(`${this.description} ${Status.Failed}`)) + else return this.doPass() } /** @@ -159,19 +152,17 @@ export default class Suite extends Runnable { */ public getStats(): SuiteStats { const childrenList = this.flatten(this.children) - // return { - // done: childrenList.filter((c) => c.result.isDone()).length, - // failed: childrenList.filter((c) => c.result.status === Status.Failed).length, - // passed: childrenList.filter((c) => c.result.status === Status.Passed).length, - // pending: childrenList.filter((c) => c.result.status === Status.Pending).length, - // running: childrenList.filter((c) => c.result.status === Status.Running).length, - // skipped: childrenList.filter((c) => c.result.status === Status.Skipped).length, - // time: this.time, - // todo: childrenList.filter((c) => c.result.status === Status.Todo).length, - // total: childrenList.length, - // } - - return {} as SuiteStats + return { + done: childrenList.filter((c) => c.isDone()).length, + failed: childrenList.filter((c) => c.result.status === RunStatus.FAILED).length, + passed: childrenList.filter((c) => c.result.status === RunStatus.PASSED).length, + pending: childrenList.filter((c) => c.result.status === RunStatus.PENDING).length, + running: childrenList.filter((c) => c.result.status === RunStatus.RUNNING).length, + skipped: childrenList.filter((c) => c.result.status === RunStatus.SKIPPED).length, + time: this.time, + todo: childrenList.filter((c) => c.result.status === RunStatus.TODO).length, + total: childrenList.length, + } } /** @@ -180,7 +171,7 @@ export default class Suite extends Runnable { private flatten(array: Runnable[]): Runnable[] { const flatTree: Runnable[] = [] for (const child of array) { - if (isSuite(child)) { + if ((isSuite(child))) { flatTree.push(...this.flatten(child.children)) continue } @@ -191,7 +182,7 @@ export default class Suite extends Runnable { } } -export { - isSuite, - rootSymbol, -} +// export { +// // isSuite, +// rootSymbol, +// } diff --git a/packages/core/test/suite.spec.ts b/packages/core/test/suite.spec.ts index 4349b11..bc49a53 100644 --- a/packages/core/test/suite.spec.ts +++ b/packages/core/test/suite.spec.ts @@ -1,7 +1,10 @@ -import { Status } from '../src/result' -import Runnable from '../src/runnable' +// import { Status } from '../src/result' +// import Runnable from '../src/runnable' import Suite, { rootSymbol } from '../src/suite' -import Test from '../src/test' +// import Test from '../src/test' + +// Types +// import { Status } from '../src/types' describe('Suite', () => { const defaultOpts = { From 097457c52b8039f3976f759c50ae0cd623eb25aa Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 10 Jan 2022 20:44:45 -0800 Subject: [PATCH 25/37] chore(core): remove utils.ts and types.ts. --- packages/core/src/types.ts | 41 -------------------------------------- packages/core/src/utils.ts | 18 ----------------- 2 files changed, 59 deletions(-) delete mode 100644 packages/core/src/types.ts delete mode 100644 packages/core/src/utils.ts diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts deleted file mode 100644 index e27d77f..0000000 --- a/packages/core/src/types.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { AnyMatchers, ExpectError } from '@onyx/matchers' - -type OmitFirstArg = A extends (x: any, ...args: infer P) => infer R ? (...args: P) => R : never - -type Expectations = { [K in keyof M]: OmitFirstArg } - -export type NegatedExpectations = Expectations & { - not: Expectations -} - - -export type ExpectResult = { - matcher: string - error?: ExpectError - status: ExpectStatus - actual: A - expected: E -} - -enum ExpectStatus { - PASS = 'Pass', - FAIL = 'Fail', -} - -const _expectFail = (fail: F): ExpectationResult => ({ _status: ExpectStatus.FAIL, fail }) -const _expectPass = (pass: P): ExpectationResult => ({ _status: ExpectStatus.PASS, pass }) - -export const expectPass: (pass: P) => ExpectationResult = _expectPass -export const expectFail: (fail: F) => ExpectationResult = _expectFail - -interface IExpectFail { - readonly _status: ExpectStatus.FAIL - readonly fail: F -} - -interface IExpectPass

{ - readonly _status: ExpectStatus.PASS - readonly pass: P -} - -type ExpectationResult = IExpectFail | IExpectPass

diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts deleted file mode 100644 index e8c875d..0000000 --- a/packages/core/src/utils.ts +++ /dev/null @@ -1,18 +0,0 @@ -export function promisifyTimeoutFn( - // error: Error = new Error(), - ms: number, - testFn: () => Promise, - timer: NodeJS.Timeout, -) { - const wait = new Promise(resolve => { - timer = setTimeout(resolve, ms) - }) - - return Promise.race([ - wait.then(() => { - clearTimeout(timer) - throw new Error() - }), - testFn(), - ]) -} From 554c493d003c96ad4fb7d16c67b8a2443995a522 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 10 Jan 2022 20:46:57 -0800 Subject: [PATCH 26/37] feat(utils): add utils. --- packages/core/src/utils/index.ts | 35 +++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index a7e2e6e..e8fcce1 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -1 +1,34 @@ -// export * as typeguards from './typeguards' +// Types +import { RunOptions } from '../types' + +const RunnerDefaults: RunOptions = { + bail: false, + sequential: false, + timeout: 10000, +} + +export function normalizeRunOptions(options: Partial = {}): RunOptions { + return { + ...RunnerDefaults, + ...options, + } +} + +export function promisifyTimeoutFn( + // error: Error = new Error(), + ms: number, + testFn: () => Promise, + timer: NodeJS.Timeout, +) { + const wait = new Promise(resolve => { + timer = setTimeout(resolve, ms) + }) + + return Promise.race([ + wait.then(() => { + clearTimeout(timer) + throw new Error() + }), + testFn(), + ]) +} From 844363ce7debc7035c5db54f3ec3367a2ddbaeb8 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 10 Jan 2022 20:50:54 -0800 Subject: [PATCH 27/37] feat(errors): add internal error classes. --- packages/core/src/BailError.ts | 7 +++++++ packages/core/src/TimeoutError.ts | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 packages/core/src/BailError.ts create mode 100644 packages/core/src/TimeoutError.ts diff --git a/packages/core/src/BailError.ts b/packages/core/src/BailError.ts new file mode 100644 index 0000000..299eed6 --- /dev/null +++ b/packages/core/src/BailError.ts @@ -0,0 +1,7 @@ +export class BailError extends Error { + /* istanbul ignore next */ + constructor(message: string) { + super(message) + this.name = 'BailError' + } +} diff --git a/packages/core/src/TimeoutError.ts b/packages/core/src/TimeoutError.ts new file mode 100644 index 0000000..59ddf63 --- /dev/null +++ b/packages/core/src/TimeoutError.ts @@ -0,0 +1,6 @@ +export class TimeoutError extends Error { + public constructor(message: string) { + super(message) + this.name = 'TimeoutError' + } +} From 1abd6bd21b7e69cdf5e745b181a237d00d18c4f6 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 10 Jan 2022 20:51:33 -0800 Subject: [PATCH 28/37] feat(types): add types directory. --- packages/core/src/types/Hooks.ts | 9 +++++++ packages/core/src/types/Result.ts | 38 +++++++++++++++++++++++++++++ packages/core/src/types/Runnable.ts | 10 ++++++++ packages/core/src/types/Runner.ts | 5 ++++ packages/core/src/types/Suite.ts | 11 +++++++++ packages/core/src/types/Test.ts | 1 + packages/core/src/types/index.ts | 11 +++++++++ 7 files changed, 85 insertions(+) create mode 100644 packages/core/src/types/Hooks.ts create mode 100644 packages/core/src/types/Result.ts create mode 100644 packages/core/src/types/Runnable.ts create mode 100644 packages/core/src/types/Runner.ts create mode 100644 packages/core/src/types/Suite.ts create mode 100644 packages/core/src/types/Test.ts create mode 100644 packages/core/src/types/index.ts diff --git a/packages/core/src/types/Hooks.ts b/packages/core/src/types/Hooks.ts new file mode 100644 index 0000000..dd958a9 --- /dev/null +++ b/packages/core/src/types/Hooks.ts @@ -0,0 +1,9 @@ +export type HookFn = (() => void) | (() => Promise) +export type Hook = HookFn[] +export interface Hooks { + beforeAll: Hook + afterAll: Hook + beforeEach: Hook + afterEach: Hook +} +export type HookName = keyof Hooks diff --git a/packages/core/src/types/Result.ts b/packages/core/src/types/Result.ts new file mode 100644 index 0000000..d8a2c8a --- /dev/null +++ b/packages/core/src/types/Result.ts @@ -0,0 +1,38 @@ +// Types +import { Hooks } from './Hooks' + +export enum RunStatus { + PENDING = 'pending', + RUNNING = 'running', + PASSED = 'passed', + FAILED = 'failed', + SKIPPED = 'skipped', + TODO = 'todo', +} + +export type _RunStatus = keyof typeof RunStatus + +export type BaseResult = { + messages: Array + failures: Array + hooks: Hooks + status: RunStatus + fullDescription: string +} + +export interface RunnableResult extends BaseResult { + id: string + description: string + time: number +} + +export enum Status { + Pending = 'pending', + Running = 'running', + Passed = 'passed', + Failed = 'failed', + Skipped = 'skipped', + Todo = 'todo', +} + +export type ResultStatus = keyof typeof Status diff --git a/packages/core/src/types/Runnable.ts b/packages/core/src/types/Runnable.ts new file mode 100644 index 0000000..a081b42 --- /dev/null +++ b/packages/core/src/types/Runnable.ts @@ -0,0 +1,10 @@ +export enum RunnableTypes { + Runnable = 'runnable', + Suite = 'suite', + Test = 'test', +} + +export interface RunnableOptions { + skip: boolean + todo: boolean +} diff --git a/packages/core/src/types/Runner.ts b/packages/core/src/types/Runner.ts new file mode 100644 index 0000000..9172bbc --- /dev/null +++ b/packages/core/src/types/Runner.ts @@ -0,0 +1,5 @@ +export interface RunOptions { + bail: boolean + sequential: boolean + timeout: number +} diff --git a/packages/core/src/types/Suite.ts b/packages/core/src/types/Suite.ts new file mode 100644 index 0000000..08248fb --- /dev/null +++ b/packages/core/src/types/Suite.ts @@ -0,0 +1,11 @@ +export interface SuiteStats { + total: number + pending: number + running: number + done: number + passed: number + failed: number + skipped: number + todo: number + time: number +} diff --git a/packages/core/src/types/Test.ts b/packages/core/src/types/Test.ts new file mode 100644 index 0000000..23f3b92 --- /dev/null +++ b/packages/core/src/types/Test.ts @@ -0,0 +1 @@ +export type TestFn = () => (void | Promise) diff --git a/packages/core/src/types/index.ts b/packages/core/src/types/index.ts new file mode 100644 index 0000000..3802582 --- /dev/null +++ b/packages/core/src/types/index.ts @@ -0,0 +1,11 @@ +export { BaseResult, RunnableResult, _RunStatus, RunStatus, Status, ResultStatus } from './Result' + +export { Hook, HookFn, Hooks } from './Hooks' + +export { RunnableOptions, RunnableTypes } from './Runnable' + +export { RunOptions } from './Runner' + +export { TestFn } from './Test' + +export { SuiteStats } from './Suite' From 3fbd310cd8e5473f1c06f741332a17449dfd0b15 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 10 Jan 2022 20:52:58 -0800 Subject: [PATCH 29/37] chore(snapshots): remove old snapshots. --- .../core/test/__snapshots__/suite.spec.ts.snap | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 packages/core/test/__snapshots__/suite.spec.ts.snap diff --git a/packages/core/test/__snapshots__/suite.spec.ts.snap b/packages/core/test/__snapshots__/suite.spec.ts.snap deleted file mode 100644 index 29f45e1..0000000 --- a/packages/core/test/__snapshots__/suite.spec.ts.snap +++ /dev/null @@ -1,18 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Suite should invoke hooks 1`] = ` -Array [ - "beforeAll1", - "beforeAll2", - "beforeEach1", - "beforeEach1", - "beforeEach2", - "beforeEach2", - "afterEach1", - "afterEach1", - "afterEach2", - "afterEach2", - "afterAll1", - "afterAll2", -] -`; From 384b73b2b048639c3e38dc9e1155ecafd2099001 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 10 Jan 2022 20:55:47 -0800 Subject: [PATCH 30/37] chore(core): WIP, circular dependencies from core class imports. --- packages/core/src/index.ts | 8 ++--- packages/core/src/newResult.ts | 36 ++++++++++----------- packages/core/src/result.ts | 22 +++++++------ packages/core/src/runnable.ts | 50 ++++++++++++++++------------- packages/core/src/runner.ts | 10 +++--- packages/core/test/runnable.spec.ts | 4 ++- 6 files changed, 70 insertions(+), 60 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7b29a63..a7658d0 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -14,9 +14,9 @@ export { beforeEach, afterEach, } from './interface' -export { Status, default as Result } from './result' -export { default as Runnable, isRunnable, RunnableTypes } from './runnable' +export { /*Status,*/ default as Result } from './result' +export { default as Runnable, isRunnable, /*RunnableTypes*/ } from './runnable' export { default as Test, isTest } from './test' -export { default as Suite, isSuite, SuiteStats, rootSymbol, BailError } from './suite' -export { RunOptions } from './runner' +export { default as Suite, isSuite, /*SuiteStats,*/ rootSymbol/*, BailError*/ } from './suite' +// export { RunOptions } from './runner' export { Hook, HookName, HookFn, Hooks } from './hooks' diff --git a/packages/core/src/newResult.ts b/packages/core/src/newResult.ts index 43d6e01..d00efe4 100644 --- a/packages/core/src/newResult.ts +++ b/packages/core/src/newResult.ts @@ -1,20 +1,20 @@ -import { Runnable, Suite, Test } from "." -import { Hooks } from "./hooks" -import { RunnableTypes } from "./runnable" +// import { Runnable, Suite, Test } from "." +// import { Hooks } from "./hooks" +// import { RunnableTypes } from "./runnable" -export enum RunStatus { - PENDING = 'pending', - RUNNING = 'running', - PASSED = 'passed', - FAILED = 'failed', - SKIPPED = 'skipped', - TODO = 'todo', -} +// export enum RunStatus { +// PENDING = 'pending', +// RUNNING = 'running', +// PASSED = 'passed', +// FAILED = 'failed', +// SKIPPED = 'skipped', +// TODO = 'todo', +// } -export type BaseResult = { - messages: Array - failures: Array - hooks: Hooks - status: RunStatus - fullDescription: string -} +// export type BaseResult = { +// messages: Array +// failures: Array +// hooks: Hooks +// status: RunStatus +// fullDescription: string +// } diff --git a/packages/core/src/result.ts b/packages/core/src/result.ts index f801dfd..78f926e 100644 --- a/packages/core/src/result.ts +++ b/packages/core/src/result.ts @@ -1,13 +1,17 @@ -import { RunnableTypes } from "." +// import { RunnableTypes } from "."\ -export enum Status { - Pending = 'pending', - Running = 'running', - Passed = 'passed', - Failed = 'failed', - Skipped = 'skipped', - Todo = 'todo', -} + +// export enum Status { +// Pending = 'pending', +// Running = 'running', +// Passed = 'passed', +// Failed = 'failed', +// Skipped = 'skipped', +// Todo = 'todo', +// } + +// Types +import { RunnableTypes, Status } from './types' /** * @todo Delete messages. diff --git a/packages/core/src/runnable.ts b/packages/core/src/runnable.ts index 8ddcbf5..ca27711 100644 --- a/packages/core/src/runnable.ts +++ b/packages/core/src/runnable.ts @@ -1,9 +1,13 @@ import { performance } from 'perf_hooks' -import { Test } from '.' -import { Hooks } from './hooks' -import { RunStatus, BaseResult } from './newResult' -import Result, { Status } from './result' -import Suite from './suite' +import type { Hooks } from './hooks' +// import { RunStatus/*, BaseResult*/ } from './newResult' +// import Result, { Status } from './result' +import type Suite from './suite' +import type Result from './result' +import type Test from './test' + +// Types +import { RunnableOptions, RunnableResult, RunStatus, RunnableTypes } from './types' export const runnableSymbol = Symbol('isRunnable') @@ -15,26 +19,26 @@ export const isRunnable = (v: unknown): v is Runnable => { else { return false } } -export const isRunnable2 = (v: unknown): v is R => { - return (v instanceof Runnable) || (v instanceof Test) || (v instanceof Suite) -} +// export const isRunnable2 = (v: unknown): v is R => { +// return (v instanceof Runnable) || (v instanceof Test) || (v instanceof Suite) +// } -export enum RunnableTypes { - Runnable = 'runnable', - Suite = 'suite', - Test = 'test', -} +// export enum RunnableTypes { +// Runnable = 'runnable', +// Suite = 'suite', +// Test = 'test', +// } -export interface RunnableOptions { - skip: boolean - todo: boolean -} +// export interface RunnableOptions { +// skip: boolean +// todo: boolean +// } -export interface RunnableResult extends BaseResult { - id: string - description: string - time: number -} +// export interface RunnableResult extends BaseResult { +// id: string +// description: string +// time: number +// } const DEFAULT_RESULT: RunnableResult = { id: '', @@ -82,7 +86,7 @@ export default abstract class Runnable { /** * @description Run a `Runnable` instance. */ - public abstract run(): Promise + public abstract run(): Promise /** diff --git a/packages/core/src/runner.ts b/packages/core/src/runner.ts index adfa0b1..efc46aa 100644 --- a/packages/core/src/runner.ts +++ b/packages/core/src/runner.ts @@ -1,10 +1,10 @@ // import Suite, { SuiteStats } from './suite' -export interface RunOptions { - bail: boolean - sequential: boolean - timeout: number -} +// export interface RunOptions { +// bail: boolean +// sequential: boolean +// timeout: number +// } // /** // * @description Default runner options // */ diff --git a/packages/core/test/runnable.spec.ts b/packages/core/test/runnable.spec.ts index a596617..564c6d2 100644 --- a/packages/core/test/runnable.spec.ts +++ b/packages/core/test/runnable.spec.ts @@ -1,6 +1,8 @@ -import { RunStatus } from '../src/newResult' +// import { RunStatus } from '../src/newResult' import { rootSymbol, Suite, Runnable, isRunnable } from '../src' +import { RunStatus } from '../src/types' + class OnyxRunnable extends Runnable { async run(shouldThrow = false) { try { From d5fecdcaa0af9d938aaf89b79f7bd07de65dc831 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Mon, 10 Jan 2022 20:57:08 -0800 Subject: [PATCH 31/37] fix(suite): export isSuite and rootSymbol. --- packages/core/src/suite.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/suite.ts b/packages/core/src/suite.ts index d8fb0af..903d3cf 100644 --- a/packages/core/src/suite.ts +++ b/packages/core/src/suite.ts @@ -12,12 +12,12 @@ import { RunnableOptions, RunOptions, RunnableResult, RunnableTypes, RunStatus, /** * @description Checks if passed value is a `Runnable` instance of type `Suite`. */ -const isSuite = (v: unknown): v is Suite => { +export const isSuite = (v: unknown): v is Suite => { if (!isRunnable(v)) { return false } else return v.type === RunnableTypes.Suite } -const rootSymbol = Symbol('isRoot') +export const rootSymbol = Symbol('isRoot') // export interface SuiteStats { // total: number From 0200e1619bdcce53735b54aa35991c17b866dae0 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Tue, 11 Jan 2022 09:40:20 -0800 Subject: [PATCH 32/37] test(Test): fix should fail. --- packages/core/src/test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/test.ts b/packages/core/src/test.ts index 5c87b57..76f7b19 100644 --- a/packages/core/src/test.ts +++ b/packages/core/src/test.ts @@ -54,7 +54,7 @@ export default class Test extends Runnable { this.doStart() try { - options && options.timeout ? await this._timeout(this.fn, options.timeout) : await this.fn() + const result = options && options.timeout ? await this._timeout(this.fn, options.timeout) : await this.fn() return this.doPass() } catch (error: any) { From 608541227569eddd37f287a8381b419cd2016c18 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Tue, 11 Jan 2022 09:45:40 -0800 Subject: [PATCH 33/37] feat(runnable): add isStatus method. --- packages/core/src/runnable.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/core/src/runnable.ts b/packages/core/src/runnable.ts index ca27711..d93f962 100644 --- a/packages/core/src/runnable.ts +++ b/packages/core/src/runnable.ts @@ -7,7 +7,7 @@ import type Result from './result' import type Test from './test' // Types -import { RunnableOptions, RunnableResult, RunStatus, RunnableTypes } from './types' +import { RunnableOptions, RunnableResult, RunStatus, RunnableTypes, RunOptions } from './types' export const runnableSymbol = Symbol('isRunnable') @@ -86,7 +86,7 @@ export default abstract class Runnable { /** * @description Run a `Runnable` instance. */ - public abstract run(): Promise + public abstract run(options: Partial): Promise /** @@ -122,6 +122,7 @@ export default abstract class Runnable { */ public doFail(error: Error) { this.result.failures = [...this.result.failures, error] + this.result.messages = [...this.result.messages, error.message] this.result.status = RunStatus.FAILED this.doEnd() @@ -145,6 +146,13 @@ export default abstract class Runnable { return this.result.status !== RunStatus.PENDING && this.result.status !== RunStatus.RUNNING } + /** + * @description Check if the current status is the same as the status argument passed in. + */ + public isStatus(status: RunStatus): boolean { + return this.result.status === status + } + /** * @description Concatenate the Parent's description and the current `Runnable`'s description. */ From 45ec3b305b280d043100d9906e0a10173dff2e6a Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Tue, 11 Jan 2022 15:57:30 -0800 Subject: [PATCH 34/37] refactor(result): remove properties, update addErrors method. --- packages/core/src/BailError.ts | 5 ++++- packages/core/src/TimeoutError.ts | 4 ++++ packages/core/src/newResult.ts | 20 -------------------- packages/core/src/result-spec.md | 17 ----------------- packages/core/src/result.ts | 10 ++++++---- 5 files changed, 14 insertions(+), 42 deletions(-) delete mode 100644 packages/core/src/newResult.ts delete mode 100644 packages/core/src/result-spec.md diff --git a/packages/core/src/BailError.ts b/packages/core/src/BailError.ts index 299eed6..adc12e1 100644 --- a/packages/core/src/BailError.ts +++ b/packages/core/src/BailError.ts @@ -1,7 +1,10 @@ export class BailError extends Error { - /* istanbul ignore next */ constructor(message: string) { super(message) this.name = 'BailError' } } + +function createBailError(message: string): BailError { + return new BailError(message) +} diff --git a/packages/core/src/TimeoutError.ts b/packages/core/src/TimeoutError.ts index 59ddf63..f35107f 100644 --- a/packages/core/src/TimeoutError.ts +++ b/packages/core/src/TimeoutError.ts @@ -4,3 +4,7 @@ export class TimeoutError extends Error { this.name = 'TimeoutError' } } + +function createTimeoutError(message: string): TimeoutError { + return new TimeoutError(message) +} diff --git a/packages/core/src/newResult.ts b/packages/core/src/newResult.ts deleted file mode 100644 index d00efe4..0000000 --- a/packages/core/src/newResult.ts +++ /dev/null @@ -1,20 +0,0 @@ -// import { Runnable, Suite, Test } from "." -// import { Hooks } from "./hooks" -// import { RunnableTypes } from "./runnable" - -// export enum RunStatus { -// PENDING = 'pending', -// RUNNING = 'running', -// PASSED = 'passed', -// FAILED = 'failed', -// SKIPPED = 'skipped', -// TODO = 'todo', -// } - -// export type BaseResult = { -// messages: Array -// failures: Array -// hooks: Hooks -// status: RunStatus -// fullDescription: string -// } diff --git a/packages/core/src/result-spec.md b/packages/core/src/result-spec.md deleted file mode 100644 index 29a3d7e..0000000 --- a/packages/core/src/result-spec.md +++ /dev/null @@ -1,17 +0,0 @@ -# Result - - - Returns from a Runnable instances run method. (Suite | Test) - - ```ts -// Base result interface -interface Result { - id: string - description: string - filePath: string - failures: { [key: Result[id]]: Error } - status: RunResultStatus - time: number - title: string - type: RunnableType -} - ``` diff --git a/packages/core/src/result.ts b/packages/core/src/result.ts index 78f926e..8494712 100644 --- a/packages/core/src/result.ts +++ b/packages/core/src/result.ts @@ -11,6 +11,8 @@ // } // Types +import { BailError } from './BailError' +import { TimeoutError } from './TimeoutError' import { RunnableTypes, Status } from './types' /** @@ -24,7 +26,6 @@ export default class Result { public time: number public title: string public description: string - public filePath: string public type: RunnableTypes constructor(messages: string | string[] = [], options: Result, errors: Error[] | Error = [], status?: Status) { @@ -33,7 +34,6 @@ export default class Result { this._internalMessages = !Array.isArray(messages) ? [ messages ] : messages this.description = options.description - this.filePath = options.filePath this.time = options.time this.title = options.title this.type = options.type @@ -77,10 +77,12 @@ export default class Result { */ public addMessages(...messages: string[]): void { if (this.isDone()) { return } - this._internalMessages.push(...messages) + this._internalMessages = [...this._internalMessages, ...messages] } public addErrors(...errors: Error[]): void { - this._internalErrors = [ ...this._internalErrors, ...errors ] + if (this.isDone()) { return } + this.addMessages(...errors.map((e) => e.message)) + this._internalErrors = [...this._internalErrors, ...errors] } } From aeff41d53780f56b64a42ff26444ef56b0f54452 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Tue, 11 Jan 2022 16:42:19 -0800 Subject: [PATCH 35/37] refactor(runnable): add isPending method, update concatenated return from getFullDescription. --- packages/core/src/runnable.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/core/src/runnable.ts b/packages/core/src/runnable.ts index d93f962..fe13c78 100644 --- a/packages/core/src/runnable.ts +++ b/packages/core/src/runnable.ts @@ -71,7 +71,6 @@ export default abstract class Runnable { public type: RunnableTypes = RunnableTypes.Runnable public [runnableSymbol] = true - public time = 0 public start = 0 /* istanbul ignore next */ @@ -153,12 +152,16 @@ export default abstract class Runnable { return this.result.status === status } + public isPending(): boolean { + return this.result.status === RunStatus.PENDING || (this.parent !== null && this.parent.isStatus(RunStatus.PENDING)) + } + /** * @description Concatenate the Parent's description and the current `Runnable`'s description. */ public getFullDescription(): string { if (this.parent && !this.parent.isRoot()) { - return `${this.parent.getFullDescription()} -> ${this.description}` + return `${this.parent.getFullDescription()} ${this.description}` } return this.description } From da3564325396f3459729338b646e4b8587305c15 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Tue, 11 Jan 2022 22:50:29 -0800 Subject: [PATCH 36/37] refactor(result): make result type generic based on test type. add createResult function. --- packages/core/src/result.ts | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/core/src/result.ts b/packages/core/src/result.ts index 8494712..f2c4a56 100644 --- a/packages/core/src/result.ts +++ b/packages/core/src/result.ts @@ -14,28 +14,33 @@ import { BailError } from './BailError' import { TimeoutError } from './TimeoutError' import { RunnableTypes, Status } from './types' +type ResultOptions = { + description: string, + time: number, + type: T, +} + +type RunnableError = Error | TimeoutError | BailError /** * @todo Delete messages. */ -export default class Result { +class Result { private _internalErrors: Error[] private _internalStatus: Status private _internalMessages: string[] - + public time: number - public title: string public description: string - public type: RunnableTypes + public type: T - constructor(messages: string | string[] = [], options: Result, errors: Error[] | Error = [], status?: Status) { + constructor(messages: string | string[] = [], options: ResultOptions, errors: RunnableError[] | RunnableError = [], status?: Status) { this._internalErrors = !Array.isArray(errors) ? [ errors ] : errors this._internalStatus = status || Status.Pending this._internalMessages = !Array.isArray(messages) ? [ messages ] : messages this.description = options.description this.time = options.time - this.title = options.title this.type = options.type } @@ -86,3 +91,9 @@ export default class Result { this._internalErrors = [...this._internalErrors, ...errors] } } + +export function createResult(messages: string | string[] = [], options: ResultOptions, errors: RunnableError[] | RunnableError = [], status?: Status): Result { + return new Result(messages, options, errors, status) +} + +export default Result From e4e5e68c330c84443cb10d8396d05eddec93fab9 Mon Sep 17 00:00:00 2001 From: Elijah Kotyluk Date: Wed, 12 Jan 2022 00:30:25 -0800 Subject: [PATCH 37/37] test(result): reintroduce result class tests. --- packages/core/src/result.ts | 8 +-- packages/core/test/result.spec.ts | 81 +++++++++++++++---------------- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/packages/core/src/result.ts b/packages/core/src/result.ts index f2c4a56..6f065e6 100644 --- a/packages/core/src/result.ts +++ b/packages/core/src/result.ts @@ -15,9 +15,9 @@ import { BailError } from './BailError' import { TimeoutError } from './TimeoutError' import { RunnableTypes, Status } from './types' type ResultOptions = { - description: string, - time: number, - type: T, + description: string + time: number + type: T } type RunnableError = Error | TimeoutError | BailError @@ -92,7 +92,7 @@ class Result { } } -export function createResult(messages: string | string[] = [], options: ResultOptions, errors: RunnableError[] | RunnableError = [], status?: Status): Result { +export function createResult(options: ResultOptions, messages: string | string[] = [], errors: RunnableError[] | RunnableError = [], status?: Status): Result { return new Result(messages, options, errors, status) } diff --git a/packages/core/test/result.spec.ts b/packages/core/test/result.spec.ts index 66f653f..3e74178 100644 --- a/packages/core/test/result.spec.ts +++ b/packages/core/test/result.spec.ts @@ -1,42 +1,41 @@ -import Result, { Status } from '../src/result' - -describe.skip('Result', () => { - // it('should change status', () => { - // const result = new Result() - - // expect(result.status).toBe(Status.Pending) - // result.status = Status.Passed - // expect(result.status).toBe(Status.Passed) - // }) - - // it('should set isDone', () => { - // const result = new Result() - - // expect(result.isDone()).toBeFalsy() - // result.status = Status.Passed - // expect(result.isDone()).toBeTruthy() - // }) - - // it('should work with messages', () => { - // const result = new Result(Status.Pending, 'Result') - - // expect(result.messages).toHaveLength(1) - // result.addMessages('Test', 'Onyx') - // expect(result.messages).toHaveLength(3) - // }) - - // it('should lock up when done', () => { - // const result = new Result() - - // result.addMessages('Test', 'Onyx') - // expect(result.messages).toHaveLength(2) - - // result.status = Status.Passed - - // result.addMessages('Test', 'Onyx') - // expect(result.messages).toHaveLength(2) - - // result.status = Status.Pending - // expect(result.status).toBe(Status.Passed) - // }) +import Result, { createResult } from '../src/result' +import { RunnableTypes, Status } from '../src/types' + +describe('Result', () => { + let result: Result + + beforeEach(() => { + result = createResult({ description: 'testResult', time: 0, type: RunnableTypes.Runnable }) + }) + + it('should change status', () => { + expect(result.status).toBe(Status.Pending) + result.status = Status.Passed + expect(result.status).toBe(Status.Passed) + }) + + it('should set isDone', () => { + expect(result.isDone()).toBeFalsy() + result.status = Status.Passed + expect(result.isDone()).toBeTruthy() + }) + + it('should work with messages', () => { + expect(result.messages).toStrictEqual([]) + result.addMessages('Test', 'Onyx') + expect(result.messages).toHaveLength(2) + }) + + it('should lock up when done', () => { + result.addMessages('Test', 'Onyx') + expect(result.messages).toHaveLength(2) + + result.status = Status.Passed + + result.addMessages('Test', 'Onyx') + expect(result.messages).toHaveLength(2) + + result.status = Status.Pending + expect(result.status).toBe(Status.Passed) + }) })