UNPKG

@pmoo/testy

Version:

A minimal testing framework, for educational purposes.

306 lines (239 loc) 5.83 kB
/*eslint max-lines: 'off'*/ import { detectUserCallingLocation, subclassResponsibility } from '../utils/index.js'; import { InvalidAssertionError } from '../errors.js'; import { I18nMessage } from '../i18n/i18n_messages.js'; /** * I represent all the possible results for a test, that can be: * - **success**: means that all the assertions on a test were successful * - **failure**: one of the test assertions failed, or the user triggered an explicit failure * - **error**: an unexpected exception occurred during the execution of the test * - **pending**: the user marked the test as such * - **skipped**: the test was not executed in the current run * - **explicitly skipped**: the user chose not to run the test */ export class TestResult { // instance creation messages static success() { return new TestSucceeded(); } static failure(description) { return new TestFailed(description); } static error(errorCause) { return new TestErrored(errorCause); } static explicitlyMarkedAsPending(reason) { return new TestExplicitlyMarkedPending(reason); } static explicitlySkip() { return new ExplicitlySkippedTest(); } // checking for test status static async evaluate(test, context) { return [ExplicitlySkippedTest, FailedFastSkippedTest, TestWithoutDefinition, TestWithDefinition] .find(result => result.canHandle(test, context)) .handle(test, context); } // statuses isSuccess() { return false; } isPending() { return false; } isExplicitlyMarkedPending() { return false; } isError() { return false; } isFailure() { return false; } isSkipped() { return false; } isExplicitlySkipped() { return false; } hasFailureMessage() { return false; } } class FailedFastSkippedTest extends TestResult { static canHandle(_test, context) { return context.failFastMode.hasFailed(); } static async handle(test) { test.markSkipped(new this()); } isSkipped() { return true; } } class ExplicitlySkippedTest extends TestResult { static canHandle(test, _context) { return test.isExplicitlySkipped(); } static async handle(test) { test.finishWithSkippedStatus(); } isExplicitlySkipped() { return true; } } class TestWithoutDefinition extends TestResult { static canHandle(test) { return !test.hasDefinition(); } static async handle(test) { test.markPending(new this()); } isPending() { return true; } } export class TestWithDefinition extends TestResult { #location; static canHandle(test) { return test.hasDefinition(); } static async handle(test, context) { await test.evaluate(context); const possibleResults = [TestWithoutAssertion, TestErrored, TestExplicitlyMarkedPending, TestSucceeded, TestFailed]; // All results evaluate synchronously at this point, so we don't need to async/await this part of the code. return possibleResults .find(result => result.canHandle(test)) .handle(test, context); } constructor() { super(); this.#location = detectUserCallingLocation(); } location() { return this.#location; } resultAsText() { return subclassResponsibility(); } } class TestExplicitlyMarkedPending extends TestWithDefinition { #reason; static canHandle(test) { return test.isPending(); } static handle(test) { test.finishWithPendingStatus(); } constructor(reason) { super(); this.#reason = reason; } reason() { return this.#reason; } isPending() { return true; } isExplicitlyMarkedPending() { return true; } resultAsText() { return I18nMessage.of('test_explicitly_marked_pending_result_message'); } } class TestErrored extends TestWithDefinition { #errorCause; static canHandle(test) { return test.isError(); } static handle(test, context) { context.failFastMode.registerFailure(); test.finishWithError(); } constructor(errorCause) { super(); this.#errorCause = errorCause; } isError() { return true; } hasFailureMessage() { return true; } failureMessage() { if (this.#errorCause instanceof InvalidAssertionError) { return this.#errorCause.reason(); } else { return this.#errorStackTrace() || this.#errorCause; } } location() { // stack already includes failed line return this.#errorStackTrace() ? '' : super.location(); } #errorStackTrace() { return this.#errorCause.stack; } resultAsText() { return I18nMessage.of('test_errored_result_message'); } } class TestWithoutAssertion extends TestErrored { static canHandle(test) { return test.hasNoResult(); } static handle(test, context) { test.setResult(new this()); super.handle(test, context); } constructor() { super('This test does not have any assertions'); } location() { return ''; } } class TestSucceeded extends TestWithDefinition { static canHandle(test) { return test.isSuccess(); } static handle(test) { test.finishWithSuccess(); } isSuccess() { return true; } location() { return ''; } resultAsText() { return I18nMessage.of('test_succeeded_result_message'); } } class TestFailed extends TestWithDefinition { #failureMessage; static canHandle() { return true; } static handle(test, context) { context.failFastMode.registerFailure(); test.finishWithFailure(); } constructor(failureMessage) { super(); this.#failureMessage = failureMessage; } isFailure() { return true; } hasFailureMessage() { return true; } failureMessage() { return this.#failureMessage; } resultAsText() { return I18nMessage.of('test_failed_result_message'); } }