├── tap ├── src │ ├── interfaces.js │ ├── index.js │ ├── util.js │ ├── interfaces.ts │ ├── index.ts │ ├── util.ts │ ├── tap.js │ ├── tap-indent.js │ ├── tap-indent.ts │ └── tap.ts ├── scripts │ └── download-zora-tests.js ├── tsconfig.json ├── rollup.js ├── example.mjs ├── package.json ├── readme.md └── package-lock.json ├── zora ├── media │ ├── bsd.png │ └── console-sc.png ├── test │ ├── samples │ │ ├── output │ │ │ ├── bailout.txt │ │ │ ├── bailout.indent.txt │ │ │ ├── check_flush.txt │ │ │ ├── bailout_nested.txt │ │ │ ├── root_level_only.txt │ │ │ ├── root_level_only.indent.txt │ │ │ ├── only.txt │ │ │ ├── bailout_nested.indent.txt │ │ │ ├── async.txt │ │ │ ├── simple.txt │ │ │ ├── check_flush.indent.txt │ │ │ ├── mixed_root_test.txt │ │ │ ├── no_only_mode.txt │ │ │ ├── no_only_mode_nested.txt │ │ │ ├── only.indent.txt │ │ │ ├── failing.txt │ │ │ ├── mixed_root_test.indent.txt │ │ │ ├── root_level_failing.txt │ │ │ ├── root_level_failing.indent.txt │ │ │ ├── failing_nested.txt │ │ │ ├── skip.txt │ │ │ ├── async.indent.txt │ │ │ ├── custom_assertion.txt │ │ │ ├── simple.indent.txt │ │ │ ├── no_only_mode.indent.txt │ │ │ ├── no_only_mode_nested.indent.txt │ │ │ ├── failing.indent.txt │ │ │ ├── custom_assertion.indent.txt │ │ │ ├── only_nested.txt │ │ │ ├── nested.txt │ │ │ ├── skip.indent.txt │ │ │ ├── nested_async.txt │ │ │ ├── symbol.txt │ │ │ ├── failing_nested.indent.txt │ │ │ ├── symbol.indent.txt │ │ │ ├── only_nested.indent.txt │ │ │ ├── nested.indent.txt │ │ │ └── nested_async.indent.txt │ │ ├── cases │ │ │ ├── root_level_only.js │ │ │ ├── failing.js │ │ │ ├── root_level_failing.js │ │ │ ├── late_collect.js │ │ │ ├── only.js │ │ │ ├── no_only_mode.js │ │ │ ├── simple.js │ │ │ ├── failing_nested.js │ │ │ ├── check_flush.js │ │ │ ├── mixed_root_test.js │ │ │ ├── symbol.js │ │ │ ├── bailout.js │ │ │ ├── skip.js │ │ │ ├── async.js │ │ │ ├── bailout_nested.js │ │ │ ├── custom_assertion.js │ │ │ ├── only_nested.js │ │ │ ├── no_only_mode_nested.js │ │ │ ├── nested.js │ │ │ └── nested_async.js │ │ └── index.js │ └── unit │ │ ├── index.js │ │ └── counter.js ├── benchmarks │ ├── tape │ │ ├── index.js │ │ └── test │ │ │ └── test1.js │ ├── ava │ │ └── test │ │ │ └── test1.js │ ├── zora │ │ ├── test │ │ │ └── test1.js │ │ └── index.js │ ├── mocha │ │ └── test │ │ │ └── test1.js │ └── jest │ │ └── test │ │ └── test1.js ├── src │ ├── reporter.ts │ ├── protocol.ts │ ├── harness.ts │ ├── test.ts │ ├── counter.ts │ ├── commons.ts │ ├── index.ts │ ├── interfaces.ts │ └── assertion.ts ├── rollup │ ├── test.js │ └── build.js ├── tsconfig.json ├── .gitlab-ci.yml ├── .github │ └── workflows │ │ └── test.yml ├── .gitignore ├── .circleci │ └── config.yml ├── LICENSE ├── package.json └── scripts │ └── generate.js ├── common ├── util.js └── util.ts ├── node ├── media │ └── test_report.png ├── test │ ├── tsconfig.json │ ├── util │ │ ├── index.js │ │ └── index.ts │ ├── theme.js │ ├── theme.ts │ ├── test_file.js │ ├── test_file.ts │ ├── output_stream.js │ └── output_stream.ts ├── tsconfig.json ├── rollup.js ├── example.mjs ├── src │ ├── output-stream.js │ ├── output-stream.ts │ ├── theme.js │ ├── test.js │ ├── theme.ts │ ├── test.ts │ ├── index.js │ ├── index.ts │ ├── diagnostic.js │ └── diagnostic.ts ├── readme.md ├── package.json └── package-lock.json ├── console ├── media │ └── screen-shot.png ├── package-lock.json ├── test │ ├── tsconfig.json │ ├── raw.js │ ├── raw.ts │ ├── console.js │ └── console.ts ├── src │ ├── raw.js │ ├── raw.ts │ ├── index.js │ ├── index.ts │ ├── console.js │ └── console.ts ├── rollup.js ├── tsconfig.json ├── example.mjs ├── readme.md └── package.json ├── README.md ├── tsconfig.json ├── package.json ├── .github └── workflows │ ├── node.yml │ └── console.yml ├── LICENSE └── .gitignore /tap/src/interfaces.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tap/scripts/download-zora-tests.js: -------------------------------------------------------------------------------- 1 | // download zora's integration test suite 2 | // todo -------------------------------------------------------------------------------- /zora/media/bsd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzofox3/zora-reporters/master/zora/media/bsd.png -------------------------------------------------------------------------------- /common/util.js: -------------------------------------------------------------------------------- 1 | export const isAssertionResult = (result) => { 2 | return 'operator' in result; 3 | }; 4 | -------------------------------------------------------------------------------- /node/media/test_report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzofox3/zora-reporters/master/node/media/test_report.png -------------------------------------------------------------------------------- /zora/media/console-sc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzofox3/zora-reporters/master/zora/media/console-sc.png -------------------------------------------------------------------------------- /console/media/screen-shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lorenzofox3/zora-reporters/master/console/media/screen-shot.png -------------------------------------------------------------------------------- /console/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zora-console-reporter", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /zora/test/samples/output/bailout.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # will not go to the end 3 | ok 1 - okay 4 | Bail out! Unhandled error. 5 | -------------------------------------------------------------------------------- /zora/test/unit/index.js: -------------------------------------------------------------------------------- 1 | import assertion from './assertion'; 2 | import test from './test'; 3 | import counter from './counter'; 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zora-reporters 2 | 3 | A set of reporters for zora's protocol 4 | 5 | [TAP reporters](./tap) 6 | 7 | [node reporter](./node) -------------------------------------------------------------------------------- /zora/test/samples/output/bailout.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Subtest: will not go to the end 3 | ok 1 - okay 4 | Bail out! Unhandled error. 5 | -------------------------------------------------------------------------------- /node/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": false, 5 | "declarationDir": null 6 | } 7 | } -------------------------------------------------------------------------------- /console/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": false, 5 | "declarationDir": null 6 | } 7 | } -------------------------------------------------------------------------------- /zora/test/samples/output/check_flush.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # tester flush 3 | ok 1 - assert1 4 | 1..1 5 | 6 | # ok 7 | # success: 1 8 | # skipped: 0 9 | # failure: 0 10 | -------------------------------------------------------------------------------- /zora/test/samples/cases/root_level_only.js: -------------------------------------------------------------------------------- 1 | import {eq, ok} from '../../../dist/bundle/module.js'; 2 | 3 | ok(true, 'true is truthy'); 4 | eq({foo: 'bar'}, {foo: 'bar'}, 'foo bar'); 5 | -------------------------------------------------------------------------------- /console/src/raw.js: -------------------------------------------------------------------------------- 1 | export const rawReporter = (logger = console) => async (stream) => { 2 | for await (const m of stream) { 3 | logger.log(JSON.stringify(m)); 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /zora/test/samples/output/bailout_nested.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # will not go to the end 3 | ok 1 - okay 4 | # some sub tester 5 | ok 2 - before the end ... 6 | Bail out! Unhandled error. 7 | -------------------------------------------------------------------------------- /zora/test/samples/output/root_level_only.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | ok 1 - true is truthy 3 | ok 2 - foo bar 4 | 1..2 5 | 6 | # ok 7 | # success: 2 8 | # skipped: 0 9 | # failure: 0 10 | -------------------------------------------------------------------------------- /zora/test/samples/output/root_level_only.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | ok 1 - true is truthy 3 | ok 2 - foo bar 4 | 1..2 5 | 6 | # ok 7 | # success: 2 8 | # skipped: 0 9 | # failure: 0 10 | -------------------------------------------------------------------------------- /common/util.ts: -------------------------------------------------------------------------------- 1 | import {AssertionResult, Test} from 'zora'; 2 | 3 | export const isAssertionResult = (result: Test | AssertionResult): result is AssertionResult => { 4 | return 'operator' in result; 5 | }; -------------------------------------------------------------------------------- /zora/test/samples/cases/failing.js: -------------------------------------------------------------------------------- 1 | import {test} from '../../../dist/bundle/module.js'; 2 | 3 | test('tester 1', t => { 4 | t.ok(true, 'assert1'); 5 | t.equal('foo', 'bar', 'foo should equal bar'); 6 | }); 7 | -------------------------------------------------------------------------------- /zora/test/samples/cases/root_level_failing.js: -------------------------------------------------------------------------------- 1 | import {eq, ok} from '../../../dist/bundle/module.js'; 2 | 3 | ok(true, 'true is truthy'); 4 | eq([], [2], 'unhappy arrays'); 5 | eq({foo: 'bar'}, {foo: 'bar'}, 'foo bar'); 6 | -------------------------------------------------------------------------------- /zora/test/samples/output/only.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # should not run 3 | ok 1 - should not run # SKIP 4 | # should run 5 | ok 2 - I ran 6 | 1..2 7 | 8 | # ok 9 | # success: 1 10 | # skipped: 1 11 | # failure: 0 12 | -------------------------------------------------------------------------------- /zora/test/samples/output/bailout_nested.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Subtest: will not go to the end 3 | ok 1 - okay 4 | # Subtest: some sub tester 5 | ok 1 - before the end ... 6 | Bail out! Unhandled error. 7 | -------------------------------------------------------------------------------- /zora/test/samples/cases/late_collect.js: -------------------------------------------------------------------------------- 1 | import {test} from '../../../dist/bundle/module.js'; 2 | 3 | test(`late collection`, async t => { 4 | t.ok(true); 5 | 6 | setTimeout(() => { 7 | t.ok(true); 8 | }, 50); 9 | }); -------------------------------------------------------------------------------- /console/rollup.js: -------------------------------------------------------------------------------- 1 | export default { 2 | input: 'src/index.js', 3 | output: [{ 4 | format: 'es', 5 | file: 'dist/index.mjs' 6 | }, { 7 | format: 'cjs', 8 | file: 'dist/index.js' 9 | }] 10 | }; -------------------------------------------------------------------------------- /node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": [ 4 | "src", 5 | "../common" 6 | ], 7 | "compilerOptions": { 8 | "declaration": true, 9 | "declarationDir": "dist/declarations" 10 | } 11 | } -------------------------------------------------------------------------------- /tap/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": [ 4 | "src", 5 | "../common" 6 | ], 7 | "compilerOptions": { 8 | "declaration": true, 9 | "declarationDir": "dist/declarations" 10 | } 11 | } -------------------------------------------------------------------------------- /zora/test/samples/output/async.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # tester 1 3 | ok 1 - assert1 4 | ok 2 - assert2 5 | # tester 2 6 | ok 3 - assert3 7 | ok 4 - assert4 8 | 1..4 9 | 10 | # ok 11 | # success: 4 12 | # skipped: 0 13 | # failure: 0 14 | -------------------------------------------------------------------------------- /zora/test/samples/output/simple.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # tester 1 3 | ok 1 - assert1 4 | ok 2 - assert2 5 | # tester 2 6 | ok 3 - assert3 7 | ok 4 - assert4 8 | 1..4 9 | 10 | # ok 11 | # success: 4 12 | # skipped: 0 13 | # failure: 0 14 | -------------------------------------------------------------------------------- /console/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": [ 4 | "src", 5 | "../common" 6 | ], 7 | "compilerOptions": { 8 | "declaration": true, 9 | "declarationDir": "dist/declarations" 10 | } 11 | } -------------------------------------------------------------------------------- /zora/test/samples/output/check_flush.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Subtest: tester flush 3 | ok 1 - assert1 4 | 1..1 5 | ok 1 - tester flush # {TIME} 6 | 1..1 7 | 8 | # ok 9 | # success: 1 10 | # skipped: 0 11 | # failure: 0 12 | -------------------------------------------------------------------------------- /console/src/raw.ts: -------------------------------------------------------------------------------- 1 | import {Message} from 'zora'; 2 | 3 | export const rawReporter = (logger = console) => async (stream: AsyncIterable>) => { 4 | for await (const m of stream) { 5 | logger.log(JSON.stringify(m)); 6 | } 7 | }; -------------------------------------------------------------------------------- /console/src/index.js: -------------------------------------------------------------------------------- 1 | import { factory } from './console'; 2 | export { rawReporter } from './raw'; 3 | export const consoleReporter = (logger = console) => { 4 | const reporter = factory(logger); 5 | return (stream) => reporter.report(stream); 6 | }; 7 | -------------------------------------------------------------------------------- /zora/test/samples/cases/only.js: -------------------------------------------------------------------------------- 1 | import {only, test} from '../../../dist/bundle/module.js'; 2 | 3 | test('should not run', t => { 4 | t.fail('I should not run '); 5 | }); 6 | 7 | only('should run', t => { 8 | t.ok(true, 'I ran'); 9 | }); 10 | -------------------------------------------------------------------------------- /zora/test/samples/cases/no_only_mode.js: -------------------------------------------------------------------------------- 1 | import {test, only} from '../../../dist/bundle/module.js'; 2 | 3 | test('should not run', t => { 4 | t.fail('I should not run '); 5 | }); 6 | 7 | only('should run', t => { 8 | t.ok(true, 'I ran'); 9 | }); 10 | -------------------------------------------------------------------------------- /zora/test/samples/output/mixed_root_test.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | ok 1 - true is truthy 3 | # a sub test 4 | ok 2 - empty object 5 | ok 3 - another assertion 6 | ok 4 - foo bar 7 | 1..4 8 | 9 | # ok 10 | # success: 4 11 | # skipped: 0 12 | # failure: 0 13 | -------------------------------------------------------------------------------- /zora/benchmarks/tape/index.js: -------------------------------------------------------------------------------- 1 | 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const tests = fs.readdirSync(path.join(process.cwd(),'./benchmarks/tape/test')); 5 | for (let f of tests){ 6 | require(path.join(process.cwd(),'./benchmarks/tape/test/',f)); 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "esnext", 5 | "dom" 6 | ], 7 | "module": "esnext", 8 | "moduleResolution": "node", 9 | "noImplicitAny": true, 10 | "target": "esnext", 11 | "esModuleInterop": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /zora/benchmarks/tape/test/test1.js: -------------------------------------------------------------------------------- 1 | 2 | const test = require('tape'); 3 | for (let i = 0; i < 10; i++) { 4 | test('test ' + i, function (assert) { 5 | setTimeout(()=>{ 6 | assert.ok(Math.random() * 100 > 5); 7 | assert.end(); 8 | },100); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /zora/src/reporter.ts: -------------------------------------------------------------------------------- 1 | import {indentedTapReporter, tapReporter} from 'zora-tap-reporter'; 2 | import {Reporter} from './interfaces'; 3 | 4 | //@ts-ignore 5 | export const mochaTapLike: Reporter = indentedTapReporter(); 6 | //@ts-ignore 7 | export const tapeTapLike: Reporter = tapReporter(); -------------------------------------------------------------------------------- /tap/rollup.js: -------------------------------------------------------------------------------- 1 | export default { 2 | input:'src/index.js', 3 | output:[{ 4 | format:'es', 5 | file:'dist/index.mjs' 6 | },{ 7 | format:'es', 8 | file:'dist/module.js' 9 | },{ 10 | format:'cjs', 11 | file:'dist/index.js' 12 | }] 13 | } -------------------------------------------------------------------------------- /zora/test/samples/cases/simple.js: -------------------------------------------------------------------------------- 1 | import {test} from '../../../dist/bundle/module.js'; 2 | 3 | test('tester 1', t => { 4 | t.ok(true, 'assert1'); 5 | t.ok(true, 'assert2'); 6 | }); 7 | 8 | test('tester 2', t => { 9 | t.ok(true, 'assert3'); 10 | t.ok(true, 'assert4'); 11 | }); 12 | -------------------------------------------------------------------------------- /zora/test/samples/output/no_only_mode.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # should not run 3 | not ok 1 - I should not run 4 | --- 5 | actual: "fail called" 6 | expected: "fail not called" 7 | operator: "fail" 8 | at:{STACK} 9 | ... 10 | # should run 11 | Bail out! Unhandled error. 12 | -------------------------------------------------------------------------------- /zora/test/samples/output/no_only_mode_nested.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # should not run 3 | not ok 1 - I should not run 4 | --- 5 | actual: "fail called" 6 | expected: "fail not called" 7 | operator: "fail" 8 | at:{STACK} 9 | ... 10 | # should run 11 | Bail out! Unhandled error. 12 | -------------------------------------------------------------------------------- /zora/test/samples/cases/failing_nested.js: -------------------------------------------------------------------------------- 1 | import {test} from '../../../dist/bundle/module.js'; 2 | 3 | test('tester 1', t => { 4 | t.ok(true, 'assert1'); 5 | t.test('inside', t => { 6 | t.ok(true, 'correct'); 7 | t.equal(4, '4', 'should fail with coercion'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /zora/test/samples/output/only.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Subtest: should not run 3 | 1..0 4 | ok 1 - should not run # SKIP 5 | # Subtest: should run 6 | ok 1 - I ran 7 | 1..1 8 | ok 2 - should run # {TIME} 9 | 1..2 10 | 11 | # ok 12 | # success: 1 13 | # skipped: 1 14 | # failure: 0 15 | -------------------------------------------------------------------------------- /zora/benchmarks/ava/test/test1.js: -------------------------------------------------------------------------------- 1 | 2 | const test = require('ava'); 3 | for (let i = 0; i < 10; i++) { 4 | test('test ' + i, async function (assert) { 5 | await new Promise(resolve => { 6 | setTimeout(()=>resolve(),100); 7 | }); 8 | assert.truthy(Math.random() * 100 > 5); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /zora/test/samples/cases/check_flush.js: -------------------------------------------------------------------------------- 1 | import {test} from '../../../dist/bundle/module.js'; 2 | 3 | const wait = time => new Promise(resolve => { 4 | setTimeout(() => resolve(), time); 5 | }); 6 | 7 | test('tester flush', async t => { 8 | await wait(1000); 9 | t.ok(true, 'assert1'); 10 | }); 11 | -------------------------------------------------------------------------------- /zora/benchmarks/zora/test/test1.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports =(({test}) => { 3 | for (let i = 0; i < 10; i++) { 4 | test('test ' + i, async function (assert) { 5 | await new Promise(resolve => { 6 | setTimeout(()=>resolve(),100); 7 | }); 8 | assert.ok(Math.random() * 100 > 5); 9 | }); 10 | }}); 11 | -------------------------------------------------------------------------------- /console/src/index.ts: -------------------------------------------------------------------------------- 1 | import {factory} from './console'; 2 | import {Message} from 'zora'; 3 | 4 | export {rawReporter} from './raw'; 5 | export const consoleReporter = (logger: Console = console) => { 6 | const reporter = factory(logger); 7 | return (stream: AsyncIterable>) => reporter.report(stream); 8 | }; -------------------------------------------------------------------------------- /zora/rollup/test.js: -------------------------------------------------------------------------------- 1 | import node from 'rollup-plugin-node-resolve'; 2 | import cjs from 'rollup-plugin-commonjs'; 3 | 4 | export default { 5 | input: './test/unit/index.js', 6 | output: [{ 7 | format: 'cjs' 8 | }], 9 | plugins: [node(), cjs({ 10 | ignore:['tape'] 11 | })] 12 | }; 13 | -------------------------------------------------------------------------------- /zora/test/samples/cases/mixed_root_test.js: -------------------------------------------------------------------------------- 1 | import {eq, ok, test} from '../../../dist/bundle/module.js'; 2 | 3 | ok(true, 'true is truthy'); 4 | 5 | test('a sub test', t => { 6 | t.eq({}, {}, 'empty object'); 7 | t.ok(true, 'another assertion'); 8 | }); 9 | 10 | eq({foo: 'bar'}, {foo: 'bar'}, 'foo bar'); 11 | -------------------------------------------------------------------------------- /zora/test/samples/output/failing.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # tester 1 3 | ok 1 - assert1 4 | not ok 2 - foo should equal bar 5 | --- 6 | actual: "foo" 7 | expected: "bar" 8 | operator: "equal" 9 | at:{STACK} 10 | ... 11 | 1..2 12 | 13 | # not ok 14 | # success: 1 15 | # skipped: 0 16 | # failure: 1 17 | -------------------------------------------------------------------------------- /zora/test/samples/cases/symbol.js: -------------------------------------------------------------------------------- 1 | import {test} from '../../../dist/bundle/module.js'; 2 | 3 | test('symbol tester 1', t => { 4 | t.equal(Symbol('foo'), Symbol('bar'), 'Symbol foo should equal Symbol bar'); 5 | t.equal({symbol: Symbol('foo')}, {symbol: Symbol('bar')}, 'Property Symbol foo should equal Symbol bar'); 6 | }); 7 | -------------------------------------------------------------------------------- /zora/test/samples/output/mixed_root_test.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | ok 1 - true is truthy 3 | # Subtest: a sub test 4 | ok 1 - empty object 5 | ok 2 - another assertion 6 | 1..2 7 | ok 2 - a sub test # {TIME} 8 | ok 3 - foo bar 9 | 1..3 10 | 11 | # ok 12 | # success: 4 13 | # skipped: 0 14 | # failure: 0 15 | -------------------------------------------------------------------------------- /zora/test/samples/output/root_level_failing.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | ok 1 - true is truthy 3 | not ok 2 - unhappy arrays 4 | --- 5 | actual: [] 6 | expected: [2] 7 | operator: "equal" 8 | at:{STACK} 9 | ... 10 | ok 3 - foo bar 11 | 1..3 12 | 13 | # not ok 14 | # success: 2 15 | # skipped: 0 16 | # failure: 1 17 | -------------------------------------------------------------------------------- /zora/benchmarks/mocha/test/test1.js: -------------------------------------------------------------------------------- 1 | 2 | const assert = require('assert'); 3 | describe('test file', function() { 4 | for(let i=0; i < 10;i++){ 5 | it('test ' + i, function(done) { 6 | setTimeout(()=>{ 7 | assert.ok(Math.random() * 100 > 5); 8 | done(); 9 | },100); 10 | }); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /zora/test/samples/output/root_level_failing.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | ok 1 - true is truthy 3 | not ok 2 - unhappy arrays 4 | --- 5 | wanted: [2] 6 | found: [] 7 | at:{STACK} 8 | operator: "equal" 9 | ... 10 | ok 3 - foo bar 11 | 1..3 12 | 13 | # not ok 14 | # success: 2 15 | # skipped: 0 16 | # failure: 1 17 | -------------------------------------------------------------------------------- /zora/benchmarks/jest/test/test1.js: -------------------------------------------------------------------------------- 1 | 2 | describe('add', function () { 3 | for (let i = 0; i < 10; i++) { 4 | it('should test',async function () { 5 | await new Promise(resolve => { 6 | setTimeout(()=>resolve(),100); 7 | }); 8 | expect(Math.random() * 100 > 5).toBeTruthy(); 9 | }); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /zora/benchmarks/zora/index.js: -------------------------------------------------------------------------------- 1 | 2 | const {test} = require('../../dist/bundle/index.js'); 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const tests = fs.readdirSync(path.join(process.cwd(),'./benchmarks/zora/test')); 6 | for (let f of tests){ 7 | test(f,require(path.join(process.cwd(),'./benchmarks/zora/test/',f))); 8 | } 9 | -------------------------------------------------------------------------------- /zora/test/samples/cases/bailout.js: -------------------------------------------------------------------------------- 1 | import {test} from '../../../dist/bundle/module.js'; 2 | 3 | test('will not go to the end', t => { 4 | t.ok(true, 'okay'); 5 | 6 | throw new Error('Oh no!'); 7 | 8 | t.ok(true, 'will never be reached'); 9 | }); 10 | 11 | test('another one', t => { 12 | t.ok(true, 'will never be reported'); 13 | }); 14 | -------------------------------------------------------------------------------- /zora/test/samples/output/failing_nested.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # tester 1 3 | ok 1 - assert1 4 | # inside 5 | ok 2 - correct 6 | not ok 3 - should fail with coercion 7 | --- 8 | actual: 4 9 | expected: "4" 10 | operator: "equal" 11 | at:{STACK} 12 | ... 13 | 1..3 14 | 15 | # not ok 16 | # success: 2 17 | # skipped: 0 18 | # failure: 1 19 | -------------------------------------------------------------------------------- /zora/test/samples/output/skip.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | ok 1 - hey hey 3 | ok 2 - hey hey bis 4 | # hello world 5 | ok 3 - should be truthy 6 | # blah 7 | ok 4 - blah # SKIP 8 | # for some reason 9 | ok 5 - for some reason # SKIP 10 | # failing text 11 | ok 6 - failing text # SKIP 12 | 1..6 13 | 14 | # ok 15 | # success: 3 16 | # skipped: 3 17 | # failure: 0 18 | -------------------------------------------------------------------------------- /zora/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "esnext" 5 | ], 6 | "module": "es2015", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "declarationDir": "dist/declarations", 10 | "target": "esnext", 11 | "esModuleInterop": true 12 | }, 13 | "include": [ 14 | "./src" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /zora/test/samples/output/async.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Subtest: tester 1 3 | ok 1 - assert1 4 | ok 2 - assert2 5 | 1..2 6 | ok 1 - tester 1 # {TIME} 7 | # Subtest: tester 2 8 | ok 1 - assert3 9 | ok 2 - assert4 10 | 1..2 11 | ok 2 - tester 2 # {TIME} 12 | 1..2 13 | 14 | # ok 15 | # success: 4 16 | # skipped: 0 17 | # failure: 0 18 | -------------------------------------------------------------------------------- /zora/test/samples/output/custom_assertion.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # tester 1 3 | ok 1 - foo should equal foo 4 | not ok 2 - should be "foo" 5 | --- 6 | expected: "foo" 7 | actual: "blah" 8 | operator: "isFoo" 9 | other: "property" 10 | at:{STACK} 11 | ... 12 | 1..2 13 | 14 | # not ok 15 | # success: 1 16 | # skipped: 0 17 | # failure: 1 18 | -------------------------------------------------------------------------------- /zora/test/samples/output/simple.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Subtest: tester 1 3 | ok 1 - assert1 4 | ok 2 - assert2 5 | 1..2 6 | ok 1 - tester 1 # {TIME} 7 | # Subtest: tester 2 8 | ok 1 - assert3 9 | ok 2 - assert4 10 | 1..2 11 | ok 2 - tester 2 # {TIME} 12 | 1..2 13 | 14 | # ok 15 | # success: 4 16 | # skipped: 0 17 | # failure: 0 18 | -------------------------------------------------------------------------------- /node/rollup.js: -------------------------------------------------------------------------------- 1 | import node from 'rollup-plugin-node-resolve'; 2 | import cjs from 'rollup-plugin-commonjs'; 3 | 4 | export default { 5 | input: 'src/index.js', 6 | output: [{ 7 | format: 'es', 8 | file: 'dist/index.mjs' 9 | }, { 10 | format: 'cjs', 11 | file: 'dist/index.js' 12 | }], 13 | plugins: [node(), cjs()] 14 | }; -------------------------------------------------------------------------------- /zora/test/samples/cases/skip.js: -------------------------------------------------------------------------------- 1 | import {test, ok, skip} from '../../../dist/bundle/module.js'; 2 | 3 | ok(true, 'hey hey'); 4 | ok(true, 'hey hey bis'); 5 | 6 | test('hello world', t => { 7 | t.ok(true); 8 | t.skip('blah', t => { 9 | t.ok(false); 10 | }); 11 | t.skip('for some reason'); 12 | }); 13 | 14 | skip('failing text', t => { 15 | t.ok(false); 16 | }); 17 | -------------------------------------------------------------------------------- /zora/test/samples/output/no_only_mode.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Subtest: should not run 3 | not ok 1 - I should not run 4 | --- 5 | wanted: "fail not called" 6 | found: "fail called" 7 | at:{STACK} 8 | operator: "fail" 9 | ... 10 | 1..1 11 | not ok 1 - should not run # {TIME} 12 | # Subtest: should run 13 | Bail out! Unhandled error. 14 | -------------------------------------------------------------------------------- /zora/test/samples/output/no_only_mode_nested.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Subtest: should not run 3 | not ok 1 - I should not run 4 | --- 5 | wanted: "fail not called" 6 | found: "fail called" 7 | at:{STACK} 8 | operator: "fail" 9 | ... 10 | 1..1 11 | not ok 1 - should not run # {TIME} 12 | # Subtest: should run 13 | Bail out! Unhandled error. 14 | -------------------------------------------------------------------------------- /zora/test/samples/output/failing.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Subtest: tester 1 3 | ok 1 - assert1 4 | not ok 2 - foo should equal bar 5 | --- 6 | wanted: "bar" 7 | found: "foo" 8 | at:{STACK} 9 | operator: "equal" 10 | ... 11 | 1..2 12 | not ok 1 - tester 1 # {TIME} 13 | 1..1 14 | 15 | # not ok 16 | # success: 1 17 | # skipped: 0 18 | # failure: 1 19 | -------------------------------------------------------------------------------- /tap/src/index.js: -------------------------------------------------------------------------------- 1 | import { factory as indentedTapReporterFactory } from './tap-indent'; 2 | import { factory as tapReporterFactory } from './tap'; 3 | const report = (factory) => (logger = console) => { 4 | const log = logger.log.bind(logger); 5 | return async (stream) => factory(log).report(stream); 6 | }; 7 | export const tapReporter = report(tapReporterFactory); 8 | export const indentedTapReporter = report(indentedTapReporterFactory); 9 | -------------------------------------------------------------------------------- /zora/test/samples/output/custom_assertion.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Subtest: tester 1 3 | ok 1 - foo should equal foo 4 | not ok 2 - should be "foo" 5 | --- 6 | wanted: "foo" 7 | found: "blah" 8 | at:{STACK} 9 | operator: "isFoo" 10 | other: "property" 11 | ... 12 | 1..2 13 | not ok 1 - tester 1 # {TIME} 14 | 1..1 15 | 16 | # not ok 17 | # success: 1 18 | # skipped: 0 19 | # failure: 1 20 | -------------------------------------------------------------------------------- /zora/test/samples/cases/async.js: -------------------------------------------------------------------------------- 1 | import {test} from '../../../dist/bundle/module.js'; 2 | 3 | const wait = time => new Promise(resolve => { 4 | setTimeout(() => resolve(), time); 5 | }); 6 | 7 | test('tester 1', async t => { 8 | t.ok(true, 'assert1'); 9 | await wait(500); 10 | t.ok(true, 'assert2'); 11 | }); 12 | 13 | test('tester 2', async t => { 14 | t.ok(true, 'assert3'); 15 | await wait(300); 16 | t.ok(true, 'assert4'); 17 | }); 18 | -------------------------------------------------------------------------------- /zora/test/samples/cases/bailout_nested.js: -------------------------------------------------------------------------------- 1 | import {test} from '../../../dist/bundle/module.js'; 2 | 3 | test('will not go to the end', t => { 4 | t.ok(true, 'okay'); 5 | 6 | t.test('some sub tester', async t => { 7 | t.ok(true, 'before the end ...'); 8 | throw new Error('Oh no!'); 9 | }); 10 | 11 | t.ok(true, 'will never be reached'); 12 | }); 13 | 14 | test('another one', t => { 15 | t.ok(true, 'will never be reported'); 16 | }); 17 | -------------------------------------------------------------------------------- /zora/rollup/build.js: -------------------------------------------------------------------------------- 1 | import node from 'rollup-plugin-node-resolve'; 2 | import cjs from 'rollup-plugin-commonjs'; 3 | 4 | export default { 5 | input: './src/index.js', 6 | output: [{ 7 | file: './dist/bundle/index.js', 8 | format: 'cjs' 9 | }, { 10 | file: './dist/bundle/index.mjs', 11 | format: 'es' 12 | }, { 13 | file: './dist/bundle/module.js', 14 | format: 'es' 15 | }], 16 | plugins: [node(), cjs()] 17 | }; 18 | -------------------------------------------------------------------------------- /zora/test/samples/output/only_nested.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # should not run 3 | ok 1 - should not run # SKIP 4 | # should run 5 | ok 2 - I ran 6 | # keep running 7 | # keeeeeep running 8 | ok 3 - I got there 9 | # should not run 10 | ok 4 - should not run # SKIP 11 | # should run but nothing inside 12 | # will not run 13 | ok 5 - will not run # SKIP 14 | # will not run 15 | ok 6 - will not run # SKIP 16 | 1..6 17 | 18 | # ok 19 | # success: 2 20 | # skipped: 4 21 | # failure: 0 22 | -------------------------------------------------------------------------------- /tap/src/util.js: -------------------------------------------------------------------------------- 1 | export const map = (fn) => async function* (stream) { 2 | for await (const m of stream) { 3 | yield fn(m); 4 | } 5 | }; 6 | // ! it mutates the underlying structure yet it is more efficient regarding performances 7 | export const flatten = map((m) => { 8 | m.offset = 0; 9 | return m; 10 | }); 11 | export const stringifySymbol = (key, value) => { 12 | if (typeof value === 'symbol') { 13 | return value.toString(); 14 | } 15 | return value; 16 | }; 17 | -------------------------------------------------------------------------------- /zora/test/samples/output/nested.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # tester 1 3 | ok 1 - assert1 4 | # some nested tester 5 | ok 2 - nested 1 6 | ok 3 - nested 2 7 | # some nested tester bis 8 | ok 4 - nested 1 9 | # deeply nested 10 | ok 5 - deeply nested really 11 | ok 6 - deeply nested again 12 | ok 7 - nested 2 13 | ok 8 - assert2 14 | # tester 2 15 | ok 9 - assert3 16 | # nested in two 17 | ok 10 - still happy 18 | ok 11 - assert4 19 | 1..11 20 | 21 | # ok 22 | # success: 11 23 | # skipped: 0 24 | # failure: 0 25 | -------------------------------------------------------------------------------- /node/example.mjs: -------------------------------------------------------------------------------- 1 | import {createHarness} from 'zora/dist/bundle/index.mjs'; 2 | import {reporter} from './dist/index.mjs'; 3 | 4 | const h = createHarness(); 5 | 6 | const {test} = h; 7 | 8 | test(`hello world`, t => { 9 | t.ok(true); 10 | 11 | t.test('nested', t => { 12 | t.eq('foo', 'fob'); 13 | }); 14 | }); 15 | 16 | test(`hello world`, t => { 17 | t.ok(true); 18 | 19 | t.test('nested', t => { 20 | t.eq('foo', 'fob'); 21 | }); 22 | }); 23 | 24 | h.report(reporter()); -------------------------------------------------------------------------------- /zora/test/samples/output/skip.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | ok 1 - hey hey 3 | ok 2 - hey hey bis 4 | # Subtest: hello world 5 | ok 1 - should be truthy 6 | # Subtest: blah 7 | 1..0 8 | ok 2 - blah # SKIP 9 | # Subtest: for some reason 10 | 1..0 11 | ok 3 - for some reason # SKIP 12 | 1..3 13 | ok 3 - hello world # {TIME} 14 | # Subtest: failing text 15 | 1..0 16 | ok 4 - failing text # SKIP 17 | 1..4 18 | 19 | # ok 20 | # success: 3 21 | # skipped: 3 22 | # failure: 0 23 | -------------------------------------------------------------------------------- /zora/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # This file is a template, and might need editing before it works on your project. 2 | # Official framework image. Look for the different tagged releases at: 3 | # https://hub.docker.com/r/library/node/tags/ 4 | image: node:latest 5 | 6 | # This folder is cached between builds 7 | # http://docs.gitlab.com/ce/ci/yaml/README.html#cache 8 | cache: 9 | paths: 10 | - node_modules/ 11 | 12 | test: 13 | script: 14 | - npm install 15 | - npm run build 16 | - npm run test 17 | 18 | 19 | -------------------------------------------------------------------------------- /console/example.mjs: -------------------------------------------------------------------------------- 1 | import {createHarness} from 'zora/dist/bundle/index.mjs'; 2 | import {consoleReporter} from './dist/index.mjs'; 3 | 4 | const h = createHarness(); 5 | 6 | const {test} = h; 7 | 8 | test(`hello world`, t => { 9 | t.ok(true); 10 | 11 | t.test('nested', t => { 12 | t.eq('foo', 'fob'); 13 | }); 14 | }); 15 | 16 | test(`hello world`, t => { 17 | t.ok(true); 18 | 19 | t.test('nested', t => { 20 | t.eq('foo', 'fob'); 21 | }); 22 | }); 23 | 24 | h.report(consoleReporter()); -------------------------------------------------------------------------------- /zora/test/samples/output/nested_async.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # tester 1 3 | # update counter with delay 4 | ok 1 - nested 1 5 | ok 2 - nested 2 6 | # check counter 7 | ok 3 - should see the old value of the counter 8 | ok 4 - assert2 9 | # tester 2 10 | ok 5 - assert3 11 | # update counter with delay but blocking 12 | ok 6 - nested 1 13 | ok 7 - nested 2 14 | # check counter bis 15 | ok 8 - should see the new value of the counter 16 | ok 9 - whatever 17 | 1..9 18 | 19 | # ok 20 | # success: 9 21 | # skipped: 0 22 | # failure: 0 23 | -------------------------------------------------------------------------------- /tap/src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import {AssertionMessage, BailoutMessage, Message, StartTestMessage, TestEndMessage} from 'zora'; 2 | 3 | export interface Reporter { 4 | printComment(comment: string, offset?: number): void; 5 | 6 | printBailOut(message: BailoutMessage): void; 7 | 8 | printTestStart(message: StartTestMessage): void; 9 | 10 | printTestEnd(message: TestEndMessage): void; 11 | 12 | printAssertion(message: AssertionMessage): void; 13 | 14 | report(stream: AsyncIterable>): Promise; 15 | } -------------------------------------------------------------------------------- /tap/example.mjs: -------------------------------------------------------------------------------- 1 | import {createHarness} from 'zora/dist/bundle/index.mjs'; 2 | import {tapReporter, indentedTapReporter} from './dist/index.mjs'; 3 | 4 | const h = createHarness(); 5 | 6 | const {test} = h; 7 | 8 | test(`hello world`, t => { 9 | t.ok(true); 10 | 11 | t.test('nested', t => { 12 | t.eq('foo', 'fob'); 13 | }); 14 | }); 15 | 16 | test(`hello world`, t => { 17 | t.ok(true); 18 | 19 | t.test('nested', t => { 20 | t.eq('foo', 'fob'); 21 | }); 22 | }); 23 | 24 | h.report(indentedTapReporter()); -------------------------------------------------------------------------------- /zora/test/samples/output/symbol.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # symbol tester 1 3 | not ok 1 - Symbol foo should equal Symbol bar 4 | --- 5 | actual: "Symbol(foo)" 6 | expected: "Symbol(bar)" 7 | operator: "equal" 8 | at:{STACK} 9 | ... 10 | not ok 2 - Property Symbol foo should equal Symbol bar 11 | --- 12 | actual: {"symbol":"Symbol(foo)"} 13 | expected: {"symbol":"Symbol(bar)"} 14 | operator: "equal" 15 | at:{STACK} 16 | ... 17 | 1..2 18 | 19 | # not ok 20 | # success: 0 21 | # skipped: 0 22 | # failure: 2 23 | -------------------------------------------------------------------------------- /zora/test/samples/output/failing_nested.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Subtest: tester 1 3 | ok 1 - assert1 4 | # Subtest: inside 5 | ok 1 - correct 6 | not ok 2 - should fail with coercion 7 | --- 8 | wanted: "4" 9 | found: 4 10 | at:{STACK} 11 | operator: "equal" 12 | ... 13 | 1..2 14 | not ok 2 - inside # {TIME} 15 | 1..2 16 | not ok 1 - tester 1 # {TIME} 17 | 1..1 18 | 19 | # not ok 20 | # success: 2 21 | # skipped: 0 22 | # failure: 1 23 | -------------------------------------------------------------------------------- /zora/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | node-version: [10.x, 12.x] 9 | steps: 10 | - uses: actions/checkout@v1 11 | - name: Use Node.js ${{ matrix.node-version }} 12 | uses: actions/setup-node@v1 13 | with: 14 | node-version: ${{ matrix.node-version }} 15 | - name: npm install, build, and test 16 | run: | 17 | npm install 18 | npm run build --if-present 19 | npm test 20 | -------------------------------------------------------------------------------- /tap/src/index.ts: -------------------------------------------------------------------------------- 1 | import {Message} from 'zora'; 2 | import {factory as indentedTapReporterFactory} from './tap-indent' 3 | import {factory as tapReporterFactory} from './tap'; 4 | 5 | export {TapReporter} from './tap'; 6 | 7 | const report = (factory: Function) => (logger: Console = console) => { 8 | const log = logger.log.bind(logger); 9 | return async (stream: AsyncIterable>) => factory(log).report(stream); 10 | }; 11 | 12 | export const tapReporter = report(tapReporterFactory); 13 | export const indentedTapReporter = report(indentedTapReporterFactory); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zora-reporters", 3 | "description": "A set of reporters for zora's protocol", 4 | "repository": { 5 | "type": "git", 6 | "url": "git+https://github.com/lorenzofox3/zora-reporters.git" 7 | }, 8 | "author": "Laurent RENARD", 9 | "license": "MIT", 10 | "bugs": { 11 | "url": "https://github.com/lorenzofox3/zora-reporters/issues" 12 | }, 13 | "homepage": "https://github.com/lorenzofox3/zora-reporters#readme", 14 | "devDependencies": { 15 | "pta": "^0.1.2", 16 | "rollup": "^1.27.9", 17 | "typescript": "^3.7.3", 18 | "zora": "^3.1.6" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tap/src/util.ts: -------------------------------------------------------------------------------- 1 | import {Message} from 'zora'; 2 | 3 | export const map = (fn: Function) => async function* (stream: AsyncIterable>) { 4 | for await (const m of stream) { 5 | yield fn(m); 6 | } 7 | }; 8 | 9 | // ! it mutates the underlying structure yet it is more efficient regarding performances 10 | export const flatten = map((m: any) => { 11 | m.offset = 0; 12 | return m; 13 | }); 14 | 15 | export const stringifySymbol = (key: string, value: Symbol) => { 16 | if (typeof value === 'symbol') { 17 | return value.toString(); 18 | } 19 | 20 | return value; 21 | }; -------------------------------------------------------------------------------- /console/readme.md: -------------------------------------------------------------------------------- 1 | # zora-console-reporter 2 | 3 | Reporters using the console API, should work in bot environment the browser and Nodejs 4 | 5 | ## installation 6 | 7 | ``npm run install zora-console-reporter`` 8 | 9 | ## raw 10 | 11 | Stringify zora's messages in the console. It is particularly useful if you want to automate a browser and use some DevTool protocol. Note: it won't stop on Bailout 12 | 13 | ## console 14 | 15 | Uses advanced console API to group and flag messages. Ideal to use in the browser which provides many way to interact with the console logs (filter, expand, etc) 16 | 17 | ![console report screenshot](./media/screen-shot.png) 18 | -------------------------------------------------------------------------------- /zora/test/samples/output/symbol.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Subtest: symbol tester 1 3 | not ok 1 - Symbol foo should equal Symbol bar 4 | --- 5 | wanted: "Symbol(bar)" 6 | found: "Symbol(foo)" 7 | at:{STACK} 8 | operator: "equal" 9 | ... 10 | not ok 2 - Property Symbol foo should equal Symbol bar 11 | --- 12 | wanted: {"symbol":"Symbol(bar)"} 13 | found: {"symbol":"Symbol(foo)"} 14 | at:{STACK} 15 | operator: "equal" 16 | ... 17 | 1..2 18 | not ok 1 - symbol tester 1 # {TIME} 19 | 1..1 20 | 21 | # not ok 22 | # success: 0 23 | # skipped: 0 24 | # failure: 2 25 | -------------------------------------------------------------------------------- /zora/test/samples/cases/custom_assertion.js: -------------------------------------------------------------------------------- 1 | import {test} from '../../../dist/bundle/module.js'; 2 | 3 | const customAssert = spec => t => { 4 | return spec(Object.assign(t, { 5 | isFoo(value, description = 'should be "foo"') { 6 | return this.collect({ 7 | pass: value === 'foo', 8 | expected: 'foo', 9 | actual: value, 10 | operator: 'isFoo', 11 | description, 12 | other: 'property' 13 | }); 14 | } 15 | })); 16 | }; 17 | 18 | test('tester 1', customAssert(t => { 19 | t.equal('foo', 'foo', 'foo should equal foo'); 20 | t.isFoo('blah'); 21 | })); 22 | -------------------------------------------------------------------------------- /.github/workflows/node.yml: -------------------------------------------------------------------------------- 1 | name: node-reporter CI 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | node-version: [10.x, 12.x] 9 | steps: 10 | - uses: actions/checkout@v1 11 | - name: Use Node.js ${{ matrix.node-version }} 12 | uses: actions/setup-node@v1 13 | with: 14 | node-version: ${{ matrix.node-version }} 15 | - name: npm install common 16 | run: | 17 | npm install 18 | - name: install - build and test 19 | working-directory: ./console 20 | run: | 21 | npm install 22 | npm run build 23 | npm run build:test 24 | npm t 25 | -------------------------------------------------------------------------------- /.github/workflows/console.yml: -------------------------------------------------------------------------------- 1 | name: console-reporter CI 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | node-version: [10.x, 12.x] 9 | steps: 10 | - uses: actions/checkout@v1 11 | - name: Use Node.js ${{ matrix.node-version }} 12 | uses: actions/setup-node@v1 13 | with: 14 | node-version: ${{ matrix.node-version }} 15 | - name: npm install common 16 | run: | 17 | npm install 18 | - name: install - build and test 19 | working-directory: ./console 20 | run: | 21 | npm install 22 | npm run build 23 | npm run build:test 24 | npm t 25 | -------------------------------------------------------------------------------- /zora/test/samples/cases/only_nested.js: -------------------------------------------------------------------------------- 1 | import {test, only} from '../../../dist/bundle/module.js'; 2 | 3 | test('should not run', t => { 4 | t.fail('I should not run '); 5 | }); 6 | 7 | only('should run', t => { 8 | t.ok(true, 'I ran'); 9 | 10 | t.only('keep running', t => { 11 | t.only('keeeeeep running', t => { 12 | t.ok(true, ' I got there'); 13 | }); 14 | }); 15 | 16 | t.test('should not run', t => { 17 | t.fail('shouldn ot run'); 18 | }); 19 | }); 20 | 21 | only('should run but nothing inside', t => { 22 | t.test('will not run', t => { 23 | t.fail('should not run'); 24 | }); 25 | t.test('will not run', t => { 26 | t.fail('should not run'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /zora/test/samples/cases/no_only_mode_nested.js: -------------------------------------------------------------------------------- 1 | import {test, only} from '../../../dist/bundle/module.js'; 2 | 3 | test('should not run', t => { 4 | t.fail('I should not run '); 5 | }); 6 | 7 | only('should run', t => { 8 | t.ok(true, 'I ran'); 9 | 10 | t.only('keep running', t => { 11 | t.only('keeeeeep running', t => { 12 | t.ok(true, ' I got there'); 13 | }); 14 | }); 15 | 16 | t.test('should not run', t => { 17 | t.fail('shouldn ot run'); 18 | }); 19 | }); 20 | 21 | only('should run but nothing inside', t => { 22 | t.test('will not run', t => { 23 | t.fail('should not run'); 24 | }); 25 | t.test('will not run', t => { 26 | t.fail('should not run'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /node/src/output-stream.js: -------------------------------------------------------------------------------- 1 | import { EOL } from 'os'; 2 | export const delegate = (...methods) => (target) => { 3 | const output = {}; 4 | for (const m of methods) { 5 | // @ts-ignore 6 | output[m] = (...args) => target[m](...args); 7 | } 8 | return output; 9 | }; 10 | // @ts-ignore 11 | const delegateTTY = delegate('write', 'clearLine', 'cursorTo', 'moveCursor'); 12 | export const output = (stream) => { 13 | return Object.assign(delegateTTY(stream), { 14 | writeLine(message = '', padding = 0) { 15 | this.write(' '.repeat(padding) + message + EOL); 16 | }, 17 | writeBlock(message, padding = 0) { 18 | this.writeLine(); 19 | this.writeLine(message, padding); 20 | } 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /zora/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | .idea 40 | test/dist 41 | benchmarks 42 | dist 43 | src/*.js 44 | sample.html 45 | -------------------------------------------------------------------------------- /zora/test/samples/cases/nested.js: -------------------------------------------------------------------------------- 1 | import {test} from '../../../dist/bundle/module.js'; 2 | 3 | test('tester 1', t => { 4 | 5 | t.ok(true, 'assert1'); 6 | 7 | t.test('some nested tester', t => { 8 | t.ok(true, 'nested 1'); 9 | t.ok(true, 'nested 2'); 10 | }); 11 | 12 | t.test('some nested tester bis', t => { 13 | t.ok(true, 'nested 1'); 14 | 15 | t.test('deeply nested', t => { 16 | t.ok(true, 'deeply nested really'); 17 | t.ok(true, 'deeply nested again'); 18 | }); 19 | 20 | t.ok(true, 'nested 2'); 21 | }); 22 | 23 | t.ok(true, 'assert2'); 24 | }); 25 | 26 | test('tester 2', t => { 27 | t.ok(true, 'assert3'); 28 | 29 | t.test('nested in two', t => { 30 | t.ok(true, 'still happy'); 31 | }); 32 | 33 | t.ok(true, 'assert4'); 34 | }); 35 | -------------------------------------------------------------------------------- /zora/.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:latest 11 | 12 | steps: 13 | - checkout 14 | 15 | # Download and cache dependencies 16 | - restore_cache: 17 | keys: 18 | - v1-dependencies-{{ checksum "package.json" }} 19 | # fallback to using the latest cache if no exact match is found 20 | - v1-dependencies- 21 | 22 | - run: npm install 23 | 24 | - save_cache: 25 | paths: 26 | - node_modules 27 | key: v1-dependencies-{{ checksum "package.json" }} 28 | 29 | - run: npm run build 30 | 31 | # run tests! 32 | - run: npm run test 33 | -------------------------------------------------------------------------------- /zora/test/samples/output/only_nested.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Subtest: should not run 3 | 1..0 4 | ok 1 - should not run # SKIP 5 | # Subtest: should run 6 | ok 1 - I ran 7 | # Subtest: keep running 8 | # Subtest: keeeeeep running 9 | ok 1 - I got there 10 | 1..1 11 | ok 1 - keeeeeep running # {TIME} 12 | 1..1 13 | ok 2 - keep running # {TIME} 14 | # Subtest: should not run 15 | 1..0 16 | ok 3 - should not run # SKIP 17 | 1..3 18 | ok 2 - should run # {TIME} 19 | # Subtest: should run but nothing inside 20 | # Subtest: will not run 21 | 1..0 22 | ok 1 - will not run # SKIP 23 | # Subtest: will not run 24 | 1..0 25 | ok 2 - will not run # SKIP 26 | 1..2 27 | ok 3 - should run but nothing inside # {TIME} 28 | 1..3 29 | 30 | # ok 31 | # success: 2 32 | # skipped: 4 33 | # failure: 0 34 | -------------------------------------------------------------------------------- /console/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zora-console-reporter", 3 | "version": "0.0.1", 4 | "description": "zora reporters which use the console API", 5 | "main": "dist/index", 6 | "module": "dist/index.mjs", 7 | "types": "dist/declarations/index.d.ts", 8 | "directories": { 9 | "test": "test" 10 | }, 11 | "scripts": { 12 | "test": "../node_modules/.bin/pta", 13 | "build:compile": "../node_modules/.bin/tsc", 14 | "build:bundle": "../node_modules/.bin/rollup -c rollup.js", 15 | "build": "npm run build:compile && npm run build:bundle", 16 | "build:test": "../node_modules/.bin/tsc --build test/tsconfig.json" 17 | }, 18 | "files": [ 19 | "dist" 20 | ], 21 | "keywords": [ 22 | "zora", 23 | "reporter", 24 | "test", 25 | "testing", 26 | "console", 27 | "tool", 28 | "unit" 29 | ], 30 | "author": "Laurent RENARD", 31 | "license": "MIT", 32 | "devDependencies": {} 33 | } 34 | -------------------------------------------------------------------------------- /zora/src/protocol.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AssertionMessage, 3 | AssertionResult, 4 | BailoutMessage, 5 | MessageType, 6 | StartTestMessage, 7 | Test, 8 | TestEndMessage 9 | } from './interfaces'; 10 | 11 | export const startTestMessage = (test: { description }, offset: number): StartTestMessage => ({ 12 | type: MessageType.TEST_START, 13 | data: test, 14 | offset 15 | }); 16 | 17 | export const assertionMessage = (assertion: Test | AssertionResult, offset: number): AssertionMessage => ({ 18 | type: MessageType.ASSERTION, 19 | data: assertion, 20 | offset 21 | }); 22 | 23 | export const endTestMessage = (test: Test, offset: number): TestEndMessage => ({ 24 | type: MessageType.TEST_END, 25 | data: test, 26 | offset 27 | }); 28 | 29 | export const bailout = (error: Error, offset: number): BailoutMessage => ({ 30 | type: MessageType.BAIL_OUT, 31 | data: error, 32 | offset 33 | }); 34 | -------------------------------------------------------------------------------- /zora/test/samples/output/nested.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Subtest: tester 1 3 | ok 1 - assert1 4 | # Subtest: some nested tester 5 | ok 1 - nested 1 6 | ok 2 - nested 2 7 | 1..2 8 | ok 2 - some nested tester # {TIME} 9 | # Subtest: some nested tester bis 10 | ok 1 - nested 1 11 | # Subtest: deeply nested 12 | ok 1 - deeply nested really 13 | ok 2 - deeply nested again 14 | 1..2 15 | ok 2 - deeply nested # {TIME} 16 | ok 3 - nested 2 17 | 1..3 18 | ok 3 - some nested tester bis # {TIME} 19 | ok 4 - assert2 20 | 1..4 21 | ok 1 - tester 1 # {TIME} 22 | # Subtest: tester 2 23 | ok 1 - assert3 24 | # Subtest: nested in two 25 | ok 1 - still happy 26 | 1..1 27 | ok 2 - nested in two # {TIME} 28 | ok 3 - assert4 29 | 1..3 30 | ok 2 - tester 2 # {TIME} 31 | 1..2 32 | 33 | # ok 34 | # success: 11 35 | # skipped: 0 36 | # failure: 0 37 | -------------------------------------------------------------------------------- /zora/test/samples/output/nested_async.indent.txt: -------------------------------------------------------------------------------- 1 | TAP version 13 2 | # Subtest: tester 1 3 | # Subtest: update counter with delay 4 | ok 1 - nested 1 5 | ok 2 - nested 2 6 | 1..2 7 | ok 1 - update counter with delay # {TIME} 8 | # Subtest: check counter 9 | ok 1 - should see the old value of the counter 10 | 1..1 11 | ok 2 - check counter # {TIME} 12 | ok 3 - assert2 13 | 1..3 14 | ok 1 - tester 1 # {TIME} 15 | # Subtest: tester 2 16 | ok 1 - assert3 17 | # Subtest: update counter with delay but blocking 18 | ok 1 - nested 1 19 | ok 2 - nested 2 20 | 1..2 21 | ok 2 - update counter with delay but blocking # {TIME} 22 | # Subtest: check counter bis 23 | ok 1 - should see the new value of the counter 24 | 1..1 25 | ok 3 - check counter bis # {TIME} 26 | ok 4 - whatever 27 | 1..4 28 | ok 2 - tester 2 # {TIME} 29 | 1..2 30 | 31 | # ok 32 | # success: 9 33 | # skipped: 0 34 | # failure: 0 35 | -------------------------------------------------------------------------------- /tap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zora-tap-reporter", 3 | "version": "2.0.0", 4 | "description": "TAP reporters for zora testing library", 5 | "main": "dist/index", 6 | "module": "dist/module.js", 7 | "types": "dist/declarations/index.d.ts", 8 | "directories": { 9 | "test": "test" 10 | }, 11 | "scripts": { 12 | "build:compile": "../node_modules/.bin/tsc", 13 | "build:bundle": "../node_modules/.bin/rollup -c rollup.js", 14 | "build": "npm run build:compile && npm run build:bundle", 15 | "update-test-suite": "node scripts/download-zora-tests.js", 16 | "test": "node ./test/samples/index.js" 17 | }, 18 | "keywords": [ 19 | "zora", 20 | "tap", 21 | "reporter", 22 | "test", 23 | "testing", 24 | "bdd", 25 | "unit", 26 | "tool", 27 | "productivity", 28 | "browser", 29 | "nodejs" 30 | ], 31 | "author": "Laurent RENARD", 32 | "license": "MIT", 33 | "files": [ 34 | "dist" 35 | ], 36 | "devDependencies": { 37 | "tape": "^4.11.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /console/test/raw.js: -------------------------------------------------------------------------------- 1 | import { rawReporter } from '../src'; 2 | const fakeOutputStream = () => { 3 | const buffer = []; 4 | return { 5 | log(val) { 6 | buffer.push(val); 7 | }, 8 | compare(assert, expected) { 9 | return assert.eq(buffer.map(i => JSON.parse(i)), expected); 10 | } 11 | }; 12 | }; 13 | export default (t) => { 14 | t.test(`dump messages as they enter`, async (t) => { 15 | const out = fakeOutputStream(); 16 | //@ts-ignore 17 | const report = rawReporter(out); 18 | const error = new Error('an error'); 19 | const messages = [ 20 | { type: 'TEST_START', data: 1 }, 21 | { type: 'TEST_START', data: 2 }, 22 | { type: 'ASSERTION', data: 4 }, 23 | { type: 'BAIL_OUT', data: error }, 24 | { type: 'TEST_END', data: 3 }, 25 | { type: 'TEST_END', data: -1 } 26 | ]; 27 | //@ts-ignore 28 | await report(messages); 29 | out.compare(t, messages); 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /console/test/raw.ts: -------------------------------------------------------------------------------- 1 | import {rawReporter} from '../src'; 2 | import {Assert} from 'zora'; 3 | 4 | const fakeOutputStream = () => { 5 | const buffer: any[] = []; 6 | return { 7 | log(val: any) { 8 | buffer.push(val); 9 | }, 10 | compare(assert: Assert, expected: any) { 11 | return assert.eq(buffer.map(i => JSON.parse(i)), expected); 12 | } 13 | }; 14 | }; 15 | 16 | export default (t: Assert) => { 17 | t.test(`dump messages as they enter`, async t => { 18 | const out = fakeOutputStream(); 19 | //@ts-ignore 20 | const report = rawReporter(out); 21 | const error = new Error('an error'); 22 | const messages = [ 23 | {type: 'TEST_START', data: 1}, 24 | {type: 'TEST_START', data: 2}, 25 | {type: 'ASSERTION', data: 4}, 26 | {type: 'BAIL_OUT', data: error}, 27 | {type: 'TEST_END', data: 3}, 28 | {type: 'TEST_END', data: -1} 29 | ]; 30 | //@ts-ignore 31 | await report(messages); 32 | 33 | out.compare(t, messages); 34 | }); 35 | }; -------------------------------------------------------------------------------- /node/readme.md: -------------------------------------------------------------------------------- 1 | # zora-node-reporter 2 | 3 | A reporter which takes advantage of TTYs to create very informative, straight to the point reports: 4 | 5 | 1. A test files diagnostic 6 | 2. A diagnostic per failing assertion (with location, semantic structure, and detailed difference between expected and actual value) 7 | 3. A summary counter. 8 | 9 |
10 | Report screen shot 11 | 12 | ![test report screen shot](./media/test_report.png) 13 | 14 |
15 | 16 | ## installation 17 | 18 | ``npm install zora-node-reporter`` 19 | 20 | ## usage 21 | 22 | In a **Nodejs** environment 23 | 24 | ```javascript 25 | import {createHarness} from 'zora'; 26 | import {reporter} from 'zora-node-reporter'; 27 | 28 | const h = createHarness(); 29 | 30 | const {test} = h; 31 | 32 | test(`hello world`, t => { 33 | t.ok(true); 34 | 35 | t.test('nested', t => { 36 | t.eq('foo', 'fob'); 37 | }); 38 | }); 39 | 40 | test(`hello world`, t => { 41 | t.ok(true); 42 | 43 | t.test('nested', t => { 44 | t.eq('foo', 'fob'); 45 | }); 46 | }); 47 | 48 | h.report(reporter()); 49 | ``` -------------------------------------------------------------------------------- /zora/test/samples/cases/nested_async.js: -------------------------------------------------------------------------------- 1 | import {test} from '../../../dist/bundle/module.js'; 2 | 3 | const wait = time => new Promise(resolve => { 4 | setTimeout(() => resolve(), time); 5 | }); 6 | 7 | test('tester 1', async t => { 8 | 9 | let counter = 0; 10 | 11 | t.test('update counter with delay', async t => { 12 | t.ok(true, 'nested 1'); 13 | await wait(100); 14 | counter++; 15 | t.ok(true, 'nested 2'); 16 | }); 17 | 18 | t.test('check counter', t => { 19 | t.equal(counter, 0, 'should see the old value of the counter'); 20 | }); 21 | 22 | t.ok(true, 'assert2'); 23 | }); 24 | 25 | test('tester 2', async t => { 26 | let counter = 0; 27 | t.ok(true, 'assert3'); 28 | 29 | await t.test('update counter with delay but blocking', async t => { 30 | t.ok(true, 'nested 1'); 31 | await wait(100); 32 | counter++; 33 | t.ok(true, 'nested 2'); 34 | }); 35 | 36 | t.test('check counter bis', t => { 37 | t.equal(counter, 1, 'should see the new value of the counter'); 38 | }); 39 | 40 | t.ok(true, 'whatever'); 41 | }); 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 RENARD Laurent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /zora/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 RENARD Laurent 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /zora/src/harness.ts: -------------------------------------------------------------------------------- 1 | import {assert} from './assertion'; 2 | import {mochaTapLike, tapeTapLike} from './reporter'; 3 | import {TestHarness, TestHarnessConfiguration} from './interfaces'; 4 | import {testerLikeProvider, TesterPrototype} from './commons'; 5 | 6 | export const harnessFactory = ({runOnly = false, indent = false}: TestHarnessConfiguration = { 7 | runOnly: false, 8 | indent: false 9 | }): TestHarness => { 10 | const tests = []; 11 | const rootOffset = 0; 12 | const collect = item => tests.push(item); 13 | const api = assert(collect, rootOffset, runOnly); 14 | let error = null; 15 | 16 | const factory = testerLikeProvider(Object.assign(api, TesterPrototype, { 17 | report: async function (reporter) { 18 | const rep = reporter || (indent ? mochaTapLike : tapeTapLike); 19 | return rep(this); 20 | } 21 | })); 22 | 23 | return Object.defineProperties(factory(tests, Promise.resolve(), rootOffset), { 24 | error: { 25 | get() { 26 | return error; 27 | }, 28 | set(val) { 29 | error = val; 30 | } 31 | } 32 | }); 33 | }; -------------------------------------------------------------------------------- /node/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zora-node-reporter", 3 | "version": "0.0.1", 4 | "description": "A reporter for zora which targets nodejs environment", 5 | "main": "dist/index", 6 | "types": "dist/declarations/index.d.ts", 7 | "module": "dist/index.mjs", 8 | "directories": { 9 | "test": "test" 10 | }, 11 | "scripts": { 12 | "build:compile": "./node_modules/.bin/tsc", 13 | "build:bundle": "./node_modules/.bin/rollup -c rollup.js", 14 | "build:test": "./node_modules/.bin/tsc --build test/tsconfig.json", 15 | "build": "npm run build:compile && npm run build:bundle", 16 | "test": "./node_modules/.bin/pta test/*.js" 17 | }, 18 | "keywords": [ 19 | "zora", 20 | "reporter", 21 | "nodejs", 22 | "test", 23 | "unit", 24 | "testing", 25 | "cli", 26 | "tool" 27 | ], 28 | "files": [ 29 | "dist" 30 | ], 31 | "author": "Laurent RENARD", 32 | "license": "MIT", 33 | "devDependencies": { 34 | "diff": "^4.0.1", 35 | "kleur": "^3.0.3", 36 | "rollup": "^1.27.9", 37 | "rollup-plugin-commonjs": "^10.1.0", 38 | "rollup-plugin-node-resolve": "^5.2.0", 39 | "typescript": "^3.7.3" 40 | }, 41 | "dependencies": {} 42 | } 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | dist 64 | .idea 65 | -------------------------------------------------------------------------------- /node/src/output-stream.ts: -------------------------------------------------------------------------------- 1 | import {EOL} from 'os'; 2 | import {Direction, WriteStream} from 'tty'; 3 | import {Theme} from './theme'; 4 | 5 | interface TTY { 6 | clearLine(dir: Direction, callback?: () => void): boolean; 7 | 8 | cursorTo(x: number, y?: number, callback?: () => void): boolean; 9 | 10 | cursorTo(x: number, callback: () => void): boolean; 11 | 12 | moveCursor(dx: number, dy: number, callback?: () => void): boolean; 13 | 14 | write(str: string): void; 15 | 16 | width: number; 17 | } 18 | 19 | export const delegate = (...methods: K[]) => (target: T): Pick => { 20 | const output = {} as Pick; 21 | 22 | for (const m of methods) { 23 | // @ts-ignore 24 | output[m] = (...args: unknown[]) => target[m](...args); 25 | } 26 | 27 | return output; 28 | }; 29 | 30 | // @ts-ignore 31 | const delegateTTY = delegate('write', 'clearLine', 'cursorTo', 'moveCursor'); 32 | 33 | export interface Output extends TTY, Theme { 34 | writeLine(message: string, padding?: number): void; 35 | 36 | writeBlock(message: string, padding?: number): void; 37 | } 38 | 39 | export const output = (stream: WriteStream): Output => { 40 | return Object.assign(delegateTTY(stream), { 41 | writeLine(message = '', padding = 0) { 42 | this.write(' '.repeat(padding) + message + EOL); 43 | }, 44 | writeBlock(message: string, padding = 0) { 45 | this.writeLine(); 46 | this.writeLine(message, padding); 47 | } 48 | }); 49 | }; -------------------------------------------------------------------------------- /node/src/theme.js: -------------------------------------------------------------------------------- 1 | import kleur from 'kleur'; 2 | export const theme = ({ bgGreen, bgRed, bgYellow, green, red, cyan, gray, yellow, bold, underline } = kleur) => ({ 3 | emphasis(message) { 4 | return underline().bold(message); 5 | }, 6 | successBadge(message) { 7 | return bgGreen().black().bold(message); 8 | }, 9 | failureBadge(message) { 10 | return bgRed().black().bold(message); 11 | }, 12 | skipBadge(m) { 13 | return bgYellow().black().bold(m); 14 | }, 15 | path(message) { 16 | const [first, ...rest] = message.split('/').reverse(); 17 | return underline(gray(rest.reverse().join('/')) + '/' + first); 18 | }, 19 | operator(operator) { 20 | return yellow(`${gray('[')} ${operator} ${gray(']')}`); 21 | }, 22 | adornment(symbol) { 23 | return gray(symbol); 24 | }, 25 | stackTrace(stack) { 26 | return cyan().underline(stack.trim()); 27 | }, 28 | summaryPass(count) { 29 | return green(`${bold('✔ PASS')}: ${count}`); 30 | }, 31 | summarySkip(count) { 32 | return yellow(`${bold('⚠ SKIP')}: ${count}`); 33 | }, 34 | summaryFail(count) { 35 | return red(`${bold('✔ FAIL')}: ${count}`); 36 | }, 37 | error(value) { 38 | return red(value); 39 | }, 40 | success(value) { 41 | return green(value); 42 | }, 43 | diffSame(val) { 44 | return gray(val); 45 | }, 46 | diffRemove(val) { 47 | return bgRed().black(val); 48 | }, 49 | diffAdd(val) { 50 | return bgGreen().black(val); 51 | } 52 | }); 53 | export const paint = theme(); 54 | -------------------------------------------------------------------------------- /console/src/console.js: -------------------------------------------------------------------------------- 1 | import { isAssertionResult } from '../../common/util'; 2 | const ConsoleReporter = { 3 | printComment(comment) { 4 | this.logger.log(comment); 5 | }, 6 | printBailOut(message) { 7 | }, 8 | printTestStart(message) { 9 | this.logger.group(message.data.description); 10 | }, 11 | printTestEnd(message) { 12 | this.logger.groupEnd(); 13 | }, 14 | printAssertion(message) { 15 | if (isAssertionResult(message.data)) { 16 | if (message.data.pass) { 17 | this.logger.log(message.data.description); 18 | } 19 | else { 20 | const { expected, actual } = message.data; 21 | this.logger.table({ expected, actual }); 22 | } 23 | } 24 | else if (message.data.skip) { 25 | // we log sub test point only when they are skipped 26 | this.logger.warn(`SKIP: ${message.data.description}`); 27 | } 28 | }, 29 | async report(stream) { 30 | for await (const message of stream) { 31 | switch (message.type) { 32 | case "BAIL_OUT" /* BAIL_OUT */: 33 | throw message.data; 34 | case "TEST_START" /* TEST_START */: 35 | this.printTestStart(message); 36 | break; 37 | case "ASSERTION" /* ASSERTION */: 38 | this.printAssertion(message); 39 | break; 40 | case "TEST_END" /* TEST_END */: 41 | this.printTestEnd(message); 42 | break; 43 | } 44 | } 45 | } 46 | }; 47 | export const factory = (logger) => { 48 | return Object.create(ConsoleReporter, { 49 | logger: { value: logger } 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /node/test/util/index.js: -------------------------------------------------------------------------------- 1 | const stubBehavior = { 2 | addCall(...args) { 3 | this.callList.push(...args); 4 | }, 5 | argsFor(call = 0) { 6 | return this.callList[call]; 7 | } 8 | }; 9 | export const stubFn = () => { 10 | const callList = []; 11 | //@ts-ignore 12 | const fn = function (...args) { 13 | fn.addCall(args); 14 | return fn; 15 | }; 16 | Object.assign(fn, stubBehavior); 17 | return Object.defineProperties(fn, { 18 | callList: { value: callList }, 19 | callCount: { 20 | get() { 21 | return callList.length; 22 | } 23 | } 24 | }); 25 | }; 26 | export const flags = Object.freeze({ 27 | black: 1, 28 | cyan: 1 << 1, 29 | gray: 1 << 2, 30 | green: 1 << 3, 31 | red: 1 << 4, 32 | yellow: 1 << 5, 33 | bgGreen: 1 << 6, 34 | bgRed: 1 << 7, 35 | bgYellow: 1 << 8, 36 | bold: 1 << 9, 37 | underline: 1 << 10 38 | }); 39 | export const fakeKleur = () => { 40 | let string = ''; 41 | let flag = 0; 42 | const instance = Object.defineProperties({}, { 43 | flag: { 44 | get() { 45 | return flag; 46 | } 47 | }, 48 | string: { 49 | get() { 50 | return string; 51 | } 52 | }, 53 | style: { 54 | get() { 55 | //@ts-ignore 56 | return Object.keys(flags).filter(k => flag & flags[k]); 57 | } 58 | } 59 | }); 60 | for (const key of Object.keys(flags)) { 61 | instance[key] = function (s) { 62 | string = s !== void 0 ? s : string; 63 | //@ts-ignore 64 | flag |= flags[key]; 65 | return instance; 66 | }; 67 | } 68 | return instance; 69 | }; 70 | -------------------------------------------------------------------------------- /zora/src/test.ts: -------------------------------------------------------------------------------- 1 | import {assert} from './assertion'; 2 | import {Test} from './interfaces'; 3 | import {defaultTestOptions, noop, testerFactory} from './commons'; 4 | 5 | export const tester = (description, spec, {offset = 0, skip = false, runOnly = false} = defaultTestOptions): Test => { 6 | let executionTime = 0; 7 | let error = null; 8 | let done = false; 9 | const assertions = []; 10 | const collect = item => { 11 | if (done) { 12 | throw new Error(`test "${description}" 13 | tried to collect an assertion after it has run to its completion. 14 | You might have forgotten to wait for an asynchronous task to complete 15 | ------ 16 | ${spec.toString()} 17 | `); 18 | } 19 | assertions.push(item); 20 | }; 21 | const specFunction = skip === true ? noop : function zora_spec_fn() { 22 | return spec(assert(collect, offset, runOnly)); 23 | }; 24 | const testRoutine = (async function () { 25 | try { 26 | const start = Date.now(); 27 | const result = await specFunction(); 28 | executionTime = Date.now() - start; 29 | return result; 30 | } catch (e) { 31 | error = e; 32 | } finally { 33 | done = true; 34 | } 35 | })(); 36 | 37 | return Object.defineProperties(testerFactory(assertions, testRoutine, offset), { 38 | error: { 39 | get() { 40 | return error; 41 | }, 42 | set(val) { 43 | error = val; 44 | } 45 | }, 46 | executionTime: { 47 | enumerable: true, 48 | get() { 49 | return executionTime; 50 | } 51 | }, 52 | skip: { 53 | value: skip 54 | }, 55 | description: { 56 | enumerable: true, 57 | value: description 58 | } 59 | }); 60 | }; 61 | -------------------------------------------------------------------------------- /zora/src/counter.ts: -------------------------------------------------------------------------------- 1 | import {isAssertionResult} from './assertion'; 2 | import {AssertionResult, Counter, Test, TestCounter} from './interfaces'; 3 | 4 | export const delegateToCounter = (counter: Counter) => (target: T): T & Counter => Object.defineProperties(target, { 5 | skipCount: { 6 | get() { 7 | return counter.skipCount; 8 | } 9 | }, 10 | failureCount: { 11 | get() { 12 | return counter.failureCount; 13 | } 14 | }, 15 | successCount: { 16 | get() { 17 | return counter.successCount; 18 | } 19 | }, 20 | count: { 21 | get() { 22 | return counter.count; 23 | } 24 | } 25 | }); 26 | 27 | export const counter = (): TestCounter => { 28 | let success = 0; 29 | let failure = 0; 30 | let skip = 0; 31 | return Object.defineProperties({ 32 | update(assertion: Test | AssertionResult) { 33 | const {pass, skip: isSkipped} = assertion; 34 | if (isSkipped) { 35 | skip++; 36 | } else if (!isAssertionResult(assertion)) { 37 | skip += assertion.skipCount; 38 | success += assertion.successCount; 39 | failure += assertion.failureCount; 40 | } else if (pass) { 41 | success++; 42 | } else { 43 | failure++; 44 | } 45 | } 46 | }, { 47 | successCount: { 48 | get() { 49 | return success; 50 | } 51 | }, 52 | failureCount: { 53 | get() { 54 | return failure; 55 | } 56 | }, 57 | skipCount: { 58 | get() { 59 | return skip; 60 | } 61 | }, 62 | count: { 63 | get() { 64 | return skip + success + failure; 65 | } 66 | } 67 | }); 68 | }; 69 | -------------------------------------------------------------------------------- /node/test/util/index.ts: -------------------------------------------------------------------------------- 1 | interface Stub { 2 | addCall(...args: any[]): void; 3 | 4 | argsFor(call?: number): any[]; 5 | } 6 | 7 | const stubBehavior = { 8 | addCall(...args: any[]) { 9 | this.callList.push(...args); 10 | }, 11 | argsFor(call = 0) { 12 | return this.callList[call]; 13 | } 14 | }; 15 | 16 | export const stubFn = () => { 17 | const callList: any[][] = []; 18 | //@ts-ignore 19 | const fn: Stub = function (...args: any[]) { 20 | fn.addCall(args); 21 | return fn; 22 | }; 23 | 24 | Object.assign(fn, stubBehavior); 25 | 26 | return Object.defineProperties(fn, { 27 | callList: {value: callList}, 28 | callCount: { 29 | get() { 30 | return callList.length; 31 | } 32 | } 33 | }); 34 | }; 35 | 36 | export const flags = Object.freeze({ 37 | black: 1, 38 | cyan: 1 << 1, 39 | gray: 1 << 2, 40 | green: 1 << 3, 41 | red: 1 << 4, 42 | yellow: 1 << 5, 43 | bgGreen: 1 << 6, 44 | bgRed: 1 << 7, 45 | bgYellow: 1 << 8, 46 | bold: 1 << 9, 47 | underline: 1 << 10 48 | }); 49 | 50 | export const fakeKleur = () => { 51 | let string = ''; 52 | let flag = 0; 53 | const instance = Object.defineProperties({}, { 54 | flag: { 55 | get() { 56 | return flag; 57 | } 58 | }, 59 | string: { 60 | get() { 61 | return string; 62 | } 63 | }, 64 | style: { 65 | get() { 66 | //@ts-ignore 67 | return Object.keys(flags).filter(k => flag & flags[k]); 68 | } 69 | } 70 | }); 71 | for (const key of Object.keys(flags)) { 72 | instance[key] = function (s: any) { 73 | string = s !== void 0 ? s : string; 74 | //@ts-ignore 75 | flag |= flags[key]; 76 | return instance; 77 | }; 78 | } 79 | return instance; 80 | }; 81 | -------------------------------------------------------------------------------- /console/src/console.ts: -------------------------------------------------------------------------------- 1 | import {Reporter} from '../../tap/src/interfaces'; 2 | import {AssertionMessage, BailoutMessage, Message, MessageType, StartTestMessage, TestEndMessage} from 'zora'; 3 | import {isAssertionResult} from '../../common/util'; 4 | 5 | const ConsoleReporter = { 6 | printComment(comment: string) { 7 | this.logger.log(comment); 8 | }, 9 | printBailOut(message: BailoutMessage) { 10 | }, 11 | printTestStart(message: StartTestMessage) { 12 | this.logger.group(message.data.description); 13 | }, 14 | printTestEnd(message: TestEndMessage): void { 15 | this.logger.groupEnd(); 16 | }, 17 | printAssertion(message: AssertionMessage): void { 18 | if (isAssertionResult(message.data)) { 19 | if (message.data.pass) { 20 | this.logger.log(message.data.description); 21 | } else { 22 | const {expected, actual} = message.data; 23 | this.logger.table({expected, actual}); 24 | } 25 | } else if (message.data.skip) { 26 | // we log sub test point only when they are skipped 27 | this.logger.warn(`SKIP: ${message.data.description}`); 28 | } 29 | }, 30 | async report(stream: AsyncIterable>): Promise { 31 | for await (const message of stream) { 32 | switch (message.type) { 33 | case MessageType.BAIL_OUT: 34 | throw message.data; 35 | case MessageType.TEST_START: 36 | this.printTestStart(message); 37 | break; 38 | case MessageType.ASSERTION: 39 | this.printAssertion(message); 40 | break; 41 | case MessageType.TEST_END: 42 | this.printTestEnd(message); 43 | break; 44 | } 45 | } 46 | } 47 | }; 48 | 49 | export const factory = (logger: Console): Reporter => { 50 | return Object.create(ConsoleReporter, { 51 | logger: {value: logger} 52 | }); 53 | }; -------------------------------------------------------------------------------- /node/src/test.js: -------------------------------------------------------------------------------- 1 | const TestFilePrototype = { 2 | [Symbol.iterator]() { 3 | return this.failureList[Symbol.iterator](); 4 | }, 5 | incrementSuccess() { 6 | this.success++; 7 | }, 8 | incrementFailure() { 9 | this.failure++; 10 | }, 11 | incrementSkip() { 12 | this.skip++; 13 | }, 14 | writeLine() { 15 | this.out.clearLine(0); 16 | this.out.cursorTo(0); 17 | const statusSymbol = (this.failure > 0 ? ' ✖' : (this.skip > 0 ? ' ⚠' : ' ✔')); 18 | const style = (this.failure > 0 ? 'failureBadge' : (this.skip > 0 ? 'skipBadge' : 'successBadge')); 19 | let summaryString = `${this.success}/${this.total} `; 20 | summaryString = `${statusSymbol}${summaryString.padStart(8)}`; 21 | this.out.writeLine(`${this.out[style](summaryString)} ${this.out.path(this.file)}`, 1); 22 | }, 23 | goIn(path) { 24 | this.path.push(path); 25 | }, 26 | goOut() { 27 | this.path.pop(); 28 | }, 29 | addFailure(data) { 30 | const path = [...this.path]; 31 | this.failureList.push({ path, data }); 32 | } 33 | }; 34 | export const test = (file, out) => { 35 | let success = 0; 36 | let failure = 0; 37 | let skip = 0; 38 | const path = [file]; 39 | const failureList = []; 40 | return Object.create(TestFilePrototype, { 41 | file: { 42 | value: file 43 | }, 44 | out: { 45 | value: out 46 | }, 47 | total: { 48 | get() { 49 | return success + failure + skip; 50 | } 51 | }, 52 | success: { 53 | get() { 54 | return success; 55 | }, 56 | set(val) { 57 | success = val; 58 | } 59 | }, 60 | failure: { 61 | get() { 62 | return failure; 63 | }, 64 | set(val) { 65 | failure = val; 66 | } 67 | }, 68 | skip: { 69 | get() { 70 | return skip; 71 | }, 72 | set(val) { 73 | skip = val; 74 | } 75 | }, 76 | path: { value: path }, 77 | failureList: { value: failureList } 78 | }); 79 | }; 80 | -------------------------------------------------------------------------------- /zora/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zora", 3 | "version": "3.1.8", 4 | "description": "tap test harness for nodejs and browsers", 5 | "repository": "github:lorenzofox3/zora", 6 | "keywords": [ 7 | "tap", 8 | "test", 9 | "node", 10 | "browsers", 11 | "testing", 12 | "tests", 13 | "harness", 14 | "tap-producer" 15 | ], 16 | "main": "./dist/bundle/index", 17 | "module": "./dist/bundle/module.js", 18 | "types": "./dist/declarations/index.d.ts", 19 | "files": [ 20 | "dist/bundle", 21 | "dist/declarations" 22 | ], 23 | "scripts": { 24 | "build:clean": "rm -rf ./dist && mkdir -p ./dist/bundle ./dist/declarations", 25 | "build:compile": "tsc", 26 | "build:bundle": "rollup -c ./rollup/build.js", 27 | "build": "npm run build:clean && npm run build:compile && npm run build:bundle", 28 | "bench:clean": "rm -r ./benchmarks && mkdir -p ./benchmarks/zora/test/ ./benchmarks/ava/test ./benchmarks/jest/test ./benchmarks/mocha/test ./benchmarks/tape/test", 29 | "bench:build": "npm run bench:clean && node ./scripts/generate.js", 30 | "bench:zora": "time node ./benchmarks/zora/index.js", 31 | "bench:ava": "time ava ./benchmarks/ava/test/*.js", 32 | "bench:mocha": "time mocha ./benchmarks/mocha/test/", 33 | "bench:tape": "time node ./benchmarks/tape/index", 34 | "bench:jest": "time jest", 35 | "bench:pta": "time pta benchmarks/zora/test/*.js", 36 | "test:unit": "rollup -c ./rollup/test.js | node", 37 | "test:sample": "node ./test/samples/index.js", 38 | "test": "npm run test:unit && npm run test:sample", 39 | "dev": "npm run build:compile -- -w" 40 | }, 41 | "author": { 42 | "name": "Laurent Renard", 43 | "email": "laurent34azerty@gmail.com" 44 | }, 45 | "license": "MIT", 46 | "dependencies": {}, 47 | "devDependencies": { 48 | "ava": "^3.0.0", 49 | "fast-deep-equal": "^3.1.1", 50 | "jest": "^25.1.0", 51 | "mocha": "^7.0.0", 52 | "pta": "^0.1.3", 53 | "rollup": "^1.29.1", 54 | "rollup-plugin-commonjs": "^10.1.0", 55 | "rollup-plugin-node-resolve": "^5.2.0", 56 | "tape": "^4.13.0", 57 | "typescript": "^3.7.5", 58 | "zora-tap-reporter": "^2.0.0" 59 | }, 60 | "jest": { 61 | "testRegex": "test\\d+.js", 62 | "roots": [ 63 | "./benchmarks/jest/test" 64 | ], 65 | "testEnvironment": "node" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /zora/test/samples/index.js: -------------------------------------------------------------------------------- 1 | const {spawnSync} = require('child_process'); 2 | const path = require('path'); 3 | const test = require('tape'); 4 | const {readdirSync, readFileSync} = require('fs'); 5 | const node = process.execPath; 6 | 7 | const sampleRoot = path.resolve(process.cwd(), './test/samples/cases/'); 8 | const files = readdirSync(sampleRoot) 9 | .filter(f => f.split('.').reverse()[0] === 'js' && f !== 'late_collect.js'); // late collect will be checked separately 10 | 11 | for (const f of files) { 12 | test(`sample output: ${f}`, t => { 13 | const cp = spawnSync(node, ['-r', 'esm', f], { 14 | cwd: sampleRoot, 15 | stdio: ['pipe', 'pipe', 'ignore'], 16 | env: {RUN_ONLY: f.startsWith('only')} 17 | }); 18 | const actualOutput = cp.stdout.toString() 19 | .replace(/at:.*/g, 'at:{STACK}'); 20 | const outputFile = `../output/${[f.split('.')[0], 'txt'].join('.')}`; 21 | const expectedOutput = readFileSync(path.resolve(sampleRoot, outputFile), {encoding: 'utf8'}); 22 | t.equal(actualOutput, expectedOutput); 23 | t.end(); 24 | }); 25 | 26 | test(`sample output indented: ${f}`, t => { 27 | const cp = spawnSync(node, ['-r', 'esm', f], { 28 | cwd: sampleRoot, 29 | stdio: ['pipe', 'pipe', 'pipe'], 30 | env: { 31 | RUN_ONLY: f.startsWith('only'), 32 | INDENT: true 33 | } 34 | }); 35 | const actualOutput = cp.stdout.toString() 36 | .replace(/at:.*/g, 'at:{STACK}') 37 | .replace(/[0-9]+ms/g, '{TIME}'); 38 | const outputFile = `../output/${[f.split('.')[0], 'indent', 'txt'].join('.')}`; 39 | const expectedOutput = readFileSync(path.resolve(sampleRoot, outputFile), {encoding: 'utf8'}); 40 | t.equal(actualOutput, expectedOutput); 41 | t.end(); 42 | }); 43 | } 44 | 45 | test(`late collect should report an error on stderr`, t => { 46 | const cp = spawnSync(node, ['-r', 'esm', 'late_collect.js'], { 47 | cwd: sampleRoot, 48 | stdio: ['pipe', 'pipe', 'pipe'] 49 | }); 50 | const actualOutput = cp.stderr.toString(); 51 | t.ok(actualOutput.startsWith(`Error: test "late collection" 52 | tried to collect an assertion after it has run to its completion. 53 | You might have forgotten to wait for an asynchronous task to complete 54 | ------ 55 | async t => { 56 | t.ok(true); 57 | 58 | setTimeout(() => { 59 | t.ok(true); 60 | }, 50); 61 | }`)); 62 | t.end(); 63 | }); -------------------------------------------------------------------------------- /zora/src/commons.ts: -------------------------------------------------------------------------------- 1 | import {assertionMessage, bailout, endTestMessage, startTestMessage} from './protocol'; 2 | import {counter, delegateToCounter} from './counter'; 3 | import {Message} from './interfaces'; 4 | 5 | export const defaultTestOptions = Object.freeze({ 6 | offset: 0, 7 | skip: false, 8 | runOnly: false 9 | }); 10 | 11 | export const noop = () => { 12 | }; 13 | 14 | export const TesterPrototype = { 15 | [Symbol.asyncIterator]: async function* () { 16 | await this.routine; 17 | for (const assertion of this.assertions) { 18 | if (assertion[Symbol.asyncIterator]) { 19 | // Sub test 20 | yield startTestMessage({description: assertion.description}, this.offset); 21 | yield* assertion; 22 | if (assertion.error !== null) { 23 | // Bubble up the error and return 24 | this.error = assertion.error; 25 | this.pass = false; 26 | return; 27 | } 28 | } 29 | yield assertionMessage(assertion, this.offset); 30 | this.pass = this.pass && assertion.pass; 31 | this.counter.update(assertion); 32 | } 33 | 34 | return this.error !== null ? 35 | yield bailout(this.error, this.offset) : 36 | yield endTestMessage(this, this.offset); 37 | } 38 | }; 39 | 40 | export const testerLikeProvider = (BaseProto = TesterPrototype) => (assertions: Array, routine: Promise, offset: number) => { 41 | const testCounter = counter(); 42 | const withTestCounter = delegateToCounter(testCounter); 43 | let pass = true; 44 | 45 | return withTestCounter(Object.create(BaseProto, { 46 | routine: { 47 | value: routine 48 | }, 49 | assertions: { 50 | value: assertions 51 | }, 52 | offset: { 53 | value: offset 54 | }, 55 | counter: { 56 | value: testCounter 57 | }, 58 | length: { 59 | get() { 60 | return assertions.length; 61 | } 62 | }, 63 | pass: { 64 | enumerable: true, 65 | get() { 66 | return pass; 67 | }, 68 | set(val) { 69 | pass = val; 70 | } 71 | } 72 | })); 73 | 74 | }; 75 | 76 | export const testerFactory = testerLikeProvider(); 77 | 78 | export const map = fn => async function* (stream: AsyncIterable>) { 79 | for await (const m of stream) { 80 | yield fn(m); 81 | } 82 | }; -------------------------------------------------------------------------------- /node/src/theme.ts: -------------------------------------------------------------------------------- 1 | import kleur from 'kleur'; 2 | 3 | type primitive = string | boolean | number; 4 | 5 | export interface Theme { 6 | emphasis(message: primitive): string; 7 | 8 | successBadge(message: primitive): string; 9 | 10 | failureBadge(message: primitive): string; 11 | 12 | skipBadge(message: primitive): string; 13 | 14 | path(message: primitive): string; 15 | 16 | operator(operator: primitive): string; 17 | 18 | adornment(symbol: primitive): string; 19 | 20 | stackTrace(stack: primitive): string; 21 | 22 | summaryPass(count: number): string; 23 | 24 | summarySkip(count: number): string 25 | 26 | summaryFail(count: number): string 27 | 28 | error(value: primitive): string 29 | 30 | success(value: primitive): string 31 | 32 | diffSame(val: primitive): string 33 | 34 | diffRemove(val: primitive): string 35 | 36 | diffAdd(val: primitive): string 37 | } 38 | 39 | export const theme = ({ 40 | bgGreen, 41 | bgRed, 42 | bgYellow, 43 | green, 44 | red, 45 | cyan, 46 | gray, 47 | yellow, 48 | bold, 49 | underline 50 | }: kleur.Kleur = kleur): Theme => ({ 51 | emphasis(message: string) { 52 | return underline().bold(message); 53 | }, 54 | successBadge(message: string) { 55 | return bgGreen().black().bold(message); 56 | }, 57 | failureBadge(message: string) { 58 | return bgRed().black().bold(message); 59 | }, 60 | skipBadge(m: string) { 61 | return bgYellow().black().bold(m); 62 | }, 63 | path(message: string) { 64 | const [first, ...rest] = message.split('/').reverse(); 65 | return underline(gray(rest.reverse().join('/')) + '/' + first); 66 | }, 67 | operator(operator: string) { 68 | return yellow(`${gray('[')} ${operator} ${gray(']')}`); 69 | }, 70 | adornment(symbol: string) { 71 | return gray(symbol); 72 | }, 73 | stackTrace(stack: string) { 74 | return cyan().underline(stack.trim()); 75 | }, 76 | summaryPass(count: number) { 77 | return green(`${bold('✔ PASS')}: ${count}`); 78 | }, 79 | summarySkip(count: number) { 80 | return yellow(`${bold('⚠ SKIP')}: ${count}`); 81 | }, 82 | summaryFail(count: number) { 83 | return red(`${bold('✔ FAIL')}: ${count}`); 84 | }, 85 | error(value: string) { 86 | return red(value); 87 | }, 88 | success(value: string) { 89 | return green(value); 90 | }, 91 | diffSame(val: string) { 92 | return gray(val); 93 | }, 94 | diffRemove(val: string) { 95 | return bgRed().black(val); 96 | }, 97 | diffAdd(val: string) { 98 | return bgGreen().black(val); 99 | } 100 | }); 101 | 102 | export const paint = theme(); -------------------------------------------------------------------------------- /node/src/test.ts: -------------------------------------------------------------------------------- 1 | import {AssertionResult} from 'zora'; 2 | import {Output} from './output-stream'; 3 | 4 | export interface Failure { 5 | path: string; 6 | data: AssertionResult; 7 | } 8 | 9 | export interface Test extends Iterable { 10 | incrementSuccess(): void; 11 | 12 | incrementFailure(): void; 13 | 14 | incrementSkip(): void; 15 | 16 | writeLine(): void; 17 | 18 | goIn(path: string): void; 19 | 20 | goOut(path: string): void; 21 | 22 | addFailure(failure: Failure): void; 23 | 24 | readonly success: number; 25 | readonly failure: number; 26 | readonly skip: number 27 | } 28 | 29 | const TestFilePrototype = { 30 | [Symbol.iterator]() { 31 | return this.failureList[Symbol.iterator](); 32 | }, 33 | incrementSuccess() { 34 | this.success++; 35 | }, 36 | incrementFailure() { 37 | this.failure++; 38 | }, 39 | incrementSkip() { 40 | this.skip++; 41 | }, 42 | writeLine() { 43 | this.out.clearLine(0); 44 | this.out.cursorTo(0); 45 | 46 | const statusSymbol = (this.failure > 0 ? ' ✖' : (this.skip > 0 ? ' ⚠' : ' ✔')); 47 | const style = (this.failure > 0 ? 'failureBadge' : (this.skip > 0 ? 'skipBadge' : 'successBadge')); 48 | 49 | let summaryString = `${this.success}/${this.total} `; 50 | 51 | summaryString = `${statusSymbol}${summaryString.padStart(8)}`; 52 | 53 | this.out.writeLine(`${this.out[style](summaryString)} ${this.out.path(this.file)}`, 1); 54 | }, 55 | goIn(path: string) { 56 | this.path.push(path); 57 | }, 58 | goOut() { 59 | this.path.pop(); 60 | }, 61 | addFailure(data: string) { 62 | const path = [...this.path]; 63 | this.failureList.push({path, data}); 64 | } 65 | }; 66 | 67 | export const test = (file: string, out: Output) => { 68 | let success = 0; 69 | let failure = 0; 70 | let skip = 0; 71 | const path = [file]; 72 | const failureList: Failure[] = []; 73 | 74 | return Object.create(TestFilePrototype, { 75 | file: { 76 | value: file 77 | }, 78 | out: { 79 | value: out 80 | }, 81 | total: { 82 | get() { 83 | return success + failure + skip; 84 | } 85 | }, 86 | success: { 87 | get() { 88 | return success; 89 | }, 90 | set(val) { 91 | success = val; 92 | } 93 | }, 94 | failure: { 95 | get() { 96 | return failure; 97 | }, 98 | set(val) { 99 | failure = val; 100 | } 101 | }, 102 | skip: { 103 | get() { 104 | return skip; 105 | }, 106 | set(val) { 107 | skip = val; 108 | } 109 | }, 110 | path: {value: path}, 111 | failureList: {value: failureList} 112 | }); 113 | }; -------------------------------------------------------------------------------- /node/test/theme.js: -------------------------------------------------------------------------------- 1 | import { theme } from '../src/theme'; 2 | import { fakeKleur } from './util'; 3 | export default (t) => { 4 | t.test(`theme.emphasis: bold && underline`, t => { 5 | t.ok(true); 6 | const kleur = fakeKleur(); 7 | const c = theme(kleur); 8 | c.emphasis('hello'); 9 | t.eq(kleur.string, 'hello'); 10 | t.eq(kleur.style, ['bold', 'underline']); 11 | }); 12 | t.test(`theme.successBadge: bgGreen && black && bold`, t => { 13 | t.ok(true); 14 | const kleur = fakeKleur(); 15 | const c = theme(kleur); 16 | c.successBadge('success'); 17 | t.eq(kleur.string, 'success'); 18 | t.eq(kleur.style, ['black', 'bgGreen', 'bold']); 19 | }); 20 | t.test(`theme.failureBadge: bgRed && black && bold`, t => { 21 | t.ok(true); 22 | const kleur = fakeKleur(); 23 | const c = theme(kleur); 24 | c.failureBadge('failure'); 25 | t.eq(kleur.string, 'failure'); 26 | t.eq(kleur.style, ['black', 'bgRed', 'bold']); 27 | }); 28 | t.test(`theme.skipBadge: bgYello && black && bold`, t => { 29 | t.ok(true); 30 | const kleur = fakeKleur(); 31 | const c = theme(kleur); 32 | c.skipBadge('skip'); 33 | t.eq(kleur.string, 'skip'); 34 | t.eq(kleur.style, ['black', 'bgYellow', 'bold']); 35 | }); 36 | t.test(`theme.adornment`, t => { 37 | t.ok(true); 38 | const kleur = fakeKleur(); 39 | const c = theme(kleur); 40 | c.adornment('beh'); 41 | t.eq(kleur.string, 'beh'); 42 | t.eq(kleur.style, ['gray']); 43 | }); 44 | t.test(`theme.stacktrace`, t => { 45 | t.ok(true); 46 | const kleur = fakeKleur(); 47 | const c = theme(kleur); 48 | c.stackTrace(' stackwithpadding '); 49 | t.eq(kleur.string, 'stackwithpadding'); 50 | t.eq(kleur.style, ['cyan', 'underline']); 51 | }); 52 | t.test(`theme.error`, t => { 53 | t.ok(true); 54 | const kleur = fakeKleur(); 55 | const c = theme(kleur); 56 | c.error('woot woot'); 57 | t.eq(kleur.string, 'woot woot'); 58 | t.eq(kleur.style, ['red']); 59 | }); 60 | t.test(`theme.success`, t => { 61 | t.ok(true); 62 | const kleur = fakeKleur(); 63 | const c = theme(kleur); 64 | c.success('woot'); 65 | t.eq(kleur.string, 'woot'); 66 | t.eq(kleur.style, ['green']); 67 | }); 68 | t.test(`theme.diffSame`, t => { 69 | t.ok(true); 70 | const kleur = fakeKleur(); 71 | const c = theme(kleur); 72 | c.diffSame('blah'); 73 | t.eq(kleur.string, 'blah'); 74 | t.eq(kleur.style, ['gray']); 75 | }); 76 | t.test(`theme.diffRemove`, t => { 77 | t.ok(true); 78 | const kleur = fakeKleur(); 79 | const c = theme(kleur); 80 | c.diffRemove('blah'); 81 | t.eq(kleur.string, 'blah'); 82 | t.eq(kleur.style, ['black', 'bgRed']); 83 | }); 84 | t.test(`theme.diffAdd`, t => { 85 | t.ok(true); 86 | const kleur = fakeKleur(); 87 | const c = theme(kleur); 88 | c.diffAdd('blah'); 89 | t.eq(kleur.string, 'blah'); 90 | t.eq(kleur.style, ['black', 'bgGreen']); 91 | }); 92 | }; 93 | -------------------------------------------------------------------------------- /zora/src/index.ts: -------------------------------------------------------------------------------- 1 | import {harnessFactory} from './harness'; 2 | import {mochaTapLike, tapeTapLike} from './reporter'; 3 | import { 4 | BooleanAssertionFunction, 5 | ComparatorAssertionFunction, 6 | ErrorAssertionFunction, 7 | MessageAssertionFunction, 8 | RootTest, 9 | TestFunction, 10 | TestHarness 11 | } from './interfaces'; 12 | 13 | const findConfigurationFlag = (name) => { 14 | if (typeof process !== 'undefined') { 15 | return process.env[name] === 'true'; 16 | // @ts-ignore 17 | } else if (typeof window !== 'undefined') { 18 | // @ts-ignore 19 | return Boolean(window[name]); 20 | } 21 | return false; 22 | }; 23 | 24 | const defaultTestHarness = harnessFactory({ 25 | runOnly: findConfigurationFlag('RUN_ONLY') 26 | }); 27 | 28 | let autoStart = true; 29 | let indent = findConfigurationFlag('INDENT'); 30 | 31 | const rootTest = defaultTestHarness.test.bind(defaultTestHarness); 32 | rootTest.indent = () => { 33 | console.warn('indent function is deprecated, use "INDENT" configuration flag instead'); 34 | indent = true; 35 | }; 36 | 37 | export * from './interfaces'; 38 | 39 | export {tapeTapLike, mochaTapLike} from './reporter'; 40 | export {AssertPrototype} from './assertion'; 41 | export const test: RootTest = rootTest; 42 | export const skip: TestFunction = defaultTestHarness.skip.bind(defaultTestHarness); 43 | export const only: TestFunction = defaultTestHarness.only.bind(defaultTestHarness); 44 | rootTest.skip = skip; 45 | export const equal: ComparatorAssertionFunction = defaultTestHarness.equal.bind(defaultTestHarness); 46 | export const equals = equal; 47 | export const eq = equal; 48 | export const deepEqual = equal; 49 | 50 | export const notEqual: ComparatorAssertionFunction = defaultTestHarness.notEqual.bind(defaultTestHarness); 51 | export const notEquals = notEqual; 52 | export const notEq = notEqual; 53 | export const notDeepEqual = notEqual; 54 | 55 | export const is: ComparatorAssertionFunction = defaultTestHarness.is.bind(defaultTestHarness); 56 | export const same = is; 57 | 58 | export const isNot: ComparatorAssertionFunction = defaultTestHarness.isNot.bind(defaultTestHarness); 59 | export const notSame = isNot; 60 | 61 | export const ok: BooleanAssertionFunction = defaultTestHarness.ok.bind(defaultTestHarness); 62 | export const truthy = ok; 63 | 64 | export const notOk: BooleanAssertionFunction = defaultTestHarness.notOk.bind(defaultTestHarness); 65 | export const falsy = notOk; 66 | 67 | export const fail: MessageAssertionFunction = defaultTestHarness.fail.bind(defaultTestHarness); 68 | 69 | export const throws: ErrorAssertionFunction = defaultTestHarness.throws.bind(defaultTestHarness); 70 | export const doesNotThrow: ErrorAssertionFunction = defaultTestHarness.doesNotThrow.bind(defaultTestHarness); 71 | 72 | export const createHarness = (opts = {}): TestHarness => { 73 | autoStart = false; 74 | return harnessFactory(opts); 75 | }; 76 | 77 | const start = () => { 78 | if (autoStart) { 79 | defaultTestHarness.report(indent ? mochaTapLike : tapeTapLike); 80 | } 81 | }; 82 | 83 | // on next tick start reporting 84 | // @ts-ignore 85 | if (typeof window === 'undefined') { 86 | setTimeout(start, 0); 87 | } else { 88 | // @ts-ignore 89 | window.addEventListener('load', start); 90 | } 91 | -------------------------------------------------------------------------------- /tap/src/tap.js: -------------------------------------------------------------------------------- 1 | import { flatten, stringifySymbol } from './util'; 2 | import { isAssertionResult } from '../../common/util'; 3 | // @ts-ignore 4 | const flatDiagnostic = ({ pass, description, ...rest }) => rest; 5 | export const Tap = { 6 | print(message, offset = 0) { 7 | this.log(message.padStart(message.length + (offset * 4))); // 4 white space used as indent (see tap-parser) 8 | }, 9 | printYAML(obj, offset = 0) { 10 | const YAMLOffset = offset + 0.5; 11 | this.print('---', YAMLOffset); 12 | for (const [prop, value] of Object.entries(obj)) { 13 | this.print(`${prop}: ${JSON.stringify(value, stringifySymbol)}`, YAMLOffset + 0.5); 14 | } 15 | this.print('...', YAMLOffset); 16 | }, 17 | printComment(comment, offset = 0) { 18 | this.print(`# ${comment}`, offset); 19 | }, 20 | printBailOut(message) { 21 | this.print('Bail out! Unhandled error.'); 22 | }, 23 | printTestStart(message) { 24 | const { data: { description }, offset } = message; 25 | this.printComment(description, offset); 26 | }, 27 | printTestEnd(message) { 28 | // do nothing 29 | }, 30 | printAssertion(message) { 31 | const { data, offset } = message; 32 | const { pass, description } = data; 33 | const label = pass === true ? 'ok' : 'not ok'; 34 | if (isAssertionResult(data)) { 35 | const id = this.nextId(); 36 | this.print(`${label} ${id} - ${description}`, offset); 37 | if (pass === false) { 38 | this.printYAML(flatDiagnostic(data), offset); 39 | } 40 | } 41 | else if (data.skip) { 42 | const id = this.nextId(); 43 | this.print(`${pass ? 'ok' : 'not ok'} ${id} - ${description} # SKIP`, offset); 44 | } 45 | }, 46 | printSummary(endMessage) { 47 | this.print('', 0); 48 | this.printComment(endMessage.data.pass ? 'ok' : 'not ok', 0); 49 | this.printComment(`success: ${endMessage.data.successCount}`, 0); 50 | this.printComment(`skipped: ${endMessage.data.skipCount}`, 0); 51 | this.printComment(`failure: ${endMessage.data.failureCount}`, 0); 52 | }, 53 | async report(stream) { 54 | const src = flatten(stream); 55 | let lastMessage = null; 56 | this.print('TAP version 13'); 57 | for await (const message of src) { 58 | lastMessage = message; 59 | switch (message.type) { 60 | case "TEST_START" /* TEST_START */: 61 | this.printTestStart(message); 62 | break; 63 | case "ASSERTION" /* ASSERTION */: 64 | this.printAssertion(message); 65 | break; 66 | case "BAIL_OUT" /* BAIL_OUT */: 67 | this.printBailOut(message); 68 | throw message.data; 69 | } 70 | } 71 | this.print(`1..${lastMessage.data.count}`, 0); 72 | this.printSummary(lastMessage); 73 | } 74 | }; 75 | export const factory = (log) => { 76 | let i = 0; 77 | return Object.create(Tap, { 78 | nextId: { 79 | enumerable: true, 80 | value: () => { 81 | return ++i; 82 | } 83 | }, 84 | log: { value: log } 85 | }); 86 | }; 87 | -------------------------------------------------------------------------------- /tap/src/tap-indent.js: -------------------------------------------------------------------------------- 1 | import { Tap } from './tap'; 2 | import { isAssertionResult } from '../../common/util'; 3 | const indentedDiagnostic = ({ expected, pass, description, actual, operator, at = 'N/A', ...rest }) => ({ 4 | wanted: expected, 5 | found: actual, 6 | at, 7 | operator, 8 | ...rest 9 | }); 10 | const id = function* () { 11 | let i = 0; 12 | while (true) { 13 | yield ++i; 14 | } 15 | }; 16 | const idGen = () => { 17 | let stack = [id()]; 18 | return { 19 | [Symbol.iterator]() { 20 | return this; 21 | }, 22 | next() { 23 | return stack[0].next(); 24 | }, 25 | fork() { 26 | stack.unshift(id()); 27 | }, 28 | merge() { 29 | stack.shift(); 30 | } 31 | }; 32 | }; 33 | const IndentedTap = Object.assign({}, Tap, { 34 | printTestStart(message) { 35 | const { data: { description }, offset } = message; 36 | this.printComment(`Subtest: ${description}`, offset); 37 | }, 38 | printAssertion(message) { 39 | const { data, offset } = message; 40 | const { pass, description } = data; 41 | const label = pass === true ? 'ok' : 'not ok'; 42 | const id = this.nextId(); 43 | if (isAssertionResult(data)) { 44 | this.print(`${label} ${id} - ${description}`, offset); 45 | if (pass === false) { 46 | this.printYAML(indentedDiagnostic(data), offset); 47 | } 48 | } 49 | else { 50 | const comment = data.skip === true ? 'SKIP' : `${data.executionTime}ms`; 51 | this.print(`${pass ? 'ok' : 'not ok'} ${id} - ${description} # ${comment}`, message.offset); 52 | } 53 | }, 54 | printTestEnd(message) { 55 | const length = message.data.length; 56 | const { offset } = message; 57 | this.print(`1..${length}`, offset); 58 | } 59 | }); 60 | export const factory = (log) => { 61 | const id = idGen(); 62 | return Object.create(IndentedTap, { 63 | nextId: { 64 | enumerable: true, 65 | value: () => { 66 | return id.next().value; 67 | } 68 | }, 69 | report: { 70 | enumerable: true, 71 | value: async function (stream) { 72 | this.print('TAP version 13'); 73 | let lastMessage = null; 74 | for await (const message of stream) { 75 | lastMessage = message; 76 | switch (message.type) { 77 | case "TEST_START" /* TEST_START */: 78 | id.fork(); 79 | this.printTestStart(message); 80 | break; 81 | case "ASSERTION" /* ASSERTION */: 82 | this.printAssertion(message); 83 | break; 84 | case "TEST_END" /* TEST_END */: 85 | id.merge(); 86 | this.printTestEnd(message); 87 | break; 88 | case "BAIL_OUT" /* BAIL_OUT */: 89 | this.printBailOut(message); 90 | throw message.data; 91 | } 92 | } 93 | this.printSummary(lastMessage); 94 | } 95 | }, 96 | log: { value: log } 97 | }); 98 | }; 99 | -------------------------------------------------------------------------------- /node/test/theme.ts: -------------------------------------------------------------------------------- 1 | import {theme} from '../src/theme'; 2 | import {fakeKleur} from './util'; 3 | import {Assert} from 'zora'; 4 | 5 | export default (t: Assert) => { 6 | t.test(`theme.emphasis: bold && underline`, t => { 7 | t.ok(true); 8 | const kleur = fakeKleur(); 9 | const c = theme(kleur); 10 | c.emphasis('hello'); 11 | t.eq(kleur.string, 'hello'); 12 | t.eq(kleur.style, ['bold', 'underline']); 13 | }); 14 | 15 | t.test(`theme.successBadge: bgGreen && black && bold`, t => { 16 | t.ok(true); 17 | const kleur = fakeKleur(); 18 | const c = theme(kleur); 19 | c.successBadge('success'); 20 | t.eq(kleur.string, 'success'); 21 | t.eq(kleur.style, ['black', 'bgGreen', 'bold']); 22 | }); 23 | 24 | t.test(`theme.failureBadge: bgRed && black && bold`, t => { 25 | t.ok(true); 26 | const kleur = fakeKleur(); 27 | const c = theme(kleur); 28 | c.failureBadge('failure'); 29 | t.eq(kleur.string, 'failure'); 30 | t.eq(kleur.style, ['black', 'bgRed', 'bold']); 31 | }); 32 | 33 | t.test(`theme.skipBadge: bgYello && black && bold`, t => { 34 | t.ok(true); 35 | const kleur = fakeKleur(); 36 | const c = theme(kleur); 37 | c.skipBadge('skip'); 38 | t.eq(kleur.string, 'skip'); 39 | t.eq(kleur.style, ['black', 'bgYellow', 'bold']); 40 | }); 41 | 42 | t.test(`theme.adornment`, t => { 43 | t.ok(true); 44 | const kleur = fakeKleur(); 45 | const c = theme(kleur); 46 | c.adornment('beh'); 47 | t.eq(kleur.string, 'beh'); 48 | t.eq(kleur.style, ['gray']); 49 | }); 50 | 51 | t.test(`theme.stacktrace`, t => { 52 | t.ok(true); 53 | const kleur = fakeKleur(); 54 | const c = theme(kleur); 55 | c.stackTrace(' stackwithpadding '); 56 | t.eq(kleur.string, 'stackwithpadding'); 57 | t.eq(kleur.style, ['cyan', 'underline']); 58 | }); 59 | 60 | t.test(`theme.error`, t => { 61 | t.ok(true); 62 | const kleur = fakeKleur(); 63 | const c = theme(kleur); 64 | c.error('woot woot'); 65 | t.eq(kleur.string, 'woot woot'); 66 | t.eq(kleur.style, ['red']); 67 | }); 68 | 69 | t.test(`theme.success`, t => { 70 | t.ok(true); 71 | const kleur = fakeKleur(); 72 | const c = theme(kleur); 73 | c.success('woot'); 74 | t.eq(kleur.string, 'woot'); 75 | t.eq(kleur.style, ['green']); 76 | }); 77 | 78 | t.test(`theme.diffSame`, t => { 79 | t.ok(true); 80 | const kleur = fakeKleur(); 81 | const c = theme(kleur); 82 | c.diffSame('blah'); 83 | t.eq(kleur.string, 'blah'); 84 | t.eq(kleur.style, ['gray']); 85 | }); 86 | 87 | t.test(`theme.diffRemove`, t => { 88 | t.ok(true); 89 | const kleur = fakeKleur(); 90 | const c = theme(kleur); 91 | c.diffRemove('blah'); 92 | t.eq(kleur.string, 'blah'); 93 | t.eq(kleur.style, ['black', 'bgRed']); 94 | }); 95 | 96 | t.test(`theme.diffAdd`, t => { 97 | t.ok(true); 98 | const kleur = fakeKleur(); 99 | const c = theme(kleur); 100 | c.diffAdd('blah'); 101 | t.eq(kleur.string, 'blah'); 102 | t.eq(kleur.style, ['black', 'bgGreen']); 103 | }); 104 | 105 | } -------------------------------------------------------------------------------- /zora/scripts/generate.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const filesCount = 12; 5 | const testCount = 10; 6 | const waitTime = 100; 7 | const errorRate = 5; 8 | 9 | const zoraCode = ` 10 | module.exports =(({test}) => { 11 | for (let i = 0; i < ${testCount}; i++) { 12 | test('test ' + i, async function (assert) { 13 | await new Promise(resolve => { 14 | setTimeout(()=>resolve(),${waitTime}); 15 | }); 16 | assert.ok(Math.random() * 100 > ${errorRate}); 17 | }); 18 | }}); 19 | `; 20 | 21 | const avaCode = ` 22 | const test = require('ava'); 23 | for (let i = 0; i < ${testCount}; i++) { 24 | test('test ' + i, async function (assert) { 25 | await new Promise(resolve => { 26 | setTimeout(()=>resolve(),${waitTime}); 27 | }); 28 | assert.truthy(Math.random() * 100 > ${errorRate}); 29 | }); 30 | } 31 | `; 32 | 33 | const mochaCode = ` 34 | const assert = require('assert'); 35 | describe('test file', function() { 36 | for(let i=0; i < ${testCount};i++){ 37 | it('test ' + i, function(done) { 38 | setTimeout(()=>{ 39 | assert.ok(Math.random() * 100 > ${errorRate}); 40 | done(); 41 | },${waitTime}); 42 | }); 43 | } 44 | }); 45 | `; 46 | 47 | const tapeCode = ` 48 | const test = require('tape'); 49 | for (let i = 0; i < ${testCount}; i++) { 50 | test('test ' + i, function (assert) { 51 | setTimeout(()=>{ 52 | assert.ok(Math.random() * 100 > ${errorRate}); 53 | assert.end(); 54 | },${waitTime}); 55 | }); 56 | } 57 | `; 58 | 59 | const jestCode = ` 60 | describe('add', function () { 61 | for (let i = 0; i < ${testCount}; i++) { 62 | it('should test',async function () { 63 | await new Promise(resolve => { 64 | setTimeout(()=>resolve(),${waitTime}); 65 | }); 66 | expect(Math.random() * 100 > ${errorRate}).toBeTruthy(); 67 | }); 68 | } 69 | }); 70 | `; 71 | 72 | const tapeIndex = ` 73 | const path = require('path'); 74 | const fs = require('fs'); 75 | const tests = fs.readdirSync(path.join(process.cwd(),'./benchmarks/tape/test')); 76 | for (let f of tests){ 77 | require(path.join(process.cwd(),'./benchmarks/tape/test/',f)); 78 | } 79 | `; 80 | const zoraIndex = ` 81 | const {test} = require('../../dist/bundle/index.js'); 82 | const path = require('path'); 83 | const fs = require('fs'); 84 | const tests = fs.readdirSync(path.join(process.cwd(),'./benchmarks/zora/test')); 85 | for (let f of tests){ 86 | test(f,require(path.join(process.cwd(),'./benchmarks/zora/test/',f))); 87 | } 88 | `; 89 | 90 | for (let i = 1; i <= filesCount; i++) { 91 | const zoraPath = path.join(process.cwd(), '/benchmarks/zora/test/', 'test' + i + '.js'); 92 | const avaPath = path.join(process.cwd(), '/benchmarks/ava/test/', 'test' + i + '.js'); 93 | const mochaPath = path.join(process.cwd(), '/benchmarks/mocha/test/', 'test' + i + '.js'); 94 | const tapePath = path.join(process.cwd(), '/benchmarks/tape/test/', 'test' + i + '.js'); 95 | const jestPath = path.join(process.cwd(), '/benchmarks/jest/test/', 'test' + i + '.js'); 96 | fs.writeFileSync(zoraPath, zoraCode); 97 | fs.writeFileSync(avaPath, avaCode); 98 | fs.writeFileSync(mochaPath, mochaCode); 99 | fs.writeFileSync(tapePath, tapeCode); 100 | fs.writeFileSync(jestPath, jestCode); 101 | fs.writeFileSync(path.join(process.cwd(), '/benchmarks/tape/index.js'), tapeIndex); 102 | fs.writeFileSync(path.join(process.cwd(), '/benchmarks/zora/index.js'), zoraIndex); 103 | } 104 | -------------------------------------------------------------------------------- /tap/readme.md: -------------------------------------------------------------------------------- 1 | # zora-tap-reporter 2 | 3 | [Test Anything Protocol](https://testanything.org/) reporters for [zora](https://github.com/lorenzofox3/zora). 4 | 5 | Two flavors of TAP protocol which work both on the browser and on Nodejs 6 | 7 | ## Install 8 | 9 | ``npm install zora-tap-reporter`` 10 | 11 | ## TAP reporter 12 | 13 | Basic TAP reporter which outputs a TAP stream compatible with any [tape](https://github.com/substack/tape) reporter 14 | 15 | ```javascript 16 | import {createHarness} from 'zora'; 17 | import {tapReporter} from 'zora-tap-reporter'; 18 | 19 | const h = createHarness(); 20 | 21 | const {test} = h; 22 | 23 | test(`hello world`, t => { 24 | t.ok(true); 25 | 26 | t.test('nested', t => { 27 | t.eq('foo', 'fob'); 28 | }); 29 | }); 30 | 31 | test(`hello world`, t => { 32 | t.ok(true); 33 | 34 | t.test('nested', t => { 35 | t.eq('foo', 'fob'); 36 | }); 37 | }); 38 | 39 | h.report(tapReporter()); 40 | ``` 41 | 42 | will output 43 | 44 | ```TAP 45 | TAP version 13 46 | # hello world 47 | ok 1 - should be truthy 48 | # nested 49 | not ok 2 - should be equivalent 50 | --- 51 | actual: "foo" 52 | expected: "fob" 53 | operator: "equal" 54 | at: " file:///Volumes/data/code/zora-reporters/tap/example.mjs:12:11" 55 | ... 56 | # hello world 57 | ok 3 - should be truthy 58 | # nested 59 | not ok 4 - should be equivalent 60 | --- 61 | actual: "foo" 62 | expected: "fob" 63 | operator: "equal" 64 | at: " file:///Volumes/data/code/zora-reporters/tap/example.mjs:20:11" 65 | ... 66 | 1..4 67 | 68 | # not ok 69 | # success: 2 70 | # skipped: 0 71 | # failure: 2 72 | ``` 73 | 74 | ## Indented TAP reporter 75 | 76 | Richer structure which can be parsed by any TAP parser and will provide better information for parsers (or downstream reporters) which understand this specific structure. Example: [tap-mocha-reporter](https://www.npmjs.com/package/tap-mocha-reporter) 77 | 78 | ```javascript 79 | import {createHarness} from 'zora'; 80 | import {indentedTapReporter} from 'zora-tap-reporter'; 81 | 82 | const h = createHarness(); 83 | 84 | const {test} = h; 85 | 86 | test(`hello world`, t => { 87 | t.ok(true); 88 | 89 | t.test('nested', t => { 90 | t.eq('foo', 'fob'); 91 | }); 92 | }); 93 | 94 | test(`hello world`, t => { 95 | t.ok(true); 96 | 97 | t.test('nested', t => { 98 | t.eq('foo', 'fob'); 99 | }); 100 | }); 101 | 102 | h.report(indentedTapReporter()); 103 | ``` 104 | 105 | will output 106 | 107 | ```TAP 108 | TAP version 13 109 | # Subtest: hello world 110 | ok 1 - should be truthy 111 | # Subtest: nested 112 | not ok 1 - should be equivalent 113 | --- 114 | wanted: "fob" 115 | found: "foo" 116 | at: " file:///Volumes/data/code/zora-reporters/tap/example.mjs:12:11" 117 | operator: "equal" 118 | ... 119 | 1..1 120 | not ok 2 - nested # 5ms 121 | 1..2 122 | not ok 1 - hello world # 7ms 123 | # Subtest: hello world 124 | ok 1 - should be truthy 125 | # Subtest: nested 126 | not ok 1 - should be equivalent 127 | --- 128 | wanted: "fob" 129 | found: "foo" 130 | at: " file:///Volumes/data/code/zora-reporters/tap/example.mjs:20:11" 131 | operator: "equal" 132 | ... 133 | 1..1 134 | not ok 2 - nested # 3ms 135 | 1..2 136 | not ok 2 - hello world # 3ms 137 | 1..2 138 | 139 | # not ok 140 | # success: 2 141 | # skipped: 0 142 | # failure: 2 143 | ``` 144 | -------------------------------------------------------------------------------- /tap/src/tap-indent.ts: -------------------------------------------------------------------------------- 1 | import {Tap, TapReporter} from './tap'; 2 | import {AssertionMessage, AssertionResult, Message, MessageType, StartTestMessage, TestEndMessage} from 'zora'; 3 | import {isAssertionResult} from '../../common/util'; 4 | 5 | const indentedDiagnostic = ({expected, pass, description, actual, operator, at = 'N/A', ...rest}: AssertionResult) => ({ 6 | wanted: expected, 7 | found: actual, 8 | at, 9 | operator, 10 | ...rest 11 | }); 12 | 13 | const id = function* () { 14 | let i = 0; 15 | while (true) { 16 | yield ++i; 17 | } 18 | }; 19 | 20 | interface IdGenerator extends IterableIterator { 21 | fork(): void; 22 | 23 | merge(): void; 24 | } 25 | 26 | const idGen = (): IdGenerator => { 27 | let stack = [id()]; 28 | return { 29 | [Symbol.iterator]() { 30 | return this; 31 | }, 32 | next() { 33 | return stack[0].next(); 34 | }, 35 | fork() { 36 | stack.unshift(id()); 37 | }, 38 | merge() { 39 | stack.shift(); 40 | } 41 | }; 42 | }; 43 | 44 | const IndentedTap = Object.assign({}, Tap, { 45 | printTestStart(message: StartTestMessage) { 46 | const {data: {description}, offset} = message; 47 | this.printComment(`Subtest: ${description}`, offset); 48 | }, 49 | printAssertion(message: AssertionMessage) { 50 | const {data, offset} = message; 51 | const {pass, description} = data; 52 | const label = pass === true ? 'ok' : 'not ok'; 53 | const id = this.nextId(); 54 | if (isAssertionResult(data)) { 55 | this.print(`${label} ${id} - ${description}`, offset); 56 | if (pass === false) { 57 | this.printYAML(indentedDiagnostic(data), offset); 58 | } 59 | } else { 60 | const comment = data.skip === true ? 'SKIP' : `${data.executionTime}ms`; 61 | this.print(`${pass ? 'ok' : 'not ok'} ${id} - ${description} # ${comment}`, message.offset); 62 | } 63 | }, 64 | printTestEnd(message: TestEndMessage) { 65 | const length = message.data.length; 66 | const {offset} = message; 67 | this.print(`1..${length}`, offset); 68 | } 69 | }); 70 | 71 | export const factory = (log: (m: any) => void): TapReporter => { 72 | const id = idGen(); 73 | return Object.create(IndentedTap, { 74 | nextId: { 75 | enumerable: true, 76 | value: () => { 77 | return id.next().value; 78 | } 79 | }, 80 | report: { 81 | enumerable: true, 82 | value: async function (stream: AsyncIterable>) { 83 | this.print('TAP version 13'); 84 | let lastMessage = null; 85 | for await (const message of stream) { 86 | lastMessage = message; 87 | switch (message.type) { 88 | case MessageType.TEST_START: 89 | id.fork(); 90 | this.printTestStart(message); 91 | break; 92 | case MessageType.ASSERTION: 93 | this.printAssertion(message); 94 | break; 95 | case MessageType.TEST_END: 96 | id.merge(); 97 | this.printTestEnd(message); 98 | break; 99 | case MessageType.BAIL_OUT: 100 | this.printBailOut(message); 101 | throw message.data; 102 | } 103 | } 104 | this.printSummary(lastMessage); 105 | } 106 | }, 107 | log: {value: log} 108 | }); 109 | }; -------------------------------------------------------------------------------- /node/src/index.js: -------------------------------------------------------------------------------- 1 | import { test } from './test'; 2 | import { EOL } from 'os'; 3 | import { output } from './output-stream'; 4 | import { paint } from './theme'; 5 | import { isAssertionResult } from '../../common/util'; 6 | import { getDiagnosticReporter } from './diagnostic'; 7 | const printHeader = (message, out) => { 8 | const header = message.toUpperCase(); 9 | out.writeBlock(out.emphasis(header), 1); 10 | }; 11 | const printFailures = (tests, out) => { 12 | const failing = tests 13 | .filter(t => t.failure) 14 | .reduce((acc, curr) => acc.concat([...curr]), []); 15 | if (failing.length === 0) { 16 | out.writeLine('N/A', 2); 17 | return; 18 | } 19 | failing.forEach((failure, index) => { 20 | const data = failure.data; 21 | const [file, ...testPath] = failure.path; 22 | const header = testPath.concat(out.emphasis(data.description)).join(out.adornment(' > ')); 23 | out.writeBlock((`${paint.adornment(`${index + 1}.`)} ${header} ${out.adornment('<--')} ${out.operator(data.operator)}`)); 24 | out.writeLine(`${paint.adornment('at')} ${paint.stackTrace(data.at)}`, 4); 25 | getDiagnosticReporter(data).report(out); 26 | out.writeLine(out.adornment('_'.repeat(out.width))); 27 | }); 28 | }; 29 | const printFooter = (tests, out) => { 30 | const skipped = tests.reduce((acc, curr) => acc + curr.skip, 0); 31 | const failure = tests.reduce((acc, curr) => acc + curr.failure, 0); 32 | const success = tests.reduce((acc, curr) => acc + curr.success, 0); 33 | out.writeLine(paint.summaryPass(success), 1); 34 | out.writeLine(paint.summarySkip(skipped), 1); 35 | out.writeLine(paint.summaryFail(failure), 1); 36 | out.writeLine(`${EOL}`); 37 | }; 38 | export const reporter = (theme = paint, stream = process.stdout) => { 39 | const out = Object.assign(output(stream), theme, { 40 | width: stream.columns || 80 41 | }); 42 | return async (stream) => { 43 | const tests = []; 44 | let testLines = 0; 45 | let pass = true; 46 | printHeader('tests files', out); 47 | out.writeLine(''); 48 | for await (const message of stream) { 49 | const current = tests[tests.length - 1]; 50 | const { data, offset, type } = message; 51 | if (type === "BAIL_OUT" /* BAIL_OUT */) { 52 | throw data; 53 | } 54 | if (type === "TEST_END" /* TEST_END */ && offset > 0) { 55 | current.goOut(); 56 | } 57 | if (type === "TEST_START" /* TEST_START */) { 58 | if (offset === 0) { 59 | testLines++; 60 | const newTest = test(data.description, out); 61 | newTest.writeLine(); 62 | tests.push(newTest); 63 | } 64 | else { 65 | current.goIn(data.description); 66 | } 67 | } 68 | if (type === "ASSERTION" /* ASSERTION */) { 69 | if (isAssertionResult(data) || data.skip) { 70 | pass = pass && data.pass; 71 | if (data.pass === false) { 72 | current.incrementFailure(); 73 | current.addFailure(data); 74 | } 75 | else if (data.skip) { 76 | current.incrementSkip(); 77 | } 78 | else { 79 | current.incrementSuccess(); 80 | } 81 | out.moveCursor(0, -1); 82 | out.clearLine(0); 83 | tests[tests.length - 1].writeLine(); 84 | } 85 | } 86 | } 87 | printHeader('failures', out); 88 | printFailures(tests, out); 89 | printHeader('summary', out); 90 | out.writeLine(''); 91 | printFooter(tests, out); 92 | }; 93 | }; 94 | -------------------------------------------------------------------------------- /zora/test/unit/counter.js: -------------------------------------------------------------------------------- 1 | import {counter} from '../../src/counter.js'; 2 | import {assert} from '../../src/assertion.js'; 3 | import {tester} from '../../src/test.js'; 4 | 5 | const test = require('tape'); 6 | 7 | test('counter should be initialized with every values at 0', t => { 8 | const c = counter(); 9 | t.equal(c.successCount, 0, 'success count'); 10 | t.equal(c.failureCount, 0, 'failure count'); 11 | t.equal(c.skipCount, 0, 'skipped count'); 12 | t.equal(c.count, 0, 'total count'); 13 | t.end(); 14 | }); 15 | 16 | test('counter should update success count with successful assertion result', t => { 17 | const assertions = []; 18 | const a = assert(i => assertions.push(i)); 19 | 20 | a.ok(true); 21 | 22 | const c = counter(); 23 | c.update(assertions[0]); 24 | 25 | t.equal(c.successCount, 1, 'success count'); 26 | t.equal(c.failureCount, 0, 'failure count'); 27 | t.equal(c.skipCount, 0, 'skipped count'); 28 | t.equal(c.count, 1, 'total count'); 29 | t.end(); 30 | }); 31 | 32 | test('counter should update failing count with failing assertion result', t => { 33 | const assertions = []; 34 | const a = assert(i => assertions.push(i)); 35 | 36 | a.ok(false); 37 | 38 | const c = counter(); 39 | c.update(assertions[0]); 40 | 41 | t.equal(c.successCount, 0, 'success count'); 42 | t.equal(c.failureCount, 1, 'failure count'); 43 | t.equal(c.skipCount, 0, 'skipped count'); 44 | t.equal(c.count, 1, 'total count'); 45 | t.end(); 46 | }); 47 | 48 | test('counter should update skip count with skipped assertion result', t => { 49 | const assertions = []; 50 | const a = assert(i => assertions.push(i)); 51 | 52 | a.skip('some reason'); 53 | 54 | const c = counter(); 55 | c.update(assertions[0]); 56 | 57 | t.equal(c.successCount, 0, 'success count'); 58 | t.equal(c.failureCount, 0, 'failure count'); 59 | t.equal(c.skipCount, 1, 'skipped count'); 60 | t.equal(c.count, 1, 'total count'); 61 | t.end(); 62 | }); 63 | 64 | test('counter should update total count with skip, failing and success values', t => { 65 | const assertions = []; 66 | const a = assert(i => assertions.push(i)); 67 | 68 | a.skip('some reason'); 69 | a.ok(true); 70 | a.fail(); 71 | 72 | const c = counter(); 73 | 74 | for (const assertion of assertions) { 75 | c.update(assertion); 76 | } 77 | 78 | t.equal(c.successCount, 1, 'success count'); 79 | t.equal(c.failureCount, 1, 'failure count'); 80 | t.equal(c.skipCount, 1, 'skipped count'); 81 | t.equal(c.count, 3, 'total count'); 82 | t.end(); 83 | }); 84 | 85 | test('counter should update with a sub test own count values', async t => { 86 | const c = counter(); 87 | const test = tester('foo', t => { 88 | t.ok(true); 89 | t.skip('skipped'); 90 | 91 | t.ok(true); 92 | 93 | t.skip('foo bar', t => { 94 | t.ok(false); 95 | }); 96 | t.ok(false); 97 | t.ok(false); 98 | }); 99 | 100 | let count = 0; 101 | for await (const op of test) { 102 | count++; 103 | } 104 | t.ok(count, 'should have consumed the test'); 105 | 106 | c.update(test); 107 | 108 | t.equal(test.successCount, 2, 'test success count'); 109 | t.equal(test.failureCount, 2, 'test failure count'); 110 | t.equal(test.skipCount, 2, 'test skipped count'); 111 | t.equal(test.count, 6, 'test total count'); 112 | 113 | t.equal(c.successCount, test.successCount, 'counter success count should match test success count'); 114 | t.equal(c.failureCount, test.failureCount, 'counter failure count should match test failure count'); 115 | t.equal(c.skipCount, test.skipCount, 'counter skip count should match test skipped count'); 116 | t.equal(c.count, test.count, 'counter total count should match test total count'); 117 | t.end(); 118 | }); 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /tap/src/tap.ts: -------------------------------------------------------------------------------- 1 | import {AssertionResult, BailoutMessage, Message, MessageType, StartTestMessage, Test, TestEndMessage} from 'zora'; 2 | import {flatten, stringifySymbol} from './util'; 3 | import {Reporter} from './interfaces'; 4 | import {isAssertionResult} from '../../common/util'; 5 | 6 | // @ts-ignore 7 | const flatDiagnostic = ({pass, description, ...rest}) => rest; 8 | 9 | export interface TapReporter extends Reporter { 10 | nextId(): number; 11 | 12 | print(message: string, offset?: number): void; 13 | 14 | printYAML(obj: Object, offset ?: number): void; 15 | 16 | printSummary(endMessage: TestEndMessage): void; 17 | } 18 | 19 | export const Tap = { 20 | print(message: string, offset = 0) { 21 | this.log(message.padStart(message.length + (offset * 4))); // 4 white space used as indent (see tap-parser) 22 | }, 23 | 24 | printYAML(obj: object, offset = 0) { 25 | const YAMLOffset = offset + 0.5; 26 | this.print('---', YAMLOffset); 27 | for (const [prop, value] of Object.entries(obj)) { 28 | this.print(`${prop}: ${JSON.stringify(value, stringifySymbol)}`, YAMLOffset + 0.5); 29 | } 30 | this.print('...', YAMLOffset); 31 | }, 32 | 33 | printComment(comment: string, offset = 0) { 34 | this.print(`# ${comment}`, offset); 35 | }, 36 | 37 | printBailOut(message: BailoutMessage) { 38 | this.print('Bail out! Unhandled error.'); 39 | }, 40 | 41 | printTestStart(message: StartTestMessage) { 42 | const {data: {description}, offset} = message; 43 | this.printComment(description, offset); 44 | }, 45 | 46 | printTestEnd(message: TestEndMessage): void { 47 | // do nothing 48 | }, 49 | 50 | printAssertion(message: Message): void { 51 | const {data, offset} = message; 52 | const {pass, description} = data; 53 | const label = pass === true ? 'ok' : 'not ok'; 54 | if (isAssertionResult(data)) { 55 | const id = this.nextId(); 56 | this.print(`${label} ${id} - ${description}`, offset); 57 | if (pass === false) { 58 | this.printYAML(flatDiagnostic(data), offset); 59 | } 60 | } else if (data.skip) { 61 | const id = this.nextId(); 62 | this.print(`${pass ? 'ok' : 'not ok'} ${id} - ${description} # SKIP`, offset); 63 | } 64 | }, 65 | 66 | printSummary(endMessage: TestEndMessage) { 67 | this.print('', 0); 68 | this.printComment(endMessage.data.pass ? 'ok' : 'not ok', 0); 69 | this.printComment(`success: ${endMessage.data.successCount}`, 0); 70 | this.printComment(`skipped: ${endMessage.data.skipCount}`, 0); 71 | this.printComment(`failure: ${endMessage.data.failureCount}`, 0); 72 | }, 73 | 74 | async report(stream: AsyncIterable>) { 75 | const src = flatten(stream); 76 | let lastMessage = null; 77 | this.print('TAP version 13'); 78 | for await (const message of src) { 79 | lastMessage = message; 80 | switch (message.type) { 81 | case MessageType.TEST_START: 82 | this.printTestStart(message); 83 | break; 84 | case MessageType.ASSERTION: 85 | this.printAssertion(message); 86 | break; 87 | case MessageType.BAIL_OUT: 88 | this.printBailOut(message); 89 | throw message.data; 90 | } 91 | } 92 | this.print(`1..${lastMessage.data.count}`, 0); 93 | this.printSummary(lastMessage); 94 | } 95 | }; 96 | 97 | export const factory = (log: (p: any) => void): TapReporter => { 98 | let i = 0; 99 | return Object.create(Tap, { 100 | nextId: { 101 | enumerable: true, 102 | value: () => { 103 | return ++i; 104 | } 105 | }, 106 | log: {value: log} 107 | }); 108 | }; -------------------------------------------------------------------------------- /node/src/index.ts: -------------------------------------------------------------------------------- 1 | import {Failure, Test, test} from './test'; 2 | import {EOL} from 'os'; 3 | import {Output, output} from './output-stream'; 4 | import {Message, MessageType, Reporter} from 'zora'; 5 | import {paint} from './theme'; 6 | import {isAssertionResult} from '../../common/util'; 7 | import {getDiagnosticReporter} from './diagnostic'; 8 | 9 | const printHeader = (message: string, out: Output): void => { 10 | const header = message.toUpperCase(); 11 | out.writeBlock(out.emphasis(header), 1); 12 | }; 13 | 14 | const printFailures = (tests: Test[], out: Output) => { 15 | const failing: Failure[] = tests 16 | .filter(t => t.failure) 17 | .reduce((acc, curr) => acc.concat([...curr]), []); 18 | 19 | if (failing.length === 0) { 20 | out.writeLine('N/A', 2); 21 | return; 22 | } 23 | 24 | failing.forEach((failure, index) => { 25 | const data = failure.data; 26 | const [file, ...testPath] = failure.path; 27 | const header = testPath.concat(out.emphasis(data.description)).join(out.adornment(' > ')); 28 | out.writeBlock((`${paint.adornment(`${index + 1}.`)} ${header} ${out.adornment('<--')} ${out.operator(data.operator)}`)); 29 | out.writeLine(`${paint.adornment('at')} ${paint.stackTrace(data.at)}`, 4); 30 | getDiagnosticReporter(data).report(out); 31 | out.writeLine(out.adornment('_'.repeat(out.width))); 32 | 33 | }); 34 | }; 35 | 36 | const printFooter = (tests: Test[], out: Output) => { 37 | const skipped = tests.reduce((acc, curr) => acc + curr.skip, 0); 38 | const failure = tests.reduce((acc, curr) => acc + curr.failure, 0); 39 | const success = tests.reduce((acc, curr) => acc + curr.success, 0); 40 | 41 | out.writeLine(paint.summaryPass(success), 1); 42 | out.writeLine(paint.summarySkip(skipped), 1); 43 | out.writeLine(paint.summaryFail(failure), 1); 44 | 45 | out.writeLine(`${EOL}`); 46 | }; 47 | 48 | export const reporter = (theme = paint, stream = process.stdout): Reporter => { 49 | 50 | const out = Object.assign(output(stream), theme, { 51 | width: stream.columns || 80 52 | }); 53 | 54 | return async (stream: AsyncIterable>) => { 55 | const tests = []; 56 | let testLines = 0; 57 | let pass = true; 58 | 59 | printHeader('tests files', out); 60 | out.writeLine(''); 61 | 62 | for await (const message of stream) { 63 | const current = tests[tests.length - 1]; 64 | const {data, offset, type} = message; 65 | 66 | if (type === MessageType.BAIL_OUT) { 67 | throw data; 68 | } 69 | 70 | if (type === MessageType.TEST_END && offset > 0) { 71 | current.goOut(); 72 | } 73 | 74 | if (type === MessageType.TEST_START) { 75 | if (offset === 0) { 76 | testLines++; 77 | const newTest = test(data.description, out); 78 | newTest.writeLine(); 79 | tests.push(newTest); 80 | } else { 81 | current.goIn(data.description); 82 | } 83 | } 84 | 85 | if (type === MessageType.ASSERTION) { 86 | if (isAssertionResult(data) || data.skip) { 87 | pass = pass && data.pass; 88 | if (data.pass === false) { 89 | current.incrementFailure(); 90 | current.addFailure(data); 91 | } else if (data.skip) { 92 | current.incrementSkip(); 93 | } else { 94 | current.incrementSuccess(); 95 | } 96 | out.moveCursor(0, -1); 97 | out.clearLine(0); 98 | tests[tests.length - 1].writeLine(); 99 | } 100 | } 101 | 102 | } 103 | 104 | printHeader('failures', out); 105 | printFailures(tests, out); 106 | 107 | printHeader('summary', out); 108 | 109 | out.writeLine(''); 110 | 111 | printFooter(tests, out); 112 | }; 113 | }; 114 | -------------------------------------------------------------------------------- /node/test/test_file.js: -------------------------------------------------------------------------------- 1 | import { test } from '../src/test'; 2 | export default (t) => { 3 | t.test(`factory should init an instance`, t => { 4 | //@ts-ignore 5 | const out = {}; 6 | const tf = test(`hello/world.js`, out); 7 | t.is(tf.out, out); 8 | t.eq(tf.file, `hello/world.js`); 9 | t.eq(tf.success, 0); 10 | t.eq(tf.skip, 0); 11 | t.eq(tf.failure, 0); 12 | t.eq(tf.total, 0); 13 | t.eq(tf.path, [`hello/world.js`]); 14 | t.eq(tf.failureList, []); 15 | }); 16 | t.test(`testFile.incrementSuccess: should update success`, t => { 17 | //@ts-ignore 18 | const out = {}; 19 | const tf = test('foo', out); 20 | t.eq(tf.success, 0); 21 | tf.incrementSuccess(); 22 | t.eq(tf.success, 1); 23 | }); 24 | t.test(`testFile.incrementFailure: should update failure`, t => { 25 | //@ts-ignore 26 | const out = {}; 27 | const tf = test('foo', out); 28 | t.eq(tf.failure, 0); 29 | tf.incrementFailure(); 30 | t.eq(tf.failure, 1); 31 | }); 32 | t.test(`testFile.incrementSkip: should update skip`, t => { 33 | //@ts-ignore 34 | const out = {}; 35 | const tf = test('foo', out); 36 | t.eq(tf.skip, 0); 37 | tf.incrementSkip(); 38 | t.eq(tf.skip, 1); 39 | }); 40 | t.test(`testFile.total: should return the sum of "failure", "success" and "skip`, t => { 41 | //@ts-ignore 42 | const out = {}; 43 | const tf = test('foo', out); 44 | t.eq(tf.total, 0); 45 | tf.incrementSkip(); 46 | tf.incrementSkip(); 47 | t.eq(tf.skip, 2); 48 | t.eq(tf.failure, 0); 49 | t.eq(tf.success, 0); 50 | t.eq(tf.total, 2); 51 | tf.incrementFailure(); 52 | t.eq(tf.skip, 2); 53 | t.eq(tf.failure, 1); 54 | t.eq(tf.success, 0); 55 | t.eq(tf.total, 3); 56 | tf.incrementSuccess(); 57 | t.eq(tf.skip, 2); 58 | t.eq(tf.failure, 1); 59 | t.eq(tf.success, 1); 60 | t.eq(tf.total, 4); 61 | }); 62 | t.test(`testFile.goIn should deepen the path`, t => { 63 | //@ts-ignore 64 | const out = {}; 65 | const tf = test('foo', out); 66 | t.eq(tf.path, ['foo']); 67 | tf.goIn('bar'); 68 | t.eq(tf.path, ['foo', 'bar']); 69 | tf.goIn('baz'); 70 | t.eq(tf.path, ['foo', 'bar', 'baz']); 71 | }); 72 | t.test(`testFile.goOut should shallow the path`, t => { 73 | //@ts-ignore 74 | const out = {}; 75 | const tf = test('foo', out); 76 | t.eq(tf.path, ['foo']); 77 | tf.goIn('bar'); 78 | t.eq(tf.path, ['foo', 'bar']); 79 | tf.goIn('baz'); 80 | t.eq(tf.path, ['foo', 'bar', 'baz']); 81 | tf.goOut(); 82 | t.eq(tf.path, ['foo', 'bar']); 83 | tf.goOut(); 84 | t.eq(tf.path, ['foo']); 85 | }); 86 | t.test(`testFile.addFailure: should save the failure with current path`, t => { 87 | //@ts-ignore 88 | const out = {}; 89 | const tf = test('foo', out); 90 | t.eq(tf.path, ['foo']); 91 | tf.addFailure({ error: 'something is wrong' }); 92 | t.eq(tf.failureList, [{ path: ['foo'], data: { error: 'something is wrong' } }]); 93 | tf.goIn('bar'); 94 | tf.addFailure({ error: 'something is wrong again' }); 95 | t.eq(tf.failureList, [{ 'path': ['foo'], 'data': { 'error': 'something is wrong' } }, { 96 | 'path': ['foo', 'bar'], 97 | 'data': { 'error': 'something is wrong again' } 98 | }]); 99 | }); 100 | t.test(`testFile should be an iterable on failure`, t => { 101 | //@ts-ignore 102 | const out = {}; 103 | const tf = test('foo', out); 104 | t.ok(tf[Symbol.iterator]); 105 | tf.addFailure('fail1'); 106 | tf.addFailure('fail2'); 107 | t.eq([...tf], [{ path: ['foo'], data: 'fail1' }, { path: ['foo'], data: 'fail2' }]); 108 | t.eq([...tf], [{ path: ['foo'], data: 'fail1' }, { 109 | path: ['foo'], 110 | data: 'fail2' 111 | }], 'should be able to iterate several times'); 112 | }); 113 | }; 114 | -------------------------------------------------------------------------------- /zora/src/interfaces.ts: -------------------------------------------------------------------------------- 1 | export const enum Operator { 2 | EQUAL = 'equal', 3 | NOT_EQUAL = 'notEqual', 4 | IS = 'is', 5 | OK = 'ok', 6 | NOT_OK = 'notOk', 7 | IS_NOT = 'isNot', 8 | FAIL = 'fail', 9 | THROWS = 'throws', 10 | DOES_NOT_THROW = 'doesNotThrow' 11 | } 12 | 13 | export interface Result { 14 | pass: boolean; 15 | description: string; 16 | id?: number; 17 | skip?: boolean; 18 | } 19 | 20 | export interface TestResult extends Result { 21 | executionTime: number; 22 | } 23 | 24 | export interface AssertionResult extends Result { 25 | operator: Operator; 26 | expected: any; 27 | actual: any; 28 | at?: string; 29 | } 30 | 31 | export interface SpecFunction { 32 | (t: Assert): any 33 | } 34 | 35 | export interface ComparatorAssertionFunction { 36 | (actual: T, expected: T, description?: string): AssertionResult; 37 | } 38 | 39 | export interface BooleanAssertionFunction { 40 | (actual: T, description?: string): AssertionResult; 41 | } 42 | 43 | export interface ErrorAssertionFunction { 44 | (fn: Function, expected?: string | RegExp | Function, description ?: string): AssertionResult; 45 | } 46 | 47 | export interface MessageAssertionFunction { 48 | (message?: string): AssertionResult; 49 | } 50 | 51 | export interface TestFunction { 52 | (description: string, spec: SpecFunction, options?: object): Promise; 53 | } 54 | 55 | export interface Assert { 56 | equal: ComparatorAssertionFunction; 57 | 58 | equals: ComparatorAssertionFunction; 59 | 60 | eq: ComparatorAssertionFunction; 61 | 62 | deepEqual: ComparatorAssertionFunction; 63 | 64 | notEqual: ComparatorAssertionFunction; 65 | 66 | notEquals: ComparatorAssertionFunction; 67 | 68 | notEq: ComparatorAssertionFunction; 69 | 70 | notDeepEqual: ComparatorAssertionFunction; 71 | 72 | is: ComparatorAssertionFunction; 73 | 74 | same: ComparatorAssertionFunction; 75 | 76 | isNot: ComparatorAssertionFunction; 77 | 78 | notSame: ComparatorAssertionFunction; 79 | 80 | ok: BooleanAssertionFunction; 81 | 82 | truthy: BooleanAssertionFunction; 83 | 84 | notOk: BooleanAssertionFunction; 85 | 86 | falsy: BooleanAssertionFunction; 87 | 88 | fail: MessageAssertionFunction; 89 | 90 | throws: ErrorAssertionFunction; 91 | 92 | doesNotThrow: ErrorAssertionFunction; 93 | 94 | test: TestFunction; 95 | 96 | skip: TestFunction; 97 | 98 | only: TestFunction; 99 | } 100 | 101 | export type AssertionFunction = 102 | ComparatorAssertionFunction 103 | | BooleanAssertionFunction 104 | | ErrorAssertionFunction 105 | | MessageAssertionFunction; 106 | 107 | export interface Counter { 108 | successCount: number; 109 | failureCount: number; 110 | skipCount: number; 111 | count: number; 112 | } 113 | 114 | export interface Test extends AsyncIterable>, TestResult, Counter { 115 | readonly routine: Promise; 116 | readonly length: number; 117 | readonly error?: any; 118 | } 119 | 120 | export interface TestCounter extends Counter { 121 | update(assertion: Test | AssertionResult): void; 122 | } 123 | 124 | export interface TestHarnessConfiguration { 125 | runOnly?: boolean; 126 | indent?: boolean; 127 | } 128 | 129 | export interface TestHarness extends Assert, AsyncIterable>, Counter { 130 | report: (reporter?: Reporter) => Promise; 131 | pass: boolean; 132 | } 133 | 134 | export interface RootTest extends TestFunction { 135 | indent: () => void; 136 | skip: TestFunction; 137 | } 138 | 139 | export const enum MessageType { 140 | TEST_START = 'TEST_START', 141 | ASSERTION = 'ASSERTION', 142 | TEST_END = 'TEST_END', 143 | BAIL_OUT = 'BAIL_OUT' 144 | } 145 | 146 | export interface Message { 147 | offset: number; 148 | type: MessageType, 149 | data: T 150 | } 151 | 152 | export interface Reporter { 153 | (stream: AsyncIterable>): Promise; 154 | } 155 | 156 | export type StartTestMessage = Message<{ description: string }> 157 | 158 | export type AssertionMessage = Message; 159 | 160 | export type TestEndMessage = Message 161 | 162 | export type BailoutMessage = Message; -------------------------------------------------------------------------------- /node/test/test_file.ts: -------------------------------------------------------------------------------- 1 | import {test} from '../src/test'; 2 | import {Assert} from 'zora'; 3 | import {Output} from '../src/output-stream'; 4 | 5 | export default (t: Assert) => { 6 | t.test(`factory should init an instance`, t => { 7 | //@ts-ignore 8 | const out: Output = {}; 9 | const tf = test(`hello/world.js`, out); 10 | t.is(tf.out, out); 11 | t.eq(tf.file, `hello/world.js`); 12 | t.eq(tf.success, 0); 13 | t.eq(tf.skip, 0); 14 | t.eq(tf.failure, 0); 15 | t.eq(tf.total, 0); 16 | t.eq(tf.path, [`hello/world.js`]); 17 | t.eq(tf.failureList, []); 18 | }); 19 | 20 | t.test(`testFile.incrementSuccess: should update success`, t => { 21 | //@ts-ignore 22 | const out: Output = {}; 23 | const tf = test('foo', out); 24 | t.eq(tf.success, 0); 25 | tf.incrementSuccess(); 26 | t.eq(tf.success, 1); 27 | }); 28 | 29 | t.test(`testFile.incrementFailure: should update failure`, t => { 30 | //@ts-ignore 31 | const out: Output = {}; 32 | const tf = test('foo', out); 33 | t.eq(tf.failure, 0); 34 | tf.incrementFailure(); 35 | t.eq(tf.failure, 1); 36 | }); 37 | 38 | t.test(`testFile.incrementSkip: should update skip`, t => { 39 | //@ts-ignore 40 | const out: Output = {}; 41 | const tf = test('foo', out); 42 | t.eq(tf.skip, 0); 43 | tf.incrementSkip(); 44 | t.eq(tf.skip, 1); 45 | }); 46 | 47 | t.test(`testFile.total: should return the sum of "failure", "success" and "skip`, t => { 48 | //@ts-ignore 49 | const out: Output = {}; 50 | const tf = test('foo', out); 51 | t.eq(tf.total, 0); 52 | tf.incrementSkip(); 53 | tf.incrementSkip(); 54 | t.eq(tf.skip, 2); 55 | t.eq(tf.failure, 0); 56 | t.eq(tf.success, 0); 57 | t.eq(tf.total, 2); 58 | tf.incrementFailure(); 59 | t.eq(tf.skip, 2); 60 | t.eq(tf.failure, 1); 61 | t.eq(tf.success, 0); 62 | t.eq(tf.total, 3); 63 | tf.incrementSuccess(); 64 | t.eq(tf.skip, 2); 65 | t.eq(tf.failure, 1); 66 | t.eq(tf.success, 1); 67 | t.eq(tf.total, 4); 68 | }); 69 | 70 | t.test(`testFile.goIn should deepen the path`, t => { 71 | //@ts-ignore 72 | const out: Output = {}; 73 | const tf = test('foo', out); 74 | t.eq(tf.path, ['foo']); 75 | tf.goIn('bar'); 76 | t.eq(tf.path, ['foo', 'bar']); 77 | tf.goIn('baz'); 78 | t.eq(tf.path, ['foo', 'bar', 'baz']); 79 | }); 80 | 81 | t.test(`testFile.goOut should shallow the path`, t => { 82 | //@ts-ignore 83 | const out: Output = {}; 84 | const tf = test('foo', out); 85 | t.eq(tf.path, ['foo']); 86 | tf.goIn('bar'); 87 | t.eq(tf.path, ['foo', 'bar']); 88 | tf.goIn('baz'); 89 | t.eq(tf.path, ['foo', 'bar', 'baz']); 90 | tf.goOut(); 91 | t.eq(tf.path, ['foo', 'bar']); 92 | tf.goOut(); 93 | t.eq(tf.path, ['foo']); 94 | }); 95 | 96 | t.test(`testFile.addFailure: should save the failure with current path`, t => { 97 | //@ts-ignore 98 | const out: Output = {}; 99 | const tf = test('foo', out); 100 | t.eq(tf.path, ['foo']); 101 | tf.addFailure({error: 'something is wrong'}); 102 | t.eq(tf.failureList, [{path: ['foo'], data: {error: 'something is wrong'}}]); 103 | tf.goIn('bar'); 104 | tf.addFailure({error: 'something is wrong again'}); 105 | t.eq(tf.failureList, [{'path': ['foo'], 'data': {'error': 'something is wrong'}}, { 106 | 'path': ['foo', 'bar'], 107 | 'data': {'error': 'something is wrong again'} 108 | }]); 109 | }); 110 | 111 | t.test(`testFile should be an iterable on failure`, t => { 112 | //@ts-ignore 113 | const out: Output = {}; 114 | const tf = test('foo', out); 115 | t.ok(tf[Symbol.iterator]); 116 | tf.addFailure('fail1'); 117 | tf.addFailure('fail2'); 118 | t.eq([...tf], [{path: ['foo'], data: 'fail1'}, {path: ['foo'], data: 'fail2'}]); 119 | t.eq([...tf], [{path: ['foo'], data: 'fail1'}, { 120 | path: ['foo'], 121 | data: 'fail2' 122 | }], 'should be able to iterate several times'); 123 | }); 124 | }; -------------------------------------------------------------------------------- /node/test/output_stream.js: -------------------------------------------------------------------------------- 1 | import { stubFn } from './util'; 2 | import { delegate, output } from '../src/output-stream'; 3 | import { EOL } from 'os'; 4 | export default ({ test }) => { 5 | test(`delegate`, t => { 6 | const target = { 7 | foo: stubFn(), 8 | bar: stubFn(), 9 | baz: stubFn() 10 | }; 11 | const object = Object.assign({ 12 | baz() { 13 | } 14 | //@ts-ignore 15 | }, delegate('foo', 'bar')(target)); 16 | object.foo('foo', 'foo2'); 17 | t.eq(target.foo.callCount, 1, 'target.foo should have been called'); 18 | t.eq(target.foo.argsFor(), ['foo', 'foo2'], 'should have forwarded the arguments'); 19 | object.bar('bar', 'bar2'); 20 | t.eq(target.bar.callCount, 1, 'target.bar should have been called'); 21 | t.eq(target.bar.argsFor(), ['bar', 'bar2'], 'should have passed the args'); 22 | object.baz('baz'); 23 | t.eq(target.baz.callCount, 0, 'target baz should not have been called'); 24 | }); 25 | test(`output stream factory`, t => { 26 | t.test(`output stream delegation`, t => { 27 | const stream = { 28 | write: stubFn(), 29 | clearLine: stubFn(), 30 | cursorTo: stubFn(), 31 | moveCursor: stubFn() 32 | }; 33 | //@ts-ignore 34 | const out = output(stream); 35 | t.eq(typeof out.write, 'function', 'output stream should have a "write" method'); 36 | t.eq(typeof out.clearLine, 'function', 'output stream should have a "clearLine" method'); 37 | t.eq(typeof out.cursorTo, 'function', 'output stream should have a "cursorTo" method'); 38 | t.eq(typeof out.moveCursor, 'function', 'output stream should have a "moveCursor" method'); 39 | out.write('foo bar bim'); 40 | out.clearLine(0); 41 | out.cursorTo(2, 45); 42 | out.moveCursor(1, 2); 43 | t.eq(stream.write.callCount, 1, 'stream.write should have been called once'); 44 | t.eq(stream.write.argsFor(), ['foo bar bim'], 'should have forwarded arguments'); 45 | t.eq(stream.clearLine.callCount, 1, 'stream.clearLine should have been called once'); 46 | t.eq(stream.clearLine.argsFor(), [0], 'should have forwarded arguments'); 47 | t.eq(stream.cursorTo.callCount, 1, 'stream.cursorTo should have been called once'); 48 | t.eq(stream.cursorTo.argsFor(), [2, 45], 'should have forwarded arguments'); 49 | t.eq(stream.moveCursor.callCount, 1, 'stream.moveCursor should have been called once'); 50 | t.eq(stream.moveCursor.argsFor(), [1, 2], 'should have forwarded arguments'); 51 | }); 52 | t.test(`output.writeLine with no padding`, t => { 53 | const stream = { 54 | write: stubFn() 55 | }; 56 | //@ts-ignore 57 | const out = output(stream); 58 | out.writeLine('hello line'); 59 | t.eq(stream.write.callCount, 1, 'stream.write should have been called'); 60 | t.eq(stream.write.argsFor(), [`hello line${EOL}`], 'should have appended EOL to the message'); 61 | }); 62 | t.test(`output.writeLine with padding`, t => { 63 | const stream = { 64 | write: stubFn() 65 | }; 66 | //@ts-ignore 67 | const out = output(stream); 68 | out.writeLine('hello line', 4); 69 | t.eq(stream.write.callCount, 1, 'stream.write should have been called'); 70 | t.eq(stream.write.argsFor(), [` hello line${EOL}`], 'should have appended EOL to the message and prepended the padding'); 71 | }); 72 | t.test(`output.writeBlock with no padding`, t => { 73 | const stream = { 74 | write: stubFn() 75 | }; 76 | //@ts-ignore 77 | const out = output(stream); 78 | out.writeBlock('hello world'); 79 | t.eq(stream.write.callCount, 2, 'stream.writeLine should have been called twice'); 80 | t.eq(stream.write.argsFor(), [EOL], 'should have framed the message with EOL'); 81 | t.eq(stream.write.argsFor(1), [`hello world${EOL}`], 'should have framed the message with EOL'); 82 | }); 83 | t.test(`output.writeBlock with padding`, t => { 84 | const stream = { 85 | write: stubFn() 86 | }; 87 | //@ts-ignore 88 | const out = output(stream); 89 | out.writeBlock('hello world', 4); 90 | t.eq(stream.write.callCount, 2, 'stream.writeLine should have been called twice'); 91 | t.eq(stream.write.argsFor(), [EOL], 'should have framed the message with EOL'); 92 | t.eq(stream.write.argsFor(1), [` hello world${EOL}`], 'should have framed the message with EOL'); 93 | }); 94 | }); 95 | }; 96 | -------------------------------------------------------------------------------- /node/test/output_stream.ts: -------------------------------------------------------------------------------- 1 | import {stubFn} from './util'; 2 | import {delegate, output} from '../src/output-stream'; 3 | import {EOL} from 'os'; 4 | import {Assert} from 'zora'; 5 | 6 | export default ({test}: Assert) => { 7 | test(`delegate`, t => { 8 | const target = { 9 | foo: stubFn(), 10 | bar: stubFn(), 11 | baz: stubFn() 12 | }; 13 | const object = Object.assign({ 14 | baz() { 15 | } 16 | //@ts-ignore 17 | }, delegate('foo', 'bar')(target)); 18 | object.foo('foo', 'foo2'); 19 | 20 | t.eq(target.foo.callCount, 1, 'target.foo should have been called'); 21 | t.eq(target.foo.argsFor(), ['foo', 'foo2'], 'should have forwarded the arguments'); 22 | 23 | object.bar('bar', 'bar2'); 24 | 25 | t.eq(target.bar.callCount, 1, 'target.bar should have been called'); 26 | t.eq(target.bar.argsFor(), ['bar', 'bar2'], 'should have passed the args'); 27 | 28 | object.baz('baz'); 29 | 30 | t.eq(target.baz.callCount, 0, 'target baz should not have been called'); 31 | }); 32 | 33 | test(`output stream factory`, t => { 34 | 35 | t.test(`output stream delegation`, t => { 36 | const stream = { 37 | write: stubFn(), 38 | clearLine: stubFn(), 39 | cursorTo: stubFn(), 40 | moveCursor: stubFn() 41 | }; 42 | 43 | //@ts-ignore 44 | const out = output(stream); 45 | 46 | t.eq(typeof out.write, 'function', 'output stream should have a "write" method'); 47 | t.eq(typeof out.clearLine, 'function', 'output stream should have a "clearLine" method'); 48 | t.eq(typeof out.cursorTo, 'function', 'output stream should have a "cursorTo" method'); 49 | t.eq(typeof out.moveCursor, 'function', 'output stream should have a "moveCursor" method'); 50 | 51 | out.write('foo bar bim'); 52 | out.clearLine(0); 53 | out.cursorTo(2, 45); 54 | out.moveCursor(1, 2); 55 | t.eq(stream.write.callCount, 1, 'stream.write should have been called once'); 56 | t.eq(stream.write.argsFor(), ['foo bar bim'], 'should have forwarded arguments'); 57 | t.eq(stream.clearLine.callCount, 1, 'stream.clearLine should have been called once'); 58 | t.eq(stream.clearLine.argsFor(), [0], 'should have forwarded arguments'); 59 | t.eq(stream.cursorTo.callCount, 1, 'stream.cursorTo should have been called once'); 60 | t.eq(stream.cursorTo.argsFor(), [2, 45], 'should have forwarded arguments'); 61 | t.eq(stream.moveCursor.callCount, 1, 'stream.moveCursor should have been called once'); 62 | t.eq(stream.moveCursor.argsFor(), [1, 2], 'should have forwarded arguments'); 63 | }); 64 | 65 | t.test(`output.writeLine with no padding`, t => { 66 | const stream = { 67 | write: stubFn() 68 | }; 69 | 70 | //@ts-ignore 71 | const out = output(stream); 72 | 73 | out.writeLine('hello line'); 74 | t.eq(stream.write.callCount, 1, 'stream.write should have been called'); 75 | t.eq(stream.write.argsFor(), [`hello line${EOL}`], 'should have appended EOL to the message'); 76 | }); 77 | 78 | t.test(`output.writeLine with padding`, t => { 79 | const stream = { 80 | write: stubFn() 81 | }; 82 | 83 | //@ts-ignore 84 | const out = output(stream); 85 | 86 | out.writeLine('hello line', 4); 87 | t.eq(stream.write.callCount, 1, 'stream.write should have been called'); 88 | t.eq(stream.write.argsFor(), [` hello line${EOL}`], 'should have appended EOL to the message and prepended the padding'); 89 | }); 90 | 91 | t.test(`output.writeBlock with no padding`, t => { 92 | const stream = { 93 | write: stubFn() 94 | }; 95 | 96 | //@ts-ignore 97 | const out = output(stream); 98 | 99 | out.writeBlock('hello world'); 100 | t.eq(stream.write.callCount, 2, 'stream.writeLine should have been called twice'); 101 | t.eq(stream.write.argsFor(), [EOL], 'should have framed the message with EOL'); 102 | t.eq(stream.write.argsFor(1), [`hello world${EOL}`], 'should have framed the message with EOL'); 103 | }); 104 | 105 | t.test(`output.writeBlock with padding`, t => { 106 | const stream = { 107 | write: stubFn() 108 | }; 109 | 110 | //@ts-ignore 111 | const out = output(stream); 112 | 113 | out.writeBlock('hello world', 4); 114 | t.eq(stream.write.callCount, 2, 'stream.writeLine should have been called twice'); 115 | t.eq(stream.write.argsFor(), [EOL], 'should have framed the message with EOL'); 116 | t.eq(stream.write.argsFor(1), [` hello world${EOL}`], 'should have framed the message with EOL'); 117 | }); 118 | 119 | }); 120 | }; 121 | 122 | -------------------------------------------------------------------------------- /console/test/console.js: -------------------------------------------------------------------------------- 1 | import { factory } from '../src/console'; 2 | async function* asyncIter(messages) { 3 | yield* messages; 4 | } 5 | const loggerFactory = () => { 6 | const buffer = []; 7 | return Object.defineProperties({ 8 | log(message) { 9 | buffer.push({ text: String(message), type: 'log' }); 10 | }, 11 | group(message) { 12 | buffer.push({ text: String(message), type: 'group' }); 13 | }, 14 | groupEnd() { 15 | buffer.push({ type: 'groupEnd' }); 16 | }, 17 | table(data) { 18 | buffer.push({ type: 'table', text: data }); 19 | }, 20 | warn(message) { 21 | buffer.push({ type: 'warn', text: String(message) }); 22 | } 23 | }, { 24 | buffer: { value: buffer } 25 | }); 26 | }; 27 | export default (t) => { 28 | t.test(`printComment should simply log the message`, t => { 29 | const logger = loggerFactory(); 30 | const c = factory(logger); 31 | c.printComment(`hello world`); 32 | t.eq(logger.buffer, [{ type: 'log', text: `hello world` }]); 33 | }); 34 | t.test(`printBailOut should not do anything`, t => { 35 | const logger = loggerFactory(); 36 | const c = factory(logger); 37 | c.printBailOut({ type: "BAIL_OUT" /* BAIL_OUT */, data: new Error('some erorr'), offset: 0 }); 38 | t.eq(logger.buffer, []); 39 | }); 40 | t.test(`printTestStart should start a console group with the description as the label`, t => { 41 | const logger = loggerFactory(); 42 | const c = factory(logger); 43 | c.printTestStart({ 44 | type: "TEST_START" /* TEST_START */, offset: 0, data: { 45 | description: 'new test' 46 | } 47 | }); 48 | t.eq(logger.buffer, [{ 49 | type: 'group', 50 | text: 'new test' 51 | }]); 52 | }); 53 | t.test(`printTestEnd should end the current console group`, t => { 54 | const logger = loggerFactory(); 55 | const c = factory(logger); 56 | //@ts-ignore 57 | c.printTestEnd({ type: "TEST_END" /* TEST_END */, data: 'whatever' }); 58 | t.eq(logger.buffer, [{ 59 | type: 'groupEnd' 60 | }]); 61 | }); 62 | t.test(`printAssertion message when the assertion passes should simply log the description`, t => { 63 | const logger = loggerFactory(); 64 | const c = factory(logger); 65 | c.printAssertion({ 66 | type: "ASSERTION" /* ASSERTION */, offset: 0, data: { 67 | description: 'should pass', 68 | pass: true, 69 | expected: true, 70 | actual: true, 71 | operator: "ok" /* OK */ 72 | } 73 | }); 74 | t.eq(logger.buffer, [{ 75 | type: 'log', 76 | text: `should pass` 77 | }]); 78 | }); 79 | t.test(`printAssertion message when the assertion fails should log the diagnostic with table console method`, t => { 80 | const logger = loggerFactory(); 81 | const c = factory(logger); 82 | c.printAssertion({ 83 | type: "ASSERTION" /* ASSERTION */, offset: 0, data: { 84 | description: 'should be ok', 85 | pass: false, 86 | expected: true, 87 | actual: false, 88 | operator: "ok" /* OK */ 89 | } 90 | }); 91 | t.eq(logger.buffer, [{ 92 | type: 'table', 93 | text: { expected: true, actual: false } 94 | }]); 95 | }); 96 | t.test(`printAssertion when the assertions refers to a test point should not to anything`, t => { 97 | const logger = loggerFactory(); 98 | const c = factory(logger); 99 | c.printAssertion({ 100 | //@ts-ignore 101 | type: "ASSERTION" /* ASSERTION */, data: { 102 | pass: false 103 | } 104 | }); 105 | t.eq(logger.buffer, []); 106 | }); 107 | t.test(`printAssertion when the assertions refers to a skipped test point should set a warning in the console`, t => { 108 | const logger = loggerFactory(); 109 | const c = factory(logger); 110 | c.printAssertion({ 111 | //@ts-ignore 112 | type: "ASSERTION" /* ASSERTION */, data: { 113 | pass: true, 114 | skip: true, 115 | description: 'some description' 116 | } 117 | }); 118 | t.eq(logger.buffer, [{ 119 | type: 'warn', 120 | text: `SKIP: some description` 121 | }]); 122 | }); 123 | t.test(`report method should throw when it encounters a BAILOUT message`, async (t) => { 124 | const logger = loggerFactory(); 125 | const c = factory(logger); 126 | const err = new Error(`some error`); 127 | try { 128 | await c.report(asyncIter([{ 129 | type: "TEST_START" /* TEST_START */, 130 | data: { description: 'some test' }, 131 | offset: 0 132 | }, { 133 | type: "BAIL_OUT" /* BAIL_OUT */, 134 | data: err, 135 | offset: 0 136 | }])); 137 | t.fail(`should not reach`); 138 | } 139 | catch (e) { 140 | t.is(e, err, 'should have thrown the error'); 141 | } 142 | }); 143 | t.test(`report method should route messages depending on their nature`, async (t) => { 144 | const logger = loggerFactory(); 145 | const c = factory(logger); 146 | await c.report(asyncIter([{ 147 | type: "TEST_START" /* TEST_START */, 148 | data: { description: 'some test' }, 149 | offset: 0 150 | }, { 151 | type: "ASSERTION" /* ASSERTION */, 152 | data: { 153 | pass: true, 154 | description: 'should pass', 155 | actual: true, 156 | expected: true, 157 | operator: "ok" /* OK */ 158 | }, 159 | offset: 0 160 | }, { 161 | type: "TEST_END" /* TEST_END */, 162 | data: { description: `some test` }, 163 | offset: 0 164 | }])); 165 | t.eq(logger.buffer, [{ 'text': 'some test', 'type': 'group' }, { 166 | 'text': 'should pass', 167 | 'type': 'log' 168 | }, { 'type': 'groupEnd' }]); 169 | }); 170 | }; 171 | -------------------------------------------------------------------------------- /console/test/console.ts: -------------------------------------------------------------------------------- 1 | import {Assert, Message, MessageType, Operator} from 'zora'; 2 | import {factory} from '../src/console'; 3 | 4 | interface TestLogger extends Console { 5 | buffer: { type: string, text?: any }[]; 6 | } 7 | 8 | async function* asyncIter(messages: Message[]) { 9 | yield* messages; 10 | } 11 | 12 | const loggerFactory = (): TestLogger => { 13 | const buffer: { type: string, text?: any }[] = []; 14 | 15 | return Object.defineProperties({ 16 | log(message: string) { 17 | buffer.push({text: String(message), type: 'log'}); 18 | }, 19 | group(message: string) { 20 | buffer.push({text: String(message), type: 'group'}); 21 | }, 22 | groupEnd() { 23 | buffer.push({type: 'groupEnd'}); 24 | }, 25 | table(data: unknown) { 26 | buffer.push({type: 'table', text: data}); 27 | }, 28 | warn(message?: any): void { 29 | buffer.push({type: 'warn', text: String(message)}); 30 | } 31 | }, { 32 | buffer: {value: buffer} 33 | }); 34 | }; 35 | 36 | export default (t: Assert) => { 37 | t.test(`printComment should simply log the message`, t => { 38 | const logger = loggerFactory(); 39 | const c = factory(logger); 40 | c.printComment(`hello world`); 41 | t.eq(logger.buffer, [{type: 'log', text: `hello world`}]); 42 | }); 43 | 44 | t.test(`printBailOut should not do anything`, t => { 45 | const logger = loggerFactory(); 46 | const c = factory(logger); 47 | c.printBailOut({type: MessageType.BAIL_OUT, data: new Error('some erorr'), offset: 0}); 48 | t.eq(logger.buffer, []); 49 | }); 50 | 51 | t.test(`printTestStart should start a console group with the description as the label`, t => { 52 | const logger = loggerFactory(); 53 | const c = factory(logger); 54 | c.printTestStart({ 55 | type: MessageType.TEST_START, offset: 0, data: { 56 | description: 'new test' 57 | } 58 | }); 59 | 60 | t.eq(logger.buffer, [{ 61 | type: 'group', 62 | text: 'new test' 63 | }]); 64 | }); 65 | 66 | t.test(`printTestEnd should end the current console group`, t => { 67 | const logger = loggerFactory(); 68 | const c = factory(logger); 69 | //@ts-ignore 70 | c.printTestEnd({type: MessageType.TEST_END, data: 'whatever'}); 71 | 72 | t.eq(logger.buffer, [{ 73 | type: 'groupEnd' 74 | }]); 75 | }); 76 | 77 | t.test(`printAssertion message when the assertion passes should simply log the description`, t => { 78 | const logger = loggerFactory(); 79 | const c = factory(logger); 80 | c.printAssertion({ 81 | type: MessageType.ASSERTION, offset: 0, data: { 82 | description: 'should pass', 83 | pass: true, 84 | expected: true, 85 | actual: true, 86 | operator: Operator.OK 87 | } 88 | }); 89 | t.eq(logger.buffer, [{ 90 | type: 'log', 91 | text: `should pass` 92 | }]); 93 | }); 94 | 95 | t.test(`printAssertion message when the assertion fails should log the diagnostic with table console method`, t => { 96 | const logger = loggerFactory(); 97 | const c = factory(logger); 98 | c.printAssertion({ 99 | type: MessageType.ASSERTION, offset: 0, data: { 100 | description: 'should be ok', 101 | pass: false, 102 | expected: true, 103 | actual: false, 104 | operator: Operator.OK 105 | } 106 | }); 107 | t.eq(logger.buffer, [{ 108 | type: 'table', 109 | text: {expected: true, actual: false} 110 | }]); 111 | }); 112 | 113 | t.test(`printAssertion when the assertions refers to a test point should not to anything`, t => { 114 | const logger = loggerFactory(); 115 | const c = factory(logger); 116 | c.printAssertion({ 117 | //@ts-ignore 118 | type: MessageType.ASSERTION, data: { 119 | pass: false 120 | } 121 | }); 122 | 123 | t.eq(logger.buffer, []); 124 | }); 125 | 126 | t.test(`printAssertion when the assertions refers to a skipped test point should set a warning in the console`, t => { 127 | const logger = loggerFactory(); 128 | const c = factory(logger); 129 | c.printAssertion({ 130 | //@ts-ignore 131 | type: MessageType.ASSERTION, data: { 132 | pass: true, 133 | skip: true, 134 | description: 'some description' 135 | } 136 | }); 137 | 138 | t.eq(logger.buffer, [{ 139 | type: 'warn', 140 | text: `SKIP: some description` 141 | }]); 142 | }); 143 | 144 | t.test(`report method should throw when it encounters a BAILOUT message`, async t => { 145 | const logger = loggerFactory(); 146 | const c = factory(logger); 147 | const err = new Error(`some error`); 148 | try { 149 | await c.report(asyncIter([{ 150 | type: MessageType.TEST_START, 151 | data: {description: 'some test'}, 152 | offset: 0 153 | }, { 154 | type: MessageType.BAIL_OUT, 155 | data: err, 156 | offset: 0 157 | }])); 158 | t.fail(`should not reach`); 159 | } catch (e) { 160 | t.is(e, err, 'should have thrown the error'); 161 | } 162 | }); 163 | 164 | t.test(`report method should route messages depending on their nature`, async t => { 165 | const logger = loggerFactory(); 166 | const c = factory(logger); 167 | await c.report(asyncIter([{ 168 | type: MessageType.TEST_START, 169 | data: {description: 'some test'}, 170 | offset: 0 171 | }, { 172 | type: MessageType.ASSERTION, 173 | data: { 174 | pass: true, 175 | description: 'should pass', 176 | actual: true, 177 | expected: true, 178 | operator: Operator.OK 179 | }, 180 | offset: 0 181 | }, { 182 | type: MessageType.TEST_END, 183 | data: {description: `some test`}, 184 | offset: 0 185 | }])); 186 | 187 | t.eq(logger.buffer, [{'text': 'some test', 'type': 'group'}, { 188 | 'text': 'should pass', 189 | 'type': 'log' 190 | }, {'type': 'groupEnd'}]); 191 | }); 192 | } -------------------------------------------------------------------------------- /node/src/diagnostic.js: -------------------------------------------------------------------------------- 1 | // todo 2 | // @ts-ignore 3 | import { diffChars, diffJson } from 'diff'; 4 | import { EOL } from 'os'; 5 | export const valToTypeString = (val) => { 6 | const type = typeof val; 7 | switch (type) { 8 | case 'object': { 9 | if (val === null) { 10 | return null; 11 | } 12 | if (Array.isArray(val)) { 13 | return 'array'; 14 | } 15 | return type; 16 | } 17 | default: 18 | return type; 19 | } 20 | }; 21 | export const truthyDiagnostic = (diag) => ({ 22 | report(out) { 23 | let val = diag.actual; 24 | if (val === '') { 25 | val = '""'; 26 | } 27 | if (val === undefined) { 28 | val = 'undefined'; 29 | } 30 | out.writeBlock(`expected a ${out.operator('TRUTHY')} value but got ${out.error(val)}`, 4); 31 | } 32 | }); 33 | export const falsyDiagnostic = (diag) => ({ 34 | report(out) { 35 | out.writeBlock(`expected a ${out.operator('FALSY')} value but got ${out.error(JSON.stringify(diag.actual))}`, 4); 36 | } 37 | }); 38 | export const notEqualDiagnostic = () => ({ 39 | report(out) { 40 | out.writeBlock(`expected values ${out.operator('NOT TO BE EQUIVALENT')} but they are`, 4); 41 | } 42 | }); 43 | export const unknownOperatorDiagnostic = (diag) => ({ 44 | report(out) { 45 | out.writeBlock(`(unknown operator: ${diag.operator})`, 4); 46 | } 47 | }); 48 | export const isDiagnostic = () => ({ 49 | report(out) { 50 | out.writeBlock(`expected values to point the ${out.operator('SAME REFERENCE')} but they don't`, 4); 51 | } 52 | }); 53 | export const isNotDiagnostic = () => ({ 54 | report(out) { 55 | out.writeBlock(`expected values to point ${out.operator('DIFFERENT REFERENCES')} but they point the same`, 4); 56 | } 57 | }); 58 | export const countPadding = (string) => { 59 | let counter = 0; 60 | let i = 0; 61 | if (typeof string !== 'string') { 62 | return 0; 63 | } 64 | for (i; i < string.length; i++) { 65 | if (string[i] !== ' ') { 66 | return counter; 67 | } 68 | counter++; 69 | } 70 | return counter; 71 | }; 72 | const writeNumberDifference = (out, actual, expected) => { 73 | out.writeBlock(`expected ${out.emphasis('number')} to be ${out.operator(expected)} but got ${out.error(actual)}`, 4); 74 | }; 75 | const writeStringDifference = (out, actual, expected) => { 76 | const charDiffs = diffChars(actual, expected); 77 | const toRemove = charDiffs 78 | .filter((diff) => !diff.added) 79 | .map((d) => d.removed ? out.diffRemove(d.value) : out.diffSame(d.value)) 80 | .join(''); 81 | const toAdd = charDiffs 82 | .filter((diff) => !diff.removed) 83 | .map(d => d.added ? out.diffAdd(d.value) : out.diffSame(d.value)) 84 | .join(''); 85 | out.writeBlock(`expected ${out.emphasis('string')} to be ${out.operator(expected)} but got the following differences:`, 4); 86 | out.writeBlock(`${out.error('-')} ${toRemove}`, 4); 87 | out.writeLine(`${out.success('+')} ${toAdd}`, 4); 88 | }; 89 | const writeBooleanDifference = (out, actual, expected) => { 90 | out.writeBlock(`expected ${out.emphasis('boolean')} to be ${out.operator(expected)} but got ${out.error(actual)}`, 4); 91 | }; 92 | export const expandNewLines = (val, curr) => { 93 | const { value } = curr; 94 | const flatten = value 95 | .split(EOL) 96 | .filter(v => v !== '') 97 | .map(p => Object.assign({}, curr, { 98 | value: p.trim(), 99 | padding: countPadding(p) 100 | })); 101 | val.push(...flatten); 102 | return val; 103 | }; 104 | const writeObjectLikeDifference = (type) => (out, actual, expected) => { 105 | const diff = diffJson(actual, expected); 106 | const printDiffLines = ((diff) => { 107 | if (diff.added) { 108 | return `${out.success('+')} ${' '.repeat(diff.padding)}${out.diffAdd(diff.value)}`; 109 | } 110 | if (diff.removed) { 111 | return `${out.error('-')} ${' '.repeat(diff.padding)}${out.diffRemove(diff.value)}`; 112 | } 113 | return ` ${' '.repeat(diff.padding)}${out.diffSame(diff.value)}`; 114 | }); 115 | const lineDiffs = diff 116 | .reduce(expandNewLines, []) 117 | .map(printDiffLines); 118 | out.writeBlock(`expected ${out.emphasis(type)} to be ${out.operator('EQUIVALENT')} but got the following differences:`, 4); 119 | out.writeLine(''); 120 | for (const l of lineDiffs) { 121 | out.writeLine(l, 2); 122 | } 123 | }; 124 | const writeObjectDifference = writeObjectLikeDifference('objects'); 125 | const writeArrayDifference = writeObjectLikeDifference('arrays'); 126 | export const equalDiagnostic = (diag) => { 127 | const { actual, expected } = diag; 128 | const expectedType = valToTypeString(expected); 129 | const actualType = valToTypeString(actual); 130 | const isSameType = actualType === expectedType; 131 | return { 132 | report(out) { 133 | if (isSameType === false) { 134 | out.writeBlock(`expected ${out.operator(`${expectedType} (${expected})`)} but got ${out.error(actualType)}`, 4); 135 | } 136 | else { 137 | switch (expectedType) { 138 | case 'number': 139 | writeNumberDifference(out, actual, expected); 140 | break; 141 | case 'string': 142 | writeStringDifference(out, actual, expected); 143 | break; 144 | case 'boolean': 145 | writeBooleanDifference(out, actual, expected); 146 | break; 147 | case 'object': 148 | writeObjectDifference(out, actual, expected); 149 | break; 150 | case 'array': 151 | writeArrayDifference(out, actual, expected); 152 | break; 153 | default: 154 | out.writeBlock(`expected ${out.emphasis(expectedType)} to be ${out.operator('EQUIVALENT')} but they are not`, 4); 155 | } 156 | } 157 | } 158 | }; 159 | }; 160 | export const getDiagnosticReporter = (diag) => { 161 | switch (diag.operator) { 162 | case "ok" /* OK */: 163 | return truthyDiagnostic(diag); 164 | case "notOk" /* NOT_OK */: 165 | return falsyDiagnostic(diag); 166 | case "notEqual" /* NOT_EQUAL */: 167 | return notEqualDiagnostic(); 168 | case "is" /* IS */: 169 | return isDiagnostic(); 170 | case "isNot" /* IS_NOT */: 171 | return isNotDiagnostic(); 172 | case "equal" /* EQUAL */: 173 | return equalDiagnostic(diag); 174 | default: 175 | return unknownOperatorDiagnostic(diag); 176 | } 177 | }; 178 | -------------------------------------------------------------------------------- /zora/src/assertion.ts: -------------------------------------------------------------------------------- 1 | import {tester} from './test'; 2 | import {Assert, AssertionFunction, AssertionResult, Operator, Test} from './interfaces'; 3 | import equal from 'fast-deep-equal'; 4 | import {defaultTestOptions, noop} from './commons'; 5 | 6 | export const isAssertionResult = (result: Test | AssertionResult): result is AssertionResult => { 7 | return 'operator' in result; 8 | }; 9 | 10 | const specFnRegexp = /zora_spec_fn/; 11 | const zoraInternal = /zora\/dist\/bundle/; 12 | const filterStackLine = l => (l && !zoraInternal.test(l) && !l.startsWith('Error') || specFnRegexp.test(l)); 13 | 14 | const getAssertionLocation = (): string => { 15 | const err = new Error(); 16 | const stack = (err.stack || '') 17 | .split('\n') 18 | .map(l => l.trim()) 19 | .filter(filterStackLine); 20 | const userLandIndex = stack.findIndex(l => specFnRegexp.test(l)); 21 | const stackline = userLandIndex >= 1 ? stack[userLandIndex - 1] : (stack[0] || 'N/A'); 22 | return stackline 23 | .replace(/^at|^@/, ''); 24 | }; 25 | 26 | const assertMethodHook = (fn: AssertionFunction): AssertionFunction => function (...args) { 27 | // @ts-ignore 28 | return this.collect(fn(...args)); 29 | }; 30 | 31 | const aliasMethodHook = (methodName: string) => function (...args) { 32 | return this[methodName](...args); 33 | }; 34 | 35 | export const AssertPrototype = { 36 | equal: assertMethodHook((actual, expected, description = 'should be equivalent') => ({ 37 | pass: equal(actual, expected), 38 | actual, 39 | expected, 40 | description, 41 | operator: Operator.EQUAL 42 | })), 43 | equals: aliasMethodHook('equal'), 44 | eq: aliasMethodHook('equal'), 45 | deepEqual: aliasMethodHook('equal'), 46 | notEqual: assertMethodHook((actual, expected, description = 'should not be equivalent') => ({ 47 | pass: !equal(actual, expected), 48 | actual, 49 | expected, 50 | description, 51 | operator: Operator.NOT_EQUAL 52 | })), 53 | notEquals: aliasMethodHook('notEqual'), 54 | notEq: aliasMethodHook('notEqual'), 55 | notDeepEqual: aliasMethodHook('notEqual'), 56 | is: assertMethodHook((actual, expected, description = 'should be the same') => ({ 57 | pass: Object.is(actual, expected), 58 | actual, 59 | expected, 60 | description, 61 | operator: Operator.IS 62 | })), 63 | same: aliasMethodHook('is'), 64 | isNot: assertMethodHook((actual, expected, description = 'should not be the same') => ({ 65 | pass: !Object.is(actual, expected), 66 | actual, 67 | expected, 68 | description, 69 | operator: Operator.IS_NOT 70 | })), 71 | notSame: aliasMethodHook('isNot'), 72 | ok: assertMethodHook((actual, description = 'should be truthy') => ({ 73 | pass: Boolean(actual), 74 | actual, 75 | expected: 'truthy value', 76 | description, 77 | operator: Operator.OK 78 | })), 79 | truthy: aliasMethodHook('ok'), 80 | notOk: assertMethodHook((actual, description = 'should be falsy') => ({ 81 | pass: !Boolean(actual), 82 | actual, 83 | expected: 'falsy value', 84 | description, 85 | operator: Operator.NOT_OK 86 | })), 87 | falsy: aliasMethodHook('notOk'), 88 | fail: assertMethodHook((description = 'fail called') => ({ 89 | pass: false, 90 | actual: 'fail called', 91 | expected: 'fail not called', 92 | description, 93 | operator: Operator.FAIL 94 | })), 95 | throws: assertMethodHook((func: Function, expected, description?: string) => { 96 | let caught; 97 | let pass: boolean; 98 | let actual; 99 | if (typeof expected === 'string') { 100 | [expected, description] = [description, expected]; 101 | } 102 | try { 103 | func(); 104 | } catch (err) { 105 | caught = {error: err}; 106 | } 107 | pass = caught !== undefined; 108 | actual = caught && caught.error; 109 | if (expected instanceof RegExp) { 110 | pass = expected.test(actual) || expected.test(actual && actual.message); 111 | actual = actual && actual.message || actual; 112 | expected = String(expected); 113 | } else if (typeof expected === 'function' && caught) { 114 | pass = actual instanceof expected; 115 | actual = actual.constructor; 116 | } 117 | return { 118 | pass, 119 | actual, 120 | expected, 121 | description: description || 'should throw', 122 | operator: Operator.THROWS 123 | }; 124 | }), 125 | doesNotThrow: assertMethodHook((func, expected, description?: string) => { 126 | let caught; 127 | if (typeof expected === 'string') { 128 | [expected, description] = [description, expected]; 129 | } 130 | try { 131 | func(); 132 | } catch (err) { 133 | caught = {error: err}; 134 | } 135 | return { 136 | pass: caught === undefined, 137 | expected: 'no thrown error', 138 | actual: caught && caught.error, 139 | operator: Operator.DOES_NOT_THROW, 140 | description: description || 'should not throw' 141 | }; 142 | }) 143 | }; 144 | 145 | export const assert = (collect, offset: number, runOnly = false): Assert => { 146 | const actualCollect = item => { 147 | if (!item.pass) { 148 | item.at = getAssertionLocation(); 149 | } 150 | collect(item); 151 | return item; 152 | }; 153 | 154 | const test = (description, spec, opts) => { 155 | const options = Object.assign({}, defaultTestOptions, opts, {offset: offset + 1, runOnly}); 156 | const subTest = tester(description, spec, options); 157 | collect(subTest); 158 | return subTest.routine; 159 | }; 160 | 161 | const skip = (description, spec, opts) => { 162 | return test(description, spec, Object.assign({}, opts, {skip: true})); 163 | }; 164 | 165 | return Object.assign( 166 | Object.create(AssertPrototype, {collect: {value: actualCollect}}), 167 | { 168 | test(description, spec, opts = {}) { 169 | if (runOnly) { 170 | return skip(description, spec, opts); 171 | } 172 | return test(description, spec, opts); 173 | }, 174 | skip(description: string, spec = noop, opts = {}) { 175 | return skip(description, spec, opts); 176 | }, 177 | only(description: string, spec, opts = {}) { 178 | const specFn = runOnly === false ? _ => { 179 | throw new Error(`Can not use "only" method when not in run only mode`); 180 | } : spec; 181 | return test(description, specFn, opts); 182 | } 183 | } 184 | ); 185 | }; 186 | -------------------------------------------------------------------------------- /node/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zora-node-reporter", 3 | "version": "0.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/estree": { 8 | "version": "0.0.40", 9 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.40.tgz", 10 | "integrity": "sha512-p3KZgMto/JyxosKGmnLDJ/dG5wf+qTRMUjHJcspC2oQKa4jP7mz+tv0ND56lLBu3ojHlhzY33Ol+khLyNmilkA==", 11 | "dev": true 12 | }, 13 | "@types/node": { 14 | "version": "12.12.16", 15 | "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.16.tgz", 16 | "integrity": "sha512-vRuMyoOr5yfNf8QWxXegOjeyjpWJxFePzHzmBOIzDIzo+rSqF94RW0PkS6y4T2+VjAWLXHWrfbIJY3E3aS7lUw==", 17 | "dev": true 18 | }, 19 | "@types/resolve": { 20 | "version": "0.0.8", 21 | "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", 22 | "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", 23 | "dev": true, 24 | "requires": { 25 | "@types/node": "*" 26 | } 27 | }, 28 | "acorn": { 29 | "version": "7.1.0", 30 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", 31 | "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", 32 | "dev": true 33 | }, 34 | "builtin-modules": { 35 | "version": "3.1.0", 36 | "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", 37 | "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==", 38 | "dev": true 39 | }, 40 | "diff": { 41 | "version": "4.0.1", 42 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", 43 | "integrity": "sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==", 44 | "dev": true 45 | }, 46 | "estree-walker": { 47 | "version": "0.6.1", 48 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", 49 | "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", 50 | "dev": true 51 | }, 52 | "is-module": { 53 | "version": "1.0.0", 54 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", 55 | "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", 56 | "dev": true 57 | }, 58 | "is-reference": { 59 | "version": "1.1.4", 60 | "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz", 61 | "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==", 62 | "dev": true, 63 | "requires": { 64 | "@types/estree": "0.0.39" 65 | }, 66 | "dependencies": { 67 | "@types/estree": { 68 | "version": "0.0.39", 69 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", 70 | "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", 71 | "dev": true 72 | } 73 | } 74 | }, 75 | "kleur": { 76 | "version": "3.0.3", 77 | "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", 78 | "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", 79 | "dev": true 80 | }, 81 | "magic-string": { 82 | "version": "0.25.4", 83 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.4.tgz", 84 | "integrity": "sha512-oycWO9nEVAP2RVPbIoDoA4Y7LFIJ3xRYov93gAyJhZkET1tNuB0u7uWkZS2LpBWTJUWnmau/To8ECWRC+jKNfw==", 85 | "dev": true, 86 | "requires": { 87 | "sourcemap-codec": "^1.4.4" 88 | } 89 | }, 90 | "path-parse": { 91 | "version": "1.0.6", 92 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 93 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 94 | "dev": true 95 | }, 96 | "resolve": { 97 | "version": "1.13.1", 98 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz", 99 | "integrity": "sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==", 100 | "dev": true, 101 | "requires": { 102 | "path-parse": "^1.0.6" 103 | } 104 | }, 105 | "rollup": { 106 | "version": "1.27.9", 107 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.27.9.tgz", 108 | "integrity": "sha512-8AfW4cJTPZfG6EXWwT/ujL4owUsDI1Xl8J1t+hvK4wDX81F5I4IbwP9gvGbHzxnV19fnU4rRABZQwZSX9J402Q==", 109 | "dev": true, 110 | "requires": { 111 | "@types/estree": "*", 112 | "@types/node": "*", 113 | "acorn": "^7.1.0" 114 | } 115 | }, 116 | "rollup-plugin-commonjs": { 117 | "version": "10.1.0", 118 | "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz", 119 | "integrity": "sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==", 120 | "dev": true, 121 | "requires": { 122 | "estree-walker": "^0.6.1", 123 | "is-reference": "^1.1.2", 124 | "magic-string": "^0.25.2", 125 | "resolve": "^1.11.0", 126 | "rollup-pluginutils": "^2.8.1" 127 | } 128 | }, 129 | "rollup-plugin-node-resolve": { 130 | "version": "5.2.0", 131 | "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-5.2.0.tgz", 132 | "integrity": "sha512-jUlyaDXts7TW2CqQ4GaO5VJ4PwwaV8VUGA7+km3n6k6xtOEacf61u0VXwN80phY/evMcaS+9eIeJ9MOyDxt5Zw==", 133 | "dev": true, 134 | "requires": { 135 | "@types/resolve": "0.0.8", 136 | "builtin-modules": "^3.1.0", 137 | "is-module": "^1.0.0", 138 | "resolve": "^1.11.1", 139 | "rollup-pluginutils": "^2.8.1" 140 | } 141 | }, 142 | "rollup-pluginutils": { 143 | "version": "2.8.2", 144 | "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", 145 | "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", 146 | "dev": true, 147 | "requires": { 148 | "estree-walker": "^0.6.1" 149 | } 150 | }, 151 | "sourcemap-codec": { 152 | "version": "1.4.6", 153 | "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz", 154 | "integrity": "sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==", 155 | "dev": true 156 | }, 157 | "typescript": { 158 | "version": "3.7.3", 159 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.3.tgz", 160 | "integrity": "sha512-Mcr/Qk7hXqFBXMN7p7Lusj1ktCBydylfQM/FZCk5glCNQJrCUKPkMHdo9R0MTFWsC/4kPFvDS0fDPvukfCkFsw==", 161 | "dev": true 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /node/src/diagnostic.ts: -------------------------------------------------------------------------------- 1 | // todo 2 | // @ts-ignore 3 | import {diffChars, diffJson} from 'diff'; 4 | import {EOL} from 'os'; 5 | import {AssertionResult, Operator} from 'zora'; 6 | import {Output} from './output-stream'; 7 | 8 | // todo (cf above) 9 | export interface Diff { 10 | added: boolean; 11 | removed: boolean; 12 | padding: number; 13 | value: string; 14 | } 15 | 16 | export const valToTypeString = (val: unknown) => { 17 | const type = typeof val; 18 | switch (type) { 19 | case 'object': { 20 | if (val === null) { 21 | return null; 22 | } 23 | 24 | if (Array.isArray(val)) { 25 | return 'array'; 26 | } 27 | 28 | return type; 29 | } 30 | default: 31 | return type; 32 | } 33 | }; 34 | 35 | interface DiagnosticReporterFactory { 36 | (diag?: AssertionResult): { report(out: Output): void } 37 | } 38 | 39 | export const truthyDiagnostic: DiagnosticReporterFactory = (diag: AssertionResult) => ({ 40 | report(out: Output) { 41 | let val = diag.actual; 42 | 43 | if (val === '') { 44 | val = '""'; 45 | } 46 | 47 | if (val === undefined) { 48 | val = 'undefined'; 49 | } 50 | 51 | out.writeBlock(`expected a ${out.operator('TRUTHY')} value but got ${out.error(val)}`, 4); 52 | } 53 | }); 54 | 55 | export const falsyDiagnostic: DiagnosticReporterFactory = (diag: AssertionResult) => ({ 56 | report(out) { 57 | out.writeBlock(`expected a ${out.operator('FALSY')} value but got ${out.error(JSON.stringify(diag.actual))}`, 4); 58 | } 59 | }); 60 | 61 | export const notEqualDiagnostic: DiagnosticReporterFactory = () => ({ 62 | report(out) { 63 | out.writeBlock(`expected values ${out.operator('NOT TO BE EQUIVALENT')} but they are`, 4); 64 | } 65 | }); 66 | 67 | export const unknownOperatorDiagnostic: DiagnosticReporterFactory = (diag: AssertionResult) => ({ 68 | report(out) { 69 | out.writeBlock(`(unknown operator: ${diag.operator})`, 4); 70 | } 71 | }); 72 | 73 | export const isDiagnostic: DiagnosticReporterFactory = () => ({ 74 | report(out) { 75 | out.writeBlock(`expected values to point the ${out.operator('SAME REFERENCE')} but they don't`, 4); 76 | } 77 | }); 78 | 79 | export const isNotDiagnostic: DiagnosticReporterFactory = () => ({ 80 | report(out) { 81 | out.writeBlock(`expected values to point ${out.operator('DIFFERENT REFERENCES')} but they point the same`, 4); 82 | } 83 | }); 84 | 85 | export const countPadding = (string: string) => { 86 | let counter = 0; 87 | let i = 0; 88 | 89 | if (typeof string !== 'string') { 90 | return 0; 91 | } 92 | 93 | for (i; i < string.length; i++) { 94 | if (string[i] !== ' ') { 95 | return counter; 96 | } 97 | counter++; 98 | } 99 | return counter; 100 | }; 101 | 102 | interface WriteDifference { 103 | (out: Output, actual: T, expected: T): void; 104 | } 105 | 106 | const writeNumberDifference: WriteDifference = (out, actual, expected) => { 107 | out.writeBlock(`expected ${out.emphasis('number')} to be ${out.operator(expected)} but got ${out.error(actual)}`, 4); 108 | }; 109 | 110 | const writeStringDifference: WriteDifference = (out, actual, expected) => { 111 | const charDiffs: Diff[] = diffChars(actual, expected); 112 | const toRemove = charDiffs 113 | .filter((diff: Diff) => !diff.added) 114 | .map((d: Diff) => d.removed ? out.diffRemove(d.value) : out.diffSame(d.value)) 115 | .join(''); 116 | const toAdd = charDiffs 117 | .filter((diff: Diff) => !diff.removed) 118 | .map(d => d.added ? out.diffAdd(d.value) : out.diffSame(d.value)) 119 | .join(''); 120 | 121 | out.writeBlock(`expected ${out.emphasis('string')} to be ${out.operator(expected)} but got the following differences:`, 4); 122 | out.writeBlock(`${out.error('-')} ${toRemove}`, 4); 123 | out.writeLine(`${out.success('+')} ${toAdd}`, 4); 124 | }; 125 | 126 | const writeBooleanDifference: WriteDifference = (out, actual: boolean, expected: boolean) => { 127 | out.writeBlock(`expected ${out.emphasis('boolean')} to be ${out.operator(expected)} but got ${out.error(actual)}`, 4); 128 | }; 129 | 130 | export const expandNewLines = (val: Diff[], curr: Diff) => { 131 | const {value} = curr; 132 | const flatten = value 133 | .split(EOL) 134 | .filter(v => v !== '') 135 | .map(p => Object.assign({}, curr, { 136 | value: p.trim(), 137 | padding: countPadding(p) 138 | })); 139 | 140 | val.push(...flatten); 141 | return val; 142 | }; 143 | 144 | const writeObjectLikeDifference = (type: string): WriteDifference => (out, actual, expected) => { 145 | const diff = diffJson(actual, expected); 146 | const printDiffLines = ((diff: Diff) => { 147 | if (diff.added) { 148 | return `${out.success('+')} ${' '.repeat(diff.padding)}${out.diffAdd(diff.value)}`; 149 | } 150 | if (diff.removed) { 151 | return `${out.error('-')} ${' '.repeat(diff.padding)}${out.diffRemove(diff.value)}`; 152 | } 153 | return ` ${' '.repeat(diff.padding)}${out.diffSame(diff.value)}`; 154 | }); 155 | const lineDiffs = diff 156 | .reduce(expandNewLines, []) 157 | .map(printDiffLines); 158 | 159 | out.writeBlock(`expected ${out.emphasis(type)} to be ${out.operator('EQUIVALENT')} but got the following differences:`, 4); 160 | out.writeLine(''); 161 | for (const l of lineDiffs) { 162 | out.writeLine(l, 2); 163 | } 164 | 165 | }; 166 | const writeObjectDifference = writeObjectLikeDifference('objects'); 167 | const writeArrayDifference = writeObjectLikeDifference('arrays'); 168 | 169 | export const equalDiagnostic: DiagnosticReporterFactory = (diag: AssertionResult) => { 170 | const {actual, expected} = diag; 171 | const expectedType = valToTypeString(expected); 172 | const actualType = valToTypeString(actual); 173 | const isSameType = actualType === expectedType; 174 | return { 175 | report(out) { 176 | if (isSameType === false) { 177 | out.writeBlock(`expected ${out.operator(`${expectedType} (${expected})`)} but got ${out.error(actualType)}`, 4); 178 | } else { 179 | switch (expectedType) { 180 | case 'number': 181 | writeNumberDifference(out, actual, expected); 182 | break; 183 | case 'string': 184 | writeStringDifference(out, actual, expected); 185 | break; 186 | case 'boolean': 187 | writeBooleanDifference(out, actual, expected); 188 | break; 189 | case 'object': 190 | writeObjectDifference(out, actual, expected); 191 | break; 192 | case 'array': 193 | writeArrayDifference(out, actual, expected); 194 | break; 195 | default: 196 | out.writeBlock(`expected ${out.emphasis(expectedType)} to be ${out.operator('EQUIVALENT')} but they are not`, 4); 197 | } 198 | } 199 | 200 | } 201 | }; 202 | }; 203 | 204 | export const getDiagnosticReporter = (diag: AssertionResult) => { 205 | switch (diag.operator) { 206 | case Operator.OK: 207 | return truthyDiagnostic(diag); 208 | case Operator.NOT_OK: 209 | return falsyDiagnostic(diag); 210 | case Operator.NOT_EQUAL: 211 | return notEqualDiagnostic(); 212 | case Operator.IS: 213 | return isDiagnostic(); 214 | case Operator.IS_NOT: 215 | return isNotDiagnostic(); 216 | case Operator.EQUAL: 217 | return equalDiagnostic(diag); 218 | default: 219 | return unknownOperatorDiagnostic(diag); 220 | } 221 | }; -------------------------------------------------------------------------------- /tap/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zora-tap-reporter", 3 | "version": "2.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "balanced-match": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 10 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 11 | "dev": true 12 | }, 13 | "brace-expansion": { 14 | "version": "1.1.11", 15 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 16 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 17 | "dev": true, 18 | "requires": { 19 | "balanced-match": "^1.0.0", 20 | "concat-map": "0.0.1" 21 | } 22 | }, 23 | "concat-map": { 24 | "version": "0.0.1", 25 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 26 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 27 | "dev": true 28 | }, 29 | "deep-equal": { 30 | "version": "1.0.1", 31 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", 32 | "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", 33 | "dev": true 34 | }, 35 | "define-properties": { 36 | "version": "1.1.3", 37 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 38 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 39 | "dev": true, 40 | "requires": { 41 | "object-keys": "^1.0.12" 42 | } 43 | }, 44 | "defined": { 45 | "version": "1.0.0", 46 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", 47 | "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", 48 | "dev": true 49 | }, 50 | "es-abstract": { 51 | "version": "1.16.0", 52 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz", 53 | "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==", 54 | "dev": true, 55 | "requires": { 56 | "es-to-primitive": "^1.2.0", 57 | "function-bind": "^1.1.1", 58 | "has": "^1.0.3", 59 | "has-symbols": "^1.0.0", 60 | "is-callable": "^1.1.4", 61 | "is-regex": "^1.0.4", 62 | "object-inspect": "^1.6.0", 63 | "object-keys": "^1.1.1", 64 | "string.prototype.trimleft": "^2.1.0", 65 | "string.prototype.trimright": "^2.1.0" 66 | } 67 | }, 68 | "es-to-primitive": { 69 | "version": "1.2.0", 70 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", 71 | "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", 72 | "dev": true, 73 | "requires": { 74 | "is-callable": "^1.1.4", 75 | "is-date-object": "^1.0.1", 76 | "is-symbol": "^1.0.2" 77 | } 78 | }, 79 | "for-each": { 80 | "version": "0.3.3", 81 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 82 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", 83 | "dev": true, 84 | "requires": { 85 | "is-callable": "^1.1.3" 86 | } 87 | }, 88 | "fs.realpath": { 89 | "version": "1.0.0", 90 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 91 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 92 | "dev": true 93 | }, 94 | "function-bind": { 95 | "version": "1.1.1", 96 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 97 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 98 | "dev": true 99 | }, 100 | "glob": { 101 | "version": "7.1.6", 102 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 103 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 104 | "dev": true, 105 | "requires": { 106 | "fs.realpath": "^1.0.0", 107 | "inflight": "^1.0.4", 108 | "inherits": "2", 109 | "minimatch": "^3.0.4", 110 | "once": "^1.3.0", 111 | "path-is-absolute": "^1.0.0" 112 | } 113 | }, 114 | "has": { 115 | "version": "1.0.3", 116 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 117 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 118 | "dev": true, 119 | "requires": { 120 | "function-bind": "^1.1.1" 121 | } 122 | }, 123 | "has-symbols": { 124 | "version": "1.0.0", 125 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", 126 | "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", 127 | "dev": true 128 | }, 129 | "inflight": { 130 | "version": "1.0.6", 131 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 132 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 133 | "dev": true, 134 | "requires": { 135 | "once": "^1.3.0", 136 | "wrappy": "1" 137 | } 138 | }, 139 | "inherits": { 140 | "version": "2.0.4", 141 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 142 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 143 | "dev": true 144 | }, 145 | "is-callable": { 146 | "version": "1.1.4", 147 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", 148 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", 149 | "dev": true 150 | }, 151 | "is-date-object": { 152 | "version": "1.0.1", 153 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 154 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 155 | "dev": true 156 | }, 157 | "is-regex": { 158 | "version": "1.0.4", 159 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 160 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 161 | "dev": true, 162 | "requires": { 163 | "has": "^1.0.1" 164 | } 165 | }, 166 | "is-symbol": { 167 | "version": "1.0.2", 168 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", 169 | "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", 170 | "dev": true, 171 | "requires": { 172 | "has-symbols": "^1.0.0" 173 | } 174 | }, 175 | "minimatch": { 176 | "version": "3.0.4", 177 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 178 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 179 | "dev": true, 180 | "requires": { 181 | "brace-expansion": "^1.1.7" 182 | } 183 | }, 184 | "minimist": { 185 | "version": "1.2.5", 186 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 187 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 188 | "dev": true 189 | }, 190 | "object-inspect": { 191 | "version": "1.6.0", 192 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", 193 | "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", 194 | "dev": true 195 | }, 196 | "object-keys": { 197 | "version": "1.1.1", 198 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 199 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 200 | "dev": true 201 | }, 202 | "once": { 203 | "version": "1.4.0", 204 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 205 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 206 | "dev": true, 207 | "requires": { 208 | "wrappy": "1" 209 | } 210 | }, 211 | "path-is-absolute": { 212 | "version": "1.0.1", 213 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 214 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 215 | "dev": true 216 | }, 217 | "path-parse": { 218 | "version": "1.0.6", 219 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 220 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", 221 | "dev": true 222 | }, 223 | "resolve": { 224 | "version": "1.11.1", 225 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", 226 | "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", 227 | "dev": true, 228 | "requires": { 229 | "path-parse": "^1.0.6" 230 | } 231 | }, 232 | "resumer": { 233 | "version": "0.0.0", 234 | "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", 235 | "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", 236 | "dev": true, 237 | "requires": { 238 | "through": "~2.3.4" 239 | } 240 | }, 241 | "string.prototype.trim": { 242 | "version": "1.1.2", 243 | "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", 244 | "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", 245 | "dev": true, 246 | "requires": { 247 | "define-properties": "^1.1.2", 248 | "es-abstract": "^1.5.0", 249 | "function-bind": "^1.0.2" 250 | } 251 | }, 252 | "string.prototype.trimleft": { 253 | "version": "2.1.0", 254 | "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", 255 | "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==", 256 | "dev": true, 257 | "requires": { 258 | "define-properties": "^1.1.3", 259 | "function-bind": "^1.1.1" 260 | } 261 | }, 262 | "string.prototype.trimright": { 263 | "version": "2.1.0", 264 | "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz", 265 | "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==", 266 | "dev": true, 267 | "requires": { 268 | "define-properties": "^1.1.3", 269 | "function-bind": "^1.1.1" 270 | } 271 | }, 272 | "tape": { 273 | "version": "4.11.0", 274 | "resolved": "https://registry.npmjs.org/tape/-/tape-4.11.0.tgz", 275 | "integrity": "sha512-yixvDMX7q7JIs/omJSzSZrqulOV51EC9dK8dM0TzImTIkHWfe2/kFyL5v+d9C+SrCMaICk59ujsqFAVidDqDaA==", 276 | "dev": true, 277 | "requires": { 278 | "deep-equal": "~1.0.1", 279 | "defined": "~1.0.0", 280 | "for-each": "~0.3.3", 281 | "function-bind": "~1.1.1", 282 | "glob": "~7.1.4", 283 | "has": "~1.0.3", 284 | "inherits": "~2.0.4", 285 | "minimist": "~1.2.0", 286 | "object-inspect": "~1.6.0", 287 | "resolve": "~1.11.1", 288 | "resumer": "~0.0.0", 289 | "string.prototype.trim": "~1.1.2", 290 | "through": "~2.3.8" 291 | } 292 | }, 293 | "through": { 294 | "version": "2.3.8", 295 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 296 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 297 | "dev": true 298 | }, 299 | "wrappy": { 300 | "version": "1.0.2", 301 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 302 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 303 | "dev": true 304 | } 305 | } 306 | } 307 | --------------------------------------------------------------------------------