├── perfs
├── .gitignore
├── scripts
│ ├── test-files-cpu.js
│ └── test-files-io.js
├── package.json
└── readme.md
├── pta
├── test
│ └── samples
│ │ ├── es
│ │ ├── src
│ │ │ └── sum.js
│ │ ├── package.json
│ │ └── test
│ │ │ ├── pos-integer.mjs
│ │ │ └── neg-integer.js
│ │ ├── cjs
│ │ ├── src
│ │ │ └── sum.js
│ │ ├── package.json
│ │ └── test
│ │ │ ├── neg-integer.js
│ │ │ └── pos-integer.cjs
│ │ ├── serial
│ │ ├── shared-state.js
│ │ ├── a.spec.js
│ │ ├── c.spec.js
│ │ └── b.spec.js
│ │ ├── dummy
│ │ ├── test.js
│ │ ├── tap.txt
│ │ └── log.txt
│ │ ├── failing
│ │ └── test.js
│ │ ├── errored
│ │ └── test.js
│ │ ├── only
│ │ └── spec.js
│ │ └── index.js
├── src
│ ├── usage.txt
│ └── bin.js
├── readme.md
└── package.json
├── assert
├── test
│ ├── index.js
│ ├── factory.js
│ └── assert.js
├── package.json
├── src
│ ├── index.js
│ ├── utils.js
│ ├── index.d.ts
│ └── assert.js
└── readme.md
├── media
├── diff.png
└── tap.png
├── examples
├── node-ts
│ ├── test
│ │ ├── index.ts
│ │ └── specs
│ │ │ ├── is-odd.ts
│ │ │ └── is-even.ts
│ ├── src
│ │ ├── is-even.ts
│ │ ├── index.ts
│ │ └── is-odd.ts
│ ├── tsconfig.json
│ └── package.json
├── node-es
│ ├── src
│ │ ├── is-even.js
│ │ ├── is-odd.js
│ │ └── index.js
│ ├── test
│ │ ├── index.js
│ │ └── specs
│ │ │ ├── is-odd.js
│ │ │ └── is-even.js
│ └── package.json
├── browser-vanilla-vite
│ ├── .gitignore
│ ├── test
│ │ ├── index.html
│ │ ├── util.js
│ │ ├── specs
│ │ │ └── is-even-component.js
│ │ └── runner.js
│ ├── example
│ │ └── index.html
│ ├── package.json
│ ├── src
│ │ └── is-even-component.js
│ ├── bin.js
│ └── package-lock.json
└── readme.md
├── reporters
├── src
│ ├── diff
│ │ ├── diagnostic
│ │ │ ├── timeout.js
│ │ │ ├── is.js
│ │ │ ├── isNot.js
│ │ │ ├── notEqual.js
│ │ │ ├── notOk.js
│ │ │ ├── ok.js
│ │ │ ├── fail.js
│ │ │ ├── throws.js
│ │ │ ├── index.js
│ │ │ ├── equal.test.js
│ │ │ └── equal.js
│ │ ├── stack.js
│ │ ├── utils.js
│ │ ├── writer
│ │ │ ├── diagnostic.js
│ │ │ ├── summary.js
│ │ │ ├── index.js
│ │ │ ├── summary.test.js
│ │ │ └── diagnostic.test.js
│ │ ├── theme.js
│ │ ├── utils.test.js
│ │ └── index.js
│ ├── index.js
│ ├── usage.txt
│ ├── log
│ │ └── index.js
│ ├── message-parser.js
│ ├── protocol.js
│ ├── counter.js
│ ├── utils.test.js
│ ├── bin.js
│ ├── types
│ │ └── index.d.ts
│ ├── tap
│ │ ├── index.js
│ │ ├── writer.js
│ │ └── writer.test.js
│ ├── utils.js
│ ├── counter.test.js
│ └── message-parser.test.js
├── rollup.js
├── rollup-types.js
├── package.json
└── readme.md
├── zora
├── test
│ ├── samples
│ │ ├── bailout.txt
│ │ ├── flush.txt
│ │ ├── bailout_nested.txt
│ │ ├── only.txt
│ │ ├── failing.js
│ │ ├── late_collect.js
│ │ ├── async.txt
│ │ ├── simple.txt
│ │ ├── only.js
│ │ ├── no_only_mode.js
│ │ ├── skip.txt
│ │ ├── simple.js
│ │ ├── failing_nested.js
│ │ ├── circular_ref_obj.js
│ │ ├── flush.js
│ │ ├── failing.txt
│ │ ├── skip.js
│ │ ├── bailout.js
│ │ ├── failing_nested.txt
│ │ ├── custom_assertion.txt
│ │ ├── symbol.js
│ │ ├── circular_ref_obj.txt
│ │ ├── only_nested.txt
│ │ ├── custom_assertion.js
│ │ ├── bailout_nested.js
│ │ ├── async.js
│ │ ├── nested.txt
│ │ ├── nested_async.txt
│ │ ├── symbol.txt
│ │ ├── only_nested.js
│ │ ├── no_only_mode_nested.js
│ │ ├── nested.js
│ │ ├── timeout.js
│ │ ├── nested_async.js
│ │ └── timeout.txt
│ └── run.js
├── rollup-types.js
├── src
│ ├── env.js
│ ├── harness.js
│ ├── index.js
│ ├── types
│ │ └── index.d.ts
│ └── test.js
├── rollup.js
├── package.json
└── readme.md
├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── package.json
├── LICENSE
├── contributing.md
└── readme.md
/perfs/.gitignore:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/perfs/scripts/test-files-cpu.js:
--------------------------------------------------------------------------------
1 | // todo
2 |
--------------------------------------------------------------------------------
/pta/test/samples/es/src/sum.js:
--------------------------------------------------------------------------------
1 | export default (a, b) => a + b;
2 |
--------------------------------------------------------------------------------
/assert/test/index.js:
--------------------------------------------------------------------------------
1 | import './factory.js';
2 | import './assert.js';
3 |
--------------------------------------------------------------------------------
/pta/test/samples/cjs/src/sum.js:
--------------------------------------------------------------------------------
1 | module.exports = (a, b) => a + b;
2 |
--------------------------------------------------------------------------------
/pta/test/samples/cjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cjs-sample-test"
3 | }
4 |
--------------------------------------------------------------------------------
/media/diff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lorenzofox3/zora/master/media/diff.png
--------------------------------------------------------------------------------
/media/tap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lorenzofox3/zora/master/media/tap.png
--------------------------------------------------------------------------------
/examples/node-ts/test/index.ts:
--------------------------------------------------------------------------------
1 | import './specs/is-odd';
2 | import './specs/is-even';
3 |
--------------------------------------------------------------------------------
/pta/test/samples/serial/shared-state.js:
--------------------------------------------------------------------------------
1 | export const counter = {
2 | value: 0,
3 | };
4 |
--------------------------------------------------------------------------------
/examples/node-es/src/is-even.js:
--------------------------------------------------------------------------------
1 | import isEven from 'is-even';
2 |
3 | export default isEven;
4 |
--------------------------------------------------------------------------------
/examples/node-es/test/index.js:
--------------------------------------------------------------------------------
1 | import './specs/is-odd.js';
2 | import './specs/is-even.js';
3 |
--------------------------------------------------------------------------------
/examples/browser-vanilla-vite/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
--------------------------------------------------------------------------------
/pta/test/samples/es/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "es-sample-test",
3 | "type": "module"
4 | }
5 |
--------------------------------------------------------------------------------
/reporters/src/diff/diagnostic/timeout.js:
--------------------------------------------------------------------------------
1 | export default () =>
2 | ({ actual }) =>
3 | actual;
4 |
--------------------------------------------------------------------------------
/examples/node-es/src/is-odd.js:
--------------------------------------------------------------------------------
1 | import isEven from 'is-even';
2 |
3 | export default (input) => !isEven(input);
4 |
--------------------------------------------------------------------------------
/examples/node-ts/src/is-even.ts:
--------------------------------------------------------------------------------
1 | import isEven from 'is-even';
2 |
3 | export default isEven as (input: number) => boolean;
4 |
--------------------------------------------------------------------------------
/zora/test/samples/bailout.txt:
--------------------------------------------------------------------------------
1 | TAP version 13
2 | # will not go to the end
3 | ok 1 - okay
4 | Bail out! Unhandled error.
5 |
--------------------------------------------------------------------------------
/examples/node-ts/src/index.ts:
--------------------------------------------------------------------------------
1 | export { default as isEven } from './is-even';
2 | export { default as isOdd } from './is-odd';
3 |
--------------------------------------------------------------------------------
/examples/node-ts/src/is-odd.ts:
--------------------------------------------------------------------------------
1 | import isEven from 'is-even';
2 |
3 | export default (input: number): boolean => !isEven(input);
4 |
--------------------------------------------------------------------------------
/examples/node-es/src/index.js:
--------------------------------------------------------------------------------
1 | export { default as isEven } from './is-even.js';
2 | export { default as isOdd } from './is-odd.js';
3 |
--------------------------------------------------------------------------------
/pta/test/samples/dummy/test.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 |
3 | test('sample', ({ ok }) => {
4 | ok(true, 'woot woot');
5 | });
6 |
--------------------------------------------------------------------------------
/pta/test/samples/failing/test.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 |
3 | test('will fail', (t) => {
4 | t.fail(`some failure`);
5 | });
6 |
--------------------------------------------------------------------------------
/pta/test/samples/errored/test.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 |
3 | test('will throw an error', (t) => {
4 | throw new Error('some error');
5 | });
6 |
--------------------------------------------------------------------------------
/reporters/src/diff/diagnostic/is.js:
--------------------------------------------------------------------------------
1 | export default (theme) => () =>
2 | `expected ${theme.emphasis('references to be the same')} but they were not`;
3 |
--------------------------------------------------------------------------------
/reporters/src/diff/diagnostic/isNot.js:
--------------------------------------------------------------------------------
1 | export default (theme) => () =>
2 | `expected ${theme.emphasis('references not to be the same')} but they were`;
3 |
--------------------------------------------------------------------------------
/pta/test/samples/dummy/tap.txt:
--------------------------------------------------------------------------------
1 | TAP version 13
2 | # sample
3 | ok 1 - woot woot
4 |
5 | 1..1
6 | # tests 1
7 | # pass 1
8 | # fail 0
9 | # skip 0
10 |
--------------------------------------------------------------------------------
/zora/test/samples/flush.txt:
--------------------------------------------------------------------------------
1 | TAP version 13
2 | # tester flush
3 | ok 1 - assert1
4 |
5 | 1..1
6 | # tests 1
7 | # pass 1
8 | # fail 0
9 | # skip 0
10 |
--------------------------------------------------------------------------------
/reporters/src/diff/diagnostic/notEqual.js:
--------------------------------------------------------------------------------
1 | export default (theme) => () =>
2 | `expected the arguments ${theme.emphasis(
3 | 'not to be equivalent'
4 | )} but they were`;
5 |
--------------------------------------------------------------------------------
/zora/test/samples/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/only.txt:
--------------------------------------------------------------------------------
1 | TAP version 13
2 | ok 1 - should not run # SKIP
3 | # should run
4 | ok 2 - I ran
5 |
6 | 1..2
7 | # tests 2
8 | # pass 1
9 | # fail 0
10 | # skip 1
11 |
--------------------------------------------------------------------------------
/zora/test/samples/failing.js:
--------------------------------------------------------------------------------
1 | import { test } from '../../src/index.js';
2 |
3 | test('tester 1', (t) => {
4 | t.ok(true, 'assert1');
5 | t.equal('foo', 'bar', 'foo should equal bar');
6 | });
7 |
--------------------------------------------------------------------------------
/pta/test/samples/es/test/pos-integer.mjs:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import sum from '../src/sum.js';
3 |
4 | test('valid sum', (t) => {
5 | t.eq(sum(2, 1), 3);
6 | t.eq(sum(0, 42), 42);
7 | });
8 |
--------------------------------------------------------------------------------
/examples/node-ts/test/specs/is-odd.ts:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import { isOdd } from '../../src/index';
3 |
4 | test(`isOdd`, (t) => {
5 | t.eq(isOdd(1), true);
6 | t.eq(isOdd(2), true);
7 | });
8 |
--------------------------------------------------------------------------------
/pta/test/samples/es/test/neg-integer.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import sum from '../src/sum.js';
3 |
4 | test('valid sum', (t) => {
5 | t.eq(sum(2, -1), 1);
6 | t.eq(sum(0, -42), -42);
7 | });
8 |
--------------------------------------------------------------------------------
/reporters/src/diff/diagnostic/notOk.js:
--------------------------------------------------------------------------------
1 | export default (theme) =>
2 | ({ actual }) =>
3 | `expected ${theme.emphasis('"falsy"')} but got ${theme.emphasis(
4 | JSON.stringify(actual)
5 | )}`;
6 |
--------------------------------------------------------------------------------
/examples/node-es/test/specs/is-odd.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import { isOdd } from '../../src/index.js';
3 |
4 | test(`isOdd`, (t) => {
5 | t.eq(isOdd(1), true);
6 | t.eq(isOdd(2), true);
7 | });
8 |
--------------------------------------------------------------------------------
/examples/node-ts/test/specs/is-even.ts:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import { isEven } from '../../src/index';
3 |
4 | test(`isEven`, (t) => {
5 | t.eq(isEven(1), true);
6 | t.eq(isEven(2), true);
7 | });
8 |
--------------------------------------------------------------------------------
/reporters/src/diff/diagnostic/ok.js:
--------------------------------------------------------------------------------
1 | export default (theme) =>
2 | ({ actual }) =>
3 | `expected ${theme.emphasis('"truthy"')} but got ${theme.emphasis(
4 | actual === '' ? '""' : actual
5 | )}`;
6 |
--------------------------------------------------------------------------------
/pta/test/samples/cjs/test/neg-integer.js:
--------------------------------------------------------------------------------
1 | const { test } = require('zora');
2 | const sum = require('../src/sum.js');
3 |
4 | test('valid sum', (t) => {
5 | t.eq(sum(2, -1), 1);
6 | t.eq(sum(0, -42), -42);
7 | });
8 |
--------------------------------------------------------------------------------
/pta/test/samples/cjs/test/pos-integer.cjs:
--------------------------------------------------------------------------------
1 | const { test } = require('zora');
2 | const sum = require('../src/sum.js');
3 |
4 | test('valid sum', (t) => {
5 | t.eq(sum(2, 1), 3);
6 | t.eq(sum(0, 42), 42);
7 | });
8 |
--------------------------------------------------------------------------------
/zora/test/samples/late_collect.js:
--------------------------------------------------------------------------------
1 | import { test } from '../../src/index.js';
2 |
3 | test(`late collection`, async (t) => {
4 | t.ok(true);
5 |
6 | setTimeout(() => {
7 | t.ok(true);
8 | }, 50);
9 | });
10 |
--------------------------------------------------------------------------------
/zora/test/samples/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 |
9 | 1..4
10 | # tests 4
11 | # pass 4
12 | # fail 0
13 | # skip 0
14 |
--------------------------------------------------------------------------------
/zora/test/samples/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 |
9 | 1..4
10 | # tests 4
11 | # pass 4
12 | # fail 0
13 | # skip 0
14 |
--------------------------------------------------------------------------------
/zora/test/samples/only.js:
--------------------------------------------------------------------------------
1 | import { only, test } from '../../src/index.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/no_only_mode.js:
--------------------------------------------------------------------------------
1 | import { test, only } from '../../src/index.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 |
--------------------------------------------------------------------------------
/reporters/src/diff/diagnostic/fail.js:
--------------------------------------------------------------------------------
1 | export default (theme) =>
2 | ({ description }) =>
3 | `expected ${theme.emphasis(
4 | 'fail'
5 | )} not to be called, but was called as ${theme.emphasis(
6 | JSON.stringify(description)
7 | )}`;
8 |
--------------------------------------------------------------------------------
/reporters/src/index.js:
--------------------------------------------------------------------------------
1 | export { default as createTAPReporter } from './tap/index.js';
2 | export { default as createJSONReporter } from './log/index.js';
3 | export { default as createDiffReporter } from './diff/index.js';
4 | export * from './protocol.js';
5 |
--------------------------------------------------------------------------------
/zora/test/samples/skip.txt:
--------------------------------------------------------------------------------
1 | TAP version 13
2 | # hello world
3 | ok 1 - should be truthy
4 | ok 2 - blah # SKIP
5 | ok 3 - for some reason # SKIP
6 | ok 4 - failing text # SKIP
7 |
8 | 1..4
9 | # tests 4
10 | # pass 1
11 | # fail 0
12 | # skip 3
13 |
--------------------------------------------------------------------------------
/reporters/rollup.js:
--------------------------------------------------------------------------------
1 | export default {
2 | input: './src/index.js',
3 | output: [
4 | {
5 | format: 'cjs',
6 | file: './dist/index.cjs',
7 | },
8 | {
9 | format: 'es',
10 | file: './dist/index.js'
11 | }
12 | ],
13 | };
14 |
--------------------------------------------------------------------------------
/zora/test/samples/simple.js:
--------------------------------------------------------------------------------
1 | import { test } from '../../src/index.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/failing_nested.js:
--------------------------------------------------------------------------------
1 | import { test } from '../../src/index.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 |
--------------------------------------------------------------------------------
/examples/browser-vanilla-vite/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/zora/test/samples/circular_ref_obj.js:
--------------------------------------------------------------------------------
1 | import { test } from '../../src/index.js';
2 |
3 | test('circular ref in diagnostic', (t) => {
4 | const b = { key: 'prop' };
5 | const a = { foo: 'bar', b };
6 |
7 | b.a = a;
8 |
9 | t.eq(a, { foo: 'bar', b: { key: 'prop' } });
10 | });
11 |
--------------------------------------------------------------------------------
/pta/test/samples/dummy/log.txt:
--------------------------------------------------------------------------------
1 | {"type":"TEST_START","data":{"description":"sample","skip":false}}
2 | {"type":"ASSERTION","data":{"pass":true,"actual":true,"expected":"truthy value","description":"woot woot","operator":"ok"}}
3 | {"type":"TEST_END","data":{"description":"sample","executionTime":{TIME}}}
4 |
--------------------------------------------------------------------------------
/zora/test/samples/flush.js:
--------------------------------------------------------------------------------
1 | import { test } from '../../src/index.js';
2 |
3 | const wait = (time) =>
4 | new Promise((resolve) => {
5 | setTimeout(() => resolve(), time);
6 | });
7 |
8 | test('tester flush', async (t) => {
9 | await wait(1000);
10 | t.ok(true, 'assert1');
11 | });
12 |
--------------------------------------------------------------------------------
/examples/node-es/test/specs/is-even.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import { isEven } from '../../src/index.js';
3 |
4 | test(`isEven`, (t) => {
5 | t.eq(isEven(1), true);
6 | t.eq(isEven(2), true);
7 | t.eq({ foo: 'bar', prop: 2 }, { foo: 'baz', prop: 2 });
8 | t.eq('foo', 'for');
9 | });
10 |
--------------------------------------------------------------------------------
/zora/rollup-types.js:
--------------------------------------------------------------------------------
1 | import dts from 'rollup-plugin-dts';
2 |
3 | export default {
4 | input: 'src/types/index.d.ts',
5 | output: [{
6 | file: 'dist/index.d.ts',
7 | format: 'es',
8 | },{
9 | file: 'dist/index.d.cts',
10 | format: 'cjs',
11 | }],
12 | plugins: [dts()],
13 | };
14 |
--------------------------------------------------------------------------------
/zora/test/samples/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 |
12 | 1..2
13 | # tests 2
14 | # pass 1
15 | # fail 1
16 | # skip 0
17 |
--------------------------------------------------------------------------------
/zora/test/samples/skip.js:
--------------------------------------------------------------------------------
1 | import { test, skip } from '../../src/index.js';
2 |
3 | test('hello world', (t) => {
4 | t.ok(true);
5 | t.skip('blah', (t) => {
6 | t.ok(false);
7 | });
8 | t.skip('for some reason');
9 | });
10 |
11 | skip('failing text', (t) => {
12 | t.ok(false);
13 | });
14 |
--------------------------------------------------------------------------------
/pta/test/samples/serial/a.spec.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import { setTimeout } from 'node:timers/promises';
3 | import { counter } from './shared-state.js';
4 |
5 | await test(`first one`, async (t) => {
6 | await setTimeout(300);
7 | counter.value += 1;
8 | t.eq(counter.value, 1);
9 | });
10 |
--------------------------------------------------------------------------------
/pta/test/samples/serial/c.spec.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import { setTimeout } from 'node:timers/promises';
3 | import { counter } from './shared-state.js';
4 |
5 | await test(`a third one`, async (t) => {
6 | await setTimeout(100);
7 | counter.value += 1;
8 | t.eq(counter.value, 3);
9 | });
10 |
--------------------------------------------------------------------------------
/reporters/rollup-types.js:
--------------------------------------------------------------------------------
1 | import dts from 'rollup-plugin-dts';
2 |
3 | export default {
4 | input: 'src/types/index.d.ts',
5 | output: [{
6 | file: 'dist/index.d.ts',
7 | format: 'es',
8 | }, {
9 | file: 'dist/index.d.cts',
10 | format: 'cjs'
11 | }],
12 | plugins: [dts()],
13 | };
14 |
--------------------------------------------------------------------------------
/pta/test/samples/serial/b.spec.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import { setTimeout } from 'node:timers/promises';
3 | import { counter } from './shared-state.js';
4 |
5 | await test(`a second one`, async (t) => {
6 | await setTimeout(200);
7 | counter.value += 1;
8 | t.eq(counter.value, 2);
9 | });
10 |
--------------------------------------------------------------------------------
/pta/test/samples/only/spec.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 |
3 | test.only('should run', ({ only, test }) => {
4 | test('should not run', ({ ok }) => {
5 | ok(false);
6 | });
7 |
8 | only('only', (t) => {
9 | t.ok(true);
10 | });
11 | });
12 |
13 | test('failing', ({ ok }) => {
14 | ok(false);
15 | });
16 |
--------------------------------------------------------------------------------
/zora/test/samples/bailout.js:
--------------------------------------------------------------------------------
1 | import { test } from '../../src/index.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/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 |
14 | 1..3
15 | # tests 3
16 | # pass 2
17 | # fail 1
18 | # skip 0
19 |
--------------------------------------------------------------------------------
/reporters/src/diff/diagnostic/throws.js:
--------------------------------------------------------------------------------
1 | export default (theme) => (m) => {
2 | const { actual, expected } = m;
3 | return expected !== void 0
4 | ? `expected the error thrown to match ${theme.emphasis(
5 | expected
6 | )} but it matched ${theme.emphasis(actual)}`
7 | : `expected ${theme.emphasis('to throw')} but it did not`;
8 | };
9 |
--------------------------------------------------------------------------------
/zora/test/samples/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 |
13 | 1..2
14 | # tests 2
15 | # pass 1
16 | # fail 1
17 | # skip 0
18 |
--------------------------------------------------------------------------------
/zora/test/samples/symbol.js:
--------------------------------------------------------------------------------
1 | import { test } from '../../src/index.js';
2 |
3 | test('symbol tester 1', (t) => {
4 | t.equal(Symbol('foo'), Symbol('bar'), 'Symbol foo should equal Symbol bar');
5 | t.equal(
6 | { symbol: Symbol('foo') },
7 | { symbol: Symbol('bar') },
8 | 'Property Symbol foo should equal Symbol bar'
9 | );
10 | });
11 |
--------------------------------------------------------------------------------
/reporters/src/diff/stack.js:
--------------------------------------------------------------------------------
1 | export const createStack = () => {
2 | const items = [];
3 | const stack = {
4 | [Symbol.iterator]() {
5 | return items[Symbol.iterator]();
6 | },
7 | push(item) {
8 | items.push(item);
9 | return stack;
10 | },
11 | pop() {
12 | return items.pop();
13 | },
14 | };
15 | return stack;
16 | };
17 |
--------------------------------------------------------------------------------
/examples/browser-vanilla-vite/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/zora/test/samples/circular_ref_obj.txt:
--------------------------------------------------------------------------------
1 | TAP version 13
2 | # circular ref in diagnostic
3 | not ok 1 - should be equivalent
4 | ---
5 | actual: {"foo":"bar","b":{"key":"prop","a":"[__CIRCULAR_REF__]"}}
6 | expected: {"foo":"bar","b":{"key":"prop"}}
7 | operator: "equal"
8 | at:{STACK}
9 | ...
10 |
11 | 1..1
12 | # tests 1
13 | # pass 0
14 | # fail 1
15 | # skip 0
16 |
--------------------------------------------------------------------------------
/zora/test/samples/only_nested.txt:
--------------------------------------------------------------------------------
1 | TAP version 13
2 | ok 1 - should not run # SKIP
3 | # should run
4 | ok 2 - I ran
5 | # keep running
6 | # keeeeeep running
7 | ok 3 - I got there
8 | ok 4 - should not run # SKIP
9 | # should run but nothing inside
10 | ok 5 - will not run # SKIP
11 | ok 6 - will not run # SKIP
12 |
13 | 1..6
14 | # tests 6
15 | # pass 2
16 | # fail 0
17 | # skip 4
18 |
--------------------------------------------------------------------------------
/examples/browser-vanilla-vite/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.0.0",
3 | "type": "module",
4 | "scripts": {
5 | "dev": "node bin.js"
6 | },
7 | "devDependencies": {
8 | "@lorenzofox3/for-await": "^0.2.2",
9 | "playwright": "^1.22.2",
10 | "vite": "^2.9.18",
11 | "ws": "^8.8.0",
12 | "zora": "*"
13 | },
14 | "dependencies": {
15 | "is-even": "~1.0.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/zora/test/samples/custom_assertion.js:
--------------------------------------------------------------------------------
1 | import { Assert, test } from '../../src/index.js';
2 |
3 | Assert.isFoo = (value, description = 'should be "foo"') => ({
4 | pass: value === 'foo',
5 | expected: 'foo',
6 | actual: value,
7 | operator: 'isFoo',
8 | description,
9 | other: 'property'
10 | });
11 |
12 | test('tester 1', (t) => {
13 | t.equal('foo', 'foo', 'foo should equal foo');
14 | t.isFoo('blah');
15 | });
16 |
--------------------------------------------------------------------------------
/zora/src/env.js:
--------------------------------------------------------------------------------
1 | export const findConfigurationValue = (name) => {
2 | if (isNode) {
3 | return process.env[name];
4 | } else if (isDeno) {
5 | return Deno.env.get(name);
6 | } else if (isBrowser) {
7 | return window[name];
8 | }
9 | };
10 |
11 | export const isNode = typeof process !== 'undefined';
12 | export const isBrowser = typeof window !== 'undefined';
13 | export const isDeno = typeof Deno !== 'undefined';
14 |
--------------------------------------------------------------------------------
/examples/readme.md:
--------------------------------------------------------------------------------
1 | # Recipes list
2 |
3 | A list of example projects with various dev environments:
4 |
5 | * [simple node library written as an ecmascript module](./node-es)
6 | * [simple node library written in typescript](./node-ts)
7 |
8 | You should usually have a look at the ``scripts`` section of the package.json files to find out how to run the tests, the dev mode, the coverage, etc. There are often alternative scripts and examples as well
9 |
--------------------------------------------------------------------------------
/zora/test/samples/bailout_nested.js:
--------------------------------------------------------------------------------
1 | import { test } from '../../src/index.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/test/samples/async.js:
--------------------------------------------------------------------------------
1 | import { test } from '../../src/index.js';
2 |
3 | const wait = (time) =>
4 | new Promise((resolve) => {
5 | setTimeout(() => resolve(), time);
6 | });
7 |
8 | test('tester 1', async (t) => {
9 | t.ok(true, 'assert1');
10 | await wait(500);
11 | t.ok(true, 'assert2');
12 | });
13 |
14 | test('tester 2', async (t) => {
15 | t.ok(true, 'assert3');
16 | await wait(300);
17 | t.ok(true, 'assert4');
18 | });
19 |
--------------------------------------------------------------------------------
/examples/node-ts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "outDir": "dist",
7 | "incremental": true,
8 | "sourceMap": true,
9 | "esModuleInterop": true,
10 | "allowSyntheticDefaultImports": true,
11 | "strict": true,
12 | "lib": [
13 | "ES2020"
14 | ]
15 | },
16 | "exclude": [
17 | "node_modules/"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/reporters/src/usage.txt:
--------------------------------------------------------------------------------
1 |
2 | Synopsis
3 | Transforms a zora testing protocol message stream into a specific reporting format
4 |
5 | Usage
6 | zr
7 |
8 | Options
9 | --reporter, -R One of "tap", "diff"(default).
10 | --strict-mode If true, throws an error when the stream parser finds a message it cannot understand (default false)
11 |
12 | Examples
13 | zr -R tap < some/file.txt
14 | ZORA_REPORTER=json node zora/program.js | zr -R diff
15 |
16 |
--------------------------------------------------------------------------------
/zora/test/samples/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 |
20 | 1..11
21 | # tests 11
22 | # pass 11
23 | # fail 0
24 | # skip 0
25 |
--------------------------------------------------------------------------------
/reporters/src/log/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | compose,
3 | defaultLogger,
4 | defaultSerializer,
5 | eventuallySetExitCode,
6 | } from '../utils.js';
7 |
8 | export default ({
9 | log = defaultLogger,
10 | serialize = defaultSerializer,
11 | } = {}) => {
12 | const print = compose([log, serialize]);
13 | return async (messageStream) => {
14 | for await (const message of messageStream) {
15 | eventuallySetExitCode(message);
16 | print(message);
17 | }
18 | };
19 | };
20 |
--------------------------------------------------------------------------------
/zora/test/samples/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 |
18 | 1..9
19 | # tests 9
20 | # pass 9
21 | # fail 0
22 | # skip 0
23 |
--------------------------------------------------------------------------------
/zora/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 | {
8 | file: './dist/index.cjs',
9 | format: 'cjs',
10 | },
11 | {
12 | file: './dist/index.js',
13 | format: 'es',
14 | },
15 | ],
16 | treeshake: {
17 | moduleSideEffects: false, // otherwise package 'diff' from 'zora-reporters' gets included
18 | },
19 | plugins: [node(), cjs()],
20 | };
21 |
--------------------------------------------------------------------------------
/reporters/src/message-parser.js:
--------------------------------------------------------------------------------
1 | import { unknownMessage } from './protocol.js';
2 |
3 | export const createJSONParser = ({ strictMode = false }) =>
4 | async function* (lineStream) {
5 | for await (const line of lineStream) {
6 | try {
7 | yield JSON.parse(line);
8 | } catch (e) {
9 | if (strictMode) {
10 | throw new Error(`could not parse line "${line}"`);
11 | } else {
12 | yield unknownMessage({ message: line });
13 | }
14 | }
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/zora/test/samples/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 |
18 | 1..2
19 | # tests 2
20 | # pass 0
21 | # fail 2
22 | # skip 0
23 |
--------------------------------------------------------------------------------
/reporters/src/diff/diagnostic/index.js:
--------------------------------------------------------------------------------
1 | export { default as okDiagnostic } from './ok.js';
2 | export { default as notOkDiagnostic } from './notOk.js';
3 | export { default as failDiagnostic } from './fail.js';
4 | export { default as notEqualDiagnostic } from './notEqual.js';
5 | export { default as isDiagnostic } from './is.js';
6 | export { default as isNotDiagnostic } from './isNot.js';
7 | export { default as throwsDiagnostic } from './throws.js';
8 | export { default as equalDiagnostic } from './equal.js';
9 | export { default as timeoutDiagnostic } from './timeout.js';
10 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | branches:
9 | - master
10 |
11 | defaults:
12 | run:
13 | shell: bash
14 |
15 | jobs:
16 | all:
17 | name: build and test
18 | runs-on: ubuntu-latest
19 | steps:
20 | - uses: actions/checkout@v3
21 | - uses: actions/setup-node@v3
22 | with:
23 | node-version: 20
24 | - name: Install
25 | run: npm run install:ci
26 | - name: Build
27 | run: npm run build
28 | - name: Test
29 | run: npm t
30 |
31 |
--------------------------------------------------------------------------------
/examples/browser-vanilla-vite/test/util.js:
--------------------------------------------------------------------------------
1 | export const shallowMount = (comp) => {
2 | const wrapper = document.createElement('div');
3 | if (typeof comp === 'string') {
4 | wrapper.innerHTML = comp;
5 | } else {
6 | wrapper.appendChild(comp);
7 | }
8 | if (comp.connectedCallback) {
9 | comp.connectedCallback();
10 | }
11 | return wrapper.firstChild;
12 | };
13 |
14 | export const map = (mapFn) =>
15 | async function* (stream) {
16 | for await (const element of stream) {
17 | yield mapFn(element);
18 | }
19 | };
20 |
21 | export const compose = (fns) => (arg) => fns.reduceRight((y, fn) => fn(y), arg);
22 |
--------------------------------------------------------------------------------
/pta/src/usage.txt:
--------------------------------------------------------------------------------
1 |
2 | Synopsis
3 | Test runner for zora spec files
4 |
5 | Usage
6 | pta [ ...]
7 |
8 | Options
9 | --only Runs zora in "only mode"
10 |
11 | --reporter, -R One of tap, log. Otherwise it will use the default reporter
12 |
13 | --timeout change the default test timeout, in milliseconde. Default is 5000ms
14 |
15 | Examples
16 | pta
17 | pta test/{unit,int}/**/*.js
18 |
19 | If no argument is provided, the CLI will use the following patterns:
20 | - **/test.js
21 | - **/*.spec.js
22 | - **/*.test.js
23 | - **/test/**/*.js
24 | - **/tests/**/*.js
25 | - **/__tests__/**/*.js
26 |
27 |
--------------------------------------------------------------------------------
/reporters/src/diff/utils.js:
--------------------------------------------------------------------------------
1 | import { compose } from '../utils.js';
2 |
3 | const curry2 = (fn) => (a, b) => {
4 | if (b === void 0) {
5 | return (b) => fn(a, b);
6 | }
7 |
8 | return fn(a, b);
9 | };
10 |
11 | export const leftPad = curry2((offset, string) =>
12 | string.padStart(string.length + offset)
13 | );
14 |
15 | export const rightPad = curry2((offset, string) =>
16 | string.padEnd(string.length + offset)
17 | );
18 |
19 | export const withMargin = compose([leftPad(1), rightPad(1)]);
20 |
21 | export const typeAsString = (value) => {
22 | if (typeof value === 'object') {
23 | return value?.constructor?.name ?? String(value);
24 | }
25 | return typeof value;
26 | };
27 |
--------------------------------------------------------------------------------
/zora/test/samples/only_nested.js:
--------------------------------------------------------------------------------
1 | import { only, test } from '../../src/index.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/no_only_mode_nested.js:
--------------------------------------------------------------------------------
1 | import { test, only } from '../../src/index.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 |
--------------------------------------------------------------------------------
/assert/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zora-assert",
3 | "version": "0.0.1",
4 | "description": "assertion library for zora",
5 | "type": "module",
6 | "types": "./src/index.d.ts",
7 | "exports": {
8 | "./package.json": "./package.json",
9 | ".": {
10 | "import": "./src/index.js",
11 | "types": "./src/index.d.ts"
12 | }
13 | },
14 | "scripts": {
15 | "test": "node ./test/index.js",
16 | "test:watch": "node --watch ./test/index.js",
17 | "dev": "npm run test:watch"
18 | },
19 | "author": "@lorenzofox3 ",
20 | "license": "MIT",
21 | "devDependencies": {
22 | "fast-deep-equal": "~3.1.3"
23 | },
24 | "prettier": {
25 | "singleQuote": true
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/assert/src/index.js:
--------------------------------------------------------------------------------
1 | import { decorateWithLocation } from './utils.js';
2 | import { Assert as AssertPrototype } from './assert.js';
3 |
4 | const noop = () => {};
5 |
6 | const hook = (onResult) => (assert) =>
7 | Object.fromEntries(
8 | Object.keys(AssertPrototype).map((methodName) => [
9 | methodName,
10 | (...args) => onResult(assert[methodName](...args)),
11 | ])
12 | );
13 |
14 | export default (
15 | { onResult = noop } = {
16 | onResult: noop,
17 | }
18 | ) => {
19 | const hookOnAssert = hook((item) => {
20 | const result = decorateWithLocation(item);
21 | onResult(result);
22 | return result;
23 | });
24 |
25 | return hookOnAssert(Object.create(AssertPrototype));
26 | };
27 |
28 | export const Assert = AssertPrototype;
29 |
--------------------------------------------------------------------------------
/zora/test/samples/nested.js:
--------------------------------------------------------------------------------
1 | import { test } from '../../src/index.js';
2 |
3 | test('tester 1', (t) => {
4 | t.ok(true, 'assert1');
5 |
6 | t.test('some nested tester', (t) => {
7 | t.ok(true, 'nested 1');
8 | t.ok(true, 'nested 2');
9 | });
10 |
11 | t.test('some nested tester bis', (t) => {
12 | t.ok(true, 'nested 1');
13 |
14 | t.test('deeply nested', (t) => {
15 | t.ok(true, 'deeply nested really');
16 | t.ok(true, 'deeply nested again');
17 | });
18 |
19 | t.ok(true, 'nested 2');
20 | });
21 |
22 | t.ok(true, 'assert2');
23 | });
24 |
25 | test('tester 2', (t) => {
26 | t.ok(true, 'assert3');
27 |
28 | t.test('nested in two', (t) => {
29 | t.ok(true, 'still happy');
30 | });
31 |
32 | t.ok(true, 'assert4');
33 | });
34 |
--------------------------------------------------------------------------------
/examples/browser-vanilla-vite/test/specs/is-even-component.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import IsEvenComponent from '../../src/is-even-component.js';
3 | import { shallowMount } from '../util.js';
4 |
5 | test('is-even component', ({ test }) => {
6 | test('isEven getter should be true at first', (t) => {
7 | const comp = shallowMount(document.createElement('is-even'));
8 | t.eq(comp.isEven, true);
9 | t.eq(comp.value, 42, 'default value should be 42');
10 | });
11 |
12 | test('isEven getter should be true when the value is even, false otherwise', (t) => {
13 | const comp = shallowMount(document.createElement('is-even'));
14 | t.eq(comp.isEven, true);
15 | t.eq(comp.value, 2);
16 | comp.value = 1;
17 | t.eq(comp.isEven, false);
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/.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 __tests__ 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 |
41 | dist
42 | suites
43 |
44 | .DS_Store
45 |
--------------------------------------------------------------------------------
/pta/readme.md:
--------------------------------------------------------------------------------
1 | # PTA
2 |
3 | Test runner for any zora testing program, using node (version >= 16).
4 |
5 | ## Installation
6 |
7 | ``npm i -g pta``
8 |
9 | ## Usage
10 |
11 | example:
12 |
13 | ``pta test/**/*.js``
14 |
15 | Note: it should work with both module format commonjs and ES
16 |
17 | ## Options
18 |
19 | ```
20 | Usage
21 | pta [ ...]
22 |
23 | Options
24 | --only Runs zora in "only mode"
25 |
26 | --reporter, -R One of tap, log. Otherwise it will use the default reporter
27 |
28 |
29 | Examples
30 | pta
31 | pta test/{unit,int}/**/*.js
32 |
33 | If no argument is provided, the CLI will use the following patterns:
34 | - **/test.js
35 | - **/*.spec.js
36 | - **/*.test.js
37 | - **/test/**/*.js
38 | - **/tests/**/*.js
39 | - **/__tests__/**/*.js
40 | ```
41 |
42 |
--------------------------------------------------------------------------------
/reporters/src/diff/writer/diagnostic.js:
--------------------------------------------------------------------------------
1 | import {
2 | okDiagnostic,
3 | notOkDiagnostic,
4 | failDiagnostic,
5 | notEqualDiagnostic,
6 | isDiagnostic,
7 | isNotDiagnostic,
8 | throwsDiagnostic,
9 | equalDiagnostic,
10 | timeoutDiagnostic,
11 | } from '../diagnostic/index.js';
12 |
13 | export default ({ theme }) => {
14 | const operators = {
15 | ok: okDiagnostic(theme),
16 | notOk: notOkDiagnostic(theme),
17 | fail: failDiagnostic(theme),
18 | notEqual: notEqualDiagnostic(theme),
19 | is: isDiagnostic(theme),
20 | isNot: isNotDiagnostic(theme),
21 | throws: throwsDiagnostic(theme),
22 | equal: equalDiagnostic(theme),
23 | timeout: timeoutDiagnostic(theme),
24 | };
25 |
26 | const unknown = ({ operator }) =>
27 | `unknown operator ${theme.emphasis(operator)}`;
28 |
29 | return (diag) => operators[diag.operator]?.(diag) ?? unknown(diag);
30 | };
31 |
--------------------------------------------------------------------------------
/pta/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pta",
3 | "version": "1.3.0",
4 | "description": "Test runner for nodejs using zora",
5 | "type": "module",
6 | "bin": {
7 | "pta": "src/bin.js"
8 | },
9 | "exports": {
10 | "./package.json": "./package.json"
11 | },
12 | "scripts": {
13 | "test": "node test/samples/index.js"
14 | },
15 | "author": "@lorenzofox3 ",
16 | "license": "MIT",
17 | "prettier": {
18 | "singleQuote": true
19 | },
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/lorenzofox3/zora.git"
23 | },
24 | "files": [
25 | "src"
26 | ],
27 | "keywords": [
28 | "zora",
29 | "test",
30 | "testing",
31 | "cli",
32 | "test-runner"
33 | ],
34 | "dependencies": {
35 | "arg": "^5.0.2",
36 | "globby": "^14.0.0",
37 | "zora-reporters": "*"
38 | },
39 | "peerDependencies": {
40 | "zora": "^6"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/reporters/src/protocol.js:
--------------------------------------------------------------------------------
1 | export const MESSAGE_TYPE = {
2 | TEST_START: 'TEST_START',
3 | ASSERTION: 'ASSERTION',
4 | TEST_END: 'TEST_END',
5 | ERROR: 'ERROR',
6 | UNKNOWN: 'UNKNOWN',
7 | };
8 |
9 | export const newTestMessage = ({ description, skip }) => ({
10 | type: MESSAGE_TYPE.TEST_START,
11 | data: { description, skip },
12 | });
13 |
14 | export const assertionMessage = (data) => ({
15 | type: MESSAGE_TYPE.ASSERTION,
16 | data,
17 | });
18 |
19 | export const testEndMessage = ({ description, executionTime }) => ({
20 | type: MESSAGE_TYPE.TEST_END,
21 | data: {
22 | description,
23 | executionTime,
24 | },
25 | });
26 |
27 | export const errorMessage = ({ error }) => ({
28 | type: MESSAGE_TYPE.ERROR,
29 | data: {
30 | error,
31 | },
32 | });
33 |
34 | export const unknownMessage = ({ message }) => ({
35 | type: MESSAGE_TYPE.UNKNOWN,
36 | data: {
37 | message,
38 | },
39 | });
40 |
--------------------------------------------------------------------------------
/examples/node-es/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "es",
3 | "version": "1.0.0",
4 | "description": "",
5 | "type": "module",
6 | "main": "index.js",
7 | "scripts": {
8 | "test": "node test/index.js",
9 | "dev": "nodemon test/index.js",
10 | "test:only": "ZORA_ONLY=true npm t",
11 | "test:coverage": "c8 node test/index.js",
12 | "test:pta-as-runner": "pta ./test/specs/",
13 | "test:pretty-reporter": "ZORA_REPORTER=json node test/index.js | zr",
14 | "test:json-pipeline": "ZORA_REPORTER=json node test/index.js | grep '\"pass\":false' | fx .data",
15 | "test:tap-pipeline": "node test/index.js | tap-difflet"
16 | },
17 | "author": "",
18 | "license": "ISC",
19 | "dependencies": {
20 | "is-even": "~1.0.0"
21 | },
22 | "devDependencies": {
23 | "c8": "^8.0.1",
24 | "fx": "^31.0.0",
25 | "nodemon": "^3.0.2",
26 | "pta": "*",
27 | "tap-difflet": "~0.7.2",
28 | "zora": "*"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/zora/src/harness.js:
--------------------------------------------------------------------------------
1 | import { Assert, createAssert } from './test.js';
2 |
3 | export const createHarness = ({ onlyMode = false } = {}) => {
4 | const tests = [];
5 |
6 | // WARNING if the "onlyMode is passed to any harness, all the harnesses will be affected.
7 | // However, we do not expect multiple harnesses to be used in the same process
8 | if (onlyMode) {
9 | const { skip, test } = Assert;
10 | Assert.test = skip;
11 | Assert.only = test;
12 | }
13 |
14 | const { test, skip, only } = createAssert({
15 | onResult: (test) => tests.push(test),
16 | });
17 |
18 | // for convenience
19 | test.only = only;
20 | test.skip = skip;
21 |
22 | return {
23 | only,
24 | test,
25 | skip,
26 | report({ reporter }) {
27 | return reporter(createMessageStream(tests));
28 | },
29 | };
30 | };
31 |
32 | async function* createMessageStream(tests) {
33 | for (const test of tests) {
34 | yield* test;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/zora/test/samples/timeout.js:
--------------------------------------------------------------------------------
1 | import { test } from '../../src/index.js';
2 | import { setTimeout } from 'node:timers/promises';
3 |
4 | test(
5 | 'broken promise',
6 | ({ ok }) => {
7 | return new Promise(() => {
8 | }).then(() => {
9 | ok(true);
10 | });
11 | },
12 | { timeout: 500 }
13 | );
14 |
15 | test('timeout in a nested test', ({ test }) => {
16 | test(
17 | 'nested',
18 | ({ ok }) => {
19 | return new Promise(() => {
20 | }).then(() => {
21 | ok(true);
22 | });
23 | },
24 | { timeout: 500 }
25 | );
26 | });
27 |
28 | test('just too long', ({ test }) => {
29 | test(
30 | 'on time',
31 | async ({ ok }) => {
32 | await setTimeout(700);
33 | ok(true);
34 | },
35 | { timeout: 1000 }
36 | );
37 |
38 | test(
39 | 'too late',
40 | async ({ ok }) => {
41 | await setTimeout(700);
42 | ok(true);
43 | },
44 | { timeout: 500 }
45 | );
46 | });
47 |
--------------------------------------------------------------------------------
/examples/browser-vanilla-vite/src/is-even-component.js:
--------------------------------------------------------------------------------
1 | import isEven from 'is-even';
2 |
3 | const template = document.createElement('template');
4 |
5 | template.innerHTML = `
6 |
7 |
`;
8 |
9 | class IsEvenComponent extends HTMLElement {
10 | get value() {
11 | const inputValue = this.shadowRoot.getElementById('input').value;
12 | return inputValue !== void 0 ? Number(inputValue) : 0;
13 | }
14 |
15 | set value(val) {
16 | this.shadowRoot.getElementById('input').value = Number(val);
17 | }
18 |
19 | get isEven() {
20 | return isEven(this.value) === true;
21 | }
22 |
23 | constructor() {
24 | super();
25 | this.attachShadow({ mode: 'open' });
26 | this.shadowRoot.appendChild(template.content.cloneNode(true));
27 | }
28 |
29 | connectedCallback() {
30 | this.value = 2;
31 | }
32 | }
33 |
34 | customElements.define('is-even', IsEvenComponent);
35 |
36 | export default IsEvenComponent;
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zora",
3 | "workspaces": [
4 | "assert",
5 | "reporters",
6 | "zora",
7 | "pta"
8 | ],
9 | "engines": {
10 | "node": ">=15",
11 | "npm": ">=7"
12 | },
13 | "scripts": {
14 | "format": "prettier --write **/{src,test}/**/*.{js,ts}",
15 | "clean": "rm -rf {.,reporters,zora,assert,pta}/{node_modules,dist,package-lock.json}",
16 | "install:fresh": "npm i && npm i --workspaces",
17 | "install:ci": "npm ci --include=dev",
18 | "build": "npm run build --workspaces --if-present",
19 | "clean:install": "npm run clean && npm run install:fresh && npm run build",
20 | "test": "npm t --workspaces --if-present"
21 | },
22 | "devDependencies": {
23 | "@rollup/plugin-commonjs": "^28.0.1",
24 | "@rollup/plugin-node-resolve": "^15.2.3",
25 | "prettier": "^3.1.1",
26 | "rollup": "^4.9.1",
27 | "rollup-plugin-dts": "^6.1.0",
28 | "typescript": "^5.3.3"
29 | },
30 | "prettier": {
31 | "singleQuote": true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/zora/test/samples/nested_async.js:
--------------------------------------------------------------------------------
1 | import { test } from '../../src/index.js';
2 |
3 | const wait = (time) =>
4 | new Promise((resolve) => {
5 | setTimeout(() => resolve(), time);
6 | });
7 |
8 | test('tester 1', async (t) => {
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 |
--------------------------------------------------------------------------------
/assert/src/utils.js:
--------------------------------------------------------------------------------
1 | export const 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 | };
11 |
12 | const specFnRegexp = /zora_spec_fn/;
13 | const zoraInternal = /zora\/dist/;
14 | const filterStackLine = (l) =>
15 | (l && !zoraInternal.test(l) && !l.startsWith('Error')) ||
16 | specFnRegexp.test(l);
17 |
18 | const getAssertionLocation = () => {
19 | const err = new Error();
20 | const stack = (err.stack || '')
21 | .split('\n')
22 | .map((l) => l.trim())
23 | .filter(filterStackLine);
24 | const userLandIndex = stack.findIndex((l) => specFnRegexp.test(l));
25 | const stackline =
26 | userLandIndex >= 1 ? stack[userLandIndex - 1] : stack[0] || 'N/A';
27 | return stackline.replace(/^at|^@/, '');
28 | };
29 |
30 | export const decorateWithLocation = (result) => {
31 | if (result.pass === false) {
32 | return {
33 | ...result,
34 | at: getAssertionLocation(),
35 | };
36 | }
37 | return result;
38 | };
39 |
--------------------------------------------------------------------------------
/perfs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "perfs",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "type": "module",
7 | "scripts": {
8 | "perf:clean": "rm -r ./suites && mkdir -p ./suites/{zora,ava,mocha,tape,uvu}/test/ ./suites/jest/__tests__",
9 | "perf:zora": "time node ./suites/zora/index.js",
10 | "perf:pta": "time pta ./suites/zora/test",
11 | "perf:ava": "time ava ./suites/ava/test/*.js",
12 | "perf:mocha": "time mocha ./suites/mocha/test/",
13 | "perf:mocha:parallel": "time mocha --parallel ./suites/mocha/test/",
14 | "perf:tape": "time tape ./suites/tape/test/*.js",
15 | "perf:jest": "time jest --env=node suites/jest/",
16 | "perf:uvu": "time uvu ./suites/uvu/test/"
17 | },
18 | "prettier": {
19 | "singleQuote": true
20 | },
21 | "author": "",
22 | "license": "ISC",
23 | "devDependencies": {
24 | "arg": "^5.0.2",
25 | "ava": "^6.0.1",
26 | "jest": "^29.7.0",
27 | "mocha": "^10.2.0",
28 | "pta": "*",
29 | "tape": "^5.7.2",
30 | "uvu": "^0.5.6",
31 | "zora": "*"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 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 |
--------------------------------------------------------------------------------
/reporters/src/diff/theme.js:
--------------------------------------------------------------------------------
1 | import * as colors from 'colorette';
2 | import { compose } from '../utils.js';
3 | import { bold, underline } from 'colorette';
4 | import { withMargin } from './utils.js';
5 |
6 | const badge = (fn) => compose([fn, bold, withMargin, String]);
7 |
8 | export const createTheme = ({
9 | bgError = colors.bgRed,
10 | bgSuccess = colors.bgGreen,
11 | bgSkip = colors.bgYellow,
12 | disableFont = colors.gray,
13 | badgeFont = colors.whiteBright,
14 | adornerFont = colors.cyan,
15 | } = {}) => {
16 | const success = compose([bgSuccess, badgeFont]);
17 | const error = compose([bgError, badgeFont]);
18 | return {
19 | errorBadge: badge(error),
20 | successBadge: badge(success),
21 | skipBadge: badge(compose([bgSkip, badgeFont])),
22 | disable: compose([disableFont, withMargin]),
23 | header: bold,
24 | adorner: adornerFont,
25 | emphasis: compose([underline, bold]),
26 | operator: adornerFont,
27 | light: disableFont,
28 | diffExpected: success,
29 | diffActual: error,
30 | };
31 | };
32 |
33 | export const dark = createTheme();
34 |
35 | export const light = createTheme();
36 |
--------------------------------------------------------------------------------
/zora/test/samples/timeout.txt:
--------------------------------------------------------------------------------
1 | TAP version 13
2 | # broken promise
3 | not ok 1 - The test did no complete on time. refer to https://github.com/lorenzofox3/zora/tree/master/zora#test-timeout for more info
4 | ---
5 | operator: "timeout"
6 | actual: "test takes longer than 500ms to complete"
7 | expected: "test takes less than 500ms to complete"
8 | ...
9 | # timeout in a nested test
10 | # nested
11 | not ok 2 - The test did no complete on time. refer to https://github.com/lorenzofox3/zora/tree/master/zora#test-timeout for more info
12 | ---
13 | operator: "timeout"
14 | actual: "test takes longer than 500ms to complete"
15 | expected: "test takes less than 500ms to complete"
16 | ...
17 | # just too long
18 | # on time
19 | ok 3 - should be truthy
20 | # too late
21 | not ok 4 - The test did no complete on time. refer to https://github.com/lorenzofox3/zora/tree/master/zora#test-timeout for more info
22 | ---
23 | operator: "timeout"
24 | actual: "test takes longer than 500ms to complete"
25 | expected: "test takes less than 500ms to complete"
26 | ...
27 |
28 | 1..4
29 | # tests 4
30 | # pass 1
31 | # fail 3
32 | # skip 0
33 |
--------------------------------------------------------------------------------
/reporters/src/diff/writer/summary.js:
--------------------------------------------------------------------------------
1 | const hasSome = (label) => (counter) => counter[label] > 0;
2 | const hasFailure = hasSome('failure');
3 | const hasSkip = hasSome('skip');
4 |
5 | const getPad =
6 | ({ total }) =>
7 | (number) =>
8 | String(number).padStart(String(total).length + 2);
9 |
10 | export default ({ theme }) => {
11 | return {
12 | fail(counter) {
13 | const padNumber = getPad(counter);
14 | const label = `FAIL:${padNumber(counter.failure)}`;
15 | return hasFailure(counter)
16 | ? theme.errorBadge(label)
17 | : theme.disable(label);
18 | },
19 | pass(counter) {
20 | const padNumber = getPad(counter);
21 | const label = `PASS:${padNumber(counter.success)}`;
22 | return hasFailure(counter)
23 | ? theme.disable(label)
24 | : theme.successBadge(label);
25 | },
26 | skip(counter) {
27 | const padNumber = getPad(counter);
28 | const label = `SKIP:${padNumber(counter.skip)}`;
29 | return hasSkip(counter) ? theme.skipBadge(label) : theme.disable(label);
30 | },
31 | total(counter) {
32 | return theme.header(`TOTAL: ${counter.total}`);
33 | },
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/zora/src/index.js:
--------------------------------------------------------------------------------
1 | import { createHarness as privateHarnessFactory } from './harness.js';
2 | import { findConfigurationValue, isBrowser } from './env.js';
3 | import { createJSONReporter, createTAPReporter } from 'zora-reporters';
4 |
5 | let autoStart = true;
6 |
7 | const harness = privateHarnessFactory({
8 | onlyMode: findConfigurationValue('ZORA_ONLY') !== void 0,
9 | });
10 |
11 | export { Assert } from './test.js';
12 | export { createJSONReporter, createTAPReporter } from 'zora-reporters';
13 |
14 | export const only = harness.only;
15 |
16 | export const test = harness.test;
17 |
18 | export const skip = harness.skip;
19 |
20 | export const report = harness.report;
21 |
22 | export const hold = () => !(autoStart = false);
23 |
24 | export const createHarness = (opts) => {
25 | hold();
26 | return privateHarnessFactory(opts);
27 | };
28 |
29 | const start = async () => {
30 | if (autoStart) {
31 | const reporter =
32 | findConfigurationValue('ZORA_REPORTER') === 'json'
33 | ? createJSONReporter()
34 | : createTAPReporter();
35 | await report({ reporter });
36 | }
37 | };
38 |
39 | // on next tick start reporting
40 | if (!isBrowser) {
41 | setTimeout(start, 0);
42 | } else {
43 | window.addEventListener('load', start);
44 | }
45 |
--------------------------------------------------------------------------------
/reporters/src/counter.js:
--------------------------------------------------------------------------------
1 | import { MESSAGE_TYPE } from './protocol.js';
2 | import { isSkipped } from './utils.js';
3 |
4 | const idSequence = () => {
5 | let id = 0;
6 | return () => ++id;
7 | };
8 |
9 | export const createCounter = () => {
10 | const nextId = idSequence();
11 | let success = 0;
12 | let failure = 0;
13 | let skip = 0;
14 |
15 | return Object.create(
16 | {
17 | increment(message) {
18 | const { type } = message;
19 | if (isSkipped(message)) {
20 | skip++;
21 | } else if (type === MESSAGE_TYPE.ASSERTION) {
22 | success += message.data.pass === true ? 1 : 0;
23 | failure += message.data.pass === false ? 1 : 0;
24 | }
25 | },
26 | nextId,
27 | },
28 | {
29 | success: {
30 | enumerable: true,
31 | get() {
32 | return success;
33 | },
34 | },
35 | failure: {
36 | enumerable: true,
37 | get() {
38 | return failure;
39 | },
40 | },
41 | skip: {
42 | enumerable: true,
43 | get() {
44 | return skip;
45 | },
46 | },
47 | total: {
48 | enumerable: true,
49 | get() {
50 | return skip + failure + success;
51 | },
52 | },
53 | }
54 | );
55 | };
56 |
--------------------------------------------------------------------------------
/reporters/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zora-reporters",
3 | "version": "2.0.0",
4 | "description": "a set of reporter for zora testing library",
5 | "type": "module",
6 | "main": "./dist/index.cjs",
7 | "bin": {
8 | "zr": "./src/bin.js"
9 | },
10 | "exports": {
11 | "./package.json": "./package.json",
12 | ".": {
13 | "import": {
14 | "default": "./dist/index.js",
15 | "types": "./dist/index.d.ts"
16 | },
17 | "require": {
18 | "default": "./dist/index.cjs",
19 | "types": "./dist/index.d.cts"
20 | }
21 | }
22 | },
23 | "scripts": {
24 | "test": "pta ./src/*.test.js ./src/**/*.test.js ./src/diff/*.test.js ./src/diff/**/*.test.js",
25 | "build:types": "rollup -c rollup-types.js",
26 | "build:cjs": "rollup -c rollup.js",
27 | "build": "npm run build:types && npm run build:cjs"
28 | },
29 | "files": [
30 | "src",
31 | "dist",
32 | "!*.test.js",
33 | "!src/types"
34 | ],
35 | "repository": {
36 | "type": "git",
37 | "url": "https://github.com/lorenzofox3/zora.git"
38 | },
39 | "author": "@lorenzofox3 ",
40 | "license": "MIT",
41 | "prettier": {
42 | "singleQuote": true
43 | },
44 | "dependencies": {
45 | "arg": "^5.0.2",
46 | "colorette": "^2.0.20",
47 | "diff": "^7.0.0"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thanks for your willingness to contribute !
4 |
5 | ## Prerequisite
6 |
7 | Before implementing anything, please make sure there is an open ticket referring to the bug you are fixing or the wanted feature you are implementing. That's easier for us to track things and make sure we are adding code to something _useful_
8 |
9 | However, if your intention is to improve the documentation, there is no need to open a ticket.
10 |
11 | ## Code
12 |
13 | You'll need [nodejs](https://nodejs.org/en/) (version >= 15) installed on your machine. You will need npm (version >= 7) too as we are using npm workspaces.
14 |
15 | 1. clone the project
16 | 2. run ``npm run clean:install && npm run build``
17 | 3. you can make a quick check everything is ok by running ``npm t``
18 |
19 | This is a monorepo with various _sub packages_. Most of the time, you will work on a project at the same time, and they come with a dev script.
20 |
21 | For example, if you are working on the assertion library:
22 | 1. ``cd assert``
23 | 2. ``npm run dev``
24 |
25 | Once you are done, make sure the integration does not break anything by running the build script and the tests at the root of the project.
26 |
27 | ## Questions
28 |
29 | If you have any further question, please contact us through the Github discussions.
30 |
31 | Thanks again !
32 |
33 |
34 |
--------------------------------------------------------------------------------
/reporters/src/utils.test.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import { defaultSerializer as stringify } from './utils.js';
3 |
4 | test(`serialize`, ({ test }) => {
5 | test('literals', ({ eq }) => {
6 | eq(stringify(4), '4');
7 | eq(stringify('foo'), '"foo"');
8 | eq(stringify(null), 'null');
9 | eq(stringify(undefined), undefined);
10 | });
11 |
12 | test(`simple object`, ({ eq }) => {
13 | eq(stringify({ foo: 'bar' }), `{"foo":"bar"}`);
14 | });
15 |
16 | test(`simple array`, ({ eq }) => {
17 | eq(stringify([{ foo: 'bar' }, 4]), `[{"foo":"bar"},4]`);
18 | });
19 |
20 | test(`symbols`, ({ eq }) => {
21 | eq(stringify(Symbol('some symbol')), '"Symbol(some symbol)"');
22 | eq(
23 | stringify({ foo: Symbol('some symbol') }),
24 | '{"foo":"Symbol(some symbol)"}',
25 | 'nested'
26 | );
27 | });
28 |
29 | test(`circular dependencies`, ({ eq }) => {
30 | const a = {
31 | foo: 'bar',
32 | };
33 | const b = {
34 | foo: 'other bar',
35 | a,
36 | };
37 |
38 | a.b = b;
39 |
40 | eq(
41 | stringify(a),
42 | '{"foo":"bar","b":{"foo":"other bar","a":"[__CIRCULAR_REF__]"}}'
43 | );
44 |
45 | delete b.a;
46 |
47 | eq(
48 | stringify(a),
49 | '{"foo":"bar","b":{"foo":"other bar"}}',
50 | 'should not keep in memory visited node'
51 | );
52 | });
53 | });
54 |
--------------------------------------------------------------------------------
/zora/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "zora",
3 | "version": "6.0.0",
4 | "description": "the lightest yet the fastest javascript testing library",
5 | "keywords": [
6 | "test",
7 | "testing",
8 | "unit",
9 | "tap",
10 | "assertion",
11 | "assert",
12 | "tdd",
13 | "bdd",
14 | "ava",
15 | "tape",
16 | "jest",
17 | "uvu",
18 | "mocha"
19 | ],
20 | "type": "module",
21 | "main": "./dist/index.cjs",
22 | "exports": {
23 | "./package.json": "./package.json",
24 | ".": {
25 | "import": {
26 | "types": "./dist/index.d.ts",
27 | "default": "./dist/index.js"
28 | },
29 | "require": {
30 | "types": "./dist/index.d.cts",
31 | "default": "./dist/index.cjs"
32 | }
33 | }
34 | },
35 | "prettier": {
36 | "singleQuote": true
37 | },
38 | "scripts": {
39 | "build:types": "rollup -c rollup-types.js",
40 | "build:js": "rollup -c rollup.js",
41 | "build": "npm run build:js && npm run build:types",
42 | "build:watch": "rollup -w -c rollup.js",
43 | "test": "node test/run.js"
44 | },
45 | "devDependencies": {
46 | "zora-assert": "*"
47 | },
48 | "files": [
49 | "dist"
50 | ],
51 | "author": "@lorenzofox3 ",
52 | "license": "MIT",
53 | "repository": {
54 | "type": "git",
55 | "url": "https://github.com/lorenzofox3/zora.git"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/reporters/src/diff/utils.test.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import { leftPad, rightPad, typeAsString, withMargin } from './utils.js';
3 |
4 | test('utils', (t) => {
5 | t.test(`typeAsString`, (t) => {
6 | t.test(`literals`, (t) => {
7 | t.eq(typeAsString('some string'), 'string');
8 | t.eq(typeAsString(42), 'number');
9 | t.eq(typeAsString(true), 'boolean');
10 | t.eq(typeAsString(undefined), 'undefined');
11 | });
12 |
13 | t.test(`objects`, (t) => {
14 | t.eq(typeAsString({}), 'Object');
15 | t.eq(typeAsString([]), 'Array');
16 | t.eq(typeAsString(new Date()), 'Date');
17 | t.eq(typeAsString(new Map()), 'Map');
18 | t.eq(typeAsString(new Set()), 'Set');
19 | t.eq(typeAsString(new (class Foo {})()), 'Foo');
20 | t.eq(typeAsString(null), 'null');
21 | });
22 | });
23 |
24 | t.test(`left pad`, (t) => {
25 | t.eq(leftPad(4, 'fo'), ' fo');
26 | const padTwo = leftPad(2);
27 | t.eq(padTwo('fo'), ' fo', 'partial application');
28 | t.eq(padTwo('bar'), ' bar', 'partial application');
29 | });
30 |
31 | t.test(`right pad`, (t) => {
32 | t.eq(rightPad(4, 'fo'), 'fo ');
33 | const padTwo = rightPad(2);
34 | t.eq(padTwo('fo'), 'fo ', 'partial application');
35 | t.eq(padTwo('bar'), 'bar ', 'partial application');
36 | });
37 |
38 | t.test(`with margin`, (t) => {
39 | t.eq(withMargin('some text'), ' some text ');
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/reporters/src/diff/index.js:
--------------------------------------------------------------------------------
1 | import { createCounter } from '../counter.js';
2 | import { createStack } from './stack.js';
3 | import { createWriter } from './writer/index.js';
4 | import { eventuallySetExitCode, isAssertionFailing } from '../utils.js';
5 | import { MESSAGE_TYPE } from '../protocol.js';
6 |
7 | const writeMessage = ({ writer, stack }) => {
8 | const writeTable = {
9 | [MESSAGE_TYPE.TEST_START](message) {
10 | stack.push(message.data.description);
11 | },
12 | [MESSAGE_TYPE.TEST_END]() {
13 | stack.pop();
14 | },
15 | [MESSAGE_TYPE.ASSERTION](message) {
16 | if (isAssertionFailing(message)) {
17 | writer.printFailingTestPath(stack);
18 | if (message.data.at) {
19 | writer.printLocation(message.data.at);
20 | }
21 | writer.printDiagnostic(message.data);
22 | }
23 | },
24 | [MESSAGE_TYPE.ERROR](message) {
25 | // todo
26 | throw message.data.error;
27 | },
28 | };
29 |
30 | return (message) => writeTable[message.type]?.(message);
31 | };
32 |
33 | export default () => async (messageStream) => {
34 | const counter = createCounter();
35 | const stack = createStack();
36 | const writer = createWriter();
37 | const write = writeMessage({ writer, stack });
38 |
39 | for await (const message of messageStream) {
40 | write(message);
41 | counter.increment(message);
42 | eventuallySetExitCode(message);
43 | }
44 | writer.printSummary(counter);
45 | };
46 |
--------------------------------------------------------------------------------
/reporters/src/bin.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { fileURLToPath } from 'url';
3 | import { dirname, resolve } from 'path';
4 | import { EOL } from 'os';
5 | import arg from 'arg';
6 | import { createTAPReporter, createDiffReporter } from './index.js';
7 | import { createReadStream } from 'fs';
8 | import { compose, filter, map, split } from './utils.js';
9 | import { createJSONParser } from './message-parser.js';
10 |
11 | const __filename = fileURLToPath(import.meta.url);
12 | const __dirname = dirname(__filename);
13 |
14 | const reporterMap = {
15 | diff: createDiffReporter({}),
16 | tap: createTAPReporter({}),
17 | };
18 |
19 | const {
20 | ['--reporter']: reporter = 'diff',
21 | ['--help']: help,
22 | ['--strict-mode']: strictMode = false,
23 | } = arg({
24 | ['--help']: Boolean,
25 | ['--reporter']: String,
26 | ['--strict-mode']: Boolean,
27 | ['-R']: '--reporter',
28 | });
29 |
30 | const splitLines = split(EOL);
31 | const compact = filter(Boolean);
32 |
33 | (async () => {
34 | if (help) {
35 | createReadStream(resolve(__dirname, './usage.txt')).pipe(process.stdout);
36 | return;
37 | }
38 |
39 | const parse = createJSONParser({
40 | strictMode,
41 | });
42 |
43 | const getInputStream = compose([parse, compact, splitLines]);
44 |
45 | const inputStream = getInputStream(process.stdin);
46 |
47 | const report = reporterMap[reporter];
48 |
49 | if (!report) {
50 | throw new Error(`unknown reporter "${reporter}"`);
51 | }
52 |
53 | await report(inputStream);
54 | })();
55 |
--------------------------------------------------------------------------------
/zora/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import { IReporter, Message } from '../../../reporters/src/types/index';
2 | import { IAssert as GenericAssertionAPI } from '../../../assert/src/index';
3 |
4 | export interface IReportOptions {
5 | reporter: IReporter;
6 | }
7 |
8 | export interface IHarnessOptions {
9 | onlyMode?: boolean;
10 | }
11 |
12 | export interface ITester {
13 | test: ITestFunction;
14 | skip: ITestFunction;
15 | only: ITestFunction;
16 | }
17 |
18 | export interface IAssert extends GenericAssertionAPI, ITester {}
19 |
20 | export interface ISpecFunction {
21 | (assert: IAssert): any;
22 | }
23 |
24 | export interface ITestOptions {
25 | skip?: boolean;
26 | timeout?: number;
27 | }
28 |
29 | export interface ITestFunction {
30 | (
31 | description: string,
32 | spec: ISpecFunction,
33 | opts?: ITestOptions
34 | ): Promise & AsyncIterable;
35 | }
36 |
37 | export interface ITestHarness extends ITester {
38 | report(opts: IReportOptions): ReturnType;
39 | }
40 |
41 | export {
42 | createJSONReporter,
43 | createTAPReporter,
44 | ILogOptions,
45 | } from '../../../reporters/src/types/index';
46 |
47 | export let Assert: IAssert;
48 | export declare function hold(): void;
49 | export declare function report(opts: IReportOptions): ReturnType;
50 | export declare function createHarness(opts: IHarnessOptions): ITestHarness;
51 | export declare const test: ITestFunction;
52 | export declare const only: ITestFunction;
53 | export declare const skip: ITestFunction;
54 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Zora
2 |
3 | All the projects related to [zora](./zora), the testing library
4 |
5 | * [zora](./zora): the testing library
6 | * [zora-assert](./assert): the assertion library behind zora(unpublished)
7 | * [zora-reporters](./reporters): a set of reporters (can be used as a CLI)
8 | * [pta](./pta): A test runner (used with a Command Line Interface) for Nodejs environment
9 |
10 | The repo also comes with
11 | * [a pseudo benchmark](./perfs) which shows the testing experience with various testing framework
12 | * [a list of recipes](./examples) to get started with different environments (node, browser, typescript, etc)
13 |
14 | ## Goals
15 |
16 | Zora is one of the lightest (if not the lightest), yet one of the fastest Javascript testing library (if not the fastest).
17 |
18 | Its design principles follow the line:
19 |
20 | * Runs with any Javascript environment ([Nodejs](https://nodejs.org/en/), [Deno](https://deno.land/), Browser ): you don't need any specific test runner to run your testing program, it is _just_ a regular javascript program
21 | * Is fast and simple: a [small codebase](https://packagephobia.com/result?p=zora) achieving the [best performances](./perfs) to deliver the best developer experience
22 | * Follows the [UNIX philosophy](https://en.wikipedia.org/wiki/Unix_philosophy): a set of focused, composable small software to deliver the best flexibility with the minimum overhead, rather than a huge monolith hard to tweak, with a large set options.
23 |
24 | ## Contributing
25 |
26 | If you wish to contribute to the project, please refer to the [guidelines](./contributing.md)
27 |
--------------------------------------------------------------------------------
/examples/node-ts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-ts",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "directories": {
7 | "test": "test"
8 | },
9 | "engines": {
10 | "node": "16"
11 | },
12 | "scripts": {
13 | "build": "tsc",
14 | "build:watch": "tsc --watch --preserveWatchOutput",
15 | "test": "node -r source-map-support/register dist/test/index.js",
16 | "test:coverage": "c8 --all -x dist/test -x test node -r source-map-support/register dist/test/index.js",
17 | "test:pretty": "ZORA_REPORTER=json node -r source-map-support/register dist/test/index.js | zr",
18 | "test:pta":"node -r source-map-support/register node_modules/.bin/pta dist/test/specs/*.js",
19 | "test:tap-pipeline": "node -r source-map-support/register dist/test/index.js | tap-difflet",
20 | "test:watch": "nodemon -r source-map-support/register --watch dist/ dist/test/index.js",
21 | "test:pta:watch": "nodemon -r source-map-support/register --watch dist/ node_modules/.bin/pta dist/test/specs/*.js",
22 | "dev": "run-p build:watch test:watch",
23 | "dev:pta": "run-p build:watch test:pta:watch"
24 | },
25 | "author": "",
26 | "license": "ISC",
27 | "devDependencies": {
28 | "@types/is-even": "~1.0.2",
29 | "c8": "~8.0.1",
30 | "nodemon": "~3.0.2",
31 | "npm-run-all": "~4.1.5",
32 | "pta": "*",
33 | "source-map-support": "~0.5.21",
34 | "tap-difflet": "~0.7.2",
35 | "typescript": "~5.3.3",
36 | "zora": "*",
37 | "zora-reporters": "*"
38 | },
39 | "dependencies": {
40 | "is-even": "~1.0.0"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/reporters/src/diff/writer/index.js:
--------------------------------------------------------------------------------
1 | import { compose, defaultLogger } from '../../utils.js';
2 | import { createTheme } from '../theme.js';
3 | import { leftPad, withMargin } from '../utils.js';
4 | import getDiagnosticMessage from './diagnostic.js';
5 | import getSummaryMessage from './summary.js';
6 |
7 | export const createWriter = ({
8 | log = defaultLogger,
9 | theme = createTheme(),
10 | } = {}) => {
11 | const print = compose([log, leftPad(2)]);
12 |
13 | const diagnostics = getDiagnosticMessage({ theme });
14 | const summary = getSummaryMessage({ theme });
15 |
16 | const printDiagnostic = (diag) => {
17 | const { description, operator } = diag;
18 | print('');
19 | const operatorString = theme.operator(`[${operator}]`);
20 | print(description);
21 | print(`${operatorString} ${diagnostics(diag)}`);
22 | };
23 |
24 | const printSummary = (counter) => {
25 | print('');
26 | print(summary.total(counter));
27 | print(summary.pass(counter));
28 | print(summary.fail(counter));
29 | print(summary.skip(counter));
30 | print('');
31 | };
32 |
33 | const printLocation = (at) => print(`${theme.light('at')}: ${at}`);
34 |
35 | const printFailingTestPath = (stack) => {
36 | print('');
37 | const testPath = [...stack];
38 | const current = testPath.pop();
39 | print(
40 | `${theme.errorBadge('FAIL')} ${theme.header(
41 | [...testPath, theme.emphasis(current)].join(
42 | theme.adorner(withMargin('>'))
43 | )
44 | )}`
45 | );
46 | };
47 |
48 | return {
49 | printDiagnostic,
50 | print,
51 | printSummary,
52 | printFailingTestPath,
53 | printLocation,
54 | };
55 | };
56 |
--------------------------------------------------------------------------------
/perfs/readme.md:
--------------------------------------------------------------------------------
1 | # perfs
2 |
3 | A program that aims at giving an idea on how fast common testing libraries/framework can run a similar test suite
4 |
5 | 1. install the dependencies ``npm install``
6 | 2. clean the suites ``npm run perf:clean``
7 | 3. generate a profile ``node scripts/test-files-io.js --files 10 --tests 8 --idle 20``
8 |
9 | Where:
10 | * files: the number of files in the suites
11 | * tests: the number of tests in a file
12 | * idle: the time (in ms) a given test is idle before resuming
13 |
14 | A test would be the equivalent with each library/framework
15 |
16 | ```javascript
17 | import {test} from 'zora';
18 |
19 | test('test ' + i, async function (assert) {
20 | await new Promise(resolve => {
21 | setTimeout(()=>resolve(),${idleTiem});
22 | });
23 | assert.ok(Math.random() * 100 > 5); // 5% of the tests should fail
24 | });
25 | ```
26 |
27 | This test should represent "common" javascript code: non-blocking IO
28 |
29 | ## Results
30 |
31 | On my machine (MacBook Pro, 2.7 GHz i5, 8 GB RAM) for various profiles I get:
32 |
33 | Library: files=5, tests=8, idle=5
34 | Small Web app: files=10, tests=10, idle=30
35 | Web app: files=100, tests=10, idle=30
36 |
37 | | | zora@next | with pta@next | tape@5.7.2 | Jest@29.7.0 | AvA@6.0.1 | Mocha@10.2.0 | uvu@0.5.6
38 | |--------|:---------:|:-------------:|:----------:|:-----------:|:---------:|:------------:|:---------:|
39 | |Library | 124ms | 204ms | 511ms | 2550ms | 1705ms | 582ms | 369ms |
40 | |small web app | 122ms | 208ms | 555ms | 1853ms | 2616ms | 767ms | 294ms |
41 | |web app | 608ms | 475ms | 3521ms | 11780ms | 35320ms | 2050ms | 1790ms |
42 |
--------------------------------------------------------------------------------
/reporters/src/diff/diagnostic/equal.test.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import { diffLine, expandNewLines, getDiffCharThemedMessage } from './equal.js';
3 |
4 | const theme = new Proxy(
5 | {},
6 | {
7 | get: function (target, prop, receiver) {
8 | return (val) => `<${prop}>${val}${prop}>`;
9 | },
10 | }
11 | );
12 |
13 | test(`getDiffCharThemedMessage`, (t) => {
14 | const getMessage = getDiffCharThemedMessage(theme);
15 | const { expected, actual } = getMessage({
16 | actual: 'fob',
17 | expected: 'foo',
18 | });
19 | t.eq(expected, 'foo');
20 | t.eq(actual, 'fob');
21 | });
22 |
23 | test('getDiffJSONThemedMessage', (t) => {
24 | t.test(`expandNewLines`, (t) => {
25 | const lines = [
26 | { value: '{\n "foo": "bar",\n' },
27 | {
28 | removed: true,
29 | value: ' "other": "waht"\n',
30 | },
31 | {
32 | added: true,
33 | value: ' "other": "what"\n',
34 | },
35 | { value: '}' },
36 | ];
37 |
38 | const actual = expandNewLines(lines);
39 |
40 | t.eq(actual, [
41 | { value: '{' },
42 | { value: ' "foo": "bar",' },
43 | {
44 | removed: true,
45 | value: ' "other": "waht"',
46 | },
47 | { added: true, value: ' "other": "what"' },
48 | { value: '}' },
49 | ]);
50 | });
51 |
52 | t.test(`diffLine`, (t) => {
53 | const diff = diffLine(theme);
54 | t.eq(
55 | diff({ added: true, value: 'foo' }),
56 | '+ foo'
57 | );
58 | t.eq(
59 | diff({ removed: true, value: 'foo' }),
60 | '- foo'
61 | );
62 | t.eq(diff({ value: 'foo' }), ' foo');
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/reporters/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | import { IAssertionResult } from '../../../assert/src/index';
2 |
3 | interface INewTestMessageInput {
4 | description: string;
5 | skip: boolean;
6 | }
7 |
8 | interface ITestEndMessageInput {
9 | description: string;
10 | executionTime: number;
11 | }
12 |
13 | interface IMessage {
14 | type: string;
15 | data: T;
16 | }
17 |
18 | interface INewTestMessage extends IMessage {
19 | type: 'TEST_START';
20 | }
21 |
22 | interface IAssertionMessage extends IMessage> {
23 | type: 'ASSERTION';
24 | }
25 |
26 | interface ITestEndMessage extends IMessage {
27 | type: 'TEST_END';
28 | }
29 |
30 | interface IErrorMessage extends IMessage<{ error: unknown }> {
31 | type: 'ERROR';
32 | }
33 |
34 | export type Message =
35 | | IAssertionMessage
36 | | IErrorMessage
37 | | ITestEndMessage
38 | | INewTestMessage;
39 |
40 | export declare function newTestMessage(
41 | opts: INewTestMessageInput
42 | ): INewTestMessage;
43 |
44 | export declare function assertionMessage(
45 | opts: IAssertionResult
46 | ): IAssertionMessage;
47 |
48 | export declare function testEndMessage(
49 | opts: INewTestMessageInput
50 | ): ITestEndMessage;
51 |
52 | export declare function errorMessage(opts: { error: unknown }): IErrorMessage;
53 |
54 | export interface IReporter {
55 | (messageStream: AsyncIterable): Promise;
56 | }
57 |
58 | export interface ILogOptions {
59 | log?: (message: any) => void;
60 | serialize?: (value: any) => string;
61 | }
62 |
63 | export declare function createJSONReporter(opts?: ILogOptions): IReporter;
64 |
65 | export declare function createTAPReporter(opts?: ILogOptions): IReporter;
66 |
67 | export declare function createDiffReporter(): IReporter;
68 |
--------------------------------------------------------------------------------
/reporters/src/tap/index.js:
--------------------------------------------------------------------------------
1 | import { assertionMessage, MESSAGE_TYPE } from '../protocol.js';
2 | import {
3 | defaultLogger,
4 | defaultSerializer,
5 | eventuallySetExitCode,
6 | filter,
7 | } from '../utils.js';
8 | import { createCounter } from '../counter.js';
9 | import { createWriter } from './writer.js';
10 |
11 | const isNotTestEnd = ({ type }) => type !== MESSAGE_TYPE.TEST_END;
12 | const filterOutTestEnd = filter(isNotTestEnd);
13 |
14 | const writeMessage = ({ writer, nextId }) => {
15 | const writerTable = {
16 | [MESSAGE_TYPE.ASSERTION](message) {
17 | return writer.printAssertion(message, { id: nextId() });
18 | },
19 | [MESSAGE_TYPE.TEST_START](message) {
20 | if (message.data.skip) {
21 | const skippedAssertionMessage = assertionMessage({
22 | description: message.data.description,
23 | pass: true,
24 | });
25 | return writer.printAssertion(skippedAssertionMessage, {
26 | comment: 'SKIP',
27 | id: nextId(),
28 | });
29 | }
30 | return writer.printTestStart(message);
31 | },
32 | [MESSAGE_TYPE.ERROR](message) {
33 | writer.printBailOut();
34 | throw message.data.error;
35 | },
36 | };
37 | return (message) => writerTable[message.type]?.(message);
38 | };
39 |
40 | export default ({ log = defaultLogger, serialize = defaultSerializer } = {}) =>
41 | async (messageStream) => {
42 | const writer = createWriter({
43 | log,
44 | serialize,
45 | });
46 | const counter = createCounter();
47 | const write = writeMessage({ writer, nextId: counter.nextId });
48 | const stream = filterOutTestEnd(messageStream);
49 |
50 | writer.printHeader();
51 | for await (const message of stream) {
52 | counter.increment(message);
53 | write(message);
54 | eventuallySetExitCode(message);
55 | }
56 | writer.printSummary(counter);
57 | };
58 |
--------------------------------------------------------------------------------
/assert/src/index.d.ts:
--------------------------------------------------------------------------------
1 | export interface IAssertionResult {
2 | pass: boolean;
3 | actual: unknown;
4 | expected: T;
5 | description: string;
6 | operator: string;
7 | at?: string;
8 | }
9 |
10 | export interface ComparatorAssertionFunction {
11 | (actual: unknown, expected: T, description?: string): IAssertionResult;
12 | }
13 |
14 | export interface BooleanAssertionFunction {
15 | (actual: unknown, description?: string): IAssertionResult;
16 | }
17 |
18 | export type ErrorAssertionFunction = {
19 | (
20 | fn: Function,
21 | expected: RegExp | Function,
22 | description?: string
23 | ): IAssertionResult;
24 | (fn: Function, description?: string): IAssertionResult;
25 | };
26 |
27 | export interface MessageAssertionFunction {
28 | (message?: string): IAssertionResult;
29 | }
30 |
31 | export interface IAssert {
32 | equal: ComparatorAssertionFunction;
33 |
34 | equals: ComparatorAssertionFunction;
35 |
36 | eq: ComparatorAssertionFunction;
37 |
38 | deepEqual: ComparatorAssertionFunction;
39 |
40 | notEqual: ComparatorAssertionFunction;
41 |
42 | notEquals: ComparatorAssertionFunction;
43 |
44 | notEq: ComparatorAssertionFunction;
45 |
46 | notDeepEqual: ComparatorAssertionFunction;
47 |
48 | is: ComparatorAssertionFunction;
49 |
50 | same: ComparatorAssertionFunction;
51 |
52 | isNot: ComparatorAssertionFunction;
53 |
54 | notSame: ComparatorAssertionFunction;
55 |
56 | ok: BooleanAssertionFunction;
57 |
58 | truthy: BooleanAssertionFunction;
59 |
60 | notOk: BooleanAssertionFunction;
61 |
62 | falsy: BooleanAssertionFunction;
63 |
64 | fail: MessageAssertionFunction;
65 |
66 | throws: ErrorAssertionFunction;
67 | }
68 |
69 | declare function factory(options?: IAssertOptions): IAssert;
70 |
71 | export declare const Assert: IAssert;
72 |
73 | export interface IAssertOptions {
74 | onResult: (result: IAssertionResult) => void;
75 | }
76 |
77 | export default factory;
78 |
--------------------------------------------------------------------------------
/reporters/src/tap/writer.js:
--------------------------------------------------------------------------------
1 | import { defaultLogger, defaultSerializer, flatDiagnostic } from '../utils.js';
2 |
3 | export const createWriter = ({
4 | log = defaultLogger,
5 | serialize = defaultSerializer,
6 | version = 13,
7 | } = {}) => {
8 | const print = (message, padding = 0) => {
9 | log(message.padStart(message.length + padding * 4)); // 4 white space used as indent
10 | };
11 |
12 | const printYAML = (obj, padding = 0) => {
13 | const YAMLPadding = padding + 0.5;
14 | print('---', YAMLPadding);
15 | for (const [prop, value] of Object.entries(obj)) {
16 | print(`${prop}: ${serialize(value)}`, YAMLPadding + 0.5);
17 | }
18 | print('...', YAMLPadding);
19 | };
20 |
21 | const printComment = (comment, padding = 0) => {
22 | print(`# ${comment}`, padding);
23 | };
24 |
25 | const printBailOut = () => {
26 | print('Bail out! Unhandled error.');
27 | };
28 |
29 | const printTestStart = (newTestMessage) => {
30 | const {
31 | data: { description },
32 | } = newTestMessage;
33 | printComment(description);
34 | };
35 |
36 | const printAssertion = (assertionMessage, { id, comment = '' }) => {
37 | const { data } = assertionMessage;
38 | const { pass, description } = data;
39 | const label = pass === true ? 'ok' : 'not ok';
40 | const directiveComment = comment ? ` # ${comment}` : '';
41 | print(`${label} ${id} - ${description}` + directiveComment);
42 | if (pass === false) {
43 | printYAML(flatDiagnostic(data));
44 | }
45 | };
46 |
47 | const printSummary = ({ success, skip, failure, total }) => {
48 | print('', 0);
49 | print(`1..${total}`);
50 | printComment(`tests ${total}`, 0);
51 | printComment(`pass ${success}`, 0);
52 | printComment(`fail ${failure}`, 0);
53 | printComment(`skip ${skip}`, 0);
54 | };
55 |
56 | const printHeader = () => {
57 | print(`TAP version ${version}`);
58 | };
59 |
60 | return {
61 | print,
62 | printYAML,
63 | printComment,
64 | printBailOut,
65 | printTestStart,
66 | printAssertion,
67 | printSummary,
68 | printHeader,
69 | };
70 | };
71 |
--------------------------------------------------------------------------------
/reporters/src/utils.js:
--------------------------------------------------------------------------------
1 | import { MESSAGE_TYPE } from './protocol.js';
2 |
3 | const isNode = typeof process !== 'undefined';
4 |
5 | export const flatDiagnostic = ({ pass, description, ...rest }) => rest;
6 |
7 | const createReplacer = () => {
8 | const visited = new Set();
9 | return (key, value) => {
10 | if (isObject(value)) {
11 | if (visited.has(value)) {
12 | return '[__CIRCULAR_REF__]';
13 | }
14 |
15 | visited.add(value);
16 | }
17 |
18 | if (typeof value === 'symbol') {
19 | return value.toString();
20 | }
21 |
22 | return value;
23 | };
24 | };
25 |
26 | const isObject = (candidate) =>
27 | typeof candidate === 'object' && candidate !== null;
28 |
29 | const stringify = (value) => JSON.stringify(value, createReplacer());
30 |
31 | export const defaultSerializer = stringify;
32 |
33 | export const defaultLogger = (value) => console.log(value);
34 |
35 | export const isAssertionFailing = (message) =>
36 | message.type === MESSAGE_TYPE.ASSERTION && !message.data.pass;
37 |
38 | export const isSkipped = (message) =>
39 | message.type === MESSAGE_TYPE.TEST_START && message.data.skip;
40 |
41 | export const eventuallySetExitCode = (message) => {
42 | if (isNode && isAssertionFailing(message)) {
43 | process.exitCode = 1;
44 | }
45 | };
46 |
47 | export const compose = (fns) => (arg) =>
48 | fns.reduceRight((arg, fn) => fn(arg), arg);
49 |
50 | export const split = (separator) =>
51 | async function* (stream) {
52 | let buffer = '';
53 | for await (const chunk of stream) {
54 | const parts = (buffer + chunk.toString()).split(separator);
55 | buffer = parts.pop();
56 | yield* parts;
57 | }
58 |
59 | if (buffer) {
60 | yield buffer;
61 | }
62 | };
63 |
64 | export const filter = (predicate) =>
65 | async function* (stream) {
66 | for await (const element of stream) {
67 | if (predicate(element)) {
68 | yield element;
69 | }
70 | }
71 | };
72 |
73 | export const map = (mapFn) =>
74 | async function* (stream) {
75 | for await (const element of stream) {
76 | yield mapFn(element);
77 | }
78 | };
79 |
--------------------------------------------------------------------------------
/examples/browser-vanilla-vite/bin.js:
--------------------------------------------------------------------------------
1 | import { createServer } from 'vite';
2 | import { pathToFileURL } from 'url';
3 | import WebSocket from 'ws';
4 | import { createDiffReporter } from 'zora-reporters';
5 | import { compose, map } from './test/util.js';
6 |
7 | const fileServerPort = process.env.FILE_SERVER_POIRT || 3000;
8 | const webSocketPort = process.env.WS_SERVER_PORT || 8000;
9 |
10 | const locateOnFS = map((message) => {
11 | if (message.data?.pass !== false) {
12 | return message;
13 | }
14 |
15 | const { at, ...restOfData } = message.data;
16 | const { pathname } = new URL(at);
17 | const onFS = new URL(pathname, pathToFileURL(process.cwd()));
18 |
19 | return {
20 | ...message,
21 | data: {
22 | ...restOfData,
23 | at: onFS,
24 | },
25 | };
26 | });
27 |
28 | (async () => {
29 | const reporter = compose([createDiffReporter(), locateOnFS]);
30 |
31 | const fileServer = await createServer({
32 | server: {
33 | port: fileServerPort,
34 | },
35 | });
36 |
37 | const webSocketServer = new WebSocket.Server({
38 | port: webSocketPort,
39 | });
40 |
41 | webSocketServer.on('connection', (ws) => {
42 | console.debug('client connected');
43 | reporter(streamRunFromSocket(ws));
44 | });
45 |
46 | webSocketServer.on('close', function close() {
47 | console.debug('client disconnected');
48 | });
49 |
50 | await fileServer.listen();
51 | })();
52 |
53 | async function* streamRunFromSocket(socket) {
54 | const buffer = [];
55 | let done = false;
56 | let release;
57 | try {
58 | socket.on('message', listener);
59 |
60 | while (true) {
61 | if (done) {
62 | break;
63 | }
64 | const message = buffer.shift();
65 | if (message) {
66 | yield message;
67 | } else {
68 | await new Promise((resolve) => (release = resolve));
69 | }
70 | }
71 | } finally {
72 | socket.off('message', listener);
73 | }
74 |
75 | function listener(message) {
76 | const messageObj = JSON.parse(message);
77 |
78 | if (messageObj.type === 'RUN_END') {
79 | done = true;
80 | }
81 |
82 | buffer.push(messageObj);
83 | release?.();
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/reporters/readme.md:
--------------------------------------------------------------------------------
1 | # zora-reporters
2 |
3 | A set of reporters to programmatically (or via a CLI) consume a stream made of the zora testing protocol.
4 |
5 | ## installation
6 |
7 | ``npm i zora-reporters``
8 |
9 | ## zora testing protocol messages
10 |
11 | A zora test harness is actually a stream of messages defined by the following interfaces
12 |
13 | ```typescript
14 | interface IMessage {
15 | type: string;
16 | data: T;
17 | }
18 |
19 | interface INewTestMessage extends IMessage {
20 | type: 'TEST_START';
21 | }
22 |
23 | interface IAssertionMessage extends IMessage> {
24 | type: 'ASSERTION';
25 | }
26 |
27 | interface ITestEndMessage extends IMessage {
28 | type: 'TEST_END';
29 | }
30 | ```
31 |
32 | ## usage
33 |
34 | ### programmatically
35 |
36 | create the reporter with a factory which you can then pass to the ``report`` function of any test harness (or to the ``report`` function of the default test harness)
37 |
38 | ```javascript
39 | import {hold, report, test} from 'zora';
40 | import {createDiffReporter} from 'zora-reporters';
41 |
42 | hold(); // prevent the default test harness to start its reporting
43 |
44 | test(`some test`, (t) => {
45 | t.ok(true)
46 | });
47 |
48 | // create your custom reporter
49 | const reporter = createDiffReporter();
50 |
51 | // start reporting
52 | report({reporter});
53 | ```
54 |
55 | ### CLI
56 |
57 | Zora testing program will output by default a TAP report, but you can configure it to output the zora testing protocol instead (thanks to the ``ZORA_REPORTER=json`` en variable for example).
58 |
59 | You can then pipe this output into the zora reporter command line interface:
60 |
61 | ``ZORA_REPORTER=json node path/to/testing/program.js | zr``
62 |
63 | #### Options
64 |
65 | ```
66 | Synopsis
67 | Transforms a zora testing protocol message stream into a specific reporting format
68 |
69 | Usage
70 | zr
71 |
72 | Options
73 | --reporter, -R One of "tap", "diff"(default).
74 |
75 | Examples
76 | zr -R tap < some/file.txt
77 | ZORA_REPORTER=json node zora/program.js | zr -R diff
78 | ```
79 |
80 | ## reporters
81 |
82 | ### TAP (Test Anything Protocol)
83 |
84 | 
85 |
86 | ### Diff
87 |
88 | The default reporter
89 |
90 | 
91 |
92 |
--------------------------------------------------------------------------------
/examples/browser-vanilla-vite/test/runner.js:
--------------------------------------------------------------------------------
1 | import { hold, report } from 'zora';
2 | import { map } from './util.js';
3 | hold();
4 |
5 | const passThroughReporter = map((message) => message);
6 |
7 | const createSocketReporter = (opts = {}) =>
8 | new Promise((resolve, reject) => {
9 | const socket = new WebSocket('ws://localhost:8000');
10 | // Connection opened
11 | socket.addEventListener('open', function (event) {
12 | resolve(async (stream) => {
13 | sendObj({ type: 'RUN_START' });
14 | for await (const message of stream) {
15 | sendObj(message);
16 | }
17 | sendObj({ type: 'RUN_END' });
18 | });
19 | });
20 |
21 | function sendObj(obj) {
22 | return socket.send(JSON.stringify(obj));
23 | }
24 |
25 | socket.addEventListener('error', (err) => {
26 | console.error(err);
27 | reject(err);
28 | });
29 | });
30 |
31 | const devToolReporter = async (stream) => {
32 | for await (const message of stream) {
33 | if (message.type === 'ASSERTION') {
34 | if (message.data.pass === true) {
35 | console.log(message);
36 | } else {
37 | console.error(message);
38 | }
39 | } else {
40 | console.info(message);
41 | }
42 | }
43 | };
44 |
45 | Promise.all(
46 | Object.values(import.meta.glob('./specs/*.js')).map((spec) => spec())
47 | )
48 | .then(() =>
49 | report({
50 | reporter: passThroughReporter,
51 | })
52 | )
53 | .then(async (messageStream) => {
54 | const [st1, st2] = tee(messageStream);
55 | const socketReporter = await createSocketReporter();
56 |
57 | socketReporter(st2);
58 | return devToolReporter(st1);
59 | });
60 |
61 | function iteratorToStream(iterator) {
62 | return new ReadableStream({
63 | async pull(controller) {
64 | const { value, done } = await iterator.next();
65 |
66 | if (done) {
67 | controller.close();
68 | } else {
69 | controller.enqueue(value);
70 | }
71 | },
72 | });
73 | }
74 |
75 | async function* streamAsyncIterator(stream) {
76 | const reader = stream.getReader();
77 |
78 | try {
79 | while (true) {
80 | const { done, value } = await reader.read();
81 | if (done) return;
82 | yield value;
83 | }
84 | } finally {
85 | reader.releaseLock();
86 | }
87 | }
88 |
89 | function tee(iterator) {
90 | return iteratorToStream(iterator).tee().map(streamAsyncIterator);
91 | }
92 |
--------------------------------------------------------------------------------
/zora/test/run.js:
--------------------------------------------------------------------------------
1 | import { spawn } from 'node:child_process';
2 | import { resolve, extname } from 'node:path';
3 | import { readFile } from 'node:fs/promises';
4 | import { readdirSync } from 'node:fs';
5 | import { execPath as node } from 'node:process';
6 | import { test } from '../src/index.js';
7 |
8 | const ONLY_ERROR = ['no_only_mode.js', 'no_only_mode_nested.js'];
9 | const sampleRoot = resolve(process.cwd(), './test/samples/');
10 |
11 | const spawnTest = (file) => {
12 | const env = {};
13 | if (file.startsWith('only')) {
14 | env.ZORA_ONLY = true;
15 | }
16 | return new Promise((resolve) => {
17 | let stdout = '';
18 | let stderr = '';
19 |
20 | const cp = spawn(node, [file], {
21 | cwd: sampleRoot,
22 | stdio: ['pipe', 'pipe', 'pipe'],
23 | env,
24 | });
25 |
26 | cp.stdout.on('data', (chunk) => (stdout += chunk));
27 | cp.stderr.on('data', (chunk) => (stderr += chunk));
28 |
29 | cp.on('exit', () => {
30 | resolve({
31 | stdout: stdout.replace(/at:.*/g, 'at:{STACK}'),
32 | stderr: stderr.replace(/at:.*/g, 'at:{STACK}'),
33 | });
34 | });
35 | });
36 | };
37 |
38 | const directoryFiles = readdirSync(sampleRoot);
39 | const testCases = directoryFiles.filter(
40 | (f) =>
41 | extname(f) === '.js' && !ONLY_ERROR.includes(f) && f !== 'late_collect.js'
42 | );
43 |
44 | for (const f of testCases) {
45 | test(`testing file ${f}`, async ({ eq }) => {
46 | const { stdout } = await spawnTest(f);
47 | const outputFile = resolve(
48 | sampleRoot,
49 | `${[f.split('.')[0], 'txt'].join('.')}`
50 | );
51 | const expectedOutput = await readFile(outputFile, {
52 | encoding: 'utf8',
53 | });
54 | eq(stdout, expectedOutput);
55 | });
56 | }
57 |
58 | test(`ONLY mode is not set`, ({ eq }) => {
59 | for (const f of directoryFiles.filter((f) => ONLY_ERROR.includes(f))) {
60 | test(`testing file ${f}`, async ({ ok }) => {
61 | const { stderr } = await spawnTest(f);
62 | ok(
63 | stderr.includes(
64 | `Error: Can not use "only" method when not in "run only" mode`
65 | )
66 | );
67 | });
68 | }
69 | });
70 |
71 | test('testing late_collect.js', async ({ ok }) => {
72 | const { stderr } = await spawnTest('late_collect.js');
73 | ok(
74 | stderr.includes(`Error: test "late collection"
75 | tried to collect an assertion after it has run to its completion.
76 | You might have forgotten to wait for an asynchronous task to complete`)
77 | );
78 | });
79 |
--------------------------------------------------------------------------------
/reporters/src/tap/writer.test.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import { createWriter } from './writer.js';
3 |
4 | const createTestWriter = ({ eq }) => {
5 | const logs = [];
6 | const writer = createWriter({
7 | log: (message) => logs.push(message),
8 | });
9 | return {
10 | writer,
11 | check(messages, description) {
12 | eq(logs, messages, description);
13 | },
14 | };
15 | };
16 |
17 | test(`tap writer`, ({ test }) => {
18 | test('print with no padding', ({ eq }) => {
19 | const { writer, check } = createTestWriter({ eq });
20 | writer.print('test');
21 | check(['test']);
22 | });
23 | test('print with padding', ({ eq }) => {
24 | const { writer, check } = createTestWriter({ eq });
25 | writer.print('test', 2);
26 | check([' test']);
27 | });
28 | test('printComment with no padding', ({ eq }) => {
29 | const { writer, check } = createTestWriter({ eq });
30 | writer.printComment('test');
31 | check(['# test']);
32 | });
33 | test('printComment with padding', ({ eq }) => {
34 | const { writer, check } = createTestWriter({ eq });
35 | writer.printComment('test', 1);
36 | check([' # test']);
37 | });
38 |
39 | test('print header', ({ eq }) => {
40 | const { writer, check } = createTestWriter({ eq });
41 | writer.printHeader();
42 | check(['TAP version 13']);
43 | });
44 |
45 | test('print bailout', ({ eq }) => {
46 | const { writer, check } = createTestWriter({ eq });
47 | writer.printBailOut();
48 | check(['Bail out! Unhandled error.']);
49 | });
50 |
51 | test('print test start', ({ eq }) => {
52 | const { writer, check } = createTestWriter({ eq });
53 | writer.printTestStart({ data: { description: 'some test' } });
54 | check(['# some test']);
55 | });
56 |
57 | test('print assertion', ({ test, skip }) => {
58 | test('simple passing assertion', ({ eq }) => {
59 | const { writer, check } = createTestWriter({ eq });
60 | writer.printAssertion(
61 | {
62 | data: {
63 | pass: true,
64 | description: 'some test',
65 | },
66 | },
67 | { id: 66 }
68 | );
69 | check(['ok 66 - some test']);
70 | });
71 | });
72 |
73 | test('print summary', ({ eq }) => {
74 | const { writer, check } = createTestWriter({ eq });
75 | writer.printSummary({
76 | success: 3,
77 | skip: 1,
78 | failure: 5,
79 | total: 9,
80 | });
81 | check(['', '1..9', '# tests 9', '# pass 3', '# fail 5', '# skip 1']);
82 | });
83 | });
84 |
--------------------------------------------------------------------------------
/pta/src/bin.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { dirname, resolve } from 'node:path';
3 | import { fileURLToPath, pathToFileURL } from 'node:url';
4 | import process from 'node:process';
5 | import { createReadStream } from 'node:fs';
6 |
7 | import arg from 'arg';
8 | import { globby } from 'globby';
9 | import {
10 | createDiffReporter,
11 | createJSONReporter,
12 | createTAPReporter,
13 | } from 'zora-reporters';
14 |
15 | const __filename = fileURLToPath(import.meta.url);
16 | const __dirname = dirname(__filename);
17 |
18 | const reporterMap = {
19 | diff: createDiffReporter(),
20 | tap: createTAPReporter(),
21 | json: createJSONReporter(),
22 | log: createJSONReporter(), // alias for "json"
23 | };
24 |
25 | const DEFAULT_FILE_PATTERNS = [
26 | '**/test.{js,cjs,mjs}',
27 | '**/*.spec.{js,cjs,mjs}',
28 | '**/*.test.{js,cjs,mjs}',
29 | '**/test/**/*.{js,cjs,mjs}',
30 | '**/tests/**/*.{js,cjs,mjs}',
31 | '**/__tests__/**/*.{js,cjs,mjs}',
32 | '!**/node_modules',
33 | '!node_modules',
34 | ];
35 |
36 | const {
37 | ['--reporter']: reporter = 'diff',
38 | ['--only']: only = false,
39 | ['--help']: help = false,
40 | ['--timeout']: defaultTimeout = 5000,
41 | _: filePatterns,
42 | } = arg({
43 | ['--reporter']: String,
44 | ['--only']: Boolean,
45 | ['--help']: Boolean,
46 | ['--timeout']: Number,
47 | // --module-loader is now ignored.
48 | // kept in schema to avoid breaking user's existing usage.
49 | ['--module-loader']: String,
50 | ['-R']: '--reporter',
51 | });
52 |
53 | (async () => {
54 | if (help) {
55 | createReadStream(resolve(__dirname, './usage.txt')).pipe(process.stdout);
56 | return;
57 | }
58 |
59 | // we set the env var before loading zora
60 | if (only) {
61 | process.env.ZORA_ONLY = true;
62 | }
63 |
64 | process.env.ZORA_TIMEOUT = defaultTimeout;
65 |
66 | // loading zora to hold the singleton
67 | const { hold, report } = await import('zora');
68 | hold();
69 |
70 | const reporterInstance = reporterMap[reporter] || reporter.diff;
71 | const files = await globby(
72 | filePatterns.length ? filePatterns : DEFAULT_FILE_PATTERNS
73 | );
74 |
75 | if (!files.length) {
76 | console.warn(`no file matching the patterns: ${filePatterns.join(', ')}`);
77 | return;
78 | }
79 |
80 | for (const file of files) {
81 | const filePath = resolve(process.cwd(), file);
82 | await import(pathToFileURL(filePath)); // load file in sequence so any top level await allows the tests to run sequentially if needed
83 | }
84 |
85 | await report({
86 | reporter: reporterInstance,
87 | });
88 | })();
89 |
--------------------------------------------------------------------------------
/reporters/src/counter.test.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import { createCounter } from './counter.js';
3 | import {
4 | testEndMessage,
5 | newTestMessage,
6 | errorMessage,
7 | assertionMessage,
8 | } from './protocol.js';
9 |
10 | test('counter', ({ test }) => {
11 | test('with tests end message, should not increment any value', ({ eq }) => {
12 | const counter = createCounter();
13 | const message = testEndMessage({
14 | description: 'example',
15 | executionTime: 42,
16 | });
17 | counter.increment(message);
18 | eq(counter, {
19 | success: 0,
20 | failure: 0,
21 | skip: 0,
22 | total: 0,
23 | });
24 | counter.increment(message);
25 | eq(counter, {
26 | success: 0,
27 | failure: 0,
28 | skip: 0,
29 | total: 0,
30 | });
31 | });
32 |
33 | test('with error message, should not increment any value', ({ eq }) => {
34 | const counter = createCounter();
35 | const message = errorMessage({ error: new Error('some error') });
36 | counter.increment(message);
37 | eq(counter, {
38 | success: 0,
39 | failure: 0,
40 | skip: 0,
41 | total: 0,
42 | });
43 | counter.increment(message);
44 | eq(counter, {
45 | success: 0,
46 | failure: 0,
47 | skip: 0,
48 | total: 0,
49 | });
50 | });
51 |
52 | test('with tests start message, should not increment any value', ({ eq }) => {
53 | const counter = createCounter();
54 | const message = newTestMessage({
55 | description: 'example',
56 | skip: false,
57 | });
58 | counter.increment(message);
59 | eq(
60 | counter,
61 | {
62 | success: 0,
63 | failure: 0,
64 | skip: 0,
65 | total: 0,
66 | },
67 | 'should not increment the counter'
68 | );
69 | counter.increment(
70 | newTestMessage({
71 | description: 'example',
72 | skip: true,
73 | })
74 | );
75 | eq(
76 | counter,
77 | {
78 | success: 0,
79 | failure: 0,
80 | skip: 1,
81 | total: 1,
82 | },
83 | 'should update counter when tests is skipped'
84 | );
85 | });
86 |
87 | test('with assertion message, should increment values depending on the test result', ({
88 | eq,
89 | }) => {
90 | const message = assertionMessage({
91 | pass: true,
92 | });
93 | const counter = createCounter();
94 | counter.increment(message);
95 | eq(
96 | counter,
97 | { success: 1, failure: 0, skip: 0, total: 1 },
98 | 'when assertion is passing'
99 | );
100 | counter.increment(message);
101 | eq(counter, { success: 2, failure: 0, skip: 0, total: 2 });
102 | counter.increment(
103 | assertionMessage({ pass: false }),
104 | 'when assertion is failing'
105 | );
106 | eq(counter, { success: 2, failure: 1, skip: 0, total: 3 });
107 | });
108 | });
109 |
--------------------------------------------------------------------------------
/assert/test/factory.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import createAssert, { Assert } from '../src/index.js';
3 |
4 | const omitAt = ({ at, ...rest }) => rest;
5 |
6 | // use callback
7 | test('use callback', ({ eq }) => {
8 | const assertions = [];
9 | const onResult = (result) => assertions.push(result);
10 |
11 | const assert = createAssert({ onResult });
12 |
13 | assert.eq('foo', 'foo');
14 | assert.eq('foo', 'bar');
15 |
16 | eq(assertions.map(omitAt), [
17 | {
18 | pass: true,
19 | actual: 'foo',
20 | expected: 'foo',
21 | description: 'should be equivalent',
22 | operator: 'equal',
23 | },
24 | {
25 | pass: false,
26 | actual: 'foo',
27 | expected: 'bar',
28 | description: 'should be equivalent',
29 | operator: 'equal',
30 | },
31 | ]);
32 |
33 | eq(Boolean(assertions[0].at), false);
34 | eq(
35 | Boolean(assertions[1].at),
36 | true,
37 | 'should have decorated the result with the location'
38 | );
39 | });
40 |
41 | // upgrade API
42 | test('upgrade API', ({ eq }) => {
43 | Assert.isFoo = (expected, description = 'should be foo') => {
44 | return {
45 | pass: expected === 'foo',
46 | expected,
47 | actual: 'foo',
48 | operator: 'isFoo',
49 | description,
50 | };
51 | };
52 |
53 | const assertions = [];
54 | const onResult = (result) => assertions.push(result);
55 |
56 | const assert = createAssert({ onResult });
57 |
58 | assert.isFoo('foo');
59 | assert.isFoo('bar');
60 |
61 | eq(assertions.map(omitAt), [
62 | {
63 | pass: true,
64 | actual: 'foo',
65 | expected: 'foo',
66 | description: 'should be foo',
67 | operator: 'isFoo',
68 | },
69 | {
70 | pass: false,
71 | actual: 'foo',
72 | expected: 'bar',
73 | description: 'should be foo',
74 | operator: 'isFoo',
75 | },
76 | ]);
77 |
78 | eq(Boolean(assertions[0].at), false);
79 | eq(
80 | Boolean(assertions[1].at),
81 | true,
82 | 'should have decorated the result with the location'
83 | );
84 | });
85 |
86 | // bind functions
87 | test('bind functions', ({ eq }) => {
88 | const assertions = [];
89 | const onResult = (result) => assertions.push(result);
90 |
91 | const { eq: t } = createAssert({ onResult });
92 |
93 | t('foo', 'foo');
94 | t('foo', 'bar');
95 |
96 | eq(assertions.map(omitAt), [
97 | {
98 | pass: true,
99 | actual: 'foo',
100 | expected: 'foo',
101 | description: 'should be equivalent',
102 | operator: 'equal',
103 | },
104 | {
105 | pass: false,
106 | actual: 'foo',
107 | expected: 'bar',
108 | description: 'should be equivalent',
109 | operator: 'equal',
110 | },
111 | ]);
112 |
113 | eq(Boolean(assertions[0].at), false);
114 | eq(
115 | Boolean(assertions[1].at),
116 | true,
117 | 'should have decorated the result with the location'
118 | );
119 | });
120 |
--------------------------------------------------------------------------------
/assert/src/assert.js:
--------------------------------------------------------------------------------
1 | import { Operator } from './utils.js';
2 | import eq from 'fast-deep-equal/es6/index.js';
3 |
4 | export const equal = (
5 | actual,
6 | expected,
7 | description = 'should be equivalent'
8 | ) => ({
9 | pass: eq(actual, expected),
10 | actual,
11 | expected,
12 | description,
13 | operator: Operator.EQUAL,
14 | });
15 |
16 | export const notEqual = (
17 | actual,
18 | expected,
19 | description = 'should not be equivalent'
20 | ) => ({
21 | pass: !eq(actual, expected),
22 | actual,
23 | expected,
24 | description,
25 | operator: Operator.NOT_EQUAL,
26 | });
27 |
28 | export const is = (actual, expected, description = 'should be the same') => ({
29 | pass: Object.is(actual, expected),
30 | actual,
31 | expected,
32 | description,
33 | operator: Operator.IS,
34 | });
35 |
36 | export const isNot = (
37 | actual,
38 | expected,
39 | description = 'should not be the same'
40 | ) => ({
41 | pass: !Object.is(actual, expected),
42 | actual,
43 | expected,
44 | description,
45 | operator: Operator.IS_NOT,
46 | });
47 |
48 | export const ok = (actual, description = 'should be truthy') => ({
49 | pass: Boolean(actual),
50 | actual,
51 | expected: 'truthy value',
52 | description,
53 | operator: Operator.OK,
54 | });
55 |
56 | export const notOk = (actual, description = 'should be falsy') => ({
57 | pass: !Boolean(actual),
58 | actual,
59 | expected: 'falsy value',
60 | description,
61 | operator: Operator.NOT_OK,
62 | });
63 |
64 | export const fail = (description = 'fail called') => ({
65 | pass: false,
66 | actual: 'fail called',
67 | expected: 'fail not called',
68 | description,
69 | operator: Operator.FAIL,
70 | });
71 |
72 | export const throws = (func, expected, description = 'should throw') => {
73 | let caught;
74 | let pass;
75 | let actual;
76 | if (typeof expected === 'string') {
77 | [expected, description] = [void 0, expected];
78 | }
79 | try {
80 | func();
81 | } catch (err) {
82 | caught = { error: err };
83 | }
84 | pass = caught !== undefined;
85 | actual = caught?.error;
86 |
87 | if (expected instanceof RegExp) {
88 | pass = expected.test(actual) || expected.test(actual && actual.message);
89 | actual = actual?.message ?? actual;
90 | expected = String(expected);
91 | } else if (typeof expected === 'function' && caught) {
92 | pass = actual instanceof expected;
93 | actual = actual.constructor;
94 | } else {
95 | actual = pass ? 'error thrown' : 'no error thrown';
96 | }
97 |
98 | return {
99 | pass,
100 | actual,
101 | expected: expected ?? 'any error thrown',
102 | description: description,
103 | operator: Operator.THROWS,
104 | };
105 | };
106 |
107 | export const Assert = {
108 | equal,
109 | equals: equal,
110 | eq: equal,
111 | deepEqual: equal,
112 | same: equal,
113 | notEqual,
114 | notEquals: notEqual,
115 | notEq: notEqual,
116 | notDeepEqual: notEqual,
117 | notSame: notEqual,
118 | is,
119 | isNot,
120 | ok,
121 | truthy: ok,
122 | notOk,
123 | falsy: notOk,
124 | fail,
125 | throws,
126 | };
127 |
--------------------------------------------------------------------------------
/zora/src/test.js:
--------------------------------------------------------------------------------
1 | import assertFactory, { Assert } from 'zora-assert';
2 | import {
3 | assertionMessage,
4 | errorMessage,
5 | newTestMessage,
6 | testEndMessage,
7 | } from 'zora-reporters';
8 | import { findConfigurationValue } from './env.js';
9 |
10 | const DEFAULT_TIMEOUT = 5_000;
11 | const defaultOptions = Object.freeze({
12 | skip: false,
13 | timeout: findConfigurationValue('ZORA_TIMEOUT') || DEFAULT_TIMEOUT,
14 | });
15 | const noop = () => {};
16 |
17 | const isTest = (assertionLike) =>
18 | assertionLike[Symbol.asyncIterator] !== void 0;
19 |
20 | Assert.test = (description, spec, opts = defaultOptions) =>
21 | test(description, spec, opts);
22 |
23 | Assert.skip = (description, spec, opts = defaultOptions) =>
24 | test(description, spec, { ...opts, skip: true });
25 |
26 | Assert.only = () => {
27 | throw new Error(`Can not use "only" method when not in "run only" mode`);
28 | };
29 |
30 | const createTimeoutResult = ({ timeout }) => ({
31 | operator: 'timeout',
32 | pass: false,
33 | actual: `test takes longer than ${timeout}ms to complete`,
34 | expected: `test takes less than ${timeout}ms to complete`,
35 | description:
36 | 'The test did no complete on time. refer to https://github.com/lorenzofox3/zora/tree/master/zora#test-timeout for more info',
37 | });
38 |
39 | export const test = (description, spec, opts = defaultOptions) => {
40 | const { skip = false, timeout = DEFAULT_TIMEOUT } = opts;
41 | const assertions = [];
42 | let executionTime;
43 | let done = false;
44 | let error;
45 |
46 | const onResult = (assertion) => {
47 | if (done) {
48 | throw new Error(`test "${description}"
49 | tried to collect an assertion after it has run to its completion.
50 | You might have forgotten to wait for an asynchronous task to complete
51 | ------
52 | ${spec.toString()}`);
53 | }
54 |
55 | assertions.push(assertion);
56 | };
57 |
58 | const specFn = skip
59 | ? noop
60 | : function zora_spec_fn() {
61 | return spec(assertFactory({ onResult }));
62 | };
63 |
64 | const testRoutine = (async function () {
65 | try {
66 | let timeoutId;
67 | const start = Date.now();
68 | const result = await Promise.race([
69 | specFn(),
70 | new Promise((resolve) => {
71 | timeoutId = setTimeout(() => {
72 | onResult(createTimeoutResult({ timeout }));
73 | resolve();
74 | }, timeout);
75 | }),
76 | ]);
77 | clearTimeout(timeoutId);
78 | executionTime = Date.now() - start;
79 | return result;
80 | } catch (e) {
81 | error = e;
82 | } finally {
83 | done = true;
84 | }
85 | })();
86 |
87 | return Object.assign(testRoutine, {
88 | [Symbol.asyncIterator]: async function* () {
89 | yield newTestMessage({ description, skip });
90 | await testRoutine;
91 | for (const assertion of assertions) {
92 | if (isTest(assertion)) {
93 | yield* assertion;
94 | } else {
95 | yield assertionMessage(assertion);
96 | }
97 | }
98 |
99 | if (error) {
100 | yield errorMessage({ error });
101 | }
102 |
103 | yield testEndMessage({ description, executionTime });
104 | },
105 | });
106 | };
107 |
108 | export { Assert } from 'zora-assert';
109 |
110 | export const createAssert = assertFactory;
111 |
--------------------------------------------------------------------------------
/reporters/src/message-parser.test.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import { newTestMessage, testEndMessage } from './protocol.js';
3 | import { createJSONParser } from './message-parser.js';
4 |
5 | const bufferStream = async (stream) => {
6 | const buffer = [];
7 | for await (const m of stream) {
8 | buffer.push(m);
9 | }
10 | return buffer;
11 | };
12 |
13 | test('parse message stream in fault tolerant mode', (t) => {
14 | t.test(`should parse zora protocol messages`, async (t) => {
15 | const messageStream = async function* () {
16 | yield JSON.stringify(newTestMessage({ description: 'test start' }));
17 | yield JSON.stringify(
18 | testEndMessage({ description: 'test start', executionTime: 666 })
19 | );
20 | };
21 |
22 | const parse = createJSONParser({ strictMode: false });
23 | const buffer = await bufferStream(parse(messageStream()));
24 | t.eq(buffer, [
25 | { type: 'TEST_START', data: { description: 'test start' } },
26 | {
27 | type: 'TEST_END',
28 | data: { description: 'test start', executionTime: 666 },
29 | },
30 | ]);
31 | });
32 |
33 | t.test(
34 | `should emit a "unknown" message for line the parser can not parse`,
35 | async (t) => {
36 | const messageStream = async function* () {
37 | yield JSON.stringify(newTestMessage({ description: 'test start' }));
38 | yield 'whatever';
39 | yield JSON.stringify(
40 | testEndMessage({ description: 'test start', executionTime: 666 })
41 | );
42 | };
43 |
44 | const parse = createJSONParser({ strictMode: false });
45 | const buffer = await bufferStream(parse(messageStream()));
46 | t.eq(buffer, [
47 | { type: 'TEST_START', data: { description: 'test start' } },
48 | { type: 'UNKNOWN', data: { message: 'whatever' } },
49 | {
50 | type: 'TEST_END',
51 | data: { description: 'test start', executionTime: 666 },
52 | },
53 | ]);
54 | }
55 | );
56 | });
57 |
58 | test('parse message stream in strict mode mode', (t) => {
59 | t.test(`should parse zora protocol messages`, async (t) => {
60 | const messageStream = async function* () {
61 | yield JSON.stringify(newTestMessage({ description: 'test start' }));
62 | yield JSON.stringify(
63 | testEndMessage({ description: 'test start', executionTime: 666 })
64 | );
65 | };
66 |
67 | const parse = createJSONParser({ strictMode: true });
68 | const buffer = await bufferStream(parse(messageStream()));
69 | t.eq(buffer, [
70 | { type: 'TEST_START', data: { description: 'test start' } },
71 | {
72 | type: 'TEST_END',
73 | data: { description: 'test start', executionTime: 666 },
74 | },
75 | ]);
76 | });
77 |
78 | t.test(
79 | `should emit a "unknown" message for line the parser can not parse`,
80 | async (t) => {
81 | const messageStream = async function* () {
82 | yield JSON.stringify(newTestMessage({ description: 'test start' }));
83 | yield 'whatever';
84 | yield JSON.stringify(
85 | testEndMessage({ description: 'test start', executionTime: 666 })
86 | );
87 | };
88 |
89 | const parse = createJSONParser({ strictMode: true });
90 | try {
91 | const buffer = await bufferStream(parse(messageStream()));
92 | t.fail('should not reach that code');
93 | } catch (e) {
94 | t.eq(e.message, 'could not parse line "whatever"');
95 | }
96 | }
97 | );
98 | });
99 |
--------------------------------------------------------------------------------
/reporters/src/diff/writer/summary.test.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import getSummaryMessage from './summary.js';
3 |
4 | const theme = new Proxy(
5 | {},
6 | {
7 | get: function (target, prop, receiver) {
8 | return (val) => `<${prop}>${val}${prop}>`;
9 | },
10 | }
11 | );
12 |
13 | test(`summary message`, (t) => {
14 | const { fail, total, pass, skip } = getSummaryMessage({ theme });
15 |
16 | t.test(`fail`, (t) => {
17 | t.eq(
18 | fail({
19 | failure: 4,
20 | total: 4,
21 | }),
22 | `FAIL: 4`,
23 | 'when failure count is greater than 0'
24 | );
25 |
26 | t.eq(
27 | fail({
28 | failure: 0,
29 | total: 4,
30 | }),
31 | `FAIL: 0`,
32 | 'when failure count is 0'
33 | );
34 |
35 | t.eq(
36 | fail({
37 | failure: 4,
38 | total: 10,
39 | }),
40 | `FAIL: 4`,
41 | 'when failure count is greater than 0 and total has one more digit'
42 | );
43 |
44 | t.eq(
45 | fail({
46 | failure: 0,
47 | total: 10,
48 | }),
49 | `FAIL: 0`,
50 | 'when failure count is 0 and total has one more digit'
51 | );
52 | });
53 |
54 | t.test(`pass`, (t) => {
55 | t.eq(
56 | pass({
57 | failure: 1,
58 | success: 5,
59 | total: 6,
60 | }),
61 | `PASS: 5`,
62 | 'when failure count is greater than 0'
63 | );
64 |
65 | t.eq(
66 | pass({
67 | failure: 0,
68 | success: 4,
69 | total: 4,
70 | }),
71 | `PASS: 4`,
72 | 'when failure count is 0'
73 | );
74 |
75 | t.eq(
76 | pass({
77 | failure: 4,
78 | success: 6,
79 | total: 10,
80 | }),
81 | `PASS: 6`,
82 | 'when failure count is greater than 0 and total has one more digit'
83 | );
84 |
85 | t.eq(
86 | pass({
87 | failure: 0,
88 | success: 9,
89 | total: 10,
90 | }),
91 | `PASS: 9`,
92 | 'when failure count is 0 and total has one more digit'
93 | );
94 | });
95 |
96 | t.test(`skip`, (t) => {
97 | t.eq(
98 | skip({
99 | failure: 1,
100 | success: 5,
101 | total: 8,
102 | skip: 2,
103 | }),
104 | `SKIP: 2`,
105 | 'when skip count is greater than 0'
106 | );
107 |
108 | t.eq(
109 | skip({
110 | failure: 0,
111 | success: 4,
112 | total: 4,
113 | skip: 0,
114 | }),
115 | `SKIP: 0`,
116 | 'when skip count is 0'
117 | );
118 |
119 | t.eq(
120 | skip({
121 | failure: 4,
122 | skip: 3,
123 | success: 3,
124 | total: 10,
125 | }),
126 | `SKIP: 3`,
127 | 'when skip count is greater than 0 and total has one more digit'
128 | );
129 |
130 | t.eq(
131 | skip({
132 | failure: 1,
133 | skip: 0,
134 | success: 9,
135 | total: 10,
136 | }),
137 | `SKIP: 0`,
138 | 'when skip count is 0 and total has one more digit'
139 | );
140 | });
141 |
142 | t.test(`total`, (t) => {
143 | t.eq(total({ total: 4 }), '');
144 | t.eq(total({ total: 10 }), '');
145 | t.eq(total({ total: 100 }), '');
146 | });
147 | });
148 |
--------------------------------------------------------------------------------
/perfs/scripts/test-files-io.js:
--------------------------------------------------------------------------------
1 | import arg from 'arg';
2 | import { writeFileSync } from 'fs';
3 | import { join } from 'path';
4 |
5 | const range = (number = 1) =>
6 | Array.from({
7 | length: number,
8 | }).map((_, index) => index + 1);
9 |
10 | const {
11 | ['--files']: filesCount = 10,
12 | ['--tests']: testCount = 8,
13 | ['--idle']: waitTime = 30,
14 | } = arg({
15 | '--files': Number,
16 | '--tests': Number,
17 | '--idle': Number,
18 | });
19 |
20 | const errorRate = 5;
21 |
22 | const zoraCode = `
23 | import {test} from 'zora';
24 | for (let i = 0; i < ${testCount}; i++) {
25 | test('test ' + i, async function (assert) {
26 | await new Promise(resolve => {
27 | setTimeout(()=>resolve(),${waitTime});
28 | });
29 | assert.ok(Math.random() * 100 > ${errorRate});
30 | });
31 | };
32 | `;
33 |
34 | const avaCode = `
35 | import test from 'ava';
36 | for (let i = 0; i < ${testCount}; i++) {
37 | test('test ' + i, async function (assert) {
38 | await new Promise(resolve => {
39 | setTimeout(()=>resolve(),${waitTime});
40 | });
41 | assert.truthy(Math.random() * 100 > ${errorRate});
42 | });
43 | }
44 | `;
45 |
46 | const mochaCode = `
47 | import assert from 'assert';
48 | describe('test file', function() {
49 | for(let i=0; i < ${testCount};i++){
50 | it('test ' + i, function(done) {
51 | setTimeout(()=>{
52 | assert.ok(Math.random() * 100 > ${errorRate});
53 | done();
54 | },${waitTime});
55 | });
56 | }
57 | });
58 | `;
59 |
60 | const tapeCode = `
61 | import test from 'tape';
62 | for (let i = 0; i < ${testCount}; i++) {
63 | test('test ' + i, function (assert) {
64 | setTimeout(()=>{
65 | assert.ok(Math.random() * 100 > ${errorRate});
66 | assert.end();
67 | },${waitTime});
68 | });
69 | }
70 | `;
71 |
72 | const jestCode = `
73 | describe('add', function () {
74 | for (let i = 0; i < ${testCount}; i++) {
75 | it('should test',async function () {
76 | await new Promise(resolve => {
77 | setTimeout(()=>resolve(),${waitTime});
78 | });
79 | expect(Math.random() * 100 > ${errorRate}).toBeTruthy();
80 | });
81 | }
82 | });
83 | `;
84 |
85 | const uvuCode = `
86 | import { test } from 'uvu';
87 | import * as assert from 'uvu/assert';
88 |
89 | for (let i = 0; i < ${testCount}; i++) {
90 | test('test ' + i, async function () {
91 | await new Promise(resolve => {
92 | setTimeout(()=>resolve(),${waitTime});
93 | });
94 | assert.ok(Math.random() * 100 > ${errorRate});
95 | });
96 | }
97 |
98 | test.run();
99 | `;
100 | const zoraIndex = `
101 | ${range(filesCount)
102 | .map((i) => `import './test/test${i}.js'`)
103 | .join(';\n')}
104 | `;
105 |
106 | for (let i = 1; i <= filesCount; i++) {
107 | const zoraPath = join(
108 | process.cwd(),
109 | '/suites/zora/test/',
110 | 'test' + i + '.js'
111 | );
112 | const avaPath = join(process.cwd(), '/suites/ava/test/', 'test' + i + '.js');
113 | const mochaPath = join(
114 | process.cwd(),
115 | '/suites/mocha/test/',
116 | 'test' + i + '.js'
117 | );
118 | const tapePath = join(
119 | process.cwd(),
120 | '/suites/tape/test/',
121 | 'test' + i + '.js'
122 | );
123 | const jestPath = join(
124 | process.cwd(),
125 | '/suites/jest/__tests__/',
126 | 'test' + i + '.js'
127 | );
128 | const uvuPath = join(process.cwd(), '/suites/uvu/test', 'test' + i + '.js');
129 | writeFileSync(zoraPath, zoraCode);
130 | writeFileSync(avaPath, avaCode);
131 | writeFileSync(mochaPath, mochaCode);
132 | writeFileSync(tapePath, tapeCode);
133 | writeFileSync(jestPath, jestCode);
134 | writeFileSync(uvuPath, uvuCode);
135 | writeFileSync(join(process.cwd(), '/suites/zora/index.js'), zoraIndex);
136 | }
137 |
--------------------------------------------------------------------------------
/assert/readme.md:
--------------------------------------------------------------------------------
1 | # zora-assert
2 |
3 | Assertion library used by zora projects
4 |
5 | This library does not throw exception but returns [assertion results](#assertion-result) instead
6 |
7 | Note: this package is not published
8 |
9 | ## assertion API
10 |
11 | ```typescript
12 | export interface ComparatorAssertionFunction {
13 | (actual: unknown, expected: T, description?: string): AssertionResult;
14 | }
15 |
16 | export interface BooleanAssertionFunction {
17 | (actual: unknown, description?: string): AssertionResult;
18 | }
19 |
20 | export interface ErrorAssertionFunction {
21 | (
22 | fn: Function,
23 | expected: RegExp | Function,
24 | description?: string
25 | ): IAssertionResult;
26 | (fn: Function, description?: string): IAssertionResult;
27 | }
28 |
29 | export interface MessageAssertionFunction {
30 | (message?: string): AssertionResult;
31 | }
32 |
33 | interface Assert {
34 | equal: ComparatorAssertionFunction;
35 |
36 | // alias
37 | equals: ComparatorAssertionFunction;
38 |
39 | // alias
40 | eq: ComparatorAssertionFunction;
41 |
42 | // alias
43 | same: ComparatorAssertionFunction;
44 |
45 | // alias
46 | deepEqual: ComparatorAssertionFunction;
47 |
48 | notEqual: ComparatorAssertionFunction;
49 |
50 | // alias
51 | notEquals: ComparatorAssertionFunction;
52 |
53 | // alias
54 | notEq: ComparatorAssertionFunction;
55 |
56 | // alias
57 | notDeepEqual: ComparatorAssertionFunction;
58 |
59 | // alias
60 | notSame: ComparatorAssertionFunction;
61 |
62 | is: ComparatorAssertionFunction;
63 |
64 | isNot: ComparatorAssertionFunction;
65 |
66 | ok: BooleanAssertionFunction;
67 |
68 | // alias
69 | truthy: BooleanAssertionFunction;
70 |
71 | notOk: BooleanAssertionFunction;
72 |
73 | // alias
74 | falsy: BooleanAssertionFunction;
75 |
76 | fail: MessageAssertionFunction;
77 |
78 | throws: ErrorAssertionFunction;
79 | }
80 | ```
81 |
82 | ## assertion result
83 |
84 | ```typescript
85 | interface AssertionResult {
86 | pass: boolean;
87 | actual: unknown;
88 | expected: T;
89 | description: string;
90 | operator: Operator;
91 | at?: string;
92 | }
93 | ```
94 |
95 | the ``at`` property is only there if the result has ``pass`` property set at ``false``
96 |
97 | ## onResult
98 |
99 | You can pass a ``onResult`` callback function which the instance will call every time it produces a new assertion
100 | result
101 |
102 | ```Javascript
103 | import createAssert from 'zora-assert';
104 |
105 | const assertions = [];
106 | const onResult = (item) => assertions.push(item);
107 |
108 | const assert = createAssert({onResult});
109 |
110 | assert.ok('truthy');
111 | assert.eq('foo', 'bar', 'some message');
112 |
113 | /*
114 | assertions >
115 | [
116 | {
117 | pass: true,
118 | actual: 'truthy',
119 | expected: 'truthy value',
120 | description: 'should be truthy',
121 | operator: 'ok'
122 | },
123 | {
124 | pass: false,
125 | actual: 'foo',
126 | expected: 'bar',
127 | description: 'some message',
128 | operator: 'equal',
129 | at: 'relevant/stacktrace/line'
130 | }
131 | ]
132 | */
133 | ```
134 |
135 | ## Assert prototype
136 |
137 | The assert methods are bound to the instance, and you can destructure the instance to use the functions as if they were
138 | regular functions
139 |
140 | ```javascript
141 | import createAssert from 'zora-assert';
142 |
143 | // destructure
144 | const {ok, eq} = createAssert({collect});
145 |
146 | ok('truthy');
147 | eq('foo', 'bar', 'some message');
148 | ```
149 |
150 | However, behind the scene, we use object delegation through the prototype chain so that you can enhance the Assert API
151 | globally if you wish to.
152 |
153 | ```javascript
154 | import createAssert, {Assert} from 'zora-assert';
155 |
156 | // upgrade API
157 | Assert.isFoo = (expected, description = 'should be foo') => {
158 | return {
159 | pass: expected === 'foo',
160 | expected,
161 | actual: 'foo',
162 | operator: 'isFoo',
163 | description
164 | };
165 | };
166 |
167 | // then use regularly
168 | const {isFoo} = createAssert();
169 |
170 | isFoo('bar') // failure
171 | isFoo('foo') // success
172 | ```
173 |
--------------------------------------------------------------------------------
/pta/test/samples/index.js:
--------------------------------------------------------------------------------
1 | import * as process from 'node:process';
2 | import { execFile } from 'node:child_process';
3 | import { promisify } from 'node:util';
4 | import { readFileSync } from 'node:fs';
5 | import { relative, resolve } from 'node:path';
6 |
7 | import { test } from 'zora';
8 |
9 | const exec = promisify(execFile);
10 | const node = process.execPath;
11 |
12 | const run = ({ args, cwd = process.cwd() }) => {
13 | const fullCWD = resolve(process.cwd(), cwd);
14 | const bin = relative(cwd, resolve(process.cwd(), 'src/bin.js'));
15 | return exec(node, [bin, ...args], {
16 | cwd: fullCWD,
17 | });
18 | };
19 |
20 | const loadFileContent = (path) =>
21 | readFileSync(resolve(process.cwd(), path)).toString();
22 |
23 | test(`only mode should only run test with only flag`, async (t) => {
24 | try {
25 | const { stderr, stdout } = await run({
26 | args: ['*.js', '--only'],
27 | cwd: './test/samples/only/',
28 | });
29 | t.ok(stdout);
30 | t.notOk(stderr);
31 | } catch (e) {
32 | t.fail(`should not have any error`);
33 | }
34 | });
35 |
36 | test(`no only mode should throw when testing program uses "only"`, async (t) => {
37 | try {
38 | await run({
39 | args: ['*.js'],
40 | cwd: 'test/samples/only/',
41 | });
42 | t.fail('should not pass');
43 | } catch (e) {
44 | t.eq(e.code, 1, 'exit code should be 1');
45 | t.ok(
46 | e.stderr.includes(
47 | `Error: Can not use "only" method when not in "run only" mode`
48 | )
49 | );
50 | }
51 | });
52 |
53 | test(`errored test suite should exit the process with code 1`, async (t) => {
54 | try {
55 | await run({
56 | args: ['*.js'],
57 | cwd: 'test/samples/errored/',
58 | });
59 | t.fail('should not pass');
60 | } catch (e) {
61 | t.eq(e.code, 1, 'exit code should be 1');
62 | t.ok(e.stderr.includes(`Error: some error`));
63 | }
64 | });
65 |
66 | test(`failing test suite should exit the process with code 1`, async (t) => {
67 | try {
68 | await run({
69 | args: ['*.js'],
70 | cwd: 'test/samples/failing',
71 | });
72 | t.fail('should not pass');
73 | } catch (e) {
74 | t.eq(e.code, 1, 'exit code should be 1');
75 | }
76 | });
77 |
78 | test('--help should output the help content', async (t) => {
79 | try {
80 | const { stderr, stdout } = await run({ args: ['--help'] });
81 | t.eq(stdout, loadFileContent('./src/usage.txt').toString());
82 | t.notOk(stderr);
83 | } catch (e) {
84 | t.fail(`should not have any error`);
85 | }
86 | });
87 |
88 | test('use tap reporter', async (t) => {
89 | try {
90 | const { stderr, stdout } = await run({
91 | args: ['*.js', '-R', 'tap'],
92 | cwd: 'test/samples/dummy',
93 | });
94 | t.eq(stdout, loadFileContent('test/samples/dummy/tap.txt'));
95 | t.notOk(stderr);
96 | } catch (e) {
97 | t.fail(`should not have any error`);
98 | }
99 | });
100 |
101 | test('use log reporter', async (t) => {
102 | try {
103 | const { stderr, stdout } = await run({
104 | args: ['*.js', '-R', 'log'],
105 | cwd: 'test/samples/dummy/',
106 | });
107 | t.eq(
108 | stdout.replace(/"executionTime":\d+/g, '"executionTime":{TIME}'),
109 | loadFileContent('test/samples/dummy/log.txt')
110 | );
111 | t.notOk(stderr);
112 | } catch (e) {
113 | t.fail(`should not have any error`);
114 | }
115 | });
116 |
117 | test(`should load cjs files`, async (t) => {
118 | try {
119 | const { stderr, stdout } = await run({
120 | args: ['test/*.{js,cjs}'],
121 | cwd: './test/samples/cjs',
122 | });
123 | t.ok(stdout);
124 | t.notOk(stderr);
125 | } catch (e) {
126 | console.log(e);
127 | t.fail(`should not have any error`);
128 | }
129 | });
130 |
131 | test(`should run test suite with es modules and import syntax`, async (t) => {
132 | try {
133 | const { stderr, stdout } = await run({
134 | args: ['test/*.{js,mjs}'],
135 | cwd: './test/samples/es',
136 | });
137 | t.ok(stdout);
138 | t.notOk(stderr);
139 | } catch (e) {
140 | console.log(e);
141 | t.fail(`should not have any error`);
142 | }
143 | });
144 |
145 | test('should run test serially when there are top level await', async (t) => {
146 | try {
147 | const { stderr, stdout } = await run({
148 | args: ['*.spec.js'],
149 | cwd: './test/samples/serial',
150 | });
151 | t.ok(stdout);
152 | t.notOk(stderr);
153 | } catch (e) {
154 | console.log(e);
155 | t.fail(`should not have any error`);
156 | }
157 | });
158 |
--------------------------------------------------------------------------------
/reporters/src/diff/diagnostic/equal.js:
--------------------------------------------------------------------------------
1 | import { diffChars, diffJson } from 'diff';
2 | import { leftPad, typeAsString } from '../utils.js';
3 | import { EOL } from 'os';
4 | import { compose } from '../../utils.js';
5 |
6 | const actualParts = ({ added }) => added !== true;
7 | const expectedParts = ({ removed }) => removed !== true;
8 |
9 | const mapActualParts =
10 | (theme) =>
11 | ({ value, removed }) =>
12 | removed ? theme.diffActual(value) : value;
13 |
14 | const mapExpectedParts =
15 | (theme) =>
16 | ({ value, added }) =>
17 | added ? theme.diffExpected(value) : value;
18 |
19 | export const getDiffCharThemedMessage =
20 | (theme) =>
21 | ({ actual, expected }) => {
22 | const diffs = diffChars(actual, expected);
23 | return {
24 | actual: diffs.filter(actualParts).map(mapActualParts(theme)).join(''),
25 | expected: diffs
26 | .filter(expectedParts)
27 | .map(mapExpectedParts(theme))
28 | .join(''),
29 | };
30 | };
31 |
32 | const diffStrings = (theme) => {
33 | const diffChars = getDiffCharThemedMessage(theme);
34 | return ({ expected, actual }) => {
35 | const { expected: expectedMessage, actual: actualMessage } = diffChars({
36 | expected,
37 | actual,
38 | });
39 |
40 | return `diff in strings:
41 | ${theme.errorBadge('- actual')} ${theme.successBadge('+ expected')}
42 |
43 | ${theme.errorBadge('-')} ${actualMessage}
44 | ${theme.successBadge('+')} ${expectedMessage}`;
45 | };
46 | };
47 |
48 | const diffNumbers =
49 | (theme) =>
50 | ({ expected, actual }) =>
51 | `expected number to be ${theme.successBadge(
52 | expected
53 | )} but got ${theme.errorBadge(actual)}`;
54 |
55 | const diffBigInts =
56 | (theme) =>
57 | ({ expected, actual }) =>
58 | `expected bigint to be ${theme.successBadge(
59 | expected
60 | )} but got ${theme.errorBadge(actual)}`;
61 |
62 | const diffDates = (theme) => {
63 | const diffChars = getDiffCharThemedMessage(theme);
64 | return ({ expected, actual }) => {
65 | const { expected: expectedMessage, actual: actualMessage } = diffChars({
66 | expected: expected.toISOString(),
67 | actual: actual.toISOString(),
68 | });
69 |
70 | return `diff in dates:
71 | ${theme.errorBadge('- actual')} ${theme.successBadge('+ expected')}
72 |
73 | ${theme.errorBadge('-')} ${actualMessage}
74 | ${theme.successBadge('+')} ${expectedMessage}`;
75 | };
76 | };
77 |
78 | const diffBooleans =
79 | (theme) =>
80 | ({ expected, actual }) =>
81 | `expected boolean to be ${theme.emphasis(
82 | expected
83 | )} but got ${theme.emphasis(actual)}`;
84 |
85 | export const expandNewLines = (lines) =>
86 | lines.flatMap((line) => {
87 | const { value, ...rest } = line;
88 | return value
89 | .split(EOL)
90 | .filter(Boolean)
91 | .map((newValue) => ({
92 | ...rest,
93 | value: newValue,
94 | }));
95 | });
96 |
97 | export const diffLine = (theme) => (diff) => {
98 | if (diff.added) {
99 | return `${theme.successBadge('+')} ${diff.value}`;
100 | }
101 |
102 | if (diff.removed) {
103 | return `${theme.errorBadge('-')} ${diff.value}`;
104 | }
105 |
106 | return leftPad(3, theme.disable(diff.value));
107 | };
108 |
109 | const getDiffJSONThemedMessage = (theme) => {
110 | const getLineDiff = diffLine(theme);
111 | return ({ actual, expected }) => {
112 | const diff = diffJson(actual, expected);
113 | return expandNewLines(diff).map(getLineDiff).map(leftPad(2)).join(EOL);
114 | };
115 | };
116 |
117 | const diffObjects = (theme) => {
118 | const diffJSON = getDiffJSONThemedMessage(theme);
119 | return ({ expected, actual }) => `diff in objects:
120 | ${theme.errorBadge('- actual')} ${theme.successBadge('+ expected')}
121 |
122 | ${diffJSON({ actual, expected })}`;
123 | };
124 |
125 | const getDifferentTypeMessage = (theme) => {
126 | const printType = compose([theme.emphasis, typeAsString]);
127 |
128 | return ({ actual, expected }) =>
129 | `expected a ${printType(expected)} but got a ${printType(actual)}`;
130 | };
131 |
132 | export default (theme) => {
133 | const differentTypes = getDifferentTypeMessage(theme);
134 |
135 | const sameTypeDiff = {
136 | ['number']: diffNumbers(theme),
137 | ['bigint']: diffBigInts(theme),
138 | ['string']: diffStrings(theme),
139 | ['boolean']: diffBooleans(theme),
140 | ['object']: ({ expected, actual }) => {
141 | if (expected.constructor === Date) {
142 | return diffDates(theme)({ expected, actual });
143 | }
144 | return diffObjects(theme)({ actual, expected });
145 | },
146 | };
147 |
148 | return (diag) => {
149 | const { actual, expected } = diag;
150 | const expectedType = typeof expected;
151 |
152 | if (typeof actual !== expectedType) {
153 | return differentTypes({ actual, expected });
154 | }
155 |
156 | return (
157 | sameTypeDiff[expectedType]?.(diag) ??
158 | `unsupported type ${theme.emphasis(expectedType)}`
159 | );
160 | };
161 | };
162 |
--------------------------------------------------------------------------------
/reporters/src/diff/writer/diagnostic.test.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import { Operator } from '../../../../assert/src/utils.js';
3 | import getDiagnosticMessage from './diagnostic.js';
4 | import getEqualDiagnosticMessage from '../diagnostic/equal.js';
5 |
6 | const theme = new Proxy(
7 | {},
8 | {
9 | get: function (target, prop, receiver) {
10 | return (val) => `<${prop}>${val}${prop}>`;
11 | },
12 | }
13 | );
14 |
15 | const getMessage = getDiagnosticMessage({ theme });
16 |
17 | test(`diagnostic messages`, (t) => {
18 | t.test(`equal diagnostic message`, (t) => {
19 | const getMessage = getEqualDiagnosticMessage(theme);
20 |
21 | t.test(`expected and actual have different types`, (t) => {
22 | t.eq(
23 | getMessage({
24 | actual: undefined,
25 | expected: { some: 'value' },
26 | operator: Operator.EQUAL,
27 | }),
28 | `expected a Object but got a undefined`
29 | );
30 | });
31 |
32 | t.test(`expected and actual are strings`, (t) => {
33 | t.eq(
34 | getMessage({
35 | actual: 'fob',
36 | expected: 'foo',
37 | operator: Operator.EQUAL,
38 | }),
39 | `diff in strings:
40 | - actual + expected
41 |
42 | - fob
43 | + foo`
44 | );
45 | });
46 |
47 | t.test(`expected and actual are booleans`, (t) => {
48 | t.eq(
49 | getMessage({ actual: true, expected: false }),
50 | `expected boolean to be false but got true`
51 | );
52 | });
53 |
54 | t.test(`expected and actual are Dates`, (t) => {
55 | t.eq(
56 | getMessage({
57 | actual: new Date(Date.UTC(2021, 4, 1)),
58 | expected: new Date(Date.UTC(2021, 5, 1)),
59 | }),
60 | `diff in dates:
61 | - actual + expected
62 |
63 | - 2021-05-01T00:00:00.000Z
64 | + 2021-06-01T00:00:00.000Z`
65 | );
66 | });
67 |
68 | t.test(`expected and actual are objects`, (t) => {
69 | const expected = {
70 | foo: 'bar',
71 | nested: {
72 | answer: 42,
73 | },
74 | };
75 | const actual = {
76 | foo: 'baz',
77 | nested: {
78 | answer: 43,
79 | },
80 | };
81 | t.eq(
82 | getMessage({ expected, actual }),
83 | `diff in objects:
84 | - actual + expected
85 |
86 | {
87 | - "foo": "baz",
88 | + "foo": "bar",
89 | "nested": {
90 | - "answer": 43
91 | + "answer": 42
92 | }
93 | }`
94 | );
95 | });
96 |
97 | t.test(`expected and actual are numbers`, (t) => {
98 | const expected = 5;
99 | const actual = 3;
100 | t.eq(
101 | getMessage({ expected, actual }),
102 | `expected number to be 5 but got 3`
103 | );
104 | });
105 |
106 | t.test(`expected and actual are BigInt's`, (t) => {
107 | const expected = 5n;
108 | const actual = 3n;
109 | t.eq(
110 | getMessage({ expected, actual }),
111 | `expected bigint to be 5 but got 3`
112 | );
113 | });
114 | });
115 |
116 | t.test(`ok diagnostic message`, (t) => {
117 | t.eq(
118 | getMessage({ actual: null, operator: Operator.OK }),
119 | `expected "truthy" but got null`
120 | );
121 |
122 | t.eq(
123 | getMessage({ actual: '', operator: Operator.OK }),
124 | `expected "truthy" but got ""`,
125 | 'display double quotes when actual is an empty string'
126 | );
127 | });
128 |
129 | t.test(`notOk diagnostic message`, (t) => {
130 | t.eq(
131 | getMessage({ actual: 'foo', operator: Operator.NOT_OK }),
132 | `expected "falsy" but got "foo"`
133 | );
134 |
135 | t.eq(
136 | getMessage({ actual: {}, operator: Operator.NOT_OK }),
137 | `expected "falsy" but got {}`
138 | );
139 |
140 | t.eq(
141 | getMessage({ actual: [], operator: Operator.NOT_OK }),
142 | `expected "falsy" but got []`
143 | );
144 | });
145 |
146 | t.test(`fail diagnostic message`, (t) => {
147 | t.eq(
148 | getMessage({
149 | description: 'should not get here',
150 | operator: Operator.FAIL,
151 | }),
152 | `expected fail not to be called, but was called as "should not get here"`
153 | );
154 | });
155 |
156 | t.test(`notEqual diagnostic message`, (t) => {
157 | t.eq(
158 | getMessage({ operator: Operator.NOT_EQUAL }),
159 | `expected the arguments not to be equivalent but they were`
160 | );
161 | });
162 |
163 | t.test(`is diagnostic message`, (t) => {
164 | t.eq(
165 | getMessage({ operator: Operator.IS }),
166 | `expected references to be the same but they were not`
167 | );
168 | });
169 |
170 | t.test(`isNot diagnostic message`, (t) => {
171 | t.eq(
172 | getMessage({ operator: Operator.IS_NOT }),
173 | `expected references not to be the same but they were`
174 | );
175 | });
176 |
177 | t.test(`unknown operator diagnostic message`, (t) => {
178 | t.eq(
179 | getMessage({ operator: 'wooty' }),
180 | `unknown operator wooty`
181 | );
182 | });
183 |
184 | t.test(`throws diagnostic message`, (t) => {
185 | t.test(`did not throw`, (t) => {
186 | t.eq(
187 | getMessage({ operator: Operator.THROWS }),
188 | `expected to throw but it did not`
189 | );
190 | });
191 |
192 | t.test('throws not matching error', (t) => {
193 | t.eq(
194 | getMessage({
195 | operator: Operator.THROWS,
196 | actual: 'foo',
197 | expected: /bar/,
198 | }),
199 | `expected the error thrown to match /bar/ but it matched foo`
200 | );
201 | });
202 | });
203 |
204 | t.test(`equal diagnostic message`, (t) => {
205 | const getMessage = getEqualDiagnosticMessage(theme);
206 | t.test(`expected and actual have different types`, (t) => {
207 | t.eq(
208 | getMessage({
209 | actual: undefined,
210 | expected: { some: 'value' },
211 | operator: Operator.EQUAL,
212 | }),
213 | `expected a Object but got a undefined`
214 | );
215 | });
216 | });
217 |
218 | t.test(`timeout diagnostic`, (t) => {
219 | t.eq(
220 | getMessage({ operator: 'timeout', actual: 'timeout description' }),
221 | `timeout description`
222 | );
223 | });
224 | });
225 |
--------------------------------------------------------------------------------
/zora/readme.md:
--------------------------------------------------------------------------------
1 | # Zora
2 |
3 | [](https://www.npmjs.com/package/zora)
4 | [](https://packagephobia.now.sh/result?p=zora)
5 |
6 | ## Usage
7 |
8 | The library is a _regular_ Javascript module and can be directly imported from a CDN:
9 |
10 | ```Javascript
11 | import {test} from 'https://unpkg.com/zora@latest/dist/index.js'
12 |
13 | test(`hello from zora`, ({ok}) => {
14 | ok(true, 'it worked');
15 | })
16 | ```
17 |
18 | (Like in the [following codepen](https://codepen.io/lorenzofox3/pen/LYWOaYV?editors=1111))
19 |
20 | Or installed via a package manager such [NPM](https://www.npmjs.com/) by running the command (assuming you have [Nodejs](https://nodejs.org/en/) installed on your machine):
21 |
22 | ``npm i -D zora``
23 |
24 | You can then build your testing program by using the exported ``test`` function
25 |
26 | ```Javascript
27 | import {test} from 'zora';
28 |
29 | test(`my very first test`, (assertion) => {
30 | const input = false;
31 | assertion.ok(input, 'input should be truthy');
32 | })
33 |
34 | ```
35 |
36 | You can run the testing program (with node's runtime for example) and it will start reporting its execution into the console
37 |
38 |
39 | test-output.tap
40 |
41 | ```TAP
42 | TAP version 13
43 | # my very first test
44 | not ok 1 - input should be truthy
45 | ---
46 | actual: false
47 | expected: "truthy value"
48 | operator: "ok"
49 | at: " file:///path/to/sample.js:5:13"
50 | ...
51 |
52 | 1..1
53 | # tests 1
54 | # pass 0
55 | # fail 1
56 | # skip 0
57 |
58 | ```
59 |
60 |
61 |
62 | ## Reporters
63 |
64 | This output format is called [TAP](https://testanything.org/tap-version-13-specification.html) (Test Anything Protocol). It is a standard text based format a machine can easily parse. It means there are [plenty of reporters](https://www.npmjs.com/search?q=tap%20reporter) you can pipe the standard output stream into (not only in the Javascript world). This will help you to tailor the reporting for your particular needs.
65 |
66 | for example, you can use [tap-diff](https://www.npmjs.com/package/tap-diff):
67 | ``node path/to/testing/program.js | tap-diff``
68 |
69 | You can even use basic bash command:
70 |
71 | ``node path/to/testing/program.js | grep '^not ok\|^\s'`` will output a basic, straight to the point test report:
72 |
73 | ```
74 | not ok 1 - input should be truthy
75 | ---
76 | actual: false
77 | expected: "truthy value"
78 | operator: "ok"
79 | at: " file:///path/to/sample.js:5:13"
80 | ...
81 | ```
82 |
83 | That is the beauty of using different processes to run the testing program and to format its output: it remains very flexible.
84 |
85 | ## Assertion API
86 |
87 | When you start a test suite with the ``test`` function. The spec functions you pass as argument will get an instance of the [Assertion object](../assert) so you can write a wide range of different expectations.
88 |
89 | For the best performances, all the spec functions run concurrently unless you specifically wait for them within an asynchronous function (if for some reason, you want to run some test one after the other, in a serial mode).
90 |
91 |
92 | control-flow.js
93 |
94 | ```Javascript
95 | import {test} from 'zora';
96 |
97 | let state = 0;
98 |
99 | test('test 1', t => {
100 | t.ok(true);
101 | state++;
102 | });
103 |
104 | test('test 2', t => {
105 | //Maybe yes maybe no, you have no guarantee ! In this case it will work as everything is sync
106 | t.equal(state, 1);
107 | });
108 |
109 | //Same thing here even in nested tests
110 | test('grouped', t => {
111 | let state = 0;
112 |
113 | t.test('test 1', t => {
114 | t.ok(true);
115 | state++;
116 | });
117 |
118 | t.test('test 2', t => {
119 | //Maybe yes maybe no, you have no guarantee ! In this case it will work as everything is sync
120 | t.equal(state, 1);
121 | });
122 | });
123 |
124 | //And
125 | test('grouped', t=>{
126 | let state = 0;
127 |
128 | t.test('test 1', async t=>{
129 | t.ok(true);
130 | await wait(100);
131 | state++;
132 | });
133 |
134 | test('test 2', t=>{
135 | t.equal(state, 0, 'see the old state value as it will have started to run before test 1 is done');
136 | });
137 | });
138 |
139 | //But
140 | test('grouped', async t => {
141 | let state = 0;
142 |
143 | //specifically WAIT for the end of this test before moving on !
144 | await t.test('test 1', async t => {
145 | t.ok(true);
146 | await wait(100);
147 | state++;
148 | });
149 |
150 | test('test 2', t => {
151 | t.equal(state, 1, 'see the updated value!');
152 | });
153 | });
154 | ```
155 |
156 |
157 |
158 |
159 | ## Environment variables
160 |
161 | You can _configure_ the testing program with environment variables. With nodejs, simply pass it with the command line:
162 |
163 | ``ZORA_REPORTER=json node path/to/testing/program.js``
164 |
165 | In the browser, you have to set it as a global before the testing program runs:
166 |
167 | ```HTML
168 |
171 |
178 | ```
179 |
180 | ### ZORA_REPORTER=json
181 |
182 | By default, the output is a TAP stream; but you can decide to produce a stream defined by [the zora json protocol messages](../reporters) if you which to build a custom reporter on top of it.
183 |
184 | ### ZORA_ONLY=true
185 |
186 | Beside the ``test`` function you can use the ``only`` function if you wish to skip all the other tests. However, from the testing program perspective, ``only`` is meaningless: it is just a convenience for a developer, locally on its machine.
187 | So if you wish to use ``only`` you need to pass the ``ZORA_ONLY`` environment variable, otherwise the program will throw an exception to prevent ``only`` statement to slip in the remote versioning control system.
188 |
189 | Beware that if you want to run only a nested test suite, all the parent test suites must use the ``only statement`:`
190 |
191 |
192 | test-with-only.js
193 |
194 | ```Javascript
195 | import {only, test} from 'zora';
196 |
197 | test('will be skipped', t => {
198 | t.ok(false);
199 | })
200 |
201 | only('some test', t => {
202 |
203 | // will be skipped as well
204 | t.test('some nested test', t => {
205 | t.ok(false);
206 | });
207 |
208 | // will run
209 | t.only('some other nested test', t => {
210 | t.ok(true);
211 | });
212 | });
213 | ```
214 |
215 |
216 |
217 | ### ZORA_TIMEOUT
218 |
219 | In milliseconds: change the default test timeout value (see below)
220 |
221 | ## test timeout
222 |
223 | If a test hangs or takes too long to complete, it will report a failure. By default, the threshold is 5000ms. You can change that global value thanks to the ``ZORA_TIMEOUT`` environment variable.
224 | You can change that value for a given text as well, thanks to the options object:
225 |
226 |
227 | timeout example
228 |
229 | ```Javascript
230 | test(
231 | 'broken promise',
232 | ({ ok }) => {
233 | return new Promise(() => {}).then(() => {
234 | ok(true);
235 | });
236 | },
237 | { timeout: 500 }
238 | );
239 | ```
240 |
241 |
242 |
243 | ## skip a test
244 |
245 | You can skip a test using the root level ``skip`` function or within a test suite using the ``skip`` method of the assertion object
246 |
247 |
248 | test-with-skip.js
249 |
250 | ```Javascript
251 | import {skip, test} from 'zora';
252 |
253 | skip('will be skipped', t => {
254 | t.ok(false);
255 | })
256 |
257 | test('some test', t => {
258 |
259 | // will be skipped as well
260 | t.skip('some nested test', t => {
261 | t.ok(false);
262 | });
263 |
264 | // will run
265 | t.test('some other nested test', t => {
266 | t.ok(true);
267 | });
268 | });
269 | ```
270 |
271 |
272 |
--------------------------------------------------------------------------------
/assert/test/assert.js:
--------------------------------------------------------------------------------
1 | import { test } from 'zora';
2 | import {
3 | equal,
4 | fail,
5 | is,
6 | isNot,
7 | notEqual,
8 | notOk,
9 | ok,
10 | throws,
11 | } from '../src/assert.js';
12 |
13 | test('"equal" operator', ({ eq }) => {
14 | eq(
15 | equal('foo', 'foo', 'a description'),
16 | {
17 | pass: true,
18 | actual: 'foo',
19 | expected: 'foo',
20 | description: 'a description',
21 | operator: 'equal',
22 | },
23 | 'should return a passing assert result when both string literals are equal'
24 | );
25 |
26 | eq(
27 | equal(4, 4, 'a description'),
28 | {
29 | pass: true,
30 | actual: 4,
31 | expected: 4,
32 | description: 'a description',
33 | operator: 'equal',
34 | },
35 | 'should return a passing assert result when both number literals are equal'
36 | );
37 |
38 | eq(
39 | equal(true, true, 'a description'),
40 | {
41 | pass: true,
42 | actual: true,
43 | expected: true,
44 | description: 'a description',
45 | operator: 'equal',
46 | },
47 | 'should return a passing assert result when both boolean literals are equal'
48 | );
49 |
50 | eq(
51 | equal({ prop: 'val' }, { prop: 'val' }, 'a description'),
52 | {
53 | pass: true,
54 | actual: { prop: 'val' },
55 | expected: { prop: 'val' },
56 | description: 'a description',
57 | operator: 'equal',
58 | },
59 | 'should return a passing assert result when two object are deeply equal'
60 | );
61 |
62 | eq(
63 | equal('foo', 'bar'),
64 | {
65 | pass: false,
66 | actual: 'foo',
67 | expected: 'bar',
68 | description: 'should be equivalent',
69 | operator: 'equal',
70 | },
71 | 'should return a failing assert result when two string literals are different'
72 | );
73 |
74 | eq(
75 | equal(4, 5, 'a description'),
76 | {
77 | pass: false,
78 | actual: 4,
79 | expected: 5,
80 | description: 'a description',
81 | operator: 'equal',
82 | },
83 | 'should return a failing assert result when two number literals are different'
84 | );
85 |
86 | eq(
87 | equal(true, false, 'a description'),
88 | {
89 | pass: false,
90 | actual: true,
91 | expected: false,
92 | description: 'a description',
93 | operator: 'equal',
94 | },
95 | 'should return a failing assert result when two boolean literals are different'
96 | );
97 |
98 | eq(
99 | equal({ prop: 'val' }, { prop: 'val' }, 'a description'),
100 | {
101 | pass: true,
102 | actual: { prop: 'val' },
103 | expected: { prop: 'val' },
104 | description: 'a description',
105 | operator: 'equal',
106 | },
107 | 'should return a passing assert result when two object are deeply equal'
108 | );
109 |
110 | eq(
111 | equal(new Map(), new Map([['a', 123]]), 'a description'),
112 | {
113 | pass: false,
114 | actual: new Map(),
115 | expected: new Map([['a', 123]]),
116 | description: 'a description',
117 | operator: 'equal',
118 | },
119 | 'should return a failing assert result when two Maps are different'
120 | );
121 |
122 | eq(
123 | equal(
124 | { map: new Map([['a', 123]]) },
125 | { map: new Map([['a', 123]]) },
126 | 'a description'
127 | ),
128 | {
129 | pass: true,
130 | actual: { map: new Map([['a', 123]]) },
131 | expected: { map: new Map([['a', 123]]) },
132 | description: 'a description',
133 | operator: 'equal',
134 | },
135 | 'should return a passing assert result when two nested Maps are the same'
136 | );
137 |
138 | eq(
139 | equal(new Set(), new Set(['a', 'b']), 'a description'),
140 | {
141 | pass: false,
142 | actual: new Set(),
143 | expected: new Set(['a', 'b']),
144 | description: 'a description',
145 | operator: 'equal',
146 | },
147 | 'should return a failing assert result when two Sets are different'
148 | );
149 |
150 | eq(
151 | equal(
152 | { set: new Set(['a', 'b']) },
153 | { set: new Set(['a', 'b']) },
154 | 'a description'
155 | ),
156 | {
157 | pass: true,
158 | actual: { set: new Set(['a', 'b']) },
159 | expected: { set: new Set(['a', 'b']) },
160 | description: 'a description',
161 | operator: 'equal',
162 | },
163 | 'should return a passing assert result when two nested Sets are the same'
164 | );
165 | });
166 |
167 | test('"not equal" operator', ({ eq }) => {
168 | eq(
169 | notEqual('foo', 'bar'),
170 | {
171 | pass: true,
172 | actual: 'foo',
173 | expected: 'bar',
174 | description: 'should not be equivalent',
175 | operator: 'notEqual',
176 | },
177 | 'should return a passing assert result'
178 | );
179 |
180 | eq(
181 | notEqual('foo', 'foo'),
182 | {
183 | pass: false,
184 | actual: 'foo',
185 | expected: 'foo',
186 | description: 'should not be equivalent',
187 | operator: 'notEqual',
188 | },
189 | 'should return a failing assert result'
190 | );
191 | });
192 |
193 | test('"is" operator', ({ eq }) => {
194 | const instance = {};
195 | eq(
196 | is({ foo: 'bar' }, { foo: 'bar' }),
197 | {
198 | pass: false,
199 | actual: { foo: 'bar' },
200 | expected: { foo: 'bar' },
201 | description: 'should be the same',
202 | operator: 'is',
203 | },
204 | 'should return a failing assert result when references are different'
205 | );
206 | eq(
207 | is(instance, instance),
208 | {
209 | pass: true,
210 | actual: {},
211 | expected: {},
212 | description: 'should be the same',
213 | operator: 'is',
214 | },
215 | 'should return a passing assert result when references are the same'
216 | );
217 | });
218 |
219 | test('"isNot" operator', ({ eq }) => {
220 | const instance = {};
221 | eq(
222 | isNot({ foo: 'bar' }, { foo: 'bar' }),
223 | {
224 | pass: true,
225 | actual: { foo: 'bar' },
226 | expected: { foo: 'bar' },
227 | description: 'should not be the same',
228 | operator: 'isNot',
229 | },
230 | 'should return a passing assert result when references are different'
231 | );
232 |
233 | eq(
234 | isNot(instance, instance),
235 | {
236 | pass: false,
237 | actual: {},
238 | expected: {},
239 | description: 'should not be the same',
240 | operator: 'isNot',
241 | },
242 | 'should return a failing assert result when references are the same'
243 | );
244 | });
245 |
246 | test('"ok" operator', ({ eq }) => {
247 | eq(
248 | ok('truthy'),
249 | {
250 | pass: true,
251 | actual: 'truthy',
252 | description: 'should be truthy',
253 | expected: 'truthy value',
254 | operator: 'ok',
255 | },
256 | 'should return a passing assert result when actual value is truthy'
257 | );
258 |
259 | eq(
260 | ok(null),
261 | {
262 | pass: false,
263 | actual: null,
264 | expected: 'truthy value',
265 | description: 'should be truthy',
266 | operator: 'ok',
267 | },
268 | 'should return a failing assert result when actual value is not truthy'
269 | );
270 |
271 | eq(
272 | ok(undefined),
273 | {
274 | pass: false,
275 | actual: undefined,
276 | expected: 'truthy value',
277 | description: 'should be truthy',
278 | operator: 'ok',
279 | },
280 | 'should return a failing assert result when actual value is not truthy'
281 | );
282 |
283 | eq(
284 | ok(''),
285 | {
286 | pass: false,
287 | actual: '',
288 | expected: 'truthy value',
289 | description: 'should be truthy',
290 | operator: 'ok',
291 | },
292 | 'should return a failing assert result when actual value is not truthy'
293 | );
294 | });
295 |
296 | test('"notOk" operator', ({ eq }) => {
297 | eq(
298 | notOk('truthy'),
299 | {
300 | pass: false,
301 | actual: 'truthy',
302 | description: 'should be falsy',
303 | expected: 'falsy value',
304 | operator: 'notOk',
305 | },
306 | 'should return a failing assert result when actual value is not falsy'
307 | );
308 |
309 | eq(
310 | notOk(null),
311 | {
312 | pass: true,
313 | actual: null,
314 | expected: 'falsy value',
315 | description: 'should be falsy',
316 | operator: 'notOk',
317 | },
318 | 'should return a passing assert result when actual value is falsy'
319 | );
320 | });
321 |
322 | test('"fail" operator', ({ eq }) => {
323 | eq(
324 | fail('this should fail'),
325 | {
326 | pass: false,
327 | actual: 'fail called',
328 | expected: 'fail not called',
329 | description: 'this should fail',
330 | operator: 'fail',
331 | },
332 | 'should always return a failing assert result'
333 | );
334 | });
335 |
336 | test('throws', ({ eq }) => {
337 | const regexp = /^totally/i;
338 |
339 | class CustomError extends Error {
340 | constructor() {
341 | super('custom error');
342 | }
343 | }
344 |
345 | eq(
346 | throws(() => {
347 | throw new Error('Totally expected error');
348 | }, regexp),
349 | {
350 | pass: true,
351 | actual: 'Totally expected error',
352 | expected: '/^totally/i',
353 | description: 'should throw',
354 | operator: 'throws',
355 | },
356 | 'expected is a regexp, passing'
357 | );
358 |
359 | eq(
360 | throws(() => {
361 | throw new Error('not the expected error');
362 | }, regexp),
363 | {
364 | pass: false,
365 | actual: 'not the expected error',
366 | expected: '/^totally/i',
367 | description: 'should throw',
368 | operator: 'throws',
369 | },
370 | 'expected is a regexp, failing'
371 | );
372 |
373 | eq(
374 | throws(() => {
375 | throw new CustomError();
376 | }, CustomError),
377 | {
378 | pass: true,
379 | actual: CustomError,
380 | expected: CustomError,
381 | description: 'should throw',
382 | operator: 'throws',
383 | },
384 | 'expected is a constructor, passing'
385 | );
386 |
387 | eq(
388 | throws(() => {
389 | throw new Error();
390 | }, CustomError),
391 | {
392 | pass: false,
393 | actual: Error,
394 | expected: CustomError,
395 | description: 'should throw',
396 | operator: 'throws',
397 | },
398 | 'expected is a constructor, failing'
399 | );
400 |
401 | eq(
402 | throws(() => {
403 | throw new Error('whatever');
404 | }, 'custom description'),
405 | {
406 | pass: true,
407 | description: 'custom description',
408 | expected: 'any error thrown',
409 | actual: 'error thrown',
410 | operator: 'throws',
411 | }
412 | );
413 |
414 | eq(
415 | throws(() => {}),
416 | {
417 | pass: false,
418 | description: 'should throw',
419 | expected: 'any error thrown',
420 | actual: 'no error thrown',
421 | operator: 'throws',
422 | }
423 | );
424 | });
425 |
--------------------------------------------------------------------------------
/examples/browser-vanilla-vite/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "browser-vanilla-vite",
3 | "version": "0.0.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "version": "0.0.0",
9 | "dependencies": {
10 | "is-even": "~1.0.0"
11 | },
12 | "devDependencies": {
13 | "@lorenzofox3/for-await": "^0.2.2",
14 | "playwright": "^1.22.2",
15 | "vite": "^2.9.18",
16 | "ws": "^8.8.0",
17 | "zora": "*"
18 | }
19 | },
20 | "node_modules/@lorenzofox3/for-await": {
21 | "version": "0.2.2",
22 | "resolved": "https://registry.npmjs.org/@lorenzofox3/for-await/-/for-await-0.2.2.tgz",
23 | "integrity": "sha512-IKIjQAyl6yu/kWTCqpc4QQwkA048pgsvvwxb8bMoqC4NBdypVBGj0wxFTL882sw8WzMSpov3g/pP6RxPhqbJHA==",
24 | "dev": true,
25 | "engines": {
26 | "node": ">=9.3.0"
27 | }
28 | },
29 | "node_modules/esbuild": {
30 | "version": "0.14.43",
31 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.43.tgz",
32 | "integrity": "sha512-Uf94+kQmy/5jsFwKWiQB4hfo/RkM9Dh7b79p8yqd1tshULdr25G2szLz631NoH3s2ujnKEKVD16RmOxvCNKRFA==",
33 | "dev": true,
34 | "hasInstallScript": true,
35 | "bin": {
36 | "esbuild": "bin/esbuild"
37 | },
38 | "engines": {
39 | "node": ">=12"
40 | },
41 | "optionalDependencies": {
42 | "esbuild-android-64": "0.14.43",
43 | "esbuild-android-arm64": "0.14.43",
44 | "esbuild-darwin-64": "0.14.43",
45 | "esbuild-darwin-arm64": "0.14.43",
46 | "esbuild-freebsd-64": "0.14.43",
47 | "esbuild-freebsd-arm64": "0.14.43",
48 | "esbuild-linux-32": "0.14.43",
49 | "esbuild-linux-64": "0.14.43",
50 | "esbuild-linux-arm": "0.14.43",
51 | "esbuild-linux-arm64": "0.14.43",
52 | "esbuild-linux-mips64le": "0.14.43",
53 | "esbuild-linux-ppc64le": "0.14.43",
54 | "esbuild-linux-riscv64": "0.14.43",
55 | "esbuild-linux-s390x": "0.14.43",
56 | "esbuild-netbsd-64": "0.14.43",
57 | "esbuild-openbsd-64": "0.14.43",
58 | "esbuild-sunos-64": "0.14.43",
59 | "esbuild-windows-32": "0.14.43",
60 | "esbuild-windows-64": "0.14.43",
61 | "esbuild-windows-arm64": "0.14.43"
62 | }
63 | },
64 | "node_modules/esbuild-darwin-64": {
65 | "version": "0.14.43",
66 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.43.tgz",
67 | "integrity": "sha512-/3PSilx011ttoieRGkSZ0XV8zjBf2C9enV4ScMMbCT4dpx0mFhMOpFnCHkOK0pWGB8LklykFyHrWk2z6DENVUg==",
68 | "cpu": [
69 | "x64"
70 | ],
71 | "dev": true,
72 | "optional": true,
73 | "os": [
74 | "darwin"
75 | ],
76 | "engines": {
77 | "node": ">=12"
78 | }
79 | },
80 | "node_modules/fsevents": {
81 | "version": "2.3.2",
82 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
83 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
84 | "dev": true,
85 | "hasInstallScript": true,
86 | "optional": true,
87 | "os": [
88 | "darwin"
89 | ],
90 | "engines": {
91 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
92 | }
93 | },
94 | "node_modules/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 | "node_modules/has": {
101 | "version": "1.0.3",
102 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
103 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
104 | "dev": true,
105 | "dependencies": {
106 | "function-bind": "^1.1.1"
107 | },
108 | "engines": {
109 | "node": ">= 0.4.0"
110 | }
111 | },
112 | "node_modules/is-buffer": {
113 | "version": "1.1.6",
114 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
115 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
116 | },
117 | "node_modules/is-core-module": {
118 | "version": "2.9.0",
119 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
120 | "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
121 | "dev": true,
122 | "dependencies": {
123 | "has": "^1.0.3"
124 | },
125 | "funding": {
126 | "url": "https://github.com/sponsors/ljharb"
127 | }
128 | },
129 | "node_modules/is-even": {
130 | "version": "1.0.0",
131 | "resolved": "https://registry.npmjs.org/is-even/-/is-even-1.0.0.tgz",
132 | "integrity": "sha512-LEhnkAdJqic4Dbqn58A0y52IXoHWlsueqQkKfMfdEnIYG8A1sm/GHidKkS6yvXlMoRrkM34csHnXQtOqcb+Jzg==",
133 | "dependencies": {
134 | "is-odd": "^0.1.2"
135 | },
136 | "engines": {
137 | "node": ">=0.10.0"
138 | }
139 | },
140 | "node_modules/is-number": {
141 | "version": "3.0.0",
142 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
143 | "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==",
144 | "dependencies": {
145 | "kind-of": "^3.0.2"
146 | },
147 | "engines": {
148 | "node": ">=0.10.0"
149 | }
150 | },
151 | "node_modules/is-odd": {
152 | "version": "0.1.2",
153 | "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-0.1.2.tgz",
154 | "integrity": "sha512-Ri7C2K7o5IrUU9UEI8losXJCCD/UtsaIrkR5sxIcFg4xQ9cRJXlWA5DQvTE0yDc0krvSNLsRGXN11UPS6KyfBw==",
155 | "dependencies": {
156 | "is-number": "^3.0.0"
157 | },
158 | "engines": {
159 | "node": ">=0.10.0"
160 | }
161 | },
162 | "node_modules/kind-of": {
163 | "version": "3.2.2",
164 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
165 | "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
166 | "dependencies": {
167 | "is-buffer": "^1.1.5"
168 | },
169 | "engines": {
170 | "node": ">=0.10.0"
171 | }
172 | },
173 | "node_modules/nanoid": {
174 | "version": "3.3.7",
175 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
176 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
177 | "dev": true,
178 | "funding": [
179 | {
180 | "type": "github",
181 | "url": "https://github.com/sponsors/ai"
182 | }
183 | ],
184 | "bin": {
185 | "nanoid": "bin/nanoid.cjs"
186 | },
187 | "engines": {
188 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
189 | }
190 | },
191 | "node_modules/path-parse": {
192 | "version": "1.0.7",
193 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
194 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
195 | "dev": true
196 | },
197 | "node_modules/picocolors": {
198 | "version": "1.0.0",
199 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
200 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
201 | "dev": true
202 | },
203 | "node_modules/playwright": {
204 | "version": "1.22.2",
205 | "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.22.2.tgz",
206 | "integrity": "sha512-hUTpg7LytIl3/O4t0AQJS1V6hWsaSY5uZ7w1oCC8r3a1AQN5d6otIdCkiB3cbzgQkcMaRxisinjMFMVqZkybdQ==",
207 | "dev": true,
208 | "hasInstallScript": true,
209 | "dependencies": {
210 | "playwright-core": "1.22.2"
211 | },
212 | "bin": {
213 | "playwright": "cli.js"
214 | },
215 | "engines": {
216 | "node": ">=14"
217 | }
218 | },
219 | "node_modules/playwright-core": {
220 | "version": "1.22.2",
221 | "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.22.2.tgz",
222 | "integrity": "sha512-w/hc/Ld0RM4pmsNeE6aL/fPNWw8BWit2tg+TfqJ3+p59c6s3B6C8mXvXrIPmfQEobkcFDc+4KirNzOQ+uBSP1Q==",
223 | "dev": true,
224 | "bin": {
225 | "playwright": "cli.js"
226 | },
227 | "engines": {
228 | "node": ">=14"
229 | }
230 | },
231 | "node_modules/postcss": {
232 | "version": "8.4.32",
233 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
234 | "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
235 | "dev": true,
236 | "funding": [
237 | {
238 | "type": "opencollective",
239 | "url": "https://opencollective.com/postcss/"
240 | },
241 | {
242 | "type": "tidelift",
243 | "url": "https://tidelift.com/funding/github/npm/postcss"
244 | },
245 | {
246 | "type": "github",
247 | "url": "https://github.com/sponsors/ai"
248 | }
249 | ],
250 | "dependencies": {
251 | "nanoid": "^3.3.7",
252 | "picocolors": "^1.0.0",
253 | "source-map-js": "^1.0.2"
254 | },
255 | "engines": {
256 | "node": "^10 || ^12 || >=14"
257 | }
258 | },
259 | "node_modules/resolve": {
260 | "version": "1.22.0",
261 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
262 | "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
263 | "dev": true,
264 | "dependencies": {
265 | "is-core-module": "^2.8.1",
266 | "path-parse": "^1.0.7",
267 | "supports-preserve-symlinks-flag": "^1.0.0"
268 | },
269 | "bin": {
270 | "resolve": "bin/resolve"
271 | },
272 | "funding": {
273 | "url": "https://github.com/sponsors/ljharb"
274 | }
275 | },
276 | "node_modules/rollup": {
277 | "version": "2.75.6",
278 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.6.tgz",
279 | "integrity": "sha512-OEf0TgpC9vU6WGROJIk1JA3LR5vk/yvqlzxqdrE2CzzXnqKXNzbAwlWUXis8RS3ZPe7LAq+YUxsRa0l3r27MLA==",
280 | "dev": true,
281 | "bin": {
282 | "rollup": "dist/bin/rollup"
283 | },
284 | "engines": {
285 | "node": ">=10.0.0"
286 | },
287 | "optionalDependencies": {
288 | "fsevents": "~2.3.2"
289 | }
290 | },
291 | "node_modules/source-map-js": {
292 | "version": "1.0.2",
293 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
294 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
295 | "dev": true,
296 | "engines": {
297 | "node": ">=0.10.0"
298 | }
299 | },
300 | "node_modules/supports-preserve-symlinks-flag": {
301 | "version": "1.0.0",
302 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
303 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
304 | "dev": true,
305 | "engines": {
306 | "node": ">= 0.4"
307 | },
308 | "funding": {
309 | "url": "https://github.com/sponsors/ljharb"
310 | }
311 | },
312 | "node_modules/vite": {
313 | "version": "2.9.18",
314 | "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.18.tgz",
315 | "integrity": "sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ==",
316 | "dev": true,
317 | "dependencies": {
318 | "esbuild": "^0.14.27",
319 | "postcss": "^8.4.13",
320 | "resolve": "^1.22.0",
321 | "rollup": ">=2.59.0 <2.78.0"
322 | },
323 | "bin": {
324 | "vite": "bin/vite.js"
325 | },
326 | "engines": {
327 | "node": ">=12.2.0"
328 | },
329 | "optionalDependencies": {
330 | "fsevents": "~2.3.2"
331 | },
332 | "peerDependencies": {
333 | "less": "*",
334 | "sass": "*",
335 | "stylus": "*"
336 | },
337 | "peerDependenciesMeta": {
338 | "less": {
339 | "optional": true
340 | },
341 | "sass": {
342 | "optional": true
343 | },
344 | "stylus": {
345 | "optional": true
346 | }
347 | }
348 | },
349 | "node_modules/ws": {
350 | "version": "8.8.0",
351 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.0.tgz",
352 | "integrity": "sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==",
353 | "dev": true,
354 | "engines": {
355 | "node": ">=10.0.0"
356 | },
357 | "peerDependencies": {
358 | "bufferutil": "^4.0.1",
359 | "utf-8-validate": "^5.0.2"
360 | },
361 | "peerDependenciesMeta": {
362 | "bufferutil": {
363 | "optional": true
364 | },
365 | "utf-8-validate": {
366 | "optional": true
367 | }
368 | }
369 | },
370 | "node_modules/zora": {
371 | "version": "5.0.3",
372 | "resolved": "https://registry.npmjs.org/zora/-/zora-5.0.3.tgz",
373 | "integrity": "sha512-3DAIXwF1LAM17da2WXbW3vxit/EuTjXFoLsDe14ZAWePaq7v7SETp4uBus2vwrl67KozYrLYWsGYJ4GOsM7NVA==",
374 | "dev": true
375 | }
376 | },
377 | "dependencies": {
378 | "@lorenzofox3/for-await": {
379 | "version": "0.2.2",
380 | "resolved": "https://registry.npmjs.org/@lorenzofox3/for-await/-/for-await-0.2.2.tgz",
381 | "integrity": "sha512-IKIjQAyl6yu/kWTCqpc4QQwkA048pgsvvwxb8bMoqC4NBdypVBGj0wxFTL882sw8WzMSpov3g/pP6RxPhqbJHA==",
382 | "dev": true
383 | },
384 | "esbuild": {
385 | "version": "0.14.43",
386 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.43.tgz",
387 | "integrity": "sha512-Uf94+kQmy/5jsFwKWiQB4hfo/RkM9Dh7b79p8yqd1tshULdr25G2szLz631NoH3s2ujnKEKVD16RmOxvCNKRFA==",
388 | "dev": true,
389 | "requires": {
390 | "esbuild-android-64": "0.14.43",
391 | "esbuild-android-arm64": "0.14.43",
392 | "esbuild-darwin-64": "0.14.43",
393 | "esbuild-darwin-arm64": "0.14.43",
394 | "esbuild-freebsd-64": "0.14.43",
395 | "esbuild-freebsd-arm64": "0.14.43",
396 | "esbuild-linux-32": "0.14.43",
397 | "esbuild-linux-64": "0.14.43",
398 | "esbuild-linux-arm": "0.14.43",
399 | "esbuild-linux-arm64": "0.14.43",
400 | "esbuild-linux-mips64le": "0.14.43",
401 | "esbuild-linux-ppc64le": "0.14.43",
402 | "esbuild-linux-riscv64": "0.14.43",
403 | "esbuild-linux-s390x": "0.14.43",
404 | "esbuild-netbsd-64": "0.14.43",
405 | "esbuild-openbsd-64": "0.14.43",
406 | "esbuild-sunos-64": "0.14.43",
407 | "esbuild-windows-32": "0.14.43",
408 | "esbuild-windows-64": "0.14.43",
409 | "esbuild-windows-arm64": "0.14.43"
410 | }
411 | },
412 | "esbuild-darwin-64": {
413 | "version": "0.14.43",
414 | "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.43.tgz",
415 | "integrity": "sha512-/3PSilx011ttoieRGkSZ0XV8zjBf2C9enV4ScMMbCT4dpx0mFhMOpFnCHkOK0pWGB8LklykFyHrWk2z6DENVUg==",
416 | "dev": true,
417 | "optional": true
418 | },
419 | "fsevents": {
420 | "version": "2.3.2",
421 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
422 | "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
423 | "dev": true,
424 | "optional": true
425 | },
426 | "function-bind": {
427 | "version": "1.1.1",
428 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
429 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
430 | "dev": true
431 | },
432 | "has": {
433 | "version": "1.0.3",
434 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
435 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
436 | "dev": true,
437 | "requires": {
438 | "function-bind": "^1.1.1"
439 | }
440 | },
441 | "is-buffer": {
442 | "version": "1.1.6",
443 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
444 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
445 | },
446 | "is-core-module": {
447 | "version": "2.9.0",
448 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz",
449 | "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==",
450 | "dev": true,
451 | "requires": {
452 | "has": "^1.0.3"
453 | }
454 | },
455 | "is-even": {
456 | "version": "1.0.0",
457 | "resolved": "https://registry.npmjs.org/is-even/-/is-even-1.0.0.tgz",
458 | "integrity": "sha512-LEhnkAdJqic4Dbqn58A0y52IXoHWlsueqQkKfMfdEnIYG8A1sm/GHidKkS6yvXlMoRrkM34csHnXQtOqcb+Jzg==",
459 | "requires": {
460 | "is-odd": "^0.1.2"
461 | }
462 | },
463 | "is-number": {
464 | "version": "3.0.0",
465 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
466 | "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==",
467 | "requires": {
468 | "kind-of": "^3.0.2"
469 | }
470 | },
471 | "is-odd": {
472 | "version": "0.1.2",
473 | "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-0.1.2.tgz",
474 | "integrity": "sha512-Ri7C2K7o5IrUU9UEI8losXJCCD/UtsaIrkR5sxIcFg4xQ9cRJXlWA5DQvTE0yDc0krvSNLsRGXN11UPS6KyfBw==",
475 | "requires": {
476 | "is-number": "^3.0.0"
477 | }
478 | },
479 | "kind-of": {
480 | "version": "3.2.2",
481 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
482 | "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
483 | "requires": {
484 | "is-buffer": "^1.1.5"
485 | }
486 | },
487 | "nanoid": {
488 | "version": "3.3.7",
489 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
490 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
491 | "dev": true
492 | },
493 | "path-parse": {
494 | "version": "1.0.7",
495 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
496 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
497 | "dev": true
498 | },
499 | "picocolors": {
500 | "version": "1.0.0",
501 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
502 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
503 | "dev": true
504 | },
505 | "playwright": {
506 | "version": "1.22.2",
507 | "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.22.2.tgz",
508 | "integrity": "sha512-hUTpg7LytIl3/O4t0AQJS1V6hWsaSY5uZ7w1oCC8r3a1AQN5d6otIdCkiB3cbzgQkcMaRxisinjMFMVqZkybdQ==",
509 | "dev": true,
510 | "requires": {
511 | "playwright-core": "1.22.2"
512 | }
513 | },
514 | "playwright-core": {
515 | "version": "1.22.2",
516 | "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.22.2.tgz",
517 | "integrity": "sha512-w/hc/Ld0RM4pmsNeE6aL/fPNWw8BWit2tg+TfqJ3+p59c6s3B6C8mXvXrIPmfQEobkcFDc+4KirNzOQ+uBSP1Q==",
518 | "dev": true
519 | },
520 | "postcss": {
521 | "version": "8.4.32",
522 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
523 | "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
524 | "dev": true,
525 | "requires": {
526 | "nanoid": "^3.3.7",
527 | "picocolors": "^1.0.0",
528 | "source-map-js": "^1.0.2"
529 | }
530 | },
531 | "resolve": {
532 | "version": "1.22.0",
533 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
534 | "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
535 | "dev": true,
536 | "requires": {
537 | "is-core-module": "^2.8.1",
538 | "path-parse": "^1.0.7",
539 | "supports-preserve-symlinks-flag": "^1.0.0"
540 | }
541 | },
542 | "rollup": {
543 | "version": "2.75.6",
544 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.75.6.tgz",
545 | "integrity": "sha512-OEf0TgpC9vU6WGROJIk1JA3LR5vk/yvqlzxqdrE2CzzXnqKXNzbAwlWUXis8RS3ZPe7LAq+YUxsRa0l3r27MLA==",
546 | "dev": true,
547 | "requires": {
548 | "fsevents": "~2.3.2"
549 | }
550 | },
551 | "source-map-js": {
552 | "version": "1.0.2",
553 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
554 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
555 | "dev": true
556 | },
557 | "supports-preserve-symlinks-flag": {
558 | "version": "1.0.0",
559 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
560 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
561 | "dev": true
562 | },
563 | "vite": {
564 | "version": "2.9.18",
565 | "resolved": "https://registry.npmjs.org/vite/-/vite-2.9.18.tgz",
566 | "integrity": "sha512-sAOqI5wNM9QvSEE70W3UGMdT8cyEn0+PmJMTFvTB8wB0YbYUWw3gUbY62AOyrXosGieF2htmeLATvNxpv/zNyQ==",
567 | "dev": true,
568 | "requires": {
569 | "esbuild": "^0.14.27",
570 | "fsevents": "~2.3.2",
571 | "postcss": "^8.4.13",
572 | "resolve": "^1.22.0",
573 | "rollup": ">=2.59.0 <2.78.0"
574 | }
575 | },
576 | "ws": {
577 | "version": "8.8.0",
578 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.0.tgz",
579 | "integrity": "sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==",
580 | "dev": true,
581 | "requires": {}
582 | },
583 | "zora": {
584 | "version": "5.0.3",
585 | "resolved": "https://registry.npmjs.org/zora/-/zora-5.0.3.tgz",
586 | "integrity": "sha512-3DAIXwF1LAM17da2WXbW3vxit/EuTjXFoLsDe14ZAWePaq7v7SETp4uBus2vwrl67KozYrLYWsGYJ4GOsM7NVA==",
587 | "dev": true
588 | }
589 | }
590 | }
591 |
--------------------------------------------------------------------------------