pax_global_header00006660000000000000000000000064141651303470014516gustar00rootroot0000000000000052 comment=0761ef9c6633f30a68152e11a72d808d5df92f8a uvu-0.5.3/000077500000000000000000000000001416513034700123425ustar00rootroot00000000000000uvu-0.5.3/.editorconfig000066400000000000000000000003231416513034700150150ustar00rootroot00000000000000# http://editorconfig.org root = true [*] indent_size = 2 indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.{json,yml,md}] indent_style = space uvu-0.5.3/.github/000077500000000000000000000000001416513034700137025ustar00rootroot00000000000000uvu-0.5.3/.github/FUNDING.yml000066400000000000000000000000171416513034700155150ustar00rootroot00000000000000github: lukeed uvu-0.5.3/.github/workflows/000077500000000000000000000000001416513034700157375ustar00rootroot00000000000000uvu-0.5.3/.github/workflows/ci.yml000066400000000000000000000020631416513034700170560ustar00rootroot00000000000000name: CI on: push: paths-ignore: - 'docs/**' - 'bench/**' - 'examples/**' - 'shots/**' branches: - master pull_request: paths-ignore: - 'docs/**' - 'bench/**' - 'examples/**' - 'shots/**' branches: - master jobs: test: name: Node.js v${{ matrix.nodejs }} (${{ matrix.os }}) runs-on: ${{ matrix.os }} timeout-minutes: 3 strategy: matrix: nodejs: [8, 10, 12, 14] os: [ubuntu-latest, windows-latest, macOS-latest] steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: node-version: ${{ matrix.nodejs }} - name: Install run: | npm install npm install -g nyc@13 - name: Test w/ Coverage run: nyc --include=src npm test - name: Report if: matrix.nodejs >= 14 && matrix.os == 'ubuntu-latest' run: | nyc report --reporter=text-lcov > coverage.lcov bash <(curl -s https://codecov.io/bash) env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} uvu-0.5.3/.gitignore000066400000000000000000000001221416513034700143250ustar00rootroot00000000000000node_modules .DS_Store *-lock.* *.lock *.log /*.d.ts /dist /assert /parse /diff uvu-0.5.3/bench/000077500000000000000000000000001416513034700134215ustar00rootroot00000000000000uvu-0.5.3/bench/index.js000066400000000000000000000022111416513034700150620ustar00rootroot00000000000000const { reset } = require('kleur'); const { promisify } = require('util'); const { execFile } = require('child_process'); const spawn = promisify(execFile); const PAD = reset().dim(' || '); const runners = { ava: [require.resolve('ava/cli.js'), 'suites/ava/**'], jest: [require.resolve('jest/bin/jest.js'), 'suites/jest', '--env=node'], mocha: [require.resolve('mocha/bin/mocha'), 'suites/mocha'], tape: [require.resolve('tape/bin/tape'), 'suites/tape'], uvu: [require.resolve('uvu/bin.js'), 'suites/uvu'], }; function format(arr) { let num = Math.round(arr[1] / 1e6); if (arr[0] > 0) return (arr[0] + num / 1e3).toFixed(2) + 's'; return `${num}ms`; } async function run(name, args) { let timer = process.hrtime(); let pid = await spawn('node', args, { cwd: __dirname }); let delta = process.hrtime(timer); console.log('~> "%s" took %s', name, format(delta)); console.log(PAD + '\n' + PAD + (pid.stderr || pid.stdout).toString().replace(/(\r?\n)/g, '$1' + PAD)); } (async function () { for (let name of Object.keys(runners)) { await run(name, runners[name]); } })().catch(err => { console.error('Oops~!', err); process.exit(1); }); uvu-0.5.3/bench/math.js000066400000000000000000000001351416513034700147070ustar00rootroot00000000000000exports.sum = (a, b) => a + b; exports.div = (a, b) => a / b; exports.mod = (a, b) => a % b; uvu-0.5.3/bench/package.json000066400000000000000000000002331416513034700157050ustar00rootroot00000000000000{ "private": true, "devDependencies": { "ava": "3.10.1", "jest": "26.1.0", "mocha": "8.0.1", "tape": "5.0.1", "uvu": "0.1.0" } } uvu-0.5.3/bench/suites/000077500000000000000000000000001416513034700147355ustar00rootroot00000000000000uvu-0.5.3/bench/suites/ava/000077500000000000000000000000001416513034700155045ustar00rootroot00000000000000uvu-0.5.3/bench/suites/ava/index.spec.js000066400000000000000000000007561416513034700201120ustar00rootroot00000000000000const test = require('ava'); const math = require('../../math'); test('sum', t => { t.is(typeof math.sum, 'function'); t.is(math.sum(1, 2), 3); t.is(math.sum(-1, -2), -3); t.is(math.sum(-1, 1), 0); }); test('div', t => { t.is(typeof math.div, 'function'); t.is(math.div(1, 2), 0.5); t.is(math.div(-1, -2), 0.5); t.is(math.div(-1, 1), -1); }); test('mod', t => { t.is(typeof math.mod, 'function'); t.is(math.mod(1, 2), 1); t.is(math.mod(-3, -2), -1); t.is(math.mod(7, 4), 3); }); uvu-0.5.3/bench/suites/jest/000077500000000000000000000000001416513034700157025ustar00rootroot00000000000000uvu-0.5.3/bench/suites/jest/index.spec.js000066400000000000000000000015321416513034700203010ustar00rootroot00000000000000const math = require('../../math'); describe('sum', () => { it('should be a function', () => { expect(typeof math.sum).toBe('function'); }); it('should compute values correctly', () => { expect(math.sum(1, 2)).toBe(3); expect(math.sum(-1, -2)).toBe(-3); expect(math.sum(-1, 1)).toBe(0); }); }); describe('div', () => { it('should be a function', () => { expect(typeof math.div).toBe('function'); }); it('should compute values correctly', () => { expect(math.div(1, 2)).toBe(0.5); expect(math.div(-1, -2)).toBe(0.5); expect(math.div(-1, 1)).toBe(-1); }); }); describe('mod', () => { it('should be a function', () => { expect(typeof math.mod).toBe('function'); }); it('should compute values correctly', () => { expect(math.mod(1, 2)).toBe(1); expect(math.mod(-3, -2)).toBe(-1); expect(math.mod(7, 4)).toBe(3); }); }); uvu-0.5.3/bench/suites/mocha/000077500000000000000000000000001416513034700160245ustar00rootroot00000000000000uvu-0.5.3/bench/suites/mocha/index.js000066400000000000000000000016101416513034700174670ustar00rootroot00000000000000const assert = require('assert'); const math = require('../../math'); describe('sum', () => { it('should be a function', () => { assert.equal(typeof math.sum, 'function'); }); it('should compute result correctly', () => { assert.equal(math.sum(1, 2), 3); assert.equal(math.sum(-1, -2), -3); assert.equal(math.sum(-1, 1), 0); }); }); describe('div', () => { it('should be a function', () => { assert.equal(typeof math.div, 'function'); }); it('should compute result correctly', () => { assert.equal(math.div(1, 2), 0.5); assert.equal(math.div(-1, -2), 0.5); assert.equal(math.div(-1, 1), -1); }); }); describe('mod', () => { it('should be a function', () => { assert.equal(typeof math.mod, 'function'); }); it('should compute result correctly', () => { assert.equal(math.mod(1, 2), 1); assert.equal(math.mod(-3, -2), -1); assert.equal(math.mod(7, 4), 3); }); }); uvu-0.5.3/bench/suites/tape/000077500000000000000000000000001416513034700156665ustar00rootroot00000000000000uvu-0.5.3/bench/suites/tape/index.js000066400000000000000000000010151416513034700173300ustar00rootroot00000000000000const test = require('tape'); const math = require('../../math'); test('sum', t => { t.is(typeof math.sum, 'function'); t.is(math.sum(1, 2), 3); t.is(math.sum(-1, -2), -3); t.is(math.sum(-1, 1), 0); t.end(); }); test('div', t => { t.is(typeof math.div, 'function'); t.is(math.div(1, 2), 0.5); t.is(math.div(-1, -2), 0.5); t.is(math.div(-1, 1), -1); t.end(); }); test('mod', t => { t.is(typeof math.mod, 'function'); t.is(math.mod(1, 2), 1); t.is(math.mod(-3, -2), -1); t.is(math.mod(7, 4), 3); t.end(); }); uvu-0.5.3/bench/suites/uvu/000077500000000000000000000000001416513034700155545ustar00rootroot00000000000000uvu-0.5.3/bench/suites/uvu/index.js000066400000000000000000000011251416513034700172200ustar00rootroot00000000000000const { test } = require('uvu'); const assert = require('uvu/assert'); const math = require('../../math'); test('sum', () => { assert.type(math.sum, 'function'); assert.is(math.sum(1, 2), 3); assert.is(math.sum(-1, -2), -3); assert.is(math.sum(-1, 1), 0); }); test('div', () => { assert.type(math.div, 'function'); assert.is(math.div(1, 2), 0.5); assert.is(math.div(-1, -2), 0.5); assert.is(math.div(-1, 1), -1); }); test('mod', () => { assert.type(math.mod, 'function'); assert.is(math.mod(1, 2), 1); assert.is(math.mod(-3, -2), -1); assert.is(math.mod(7, 4), 3); }); test.run(); uvu-0.5.3/bin.js000077500000000000000000000021251416513034700134530ustar00rootroot00000000000000#!/usr/bin/env node const sade = require('sade'); const pkg = require('./package'); const { parse } = require('./parse'); const dimport = x => new Function(`return import(${ JSON.stringify(x) })`).call(0); const hasImport = (() => { try { new Function('import').call(0) } catch (err) { return !/unexpected/i.test(err.message) } })(); sade('uvu [dir] [pattern]') .version(pkg.version) .option('-b, --bail', 'Exit on first failure') .option('-i, --ignore', 'Any file patterns to ignore') .option('-r, --require', 'Additional module(s) to preload') .option('-C, --cwd', 'The current directory to resolve from', '.') .option('-c, --color', 'Print colorized output', true) .action(async (dir, pattern, opts) => { try { if (opts.color) process.env.FORCE_COLOR = '1'; let ctx = await parse(dir, pattern, opts); if (!ctx.requires && hasImport) { await dimport('uvu/run').then(m => m.run(ctx.suites, opts)); } else { await require('uvu/run').run(ctx.suites, opts); } } catch (err) { console.error(err.stack || err.message); process.exit(1); } }) .parse(process.argv); uvu-0.5.3/docs/000077500000000000000000000000001416513034700132725ustar00rootroot00000000000000uvu-0.5.3/docs/api.assert.md000066400000000000000000000177111416513034700156740ustar00rootroot00000000000000# `uvu/assert` The `uvu/assert` module is a collection of assertion methods that, like `uvu` itself, work in Node.js _and_ browser contexts. Additionally, `uvu/assert` is _completely_ optional, allowing you to bring along existing favorites. Because `uvu` operates through thrown `Error`s (or lack thereof), any `Error`-based utility can be used as an assertion. As a basic example, this is a completely valid `uvu` test: ```js import { test } from 'uvu'; test('this will fail', () => { if (1 !== 2) throw new Error('Duh!'); }); test.run(); ``` With this, `uvu` will register that the `"this will fail"` test failed.
You will only be missing the detailed insights (aka, pretty diff'ing) that the included [`Assertion`](#assertionoptions) errors provide. ## API > For all API methods listed:
> * `T` represents any data type
> * `Message` can be a string (for custom assertion message) or an `Error` instance ### ok(actual: T, msg?: Message) Assert that `actual` is a truthy value. ```js assert.ok(12345); assert.ok(!false); assert.ok('hello'); ``` ### is(actual: T, expects: T, msg?: Message) Assert that `actual` strictly equals (`===`) the `expects` value. ```js assert.is('hello', 'hello'); const arr = [1, 2, 3]; assert.is(arr, [1, 2, 3]); //=> fails assert.is(arr, arr); //=> pass ``` ### is.not(actual: T, expects: T, msg?: Message) Assert that `actual` does _not_ strictly equal (`===`) the `expects` value. ```js assert.is.not(123, '123'); assert.is.not(true, false); ``` ### equal(actual: T, expects: T, msg?: Message) Assert that `actual` is deeply equal to the `expects` value.
Visit [`dequal`](https://github.com/lukeed/dequal) for more information. ```js const input = { foo: 123, bar: [4, 5, 6] }; assert.equal(input, { foo: 123, bar: [4, 5, 6] }); ``` ### type(actual: T, expects: Types, msg?: Message) Assert that `typeof actual` is equal to the `expects` type.
Available `Types` are: `string`, `number`, `boolean`, `object`, `undefined`, and `function`. ```js assert.type(123, 'number'); assert.type('hello', 'string'); assert.type(assert.type, 'function'); ``` ### instance(actual: T, expects: T, msg?: Message) Assert that `actual` is an `instanceof` the `expects` constructor. ```js assert.instance(new Date, Date); assert.instance([1, 2, 3], Array); assert.instance(/foobar/gi, RegExp); ``` ### match(actual: string, expects: RegExp | String, msg?: Message) Assert that `actual` matches the `expects` pattern. When `expects` is a regular expression, it must match the `actual` value. When `expects` is a string, it must exist within the `actual` value as a substring. ```js assert.match('hello world', 'wor'); assert.match('hello world', /^hel/); ``` ### snapshot(actual: string, expects: string, msg?: Message) Assert that `actual` matches the `expects` multi-line string. ```js assert.snapshot( JSON.stringify({ foo: 123 }, null, 2), `{\n "foo": 123\n}` ); ``` ### fixture(actual: string, expects: string, msg?: Message) Assert that `actual` matches the `expects` multi-line string.
Equivalent to `assert.snapshot` except that line numbers are printed in the error diff! ```js assert.fixture( JSON.stringify({ foo: 123, bar: 456 }, null, 2), fs.readFileSync('fixture.json', 'utf8') ); ``` ### throws(fn: Function, expects?: Message | RegExp | Function, msg?: Message) Assert that the `fn` function throws an Error. When `expects` is not defined, then _any_ Error thrown satisfies the assertion.
When `expects` is a string, then the `Error`'s message must contain the `expects` string.
When `expects` is a function, then `expects` will receive the thrown `Error` and must return a `boolean` determination. Since `expects` is optional, you may also invoke the `assert.throws(fn, msg)` signature. ```js const OOPS = () => (null)[0]; assert.throws(() => OOPS()); assert.throws(() => OOPS(), /Cannot read property/); assert.throws(() => OOPS(), err => err instanceof TypeError); ``` If you are trying to assert that an `async` function throws an Error, the following approach [is recommended](https://github.com/lukeed/uvu/issues/35#issuecomment-896270152): ```js try { await asyncFnThatThrows(); assert.unreachable('should have thrown'); } catch (err) { assert.instance(err, Error); assert.match(err.message, 'something specific'); assert.is(err.code, 'ERROR123'); } ``` ### unreachable(msg?: Message) Assert that a line should never be reached. ```js try { throw new Error('Oops'); assert.unreachable('I will not run'); } catch (err) { assert.is(err.message, 'Oops'); } ``` ### not(actual: T, msg?: Message) Assert that `actual` is falsey. ```js assert.not(0); assert.not(null); assert.not(false); ``` ### not.ok(actual: T, msg?: Message) Assert that `actual` is not truthy.
This is an alias for `assert.not`. ### not.equal(actual: T, expects: T, msg?: Message) Assert that `actual` does not deeply equal the `expects` value.
Visit [`dequal`](https://github.com/lukeed/dequal) for more information. ```js const input = { foo: 123, bar: [4, 5, 6] }; assert.not.equal(input, { foo: 123 }); ``` ### not.type(actual: T, expects: Types, msg?: Message) Assert that `typeof actual` is not equal to the `expects` type.
Available `Types` are: `string`, `number`, `boolean`, `object`, `undefined`, and `function`. ```js assert.not.type(123, 'object'); assert.not.type('hello', 'number'); assert.not.type(assert.type, 'undefined'); ``` ### not.instance(actual: T, expects: T, msg?: Message) Assert that `actual` is not an `instanceof` the `expects` constructor. ```js assert.not.instance(new Date, Number); assert.not.instance([1, 2, 3], String); assert.not.instance(/foobar/gi, Date); ``` ### not.match(actual: string, expects: RegExp | String, msg?: Message) Assert that `actual` does not match the `expects` pattern. When `expects` is a regular expression, it must not match the `actual` value. When `expects` is a string, it must not exist within the `actual` value as a substring. ```js assert.not.match('hello world', 'other'); assert.not.match('hello world', /other/g); ``` ### not.snapshot(actual: string, expects: string, msg?: Message) Assert that `actual` does not match the `expects` snapshot. ```js assert.not.snapshot( JSON.stringify({ foo: 123 }, null, 2), `{"foo":123,"bar":456}` ); ``` ### not.fixture(actual: string, expects: string, msg?: Message) Assert that `actual` does not match the `expects` multi-line string.
Equivalent to `assert.not.snapshot` except that line numbers are printed in the error diff! ```js assert.not.fixture( JSON.stringify({ foo: 123, bar: 456 }, null, 2), fs.readFileSync('fixture.json', 'utf8') ); ``` ### not.throws(fn: Function, expects?: Message | RegExp | Function, msg?: Message) Assert that the `fn` function does not throw, _or_ does not throw of `expects` type. ```js const PASS = () => {}; const FAIL = () => { throw new Error('Oops'); }; assert.not.throws(() => PASS()); //=> pass assert.not.throws(() => FAIL()); //=> fails assert.not.throws(() => FAIL(), /Oops/); //=> pass assert.not.throws(() => FAIL(), /foobar/); //=> fails assert.not.throws(() => FAIL(), err => err.message.length > 0); //=> pass ``` ### Assertion(options) The base `Assertion` class, which extends `Error` directly. Internally, `uvu` checks if thrown errors are `Assertion` errors as part of its formatting step. #### options.message Type: `string`
Required: `true` The error message to print. > **Note:** By default, this is the generated default from each `uvu/assert` method. #### options.details Type: `string`
Required: `false` The detailed diff output, as generated by `uvu/diff`. #### options.generated Type: `boolean`
Required: `false` If the `options.message` was generated.
This will be `false` when an `uvu/assert` method received a custom message. #### options.operator Type: `string`
Required: `true` The assertion method name. #### options.expects Type: `any`
Required: `true` The expected value. #### options.actual; Type: `any`
Required: `true` The actual value. uvu-0.5.3/docs/api.uvu.md000066400000000000000000000250451416513034700152110ustar00rootroot00000000000000# `uvu` This is the main module. All `uvu` tests require that either [`suite`](#uvusuitename-string-context-t) or [`test`](#uvutestname-string-callback-function) (or both) be imported. You may declare multiple [`Suites`](#suites) in the same file. This helps with organization as it group test output in a more readable fashion and allows related items to remain neighbors. You should choose `uvu.suite` if/when you'd like to leverage the additional organization.
You should choose `uvu.test` if you don't care about organization and/or are only planning on testing a single entity. There is no penalty for choosing `uvu.suite` vs `uvu.test`. In fact, `uvu.test` _is_ an unnamed [Suite](#suites)! No matter which you choose, the Suite's [`run`](#suiterun) must be called in order for it to be added to `uvu`'s queue. > **Note:** Because of this API decision, `uvu` test files can be executed with `node` directly! ## API ### uvu.suite(name: string, context?: T) Returns: [`Suite`](#suites) Creates a new `Suite` instance. Of course, you may have multiple `Suite`s in the same file.
However, you must remember to call `run()` on each suite! #### name Type: `String` The name of your suite.
This groups all console output together and will prefix the name of any failing test. #### context Type: `any`
Default: `{}` The suite's initial [context](#context-1) value, if any.
This will be passed to every [hook](#hooks) and to every test block within the suite. > **Note:** Before v0.4.0, `uvu` attempted to provide read-only access within test handlers. Ever since, `context` is writable/mutable anywhere it's accessed. ### uvu.test(name: string, callback: function) Returns: `void` If you don't want to separate your tests into groups (aka, "suites") – or if you don't plan on testing more than one thing in a file – then you may want to import `test` for simplicity sake (naming is hard). > **Important:** The `test` export is just an unnamed [`Suite`](#suites) instance! #### name Type: `String` The name of your test.
Choose a descriptive name as it identifies failing tests. #### callback Type: `Function` or `Promise` The callback that contains your test code.
Your callback may be asynchronous and may `return` any value, although returned values are discarded completely and have no effect. ## Suites All `uvu` test suites share the same API and can be used in the same way. In fact, `uvu.test` is actually the result of unnamed `uvu.suite` call!
The only difference between them is how their results are grouped and displayed in your terminal window. ***API*** ### suite(name, callback) Every suite instance is callable.
This is the standard usage. ### suite.only(name, callback) For this `suite`, only run this test.
This is a shortcut for isolating one (or more) test blocks. > **Note:** You can invoke `only` on multiple tests! ### suite.skip(name, callback) Skip this test block entirely. ### suite.before(callback) Invoke the provided `callback` before this suite begins.
This is ideal for creating fixtures or setting up an environment.
Please see [Hooks](#hooks) for more information. ### suite.after(callback) Invoke the provided `callback` after this suite finishes.
This is ideal for fixture or environment cleanup.
Please see [Hooks](#hooks) for more information. ### suite.before.each(callback) Invoke the provided `callback` before each test of this suite begins.
Please see [Hooks](#hooks) for more information. ### suite.after.each(callback) Invoke the provided `callback` after each test of this suite finishes.
Please see [Hooks](#hooks) for more information. ### suite.run() Start/Add the suite to the `uvu` test queue. > **Important:** You **must** call this method in order for your suite to be run! ***Example*** > Check out [`/examples`](/examples) for a list of working demos! ```js import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as dates from '../src/dates'; const Now = suite('Date.now()'); let _Date; Now.before(() => { let count = 0; _Date = global.Date; global.Date = { now: () => 100 + count++ }; }); Now.after(() => { global.Date = _Date; }); // this is not run (skip) Now.skip('should be a function', () => { assert.type(Date.now, 'function'); }); // this is not run (only) Now('should return a number', () => { assert.type(Date.now(), 'number'); }); // this is run (only) Now.only('should progress with time', () => { assert.is(Date.now(), 100); assert.is(Date.now(), 101); assert.is(Date.now(), 102); }); Now.run(); ``` ## Hooks Your suite can implement "hooks" that run before and/or after the entire suite, as well as before and/or after the suite's individual tests. It may be useful to use `suite.before` and `suite.after` to set up and teardown suite-level assumptions like: * environment variables * database clients and/or seed data * generating fixtures * mocks, spies, etc It may be appropriate to use `suite.before.each` and `suite.after.each` to reset parts of suite's context, or for passing values between tests. This may include — but of course, is not limited to — rolling back database transactions, restoring a mocked function, etc. > **Important:** Any `after` and `after.each` hooks will _always_ be invoked – including after failed assertions. Additionally, as of `uvu@0.3.0`, hooks receive the suite's `context` value. They are permitted to modify the `context` value directly, allowing you to organize and abstract hooks into reusable setup/teardown blocks. Please read [Context](#context-1) for examples and more information. ***Example: Lifecycle*** The following implements all available hooks so that their call patterns can be recorded: ```js test.before(() => { console.log('SETUP'); }); test.after(() => { console.log('CLEANUP'); }); test.before.each(() => { console.log('>> BEFORE'); }); test.after.each(() => { console.log('>> AFTER'); }); // --- test('foo', () => { console.log('>>>> TEST: FOO'); }); test('bar', () => { console.log('>>>> TEST: BAR'); }); test.run(); // SETUP // >> BEFORE // >>>> TEST: FOO // >> AFTER // >> BEFORE // >>>> TEST: BAR // >> AFTER // CLEANUP ``` ## Context When using [suite hooks](#hooks) to establish and reset environments, there's often some side-effect that you wish to make accessible to your tests. For example, this may be a HTTP client, a database table record, a JSDOM instance, etc. Typically, these side-effects would have to be saved into top-level variables that so that all parties involved can access them. The following is an example of this pattern: ```js const User = suite('User'); let client, user; User.before(async () => { client = await DB.connect(); }); User.before.each(async () => { user = await client.insert('insert into users ... returning *'); }); User.after.each(async () => { await client.destroy(`delete from users where id = ${user.id}`); user = undefined; }); User.after(async () => { client = await client.end(); }); User('should not have Teams initially', async () => { const teams = await client.select(` select id from users_teams where user_id = ${user.id}; `); assert.is(teams.length, 0); }); // ... User.run(); ``` While this certainly works, it can quickly become unruly once multiple suites exist within the same file. Additionally, it **requires** that all our suite hooks (`User.before`, `User.before.each`, etc) are defined within this file so that they may have access to the `user` and `client` variables that they're modifying. Instead, we can improve this by writing into the suite's "context" directly! > **Note:** If it helps your mental model, "context" can be interchanged with "state" – except that it's intended to umbrella the tests with a certain environment. ```js const User = suite('User'); User.before(async context => { context.client = await DB.connect(); }); User.before.each(async context => { context.user = await context.client.insert('insert into users ... returning *'); }); User.after.each(async context => { await context.client.destroy(`delete from users where id = ${user.id}`); context.user = undefined; }); User.after(async context => { context.client = await context.client.end(); }); // User.run(); ``` A "context" is unique to each suite and can be defined through [`suite()`](#uvusuitename-string) initialization and/or modified by the suite's hooks. Because of this, hooks can be abstracted into separate files and then attached safely to different suites: ```js import * as $ from './helpers'; const User = suite('User'); // Reuse generic/shared helpers // --- User.before($.DB.connect); User.after($.DB.destroy); // Keep User-specific helpers in this file // --- User.before.each(async context => { context.user = await context.client.insert('insert into users ... returning *'); }); User.after.each(async context => { await context.client.destroy(`delete from users where id = ${user.id}`); context.user = undefined; }); // User.run() ``` Individual tests will also receive the `context` value. This is how tests can access the HTTP client or database fixture you've set up, for example. Here's an example `User` test, now acessing its `user` and `client` values from context instead of globally-scoped variables: ```js User('should not have Teams initially', async context => { const { client, user } = context; const teams = await client.select(` select id from users_teams where user_id = ${user.id}; `); assert.is(teams.length, 0); }); ``` ***TypeScript*** Finally, TypeScript users can easily define their suites' contexts on a suite-by-suite basis. Let's revisit our initial example, now using context and TypeScript interfaces: ```ts interface Context { client?: DB.Client; user?: IUser; } const User = suite('User', { client: undefined, user: undefined, }); // Our `context` is type-checked // --- User.before(async context => { context.client = await DB.connect(); }); User.after(async context => { context.client = await context.client.end(); }); User.before.each(async context => { context.user = await context.client.insert('insert into users ... returning *'); }); User.after.each(async context => { await context.client.destroy(`delete from users where id = ${user.id}`); context.user = undefined; }); // Our `context` is *still* type-checked 🎉 User('should not have Teams initially', async context => { const { client, user } = context; const teams = await client.select(` select id from users_teams where user_id = ${user.id}; `); assert.is(teams.length, 0); }); User.run(); ``` uvu-0.5.3/docs/cli.md000066400000000000000000000125031416513034700143640ustar00rootroot00000000000000# CLI > The `uvu` CLI is available whenever you install the `uvu` package. The _role_ of the `uvu` CLI is to collect and execute your tests suites. In order to do that, you must tell it how/where to find your tests. Otherwise, it has a default set of file patterns to search for – but that probably won't align with your project's structure. > **Note:** Using the `uvu` CLI is actually _optional_! See [Isolation](#isolation) for more info. Let's take a look at the CLI's help text: ```sh $ uvu --help # # Usage # $ uvu [dir] [pattern] [options] # # Options # -b, --bail Exit on first failure # -i, --ignore Any file patterns to ignore # -r, --require Additional module(s) to preload # -C, --cwd The current directory to resolve from (default .) # -c, --color Print colorized output (default true) # -v, --version Displays current version # -h, --help Displays this message # ``` As you can see, there are few arguments and option flags!
They're categorized by responsibility: * where/how to locate test files (`dir`, `pattern`, `--ignore`, and `--cwd`) * environment preparation (`--require` and `--color`) * whether or not to exit on suite failures (`--bail`) ## Matching ***Aside: Glob Patterns*** Unlike other test runners, `uvu` intentionally _does not_ rely on glob patterns.
Parsing and matching globs both require a non-trivial amount of work. (Even the smallest glob-parsers are as large – or larger – than the entire `uvu` source!) This price has to be paid upfront – at startup – and then becomes completely irrelevant. And then on the consumer side, glob patterns fall into one of two categories: * extremely simple patterns (eg, `test/**`, `tests/**/*.test.(ts|js)`, etc) * extremely complicated patterns that require squinting and fine-tuning Simple patterns don't _require_ a glob pattern 99% of the time.
Complicated glob patterns can (often) be better expressed or understood as separate segments. And then there's the reoccurring issue of some shells and/or operating systems (eg, Windows) that will pre-evaluate a glob pattern, ruining a CLI's expected input... And how different systems require quotation marks, and/or certain characters to be escaped... There are issues with this approach. It certainly _can_ work, but `uvu` sidesteps this can of worms in favor of a different, but simpler approach. ***Logic*** The CLI will _always_ resolve lookups from the `--cwd` location, which is the `process.cwd()` when unspecified. By default (aka, when no arguments are given), `uvu` looks for files matching this behemoth of a pattern: ```js /((\/|^)(tests?|__tests?__)\/.*|\.(tests?|spec)|^\/?tests?)\.([mc]js|[jt]sx?)$/i; ``` This will: * search within `test`, `tests` or `__test__` or `__tests__` directories if they exist; otherwise any directory * search for files matching `*.test.{ext}` or `*.tests.{ext}` or `*.spec.{ext}` * search for files with these extensions: `mjs`, `cjs`, `js`, `jsx`, `tsx`, or `ts` Of course, you can help `uvu` out and narrow down its search party by providing a `dir` argument. Instead of searching from your project's root, `dir` tells `uvu` where to _start_ its traversal from. And by specifying a `dir`, the default `pattern` changes such that any file with a `mjs`, `cjs`, `js`, `jsx`, `tsx`, or `ts` extension will match. Finally, you may specify your own `pattern` too — assuming `dir` has been set.
The `pattern` value is passed through `new RegExp(, 'i')`. If you are nostalgic for complex glob patterns, this is your chance to relive its glory days – with substitutions, of course. For example, if we assume a [monorepo environment](/examples/monorepo/package.json), your `uvu` usage may look like this: ```sh $ uvu packages tests -i fixtures ``` This will traverse the `packages` directory, looking at files and subdirectories that match `/tests/i`, but ignore anything that matches the `/fixtures/i` pattern. ***Ignoring*** Any file or directory names matching `node_modules` or `^.git` are _always_ ignored. You can use the `-i/--ignore` flags to provide additional patterns to ignore. Much like `[pattern]`, these values are cast to a `RegExp`, allowing you to be as vague or specific as you need to be. The `-i/--ignore` flags can be passed multiple times: ```sh # Traverse "packages" diectory # ~> ignore items matching /foobar/i # ~> ignore items matching /fixtures/i # ~> ignore items matching /\d+.js$/i $ uvu packages -i foobar -i fixtures -i \\d+.js$ ``` ## Isolation When running `uvu`, it looks for all test files and will enqueue all suites for execution. You can always disable individual suites (by commenting out its `.run()` call), but sometimes you just want to execute a single file. To do this, you can simply call `node` with the path to your file! 🎉
This works _because_ `uvu` test files are self-contained – its `import`/`require` statements aren't tucked away, nor are the suites' `run()` invocation. ```sh # Before (runs everything in packages/**/**test**) $ uvu packages test # After (specific file) $ node packages/utils/test/random.js ``` Since `uvu` and `node` share the `--require` hook, you can bring your `uvu -r` arguments to `node` too~! ```sh # Before $ uvu -r esm tests $ uvu -r ts-node/register tests # After $ node -r esm tests/math.js $ node -r ts-node/register tests/math.ts ``` uvu-0.5.3/docs/coverage.md000066400000000000000000000017321416513034700154120ustar00rootroot00000000000000# Coverage Code coverage is not implemented by the [CLI](/docs/cli.md) directly. Instead, `uvu` plays nicely with existing coverage tools like [`c8`](https://www.npmjs.com/package/c8) or [`nyc`](https://www.npmjs.com/package/nyc).
Please refer to their respective documentations for usage information. ## Examples > Visit the working [`/examples/coverage`](/examples/coverage) demonstration~! Assuming we have a `uvu` command hooked up to the `npm test` script: ```js // package.json { "scripts": { "test": "uvu tests --ignore fixtures" } } ``` We can then use `nyc` or `c8` (or others) as a prefix to our `npm test` usage: ```sh $ c8 npm test $ nyc npm test $ c8 yarn test $ nyc yarn test $ nyc --include=src npm test $ c8 --all npm test ``` Of course, you can also use `c8`/`nyc` directly with `uvu` – it just makes it more confusing to distinguish CLI option flags: ```sh $ c8 uvu tests --ignore fixtures $ nyc --include=src uvu tests --ignore fixtures ``` uvu-0.5.3/docs/esm.md000066400000000000000000000066351416513034700144120ustar00rootroot00000000000000# ES Modules EcmaScript Modules have landed in Node.js (> 12.x)! Check out the official language documentation[[1](https://nodejs.org/api/esm.html#esm_modules_ecmascript_modules)][[2](https://nodejs.org/api/packages.html)] to learn more. ...but, here's the **TL;DR:** * by default, only files with `.mjs` extension are treated as ESM * by default, `.js` – and now `.cjs` – files are treated as CommonJS * by defining `"type": "module"` in your `package.json` file, all `.js` files are treated as ESM * when using ESM, any `import`s must reference the _full_ filepath, including its extension * the `.cjs` extension is _always_ CommonJS, even if `"type": "module"` is defined ## Examples Knowing the above, there are a few ways we can use/integrate ESM into our `uvu` test suites! > **Important:** Only uvu v0.5.0+ has native ESM support ### Native ESM – via `.mjs` files > Visit the working [`/examples/esm.mjs`](/examples/esm.mjs) demonstration~! This example only works in Node.js v12.0 and later. In other words, it requires that _both_ your test files _and_ your source files possess the `.mjs` extension. This is – by default – the only way Node.js will load ES Modules and allow them to import/reference one another. ***PRO*** * Modern * Native / less tooling ***CON*** * Requires Node.js 12.0 and later * Exposes you to CommonJS <-> ESM interop issues * Cannot test older Node.js versions – unless maintain a duplicate set of source _and_ test files ### Polyfill – via `esm` package > Visit the working [`/examples/esm.loader`](/examples/esm.loader) demonstration~! Thanks to [`esm`](http://npmjs.com/package/esm), this example works in **all** Node.js versions. However, for best/consistent results, you **should avoid** using `.mjs` files when using this approach. This is because `esm` has some [limitations](https://www.npmjs.com/package/esm#extensions) and chooses not to interact/tamper with files that, by definition, should only be running with the native loader anyway. ***PRO*** * Makes ESM accessible to older Node.js versions * Solves (most) CommonJS <-> ESM interop issues * Only requires a simple `--require/-r` hook * Quick to attach and quick to execute ***CON*** * Not native * Not compatible with `.mjs` files ### Native ESM – via `"type": "module"` > Visit the working [`/examples/esm.dual`](/examples/esm.dual) demonstration~! This example combines the best of both worlds! It makes use of native ESM in Node.js versions that support it, while still making it possible to run your tests in older/legacy Node.js versions. With `"type": "module"`, we are able to use ESM within `.js` files. Node 12.x and later to process those files as ESM, through native behavior. And then older Node.js versions can run/process the _same_ files by simply including the [`esm`](http://npmjs.com/package/esm) loader. At worst – all we have is a "duplicate" test script... which is much, much better than duplicating sets of files. We end up with something like this: ```js { "type": "module", // ... "scripts": { "test:legacy": "uvu -r esm tests", "test:native": "uvu tests" } } ``` Your CI environment would execute the appropriate script according to its Node version :tada: ***PRO*** * Native when possible * No additional maintenance * Run tests in wider Node.js matrix * Easy to drop legacy support at anytime ***CON*** * Defining `"type": "module"` may change how your package is consumed uvu-0.5.3/docs/watch.md000066400000000000000000000036441416513034700147310ustar00rootroot00000000000000# Watch Mode Watching – aka "re-running" – tests is not implemented by the [CLI](/docs/cli.md) directly.
This partly because `uvu` is so fast and lightweight, which means that there's _very_ little cost in rerunning your tests. Instead, `uvu` is meant to interface with other tools nicely. Some of those include (but are not limited to): * [`watchlist`](https://github.com/lukeed/watchlist) * [`watchexec`](https://github.com/watchexec/watchexec) * [`chokidar-cli`](https://www.npmjs.com/package/chokidar-cli) * [`watch`](https://www.npmjs.com/package/watch) ## Example > Visit the working [`/examples/watch`](/examples/watch) demonstration~! Assuming we have a `uvu` command hooked up to the `npm test` script: ```js // package.json { "scripts": { "test": "uvu tests --ignore fixtures" }, "devDependencies": { "uvu": "^0.2.0" } } ``` We just need to install [`watchlist`](https://github.com/lukeed/watchlist) and configure a new `scripts` entry. We'll call it `"test:watch"`: > **Note:** As mentioned, alternatives to `watchlist` will work too. ```sh $ yarn add --dev watchlist ``` ```diff { "scripts": { - "test": "uvu tests --ignore fixtures" + "test": "uvu tests --ignore fixtures", + "test:watch": "watchlist src tests -- yarn test" }, "devDependencies": { - "uvu": "^0.2.0" + "uvu": "^0.2.0", + "watchlist": "^0.2.0" } } ``` Now we need to start our test watcher: ```sh $ yarn test:watch ``` What happens is that `watchlist` will recursively watch the `src` and `tests` directories. When those directories' contents change (move, rename, etc), the `yarn test` command will be executed. And in this case, `yarn test` is just an alias for our `uvu` configuration, which means that we can keep the command option flags visually separate & keep scripts DRY for our own peace of mind :) > **Note:** This assumes that we have a `/src` directory that lives alongside our `/tests` directory. uvu-0.5.3/examples/000077500000000000000000000000001416513034700141605ustar00rootroot00000000000000uvu-0.5.3/examples/basic/000077500000000000000000000000001416513034700152415ustar00rootroot00000000000000uvu-0.5.3/examples/basic/package.json000066400000000000000000000001631416513034700175270ustar00rootroot00000000000000{ "private": true, "scripts": { "test": "uvu tests" }, "devDependencies": { "uvu": "^0.0.18" } } uvu-0.5.3/examples/basic/src/000077500000000000000000000000001416513034700160305ustar00rootroot00000000000000uvu-0.5.3/examples/basic/src/math.js000066400000000000000000000001351416513034700173160ustar00rootroot00000000000000exports.sum = (a, b) => a + b; exports.div = (a, b) => a / b; exports.mod = (a, b) => a % b; uvu-0.5.3/examples/basic/src/utils.js000066400000000000000000000003031416513034700175220ustar00rootroot00000000000000exports.capitalize = function (str) { return str[0].toUpperCase() + str.substring(1); } exports.dashify = function (str) { return str.replace(/([a-zA-Z])(?=[A-Z\d])/g, '$1-').toLowerCase(); } uvu-0.5.3/examples/basic/tests/000077500000000000000000000000001416513034700164035ustar00rootroot00000000000000uvu-0.5.3/examples/basic/tests/math.js000066400000000000000000000011261416513034700176720ustar00rootroot00000000000000const { test } = require('uvu'); const assert = require('uvu/assert'); const math = require('../src/math'); test('sum', () => { assert.type(math.sum, 'function'); assert.is(math.sum(1, 2), 3); assert.is(math.sum(-1, -2), -3); assert.is(math.sum(-1, 1), 0); }); test('div', () => { assert.type(math.div, 'function'); assert.is(math.div(1, 2), 0.5); assert.is(math.div(-1, -2), 0.5); assert.is(math.div(-1, 1), -1); }); test('mod', () => { assert.type(math.mod, 'function'); assert.is(math.mod(1, 2), 1); assert.is(math.mod(-3, -2), -1); assert.is(math.mod(7, 4), 3); }); test.run(); uvu-0.5.3/examples/basic/tests/utils.js000066400000000000000000000010001416513034700200700ustar00rootroot00000000000000const { test } = require('uvu'); const assert = require('uvu/assert'); const utils = require('../src/utils'); test('capitalize', () => { assert.type(utils.capitalize, 'function'); assert.is(utils.capitalize('hello'), 'Hello'); assert.is(utils.capitalize('foo bar'), 'Foo bar'); }); test('dashify', () => { assert.type(utils.dashify, 'function'); assert.is(utils.dashify('fooBar'), 'foo-bar'); assert.is(utils.dashify('FooBar'), 'foo-bar'); assert.is(utils.dashify('foobar'), 'foobar'); }); test.run(); uvu-0.5.3/examples/coverage/000077500000000000000000000000001416513034700157535ustar00rootroot00000000000000uvu-0.5.3/examples/coverage/.gitignore000066400000000000000000000000261416513034700177410ustar00rootroot00000000000000/coverage /coverage.* uvu-0.5.3/examples/coverage/package.json000066400000000000000000000003751416513034700202460ustar00rootroot00000000000000{ "private": true, "scripts": { "test": "uvu tests", "test:coverage": "c8 --include=src npm test", "test:report": "c8 report --reporter=text-lcov > coverage.lcov" }, "devDependencies": { "c8": "^7.2.0", "uvu": "^0.2.0" } } uvu-0.5.3/examples/coverage/src/000077500000000000000000000000001416513034700165425ustar00rootroot00000000000000uvu-0.5.3/examples/coverage/src/math.js000066400000000000000000000001351416513034700200300ustar00rootroot00000000000000exports.sum = (a, b) => a + b; exports.div = (a, b) => a / b; exports.mod = (a, b) => a % b; uvu-0.5.3/examples/coverage/src/utils.js000066400000000000000000000003031416513034700202340ustar00rootroot00000000000000exports.capitalize = function (str) { return str[0].toUpperCase() + str.substring(1); } exports.dashify = function (str) { return str.replace(/([a-zA-Z])(?=[A-Z\d])/g, '$1-').toLowerCase(); } uvu-0.5.3/examples/coverage/tests/000077500000000000000000000000001416513034700171155ustar00rootroot00000000000000uvu-0.5.3/examples/coverage/tests/math.js000066400000000000000000000011261416513034700204040ustar00rootroot00000000000000const { test } = require('uvu'); const assert = require('uvu/assert'); const math = require('../src/math'); test('sum', () => { assert.type(math.sum, 'function'); assert.is(math.sum(1, 2), 3); assert.is(math.sum(-1, -2), -3); assert.is(math.sum(-1, 1), 0); }); test('div', () => { assert.type(math.div, 'function'); assert.is(math.div(1, 2), 0.5); assert.is(math.div(-1, -2), 0.5); assert.is(math.div(-1, 1), -1); }); test('mod', () => { assert.type(math.mod, 'function'); assert.is(math.mod(1, 2), 1); assert.is(math.mod(-3, -2), -1); assert.is(math.mod(7, 4), 3); }); test.run(); uvu-0.5.3/examples/coverage/tests/utils.js000066400000000000000000000010001416513034700206020ustar00rootroot00000000000000const { test } = require('uvu'); const assert = require('uvu/assert'); const utils = require('../src/utils'); test('capitalize', () => { assert.type(utils.capitalize, 'function'); assert.is(utils.capitalize('hello'), 'Hello'); assert.is(utils.capitalize('foo bar'), 'Foo bar'); }); test('dashify', () => { assert.type(utils.dashify, 'function'); assert.is(utils.dashify('fooBar'), 'foo-bar'); assert.is(utils.dashify('FooBar'), 'foo-bar'); assert.is(utils.dashify('foobar'), 'foobar'); }); test.run(); uvu-0.5.3/examples/esbuild/000077500000000000000000000000001416513034700156075ustar00rootroot00000000000000uvu-0.5.3/examples/esbuild/package.json000066400000000000000000000003001416513034700200660ustar00rootroot00000000000000{ "private": true, "scripts": { "test": "uvu -r esbuild-register tests" }, "devDependencies": { "esbuild": "0.13.3", "esbuild-register": "3.0.0", "uvu": "^0.5.1" } } uvu-0.5.3/examples/esbuild/src/000077500000000000000000000000001416513034700163765ustar00rootroot00000000000000uvu-0.5.3/examples/esbuild/src/math.ts000066400000000000000000000003211416513034700176730ustar00rootroot00000000000000export function sum(a: number, b: number): number { return a + b; } export function div(a: number, b: number): number { return a / b; } export function mod(a: number, b: number): number { return a % b; } uvu-0.5.3/examples/esbuild/src/utils.ts000066400000000000000000000003331416513034700201050ustar00rootroot00000000000000export function capitalize(str: string): string { return str[0].toUpperCase() + str.substring(1); } export function dashify(str: string): string { return str.replace(/([a-zA-Z])(?=[A-Z\d])/g, '$1-').toLowerCase(); } uvu-0.5.3/examples/esbuild/tests/000077500000000000000000000000001416513034700167515ustar00rootroot00000000000000uvu-0.5.3/examples/esbuild/tests/math.ts000066400000000000000000000011211416513034700202450ustar00rootroot00000000000000import { test } from 'uvu'; import * as assert from 'uvu/assert'; import * as math from '../src/math'; test('sum', () => { assert.type(math.sum, 'function'); assert.is(math.sum(1, 2), 3); assert.is(math.sum(-1, -2), -3); assert.is(math.sum(-1, 1), 0); }); test('div', () => { assert.type(math.div, 'function'); assert.is(math.div(1, 2), 0.5); assert.is(math.div(-1, -2), 0.5); assert.is(math.div(-1, 1), -1); }); test('mod', () => { assert.type(math.mod, 'function'); assert.is(math.mod(1, 2), 1); assert.is(math.mod(-3, -2), -1); assert.is(math.mod(7, 4), 3); }); test.run(); uvu-0.5.3/examples/esbuild/tests/utils.ts000066400000000000000000000007731416513034700204700ustar00rootroot00000000000000import { test } from 'uvu'; import * as assert from 'uvu/assert'; import * as utils from '../src/utils'; test('capitalize', () => { assert.type(utils.capitalize, 'function'); assert.is(utils.capitalize('hello'), 'Hello'); assert.is(utils.capitalize('foo bar'), 'Foo bar'); }); test('dashify', () => { assert.type(utils.dashify, 'function'); assert.is(utils.dashify('fooBar'), 'foo-bar'); assert.is(utils.dashify('FooBar'), 'foo-bar'); assert.is(utils.dashify('foobar'), 'foobar'); }); test.run(); uvu-0.5.3/examples/esbuild/tsconfig.json000066400000000000000000000004531416513034700203200ustar00rootroot00000000000000{ "compilerOptions": { "outDir": "build", "target": "esnext", "module": "esnext", "noImplicitAny": true, "moduleResolution": "node", "forceConsistentCasingInFileNames": true }, "include": [ "@types/**/*", "src/**/*" ], "exclude": [ "node_modules" ] } uvu-0.5.3/examples/esm.dual/000077500000000000000000000000001416513034700156705ustar00rootroot00000000000000uvu-0.5.3/examples/esm.dual/package.json000066400000000000000000000003111416513034700201510ustar00rootroot00000000000000{ "private": true, "type": "module", "scripts": { "test:legacy": "uvu -r esm tests", "test:native": "uvu tests" }, "devDependencies": { "esm": "3.2.25", "uvu": "^0.5.0" } } uvu-0.5.3/examples/esm.dual/readme.md000066400000000000000000000014041416513034700174460ustar00rootroot00000000000000# Example: esm.dual Please read [/docs/esm](/docs/esm.md) for full details & comparisons. ## Why Unlike [/examples/esm.loader](/examples/esm.loader), this example uses the native ESM loader whenever it's available. Unlike [/examples/esm.mjs](/examples/esm.mjs), this example will run in all versions of Node.js – including older versions where ESM is not natively supported. ## Highlights * Define `"type": "module"` within `package.json`
Allows Node.js to treat `.js` files as ESM. * Define `import` statements with full file paths
Required by Node.js whenever ESM in use. * Define two `test` scripts: * `"test:native"` – for use within Node 12+ * `"test:legacy"` – for use with Node < 12 ## License MIT © [Luke Edwards](https://lukeed.com) uvu-0.5.3/examples/esm.dual/src/000077500000000000000000000000001416513034700164575ustar00rootroot00000000000000uvu-0.5.3/examples/esm.dual/src/math.js000066400000000000000000000001541416513034700177460ustar00rootroot00000000000000export const sum = (a, b) => a + b; export const div = (a, b) => a / b; export const mod = (a, b) => a % b; uvu-0.5.3/examples/esm.dual/src/utils.js000066400000000000000000000002731416513034700201570ustar00rootroot00000000000000export function capitalize(str) { return str[0].toUpperCase() + str.substring(1); } export function dashify(str) { return str.replace(/([a-zA-Z])(?=[A-Z\d])/g, '$1-').toLowerCase(); } uvu-0.5.3/examples/esm.dual/tests/000077500000000000000000000000001416513034700170325ustar00rootroot00000000000000uvu-0.5.3/examples/esm.dual/tests/math.js000066400000000000000000000015731416513034700203270ustar00rootroot00000000000000import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as math from '../src/math.js'; const sum = suite('sum'); sum('should be a function', () => { assert.type(math.sum, 'function'); }); sum('should compute values', () => { assert.is(math.sum(1, 2), 3); assert.is(math.sum(-1, -2), -3); assert.is(math.sum(-1, 1), 0); }); sum.run(); // --- const div = suite('div'); div('should be a function', () => { assert.type(math.div, 'function'); }); div('should compute values', () => { assert.is(math.div(1, 2), 0.5); assert.is(math.div(-1, -2), 0.5); assert.is(math.div(-1, 1), -1); }); div.run(); // --- const mod = suite('mod'); mod('should be a function', () => { assert.type(math.mod, 'function'); }); mod('should compute values', () => { assert.is(math.mod(1, 2), 1); assert.is(math.mod(-3, -2), -1); assert.is(math.mod(7, 4), 3); }); mod.run(); uvu-0.5.3/examples/esm.dual/tests/utils.js000066400000000000000000000015551416513034700205360ustar00rootroot00000000000000import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as utils from '../src/utils.js'; const capitalize = suite('capitalize'); capitalize('should be a function', () => { assert.type(utils.capitalize, 'function'); }); capitalize('should capitalize a word', () => { assert.is(utils.capitalize('hello'), 'Hello'); }); capitalize('should only capitalize the 1st word', () => { assert.is(utils.capitalize('foo bar'), 'Foo bar'); }); capitalize.run(); // --- const dashify = suite('dashify'); dashify('should be a function', () => { assert.type(utils.dashify, 'function'); }); dashify('should replace camelCase with dash-case', () => { assert.is(utils.dashify('fooBar'), 'foo-bar'); assert.is(utils.dashify('FooBar'), 'foo-bar'); }); dashify('should enforce lowercase', () => { assert.is(utils.dashify('foobar'), 'foobar'); }); dashify.run(); uvu-0.5.3/examples/esm.loader/000077500000000000000000000000001416513034700162115ustar00rootroot00000000000000uvu-0.5.3/examples/esm.loader/package.json000066400000000000000000000002161416513034700204760ustar00rootroot00000000000000{ "private": true, "scripts": { "test": "uvu -r esm tests" }, "devDependencies": { "esm": "3.2.25", "uvu": "^0.5.0" } } uvu-0.5.3/examples/esm.loader/readme.md000066400000000000000000000010631416513034700177700ustar00rootroot00000000000000# Example: esm.loader Please read [/docs/esm](/docs/esm.md) for full details & comparisons. ## Why This example makes use of the [`esm`](https://npmjs.com/package/esm) module, which rewrites all ESM syntax to CommonJS on the fly. ## Highlights * Use ESM within regular `.js` files * Works in all versions of Node.js
Because the `esm` loader is invoked – never the native behavior. * Solves CommonJS <-> ESM interop issues
A significant portion of the npm ecosystem is still CommonJS-only. ## License MIT © [Luke Edwards](https://lukeed.com) uvu-0.5.3/examples/esm.loader/src/000077500000000000000000000000001416513034700170005ustar00rootroot00000000000000uvu-0.5.3/examples/esm.loader/src/math.js000066400000000000000000000001541416513034700202670ustar00rootroot00000000000000export const sum = (a, b) => a + b; export const div = (a, b) => a / b; export const mod = (a, b) => a % b; uvu-0.5.3/examples/esm.loader/src/utils.js000066400000000000000000000002731416513034700205000ustar00rootroot00000000000000export function capitalize(str) { return str[0].toUpperCase() + str.substring(1); } export function dashify(str) { return str.replace(/([a-zA-Z])(?=[A-Z\d])/g, '$1-').toLowerCase(); } uvu-0.5.3/examples/esm.loader/tests/000077500000000000000000000000001416513034700173535ustar00rootroot00000000000000uvu-0.5.3/examples/esm.loader/tests/math.js000066400000000000000000000015701416513034700206450ustar00rootroot00000000000000import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as math from '../src/math'; const sum = suite('sum'); sum('should be a function', () => { assert.type(math.sum, 'function'); }); sum('should compute values', () => { assert.is(math.sum(1, 2), 3); assert.is(math.sum(-1, -2), -3); assert.is(math.sum(-1, 1), 0); }); sum.run(); // --- const div = suite('div'); div('should be a function', () => { assert.type(math.div, 'function'); }); div('should compute values', () => { assert.is(math.div(1, 2), 0.5); assert.is(math.div(-1, -2), 0.5); assert.is(math.div(-1, 1), -1); }); div.run(); // --- const mod = suite('mod'); mod('should be a function', () => { assert.type(math.mod, 'function'); }); mod('should compute values', () => { assert.is(math.mod(1, 2), 1); assert.is(math.mod(-3, -2), -1); assert.is(math.mod(7, 4), 3); }); mod.run(); uvu-0.5.3/examples/esm.loader/tests/utils.js000066400000000000000000000015521416513034700210540ustar00rootroot00000000000000import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as utils from '../src/utils'; const capitalize = suite('capitalize'); capitalize('should be a function', () => { assert.type(utils.capitalize, 'function'); }); capitalize('should capitalize a word', () => { assert.is(utils.capitalize('hello'), 'Hello'); }); capitalize('should only capitalize the 1st word', () => { assert.is(utils.capitalize('foo bar'), 'Foo bar'); }); capitalize.run(); // --- const dashify = suite('dashify'); dashify('should be a function', () => { assert.type(utils.dashify, 'function'); }); dashify('should replace camelCase with dash-case', () => { assert.is(utils.dashify('fooBar'), 'foo-bar'); assert.is(utils.dashify('FooBar'), 'foo-bar'); }); dashify('should enforce lowercase', () => { assert.is(utils.dashify('foobar'), 'foobar'); }); dashify.run(); uvu-0.5.3/examples/esm.mjs/000077500000000000000000000000001416513034700155345ustar00rootroot00000000000000uvu-0.5.3/examples/esm.mjs/package.json000066400000000000000000000001621416513034700200210ustar00rootroot00000000000000{ "private": true, "scripts": { "test": "uvu tests" }, "devDependencies": { "uvu": "^0.5.0" } } uvu-0.5.3/examples/esm.mjs/readme.md000066400000000000000000000007011416513034700173110ustar00rootroot00000000000000# Example: esm.mjs Please read [/docs/esm](/docs/esm.md) for full details & comparisons. ## Why This example makes use of the native ESM loader – available in version Node 12 and later! ## Highlights * Uses native ESM via the `.mjs` file extension
This is the Node's default behavior. * Define `import` statements with full file paths
Required by Node.js whenever ESM in use. ## License MIT © [Luke Edwards](https://lukeed.com) uvu-0.5.3/examples/esm.mjs/src/000077500000000000000000000000001416513034700163235ustar00rootroot00000000000000uvu-0.5.3/examples/esm.mjs/src/math.mjs000066400000000000000000000001541416513034700177670ustar00rootroot00000000000000export const sum = (a, b) => a + b; export const div = (a, b) => a / b; export const mod = (a, b) => a % b; uvu-0.5.3/examples/esm.mjs/src/utils.mjs000066400000000000000000000002731416513034700202000ustar00rootroot00000000000000export function capitalize(str) { return str[0].toUpperCase() + str.substring(1); } export function dashify(str) { return str.replace(/([a-zA-Z])(?=[A-Z\d])/g, '$1-').toLowerCase(); } uvu-0.5.3/examples/esm.mjs/tests/000077500000000000000000000000001416513034700166765ustar00rootroot00000000000000uvu-0.5.3/examples/esm.mjs/tests/math.mjs000066400000000000000000000015741416513034700203510ustar00rootroot00000000000000import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as math from '../src/math.mjs'; const sum = suite('sum'); sum('should be a function', () => { assert.type(math.sum, 'function'); }); sum('should compute values', () => { assert.is(math.sum(1, 2), 3); assert.is(math.sum(-1, -2), -3); assert.is(math.sum(-1, 1), 0); }); sum.run(); // --- const div = suite('div'); div('should be a function', () => { assert.type(math.div, 'function'); }); div('should compute values', () => { assert.is(math.div(1, 2), 0.5); assert.is(math.div(-1, -2), 0.5); assert.is(math.div(-1, 1), -1); }); div.run(); // --- const mod = suite('mod'); mod('should be a function', () => { assert.type(math.mod, 'function'); }); mod('should compute values', () => { assert.is(math.mod(1, 2), 1); assert.is(math.mod(-3, -2), -1); assert.is(math.mod(7, 4), 3); }); mod.run(); uvu-0.5.3/examples/esm.mjs/tests/utils.mjs000066400000000000000000000015561416513034700205600ustar00rootroot00000000000000import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as utils from '../src/utils.mjs'; const capitalize = suite('capitalize'); capitalize('should be a function', () => { assert.type(utils.capitalize, 'function'); }); capitalize('should capitalize a word', () => { assert.is(utils.capitalize('hello'), 'Hello'); }); capitalize('should only capitalize the 1st word', () => { assert.is(utils.capitalize('foo bar'), 'Foo bar'); }); capitalize.run(); // --- const dashify = suite('dashify'); dashify('should be a function', () => { assert.type(utils.dashify, 'function'); }); dashify('should replace camelCase with dash-case', () => { assert.is(utils.dashify('fooBar'), 'foo-bar'); assert.is(utils.dashify('FooBar'), 'foo-bar'); }); dashify('should enforce lowercase', () => { assert.is(utils.dashify('foobar'), 'foobar'); }); dashify.run(); uvu-0.5.3/examples/monorepo/000077500000000000000000000000001416513034700160165ustar00rootroot00000000000000uvu-0.5.3/examples/monorepo/package.json000066400000000000000000000001741416513034700203060ustar00rootroot00000000000000{ "private": true, "scripts": { "test": "uvu packages tests" }, "devDependencies": { "uvu": "^0.0.18" } } uvu-0.5.3/examples/monorepo/packages/000077500000000000000000000000001416513034700175745ustar00rootroot00000000000000uvu-0.5.3/examples/monorepo/packages/math/000077500000000000000000000000001416513034700205255ustar00rootroot00000000000000uvu-0.5.3/examples/monorepo/packages/math/package.json000066400000000000000000000001041416513034700230060ustar00rootroot00000000000000{ "private": true, "version": "0.0.0", "name": "@demo/math" } uvu-0.5.3/examples/monorepo/packages/math/src/000077500000000000000000000000001416513034700213145ustar00rootroot00000000000000uvu-0.5.3/examples/monorepo/packages/math/src/index.js000066400000000000000000000001351416513034700227600ustar00rootroot00000000000000exports.sum = (a, b) => a + b; exports.div = (a, b) => a / b; exports.mod = (a, b) => a % b; uvu-0.5.3/examples/monorepo/packages/math/tests/000077500000000000000000000000001416513034700216675ustar00rootroot00000000000000uvu-0.5.3/examples/monorepo/packages/math/tests/index.js000066400000000000000000000015701416513034700233370ustar00rootroot00000000000000const { suite } = require('uvu'); const assert = require('uvu/assert'); const math = require('../src'); const sum = suite('sum'); sum('should be a function', () => { assert.type(math.sum, 'function'); }); sum('should compute values', () => { assert.is(math.sum(1, 2), 3); assert.is(math.sum(-1, -2), -3); assert.is(math.sum(-1, 1), 0); }); sum.run(); // --- const div = suite('div'); div('should be a function', () => { assert.type(math.div, 'function'); }); div('should compute values', () => { assert.is(math.div(1, 2), 0.5); assert.is(math.div(-1, -2), 0.5); assert.is(math.div(-1, 1), -1); }); div.run(); // --- const mod = suite('mod'); mod('should be a function', () => { assert.type(math.mod, 'function'); }); mod('should compute values', () => { assert.is(math.mod(1, 2), 1); assert.is(math.mod(-3, -2), -1); assert.is(math.mod(7, 4), 3); }); mod.run(); uvu-0.5.3/examples/monorepo/packages/utils/000077500000000000000000000000001416513034700207345ustar00rootroot00000000000000uvu-0.5.3/examples/monorepo/packages/utils/package.json000066400000000000000000000001051416513034700232160ustar00rootroot00000000000000{ "private": true, "version": "0.0.0", "name": "@demo/utils" } uvu-0.5.3/examples/monorepo/packages/utils/src/000077500000000000000000000000001416513034700215235ustar00rootroot00000000000000uvu-0.5.3/examples/monorepo/packages/utils/src/index.js000066400000000000000000000003031416513034700231640ustar00rootroot00000000000000exports.capitalize = function (str) { return str[0].toUpperCase() + str.substring(1); } exports.dashify = function (str) { return str.replace(/([a-zA-Z])(?=[A-Z\d])/g, '$1-').toLowerCase(); } uvu-0.5.3/examples/monorepo/packages/utils/tests/000077500000000000000000000000001416513034700220765ustar00rootroot00000000000000uvu-0.5.3/examples/monorepo/packages/utils/tests/index.js000066400000000000000000000015511416513034700235450ustar00rootroot00000000000000const { suite } = require('uvu'); const assert = require('uvu/assert'); const utils = require('../src'); const capitalize = suite('capitalize'); capitalize('should be a function', () => { assert.type(utils.capitalize, 'function'); }); capitalize('should capitalize a word', () => { assert.is(utils.capitalize('hello'), 'Hello'); }); capitalize('should only capitalize the 1st word', () => { assert.is(utils.capitalize('foo bar'), 'Foo bar'); }); capitalize.run(); // --- const dashify = suite('dashify'); dashify('should be a function', () => { assert.type(utils.dashify, 'function'); }); dashify('should replace camelCase with dash-case', () => { assert.is(utils.dashify('fooBar'), 'foo-bar'); assert.is(utils.dashify('FooBar'), 'foo-bar'); }); dashify('should enforce lowercase', () => { assert.is(utils.dashify('foobar'), 'foobar'); }); dashify.run(); uvu-0.5.3/examples/preact/000077500000000000000000000000001416513034700154365ustar00rootroot00000000000000uvu-0.5.3/examples/preact/.babelrc.json000066400000000000000000000002261416513034700200010ustar00rootroot00000000000000{ "plugins": [ "@babel/plugin-syntax-jsx", ["@babel/plugin-transform-react-jsx", { "pragmaFrag": "Fragment", "pragma": "h" }] ] } uvu-0.5.3/examples/preact/package.json000066400000000000000000000006151416513034700177260ustar00rootroot00000000000000{ "private": true, "scripts": { "test": "uvu tests -r esm -r @babel/register -i setup" }, "dependencies": { "preact": "^10.4.5" }, "devDependencies": { "@babel/core": "7.10.4", "@babel/register": "7.10.4", "@babel/plugin-syntax-jsx": "7.10.4", "@babel/plugin-transform-react-jsx": "7.10.4", "esm": "3.2.25", "jsdom": "16.3.0", "uvu": "^0.2.0" } } uvu-0.5.3/examples/preact/src/000077500000000000000000000000001416513034700162255ustar00rootroot00000000000000uvu-0.5.3/examples/preact/src/Count.jsx000066400000000000000000000005771416513034700200540ustar00rootroot00000000000000import { h, Fragment } from 'preact'; import { useState } from 'preact/hooks'; export default function Counter(props) { const { count=5 } = props; const [value, setValue] = useState(count); return ( <> {value} ); } uvu-0.5.3/examples/preact/tests/000077500000000000000000000000001416513034700166005ustar00rootroot00000000000000uvu-0.5.3/examples/preact/tests/Count.js000066400000000000000000000030271416513034700202300ustar00rootroot00000000000000import { test } from 'uvu'; import * as assert from 'uvu/assert'; import * as ENV from './setup/env'; // Relies on `@babel/register` import Count from '../src/Count.jsx'; test.before(ENV.setup); test.before.each(ENV.reset); test('should render with "5" by default', () => { const { container } = ENV.render(Count); assert.snapshot( container.innerHTML, `5` ); }); test('should accept custom `count` prop', () => { const { container } = ENV.render(Count, { count: 99 }); assert.snapshot( container.innerHTML, `99` ); }); test('should increment count after `button#incr` click', async () => { const { container } = ENV.render(Count); assert.snapshot( container.innerHTML, `5` ); await ENV.fire( container.querySelector('#incr'), 'click' ); assert.snapshot( container.innerHTML, `6` ); }); test('should decrement count after `button#decr` click', async () => { const { container } = ENV.render(Count); assert.snapshot( container.innerHTML, `5` ); await ENV.fire( container.querySelector('#decr'), 'click' ); assert.snapshot( container.innerHTML, `4` ); }); test.run(); uvu-0.5.3/examples/preact/tests/setup/000077500000000000000000000000001416513034700177405ustar00rootroot00000000000000uvu-0.5.3/examples/preact/tests/setup/env.js000066400000000000000000000021531416513034700210670ustar00rootroot00000000000000import { JSDOM } from 'jsdom'; import * as Preact from 'preact'; import { act } from 'preact/test-utils'; const { window } = new JSDOM('
'); export function setup() { // @ts-ignore global.window = window; global.document = window.document; global.navigator = window.navigator; global.getComputedStyle = window.getComputedStyle; global.requestAnimationFrame = null; } export function reset() { window.document.title = ''; window.document.head.innerHTML = ''; window.document.body.innerHTML = '
'; } /** * @typedef RenderOutput * @property container {HTMLElement} * @property component {Preact.VNode} */ /** * @return {RenderOutput} */ export function render(Tag, props = {}) { const container = window.document.querySelector('main'); const component = Preact.h(Tag, props); Preact.render(component, container) return { container, component }; } /** * @param {HTMLElement} elem * @param {String} event * @param {any} [details] */ export async function fire(elem, event, details) { await act(() => { let evt = new window.Event(event, details); elem.dispatchEvent(evt); }); } uvu-0.5.3/examples/puppeteer/000077500000000000000000000000001416513034700161715ustar00rootroot00000000000000uvu-0.5.3/examples/puppeteer/package.json000066400000000000000000000003431416513034700204570ustar00rootroot00000000000000{ "private": true, "type": "module", "scripts": { "start": "sirv src --dev", "test": "uvu tests -i setup" }, "devDependencies": { "puppeteer": "^5.5.0", "sirv-cli": "^1.0.6", "uvu": "^0.5.0" } } uvu-0.5.3/examples/puppeteer/readme.md000066400000000000000000000014141416513034700177500ustar00rootroot00000000000000# Example: Puppeteer This example runs [Puppeteer](https://www.npmjs.com/package/puppeteer) programmatically. In order to invoke and test against source (`/src`) files, this example also uses [`sirv-cli`](https://www.npmjs.com/package/sirv-cli) to run a local file server. Puppeteer connects to this file server to access the `window` globals that `src/math.js` and `src/utils.js` are globally mounted to. > **Note:** Window globals are not required in order for `uvu` to work with `puppeteer`!
You may, for example, load your Preact, Svelte, Vue, ... etc application inside the devserver & interact with it through Puppeteer APIs! ## Setup ```sh $ npm install ``` ## Testing ```sh # start server $ npm start # run tests (2nd terminal) $ npm test ``` ## License MIT uvu-0.5.3/examples/puppeteer/src/000077500000000000000000000000001416513034700167605ustar00rootroot00000000000000uvu-0.5.3/examples/puppeteer/src/app.js000066400000000000000000000001731416513034700200770ustar00rootroot00000000000000import * as math from './math.js'; import * as utils from './utils.js'; window.__MATH__ = math; window.__UTILS__ = utils; uvu-0.5.3/examples/puppeteer/src/index.html000066400000000000000000000003721416513034700207570ustar00rootroot00000000000000 uvu + Puppeteer uvu-0.5.3/examples/puppeteer/src/math.js000066400000000000000000000001541416513034700202470ustar00rootroot00000000000000export const sum = (a, b) => a + b; export const div = (a, b) => a / b; export const mod = (a, b) => a % b; uvu-0.5.3/examples/puppeteer/src/utils.js000066400000000000000000000002731416513034700204600ustar00rootroot00000000000000export function capitalize(str) { return str[0].toUpperCase() + str.substring(1); } export function dashify(str) { return str.replace(/([a-zA-Z])(?=[A-Z\d])/g, '$1-').toLowerCase(); } uvu-0.5.3/examples/puppeteer/tests/000077500000000000000000000000001416513034700173335ustar00rootroot00000000000000uvu-0.5.3/examples/puppeteer/tests/index.js000066400000000000000000000012771416513034700210070ustar00rootroot00000000000000import { test } from 'uvu'; import * as assert from 'uvu/assert'; import * as ENV from './setup/puppeteer.js' test.before(ENV.setup); test.after(ENV.reset); test('can fetch data!', async context => { const data = await context.page.evaluate(() => { return fetch('https://httpbin.org/get').then(r => r.json()); }); assert.type(data, 'object'); assert.is(data.url, 'https://httpbin.org/get'); }); test('can select elements!', async context => { await context.page.goto('http://example.com/'); const text = await context.page.evaluate(() => { return document.querySelector('h1').textContent; }); assert.type(text, 'string'); assert.is(text, 'Example Domain'); }); test.run(); uvu-0.5.3/examples/puppeteer/tests/math.js000066400000000000000000000033751416513034700206320ustar00rootroot00000000000000import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as ENV from './setup/puppeteer.js' const sum = suite('sum'); sum.before(ENV.setup); sum.before.each(ENV.homepage); sum.after(ENV.reset); sum('should be a function', async context => { assert.is( await context.page.evaluate(() => typeof window.__MATH__.sum), 'function' ); }); sum('should compute values', async context => { const { page } = context; assert.is(await page.evaluate(() => window.__MATH__.sum(1, 2)), 3); assert.is(await page.evaluate(() => window.__MATH__.sum(-1, -2)), -3); assert.is(await page.evaluate(() => window.__MATH__.sum(-1, 1)), 0); }); sum.run(); // --- const div = suite('div'); div.before(ENV.setup); div.before.each(ENV.homepage); div.after(ENV.reset); div('should be a function', async context => { assert.is( await context.page.evaluate(() => typeof window.__MATH__.div), 'function' ); }); div('should compute values', async context => { const { page } = context; assert.is(await page.evaluate(() => window.__MATH__.div(1, 2)), 0.5); assert.is(await page.evaluate(() => window.__MATH__.div(-1, -2)), 0.5); assert.is(await page.evaluate(() => window.__MATH__.div(-1, 1)), -1); }); div.run(); // --- const mod = suite('mod'); mod.before(ENV.setup); mod.before.each(ENV.homepage); mod.after(ENV.reset); mod('should be a function', async context => { assert.is( await context.page.evaluate(() => typeof window.__MATH__.mod), 'function' ); }); mod('should compute values', async context => { const { page } = context; assert.is(await page.evaluate(() => window.__MATH__.mod(1, 2)), 1); assert.is(await page.evaluate(() => window.__MATH__.mod(-1, -2)), -1); assert.is(await page.evaluate(() => window.__MATH__.mod(7, 4)), 3); }); mod.run(); uvu-0.5.3/examples/puppeteer/tests/setup/000077500000000000000000000000001416513034700204735ustar00rootroot00000000000000uvu-0.5.3/examples/puppeteer/tests/setup/puppeteer.js000066400000000000000000000007511416513034700230450ustar00rootroot00000000000000import Chrome from 'puppeteer'; // Launch the browser // Add `browser` and `page` to context export async function setup(context) { context.browser = await Chrome.launch(); context.page = await context.browser.newPage(); } // Close everything on suite completion export async function reset(context) { await context.page.close(); await context.browser.close(); } // Navigate to homepage export async function homepage(context) { await context.page.goto('http://localhost:5000'); } uvu-0.5.3/examples/puppeteer/tests/utils.js000066400000000000000000000027571416513034700210440ustar00rootroot00000000000000import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as ENV from './setup/puppeteer.js' const capitalize = suite('capitalize'); capitalize.before(ENV.setup); capitalize.before.each(ENV.homepage); capitalize.after(ENV.reset); capitalize('should be a function', async context => { assert.is( await context.page.evaluate(() => typeof window.__UTILS__.capitalize), 'function' ); }); capitalize('should capitalize a word', async context => { assert.is( await context.page.evaluate(() => window.__UTILS__.capitalize('hello')), 'Hello' ); }); capitalize('should only capitalize the 1st word', async context => { assert.is( await context.page.evaluate(() => window.__UTILS__.capitalize('foo bar')), 'Foo bar' ); }); capitalize.run(); // --- const dashify = suite('dashify'); dashify.before(ENV.setup); dashify.before.each(ENV.homepage); dashify.after(ENV.reset); dashify('should be a function', async context => { assert.is( await context.page.evaluate(() => typeof window.__UTILS__.dashify), 'function' ); }); dashify('should replace camelCase with dash-case', async context => { const { page } = context; assert.is(await page.evaluate(() => window.__UTILS__.dashify('fooBar')), 'foo-bar'); assert.is(await page.evaluate(() => window.__UTILS__.dashify('FooBar')), 'foo-bar'); }); dashify('should ignore lowercase', async context => { const { page } = context; assert.is(await page.evaluate(() => window.__UTILS__.dashify('foobar')), 'foobar'); }); dashify.run(); uvu-0.5.3/examples/solidjs/000077500000000000000000000000001416513034700156275ustar00rootroot00000000000000uvu-0.5.3/examples/solidjs/package.json000066400000000000000000000004211416513034700201120ustar00rootroot00000000000000{ "private": true, "scripts": { "test": "uvu tests -r solid-register" }, "dependencies": { "solid-js": "^1.2.0" }, "devDependencies": { "solid-register": "0.0.5", "solid-testing-library": "0.2.1", "jsdom": "18.0.0", "uvu": "0.5.2" } } uvu-0.5.3/examples/solidjs/src/000077500000000000000000000000001416513034700164165ustar00rootroot00000000000000uvu-0.5.3/examples/solidjs/src/Count.tsx000066400000000000000000000005701416513034700202500ustar00rootroot00000000000000import { createSignal, Component } from 'solid-js' export type CountProps = { count?: number }; export const Count: Component = (props) => { const [value, setValue] = createSignal(props.count ?? 5); return <> {value()} } uvu-0.5.3/examples/solidjs/tests/000077500000000000000000000000001416513034700167715ustar00rootroot00000000000000uvu-0.5.3/examples/solidjs/tests/Count.test.tsx000066400000000000000000000032271416513034700216030ustar00rootroot00000000000000import { test } from 'uvu'; import * as assert from 'uvu/assert'; import { cleanup, render, fireEvent, screen } from 'solid-testing-library'; import { Count } from '../src/Count'; const isInDom = (node: Node): boolean => !!node.parentNode && (node.parentNode === document || isInDom(node.parentNode)); test.after.each(cleanup); test('should render with "5" by default', () => { const { container } = render(() => ); assert.snapshot( container.innerHTML, `5` ); }); test('should accept custom `count` prop', () => { const { container } = render(() => ); assert.snapshot( container.innerHTML, `99` ); }); test('should increment count after `button#incr` click', async () => { const { container } = render(() => ); assert.snapshot( container.innerHTML, `5` ); const button = await screen.findByRole( 'button', { name: '++' } ) assert.ok(isInDom(button)); fireEvent.click(button); assert.snapshot( container.innerHTML, `6` ); }); test('should decrement count after `button#decr` click', async () => { const { container } = render(() => ); assert.snapshot( container.innerHTML, `5` ); const button = await screen.findByRole( 'button', { name: /--/ } ) assert.ok(isInDom(button)); fireEvent.click(button); assert.snapshot( container.innerHTML, `4` ); }); test.run(); uvu-0.5.3/examples/suites/000077500000000000000000000000001416513034700154745ustar00rootroot00000000000000uvu-0.5.3/examples/suites/package.json000066400000000000000000000001631416513034700177620ustar00rootroot00000000000000{ "private": true, "scripts": { "test": "uvu tests" }, "devDependencies": { "uvu": "^0.0.18" } } uvu-0.5.3/examples/suites/src/000077500000000000000000000000001416513034700162635ustar00rootroot00000000000000uvu-0.5.3/examples/suites/src/math.js000066400000000000000000000001351416513034700175510ustar00rootroot00000000000000exports.sum = (a, b) => a + b; exports.div = (a, b) => a / b; exports.mod = (a, b) => a % b; uvu-0.5.3/examples/suites/src/utils.js000066400000000000000000000003031416513034700177550ustar00rootroot00000000000000exports.capitalize = function (str) { return str[0].toUpperCase() + str.substring(1); } exports.dashify = function (str) { return str.replace(/([a-zA-Z])(?=[A-Z\d])/g, '$1-').toLowerCase(); } uvu-0.5.3/examples/suites/tests/000077500000000000000000000000001416513034700166365ustar00rootroot00000000000000uvu-0.5.3/examples/suites/tests/math.js000066400000000000000000000015751416513034700201350ustar00rootroot00000000000000const { suite } = require('uvu'); const assert = require('uvu/assert'); const math = require('../src/math'); const sum = suite('sum'); sum('should be a function', () => { assert.type(math.sum, 'function'); }); sum('should compute values', () => { assert.is(math.sum(1, 2), 3); assert.is(math.sum(-1, -2), -3); assert.is(math.sum(-1, 1), 0); }); sum.run(); // --- const div = suite('div'); div('should be a function', () => { assert.type(math.div, 'function'); }); div('should compute values', () => { assert.is(math.div(1, 2), 0.5); assert.is(math.div(-1, -2), 0.5); assert.is(math.div(-1, 1), -1); }); div.run(); // --- const mod = suite('mod'); mod('should be a function', () => { assert.type(math.mod, 'function'); }); mod('should compute values', () => { assert.is(math.mod(1, 2), 1); assert.is(math.mod(-3, -2), -1); assert.is(math.mod(7, 4), 3); }); mod.run(); uvu-0.5.3/examples/suites/tests/utils.js000066400000000000000000000015571416513034700203440ustar00rootroot00000000000000const { suite } = require('uvu'); const assert = require('uvu/assert'); const utils = require('../src/utils'); const capitalize = suite('capitalize'); capitalize('should be a function', () => { assert.type(utils.capitalize, 'function'); }); capitalize('should capitalize a word', () => { assert.is(utils.capitalize('hello'), 'Hello'); }); capitalize('should only capitalize the 1st word', () => { assert.is(utils.capitalize('foo bar'), 'Foo bar'); }); capitalize.run(); // --- const dashify = suite('dashify'); dashify('should be a function', () => { assert.type(utils.dashify, 'function'); }); dashify('should replace camelCase with dash-case', () => { assert.is(utils.dashify('fooBar'), 'foo-bar'); assert.is(utils.dashify('FooBar'), 'foo-bar'); }); dashify('should enforce lowercase', () => { assert.is(utils.dashify('foobar'), 'foobar'); }); dashify.run(); uvu-0.5.3/examples/supertest/000077500000000000000000000000001416513034700162165ustar00rootroot00000000000000uvu-0.5.3/examples/supertest/package.json000066400000000000000000000002421416513034700205020ustar00rootroot00000000000000{ "private": true, "scripts": { "test": "uvu tests" }, "devDependencies": { "polka": "0.5.2", "supertest": "4.0.2", "uvu": "^0.2.0" } } uvu-0.5.3/examples/supertest/src/000077500000000000000000000000001416513034700170055ustar00rootroot00000000000000uvu-0.5.3/examples/supertest/src/index.js000066400000000000000000000004231416513034700204510ustar00rootroot00000000000000const polka = require('polka'); const Users = require('./users'); const Pets = require('./pets'); module.exports = ( polka() .use('/pets', Pets) .use('/users', Users) .get('/', (req, res) => { res.setHeader('Content-Type', 'text/plain'); res.end('OK'); }) ); uvu-0.5.3/examples/supertest/src/pets.js000066400000000000000000000005131416513034700203150ustar00rootroot00000000000000const polka = require('polka'); module.exports = ( polka() .get('/', (req, res) => { res.end(`GET /pets :: ${req.url}`); }) .get('/:id', (req, res) => { res.end(`GET /pets/:id :: ${req.url}`); }) .put('/:id', (req, res) => { res.statusCode = 201; // why not? res.end(`PUT /pets/:id :: ${req.url}`); }) ); uvu-0.5.3/examples/supertest/src/users.js000066400000000000000000000005161416513034700205060ustar00rootroot00000000000000const polka = require('polka'); module.exports = ( polka() .get('/', (req, res) => { res.end(`GET /users :: ${req.url}`); }) .get('/:id', (req, res) => { res.end(`GET /users/:id :: ${req.url}`); }) .put('/:id', (req, res) => { res.statusCode = 201; // why not? res.end(`PUT /users/:id :: ${req.url}`); }) ); uvu-0.5.3/examples/supertest/tests/000077500000000000000000000000001416513034700173605ustar00rootroot00000000000000uvu-0.5.3/examples/supertest/tests/index.js000066400000000000000000000020041416513034700210210ustar00rootroot00000000000000const { test } = require('uvu'); const assert = require('uvu/assert'); const request = require('supertest'); const App = require('../src'); test('should export Polka instance', () => { assert.is(App.constructor.name, 'Polka'); assert.type(App.handler, 'function'); assert.is(App.server, undefined); assert.type(App.get, 'function'); assert.type(App.head, 'function'); assert.type(App.patch, 'function'); assert.type(App.connect, 'function'); assert.type(App.delete, 'function'); assert.type(App.post, 'function'); }); // Option 1: Use `supertest` assertions test('should receive "OK" for "GET /" route', async () => { await request(App.handler) .get('/') .expect('Content-Type', /text\/plain/) .expect(200, 'OK') }); // Option 2: Save `supertest` request; assert directly test('should receive "OK" for "GET /" route', async () => { let res = await request(App.handler).get('/'); assert.is(res.header['content-type'], 'text/plain'); assert.is(res.status, 200); assert.is(res.text, 'OK'); }); test.run(); uvu-0.5.3/examples/supertest/tests/pets.js000066400000000000000000000013651416513034700206760ustar00rootroot00000000000000const { test } = require('uvu'); const request = require('supertest'); const App = require('../src'); /** * NOTE: File uses `supertest` only * @see tests/users.js for `uvu/assert` combo. */ test('GET /pets', async () => { await request(App.handler) .get('/pets') .expect(200, 'GET /pets :: /') }); test('GET /pets/:id', async () => { await request(App.handler) .get('/pets/123') .expect(200, `GET /pets/:id :: /123`) await request(App.handler) .get('/pets/dog') .expect(200, `GET /pets/:id :: /dog`) }); test('PUT /pets/:id', async () => { await request(App.handler) .put('/pets/123') .expect(201, `PUT /pets/:id :: /123`) await request(App.handler) .put('/pets/dog') .expect(201, `PUT /pets/:id :: /dog`) }); test.run(); uvu-0.5.3/examples/supertest/tests/users.js000066400000000000000000000020461416513034700210610ustar00rootroot00000000000000const { test } = require('uvu'); const request = require('supertest'); const assert = require('uvu/assert'); const App = require('../src'); /** * NOTE: File uses `supertest` w/ `uvu/assert` together * @see tests/pets.js for `supertest` usage only */ test('GET /users', async () => { let res = await request(App.handler).get('/users'); assert.is(res.text, 'GET /users :: /'); assert.is(res.status, 200); }); test('GET /users/:id', async () => { let res1 = await request(App.handler).get('/users/123'); assert.is(res1.text, `GET /users/:id :: /123`); assert.is(res1.status, 200); let res2 = await request(App.handler).get('/users/dog'); assert.is(res2.text, `GET /users/:id :: /dog`); assert.is(res2.status, 200); }); test('PUT /users/:id', async () => { let res1 = await request(App.handler).put('/users/123'); assert.is(res1.text, `PUT /users/:id :: /123`); assert.is(res1.status, 201); let res2 = await request(App.handler).put('/users/dog'); assert.is(res2.text, `PUT /users/:id :: /dog`); assert.is(res2.status, 201); }); test.run(); uvu-0.5.3/examples/svelte/000077500000000000000000000000001416513034700154625ustar00rootroot00000000000000uvu-0.5.3/examples/svelte/package.json000066400000000000000000000003361416513034700177520ustar00rootroot00000000000000{ "private": true, "scripts": { "test": "uvu tests -r esm -r tests/setup/register -i setup" }, "devDependencies": { "esm": "3.2.25", "jsdom": "16.3.0", "svelte": "3.24.0", "uvu": "^0.2.0" } } uvu-0.5.3/examples/svelte/src/000077500000000000000000000000001416513034700162515ustar00rootroot00000000000000uvu-0.5.3/examples/svelte/src/Count.svelte000066400000000000000000000003641416513034700205700ustar00rootroot00000000000000 {count} uvu-0.5.3/examples/svelte/tests/000077500000000000000000000000001416513034700166245ustar00rootroot00000000000000uvu-0.5.3/examples/svelte/tests/Count.js000066400000000000000000000030451416513034700202540ustar00rootroot00000000000000import { test } from 'uvu'; import * as assert from 'uvu/assert'; import * as ENV from './setup/env'; // Relies on `setup/register` import Count from '../src/Count.svelte'; test.before(ENV.setup); test.before.each(ENV.reset); test('should render with "5" by default', () => { const { container } = ENV.render(Count); assert.snapshot( container.innerHTML, ` 5 ` ); }); test('should accept custom `count` prop', () => { const { container } = ENV.render(Count, { count: 99 }); assert.snapshot( container.innerHTML, ` 99 ` ); }); test('should increment count after `button#incr` click', async () => { const { container } = ENV.render(Count); assert.snapshot( container.innerHTML, ` 5 ` ); await ENV.fire( container.querySelector('#incr'), 'click' ); assert.snapshot( container.innerHTML, ` 6 ` ); }); test('should decrement count after `button#decr` click', async () => { const { container } = ENV.render(Count); assert.snapshot( container.innerHTML, ` 5 ` ); await ENV.fire( container.querySelector('#decr'), 'click' ); assert.snapshot( container.innerHTML, ` 4 ` ); }); test.run(); uvu-0.5.3/examples/svelte/tests/setup/000077500000000000000000000000001416513034700177645ustar00rootroot00000000000000uvu-0.5.3/examples/svelte/tests/setup/env.js000066400000000000000000000020721416513034700211130ustar00rootroot00000000000000import { JSDOM } from 'jsdom'; import { tick } from 'svelte'; const { window } = new JSDOM(''); export function setup() { // @ts-ignore global.window = window; global.document = window.document; global.navigator = window.navigator; global.getComputedStyle = window.getComputedStyle; global.requestAnimationFrame = null; } export function reset() { window.document.title = ''; window.document.head.innerHTML = ''; window.document.body.innerHTML = ''; } /** * @typedef RenderOutput * @property container {HTMLElement} * @property component {import('svelte').SvelteComponent} */ /** * @return {RenderOutput} */ export function render(Tag, props = {}) { Tag = Tag.default || Tag; const container = window.document.body; const component = new Tag({ props, target: container }); return { container, component }; } /** * @param {HTMLElement} elem * @param {String} event * @param {any} [details] * @returns Promise */ export function fire(elem, event, details) { let evt = new window.Event(event, details); elem.dispatchEvent(evt); return tick(); } uvu-0.5.3/examples/svelte/tests/setup/register.js000066400000000000000000000014621416513034700221510ustar00rootroot00000000000000const { parse } = require('path'); const { compile } = require('svelte/compiler'); function transform(hook, source, filename) { const { name } = parse(filename); const { js, warnings } = compile(source, { name: name[0].toUpperCase() + name.substring(1), format: 'cjs', filename, }); warnings.forEach(warning => { console.warn(`\nSvelte Warning in ${warning.filename}:`); console.warn(warning.message); console.warn(warning.frame); }); return hook(js.code, filename); } const loadJS = require.extensions['.js']; // Runtime DOM hook for require("*.svelte") files // Note: for SSR/Node.js hook, use `svelte/register` require.extensions['.svelte'] = function (mod, filename) { const orig = mod._compile.bind(mod); mod._compile = code => transform(orig, code, filename); loadJS(mod, filename); } uvu-0.5.3/examples/typescript.module/000077500000000000000000000000001416513034700176525ustar00rootroot00000000000000uvu-0.5.3/examples/typescript.module/.gitignore000066400000000000000000000000071416513034700216370ustar00rootroot00000000000000/build uvu-0.5.3/examples/typescript.module/loadr.mjs000066400000000000000000000001611416513034700214640ustar00rootroot00000000000000// NOTE: used for "test:alt" script only // @see https://github.com/lukeed/loadr export const loaders = ['tsm']; uvu-0.5.3/examples/typescript.module/package.json000066400000000000000000000003601416513034700221370ustar00rootroot00000000000000{ "private": true, "type": "module", "scripts": { "test": "tsm node_modules/uvu/bin.js tests", "test:alt": "loadr -- uvu tests" }, "devDependencies": { "loadr": "^0.1.1", "tsm": "^2.0.0", "uvu": "^0.5.1" } } uvu-0.5.3/examples/typescript.module/src/000077500000000000000000000000001416513034700204415ustar00rootroot00000000000000uvu-0.5.3/examples/typescript.module/src/math.js000066400000000000000000000004041416513034700217260ustar00rootroot00000000000000/** * @param {number} a * @param {number} b */ export const sum = (a, b) => a + b; /** * @param {number} a * @param {number} b */ export const div = (a, b) => a / b; /** * @param {number} a * @param {number} b */ export const mod = (a, b) => a % b; uvu-0.5.3/examples/typescript.module/src/utils.js000066400000000000000000000003711416513034700221400ustar00rootroot00000000000000/** * @param {string} str */ export function capitalize(str) { return str[0].toUpperCase() + str.substring(1); } /** * @param {string} str */ export function dashify(str) { return str.replace(/([a-zA-Z])(?=[A-Z\d])/g, '$1-').toLowerCase(); } uvu-0.5.3/examples/typescript.module/tests/000077500000000000000000000000001416513034700210145ustar00rootroot00000000000000uvu-0.5.3/examples/typescript.module/tests/math.ts000066400000000000000000000011241416513034700223130ustar00rootroot00000000000000import { test } from 'uvu'; import * as assert from 'uvu/assert'; import * as math from '../src/math.js'; test('sum', () => { assert.type(math.sum, 'function'); assert.is(math.sum(1, 2), 3); assert.is(math.sum(-1, -2), -3); assert.is(math.sum(-1, 1), 0); }); test('div', () => { assert.type(math.div, 'function'); assert.is(math.div(1, 2), 0.5); assert.is(math.div(-1, -2), 0.5); assert.is(math.div(-1, 1), -1); }); test('mod', () => { assert.type(math.mod, 'function'); assert.is(math.mod(1, 2), 1); assert.is(math.mod(-3, -2), -1); assert.is(math.mod(7, 4), 3); }); test.run(); uvu-0.5.3/examples/typescript.module/tests/utils.ts000066400000000000000000000007761416513034700225360ustar00rootroot00000000000000import { test } from 'uvu'; import * as assert from 'uvu/assert'; import * as utils from '../src/utils.js'; test('capitalize', () => { assert.type(utils.capitalize, 'function'); assert.is(utils.capitalize('hello'), 'Hello'); assert.is(utils.capitalize('foo bar'), 'Foo bar'); }); test('dashify', () => { assert.type(utils.dashify, 'function'); assert.is(utils.dashify('fooBar'), 'foo-bar'); assert.is(utils.dashify('FooBar'), 'foo-bar'); assert.is(utils.dashify('foobar'), 'foobar'); }); test.run(); uvu-0.5.3/examples/typescript.module/tsconfig.json000066400000000000000000000005651416513034700223670ustar00rootroot00000000000000{ "compilerOptions": { "strict": true, "allowJs": true, "target": "esnext", "noImplicitAny": true, "moduleResolution": "node", "forceConsistentCasingInFileNames": true, "alwaysStrict": true, "module": "esnext", "checkJs": true, "noEmit": true }, "include": [ "src", "tests" ], "exclude": [ "node_modules" ] } uvu-0.5.3/examples/typescript/000077500000000000000000000000001416513034700163665ustar00rootroot00000000000000uvu-0.5.3/examples/typescript/.gitignore000066400000000000000000000000071416513034700203530ustar00rootroot00000000000000/build uvu-0.5.3/examples/typescript/package.json000066400000000000000000000002161416513034700206530ustar00rootroot00000000000000{ "private": true, "scripts": { "test": "uvu -r tsm tests" }, "devDependencies": { "tsm": "^2.0.0", "uvu": "^0.5.1" } } uvu-0.5.3/examples/typescript/src/000077500000000000000000000000001416513034700171555ustar00rootroot00000000000000uvu-0.5.3/examples/typescript/src/math.ts000066400000000000000000000003211416513034700204520ustar00rootroot00000000000000export function sum(a: number, b: number): number { return a + b; } export function div(a: number, b: number): number { return a / b; } export function mod(a: number, b: number): number { return a % b; } uvu-0.5.3/examples/typescript/src/utils.ts000066400000000000000000000003331416513034700206640ustar00rootroot00000000000000export function capitalize(str: string): string { return str[0].toUpperCase() + str.substring(1); } export function dashify(str: string): string { return str.replace(/([a-zA-Z])(?=[A-Z\d])/g, '$1-').toLowerCase(); } uvu-0.5.3/examples/typescript/tests/000077500000000000000000000000001416513034700175305ustar00rootroot00000000000000uvu-0.5.3/examples/typescript/tests/math.ts000066400000000000000000000011211416513034700210240ustar00rootroot00000000000000import { test } from 'uvu'; import * as assert from 'uvu/assert'; import * as math from '../src/math'; test('sum', () => { assert.type(math.sum, 'function'); assert.is(math.sum(1, 2), 3); assert.is(math.sum(-1, -2), -3); assert.is(math.sum(-1, 1), 0); }); test('div', () => { assert.type(math.div, 'function'); assert.is(math.div(1, 2), 0.5); assert.is(math.div(-1, -2), 0.5); assert.is(math.div(-1, 1), -1); }); test('mod', () => { assert.type(math.mod, 'function'); assert.is(math.mod(1, 2), 1); assert.is(math.mod(-3, -2), -1); assert.is(math.mod(7, 4), 3); }); test.run(); uvu-0.5.3/examples/typescript/tests/utils.ts000066400000000000000000000007731416513034700212470ustar00rootroot00000000000000import { test } from 'uvu'; import * as assert from 'uvu/assert'; import * as utils from '../src/utils'; test('capitalize', () => { assert.type(utils.capitalize, 'function'); assert.is(utils.capitalize('hello'), 'Hello'); assert.is(utils.capitalize('foo bar'), 'Foo bar'); }); test('dashify', () => { assert.type(utils.dashify, 'function'); assert.is(utils.dashify('fooBar'), 'foo-bar'); assert.is(utils.dashify('FooBar'), 'foo-bar'); assert.is(utils.dashify('foobar'), 'foobar'); }); test.run(); uvu-0.5.3/examples/typescript/tsconfig.json000066400000000000000000000005651416513034700211030ustar00rootroot00000000000000{ "compilerOptions": { "strict": true, "allowJs": true, "target": "esnext", "noImplicitAny": true, "moduleResolution": "node", "forceConsistentCasingInFileNames": true, "alwaysStrict": true, "module": "esnext", "checkJs": true, "noEmit": true }, "include": [ "src", "tests" ], "exclude": [ "node_modules" ] } uvu-0.5.3/examples/watch/000077500000000000000000000000001416513034700152665ustar00rootroot00000000000000uvu-0.5.3/examples/watch/package.json000066400000000000000000000002761416513034700175610ustar00rootroot00000000000000{ "private": true, "scripts": { "test": "uvu tests", "watch": "watchlist src tests -- yarn test" }, "devDependencies": { "uvu": "^0.2.0", "watchlist": "^0.2.1" } } uvu-0.5.3/examples/watch/src/000077500000000000000000000000001416513034700160555ustar00rootroot00000000000000uvu-0.5.3/examples/watch/src/math.js000066400000000000000000000001351416513034700173430ustar00rootroot00000000000000exports.sum = (a, b) => a + b; exports.div = (a, b) => a / b; exports.mod = (a, b) => a % b; uvu-0.5.3/examples/watch/src/utils.js000066400000000000000000000003031416513034700175470ustar00rootroot00000000000000exports.capitalize = function (str) { return str[0].toUpperCase() + str.substring(1); } exports.dashify = function (str) { return str.replace(/([a-zA-Z])(?=[A-Z\d])/g, '$1-').toLowerCase(); } uvu-0.5.3/examples/watch/tests/000077500000000000000000000000001416513034700164305ustar00rootroot00000000000000uvu-0.5.3/examples/watch/tests/math.js000066400000000000000000000015751416513034700177270ustar00rootroot00000000000000const { suite } = require('uvu'); const assert = require('uvu/assert'); const math = require('../src/math'); const sum = suite('sum'); sum('should be a function', () => { assert.type(math.sum, 'function'); }); sum('should compute values', () => { assert.is(math.sum(1, 2), 3); assert.is(math.sum(-1, -2), -3); assert.is(math.sum(-1, 1), 0); }); sum.run(); // --- const div = suite('div'); div('should be a function', () => { assert.type(math.div, 'function'); }); div('should compute values', () => { assert.is(math.div(1, 2), 0.5); assert.is(math.div(-1, -2), 0.5); assert.is(math.div(-1, 1), -1); }); div.run(); // --- const mod = suite('mod'); mod('should be a function', () => { assert.type(math.mod, 'function'); }); mod('should compute values', () => { assert.is(math.mod(1, 2), 1); assert.is(math.mod(-3, -2), -1); assert.is(math.mod(7, 4), 3); }); mod.run(); uvu-0.5.3/examples/watch/tests/utils.js000066400000000000000000000015571416513034700201360ustar00rootroot00000000000000const { suite } = require('uvu'); const assert = require('uvu/assert'); const utils = require('../src/utils'); const capitalize = suite('capitalize'); capitalize('should be a function', () => { assert.type(utils.capitalize, 'function'); }); capitalize('should capitalize a word', () => { assert.is(utils.capitalize('hello'), 'Hello'); }); capitalize('should only capitalize the 1st word', () => { assert.is(utils.capitalize('foo bar'), 'Foo bar'); }); capitalize.run(); // --- const dashify = suite('dashify'); dashify('should be a function', () => { assert.type(utils.dashify, 'function'); }); dashify('should replace camelCase with dash-case', () => { assert.is(utils.dashify('fooBar'), 'foo-bar'); assert.is(utils.dashify('FooBar'), 'foo-bar'); }); dashify('should enforce lowercase', () => { assert.is(utils.dashify('foobar'), 'foobar'); }); dashify.run(); uvu-0.5.3/license000066400000000000000000000021321416513034700137050ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) Luke Edwards (lukeed.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. uvu-0.5.3/package.json000066400000000000000000000030701416513034700146300ustar00rootroot00000000000000{ "name": "uvu", "version": "0.5.3", "repository": "lukeed/uvu", "description": "uvu is an extremely fast and lightweight test runner for Node.js and the browser", "module": "dist/index.mjs", "main": "dist/index.js", "types": "index.d.ts", "license": "MIT", "bin": { "uvu": "bin.js" }, "exports": { ".": { "require": "./dist/index.js", "import": "./dist/index.mjs" }, "./assert": { "require": "./assert/index.js", "import": "./assert/index.mjs" }, "./diff": { "require": "./diff/index.js", "import": "./diff/index.mjs" }, "./parse": { "require": "./parse/index.js", "import": "./parse/index.mjs" }, "./run": { "require": "./run/index.js", "import": "./run/index.mjs" } }, "files": [ "*.js", "*.d.ts", "assert", "parse", "diff", "dist", "run" ], "modes": { "diff": "src/diff.js", "parse": "src/parse.js", "assert": "src/assert.js", "default": "src/index.js" }, "scripts": { "build": "bundt", "test": "node test" }, "engines": { "node": ">=8" }, "keywords": [ "assert", "diffs", "runner", "snapshot", "test" ], "dependencies": { "dequal": "^2.0.0", "diff": "^5.0.0", "kleur": "^4.0.3", "sade": "^1.7.3" }, "devDependencies": { "bundt": "1.1.1", "esm": "3.2.25", "module-alias": "2.2.2", "totalist": "2.0.0" }, "_moduleAliases": { "uvu": "src/index.js", "uvu/diff": "src/diff.js", "uvu/assert": "src/assert.js" } } uvu-0.5.3/readme.md000066400000000000000000000067311416513034700141300ustar00rootroot00000000000000
uvu
uvu is an extremely fast and lightweight test runner for Node.js and the browser
Ultimate Velocity, Unleashed

example with suites
## Features * Super [lightweight](https://npm.anvaka.com/#/view/2d/uvu) * Extremely [performant](#benchmarks) * Individually executable test files * Supports `async`/`await` tests * Supports native ES Modules * Browser-Compatible * Familiar API ## Install ``` $ npm install --save-dev uvu ``` ## Usage > Check out [`/examples`](/examples) for a list of working demos! ```js // tests/demo.js import { test } from 'uvu'; import * as assert from 'uvu/assert'; test('Math.sqrt()', () => { assert.is(Math.sqrt(4), 2); assert.is(Math.sqrt(144), 12); assert.is(Math.sqrt(2), Math.SQRT2); }); test('JSON', () => { const input = { foo: 'hello', bar: 'world' }; const output = JSON.stringify(input); assert.snapshot(output, `{"foo":"hello","bar":"world"}`); assert.equal(JSON.parse(output), input, 'matches original'); }); test.run(); ``` Then execute this test file: ```sh # via `uvu` cli, for all `/tests/**` files $ uvu -r esm tests # via `node` directly, for file isolation $ node -r esm tests/demo.js ``` > **Note:** The `-r esm` is for legacy Node.js versions. [Learn More](/docs/esm.md) > [View the `uvu` CLI documentation](/docs/cli.md) ## Assertions The [`uvu/assert`](/docs/api.assert.md) module is _completely_ optional. In fact, you may use any assertion library, including Node's native [`assert`](https://nodejs.org/api/assert.html) module! This works because `uvu` relies on thrown Errors to detect failures. Implicitly, this also means that any uncaught exceptions and/or unhandled `Promise` rejections will result in a failure, which is what you want! ## API ### Module: `uvu` > [View `uvu` API documentation](/docs/api.uvu.md) The main entry from which you will import the `test` or `suite` methods. ### Module: `uvu/assert` > [View `uvu/assert` API documentation](/docs/api.assert.md) A collection of assertion methods to use within your tests. Please note that: * these are browser compatible * these are _completely_ optional ## Benchmarks > via the [`/bench`](/bench) directory with Node v10.21.0 Below you'll find each test runner with two timing values: * the `took ___` value is the total process execution time – from startup to termination * the parenthesis value (`(___)`) is the self-reported execution time, if known Each test runner's `stdout` is printed to the console to verify all assertions pass.
Said output is excluded below for brevity. ``` ~> "ava" took 594ms ( ??? ) ~> "jest" took 962ms (356 ms) ~> "mocha" took 209ms ( 4 ms) ~> "tape" took 122ms ( ??? ) ~> "uvu" took 72ms ( 1.3ms) ``` ## License MIT © [Luke Edwards](https://lukeed.com) uvu-0.5.3/run/000077500000000000000000000000001416513034700131465ustar00rootroot00000000000000uvu-0.5.3/run/index.d.ts000066400000000000000000000001721416513034700150470ustar00rootroot00000000000000import type { Suite } from 'uvu/parse'; export function run(suites: Suite[], options?: { bail: boolean }): Promise; uvu-0.5.3/run/index.js000066400000000000000000000004211416513034700146100ustar00rootroot00000000000000const { exec } = require('uvu'); exports.run = async function (suites, opts={}) { globalThis.UVU_DEFER = 1; suites.forEach((suite, idx) => { globalThis.UVU_QUEUE.push([suite.name]); globalThis.UVU_INDEX = idx; require(suite.file); }); await exec(opts.bail); } uvu-0.5.3/run/index.mjs000066400000000000000000000004441416513034700147720ustar00rootroot00000000000000import { exec } from 'uvu'; export async function run(suites, opts={}) { let suite, idx=0; globalThis.UVU_DEFER = 1; for (suite of suites) { globalThis.UVU_INDEX = idx++; globalThis.UVU_QUEUE.push([suite.name]); await import('file:///' + suite.file); } await exec(opts.bail); } uvu-0.5.3/shots/000077500000000000000000000000001416513034700135025ustar00rootroot00000000000000uvu-0.5.3/shots/suites.gif000066400000000000000000003437151416513034700155220ustar00rootroot00000000000000GIF89aX 8" 7)B!#,8]G&)3=Z' 5%'&4M[@'',-E8Fk'.)!39'*FLS}vF&0$!-)>`'3[ + ". #2 4= x!#."#/"$1#$/#%0#%1#&0#&0$",$#/$$0$$1$%0$&1$*5$89$N?$n=%$/&%2&?&>'#,'?'?(*3(,=(f(q<)!)>T)O8)sT)b*4?*54*6H*KY+Sw-/91-314?1cx3j3z5%5DW5w7Y8*)99@9lE9K9`@5>AJ>Qd?@{ANXBFRBtDXoGcwGvILNnN|O83O`PPXPQLORB2RSEBSU_ScuSS؃TVUVVzVWmTW}Y[_Y˄Zv[^a[ۑ]^z___ۅdfodxdeNDemwef][fgfhi~k[Nklb[m֏nmqoqx{rmYt{XB|jS|kc|}~ws~~kcvg}WťT@yz|Lcŧɱ}ǿöOLżӘɛ4ȃźɽYR._V.!! NETSCAPE2.0,X (\Rr(\ȰÇ#JHŋ3jȱǏ CIɓ(S^3U6{c<ת*bɳϟ@ JѣH*]ʴӧPJJիXu1?$JGf۷pʝKݻx˷߿ LÈ+^w @R7_. #e̹ϠCMӨ  90؜۸sͻoG4<0+_μУ=hp,ϗ0ж''Ͼ3A<~ ϿwרS  6 h2NGfr@ |`^:p $r0W UAt ! v *\,pÒ2(PFa @\:{Xq uPaE"$ =4 7H)tXppΟA0$ *D$  7ЄB Ai68Y禜vݝweiZ;{\; <*+844`ĮF kz**^l?x d[bcMEtMPٱ9Dz2Ъ0 ³A l^Υ ɲk*7Vl04Єk(LpXfаpcliƴNJ qE ?,2)Hl] 07_`l.Xqqα@` =2&mt sЖ4XudbM+t#ChnjÅ Ap3*L]s N öa܄ .&7ø-.38Ló>P%.  mZ*7h u4pJν`ClpMg<\9d70[4AV? ' )A~&l+؝)}ŅE44迓6 r@q`\r g sDw¥7 H,&:ȈN)ZΩ"Eh` ci(21d<(4pċH:ev#>~ $)B^oFˑ";#7T3Np#Ce9\@RB*$br05&AmtW c様[ )<^f0qA`-euaԚ39Nh K*Cu8A,k xppYaSҔaX `xT %? !^r{F \g3GKZb7da,IO>PN`fE-ӕDBIBHqj\f@-pB Oa@7Nrjpdu-:pAPXdB Ijx 7bZzAQKĠY%VQN-eRP4D5c* *G<՞T-mUVu#-}lr#7@̝zvptV 1s-ͥ RËXY8Jܦ66ǭُp  "HxБ<+]vͥe>P2b i\܀w8hs\0L%'R MfZUoÄ`檈;YZ ` ڲV!. /Jm=Z*V%,mMDVʅ?.Ŕ@A nfŚqel E6PמL5V10 dְB\l`Td ٶ9QwBH!8ؤ1E=137R w0 A)\Lz/prnKo[`q^Py P?L!Rcrb4E1!DeV@@`aP,6G`!Z9p d@Ԓ ("-[LϺZU ApN{m"8I2W\E> @+G,A T \@g2n죵Ǖek0 G b~`V6YKpEYn&) GٿxRfl;w@ pQ{x0忑DuP;:.LP~ !\g  u[Z7~pOy`l{eƞ • xsA8 &"z p)0ʀ&S_zǰ,Bj[50[s@ZS^p ``E@'@! @ -,9dv]G"`,p0J'Z ?{T0hxW "3(҉U?+U!1*CTX@?!pp-@@X3~@(z @]emIi10 `2][bGr`=@ Y/P P ?3y}:yDpL"(fiR_KGa%NM0>m* {:ʀʹgz-1pl ;r_`Uȍ9`90К  5UeV3Qg8~A73@!!@n::-OipYPPp"PЂ})S)e δe o9PV[j/d(`Pf 9a4NPi Em60?|] P2:r^q@tA%`U$] ,oe  "`y`X0S`|8^`U`L/` ``qY)GQUY)b}~`7 1e=p1@Up0~ƿ%+  Z Mð,#ppM0 9Z3pս/,Bl0D]f8/sU8ET),߉C$"JȔtM#`Alf $h < T0!ppPz = w ؖyQo`ncp ;IAgT( xh59ppp+ԅ#dL梘xP6pLP iY t{zX`]|SK 0*\w]H`+ꞒH xi:HN'!mx9iP@sδpzК\ /@S@j~ګPh`̵g z91P Ր ~z9 l`nݒ@N>[L6Rx籊x`zR=Yi[HK?]c4~`!b Z8`e 1pC9Vp!rXO,\Ef*6o$& 8p ;JAci!ci>$q3AJXd5$o i *p8K#6x B:dqCKyX@!`!H-7L&H@V0K A6 %ql!;VDH6 ,DvƓFK4z$@o+;]3ΞDI>}Eؠ9f.@D(,Z`c @ "&oYe Nu ([ !b8B_aJ=_|GxFva`2H!$H#D2I!G%BÕ'؂-$JX极"Dy!Le!E ڞ1}nGvX`$l>I'f;xUSԡ*$<"fXTwᅨ,Ƣ\! `hnX=N{b:^DU׃ vԜg}HshJWÒյ_X_ŝSoI~p)S tRamF5=sGmaEX6m:p >x O@\SC0%ת `zi%K9pҤV:ɝ ""ƁN:;$i 2FH9յb$ pC<ܩ1Zn7ֽPŧޥGN]] ]DE b` LQQ 9'l҄0:vHdRpOҕ,+$1eycx-oGY޵w8:-_=#$1weBOF)1D 4n64Av+  GzLO#0r!=C"EL"d(ETkN9`PEO@R(ɻRҤ'Kdđ>'EbRWBE1@JJRF)@в4''DExSԄF;P%+Y:UhUM-UI]kX ȡ}ZV+To U:Wծw+] h"xjLհr5lbX6v]|4YαEe5Yvֳe2 YfEmjUZVմmle;[ڶmnu[bnp;\EnrυntVN׺nvV]v׻.w;^򖷸5ozջ^آo|^׾-m_Wo<$F0 `7 vp%^On1a3WqaIJ%UbҦ/1c]cט3qu[?q۪wa{:ў{O6/`Ͽ7~.Fs|˗'to~z5~wos??B+@#\D@St@T;f@ @A4ADLdAt|A @@̴| B" DR%7"t¿;(&[+1+B.ܵ,C1)4"42dC[774C:;{s==:@|A$DCd=@TD3FܪGDJI=FLLMēOJE RlSDmU\DWTCRERYd4YEk\lZE_$`sb|/bDF$Qe\ctFh,iFk\lFn]G]GroDMSuftGx\GyDGz,G{G|G}F~FFFFtF\FDF,FFEEEEEEtE\EDE,EEDDDDDDlDTD>>֬>ה>|>d>L>4>>>=====t=\=D=,==<<<<<KFIIIB!=LH  >HD(3'2?;)HՄ/GA81B@DҰGŦ .57# 7kS` B`@<Z"T.pDf@>B#,+xEL ,2-k L a   E 0а ;9 (.]E[c-VziQN|o"]<BDȐ!AL6̡ĉ$L, )@ӨSN!,1'$) 7O83ANX|jSodż4 di lp,C2!2D qX)PIB!,, 5%&'$))*.%'!- 7!3 + ".!#."#/$",'#,$#/#$/%$/8*) #2#%0#%1$$0$%0$$1$&1&%2(*3-/91-3*54*4?=3314?>@5QLOBFRANXVUVPPXSU_Y[_eNDk[Nlb[|jSDXoGcwScukcy|Nn^zdxoż>EDEC"IWSVVWPHVPSSSMGWNRSRO!ńA.A/BIC)'SӧHVN :W8=3HJ E̐ C vaA'80$y%LZj MrPHK=D eTBL7AaFA(lA ݳć@(/Xr~@#MQpQc;(Af\-h+?!(D4V[l$ćfp3= + ".!#.$",'#,%$/8*) #2#%0"$1#%1#&0$%0$$1$&1&%2$*5(*3-/9(,=*54 4==3314?O83)O8>@5$n=RB2)B4M8]*6H99@)>TFL>AJ=PG*KY5DW9lE)sTFkS}>Qd+Sw1cxSEBQLOBFRANXVUVPPXk[N{XBf][WmT|jSrmYDXoGcwScugfh|kcemwnmq~wsqx{}Wkcvg=tO`i~|tc}[`(f3j5w3zNnN|@{^zdx=<~LTdm֏?ISP_eRV_f򍍑öȃӘźH*\ȰÇ#JHŋ3jȱǏ CIɓ(SHK&V G ɳϟsJ%J8J"NLj*Ejxc+;zxfٓKMlH&SpG ٻxzšFIHa)*szjdٓ}6ָ`263\ :pmĝk7/ÐT|J +/p}0NE;as@1 A ᡅn 9@wP *i:\&5iR.u5kraEC?!P !(I & Q m%DaAT"Bs,r{eD@e`@8@$4d"G P!J&| $irUJj QUC -aQUpP D@@F@|Ar2FDTthBj $?TDqhtDN MP  0 @H$CC9TB%pHT|YB`L&EZԇȅH&lAhН 1ng !)(_QڈQKH yP $DA>B\$4E Ԫ@WtdƎ@$@ d\Ac@'^!0+'qeʖ-DqX"DFϺ!VCv涁&AFA 8}&G|\Т b lj8@ Zs[uP1aP 9=&#&6, S^uA3"@.]п %A  a/AW+T9c4<`h";r}JlPfĬ38A 1AVd8H$W&evBT@Ic?$@*8:B]}6K"]f%6 @ RMOGL8(Re\"gD"yA M# |L"C|C!l2\bG0F aA>I@''L/1Pc'ZB%t!"!%$FD!+"{bAh  bKDDpeeFB 8\aY¯NCA WBF$(y7iE E*pGDTDHT(M+!H 40 ,ݐ> fw΋8[yD/ˉlYJZҀ"XմHEɑ43-9 S0d0 IgJӚ'LQ)RZ†`MJԢ":UiRZJ Qx"DT9УZX*RZ| `m$dhMIIH@Qxͫ^׾ `KMb:d'KZͬf7z hGKҚMjWֺlgKͭnw pKMr:ЍtKZͮvz xKMz|Kͯ~LN;'L [ΰ7{ GL(NW0gL8αw@L"HN&;PL*[Xβ.{`L2hN6pL:xγ>πMBЈNF;ѐ'MJ[Ҙδ7N{ӠGMRԨNWVհgMZָεw^MbNf;ЎMj[ζn{MrNvMzη~NO;'N[ϸ7{ xGN(OW0gN8Ϲw@ЇNHOҗ;PԧN[XϺַ{`NhOpN!,-,5%!#)))!%''',- 7 5)>3='#,8*) #2#&0$*5-/9(,=1-3*54=33O83Z'E8$N?>@5RB2)B4M8]*6H99@)>TFL=@D>AJ=PG*KY5DWFkS}>Qd+Sw1cxSEBBFRANXVUVY[_eNDk[N{XBlb[DXo[^aGcwScu|kcemw}Wvg9KLy|tzc}[` x(f3j5w3zNnN|@{^zdx=KG-*+@Xg\w`-6-!,,,5%!'$))!%'''!-,- 8"9' 53= + ".$",'#,#$/%$/8*)#%0"$1#%1#&0$$1$&1-/91-3*54 4=*4?14?G&)B8]*6H99@)>TFL=@D=PG*KY9lEFkS}>QdBFRANXVUVPPXeNDk[N{XB|jSDXoGcw|kc}WvgL|c}(f3j5w3zNnN|@{^zdx + ".!#.$",'#,#$/%$/ #2#%0"$1#%1#&0$$0$%0$$1$&1&%2(*3-/9(,=1-3*54*4?$89=3314?*6H)>T*KYQLOBFRANXVUVPPXeNDnmq~ożɽſ:;=1 LPPNNQB KNNL?%/.> &4  öP?!/AK@2;+ŴKQdž0;KC22-ȅ%I\paVb|0AIjx[4]H`" 19X d ae\$8Paɓ&$P 2Fs}q##`25graF $|@Z5bKfFUQN 6-5٢W eK]E4Tw ( #G YCZ`B (V@9 5װcˆ!,, 5%!')*)!%'!- 5!3 + ".!#."#/$",'#,$#/#$/%$/8*) #2#%0#%1#&0$$0$%0$$1$&1&%2(*3-/91-3*4?=3314?*6H99@)>T=@D>AJ=PG*KYSEBQLOBFRANXVUVPPXSU_k[Nnmqy|}~öźɿǿCEF9%W[[ZY\G %VZWYWW\FT? 'ɪZS@/@5QLOANXVUVżΨ8  Paif!,,!5%!#&'$))*)!%'!-!3'3 + ".!#.$",'#,$#/#$/%$/8*)#%0#%1#&0$$0$%0$$1$&1&%2(*3-/9(,=1-3=33O83=@D=PG5DWSEBQLOBFRANXVUVPPXY[_eNDDXo[^aScu|kcy||}~oöżźǿ!##*CEDG> .T[XZZ[L!#PWXUXZH#!#$.S[HPYIҝ!6FNH@.$ 5TH>@N92ݹ~uJaaA.-(QfLAqQ9; HQD #-.+(0b$\mhB,P%1p'?x C@Uw$b1( Z099cΪACk$.4]Vx!Qhآ z2 Q,!.K!X5 sJ*\(1mHua#2@~$ ߾9 @#PKB+b(n<ÈO)HNOP !,,&5%#&'$))*.)!%'!33= + "."#/$",'#,$#/#$/%$/8*) #2#%0"$1#%1#&0$$0$%0$$1$&1&%2$*5(*3-/9(,=O83*6H>AJ=PG5DW>QdSEBQLOBFRANXVUVPPXSU_Y[_DXovgy|dźſ"/CEF:"$4SYWYYZI"!1PWWTTYE(;4 3PVYI„"57 4лYG ;&Cp!D)6iatB|ɑCa7` IB~P@$ ‚<0gW4 @ @Qe\b9-BQS2jB$߾;e %\!F 1Rd~ h@ËO8!,.ANXPPXy| 0I876^(nĘfl ,&!, -%'>AJANXVUV|'I8ͻ`(diAG9Hqt 5D'AN!,WD!,,/!'$).%''''#,#%0#%1#&0$$0$%0$$1$&1&%21-3>AJBFRPPXnmqqx{y|}@pH,Ȥrl:ШtJZجvzxL.znCM~p}lxBsd bHη ҕםںj] N H *|pÇfAHW3jȱǏ MPI B\ه@AD8sArQ3<}~XӦPrI*iRjBҫ[Ê]ҵ*رhڷZ2u nTB˷\W ̒mz +YCЫKWN\kÁ9X {GNCհ3osf!>M< 2(_>yH.س !,6 $)~ws0I8ͻ`(dihlp,tmx|pH,Ȥrl:ШtJZجvzxL.zn|N~D¢Ɯʞ΋ !,WD!,WD!,-6,5%!)!'',- 8"9' 7 5&0)>3='#,8*)$*5*54 4=$89=33>@5RB2)B4M8]*6H99@)>TFL=@D>AJ=PG*KY5DW9lE7YFk>Qd+Sw1cxSEBANXeND{XBf][WmT|jSrmYDXoGcwScu|kc~wsqx{}Wkcvg=_O`Ly|tzc}[`(f3j5w3zNnN|@{^zdx=<~oLTd~?ISP_eRV_쁄żȃӘɽſ+\R%ʜ1Δ׸ MY%PWN*V`O؈N- jcken0J`[DR)ɤ% % T[LQ-B,u$&11à1syKPdEuy ~ʔL D9SHP# u>:"J,^BpZI2)@@PA.@nCFJ!ARyhEvua{lUA(DJ0u jِD)\:#`#$ "$H&=T'eFT As L\pՅO]ӨgHˣ@`\@ qX3 ^u`@E]7HNhFIb'`B!/"rhg p GHiwBtX1&ÐalH)9" 082D<Hej TAv&!b. $'r"1:"QZh"ih)U/Lzh0&y0vx8Htp:iIIԧʠ@D! !o\Xa+&Oȡ0$1" ; A!hq`d @A1ɂ+%`uv[B+9^%\I{2/UADH\pINm"!ruV2(< H4#;O"$h]p -wePG-& ?O\w`-dmhlp-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n/o'7G/Wogw!,- ,5%!#&'$))*)!%''',-9' 7 5)>3='#,8*)#%0#&0$*5(*3-/9(,=1-3*54 4=*4?$89=3314?O83G&)O8>@5RB2)B4M8]*6H99@)>TFL=@D>AJ=PG*KY5DW9lE)sTFkS}>Qd+Sw1cxSEBQLOBFRANXVUVPPXSU_Y[_eNDk[N{XBf][WmTlb[|jSrmYDXo[^aGcwScugfhdfo|kcemwnmq~wsqx{}WkcvgVzGvLy|tzc}[` x3j5w3zNnN|@{^zdx|}=<~oLTd~m֏?SP_eRV_f򁄀öżȃӘźɽŰɿǿH*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sNS@ JQk8ʴӧPIQI?Qj!k!_qBJ$)B5{P) @ ?=HFԊo fd!VX `% f䁂.ؘwb.<"Ub8bjFuW|"P7|nM yEeuQ}IbXnP  H؞ `zPdE AѝA=)Dl8PPt-% ( %QBh 0A'zU" ` @P EX 8XB|cd @XtG? Ņf-AP5@ A䕷g:4kR@1\FY%Qr AlZ҂]vo#9$B`PYИFurͩ@|UE2ir(!h ] o] !EQBd 1aCF@`$(i$%Ps$ ݊[@elTAHP#:" ZWUOBYE4<Р@Д I vi6j9J}m[Ab^e0AP1DEx %g\Ă,A1S-Ю U4A!Qm7a dJ(&U 7KWϽ;|߀.n'7G.Wngw砇.Mn飧z.篻>W^⻛<'<ǻg|WT[5a}_槯F=}> `(j~Ln'H Z̠700hDЀ:ƒH’xI Dɡn 0,& `+ d WC`L8 Z}Y5 7MMXcQ!%"B9T(A N0aH \1 c<8HL P g"'#dvj3P(B\GJ(0 B)DEYpOHPiHĕ9 P!@!ki*PJU 4@"Lv #4 ~, `e.u}hdqm*CM;܂ V(+z1n`_ IO@{Kk[=OpW;&0e /*d *@K]t%zK斴  @ G r|Yǚ $^t9U,!(\Tb:▮#$ZA XBX1BbER8b;CAWTuxE&Y*&< (ó'A> FhlS8" B+1lήH́7e,̑yn` ĘH#l)@ 8D'^& ׯ k5|[ ĪPgXsm9@jL8Vb0B(bpF|`G$B=D_%hB-<&OhqN#E!Z2Hp[r@,KZf! WW7p%eXbZWpleST@E&+pX BRISALGs p9fqB+>%!uEKX D PM`/I"D!ftH ]İGrZn&0 JEp%Mb7.Mb 0D,N" OT U]apL/\1tB?;"N-* bgafQ݁Hh,D6" /60 B!YT4%rPUVEr XY IxXt &Vm ETF aw pqD)% 0 k&E K6&eC`|t6 _XpGG N)7 'jP PK;#/R aF  J%` 3z o 80EW s Q L@f@ M wHp jSd fE!55KjvDk8' P pr 0 3 F^hE{B K2]" Omp/ 7 'php [ PNL p( p ~<`flPLA  @z o \HkSmA# rPp`RrANEbay # p$ pD*qT2R G`5ZEq#e/m<J47YY Pu HpB2yB P(đu swYh0qUpO0  ZAw P W`L@Lo Зhw7p @ `KEYLwk HVTfenyE * PV  p '@A/Fu ` b) @ጳ/ 0%4'TU P Y 97 0y v` yytā! '9E  p  )A  *wU#VDyGDf9q Ouy z p)ϸ4p 'f*,TjU -yCPu 70 .qJQX,hV@ z >6B qי Q('qvfTЂ  tR J0J1pkph~J睫:/# cp  Yc ~X[HG^zKPpz##Io k U /pJSEB E %O" M Y$ X` Pi=)c Gt TKbEPtyT>{ K jʡj 𪕀FJL Z L:1*qw&-D FJ91 KPDd K2Q`TF @^ZG z `9Q0 FF X0&tTa;` dĎ" Bj)@{qT0t *Y E R p @{;ro VfcRS[$Pf ( Ui0zV tF maS0搾pFS G$A`Gp⥦6$NT~tK p4O0d~ȧ %d 30aT DEA/jmaY]IT\<`hiY w&@gp *q@0< { 0qQ& Qie;xD 04cLeHAy Z$Kу@ pB0 +#Q` P\R,F4 ˫p&PR7 TI óT!$%i;Jg ={龫PxkJ_h!V 9 $Mq'z pвLJĐp <;Z'I,6 p˗WD& #`Yp1V' g&,{al P@!uWmk@N 0S7< Su @ T% ݥcKd:7 [1|:dKxdE LTf Jq & ` @ W `PObI ) PذpFT8j`z~ pgr peYL Kv8r DKB bB'˷ԃ+bpp 0dmIgb֪c d]:]  pC ]j΄pI4q@ l|mnFp%*KNS X&@@ZqF@^OUgB@1c1HݷL@XV‡JHAPCeKXtAt3*F~E@W—DXN^ XX0_T!EŌ>ZIcn``@A)]\m t)xn]:]:SpoyWp0`L/>8B A0DpŰ~NHYpZ#< iO S*&pF0 pQ  BDA0X}RV Ut QWLQܔPy_S`5h0U+&iO @` 0DEEpٌ>#Ud p M:5_tM'tCVGQ 5M]A~y>t>7=/{?_?_O?t@Q.#3Dez\":WqZJ-]SL5męSN=}TP4oT)LT2`C7Z-άH:P)O,;$@+, T#h0x Iߢ?Ydʕ-_nɠ/E Y[VS2+#hH$T6T]`,ybhJrխ_Ǟ]v5r0̦a5C+ܐ/(aT~<_U\*W|Q bd?@^FQb6C?:!Lőj"驨L;(T•ohb`0RV;ȆTi{w^{S>]gVzB}ה{ӧ?n{`=D`?]\/R|3= +!#.'#,$#/#$/%$/8*) #2#%0#%1$$0$%0$$1$&1&%2$*5(*3-/9(,=1-3*54 4=*4?$89=3314?O83G&Z'E8$N?)O8>@5$n=(qTFL[@=@D>AJ=PG*KY5DWvF9lEQd+Sw1cxSEBQLOBFRANXVUVPPXSU_Y[_eNDk[Nf][WmTlb[|jSrmYDXo[^aGcwScugfhdfo|kcemwnmq~wsqx{}Wkcvg&?9K=_)b9`=tBtVzO`Zvi~GvW}y|tzc[ x(f3j5w3zNnN|@{^zdx|}=<~otLTd~Y˄S؃_ۅ[ۑm֏]k?SPeRV_쁄öżȃӘźɽŰɿǿH*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8snϟ@ *1 D*]ʴA&N%"*Y`WʵW Z 'TiCd=PEAƒ>JDa!(]R-alPebr4J <QE'5E%,PYNG rTHL8H yH%?aO.(,B/h3e>DuGʈV`V5Q @my(0@ 9s"%T m/^W 41.YΔ-a1PIuF D`Kd6cwT-AG7-g A 6 MD7XD3Cv C& `xSA@ "bL„-x#q[ ^:H# \ $0$%CsTo [7%V22) 1Ҹ0K Fↄ$gX,(:x̣> IBL"F:VxzDk$iGt$o5$QIvQ#  ج#l3 .D Qr  ]If V[ÖDBeKn%y^-/%d'd "B9 y+PMBІ:D'JъZͨF7юz HGJҒ(MJWҖ0LgJӚ8ͩNwӞ@ *NH@ԢU**ZT$@2D&Ld4*( P!-Brp`aB4UTnKXxP")Pk@H@ \SWQ@X԰$ \#j٠LSP9dq.xgr.$ \0lX, ]ki?\dc{X6_` mhԐ hLX1 rZ`1~IWPtCe G/  m! SX8z-,E,j j0$Ɓ MAڰ *S(hA9Xi h1(b2re5֐3SP7c9M1/ k-O?8L\ @`_q8#25 31Ln,poL +D'$Syv9?CPpT# t,2K<@"Ӂ A@(/sœpj>ح_c@@#,maX!tMjP,.Ld@NmHBt[gjc3? 4ьi' Ѐ5LyX@Bj~T M,J D*vEv8BEeDlQ{ wh<{;y'|kC}l}B-R{G7j HI @۞5w#D{$x5~#O|ÞR#pp (wWψE[H};KST ?_[7* 88(jp7* Ѐ& *x,Ȁ*0-xݰ ^2 (DXp9ƒPNNdMJeYƃPa dN%OU:P ;ІQ aF_ƄN`dH Q >'wW8~vOy0Av O l{HN(p  ؊`(pi@ZIPPx G3HyXp慵( `  ڀ p h^@ )Kv 7P3>@0i( x :G@7SZ^ ro 0  n fxiDP)=F t Q` i;Wؘ>  @jxǠV u ] h6T5\?tP@@AOix H$X>X r6a Z0 j0 p ` (7he6.fy~ : 9SI7V `Z $I Mް A`hpE .x`q؜0 ] pi Y )\ pI`Z oZOᗣ@SF0HYpڃ?(  [0 # `0 XsɚA {0v QPe1J~ТTH 0J 0a( f i fb05Ph`Z:qgj:IZ$] i X  kgf`  ^p  {ЛF Q `7ЬN`~ IVpZ q ЫЫP  ȡiH3QPQ@*K +V@&( 葧Ȁ XI቞4| x1 E hk|>h C J 4/ ;0P`DKO[hP*xT?KmK :Fˀ8;Xd?(gk++RX~X}{+K) عRz*h;[)[H3{V: ( 0 +Ci;jNp`ix0퉉9 {@dȁ`mnf x u  ќm *6ηs5`#qL]<:/ q0"-<ҁ Y` f a?=]]!*I.dP0[TE{] 6 ^ h0~ϧql-_/C9](\ ѩ Π ώ,g EU \Y͏/ R`qݠPoΛ+.dqa qҦۚ.2s뛸cZ&Mn¥9@Jj`A̖/cęSgN{:[>cQx&M^ŚUV]~VXeu*DN6"2c2hW3I۸j]Deշql[r8f3 TM8fgrP<3Ǧ:FZj֭]cEm۷\ڲ [ᨹKګ-CFF2gL[mv< ('XniΎ'Ov0uRΚ:5qI ]~*6mƛWԐ5qF_Hh#U aҠ6Vn:c,%>ZIM6لM8*LJ=5щr$H#JĶ&iqRlF:q9i I$qaƙk@C2#6Cra8cJ=" GM!MA%ה$0pDZ.G0p&: 5H,`+f M*(SW_5V-SBKsTVTo(իB++ 46Zi%aے%ٕEb%\sEWFZ:[ wGGM_5tvemK"bgٶ0"U|8c7X5 pdKQ>Y[.ypHg};9gwjA`:h&hPyfi:jj:kk;l&lF;mfm߆;n离n;oo`a`/ A,pp< 2`#}03Ac%Jq#ȆZ <(1h&4#X0=q+Æpm *7 8BCFd )X?@BA|["  {4c$FnT2WG8N^8JpJa ĥV`4o@G:F*!%K-#3f hW$-Dl$ݲHC"t%i];X7D=QOZբЪ4fыCԵER'[m 6kUR ā1$pu4ȵ=mTC~׽t0q+^#4!m;[P @ JWbP'\%ֵkf{CǻDVl:HE-Zxk(ȹΫW;(|В@0Vr >0 ' gbPvZւl`٫QJEaNv\ͥ|]m\-_.Nz6At MA &3C'Ptx}^$ 8 D)qγŒ]Wą.{u ?o4fzGFnl$W̚y,@AvA@eQ :4;hFeieT6`li$3Ԩ/ CYEeA1iX_L0 h?̠F‘ 3c+_^p#!NQ(:c p@ (6q@ N<9qm҂4p-H*10p5AkXg q\E16+P830TojpL-@a Å{@H0Ȃ`?h0-0e003#ck3 ; <\ =XUɊ >` O:k>kpfphp@x50e-aj^ZXlHhip3P.B t j8,Xji0/@kW#8n6k2-C.-` .0UJ pD'(D-0 |;F j( цiX1kc薁!Ӗq2fӂc:FXP.i9Phn e Fh@300P*H30X44ʩ4 NPM?ǬЄ=@NѨzԱJ,q2` 4.Ɇa6p5H5XӐn#7K18.,TpD^QH-Ps{ -pH@҉G̉\u$ y@ !X@ #@>5D$ Hk.-Xjx7Nj3p f4KD, x&,`It p@ЂZЅ\3\6pL$Ͳ܁\0;'Й>(N8HtKP.ϐ`r KO|l gG(ut3r'qHe\+?yIf4 ZX&ZeÔFiNpGjtId #V hѸN.p0/-g5xK4g8CXJphZ?g Xi؅$a0 ;c,g@0Ze4P`  Є?{Xc  >%8;;p@F@ToLlm7`Hk2H7 r7xHx xu@0Hxˉά{}[aX7x7k a5 ؎0# N:;O聛 e yD*RogXjΧS2lN^t^x}ZpEڸjjp=o4ՙ[ (#@Vl ąqtAR3㐎px2xkP=mJ90̓)a+6EiTk 4㖊44P4 > _]I6 fa ǃ~<p4AmSA`=\\@b$&\Xrb#Nrab >bͩvafs+^n`-.g/f5[Pf269D!= 6E1IUzIaOl7p-%EhJЃ9cc`ap5@^_&HnD>1am G(ޖY: Xf nOJN͕тiQYcQL':&:&.4n,kYWЄ_P<=肮ЁNteq=Ȃ=g_A 7;UJ3 6!d~KZiaIu;SHtq:80x_U??iP>{ފ泗h81hEI呆//HkiEنkÒ Er*ln]6mhtـNHD؀& Pޫ0uf7遴ބ8Af-ՔAV05H_BH`qx/T7 y+lf_I`E9Lp HRkpbheOSE,ǜXDg']gpaxZ7ȡK.KB1d`WE.HP/q-W17lk.ծ'-x$. 8_рk5u i>(p^ ܋a7}қ.hp8XE!GQ_Im-`m9ER-@O;m6PJ0dx$fq q;{-/8ϳQfnHH0@Ë6#9ne޾Q͉UguΕK4hlNXτ i1qa8O1Z`,I78nn r?z=8>iHKЊ<Ѓ ѨٶYѹhT7hVW1䂾BGH|7`C~aƆ"p@o5xI(Zh&1 30\_'X+(?؄ۃ(YX>ЁHw|8|]l]j]`fǐTa c I_x Ɏfc}vW &σO{(h:(7"(XH'? 2l!Ĉ'Rh"ƌ7rѢHmf䶒V'\ʕƅkd˕` G3\@qgtܷđ-\ɒބIɬnj+XhA)Aڇ|Tk.޼zrdɬ(wdIm$`.TiMCeҵɑ{G =pH]ԲgӮm_yۍ0.^jMf񮷧Sn:6Gkf\\K&>wĭo 8!Ȟ镔6%(}Yx!j_SS8!8΅c$"*:!&>cچ9#=n69$Ey$I*$J#QJ9e5PC D ]re ]z9` &c9fV@ af Ty'y'}' :(z(*(:(J:)Zz)j)z):*z***:+z+++ ;,{,*,:,J;-Z{-j-z-㚋Q bnyɰ 0Ra ЮA ,p. 40 \DYEJ0̴bCl,#4h0 0I*2 <4TnCr2䀽 3#3LrM84E44\\22ˆ0 +j@\DCpYC Pm~4/<7k 5,sM 3dA8cM68AC4 sو#AhX3 A8C ,l040T5-ވAAC(s 2DcA a1QN5VTs-ho%xL\Ɍ-@Y,L >C 2uk`ӝgaˆ fc5`{3dyk hyc`5d15lx3h 7xz lq 08AB =0L !}pHhm^UtWf3'PP!6NV6`e!H ,, 5l$B:D}RA؁ CSU(3ky,M ~Є[ pg C';_iAP}\ޘ̟h/?acPj`gN J 1Dڪ^Zg>6 G+A; [3Uwp!3PlBh*N`۴ཉhxo{]2': θl݄ "_Nu05K ݄g`Mh>oqkQPkh!F[#H=hvP thB, @GXA(TgpP]vN<9=-c0t8$/.S<3sbPB8H/x+(4` 0C06 )PB0("0ئ8ϪAB0Tæ60/NL8W ID}"dрZcJ%/00X5C+Xzm,AB5\5H(pe,C5(C]RW 40Xf 48p%CO0ЂF2@e2-ЦZ䍥Ϝn"ڀVݪJ\^P,@2Г-T&eaf2P>A,8dĭJC\"CO5P-(E4V* 2h/ (pv)&.L3 @8@>@N(&%6<64dA`11pP>nC3;e5.C*EnR1L3fA0> 1̼%hR@(44-9DJ(30(Oi6fVCpt1n 3iv$:CCh <\[C.X$%!LD0t#8l$,PB74Vdql@rd Q&dORB @2B~ p5$H$$/_Dq$@C [`2xL54/9F(CZl84Xl+o"503; 9C-0k e/0nNpsH+Xr" QB, hS#WZ0` 5sSMA0,0|$S+(`+AB0C3 toQW UE<)F$*gn6lǶl6m׶m6ngi@!,- ,5%!#&'$)))!%''',- 8"9' 7 5!3&0'3)>3= + ".$",'#,$#/8*) #2#&0(*3-/9(,=1-3*54 4=$89=3314?O83G&Z'E8$N?)O8>@5$n=(qTFL[@=@D>AJ=PG*KY5DWvF9lEQd+Sw1cxSEBQLOBFRANXVUVPPXSU_Y[_eNDk[N{XBf][WmTlb[|jSrmYDXo[^aGcwScugfhdfo|kcemwnmq~wsqx{}Wkcvg9K=_)b9`=tBtVzO`Zvi~GvW}Ly|tzc}[`(f3j5w3zNnN|@{^zdx|}=<~otLTd~Y˄S؃_ۅ[ۑm֏]k?ISP_eRV_f򁄀öżȃӘźɽŰɿǿH*\ȰÇ#JHŋ3jȱǏ CIA&&S6좲˗0cʜI3 jɳϟ@ Drɂ~:i0N:GR1~ʵWJ>hAIH e@Ȳ Z966ᒓ09$̴R<!Ȫc+a}%X @:TĐCP"Q\$S#54A f AMAw%`ҁ.@RXGF"`3{&P/Q![lp4J&$0Arrm)8Z iIiUQ~2jjYP 4P a dN1y   52K ؐn  ĉ_@72GV t$s dȑB %SahXE&)A]&CDYqhXa @y$F BABE(D4% (&P(A2iA:q#VXfZȨP(` ɖ_F)q8F70-'Ft |$j@ k[ NJ /Nx2TkBQq%5'AZ(BgT[D)$8 BB<(•&8piB+P AC̍iBdK/<$*ABg]ЧjV 0Amy i2@8p-P=bL'd7ClTyPtBdBD5$Q3,QA9VL ǕL'KIqRRIqLHF7+TiiC1\M8Rm!Q{ApC?Z{pPt5Q"H`' y[cT'D;![AH#  e nh 0QRA /@ #Ȗ ď#pu-{ i+PR{T#0ȟ'0AtNLZVdnAa2ElBl"B~ (Ḛ$˨*LCm H/( fC>%r 5FY>U6<u"RNEiihR0NA0 )FvCS!@2ؓ`pE Pr`u"Gy 1)ԔD!ĠHJ7+Iհ=L),H[BՇBƈHڐlF袯)Bp @(CtuiQlц X\H/chA!HS!A Ҫ9EJS * d(8c(AW _u+"R:lCW@.AV\#&H alFZȸ±Cr"ƚV`Gf)U6+B< z`'d 13"b8 !!h`HeHKW)8:|*Ӥ 6 e-#}%* DfoCb xHo2V 9&fXZ11#.0نLG{!B(@ DAG}j{ r!T$k/V;jADpz"Y0c;mnnET-^sDHPƢ'@s;FģC|h!&ɐ=;2p;ƈ r Xd0dm">M(z&>X5| mKıM- AH!T@Q*ߠ sȻkB'N񝜗$EBNgQ9Mqb` "w@ЇNHOҗ;PԧN[ I`uqڱ)eΜY@r͐ҕ xF0JQsbdog- 4C֘w=H@4ƂYDHA]#مW'!`"/|=YRXƗ2q( udp9fOg$Gy'r};ΐ wH)D"R{OOOϿ8Xx ؀8Xx؁ %o&`(,8I|ւx0t]jj|7y)1nIa# {DF+T}HL3Z-YD=Yv] Hf4QrVu zYCF;Is}G8ImQry9 'HV0upN9sp ! ᚞G`"yH`.@x9YyșʹٜyOE)AFJ 3IgI8,"苕Qga  *T) zT эQnR 3X!QǙ sƟ a < c a lWa9PQ JfX<9 Ȑ[)` Q / dqQTZVzXZ\ڥ^`b:dZfzhjlڦnpr:tZvzxz|ڧ}~:H Fb4j'[zO !u7-gy_a4g֘ЪQ;%xPoآ1>_DS;EifMs*ؚںuʭZzZzu)> 1 ky4)/Y@q`S* HC0xgªJix@PNĔ ( {bwPy GW ^0y.Q(:bpkKKI mXPg$!&Ѥ ncC{Ѕ24E!۸lEѰ $!vg y \" ]~K] w KQƸ&I؊A !-0]y`d%᷆T*r/0Q*N3ˑl+H+;˯մ;[櫉2GI00 v`1HP J.x5zs ȵԤ3(D ! ! !(  8:;F!"|I©ɁKg`A3! P`pX.Z U@X< Y(K,!8ULb 1[SmP @Puţ4 )p-q*YQciJ3<kQ#Q*H!Q=ʮʰ˴\˶|˸˺˼˾<\|ȜʼҌk7us^0rXYȖG{ HtlS,g4eJR%o oze a ;Q{y`Ϲ0!Y:HbN^kַn w@#ZU QzͰ5\;SY 2}U,DPW 0?H:NPR=T]V}XXͮ\#LJ- 0L@q r )@IUjqpmi v4\[lp]AjT[*:_Y֕a (˥J@Aa)0P_'vYKIk%'qi8 @Vzc=k0-04#F6*|]}ޱ-pkn{:9I7h~Y _iAiA/!$ݦ ! Hr}ѩ^)8 ˰ieFG [ uQa/-ca *(75J#. Ч!w{M,1^f~hjlNmݜ{ '.3Ϡٱ:p p?md{8+^ ,,aDDv̑{q!\ a ?<f%cT9]W3" t6 k0|! w490jmnGqb* l,pZYfQ aa>1 y va4FN k ϰ >27! /!,- ,5%!&$))*)!%''',- 8"9' 7 5!3&0)>3= +$",'#,%$/8*)#&0&%2$*5(*3-/9(,=1-3*54 4=$89=3314?O83G&Z'E8$N?)O8>@5$n=(qTFL[@=@D>AJ=PG*KY5DWvF9lEQd+Sw1cxSEBQLOBFRANXVUVPPXSU_Y[_eNDk[N{XBf][WmTlb[|jSrmYDXo[^aGcwScugfhdfo|kcemwnmq~wsqx{}Wkcvg&?9K=_)b9`=tO`Zvi~GvW}Ly|tz}[`(f3j5w3zNnN|@{^zdx|}=<~otLTd~Y˄S؃_ۅ[ۑm֏]k?ISP_RV_f򁄀öżȃӘźɽŰɿǿH*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜIq͛8sɳO6"I嘢H*8a).Nl 5ЖE9A@1!3O+KOtL?#i8@?l2N!%nʤ<t1ALJ.F2ְDB@ܴF0 sM2 H!PU@*p'D Ԇ=&b`CA tGgQNBr6DI`PdPT]C䑋A%TLD&]A$EҐuTFo8I t1DnF BDE'I r| ji&"mbAFZdDWT"FwE:2tA,qEFR ёM%I x^)TeBMWD1BXPiPa(A(EݢCTې RCL AiVgqT0TD)mD& ZPcEз1pAi!$"=v B2tDIgD*M ;ЈBBPݚI2D~DA[t )Ff!%ka|aQ m|PMCd XPڼ3|vP.tBD+G*-K4 ԗ}јIqB@AT$UD4K0R|xQ|A'49Ak>}7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ H"Q&e (/ i\Ac $K$z" "1~e]X75$K.&rƔ"sBؑ#@#y\SH&h8(x؁xF⸻,N G#OŇO>"8G0bB G@)b!v!RJ$W0F- R |IRKoP4 CCN$ %"8!h%B~ShTHRG C̍ t%b̞RghAtĠqH rhN6pL:xγ>πMBЈNF;ѐ'MJ[Ҙδ7N{ӠGMRԨNuU@\Uհ4b]Zx9rI %Crbl!^h# Q ech& HhA*[x x Aa3 .M}PcBqPRO;d Wa%2^IH!q8 o!r`" Pb>CA2@a";w,G!rȤ>"Y+"A'x*#:yL Vr< q(7 `Cb,:`񐏼'O[ϼ7Χoü<<@Qfgd7†I<&Nn:x@D_-_\ؕ^dEa;/*>EB;pS%jXLnaB` trX~'>sx >Q}؁ "8$XF&@D p0x,X %up i`{! 1-@ mY tq '>e4r@prA /~LG uQ Vз;((r qT01nO܇<XH Y WbUՃ [SAww sЇw׊8Xx؋8XxȘʸ،8XxؘڸXf֍\`8k$~(= 明QQcE[5EfNFQ| fUxHJ#nߘЅa~po )~ "9$Y&y(ip7 pS 2/ቑb $%?B0P_hXX>;@ D'A~r&s i6kJx>ZmVl /KY=Os0i9=6#<!j7ŕ3!Q9ifOuǰŐ~ QQVw-3Q-Q{hsbwA?YӷfooQ= 0Aw(Gfm9g)v`~qP! AeCuW)ks(jFSP})i0QtFWF@yٟpW؊$JI 1EQaxiiN %J e"?k2@D jq4S%Q9[Zi_RkZt  *A 8 At KhXSYwFXkf+E !  ?'j%zv#au; FZ:YBJ d.ii?vn 1ZzȚʺڬ:Zzؚںڭ:Zz;sbz'v{ q?jZ] sDۊmwsbd z۱ ";$yP*3{'pP =rx /rTDŽwtkҨʁwZ{۹' Z0ڤ>+ _ q41u@*8-B,xȫ@qS0 Qe J5 0cG͛KF.k  aU: "IѺ?<\| l`X۳ /БYcCrAJyk !\5צ, {Gˠ{A4/ v@ {M93J@9`\PHλxÿ4wDIĄ  aMhQp;k:s¿1 1&z% PQ}9<њ ܌!,-,5%!#'$)))!%''',- 8"9' 7 5'3)>3= +'#,8*)#&0&%2(,=1-3*54 4=$89=3314?O83G&E8)O8>@5$n=RB2)B4M8]*6H99@)>TFL[@=@D>AJ=PG*KY5DWvF9lEQd+Sw1cxSEBQLOBFRANXVUVPPXSU_Y[_eNDk[Nf][lb[|jSrmYDXo[^aGcwScugfh|kcemwnmq~wsqx{&?9K=_)b9`=tBtVzO`ZvGvW}y|tzc}[ x(f3j5w3zNnN|@{^zdx|}=<~oLTdY˄S؃_ۅ[ۑm֏]k?ISP_eRV_쁄öżȃӘźɽŰɿǿH*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8G:ɳϟ@ JѣH3tJ9~h Uի@aȢ'80-ghU7DiQ1HA`ZGMCV䡣+la]s0 ^S1HA Z}ab2})0!DT`D*m<`Hy`עEr2z!/o% twEr֪G 4Ez~-?6CWTnlC*4$)T D)$o͈i)eFPR0-/|ĵlk׽@Bx*n!uSułIlG`! 5y˭.l#"2ȗ"~seA@"`s1d'mC{{ёY8HmwXST7Wkˬ!B9p{KuJ! ϗEUNdX1 9aVZd@I ؁^!_F*! -"^uG.@.V!W^lublxγ>πMBЈNF;ѐ'MJ[Ҙδ7N{ӠGMRԨNWVհYöεO?s굞Yjpue0({ЎMj[ζ=Xlq!`*Ӻ8;~pEW?8@5(qT[@=@D>AJ*KY5DWQd+SwSEBQLOBFRANXVUVeNDk[Nf][WmTlb[DXo[^aGcwScugfh|kcemwnmq~wsqx{vg9K=_)b9`=tBtZvi~GvW}Ly|tz}3j5wNn^zdx=<~od~Y˄S؃_ۅ[ۑm֏]kISP_eRいöżźɽŰɿǿH*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH t䖦PJիXɓF,r!,fӪUڷ W<ݻJ <޿&#1(0C3|.Cus-G,]4R0E> KD լ7Kƫ9^)x@S"c ^q̓# 3B$"aOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6$F:TV $VX]rdibaTew=q$;P|&P~١G DB4)bvȝ?mȜu)%(B]>dyp=5]+-!J!,2_'I"T4Q¥O*k&6F+Vkf~_l\G`%X=I[l6إ Ӟ%PWH T ;ǡSq,\w ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dD-Evmwޭ|߀.nE1_I~WFOn@.nЦ/vJ'iJUOdF@T9k 4|@JLM Z̠7z GH(L W0 gH8̡wAx pH"HL2Ic"B0M dDj.z` H2S@ZE4Dn$m )yAC|1#F&M IBAuxŃR"L9r?/NV򕰌,gIZy̥^gSdvC@. 怐̑cEeE\(LD 6 8IrL:v~ EAMBF6.tc@IGADġюz HGJҒ(MJWҭ-Еlk7cS$6[Ӟ@ PJ$孑A(R?6rdE7 ܄lHUxqt$*$DpCb2*Cf18qEu:{Hj"G! Lt KZͬf7{R6p чV b &G7^8 >2; nP!,-,5%!$))!%''' 8"9' 7 +'#,8*)&%2-/91-3$89=3314?O83Z')O8>@54M*6H99@)>T[@>AJ5DWvF9lE)sT>Qd+SwSEBQLOVUVPPXeNDk[Nf][lb[|jSrmYDXo[^aGcwScugfh|kcemwnmqqx{9K=_=tW}y|NnN|^zdx|}=~_ۅ[ۑ]SRいöżźɽŰɿǿʛAֺ&J@%c16KE xBJ B^Jt !3ڠ!CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^XW#K!A "-'1YBe>q<Ǿ36ͮ&TFL=@D>AJ5DWvF)sT7YFkS}>Qd1cxSEBQLOANXeNDk[NDXoGcwScugfhdfo|kcnmqqx{vg9K=_)bBtO`W}3j5wNn^zdx<~T~[ۑ]IS_ՁżźɽŰɿ;?9!ʻE*֤)Rޅ"1E ل#=Hӈ TB *u  #JH3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCsxӨS^ͺװc˞M۸sͻ !iTK~سkνËOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@XA~DCH2&K APF)TViXf\v`)dihlp)tix|  g%A&U|@di. pfQv駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv6Ӭٱ$,ȥk믛0+' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmX_L/PE$4AMj TA!R8+i1 0-@s(Y%$G..,R0Ѭ |kQ(6Pn!,1,5%$))!'',- 8"9' 7 53=#&01-3 4=$89=33O83G&Z'E8$N?)O8>@54M*6H99@)>T[@*KY)sT7Y>QdSEBBFRVUVk[Nf][WmTlb[DXo[^aGcwScugfh|kcnmq~wsqx{9K=_9`=tVzO`ZvGvy|`3zNn^zdx|}<~oLdY˄_ۅ[ۑk镁żźɿǿG> ̞͏Gڟ=ޣ݃ V,FCQb8 Me@{CUAJHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LO@̸Dž@F+¡eMP- Y0;3ɡ 3-ZHTMP"Ri-k_ذ2 .:()C;ËOӫ_Ͼ˟OϿ( f*À&M([D8(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf7VI\=aC etElx]p)tix|矀*蠄jh6(qI5"BVj饘f馜v駠*ꨤjꩨꪬ꫰R2 8::ܺ*k&6F;VkV \p 1 ے$D`Ak,l' 7G,Wlgw ,$&K1B#k ɇp4l8'#,$8914?G&Z'E8)O8>@5)B4M99@)>TFL[@=PG*KY5DWvFQdSEBQLOBFRVUVDXogfhemwnmq=_BtO`Zvi~GvW}|[3j5wNndx=~TY˄_ۅm֏ISˍöɽŰɿǿĚ2ɖʎ6!י/04.:;#13%'!T*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷/T LÈ+^̸ǐ#{ ˘3{ϠCMӨS^ͺװc˞M۸}2ͻ7}  ȧRN^ KNسkνËOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<6uCuDiH&L6PF)TViXf\v)Y e7,cbs kp)tix|iًBh<裐.vV馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vk:<0 4  : ͚I͂@^] 3)Al' 7G,j}@gw ,$l(,0,4l8<@AmH'L7PG-TWmXg ўDl :jlp-tM,Q <@;D\RBDۍ֐Wngw砇.褗^] @LnB!,"1,5%!)!'',- 8" 7'#,&%21-3=33O83G&E8$n=RB2)B4M*6H99@)>TFL=@D>AJ5DW)sT7YFk>Qd1cxSEBBFRVUVeNDk[Nf][lb[|jSDXoGcwScugfh|kcnmqqx{&?9K)bBtO`ZvW}y|[3j5wNn^zdx|}=<~oTm֏]ISˍżźɽŰɿE<ƞ#ʞ+: U*!BAHO8I@Q1o*\Ç#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+KsE!qȵ},d̮KȆX(3L ~'(#AZ\`i~[(ޭ,L 0l)6kd.c༺سkνËOӫ_Ͼ`u4Ͽ[0&Pr'\~yI F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf/aX̖%=;`D eʄElz(]p)tix|矀*蠄j(4qhH3$Gf馜v駠*ꨤjꩨꪬ꫰*k1@C cC+k&6AJ5DW)sT7Y>QdBFRVUVk[Nf][|jSDXoGcwScugfhemw&?=tBtO`ZvW}y|[5wNn^zdx|}=MSJװc˞M۸sͻ Nȓ+_μУK@سMC{e!=.xOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6$AOViXf\v`)dihlΙ )Y v矀*蠄j衈&袌6ꨍ <*ui0T)1 -9. 4穬꫰*무j뭸뮼+k&6F+Vkfv+k覫u¸ [}F$רl' 7G,W|xizv0!=Gd,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmH]zc $/C$r<$7G.WngCt.!,/,5%))!%''',- 8"9' 7)>3='#,8*)&%21-3$89=33O83G&E8)O8>@5*6H99@)>T[@>AJ=PG5DW)sTFk>Qd1cxSEBQLOVUVPPXeNDk[Nf][WmTlb[|jSrmYDXoGcwScugfh|kcnmqqx{9K=_O`Zvi~GvW}y|`3j3zNn^zdx|}=<~dY˄_ۅkIS_썍öżźɽŰɿIA˄Ϗ ׮NBނO?\0 8ү+dD&DR(‹ BI(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘Ś̹gE>FO,!3m(5^ A'm$f!68&Rl3Ş W)Q̠sى !ӫ_Ͼ˟OϿ(8& dTD%e/څeT|($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihl NvH=j蠄j衈&袌6裐F*餔Vj饘fX%iN:|Z jꩨꪬ꫰*무j뭸뮼;X;x &6F+VkؾC0-3;|[ *6x؉rn4@ l«At/%J,o0H' 7G,Wlgw ,$l(,0,4l8a΂TA#Y Bϋ HH'L7PG D7hi|AYK-'ۂ=@Z(׻L}7ʆtmx|߀.Pu D(.PaWngw砇.褗n騧ꬷ.n oP/o'7G/ԛ @5RB24M8])>T[@5DWvF7Y>QdSEBANXVUVf][DXoGcwgfh|kcemwqx{9K9`BtO`ZvGvW}y^zdx|}<~[ۑm֏]P_eRf򁄀öżźɽɿ̟Ѕ6 ֟ ס7߇F9"58lcv4#Jbŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KLr3k̹ϠCMӨS^ͺװc˞M۸s޽  f@μУKNسkνËOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,08T 8<@)DiH&L6PF)I p\v`)dih&z)) & nҦ) tb矠@ԉ )d衝8 }"*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6b"gjB0Z:ԎZnEpD!(jIt+kMv z@' 7G,Wlgw ,$l(ܟhmXg\w`-ZXRJlC@5$n=(q<)B4M*6H99@)>TFL[@=@D>AJ*KY5DWQdSEBBFRVUVk[Nf][lb[|jSDXo[^aGcwScugfhemw~wsvg&?=_=tBtO`Zvi~W}|[3j5wNndx|}=oTY˄S؃_ۅ[ۑm֏]IS_RV쁄żɽŰɿǿGļA͍ӧ(̾Q.c?R&5P_uz'M! E*<ZJt s1c%, 5ŠǓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3kΠCldtzcig41& QfW [{0(F ΰ5@N]Պ e|,|Q03Oy˟OϿ(h& ,,eVXfVvG@quA(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlpƩm`$gOÝzѐKT[@>AJ=PG5DWFk>Qd1cxSEBQLOVUVeNDk[Nf][WmTlb[DXoGcwScugfh|kcemwnmqqx{9K=_9`BtO`Zvi~Gvy`3j3zNn^z|}<~dY˄_ۅ[ۑm֏]kISPeRf򍍑öżź@ļ:͍ ӧ̾J[8L'+IYuZ2 !>*3='#,8*)$89=3314?O83G&E8$n=4M*6H99@[@>AJ*KY9lEQd+SwSEBBFRVUVPPXk[Nf][lb[|jSrmYDXoGcwScugfhqx{&?9K=_BtO`Zvi~y|[3j5wNnN|^zdx|}=~oTS؃[ۑm֏]ISV_썍źɽſ8ʸПE7ڻ?әG4FP'5<# rD 2!F #Jv`ŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÉ ^̸ǐ#KL˘3k̹*P1LӨS^ͺװc˞M۸sͻ'6Nx%+_ sxyW\ËOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)Did>$ ,)TViXf\v`)dihl*.10ix|矀*蠄jZ袌UFVZR@@d駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfd@бJ1rY'-DșPDLQk @d1l' 7G,YPȆ;,Hll(,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp)xl@CE/# A9GBr:d7M~42t8砇.褗n:,[N@B@r>2wI ) <#>U!,#/,5%!$)! 8"9' 78*)1-3 4==33O83G&)O8>@5RB2)B*6H99@)>T=@D>AJ5DW9lE)sT7Y>QdSEBQLOBFRANXVUVeNDk[Nf][lb[DXo[^aGcwScu|kcnmq~wsqx{vg9K=_)bBtO`Gvy|[Nn^zdxo~[ۑ]_Ձżźɽɿǿ;ȱ̎9ԧڋ(Q0A  HpSx\*Çt@Hŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿2a@ÈH<! A EކCmy3 Z*'h)\bԵs2Qa {)t t T].R) 0fbسkνËOӫ_ϾcE(EϿ)ߋt]0t 6F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TV鞀ceF–V0'=A҇lp)tix|矀J @j &Y tAF*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼J kG6F+Vkfv+k覫+k'H|Ј\0t7G,Wlgw FH1fb a ,'V4l8< >-DAH'c /d1TWmXg\w`-dmhlp-tmx|߀.n'7-w pD C@8砇.褗n騧:ġƒӐO0q. h[!YLpHMԮsp $n/؎.H#Pꁲ9/o\1 "@|AJ<DGI_t #AL!,/,5%)!'',- 8" 7 5 +#&0$89=33G&Z'E8$N?)O8$n=4M*KY9lE)sTFk>Qd1cxANXf][WmTDXoGcwgfhemwnmqqx{9K=_9`=tVzO`ZvGvy|`3j^zdx|}<~LdY˄_ۅ[ۑkIS_ՍöżźЛ=־6. B35rǎA(`@<É5ȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠC?ӨS^ͺװc˞M۸sͻ Nȓ+_~̣KNËOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)М H6HTViXf\v`)dihly %ˬ HI2 ,矀*蠄j衈&袌6裐F*餔Vjap0*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfvy l N m KV9' 7Gl Wlg1~?FD$l(,0,4l8<@-DmH'mq (PG-TWmXg\w`-dmhlp-!E|߀.n'7㐷@D"(`!,1,5%!)!%''' 8"9')>'#,8*)&%2$89=3314?O83G&Z'E8)O8>@5)B4M*6H99@)>T[@>AJ=PG*KY5DWvFQd+SwSEBQLOBFRVUVPPXeNDk[Nf][lb[|jSrmYDXoGcwScugfh|kcemwnmq&?9K=_BtO`Zvi~GvW}y|[3j5wNnN|^zdx|}=~oTY˄_ۅm֏]ISP׍öżźɽŰɿǿH&DžUG͘ ΏO)ٮ%hںD26VcELT ;@̀@NQ2#@Kw -ȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KLP ,k̹3%%4#|"H7و,m"9Aq]$Zp8d a- Ûr ~ӫ_Ͼ˟OϿ(A&)f 1|]XYiy($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihIa lCqڵ u&uF|hM*蠄j衈&袌6裐F*餔Vj饘MG@jꩨꪬ꫰*무j뭸뮼믶=@ @F+Vkfvv:kQ"k*Q+T ,l' 7G,Wlgw ,$l24 82l8<8`R\&[GlA;+I< txvW3pd lp-tmx|[:s ۯG.Wngw砇.褗n騧ꬷ.k14/o';JL6l3w/o 53ۆHnD2\0 sp Q1NcD@H4bJx1! :D0F }jAH(L WH,+̙03OgX ]%!b^.^C#:!,&/5%!$))!'',- 8" 78*)1-3 4==33O83G&Z')O8>@5RB28]*6H99@)>T*KY5DWvF)sT7YFkS}>Qd1cxQLOVUVeNDk[Nf][WmTlb[DXo[^aGcwScugfhdfo|kcnmq~wsqx{9K=_9`BtO`Zvi~Gvy`3jNn^zdx|}<~od~[ۑm֏kIS_eRf򁄀żźŰɿǿ>ʸПK*ڻDL;NYM( A rJ G#Jv`ŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^LwǪ@ʱ3t Vʡ̹A+Mpڸ3xAr}B“+_μУKNسkνËOӫK𱎉w+X߀h& 6F(Vhfv ($h(,0(4h8<@)DiH&dzU!E 'ߔ0C\Ed^h7kp)tix|矀*蠄:Z$2pAF*餔Vj饘f馜v駠*ꨤjꩨ| 5 Mjc@뮼+k>FC6˕F8+mGMkF\KKD+k覫+k,l' 7찉OpC&,zbG@ 3@Ux1fJ9 jeK`PG-TWmXg\w`"B@h\$@ U[p-tmx|߀.n'7G.Wngw砇.褗n騧ꬷ.n;F/!?. R 1A R#H򛽽`F. Pr&ɓ &/o^ kq `!,- ,5%!$)))!%''',- 8"9' 7 5&0'3)>3= +'#,8*)#&0$*5(*3-/9(,=1-3*54 4=$89=3314?O83G&Z'E8$N?)O8>@5$n=(qTFL[@=@D>AJ=PG*KY5DWvF9lEQd+Sw1cxSEBQLOBFRANXVUVPPXSU_Y[_eNDk[N{XBf][WmTlb[|jSrmYDXo[^aGcwScugfhdfo|kcemwnmq~wsqx{}Wkcvg&?9K=_)b9`=tBtVzO`Zvi~GvW}Ly|tzc}[`(f3j5w3zNnN|@{^zdx|}=<~otLTd~Y˄S؃_ۅ[ۑm֏]k?ISP_eRV_쁄öżȃӘźɽŰɿǿH*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳOAIH*`)A?NJ:2QQ H PhIgY znZHP!>(x`L& ak+>NXh%M`[z ij85S |&يťE(@$%H0(ӺlemT=`i*. 1q(P7BKa38G Qe ^F~=zI!}E]ԠL|dAtE [a "9tFgp]R1(E^Dl2BTb`% ~ Rmh/PP\W0rG&у?HC>@Tf Axd2$Do&4CG)N"D1@)P UCZ&tDV"cA%Fa*BD9-eQ=@!BG6E)PX`L.tU@1qRQ"{AA{DI С I(P.T&@YA ͋p7a &Hd @&FA(JHlqZBpE$H;$-FA3DaP& Av2.a.Bi4D(3wy(ByD@t2-Ÿ'I *П]QeQ| qH^a!ᒐtKlP'wdK#b&94QRUB3N`DL%NR)gBn/ӝ'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH8̡w@ bL#qAm&vL $FgUAo ?\D1 *qXOc@EtH"ʈr&!T/(Bƍ#@!r#@J- N#$ #(4 "3IbDa Ac$_VD1Dn C>L"/ mCCQ$T(9kB6~h#z{1nv[JXRN9C(5 & %_/uB i'FV"πMBЈNF;ѐ'MJ[Ҙδ7N{ӠGMRԨ& ˦հu*k"=  /u{ ;WI|6 LIq?RT fm]?\upAX+|"#y'|!1.NOCP ;( ^j$aq#-nHl'd@qRA#$/2-<"4:bC䟶pnOAbVMe #Tp7A>HU&N.&NA%M:D&R;Ba+de6]*]|qzNw B@x ;񐏼'O[t#A"3;7$ꩇ2s9lXľ~m >i %#_ M"?0yDV{!n/ 5^DޟE;lDB!:o;dlyfp$v Ar ؗ~,@aֳwh "8$X&x(* Gxa~,<PK1^2 g:5 fP{A|rC"!}jHJ@W#ZH!d*~qKhp&JW -q\"at01s1|<{bXp{ǧ 6=y3u y= `".v29Atx؊8Xx؋8XxȘʸ،Y!b$uڈ>Wc1 {P GϨ]Jy3O%[E و !&!sp`qaeqygV Qxّ "9$Y&9jt ` L3# e+'c#m(x'R@}ZEVD>? <`ark}a[&~9Fg\y=Mim-qPqM8=, !u>Mݓ0hQ#pRQi|1! HIWs`|3.9nR!QQnP M1 uFfgym\) "=$r;qc+  P%&y5-@h7B@4?9YyLޓxDܳH)pbl K,a@Yi`1 ! z J~f I  IWG=)iŜaw 9+xk C}.֢Y "і{A;u7aSrdqR:id $P=Q-C8LzhʥP{xw Qa U@q#qqQ)ez|ܣqF(:i_uɄ7qR A"*9hi>Im aHȚʺڬ:Zzؚںڭ:Zz蚮꺮.GʮH IZm D:n i<6&jT=H9es0kTa!Q!.ً 9ۤ~ 2;4[6{8:<۳>;hq=qT牑P㘬' 5$Gxn&@xz@3=$",'#,%$/8*)#&0&%2$*5(*3(,=1-3*54 4=$89=3314?O83G&Z'E8$N?)O8>@5$n=(qTFL[@=@D>AJ=PG*KY5DWvF9lEQd+Sw1cxSEBQLOBFRANXVUVPPXSU_Y[_eNDk[N{XBf][WmTlb[|jSrmYDXo[^aGcwScugfhdfo|kcemwnmq~wsqx{}Wkcvg&?9K=_)b9`=tO`Zvi~GvW}Ly|tz}[`(f3j5w3zNnN|@{^zdx|}=<~otLTd~Y˄S؃_ۅ[ۑm֏]k?ISP_RV_f򁄀öżȃӘźɽŰɿǿH*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳO2I嗢H*a),NJJrBCsP!}tAtOEJSQr,=4SHNc4pB=640_~ -(Hac*iά'vC%c x' I8b1$PDE#/%6o" }Gl*Z`Q``1@p(1k**=Hv<Bmh ysLQ/%}x A 4GgQJdBr2<A`P`P$CQA!LD%]A#5ҐuFo4Ir1$mB !DEt&E rz hit%"]RbABZtdDS"Fs|E624Aq=pFNёMIw])eBMWD!BXPiPe'A,hDS.T(DTP5dfB>`ЪRi]Ћ@Ж ц:kB!ԟͰ^8H^2H)X,EX>t(*-P%6,Ct9D@D`PxY%D Ÿy&J;Q&ydDIF"DamA\P&.$YH P eFaRԙA A g%1'4F&h$\_m IpPlĵY*~APM,%gБŌQk4Db@j6 @gբA)JTrċ-}βq/9=}aETIA=|& Q;A#@Ӧ/o'7G/Wogw/o觯/o HL:'H Z̠7z GH(L W0 gH υCha C !z(8OF/P7@ W"Wb(Wԧ_DW""[!(\Bn1,fC>@X+MQcrr]2!Au8ÃԎ*6"F.$Cz(^HvƓ0!#D蓡"G$` qG")]*Dp/RIT94"ņuhΤ 9e3Gx!tdH# YD`d?!dLpO$  r=HT": n" ׈1aW'p]{(unĎr8;Xه3I@L"HN&;PL*[Xβ.{`L2hN6pL:xγ>πHFBc1Jxu1tB"C2qsiU% qG`\FaS]\TW@*D ^(f-B"KQ0M"53 +x}= }n{Mr`ҭH۹`B0w iC܀< !=1h{!Ȃ\ ;o fh򨏸ݟC: DIb$ZI޻|v2ߜN'p, IF$ `ALPmA {`NhOڵ'[]ksN3"A#n'O;GlQ, mAhxEH~_#<z@=|kpR!|VgZp gH{u<5hH!q7?q=G@O~@{OO?峟~`O8aD089GGWj0qP?G܇x ly=mn@lo +p;KF s R@7 n}7nT`V AN7;Wg~G x mWAU Qq^tȳӷoZxwtXvxxz|؇~8Xx؈8Xx؉Yc(>FWmp;AM>5FW3N;C`0Ak(~T4zpZ\Ս8Xx蘎f  .Є/gf`T#:1>0P!xe]XdXА5=7 @R&zwd_V^ '_ӑh:y#/iv3aaU(=nxQ92#I!b6N'ð1{ qRt-3.!xpfė64@!wA&-HV`a :Cv _!z!I q!`=&7;p 7xmpAAz爑.qq[FUeٛص 4iWmi>y7'0Al !8_4Oz!i?@f{tV R%qعf[ChK *a 18 =qZf wz1A\&U   ¶>T`Y@ vwFXna!~<(q rf:yy]ɞigJ|y>0Gڌj1y-{:Zzڨ:ZzکX8qjWzɔFj!` jw[񜨺A96b!ʺڬ:ZzgPxv ]!,-,5%!#))!'',- 8"9' 7 5'3)>3= +'#,8*)#&0&%21-3*54 4=$89=3314?O83G&E8)O8>@5$n=RB2)B4M8]*6H99@)>TFL[@=@D>AJ=PG*KY5DWvF9lEQd+Sw1cxSEBQLOBFRANXVUVPPXSU_Y[_eNDk[Nf][lb[|jSrmYDXo[^aGcwScugfh|kcemwnmq~wsqx{&?9K=_)b9`=tBtVzO`ZvGvW}y|tzc}[ x(f3j5w3zNnN|@{^zdx|}=<~oLTdY˄S؃_ۅ[ۑm֏]k?ISP_eRV_쁄öżȃӘźɽŰɿǿH*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH+ $< e|՝J<Al<ԓֳ(X5m\y!'w EȬI娣f &rx0QAB0@7Jͨmy{pHI5 _) pAE8 N5h pSA +`y3&qfO{ah;1 #|!{M wq aCm5!wāBx4(I $No@Ry>q_AjtA`C G'IdfKP)!dGh@$@ і'1`!B/8@%Cu2n %'|&AUUF*餔Vj饘f馜v駠*ꨤjꩨꪬ*무j뭸뮼+k&6F+Vkfv+k覫+k,.AAL<o2*0MMl`S+KC$1B%Q.JKlsRu4ͪlѬ IsK0`TQ(R*d3F PBTB=[[NT]*7m|߀.n'7GnWn9osRd勇ޠ ]4uZCm_tK'?̎PaZP!&+H)gI(=2DQW/o觯/o HL:'H Z̠7ح GHMv*fBp0aD2 8̡w@ H"HL"R`DdՠuLqXJb+\QR:$FE&a,#FOp4`x*6Zt hGQ1}UBL"F:򑐌@FIj̤&7(8hTTΤ22Zb;j\D&JIeA LID!r/`$$f. Tj*K!)XR̦6nz 8Ǚ3$$rrtF{pgH|aRBb~'TT /`2"_XAԉpA1--a!YADPHB  v9 @}FAėbh e&s(MJԢHMRԦ:PTJժZXͪVծz` XJֲhMZ/Hpr'W4'9`KMb:d' ,$QV#[ kMH}@Ih%C fF0z e1BDJԛjfIl+ |؞m+ao$Dl Yt ],o`bDSBv f  Ȇ+ |K_V^i}` ÔK׬+"!YWY3bJ!r\[1l*K}JDDGPA"1!#0NZsATA0!YE%AFi;!/]"xU}T,B+c<uEOWy!UQhN6pL:xγ>πMBЈNF;ѐ'MJ[Ҙδ7N{ӠX%CMRԦ' TN24-n^MbNP\䶮ӻH^ :jCm_D@kC$a0[n˅A W  zRPC^sWZ"& -eSA$^t!U ne{ mGN0 &)X giy4UnRA ֢ߤK8Iqrm8&\!12 HغCLض !,-,5%!$)!%''',- 8"9' 7 53= +$",8*) #2#&0$*51-3*54 4==33O83G&E8$N?)O8>@5(qT[@=@D>AJ*KY5DWQd+SwSEBQLOBFRANXVUVeNDk[Nf][WmTlb[DXo[^aGcwScugfh|kcemwnmq~wsqx{vg9K=_)b9`=tBtZvi~GvW}Ly|tz}3j5wNn^zdx=<~od~Y˄S؃_ۅ[ۑm֏]kISP_eRいöżźɽŰɿǿH*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH tPJիX~+r|!,FfӪUڷ U<ݻJ 4޿#0(0C3tu.Cms-+\4RE> J ӨKƫ9Z)xR# ^Q̓" 3B$"aOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6$F9TV VX]qdi"aaw9q$;P|&P~աG CB4)bvȝ?iȜu)]$$2]=Typ=5]+)r JQ!(2_'I"D4M•O*k&6F+Vkf~^l۬\GPH%H=I[l6إ Ӟ%PȔWH T +ǡRq+\w ,$l(,0,4l8<@-DmH'L7PG-TWmXg\w`-dD-Dvmwޭ|߀.nEp1I~GFOn.nЦ/vJ'hIOT@T9kH 3x@JLM Z̠7z GH(L W0 gH8̡wxd oH"HLH c"B M dDj.z` H2S@XE4$n$m)ЇyqAC|1#F&- IBAuxŃR"L9r?.xNV򕰌,gIZy̥^gSdvC<. 怎̑cDeE\(LD6 8IrL:v~ E AMB66.tc@IG !<ġюz HGJҒ(MJWҭ-Еlk7+crS$6[Ӟ@ PJ$孑@(R?6r$E7d ܄lHUxqt*#BpCb2*Cf04qEuzP \M0\.|c'KZͬfo1W4!@hqa+*R{,gK8R~YCxPB !,-,5%!$)!%' 8"9' 7 +'#,8*)&%21-3$89=3314?O83Z')O8>@54M*6H99@)>T[@>AJ5DWvF9lE)sT>Qd+SwSEBQLOVUVPPXeNDk[Nf][lb[|jSrmYDXo[^aGcwScugfh|kcemwnmqqx{9K=_=tW}y|NnN|^zdx|}=~_ۅ[ۑ]SRいöżźɽŰɿǿʛ>ֺ#G="`.3HB xh BBB^Jt3  CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^X#K65 +$%YBސe;e<>26(.%0!^hAm&,Y"᜘ҜJዓu2k'E`02* yU˟OϿ(h& 71^4(1A0)P(] uOjr!O& gTwddJt͈X|:@)DiH&L6PF)TViXf\v`)dihlp)tix|矀*蠄jvhXС)Ytd6}AV馜v駠*ꨤjꩨꪬ꫰*+&91뭸 +k&6F+Vkfv+k覫 qϪ0_|m {0"E V嫠EZDS gw ,$l(,0,4l8<@-DmH'' .MH<.r 58zVg\w`-dmhlГJ1 kd`-߀.noiY7YiqDܥ]褗n騧ꬷ.n/&Hvd+?6c;W/kXXw/䗯槯B/́ $@ HLBD `00t a [^02)!C f38‚Xh\WNH"HLVVlC̶pz[j0%C̪D*1 ؉@!,"1,5%!$)!'',- 8" 78*)1-3 4==33O83G&)O8$n=RB2)B*6H99@)>TFL=@D>AJ5DWvF)sT7YFkS}>Qd1cxSEBQLOANXeNDk[NDXoGcwScugfhdfo|kcnmqqx{vg9K=_)bBtO`W}3j5wNn^zdx<~T~[ۑ]IS_ՁżźɽŰɿ;?9!ʻE*֤)Rޅ"1E ل#=Hӈ TB *u  #JH3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3k̹ϠCsxӨS^ͺװc˞M۸sͻ !iTK~سkνËOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@XA~DCH2&K APF)TViXf\v`)dihlp)tix|  g%A&U|@di. pfQv駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+Vkfv6Ӭٱ$,ȥk믛0+' 7G,Wlgw ,$l(,0,4l8<@-DmH'L7PG-TWmX_L/PE$4AMj TA!R8+i1 0-@s(+P7G.y< b :>FIh3-0 :,׎I !,1,5%$))!'',- 8"9' 7 53=#&01-3 4=$89=33O83G&Z'E8$N?)O8>@54M*6H99@)>T[@*KY)sT7Y>QdSEBBFRVUVk[Nf][WmTlb[DXo[^aGcwScugfh|kcnmq~wsqx{9K=_9`=tVzO`ZvGvy|`3zNn^zdx|}<~oLdY˄_ۅ[ۑk镁żźɿǿG> ̞͏Gڟ=ޣ݃ V,FCQb8 Me@{CUAJHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LO@̸Dž@F+¡eMP- Y0;3ɡ 3-ZHTMP"Ri-k_ذ2 .:()C;ËOӫ_Ͼ˟OϿ( f*À&M([D8(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf7VI\=aC etElx]p)tix|矀*蠄jh6(qI5"BVj饘f馜v駠*ꨤjꩨꪬ꫰R2 8::ܺ*k&6F;VkV \p 1 ے$D`Ak,l' 7G,Wlgw ,$&K1B#k ɇp4l8'#,$8914?G&Z'E8)O8>@5)B4M99@)>TFL[@=PG*KY5DWvFQdSEBQLOBFRVUVDXogfhemwnmq=_BtO`Zvi~GvW}|[3j5wNndx=~TY˄_ۅm֏ISˍöɽŰɿǿĚ0ɖʎ4י-.2,89!/1!T*\ȰÇ#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷/T LÈ+^̸ǐ#{ ˘3{ϠCMӨS^ͺװc˞M۸}*ͻ7} ȧRN^wKNسkνËOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<6UCmDiH&L6PF)TViXf\v) e7*cvS@lp)tix|FAA J. 袋`8@Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F+i7Z& B:&ȚIȂI^U 3!,l' 7juWlgw ,$l(,0,4l8<õ=-DmH'L7PG-TW :p

<.B!,"1,5%!)!'',- 8" 7'#,&%21-3=33O83G&E8$n=RB2)B4M*6H99@)>TFL=@D>AJ5DW)sT7YFk>Qd1cxSEBBFRVUVeNDk[Nf][lb[|jSDXoGcwScugfh|kcnmqqx{&?9K)bBtO`ZvW}y|[3j5wNn^zdx|}=<~oTm֏]ISˍżźɽŰɿE<ƞ#ʞ+: U*!BAHO8I@Q1o*\Ç#JHŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+KsE!qȵ},d̮KȆX(3L ~'(#AZ\`i~[(ޭ,L 0l)6kd.c༺سkνËOӫ_Ͼ`u4Ͽ[0&Pr'\~yI F(Vhfv ($h(,0(4h8<@)DiH&L6PF)TViXf/aX̖%=;`D eʄElz(]p)tix|矀*蠄j(4qhH3$Gf馜v駠*ꨤjꩨꪬ꫰*k1@C cC+k&6AJ5DW)sT7Y>QdBFRVUVk[Nf][|jSDXoGcwScugfhemw&?=tBtO`ZvW}y|[5wNn^zdx|}=MSJװc˞M۸sͻ Nȓ+_μУK@سMC{e!=.xOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,0(4h8<@)DiH&L6$AOViXf\v`)dihlΙ )Y v矀*蠄j衈&袌6ꨍ <*ui0T)1 -9. 4穬꫰*무j뭸뮼+k&6F+Vkfv+k覫u¸ nB@P\k' 7G,Wl]l LK ,0,4l8<@-DmH'L7PG-TWmXg\w`-dmhlp-tmt#w#%D`ߌ7G.Wngw AX!,/,5%))!%''',- 8"9' 7)>3='#,8*)&%21-3$89=33O83G&E8)O8>@5*6H99@)>T[@>AJ=PG5DW)sTFk>Qd1cxSEBQLOVUVPPXeNDk[Nf][WmTlb[|jSrmYDXoGcwScugfh|kcnmqqx{9K=_O`Zvi~GvW}y|`3j3zNn^zdx|}=<~dY˄_ۅkIS_썍öżźɽŰɿIA˄Ϗ ׮NBނO?\0 8ү+dD&DR(‹ BI(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘Ś̹gE>FO,!3m(5^ A'm$f!68&Rl3Ş W)Q̠sى !ӫ_Ͼ˟OϿ(8& dTD%e/څeT|($h(,0(4h8<@)DiH&L6PF)TViXf\v`)dihl NvH=j蠄j衈&袌6裐F*餔Vj饘fX%iN:|Z jꩨꪬ꫰*무j뭸뮼;X;x &6F+VkؾC0-3;|[ *6x؉rn4@ l«At/%J,o0H' 7G,Wlgw ,$l(,0,4l8a΂TA#Y Bϋ HH'L7PG D7hi|AOg-؞ldrwyzljmx|߀.n uP@XWngw砇.褗n騧ꬷ.n.K R.o'7G/WorpӆF]AO{觯/hpAjRcArpY xP2D +iZHV̠7z -cԺ > ubZk(l$ ck@ b'!,/,5%!$*)!%',- 8" 78*)O83G&Z')O8>@5RB24M8])>T[@5DWvF7Y>QdSEBANXVUVf][DXoGcwgfh|kcemwqx{9K9`BtO`ZvGvW}y^zdx|}<~[ۑm֏]P_eRf򁄀öżźɽɿ̟Ѕ6 ֟ ס7߇F9"58lcv4#Jbŋ3jȱǏ CIɓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KLr3k̹ϠCMӨS^ͺװc˞M۸s޽  f@μУKNسkνËOӫ_Ͼ˟OϿ(h& 6F(Vhfv ($h(,08T 8<@)DiH&L6PF)I p\v`)dih&z)) & nҦ) tb矠@ԉ )d衝8 }"*餔Vj饘f馜v駠*ꨤjꩨꪬ꫰*무j뭸뮼+k&6F;*r%3< VGBZĶ覫+kimdЪ* l' 7G,Wlgw ,$lӪp0,4l8<@-AUꍪZ HTWmXg\w`%%:A@ˆ1a-tmx|̫"TjE t D }6d.z(!,"/,5%)!,- 7'#,8*)1-3*54 4=$89=3314?O83G&Z'E8$N?)O8>@5$n=(q<)B4M*6H99@)>TFL[@=@D>AJ*KY5DWQdSEBBFRVUVk[Nf][lb[|jSDXo[^aGcwScugfhemw~wsvg&?=_=tBtO`Zvi~W}|[3j5wNndx|}=oTY˄S؃_ۅ[ۑm֏]IS_RV쁄żɽŰɿǿGļA͍ӧ(̾Q.c?R&5P_uz'M! E*<ZJt s1c%, 5ŠǓ(S\ɲ˗0cʜI͛8sɳϟ@ JѣH*]ʴӧPJJիXjʵׯ`ÊKٳhӪ]˶۷pʝKݻx˷߿ LÈ+^̸ǐ#KL˘3kΠCldtzcig41& QfW [{0(F ΰ5@N]Պ e|,|Q03Oy˟OϿ(h& ,,eVXfVvG@quA(,0(4h8<@)DiH&L6PF)TViXf\v`)dihlpƩm`$gOÝzѐKT[@>AJ=PG5DWFk>Qd1cxSEBQLOVUVeNDk[Nf][WmTlb[DXoGcwScugfh|kcemwnmqqx{9K=_9`BtO`Zvi~Gvy`3j3zNn^z|}<~dY˄_ۅ[ۑm֏]kISPeRf򍍑öżź@ļ:͍ ӧ̾J[8L'+IYuZ2 !>*Oe@tZS7= GZZVːdA@ ?5kg.@(vK۲RE^ڀg-oOɹbfz::-VӀ v-ϕh5:. O3kG2@/,U EZo^f*zӳvw9|LD#\L>@:6 W/k7:@vaz=?6劸F@{r r4>8G}?m#ox>uG +xH`  miۮ}ހ.s/9v+M~`}ljYʑpr_v+`/lC "te8ZyWLnob F@ ;7ko*@R'lJyG ;j\*(4\93,wϛHwU]roR'h]8^fZW *}X4{>-pt#'nΛ #a~=}P s[H`p&ŰdښWzɑ@R;U._I̝K3`z3n{jH GgOи3yX2E+]_s 3hzMpF3˝G5`^h6Fih<q,6 r5V׀1\cz,毗#~o͓0jYi+QZ.|Ҟguބ de( Ϣ0<FHC 3=jh$ͣpbzg6̜ PNv.DvX1@zG g978-<_`yN›ح_/@/Dz؍"E'r>ϳp]3~4ǒf|rM  !1AQq0BR"2Sa$@b#6dr%CtUcs?A9 RU D9 y^oKMo雭Lg=aGI|Gc,BElz7m M E ::j(W(Yhf6FbIvA '?=rGϣۈqp3oSDBxCМc?Yc'gX@Y@van<_v@7ED CVЎzVĿa#umG[MpZ)x_<`r{MZԴнt(xUO~ԻԳJOOW{ \DGX}m:r cR:6XnQBR7C{ب EW;h.vM\ V`OݴʠcU!- ҟɏ, y];m_l?O/ZnYrπ)R-ʕ[ ֋{AvT=2/`WX8EUmNPȒ/Fh-wV:Xt|>I׃$2݌FdBpX\&\fb^Vϸu$I #3K;alZm07$82CdjeXos[Z_'ڮ&[TFwztdqBJr:\Uy-+!_F%I*j؇a莑hYdH4( @D]5`yl]G؜oNPF;ջ0˟NGx'GSF"v %S[eL0;_üZu/TJh:Qn*APե} dޭsbN~ջ:GQ_73R7du`ȏ-;F۟>b<5` %DR缓1/(%L;T4OL:!chxu6jFGTnbDiݠ_{8ýK[Zi1 ?r߫w_TSiޥaAb}i[IB39" tD`> D :qj'=[U57 'N~o,~V~-iDP<;&Qh%U:"hjeOUӻ8ӑWF/~uaNZpmzqu9Ho-lxUA$ƤV]*`M33bt(psne=# xۄr vQse_ŧy11Z**rE$zBWqSB>8v%[ B9X>č;?j?٠Z !s_~H9Yi+ =cs7~*r vzUKU96Zu=$`%z_ S>t#V"Y7(zDf-0Bz~xJ_͒{ Ӆc2zxsqr8voNa $YьmFk)g*+3SrVoO'oz+ԪtPpS~T:XvNI*rJ{# AljnԐ9e >wѦm8+‡xV1b~>aFѧw57T}5YpS0OuvGUFGZM|WðӺ8KtV "y['%AQ'7:{8~a5*PfO" Mu fLy1(d}LttTD^ez~?9l72,?tZ5-%q9M.3%W#6GY#WCW 0̿5ervѥcA1v[fB%BGY>xzy!"=e'r妢35,t+6 Ams֒Aғ#x0y]QǁpB_?ڷOӻ7Ά>l[ņL sёr U [-܈F?Zm[U s6gvFw9:0} b|@fa+w/2rsȃb4H¨{fs㐺U,򎁴n$\r>_y~tN QsjZgk~Ɉi݉繌c3MH$ꉨz]⭵yM6Ѹ5.7}jçt?COu]atcwUg^ IrGMZ[>,Co0=!˛ 1]?[\ TB/Q51> k.N# ;0_!r#6e+"JX!8ӺYϗub0F&f8i U7]Xx1U e?@Û#?;>4F%sLjX&.w#ŗUnXzӏG@IS%l5P%E 1EDu2N˻NgN6#o Fogp鎝M`ϢqՍyw6\N^ -o*0@\٦oB CV<@pA]v"{iO]x-1Lܬ}mg̑gmkIc%@5bҭ.[rvSgGptGV;@H@Ӆ[ ybcC@FF:m%СjՎ}E;BؠА9> k͹(9yъiNf w/3t?ӻ$ex',Q!ӃDŽ(z,|\/3be"yJAM T@ϧwvkvuU!?/?fslCx-Ψ سf=$Μ‡NJ0׷,7:+tנ6yHUoR,x@9_:GA&"Q{g1c@4)T+WM{;"=jufa5n^<"03~ᆦZ|MA#No!oR@JR'_WO{cL\f}]5PTE;M=*8qQ/K]:` }=VsYρ-핖| }#l7+$P# r˳LnȮ̤2 IGCo!+ű,Uh%\#3}:QI#R%QN֋tv=- #%0ϐYo0 e;^%}j/)?6 A ZxI䨃2N{mWSR)̮Ytݤnzp-~$Q̅&EJx^HQH~.v"砬p~ոro j&"8haJZC7Dwt\ >1 < FWs:2DE Ϸ覥q ,/4Ш2ZRLA&cD)SM=. FDhVj&,ऐK$2:=%m$R2+qUnؚ֑:aK"w>&4A se1SR>)檙aAfN֭\;QzϨmdÖ_1< o56Hx];KՊf naAv~[i۩!txr-0ݞaxR<vC8%sGjm'$JS%:5}h0 Vh.$>mKGMC%Gp'XJvvz|FV9c Oi#c=ƭVC幎wxdO'ÐO|Q %%vX(#\ 2y;Va w<)v>m=C1Bv(,B u<3 xR *, 4\d#r ^s =M4^{6ݽ!eTj %eQ$agL_6 Q!0aq1234A"@B#R?-J֤S_2dzb2\{:'/ď0AI چpW??CѠVh0 qe$i1KShR Ku:J4¥O/O˃>LWj JhCעI_ 9cّe9}،v憒ApyB™u=8ADmS_)i- !>(1âYO n AQ鸔ETW=]+eV}, ^8~ٕh8dE҈Yc)ԅvT]i\-8儨(lb4֝ߋSQ)oDZ 2t[tWw :sLCVZ"Z P}-T5DS!NOL!+e'R UI͉47M|4锺l1FJ)E*Sj^XR'K .*myB|NZPD6sejBxį#tٿu.r :08Ά}0*шMbtQO}\.uZN)Yya՝rHRA͏dc U( i@NBٓoL'7MpqT@uCuo,Ua鑶(R BٯJԦeYYZʏL2^I.Hl9ڮU7i@&٢ J.5GSWǻ0s:mZ+ >7[i]VjV@I;ʺXLOpaZJ̛Ӈ'-}2Q+3l'cV?hZ%F(o`i _G74|pR-u#1(iWMNsp;tH"鑬A-AV7ucHer(~y&wK.y`ԝB =pQrM@6̼!s^x HRkO c~n}Cuo ڭ4K%M\!HL巕FIS -ޮ o|sU,Ry[MZ>DQ\=nWB2CK OM!jFuӨti b[M( vQL(P9FF@8yg`2jyn^RETua*joJMFء7Ṫ}4qLUŲ֥+^b~qk\4uc-(Qå%WaRamy-RNi5A6X隸nbM8!TI@泇|VXFfkl]uVq⩏iDRaqOQJ6@F%Pg2OeN6 S(z qk5Yɨԕjǵ")h¦QRZԳSn6!Sz qnץ*)!C0R45dvpÑ1 @0P1? ====?36*yle<͊7l{?͊\®VgfSRM MK56jl,|ڙږj{mLK5=6OwSjl,|'zn{{\<(|psS4U:::7GEcuvu-0.5.3/src/000077500000000000000000000000001416513034700131315ustar00rootroot00000000000000uvu-0.5.3/src/assert.d.ts000066400000000000000000000036041416513034700152270ustar00rootroot00000000000000type Types = 'string' | 'number' | 'boolean' | 'object' | 'undefined' | 'function'; export type Message = string | Error; export function ok(actual: any, msg?: Message): asserts actual; export function is(actual: any, expects: any, msg?: Message): void; export function equal(actual: any, expects: any, msg?: Message): void; export function type(actual: any, expects: Types, msg?: Message): void; export function instance(actual: any, expects: any, msg?: Message): void; export function snapshot(actual: string, expects: string, msg?: Message): void; export function fixture(actual: string, expects: string, msg?: Message): void; export function match(actual: string, expects: string | RegExp, msg?: Message): void; export function throws(fn: Function, expects?: Message | RegExp | Function, msg?: Message): void; export function not(actual: any, msg?: Message): void; export function unreachable(msg?: Message): void; export namespace is { function not(actual: any, expects: any, msg?: Message): void; } export namespace not { function ok(actual: any, msg?: Message): void; function equal(actual: any, expects: any, msg?: Message): void; function type(actual: any, expects: Types, msg?: Message): void; function instance(actual: any, expects: any, msg?: Message): void; function snapshot(actual: string, expects: string, msg?: Message): void; function fixture(actual: string, expects: string, msg?: Message): void; function match(actual: string, expects: string | RegExp, msg?: Message): void; function throws(fn: Function, expects?: Message | RegExp | Function, msg?: Message): void; } export class Assertion extends Error { name: 'Assertion'; code: 'ERR_ASSERTION'; details: false | string; generated: boolean; operator: string; expects: any; actual: any; constructor(options?: { message: string; details?: string; generated?: boolean; operator: string; expects: any; actual: any; }); } uvu-0.5.3/src/assert.js000066400000000000000000000123441416513034700147740ustar00rootroot00000000000000import { dequal } from 'dequal'; import { compare, lines } from 'uvu/diff'; function dedent(str) { str = str.replace(/\r?\n/g, '\n'); let arr = str.match(/^[ \t]*(?=\S)/gm); let i = 0, min = 1/0, len = (arr||[]).length; for (; i < len; i++) min = Math.min(min, arr[i].length); return len && min ? str.replace(new RegExp(`^[ \\t]{${min}}`, 'gm'), '') : str; } export class Assertion extends Error { constructor(opts={}) { super(opts.message); this.name = 'Assertion'; this.code = 'ERR_ASSERTION'; if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } this.details = opts.details || false; this.generated = !!opts.generated; this.operator = opts.operator; this.expects = opts.expects; this.actual = opts.actual; } } function assert(bool, actual, expects, operator, detailer, backup, msg) { if (bool) return; let message = msg || backup; if (msg instanceof Error) throw msg; let details = detailer && detailer(actual, expects); throw new Assertion({ actual, expects, operator, message, details, generated: !msg }); } export function ok(val, msg) { assert(!!val, false, true, 'ok', false, 'Expected value to be truthy', msg); } export function is(val, exp, msg) { assert(val === exp, val, exp, 'is', compare, 'Expected values to be strictly equal:', msg); } export function equal(val, exp, msg) { assert(dequal(val, exp), val, exp, 'equal', compare, 'Expected values to be deeply equal:', msg); } export function unreachable(msg) { assert(false, true, false, 'unreachable', false, 'Expected not to be reached!', msg); } export function type(val, exp, msg) { let tmp = typeof val; assert(tmp === exp, tmp, exp, 'type', false, `Expected "${tmp}" to be "${exp}"`, msg); } export function instance(val, exp, msg) { let name = '`' + (exp.name || exp.constructor.name) + '`'; assert(val instanceof exp, val, exp, 'instance', false, `Expected value to be an instance of ${name}`, msg); } export function match(val, exp, msg) { if (typeof exp === 'string') { assert(val.includes(exp), val, exp, 'match', false, `Expected value to include "${exp}" substring`, msg); } else { assert(exp.test(val), val, exp, 'match', false, `Expected value to match \`${String(exp)}\` pattern`, msg); } } export function snapshot(val, exp, msg) { val=dedent(val); exp=dedent(exp); assert(val === exp, val, exp, 'snapshot', lines, 'Expected value to match snapshot:', msg); } const lineNums = (x, y) => lines(x, y, 1); export function fixture(val, exp, msg) { val=dedent(val); exp=dedent(exp); assert(val === exp, val, exp, 'fixture', lineNums, 'Expected value to match fixture:', msg); } export function throws(blk, exp, msg) { if (!msg && typeof exp === 'string') { msg = exp; exp = null; } try { blk(); assert(false, false, true, 'throws', false, 'Expected function to throw', msg); } catch (err) { if (err instanceof Assertion) throw err; if (typeof exp === 'function') { assert(exp(err), false, true, 'throws', false, 'Expected function to throw matching exception', msg); } else if (exp instanceof RegExp) { assert(exp.test(err.message), false, true, 'throws', false, `Expected function to throw exception matching \`${String(exp)}\` pattern`, msg); } } } // --- export function not(val, msg) { assert(!val, true, false, 'not', false, 'Expected value to be falsey', msg); } not.ok = not; is.not = function (val, exp, msg) { assert(val !== exp, val, exp, 'is.not', false, 'Expected values not to be strictly equal', msg); } not.equal = function (val, exp, msg) { assert(!dequal(val, exp), val, exp, 'not.equal', false, 'Expected values not to be deeply equal', msg); } not.type = function (val, exp, msg) { let tmp = typeof val; assert(tmp !== exp, tmp, exp, 'not.type', false, `Expected "${tmp}" not to be "${exp}"`, msg); } not.instance = function (val, exp, msg) { let name = '`' + (exp.name || exp.constructor.name) + '`'; assert(!(val instanceof exp), val, exp, 'not.instance', false, `Expected value not to be an instance of ${name}`, msg); } not.snapshot = function (val, exp, msg) { val=dedent(val); exp=dedent(exp); assert(val !== exp, val, exp, 'not.snapshot', false, 'Expected value not to match snapshot', msg); } not.fixture = function (val, exp, msg) { val=dedent(val); exp=dedent(exp); assert(val !== exp, val, exp, 'not.fixture', false, 'Expected value not to match fixture', msg); } not.match = function (val, exp, msg) { if (typeof exp === 'string') { assert(!val.includes(exp), val, exp, 'not.match', false, `Expected value not to include "${exp}" substring`, msg); } else { assert(!exp.test(val), val, exp, 'not.match', false, `Expected value not to match \`${String(exp)}\` pattern`, msg); } } not.throws = function (blk, exp, msg) { if (!msg && typeof exp === 'string') { msg = exp; exp = null; } try { blk(); } catch (err) { if (typeof exp === 'function') { assert(!exp(err), true, false, 'not.throws', false, 'Expected function not to throw matching exception', msg); } else if (exp instanceof RegExp) { assert(!exp.test(err.message), true, false, 'not.throws', false, `Expected function not to throw exception matching \`${String(exp)}\` pattern`, msg); } else if (!exp) { assert(false, true, false, 'not.throws', false, 'Expected function not to throw', msg); } } } uvu-0.5.3/src/diff.d.ts000066400000000000000000000005211416513034700146310ustar00rootroot00000000000000export function chars(input: any, expects: any): string; export function lines(input: any, expects: any, linenum?: number): string; export function direct(input: any, expects: any, lenA?: number, lenB?: number): string; export function compare(input: any, expects: any): string; export function arrays(input: any, expects: any): string; uvu-0.5.3/src/diff.js000066400000000000000000000136111416513034700144010ustar00rootroot00000000000000import kleur from 'kleur'; import * as diff from 'diff'; const colors = { '--': kleur.red, '··': kleur.grey, '++': kleur.green, }; const TITLE = kleur.dim().italic; const TAB=kleur.dim('→'), SPACE=kleur.dim('·'), NL=kleur.dim('↵'); const LOG = (sym, str) => colors[sym](sym + PRETTY(str)) + '\n'; const LINE = (num, x) => kleur.dim('L' + String(num).padStart(x, '0') + ' '); const PRETTY = str => str.replace(/[ ]/g, SPACE).replace(/\t/g, TAB).replace(/(\r?\n)/g, NL); function line(obj, prev, pad) { let char = obj.removed ? '--' : obj.added ? '++' : '··'; let arr = obj.value.replace(/\r?\n$/, '').split('\n'); let i=0, tmp, out=''; if (obj.added) out += colors[char]().underline(TITLE('Expected:')) + '\n'; else if (obj.removed) out += colors[char]().underline(TITLE('Actual:')) + '\n'; for (; i < arr.length; i++) { tmp = arr[i]; if (tmp != null) { if (prev) out += LINE(prev + i, pad); out += LOG(char, tmp || '\n'); } } return out; } // TODO: want better diffing //~> complex items bail outright export function arrays(input, expect) { let arr = diff.diffArrays(input, expect); let i=0, j=0, k=0, tmp, val, char, isObj, str; let out = LOG('··', '['); for (; i < arr.length; i++) { char = (tmp = arr[i]).removed ? '--' : tmp.added ? '++' : '··'; if (tmp.added) { out += colors[char]().underline(TITLE('Expected:')) + '\n'; } else if (tmp.removed) { out += colors[char]().underline(TITLE('Actual:')) + '\n'; } for (j=0; j < tmp.value.length; j++) { isObj = (tmp.value[j] && typeof tmp.value[j] === 'object'); val = stringify(tmp.value[j]).split(/\r?\n/g); for (k=0; k < val.length;) { str = ' ' + val[k++] + (isObj ? '' : ','); if (isObj && k === val.length && (j + 1) < tmp.value.length) str += ','; out += LOG(char, str); } } } return out + LOG('··', ']'); } export function lines(input, expect, linenum = 0) { let i=0, tmp, output=''; let arr = diff.diffLines(input, expect); let pad = String(expect.split(/\r?\n/g).length - linenum).length; for (; i < arr.length; i++) { output += line(tmp = arr[i], linenum, pad); if (linenum && !tmp.removed) linenum += tmp.count; } return output; } export function chars(input, expect) { let arr = diff.diffChars(input, expect); let i=0, output='', tmp; let l1 = input.length; let l2 = expect.length; let p1 = PRETTY(input); let p2 = PRETTY(expect); tmp = arr[i]; if (l1 === l2) { // no length offsets } else if (tmp.removed && arr[i + 1]) { let del = tmp.count - arr[i + 1].count; if (del == 0) { // wash~ } else if (del > 0) { expect = ' '.repeat(del) + expect; p2 = ' '.repeat(del) + p2; l2 += del; } else if (del < 0) { input = ' '.repeat(-del) + input; p1 = ' '.repeat(-del) + p1; l1 += -del; } } output += direct(p1, p2, l1, l2); if (l1 === l2) { for (tmp=' '; i < l1; i++) { tmp += input[i] === expect[i] ? ' ' : '^'; } } else { for (tmp=' '; i < arr.length; i++) { tmp += ((arr[i].added || arr[i].removed) ? '^' : ' ').repeat(Math.max(arr[i].count, 0)); if (i + 1 < arr.length && ((arr[i].added && arr[i+1].removed) || (arr[i].removed && arr[i+1].added))) { arr[i + 1].count -= arr[i].count; } } } return output + kleur.red(tmp); } export function direct(input, expect, lenA = String(input).length, lenB = String(expect).length) { let gutter = 4; let lenC = Math.max(lenA, lenB); let typeA=typeof input, typeB=typeof expect; if (typeA !== typeB) { gutter = 2; let delA = gutter + lenC - lenA; let delB = gutter + lenC - lenB; input += ' '.repeat(delA) + kleur.dim(`[${typeA}]`); expect += ' '.repeat(delB) + kleur.dim(`[${typeB}]`); lenA += delA + typeA.length + 2; lenB += delB + typeB.length + 2; lenC = Math.max(lenA, lenB); } let output = colors['++']('++' + expect + ' '.repeat(gutter + lenC - lenB) + TITLE('(Expected)')) + '\n'; return output + colors['--']('--' + input + ' '.repeat(gutter + lenC - lenA) + TITLE('(Actual)')) + '\n'; } export function sort(input, expect) { var k, i=0, tmp, isArr = Array.isArray(input); var keys=[], out=isArr ? Array(input.length) : {}; if (isArr) { for (i=0; i < out.length; i++) { tmp = input[i]; if (!tmp || typeof tmp !== 'object') out[i] = tmp; else out[i] = sort(tmp, expect[i]); // might not be right } } else { for (k in expect) keys.push(k); for (; i < keys.length; i++) { if (Object.prototype.hasOwnProperty.call(input, k = keys[i])) { if (!(tmp = input[k]) || typeof tmp !== 'object') out[k] = tmp; else out[k] = sort(tmp, expect[k]); } } for (k in input) { if (!out.hasOwnProperty(k)) { out[k] = input[k]; // expect didnt have } } } return out; } export function circular() { var cache = new Set; return function print(key, val) { if (val === void 0) return '[__VOID__]'; if (typeof val === 'number' && val !== val) return '[__NAN__]'; if (!val || typeof val !== 'object') return val; if (cache.has(val)) return '[Circular]'; cache.add(val); return val; } } export function stringify(input) { return JSON.stringify(input, circular(), 2).replace(/"\[__NAN__\]"/g, 'NaN').replace(/"\[__VOID__\]"/g, 'undefined'); } export function compare(input, expect) { if (Array.isArray(expect) && Array.isArray(input)) return arrays(input, expect); if (expect instanceof RegExp) return chars(''+input, ''+expect); let isA = input && typeof input == 'object'; let isB = expect && typeof expect == 'object'; if (isA && isB) input = sort(input, expect); if (isB) expect = stringify(expect); if (isA) input = stringify(input); if (expect && typeof expect == 'object') { input = stringify(sort(input, expect)); expect = stringify(expect); } isA = typeof input == 'string'; isB = typeof expect == 'string'; if (isA && /\r?\n/.test(input)) return lines(input, ''+expect); if (isB && /\r?\n/.test(expect)) return lines(''+input, expect); if (isA && isB) return chars(input, expect); return direct(input, expect); } uvu-0.5.3/src/index.d.ts000066400000000000000000000013651416513034700150370ustar00rootroot00000000000000declare namespace uvu { type Crumbs = { __suite__: string; __test__: string }; type Callback = (context: T & Crumbs) => Promise | void; interface Hook { (hook: Callback): void; each(hook: Callback): void; } interface Test { (name: string, test: Callback): void; only(name: string, test: Callback): void; skip(name?: string, test?: Callback): void; before: Hook; after: Hook run(): () => void; } } type Context = Record; export type Test = uvu.Test; export type Callback = uvu.Callback; export const test: uvu.Test; export function suite(title?: string, context?: T): uvu.Test; export function exec(bail?: boolean): Promise; uvu-0.5.3/src/index.js000066400000000000000000000113661416513034700146050ustar00rootroot00000000000000import kleur from 'kleur'; import { compare } from 'uvu/diff'; let isCLI = false, isNode = false; let hrtime = (now = Date.now()) => () => (Date.now() - now).toFixed(2) + 'ms'; let write = console.log; const into = (ctx, key) => (name, handler) => ctx[key].push({ name, handler }); const context = (state) => ({ tests:[], before:[], after:[], bEach:[], aEach:[], only:[], skips:0, state }); const milli = arr => (arr[0]*1e3 + arr[1]/1e6).toFixed(2) + 'ms'; const hook = (ctx, key) => handler => ctx[key].push(handler); if (isNode = typeof process < 'u' && typeof process.stdout < 'u') { // globalThis polyfill; Node < 12 if (typeof globalThis !== 'object') { Object.defineProperty(global, 'globalThis', { get: function () { return this } }); } let rgx = /(\.bin[\\+\/]uvu$|uvu[\\+\/]bin\.js)/i; isCLI = process.argv.some(x => rgx.test(x)); // attach node-specific utils write = x => process.stdout.write(x); hrtime = (now = process.hrtime()) => () => milli(process.hrtime(now)); } else if (typeof performance < 'u') { hrtime = (now = performance.now()) => () => (performance.now() - now).toFixed(2) + 'ms'; } globalThis.UVU_QUEUE = globalThis.UVU_QUEUE || []; isCLI || UVU_QUEUE.push([null]); const QUOTE = kleur.dim('"'), GUTTER = '\n '; const FAIL = kleur.red('✘ '), PASS = kleur.gray('• '); const IGNORE = /^\s*at.*(?:\(|\s)(?:node|(internal\/[\w/]*))/; const FAILURE = kleur.bold().bgRed(' FAIL '); const FILE = kleur.bold().underline().white; const SUITE = kleur.bgWhite().bold; function stack(stack, idx) { let i=0, line, out=''; let arr = stack.substring(idx).replace(/\\/g, '/').split('\n'); for (; i < arr.length; i++) { line = arr[i].trim(); if (line.length && !IGNORE.test(line)) { out += '\n ' + line; } } return kleur.grey(out) + '\n'; } function format(name, err, suite = '') { let { details, operator='' } = err; let idx = err.stack && err.stack.indexOf('\n'); if (err.name.startsWith('AssertionError') && !operator.includes('not')) details = compare(err.actual, err.expected); // TODO? let str = ' ' + FAILURE + (suite ? kleur.red(SUITE(` ${suite} `)) : '') + ' ' + QUOTE + kleur.red().bold(name) + QUOTE; str += '\n ' + err.message + (operator ? kleur.italic().dim(` (${operator})`) : '') + '\n'; if (details) str += GUTTER + details.split('\n').join(GUTTER); if (!!~idx) str += stack(err.stack, idx); return str + '\n'; } async function runner(ctx, name) { let { only, tests, before, after, bEach, aEach, state } = ctx; let hook, test, arr = only.length ? only : tests; let num=0, errors='', total=arr.length; try { if (name) write(SUITE(kleur.black(` ${name} `)) + ' '); for (hook of before) await hook(state); for (test of arr) { state.__test__ = test.name; try { for (hook of bEach) await hook(state); await test.handler(state); for (hook of aEach) await hook(state); write(PASS); num++; } catch (err) { for (hook of aEach) await hook(state); if (errors.length) errors += '\n'; errors += format(test.name, err, name); write(FAIL); } } } finally { state.__test__ = ''; for (hook of after) await hook(state); let msg = ` (${num} / ${total})\n`; let skipped = (only.length ? tests.length : 0) + ctx.skips; write(errors.length ? kleur.red(msg) : kleur.green(msg)); return [errors || true, num, skipped, total]; } } let timer; function defer() { clearTimeout(timer); timer = setTimeout(exec); } function setup(ctx, name = '') { ctx.state.__test__ = ''; ctx.state.__suite__ = name; const test = into(ctx, 'tests'); test.before = hook(ctx, 'before'); test.before.each = hook(ctx, 'bEach'); test.after = hook(ctx, 'after'); test.after.each = hook(ctx, 'aEach'); test.only = into(ctx, 'only'); test.skip = () => { ctx.skips++ }; test.run = () => { let copy = { ...ctx }; let run = runner.bind(0, copy, name); Object.assign(ctx, context(copy.state)); UVU_QUEUE[globalThis.UVU_INDEX || 0].push(run); isCLI || defer(); }; return test; } export const suite = (name = '', state = {}) => setup(context(state), name); export const test = suite(); export async function exec(bail) { let timer = hrtime(); let done=0, total=0, skips=0, code=0; for (let group of UVU_QUEUE) { if (total) write('\n'); let name = group.shift(); if (name != null) write(FILE(name) + '\n'); for (let test of group) { let [errs, ran, skip, max] = await test(); total += max; done += ran; skips += skip; if (errs.length) { write('\n' + errs + '\n'); code=1; if (bail) return isNode && process.exit(1); } } } write('\n Total: ' + total); write((code ? kleur.red : kleur.green)('\n Passed: ' + done)); write('\n Skipped: ' + (skips ? kleur.yellow(skips) : skips)); write('\n Duration: ' + timer() + '\n\n'); if (isNode) process.exitCode = code; } uvu-0.5.3/src/parse.d.ts000066400000000000000000000006711416513034700150410ustar00rootroot00000000000000type Arrayable = T[] | T; export interface Suite { /** The relative file path */ name: string; /** The absolute file path */ file: string; } export interface Options { cwd: string; require: Arrayable; ignore: Arrayable; } export interface Argv { dir: string; suites: Suite[]; requires: boolean; } export function parse(dir?: string, pattern?: string|RegExp, opts?: Partial): Promise; uvu-0.5.3/src/parse.js000066400000000000000000000031311416513034700145770ustar00rootroot00000000000000import { readdir, stat } from 'fs'; import { resolve, join } from 'path'; import { promisify } from 'util'; const ls = promisify(readdir); const toStat = promisify(stat); const toRegex = x => new RegExp(x, 'i'); function exists(dep) { try { return require.resolve(dep) } catch (err) { return false } } export async function parse(dir, pattern, opts = {}) { if (pattern) pattern = toRegex(pattern); else if (dir) pattern = /(((?:[^\/]*(?:\/|$))*)[\\\/])?\w+\.([mc]js|[jt]sx?)$/; else pattern = /((\/|^)(tests?|__tests?__)\/.*|\.(tests?|spec)|^\/?tests?)\.([mc]js|[jt]sx?)$/i; dir = resolve(opts.cwd || '.', dir || '.'); let suites = []; let requires = [].concat(opts.require || []).filter(Boolean); let ignores = ['^.git', 'node_modules'].concat(opts.ignore || []).map(toRegex); requires.forEach(name => { let tmp = exists(name); if (tmp) return require(tmp); if (tmp = exists(resolve(name))) return require(tmp); throw new Error(`Cannot find module '${name}'`); }); // NOTE: Node 8.x support // @modified lukeed/totalist await (async function collect(d, p) { await ls(d).then(files => { return Promise.all( files.map(async str => { let name = join(p, str); for (let i = ignores.length; i--;) { if (ignores[i].test(name)) return; } let file = join(d, str); let stats = await toStat(file); if (stats.isDirectory()) return collect(file, name); else if (pattern.test(name)) suites.push({ name, file }); }) ); }); })(dir, ''); suites.sort((a, b) => a.name.localeCompare(b.name)); return { dir, suites, requires: requires.length > 0 }; } uvu-0.5.3/test/000077500000000000000000000000001416513034700133215ustar00rootroot00000000000000uvu-0.5.3/test/assert.js000066400000000000000000000432501416513034700151640ustar00rootroot00000000000000import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as $ from '../src/assert'; function isError(err, msg, input, expect, operator, details) { assert.instance(err, Error); if (msg) assert.is(err.message, msg, '~> message'); assert.is(!!err.generated, !msg, '~> generated'); assert.ok(err.stack, '~> stack'); assert.instance(err, $.Assertion); assert.is(err.name, 'Assertion'); assert.is(err.code, 'ERR_ASSERTION'); assert.is(!!err.details, !!details, '~> details'); assert.is(err.operator, operator, '~> operator'); assert.equal(err.expects, expect, '~> expects'); assert.equal(err.actual, input, '~> actual'); } const Assertion = suite('Assertion'); Assertion('should extend Error', () => { assert.instance(new $.Assertion(), Error); }); Assertion('should expect input options', () => { let err = new $.Assertion({ actual: 'foo', operator: 'is', expects: 'bar', message: 'Expected "foo" to equal "bar"', generated: false, details: true, }); isError( err, 'Expected "foo" to equal "bar"', 'foo', 'bar', 'is', true ); }); Assertion.run(); // --- const ok = suite('ok'); ok('should be a function', () => { assert.type($.ok, 'function'); }); ok('should not throw if valid', () => { assert.not.throws(() => $.ok(true)); }); ok('should throw if invalid', () => { try { $.ok(false); } catch (err) { isError(err, '', false, true, 'ok', false); } }); ok('should throw with custom message', () => { try { $.ok(false, 'hello there'); } catch (err) { isError(err, 'hello there', false, true, 'ok', false); } }); ok.run(); // --- const is = suite('is'); is('should be a function', () => { assert.type($.is, 'function'); }); is('should not throw if valid', () => { assert.not.throws(() => $.is('abc', 'abc')); assert.not.throws(() => $.is(true, true)); assert.not.throws(() => $.is(123, 123)); }); is('should throw if invalid', () => { try { $.is('foo', 'bar'); } catch (err) { isError(err, '', 'foo', 'bar', 'is', true); } }); is('should throw with custom message', () => { try { $.is(123, 456, 'hello there'); } catch (err) { isError(err, 'hello there', 123, 456, 'is', true); } }); // TODO: add throws() instance matcher? // assert.throws(() => {}, $.Assertion) is('should use strict equality checks', () => { try { let arr = [3, 4, 5]; $.is(arr, arr.slice()); assert.unreachable(); } catch (err) { assert.instance(err, $.Assertion); } try { let obj = { foo: 123 } $.is(obj, { ...obj }); assert.unreachable(); } catch (err) { assert.instance(err, $.Assertion); } }); is.run(); // --- const equal = suite('equal'); equal('should be a function', () => { assert.type($.equal, 'function'); }); equal('should not throw if valid', () => { assert.not.throws(() => $.equal('abc', 'abc')); assert.not.throws(() => $.equal(true, true)); assert.not.throws(() => $.equal(123, 123)); assert.not.throws(() => $.equal([1, 2, 3], [1, 2, 3])); assert.not.throws(() => $.equal({ foo: [2, 3] }, { foo: [2, 3] })); }); equal('should throw if invalid', () => { let input = { foo: ['aaa'], bar: [2, 3] }; let expect = { foo: ['a', 'a'], bar: [{ a: 1, b: 2 }] }; try { $.equal(input, expect); } catch (err) { isError(err, '', input, expect, 'equal', true); } }); equal('should throw with custom message', () => { let input = { foo: ['aaa'], bar: [2, 3] }; let expect = { foo: ['a', 'a'], bar: [{ a: 1, b: 2 }] }; try { $.equal(input, expect, 'hello there'); } catch (err) { isError(err, 'hello there', input, expect, 'equal', true); } }); equal('should use deep equality checks', () => { try { $.equal( { a:[{ b:2 }] }, { a:[{ b:1 }] } ); assert.unreachable(); } catch (err) { assert.instance(err, $.Assertion); } try { $.equal( [{ a:2 }, { b:2 }], [{ a:1 }, { b:2 }] ); assert.unreachable(); } catch (err) { assert.instance(err, $.Assertion); } }); equal('should throw assertion error on array type mismatch', () => { try { $.equal(null, []); assert.unreachable(); } catch (err) { assert.instance(err, $.Assertion); } try { $.equal([], null); assert.unreachable(); } catch (err) { assert.instance(err, $.Assertion); } }); equal.run(); // --- const unreachable = suite('unreachable'); unreachable('should be a function', () => { assert.type($.unreachable, 'function'); }); unreachable('should always throw', () => { try { $.unreachable(); } catch (err) { isError(err, '', true, false, 'unreachable', false); } }); unreachable('should customize message', () => { try { $.unreachable('hello'); } catch (err) { isError(err, 'hello', true, false, 'unreachable', false); } }); unreachable.run(); // --- const instance = suite('instance'); instance('should be a function', () => { assert.type($.instance, 'function'); }); instance('should not throw if valid', () => { assert.not.throws(() => $.instance(new Date, Date)); assert.not.throws(() => $.instance(/foo/, RegExp)); assert.not.throws(() => $.instance({}, Object)); assert.not.throws(() => $.instance([], Array)); }); instance('should throw if invalid', () => { try { $.instance('foo', Error); } catch (err) { isError(err, '', 'foo', Error, 'instance', false); } }); instance('should throw with custom message', () => { try { $.instance('foo', Error, 'hello there'); } catch (err) { isError(err, 'hello there', 'foo', Error, 'instance', false); } }); instance.run(); // --- const type = suite('type'); type('should be a function', () => { assert.type($.type, 'function'); }); type('should not throw if valid', () => { assert.not.throws(() => $.type(123, 'number')); assert.not.throws(() => $.type(true, 'boolean')); assert.not.throws(() => $.type($.type, 'function')); assert.not.throws(() => $.type('abc', 'string')); assert.not.throws(() => $.type(/x/, 'object')); }); type('should throw if invalid', () => { try { $.type('foo', 'number'); } catch (err) { isError(err, '', 'string', 'number', 'type', false); } }); type('should throw with custom message', () => { try { $.type('foo', 'number', 'hello there'); } catch (err) { isError(err, 'hello there', 'string', 'number', 'type', false); } }); type.run(); // --- // TODO const snapshot = suite('snapshot'); snapshot('should be a function', () => { assert.type($.snapshot, 'function'); }); snapshot.run(); // --- // TODO const fixture = suite('fixture'); fixture('should be a function', () => { assert.type($.fixture, 'function'); }); fixture.run(); // --- const match = suite('match'); match('should be a function', () => { assert.type($.match, 'function'); }); match('should not throw if valid', () => { assert.not.throws(() => $.match('foobar', 'foo')); assert.not.throws(() => $.match('foobar', 'bar')); assert.not.throws(() => $.match('foobar', /foo/)); assert.not.throws(() => $.match('foobar', /bar/i)); }); match('should throw if invalid', () => { try { $.match('foobar', 'hello'); } catch (err) { isError(err, '', 'foobar', 'hello', 'match', false); } try { $.match('foobar', /hello/i); } catch (err) { isError(err, '', 'foobar', /hello/i, 'match', false); } }); match('should throw with custom message', () => { try { $.match('foobar', 'hello', 'howdy partner'); } catch (err) { isError(err, 'howdy partner', 'foobar', 'hello', 'match', false); } }); match.run(); // --- const throws = suite('throws'); throws('should be a function', () => { assert.type($.throws, 'function'); }); throws('should throw if function does not throw Error :: generic', () => { try { $.throws(() => 123); } catch (err) { assert.is(err.message, 'Expected function to throw'); isError(err, '', false, true, 'throws', false); // no details (true vs false) } }); throws('should throw if function does not throw matching Error :: RegExp', () => { try { $.throws(() => { throw new Error('hello') }, /world/); } catch (err) { assert.is(err.message, 'Expected function to throw exception matching `/world/` pattern'); isError(err, '', false, true, 'throws', false); // no details } }); throws('should throw if function does not throw matching Error :: Function', () => { try { $.throws( () => { throw new Error }, (err) => err.message.includes('foobar') ); } catch (err) { assert.is(err.message, 'Expected function to throw matching exception'); isError(err, '', false, true, 'throws', false); // no details } }); throws('should not throw if function does throw Error :: generic', () => { assert.not.throws( () => $.throws(() => { throw new Error }) ); }); throws('should not throw if function does throw matching Error :: RegExp', () => { assert.not.throws( () => $.throws(() => { throw new Error('hello') }, /hello/) ); }); throws('should not throw if function does throw matching Error :: Function', () => { assert.not.throws(() => { $.throws( () => { throw new Error('foobar') }, (err) => err.message.includes('foobar') ) }); }); throws.run(); // --- const not = suite('not'); not('should be a function', () => { assert.type($.not, 'function'); }); not('should not throw if falsey', () => { assert.not.throws(() => $.not(false)); assert.not.throws(() => $.not(undefined)); assert.not.throws(() => $.not(null)); assert.not.throws(() => $.not('')); assert.not.throws(() => $.not(0)); }); not('should throw if truthy', () => { try { $.not(true); } catch (err) { isError(err, '', true, false, 'not', false); } }); not('should throw with custom message', () => { try { $.not(true, 'hello there'); } catch (err) { isError(err, 'hello there', true, false, 'not', false); } }); not.run(); // --- const notOk = suite('not.ok'); notOk('should be a function', () => { assert.type($.not.ok, 'function'); }); notOk('should not throw if falsey', () => { assert.not.throws(() => $.not.ok(false)); assert.not.throws(() => $.not.ok(undefined)); assert.not.throws(() => $.not.ok(null)); assert.not.throws(() => $.not.ok('')); assert.not.throws(() => $.not.ok(0)); }); notOk('should throw if truthy', () => { try { $.not.ok(true); } catch (err) { isError(err, '', true, false, 'not', false); } }); notOk('should throw with custom message', () => { try { $.not.ok(true, 'hello there'); } catch (err) { isError(err, 'hello there', true, false, 'not', false); } }); notOk.run(); // --- const notIs = suite('is.not'); notIs('should be a function', () => { assert.type($.is.not, 'function'); }); notIs('should not throw if values are not strictly equal', () => { assert.not.throws(() => $.is.not(true, false)); assert.not.throws(() => $.is.not(123, undefined)); assert.not.throws(() => $.is.not('123', 123)); assert.not.throws(() => $.is.not(NaN, NaN)); assert.not.throws(() => $.is.not([], [])); }); notIs('should throw if values are strictly equal', () => { try { $.is.not('hello', 'hello'); } catch (err) { isError(err, '', 'hello', 'hello', 'is.not', false); } }); notIs('should throw with custom message', () => { try { $.is.not('foo', 'foo', 'hello there'); } catch (err) { isError(err, 'hello there', 'foo', 'foo', 'is.not', false); } }); notIs.run(); // --- const notEqual = suite('not.equal'); notEqual('should be a function', () => { assert.type($.not.equal, 'function'); }); notEqual('should throw if values are deeply equal', () => { try { $.not.equal('abc', 'abc'); } catch (err) { isError(err, '', 'abc', 'abc', 'not.equal', false); // no details } try { $.not.equal(true, true); } catch (err) { isError(err, '', true, true, 'not.equal', false); // no details } try { $.not.equal(123, 123); } catch (err) { isError(err, '', 123, 123, 'not.equal', false); // no details } let arr = [1, 2, 3]; let obj = { foo: [2, 3] }; try { $.not.equal(arr, arr); } catch (err) { isError(err, '', arr, arr, 'not.equal', false); // no details } try { $.not.equal(obj, obj); } catch (err) { isError(err, '', obj, obj, 'not.equal', false); // no details } }); notEqual('should not throw if values are not deeply equal', () => { let input = { foo: ['aaa'], bar: [2, 3] }; let expect = { foo: ['a', 'a'], bar: [{ a: 1, b: 2 }] }; assert.not.throws( () => $.not.equal(input, expect) ); }); notEqual('should throw with custom message', () => { let input = { foo: ['aaa'], bar: [2, 3] }; try { $.not.equal(input, input, 'hello there'); } catch (err) { isError(err, 'hello there', input, input, 'not.equal', false); // no details } }); notEqual('should use deep equality checks', () => { try { $.not.equal( { a:[{ b:2 }] }, { a:[{ b:2 }] }, ); assert.unreachable(); } catch (err) { assert.instance(err, $.Assertion); assert.is(err.operator, 'not.equal'); } try { $.not.equal( [{ a:2 }, { b:2 }], [{ a:2 }, { b:2 }], ); assert.unreachable(); } catch (err) { assert.instance(err, $.Assertion); assert.is(err.operator, 'not.equal'); } }); notEqual.run(); // --- const notType = suite('not.type'); notType('should be a function', () => { assert.type($.not.type, 'function'); }); notType('should throw if types match', () => { try { $.not.type(123, 'number'); } catch (err) { isError(err, '', 'number', 'number', 'not.type', false); } try { $.not.type(true, 'boolean'); } catch (err) { isError(err, '', 'boolean', 'boolean', 'not.type', false); } try { $.not.type($.not.type, 'function'); } catch (err) { isError(err, '', 'function', 'function', 'not.type', false); } try { $.not.type('abc', 'string'); } catch (err) { isError(err, '', 'string', 'string', 'not.type', false); } try { $.not.type(/x/, 'object'); } catch (err) { isError(err, '', 'object', 'object', 'not.type', false); } }); notType('should not throw if types do not match', () => { assert.not.throws( () => $.not.type('foo', 'number') ); }); notType('should throw with custom message', () => { try { $.not.type('abc', 'string', 'hello world'); } catch (err) { isError(err, 'hello world', 'string', 'string', 'not.type', false); } }); notType.run(); // --- const notInstance = suite('not.instance'); notInstance('should be a function', () => { assert.type($.not.instance, 'function'); }); notInstance('should throw if values match', () => { // isError uses is() check -- lazy let inputs = { date: new Date, regexp: /foo/, object: {}, array: [], }; try { $.not.instance(inputs.date, Date); } catch (err) { isError(err, '', inputs.date, Date, 'not.instance', false); } try { $.not.instance(inputs.regexp, RegExp); } catch (err) { isError(err, '', inputs.regexp, RegExp, 'not.instance', false); } try { $.not.instance(inputs.object, Object); } catch (err) { isError(err, '', inputs.object, Object, 'not.instance', false); } try { $.not.instance(inputs.array, Array); } catch (err) { isError(err, '', inputs.array, Array, 'not.instance', false); } }); notInstance('should not throw on mismatch', () => { assert.not.throws(() => $.not.instance('foo', Error)); }); notInstance('should throw with custom message', () => { try { $.not.instance('foo', String, 'hello there'); } catch (err) { isError(err, 'hello there', 'foo', String, 'instance', false); } }); notInstance.run(); // --- // TODO // const notSnapshot = suite('not.snapshot'); // notSnapshot.run(); // --- // TODO // const notFixture = suite('not.fixture'); // notFixture.run(); // --- const notMatch = suite('not.match'); notMatch('should be a function', () => { assert.type($.not.match, 'function'); }); notMatch('should throw if values match', () => { try { $.not.match('foobar', 'foo'); } catch (err) { isError(err, '', 'foobar', 'foo', 'not.match', false); } try { $.not.match('foobar', 'bar'); } catch (err) { isError(err, '', 'foobar', 'bar', 'not.match', false); } try { $.not.match('foobar', /foo/); } catch (err) { isError(err, '', 'foobar', /foo/, 'not.match', false); } }); notMatch('should not throw if types do not match', () => { assert.not.throws( () => $.not.match('foobar', 'hello') ); assert.not.throws( () => $.not.match('foobar', /hello/) ); }); notMatch('should throw with custom message', () => { try { $.not.match('foobar', 'hello', 'hello world'); } catch (err) { isError(err, 'hello world', 'foobar', 'hello', 'not.match', false); } }); notMatch.run(); // --- const notThrows = suite('not.throws'); notThrows('should be a function', () => { assert.type($.throws, 'function'); }); notThrows('should not throw if function does not throw Error :: generic', () => { assert.not.throws( () => $.not.throws(() => 123) ); }); notThrows('should not throw if function does not throw matching Error :: RegExp', () => { assert.not.throws( () => $.not.throws(() => { throw new Error('hello') }, /world/) ); }); notThrows('should not throw if function does not throw matching Error :: Function', () => { assert.not.throws(() => { $.not.throws( () => { throw new Error('hello') }, (err) => err.message.includes('world') ) }); }); notThrows('should throw if function does throw Error :: generic', () => { try { $.not.throws(() => { throw new Error }); } catch (err) { assert.is(err.message, 'Expected function not to throw'); isError(err, '', true, false, 'not.throws', false); // no details } }); notThrows('should throw if function does throw matching Error :: RegExp', () => { try { $.not.throws(() => { throw new Error('hello') }, /hello/); } catch (err) { assert.is(err.message, 'Expected function not to throw exception matching `/hello/` pattern'); isError(err, '', true, false, 'not.throws', false); // no details } }); notThrows('should throw if function does throw matching Error :: Function', () => { try { $.not.throws( () => { throw new Error }, (err) => err instanceof Error ); } catch (err) { assert.is(err.message, 'Expected function not to throw matching exception'); isError(err, '', true, false, 'not.throws', false); // no details } }); notThrows.run(); uvu-0.5.3/test/diff.js000066400000000000000000000563121416513034700145760ustar00rootroot00000000000000import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as $ from '../src/diff'; const strip = str => str.replace(/[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d\/#&.:=?%@~_]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-ntqry=><~]))/g, ''); const arrays = suite('arrays'); arrays('should be a function', () => { assert.type($.arrays, 'function'); }); arrays('should handle simple values', () => { assert.is( strip($.arrays([1, 2, 3], [1, 2, 4])), '··[\n' + '····1,\n' + '····2,\n' + 'Actual:\n' + '--··3,\n' + 'Expected:\n' + '++··4,\n' + '··]\n' ); }); arrays('should handle nullish values', () => { assert.is( strip($.arrays(['foo', 'bar', undefined], ['foo', 'bar', 'baz'])), '··[\n' + '····"foo",\n' + '····"bar",\n' + 'Actual:\n' + '--··undefined,\n' + 'Expected:\n' + '++··"baz",\n' + '··]\n' ); assert.is( strip($.arrays([1, 2, NaN, undefined], [1, 2, null, null])), '··[\n' + '····1,\n' + '····2,\n' + 'Actual:\n' + '--··NaN,\n' + '--··undefined,\n' + 'Expected:\n' + '++··null,\n' + '++··null,\n' + '··]\n' ); }); arrays('should allow dangling "Actual" block', () => { assert.is( strip($.arrays([1, 2, 3, 4], [1, 2, 4])), '··[\n' + '····1,\n' + '····2,\n' + 'Actual:\n' + '--··3,\n' + '····4,\n' + '··]\n' ); }); arrays('should allow dangling "Expected" block', () => { assert.is( strip($.arrays([1, 2, 4], [1, 2, 3, 4])), '··[\n' + '····1,\n' + '····2,\n' + 'Expected:\n' + '++··3,\n' + '····4,\n' + '··]\n' ); }); // TODO: improve later arrays('should print/bail on complex objects', () => { assert.is( strip( $.arrays( [{ foo: 1 }, { bar: 2 }], [{ foo: 1 }] ) ), '··[\n' + 'Actual:\n' + '--··{\n' + '--····"foo":·1\n' + '--··},\n' + '--··{\n' + '--····"bar":·2\n' + '--··}\n' + 'Expected:\n' + '++··{\n' + '++····"foo":·1\n' + '++··}\n' + '··]\n' ); assert.is( strip( $.arrays( [ [111], ['bbb'] ], [ [333], ['xxx'] ], ) ), '··[\n' + 'Actual:\n' + '--··[\n' + '--····111\n' + '--··],\n' + '--··[\n' + '--····"bbb"\n' + '--··]\n' + 'Expected:\n' + '++··[\n' + '++····333\n' + '++··],\n' + '++··[\n' + '++····"xxx"\n' + '++··]\n' + '··]\n' ); assert.is( strip( $.arrays( [ [111, 222], ['aaa', 'bbb'] ], [ [333, 444], ['aaa', 'xxx'] ], ) ), '··[\n' + 'Actual:\n' + '--··[\n' + '--····111,\n' + '--····222\n' + '--··],\n' + '--··[\n' + '--····"aaa",\n' + '--····"bbb"\n' + '--··]\n' + 'Expected:\n' + '++··[\n' + '++····333,\n' + '++····444\n' + '++··],\n' + '++··[\n' + '++····"aaa",\n' + '++····"xxx"\n' + '++··]\n' + '··]\n' ); assert.is( strip( $.arrays( [{ foobar: 123, position: { start: { line: 1, column: 1, offset: 0, index: 0 }, end: { line: 1, column: 8, offset: 7 } } }], [{ foobar: 456, position: { start: { line: 2, column: 1, offset: 0, index: 0 }, end: { line: 9, column: 9, offset: 6 } } }] ) ), '··[\n' + 'Actual:\n' + '--··{\n' + '--····"foobar":·123,\n' + '--····"position":·{\n' + '--······"start":·{\n' + '--········"line":·1,\n' + '--········"column":·1,\n' + '--········"offset":·0,\n' + '--········"index":·0\n' + '--······},\n' + '--······"end":·{\n' + '--········"line":·1,\n' + '--········"column":·8,\n' + '--········"offset":·7\n' + '--······}\n' + '--····}\n' + '--··}\n' + 'Expected:\n' + '++··{\n' + '++····"foobar":·456,\n' + '++····"position":·{\n' + '++······"start":·{\n' + '++········"line":·2,\n' + '++········"column":·1,\n' + '++········"offset":·0,\n' + '++········"index":·0\n' + '++······},\n' + '++······"end":·{\n' + '++········"line":·9,\n' + '++········"column":·9,\n' + '++········"offset":·6\n' + '++······}\n' + '++····}\n' + '++··}\n' + '··]\n' ); }); arrays.run(); // --- const lines = suite('lines'); lines('should be a function', () => { assert.type($.lines, 'function'); }); lines('should split on `\\r\\n` chars', () => { assert.is( strip($.lines('foo\nbar', 'foo\nbat')), '··foo\n' + 'Actual:\n' + '--bar\n' + 'Expected:\n' + '++bat\n' ); assert.is( strip($.lines('foo\r\nbar', 'foo\r\nbat')), '··foo\n' + 'Actual:\n' + '--bar\n' + 'Expected:\n' + '++bat\n' ); }); lines('should allow for dangling "Actual" block', () => { assert.is( strip($.lines('foo\nbar\nbaz', 'foo\nbaz')), '··foo\n' + 'Actual:\n' + '--bar\n' + '··baz\n' ); }); lines('should allow for dangling "Expected" block', () => { assert.is( strip($.lines('foo\nbaz', 'foo\nbar\nbaz')), '··foo\n' + 'Expected:\n' + '++bar\n' + '··baz\n' ); }); lines('should accept line numbers', () => { assert.is( strip($.lines('foo\nbar', 'foo\nbat', 1)), 'L1 ··foo\n' + 'Actual:\n' + 'L2 --bar\n' + 'Expected:\n' + 'L2 ++bat\n' ); }); lines('should handle line numbers with num-digits change', () => { assert.is( strip($.lines( '1\n2\n3\n4\n5\n6\n7\n8a\n9a\n10a\n11\n12\n13', '1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13', 1 )), 'L01 ··1\n' + 'L02 ··2\n' + 'L03 ··3\n' + 'L04 ··4\n' + 'L05 ··5\n' + 'L06 ··6\n' + 'L07 ··7\n' + 'Actual:\n' + 'L08 --8a\n' + 'L09 --9a\n' + 'L10 --10a\n' + 'Expected:\n' + 'L08 ++8\n' + 'L09 ++9\n' + 'L10 ++10\n' + 'L11 ··11\n' + 'L12 ··12\n' + 'L13 ··13\n' ); }); lines('should track "expected" for line numbers', () => { assert.is( strip($.lines('foo\nbaz', 'foo\nbar\nbaz', 1)), 'L1 ··foo\n' + 'Expected:\n' + 'L2 ++bar\n' + 'L3 ··baz\n' ); assert.is( strip($.lines('foo\nbar\nbaz', 'foo\nbaz', 1)), 'L1 ··foo\n' + 'Actual:\n' + 'L2 --bar\n' + 'L2 ··baz\n' ); }); lines('should retain new lines ("↵") differences', () => { assert.is( strip($.lines('foo\nbaz', 'foo\n\n\nbaz')), '··foo\n' + 'Expected:\n' + '++↵\n' + '++↵\n' + '··baz\n' ); assert.is( strip($.lines('foo\nbaz', 'foo\n\n\nbaz', 1)), 'L1 ··foo\n' + 'Expected:\n' + 'L2 ++↵\n' + 'L3 ++↵\n' + 'L4 ··baz\n' ); }); lines.run(); // --- const chars = suite('chars'); chars('should be a function', () => { assert.type($.chars, 'function'); }); // TODO: return nothing ? chars.skip('should no differences with identical text', () => { // assert.fixture(); }); chars('should handle `"foo"` vs `"fo"` diff', () => { assert.is( strip($.chars('foo', 'fo')), '++fo (Expected)\n' + '--foo (Actual)\n'+ ' ^' ); }); chars('should handle `"fo"` vs `"foo"` diff', () => { assert.is( strip($.chars('fo', 'foo')), '++foo (Expected)\n' + '--fo (Actual)\n'+ ' ^' ); }); chars('should handle `"foo"` vs `"bar"` diff', () => { assert.is( strip($.chars('foo', 'bar')), '++bar (Expected)\n' + '--foo (Actual)\n' + ' ^^^' ); }); chars('should handle `"foobar"` vs `"foobaz"` diff', () => { assert.is( strip($.chars('foobar', 'foobaz')), '++foobaz (Expected)\n' + '--foobar (Actual)\n' + ' ^' ); }); chars('should handle two `Date.toISOString()` diff', () => { assert.is( strip($.chars('2019-12-23T01:26:30.092Z', '2020-06-23T01:45:31.268Z')), '++2020-06-23T01:45:31.268Z (Expected)\n' + '--2019-12-23T01:26:30.092Z (Actual)\n' + ' ^^ ^^ ^^ ^ ^^^ ' ); assert.is( strip($.chars('2019-12-23T01:26:30.098Z', '2020-06-23T01:45:31.268Z')), '++2020-06-23T01:45:31.268Z (Expected)\n' + '--2019-12-23T01:26:30.098Z (Actual)\n' + ' ^^ ^^ ^^ ^ ^^ ' ); assert.is( strip($.chars('2020-09-23T01:45:31.268Z', '2020-06-23T01:45:31.268Z')), '++2020-06-23T01:45:31.268Z (Expected)\n' + '--2020-09-23T01:45:31.268Z (Actual)\n' + ' ^ ' ); }); chars('should handle `"help"` vs `"hello"` diff', () => { assert.is( strip($.chars('help', 'hello')), '++hello (Expected)\n' + '--help (Actual)\n' + ' ^^' ); }); chars('should handle `"yellow"` vs `"hello"` diff', () => { assert.is( strip($.chars('yellow', 'hello')), '++hello (Expected)\n' + '--yellow (Actual)\n' + ' ^ ^' ); assert.is( strip($.chars('hello', 'yellow')), '++yellow (Expected)\n' + '--hello (Actual)\n' + ' ^ ^' ); }); chars('should handle shared prefix', () => { assert.is( strip($.chars('abc123', 'abc1890')), '++abc1890 (Expected)\n' + '--abc123 (Actual)\n' + ' ^^^' ); assert.is( strip($.chars('abc1890', 'abc123')), '++abc123 (Expected)\n' + '--abc1890 (Actual)\n' + ' ^^^' ); assert.is( strip($.chars('abc1890', 'abc1234')), '++abc1234 (Expected)\n' + '--abc1890 (Actual)\n' + ' ^^^' ); }); chars('should handle shared suffix', () => { assert.is( strip($.chars('123xyz', '00xyz')), '++ 00xyz (Expected)\n' + '--123xyz (Actual)\n' + ' ^^^ ' ); assert.is( strip($.chars('00xyz', '123xyz')), '++123xyz (Expected)\n' + '-- 00xyz (Actual)\n' + ' ^^^ ' ); assert.is( strip($.chars('000xyz', '123xyz')), '++123xyz (Expected)\n' + '--000xyz (Actual)\n' + ' ^^^ ' ); }); chars('should handle shared middle', () => { assert.is( strip($.chars('123xyz456', '789xyz000')), '++789xyz000 (Expected)\n' + '--123xyz456 (Actual)\n' + ' ^^^ ^^^' ); assert.is( strip($.chars('123xyz45', '789xyz000')), '++789xyz000 (Expected)\n' + '--123xyz45 (Actual)\n' + ' ^^^ ^^^' ); assert.is( strip($.chars('23xyz45', '789xyz000')), '++789xyz000 (Expected)\n' + '-- 23xyz45 (Actual)\n' + ' ^^^ ^^^' ); }); chars('should print "·" character for space', () => { assert.is( strip($.chars('foo bar', 'foo bar baz')), '++foo·bar·baz (Expected)\n' + '--foo·bar (Actual)\n' + ' ^^^^' ); assert.is( strip($.chars('foo bar', 'foo bar')), '++foo·bar (Expected)\n' + '--foo···bar (Actual)\n' + ' ^^ ' ); }); chars('should print "→" character for tab', () => { assert.is( strip($.chars('foo bar\tbaz \t', 'foo bar\tbaz \t\t bat')), '++foo·bar→baz·→→·bat (Expected)\n' + '--foo·bar→baz·→ (Actual)\n' + ' ^^^^^' ); assert.is( strip($.chars('foo bar\tbaz \t\t bat', 'foo bar\tbaz \t')), '++foo·bar→baz·→ (Expected)\n' + '--foo·bar→baz·→→·bat (Actual)\n' + ' ^^^^^' ); assert.is( strip($.chars('foo\tbar\tbaz', 'foo bar baz')), '++foo·bar·baz (Expected)\n' + '--foo→bar→baz (Actual)\n' + ' ^ ^ ' ); }); chars('should handle empty string', () => { assert.is( strip($.chars('foo bar', '')), '++ (Expected)\n' + '--foo·bar (Actual)\n' + ' ^^^^^^^' ); assert.is( strip($.chars('', 'foo bar')), '++foo·bar (Expected)\n' + '-- (Actual)\n' + ' ^^^^^^^' ); }) chars.run(); // --- const direct = suite('direct'); direct('should be a function', () => { assert.type($.direct, 'function'); }); // TODO: return nothing ? direct.skip('should no differences with identical text', () => { // assert.fixture(); }); direct('should handle `"foo"` vs `"fo"` diff', () => { assert.snapshot( strip($.direct('foo', 'fo')), '++fo (Expected)\n' + '--foo (Actual)\n' ); }); direct('should handle `"fo"` vs `"foo"` diff', () => { assert.snapshot( strip($.direct('fo', 'foo')), '++foo (Expected)\n' + '--fo (Actual)\n' ); }); direct('should handle `"foo"` vs `"bar"` diff', () => { assert.snapshot( strip($.direct('foo', 'bar')), '++bar (Expected)\n' + '--foo (Actual)\n' ); }); direct('should handle `123` vs `456` diff', () => { assert.snapshot( strip($.direct(123, 456)), '++456 (Expected)\n' + '--123 (Actual)\n' ); }); direct('should handle `123` vs `"123"` diff', () => { assert.snapshot( strip($.direct(123, '123')), '++123 [string] (Expected)\n' + '--123 [number] (Actual)\n' ); assert.snapshot( strip($.direct('123', 123)), '++123 [number] (Expected)\n' + '--123 [string] (Actual)\n' ); }); direct('should handle `12` vs `"123"` diff', () => { assert.snapshot( strip($.direct(12, '123')), '++123 [string] (Expected)\n' + '--12 [number] (Actual)\n' ); assert.snapshot( strip($.direct('123', 12)), '++12 [number] (Expected)\n' + '--123 [string] (Actual)\n' ); }); direct('should handle `null` vs `"null"` diff', () => { assert.snapshot( strip($.direct(null, 'null')), '++null [string] (Expected)\n' + '--null [object] (Actual)\n' ); assert.snapshot( strip($.direct('null', null)), '++null [object] (Expected)\n' + '--null [string] (Actual)\n' ); }); direct('should handle `true` vs `"true"` diff', () => { assert.snapshot( strip($.direct(true, 'true')), '++true [string] (Expected)\n' + '--true [boolean] (Actual)\n' ); assert.snapshot( strip($.direct('true', true)), '++true [boolean] (Expected)\n' + '--true [string] (Actual)\n' ); }); direct('should handle `false` vs `"true"` diff', () => { assert.snapshot( strip($.direct(false, 'true')), '++true [string] (Expected)\n' + '--false [boolean] (Actual)\n' ); assert.snapshot( strip($.direct('true', false)), '++false [boolean] (Expected)\n' + '--true [string] (Actual)\n' ); }); direct.run(); // --- const compare = suite('compare'); compare('should be a function', () => { assert.type($.compare, 'function'); }); compare('should proxy `$.arrays` for Array inputs', () => { assert.is( strip($.compare([1, 2, 3], [1, 2, 4])), '··[\n' + '····1,\n' + '····2,\n' + 'Actual:\n' + '--··3,\n' + 'Expected:\n' + '++··4,\n' + '··]\n' ); }); compare('should proxy `$.chars` for RegExp inputs', () => { assert.is( strip($.compare(/foo/g, /foobar/gi)), '++/foobar/gi (Expected)\n' + '--/foo/g (Actual)\n' + ' ^^^ ^' ); assert.is( strip($.compare(/foobar/gi, /foo/g)), '++/foo/g (Expected)\n' + '--/foobar/gi (Actual)\n' + ' ^^^ ^' ); }); compare('should proxy `$.lines` for Object inputs', () => { assert.is( strip($.compare({ foo: 1 }, { foo: 2, bar: 3 })), '··{\n' + 'Actual:\n' + '--··"foo":·1\n' + 'Expected:\n' + '++··"foo":·2,\n' + '++··"bar":·3\n' + '··}\n' ); assert.is( strip($.compare({ foo: 2, bar: 3 }, { foo: 1 })), '··{\n' + 'Actual:\n' + '--··"foo":·2,\n' + '--··"bar":·3\n' + 'Expected:\n' + '++··"foo":·1\n' + '··}\n' ); assert.is( strip($.compare({ foo: 2, bar: undefined, baz: NaN }, { foo: 1, bar: null })), '··{\n' + 'Actual:\n' + '--··"foo":·2,\n' + '--··"bar":·undefined,\n' + '--··"baz":·NaN\n' + 'Expected:\n' + '++··"foo":·1,\n' + '++··"bar":·null\n' + '··}\n' ); assert.is( strip($.compare({ foo: 2, bar: null, baz: NaN }, { foo: 2, bar: undefined, baz: NaN })), '··{\n' + '····"foo":·2,\n' + 'Actual:\n' + '--··"bar":·null,\n' + 'Expected:\n' + '++··"bar":·undefined,\n' + '····"baz":·NaN\n' + '··}\n' ); }); compare('should proxy `$.lines` for multi-line String inputs', () => { assert.is( strip($.compare('foo\nbar', 'foo\nbat')), '··foo\n' + 'Actual:\n' + '--bar\n' + 'Expected:\n' + '++bat\n' ); }); compare('should proxy `$.chars` for single-line String inputs', () => { assert.is( strip($.compare('foobar', 'foobaz')), '++foobaz (Expected)\n' + '--foobar (Actual)\n' + ' ^' ); }); compare('should proxy `$.direct` for Number inputs', () => { assert.snapshot( strip($.compare(123, 12345)), '++12345 (Expected)\n' + '--123 (Actual)\n' ); assert.snapshot( strip($.compare(123, NaN)), '++NaN (Expected)\n' + '--123 (Actual)\n' ); assert.snapshot( strip($.compare(NaN, 123)), '++123 (Expected)\n' + '--NaN (Actual)\n' ); }); compare('should proxy `$.direct` for Boolean inputs', () => { assert.snapshot( strip($.compare(true, false)), '++false (Expected)\n' + '--true (Actual)\n' ); }); compare('should handle string against non-type mismatch', () => { assert.snapshot( strip($.compare('foobar', null)), '++null [object] (Expected)\n' + '--foobar [string] (Actual)\n' ); assert.snapshot( strip($.compare(null, 'foobar')), '++foobar [string] (Expected)\n' + '--null [object] (Actual)\n' ); assert.snapshot( strip($.compare('foobar', 123)), '++123 [number] (Expected)\n' + '--foobar [string] (Actual)\n' ); assert.snapshot( strip($.compare(123, 'foobar')), '++foobar [string] (Expected)\n' + '--123 [number] (Actual)\n' ); assert.snapshot( strip($.compare('foobar', undefined)), '++undefined [undefined] (Expected)\n' + '--foobar [string] (Actual)\n' ); assert.snapshot( strip($.compare(undefined, 'foobar')), '++foobar [string] (Expected)\n' + '--undefined [undefined] (Actual)\n' ); assert.snapshot( strip($.compare(NaN, undefined)), '++undefined [undefined] (Expected)\n' + '--NaN [number] (Actual)\n' ); assert.snapshot( strip($.compare(undefined, NaN)), '++NaN [number] (Expected)\n' + '--undefined [undefined] (Actual)\n' ); }); compare('should handle multi-line string against non-type mismatch', () => { assert.snapshot( strip($.compare('foo\nbar', null)), 'Actual:\n' + '--foo\n' + '--bar\n' + 'Expected:\n' + '++null\n' ); assert.snapshot( strip($.compare(null, 'foo\nbar')), 'Actual:\n' + '--null\n' + 'Expected:\n' + '++foo\n' + '++bar\n' ); assert.snapshot( strip($.compare('foo\nbar', 123)), 'Actual:\n' + '--foo\n' + '--bar\n' + 'Expected:\n' + '++123\n' ); assert.snapshot( strip($.compare(123, 'foo\nbar')), 'Actual:\n' + '--123\n' + 'Expected:\n' + '++foo\n' + '++bar\n' ); assert.snapshot( strip($.compare('foo\nbar', undefined)), 'Actual:\n' + '--foo\n' + '--bar\n' + 'Expected:\n' + '++undefined\n' ); assert.snapshot( strip($.compare(undefined, 'foo\nbar')), 'Actual:\n' + '--undefined\n' + 'Expected:\n' + '++foo\n' + '++bar\n' ); }); compare('should handle `null` vs object', () => { assert.snapshot( strip($.compare(null, { foo: 123 })), 'Actual:\n' + '--null\n' + 'Expected:\n' + '++{\n' + '++··"foo":·123\n' + '++}\n' ); assert.snapshot( strip($.compare({ foo: 123 }, null)), 'Actual:\n' + '--{\n' + '--··"foo":·123\n' + '--}\n' + 'Expected:\n' + '++null\n' ); }); compare('should handle `undefined` vs object', () => { assert.snapshot( strip($.compare(undefined, { foo: 123 })), 'Actual:\n' + '--undefined\n' + 'Expected:\n' + '++{\n' + '++··"foo":·123\n' + '++}\n' ); assert.snapshot( strip($.compare({ foo: 123 }, undefined)), 'Actual:\n' + '--{\n' + '--··"foo":·123\n' + '--}\n' + 'Expected:\n' + '++undefined\n' ); }); compare.run(); // --- const sort = suite('sort()'); sort('should ignore Date instances', () => { assert.equal($.sort({}, new Date), {}); assert.equal($.sort(new Date, new Date), {}); assert.equal($.sort(new Date, {}), {}); }); sort('should ignore RegExp instances', () => { assert.equal($.sort({}, /foo/), {}); assert.equal($.sort(/foo/, /foo/), {}); assert.equal($.sort(/foo/, {}), {}); }); sort('should ignore Set instances', () => { assert.equal($.sort({}, new Set), {}); assert.equal($.sort(new Set, new Set), {}); assert.equal($.sort(new Set, {}), {}); }); sort('should ignore Map instances', () => { assert.equal($.sort({}, new Map), {}); assert.equal($.sort(new Map, new Map), {}); assert.equal($.sort(new Map, {}), {}); }); sort('should align `input` to `expect` key order', () => { assert.equal( $.sort({ b: 2, a: 1 }, { a: 1, b: 2 }), { a: 1, b: 2 } ); }); sort('should append extra `input` keys', () => { assert.equal( $.sort({ c: 3, b: 2, a: 1 }, { a: 1 }), { a: 1, c: 3, b: 2 } ); }); sort('should omit missing `expect` keys', () => { assert.equal( $.sort({ c: 3, a: 1 }, { a: 1, b: 2, c: 3 }), { a: 1, c: 3 } ); }); sort('should loop through Arrays for nested sorts', () => { assert.equal( $.sort([ { a2: 2, a1: 1 }, { b3: 3, b2: 2 }, ], [ { a1: 1, a2: 2, a3: 3 }, { b1: 1, b2: 2, b3: 3 }, ]), [ { a1: 1, a2: 2 }, { b2: 2, b3: 3 }, ] ); }); sort('should handle nested Object sorts', () => { assert.equal( $.sort({ bar: { b:2, a:1 }, foo: { c:3, b:2, a:1 }, }, { foo: { b:2, c:3 }, bar: { b:2 }, }), { foo: { b:2, c:3, a:1 }, bar: { b:2, a:1 }, } ); }); sort('should handle Object dictionary', () => { let input = Object.create(null); let expect = Object.create(null); input.aaa = 123; input.bbb = 123; input.ccc = 123; expect.ccc = 123; expect.aaa = 123; expect.bbb = 123; assert.equal( $.sort(input, expect), { ccc: 123, bbb: 123, aaa: 123 } ); }); sort.run(); // --- const circular = suite('circular'); circular('should ignore non-object values', () => { const input = { a:1, b:2, c:'c', d:null, e:()=>{} }; assert.is( JSON.stringify(input, $.circular()), '{"a":1,"b":2,"c":"c","d":null}' ); }); circular('should retain `undefined` and `NaN` values', () => { const input = { a:1, b:undefined, c:NaN }; assert.is( JSON.stringify(input, $.circular()), '{"a":1,"b":"[__VOID__]","c":"[__NAN__]"}' ); assert.is( JSON.stringify(input), '{"a":1,"c":null}' ); }); circular('should replace circular references with "[Circular]" :: Object', () => { const input = { a:1, b:2 }; input.self = input; assert.is( JSON.stringify(input, $.circular()), '{"a":1,"b":2,"self":"[Circular]"}' ); assert.throws( () => JSON.stringify(input), 'Converting circular structure to JSON' ); assert.is( JSON.stringify({ aaa: input, bbb: 123 }, $.circular()), '{"aaa":{"a":1,"b":2,"self":"[Circular]"},"bbb":123}' ); }); circular('should replace circular references with "[Circular]" :: Array', () => { const input = { a:1, b:2 }; input.self = input; assert.is( JSON.stringify([input], $.circular()), '[{"a":1,"b":2,"self":"[Circular]"}]' ); assert.throws( () => JSON.stringify(input), 'Converting circular structure to JSON' ); assert.is( JSON.stringify([{ aaa:1 }, { aaa:input }], $.circular()), '[{"aaa":1},{"aaa":{"a":1,"b":2,"self":"[Circular]"}}]', ); }); circular.run(); // --- const stringify = suite('stringify'); stringify('should wrap `JSON.stringify` native', () => { const input = { a:1, b:2, c:'c', d:null, e:()=>{} }; assert.is( $.stringify(input), JSON.stringify(input, null, 2) ); }); stringify('should retain `undefined` and `NaN` values :: Object', () => { assert.is( $.stringify({ a: 1, b: undefined, c: NaN }), '{\n "a": 1,\n "b": undefined,\n "c": NaN\n}' ); }); // In ES6, array holes are treated like `undefined` values stringify('should retain `undefined` and `NaN` values :: Array', () => { assert.is( $.stringify([1, undefined, 2, , 3, NaN, 4, 5]), '[\n 1,\n undefined,\n 2,\n undefined,\n 3,\n NaN,\n 4,\n 5\n]' ); }); stringify.run(); uvu-0.5.3/test/index.js000066400000000000000000000017101416513034700147650ustar00rootroot00000000000000const kleur = require('kleur'); const assert = require('assert'); const { totalist } = require('totalist/sync'); const { spawnSync } = require('child_process'); let code = 0; const LEFT = kleur.dim().red(' || '); const HOOK = ['-r', 'esm', '-r', 'module-alias/register']; const PASS = kleur.green().bold('[PASS] '); const FAIL = kleur.red().bold('[FAIL] '); totalist(__dirname, (rel, abs) => { if (rel === 'index.js') return; let pid = spawnSync('node', HOOK.concat(abs)); let file = kleur.bold().underline(rel); try { assert.equal(pid.status, 0, 'run w/o error code'); assert.equal(pid.stderr.length, 0, 'run w/o stderr'); assert.equal(pid.stdout.length > 0, true, 'run w/ stdout'); console.log(PASS + file); } catch (err) { console.error(FAIL + file + ' :: "%s"', err.message); if (pid.stdout.length) { console.error(LEFT + '\n' + LEFT + pid.stdout.toString().replace(/(\r?\n)/g, '$1' + LEFT)); } code = 1; } }); process.exit(code); uvu-0.5.3/test/parse.js000066400000000000000000000077351416513034700150050ustar00rootroot00000000000000import { suite } from 'uvu'; import { readdirSync } from 'fs'; import { isAbsolute } from 'path'; import * as assert from 'uvu/assert'; import * as $ from '../src/parse'; const FILES = readdirSync(__dirname); const parse = suite('parse'); parse('should be a function', () => { assert.type($.parse, 'function'); }); parse('should rely on defaults', async () => { // dirname to avoid node_modules let output = await $.parse(__dirname); assert.type(output, 'object'); assert.is(output.dir, __dirname); assert.is(output.requires, false); assert.instance(output.suites, Array); assert.is(output.suites.length, FILES.length); output.suites.forEach(suite => { assert.is.not(isAbsolute(suite.name)); assert.is(FILES.includes(suite.name), true, '~> suite.name is relative filename') assert.is(isAbsolute(suite.file), true, '~> suite.file is absolute path'); }); }); parse.run(); // --- const dir = suite('dir'); dir('should accept relative `dir` input', async () => { let output = await $.parse('test'); assert.type(output, 'object'); assert.is(output.dir, __dirname); assert.is(output.requires, false); assert.instance(output.suites, Array); assert.is(output.suites.length, FILES.length); output.suites.forEach(suite => { assert.is.not(isAbsolute(suite.name)); assert.is(FILES.includes(suite.name), true, '~> suite.name is relative filename') assert.is(isAbsolute(suite.file), true, '~> suite.file is absolute path'); }); }); dir.run(); // --- const pattern = suite('pattern'); pattern('should only load tests matching pattern :: RegExp', async () => { let foo = await $.parse(__dirname, /assert/); assert.is(foo.suites[0].name, 'assert.js'); assert.is(foo.suites.length, 1); let bar = await $.parse(__dirname, /^uvu\.js$/); assert.is(bar.suites[0].name, 'uvu.js'); assert.is(bar.suites.length, 1); }); pattern('should only load tests matching pattern :: string', async () => { let foo = await $.parse(__dirname, 'assert'); assert.is(foo.suites[0].name, 'assert.js'); assert.is(foo.suites.length, 1); let bar = await $.parse(__dirname, '^uvu\\.js$'); assert.is(bar.suites[0].name, 'uvu.js'); assert.is(bar.suites.length, 1); }); pattern.run(); // --- const cwd = suite('options.cwd'); cwd('should affect from where `dir` resolves', async () => { let foo = await $.parse('.', '', { cwd: __dirname }); assert.is(foo.suites.length, FILES.length); foo.suites.forEach(suite => { assert.is(FILES.includes(suite.name), true, '~> suite.name is relative filename') assert.is(isAbsolute(suite.file), true, '~> suite.file is absolute path'); }); }); cwd.run(); // --- const ignore = suite('options.ignore'); ignore('should ignore test files matching :: RegExp', async () => { let foo = await $.parse(__dirname, '', { ignore: /assert/ }); assert.is(foo.suites.find(x => x.name === 'assert.js'), undefined); assert.is(foo.suites.length, FILES.length - 1); let bar = await $.parse(__dirname, '', { ignore: /^uvu\.js$/ }); assert.is(bar.suites.find(x => x.name === 'uvu.js'), undefined); assert.is(bar.suites.length, FILES.length - 1); }); ignore('should ignore test files matching :: RegExp', async () => { let foo = await $.parse(__dirname, '', { ignore: 'assert' }); assert.is(foo.suites.find(x => x.name === 'assert.js'), undefined); assert.is(foo.suites.length, FILES.length - 1); let bar = await $.parse(__dirname, '', { ignore: 'uvu.js' }); assert.is(bar.suites.find(x => x.name === 'uvu.js'), undefined); assert.is(bar.suites.length, FILES.length - 1); }); ignore.run(); // --- const requires = suite('options.require'); requires('should throw on invalid value(s)', async () => { try { await $.parse(__dirname, '', { require: ['foobar'] }); assert.unreachable('should have thrown'); } catch (err) { assert.instance(err, Error); assert.match(err.message, `Cannot find module 'foobar'`); } }); requires('should `require` valid value(s)', async () => { let foo = await $.parse(__dirname, '', { require: ['esm'] }); assert.is(foo.requires, true); }); requires.run(); uvu-0.5.3/test/suite.js000066400000000000000000000134461416513034700150200ustar00rootroot00000000000000import { suite } from 'uvu'; import * as assert from 'uvu/assert'; const hooks = suite('hooks'); const hooks_state = { before: 0, after: 0, each: 0, }; hooks.before(() => { assert.is(hooks_state.before, 0); assert.is(hooks_state.after, 0); assert.is(hooks_state.each, 0); hooks_state.before++; }); hooks.after(() => { assert.is(hooks_state.before, 1); assert.is(hooks_state.after, 0); assert.is(hooks_state.each, 0); hooks_state.after++; }); hooks.before.each(() => { assert.is(hooks_state.before, 1); assert.is(hooks_state.after, 0); assert.is(hooks_state.each, 0); hooks_state.each++; }); hooks.after.each(() => { assert.is(hooks_state.before, 1); assert.is(hooks_state.after, 0); assert.is(hooks_state.each, 1); hooks_state.each--; }); hooks('test #1', () => { assert.is(hooks_state.before, 1); assert.is(hooks_state.after, 0); assert.is(hooks_state.each, 1); }); hooks('test #2', () => { assert.is(hooks_state.before, 1); assert.is(hooks_state.after, 0); assert.is(hooks_state.each, 1); }); hooks.run(); hooks('ensure after() ran', () => { assert.is(hooks_state.before, 1); assert.is(hooks_state.after, 1); assert.is(hooks_state.each, 0); }); hooks.run(); // --- const skips = suite('suite.skip()'); const skips_state = { each: 0, }; skips.before.each(() => { skips_state.each++; }); skips('normal #1', () => { assert.ok('i should run'); assert.is(skips_state.each, 1); }); skips.skip('literal', () => { assert.unreachable('i should not run'); }); skips('normal #2', () => { assert.ok('but i should'); assert.is(skips_state.each, 2, 'did not run hook for skipped function'); }); skips.run(); // --- const only = suite('suite.only()'); const only_state = { each: 0, }; only.before.each(() => { only_state.each++; }); only('normal', () => { assert.unreachable('i should not run'); }); only.skip('modifier: skip', () => { assert.unreachable('i should not run'); }); only.only('modifier: only #1', () => { assert.ok('i should run'); assert.is(only_state.each, 1, 'did not run normal or skipped tests'); }); only.only('modifier: only #2', () => { assert.ok('i should also run'); assert.is(only_state.each, 2, 'did not run normal or skipped tests'); }); only.run(); // --- const context1 = suite('context #1'); context1.before(ctx => { assert.is(ctx.before, undefined); assert.is(ctx.after, undefined); assert.is(ctx.each, undefined); Object.assign(ctx, { before: 1, after: 0, each: 0, }); }); context1.after(ctx => { assert.is(ctx.before, 1); assert.is(ctx.after, 0); assert.is(ctx.each, 0); ctx.after++; }); context1.before.each(ctx => { assert.is(ctx.before, 1); assert.is(ctx.after, 0); assert.is(ctx.each, 0); ctx.each++; }); context1.after.each(ctx => { assert.is(ctx.before, 1); assert.is(ctx.after, 0); assert.is(ctx.each, 1); ctx.each--; }); context1('test #1', ctx => { assert.is(ctx.before, 1); assert.is(ctx.after, 0); assert.is(ctx.each, 1); }); context1('test #2', ctx => { assert.is(ctx.before, 1); assert.is(ctx.after, 0); assert.is(ctx.each, 1); }); context1.run(); context1('ensure after() ran', ctx => { assert.is(ctx.before, 1); assert.is(ctx.after, 1); assert.is(ctx.each, 0); }); context1.run(); // --- const context2 = suite('context #2', { before: 0, after: 0, each: 0, }); context2.before(ctx => { assert.is(ctx.before, 0); assert.is(ctx.after, 0); assert.is(ctx.each, 0); ctx.before++; }); context2.after(ctx => { assert.is(ctx.before, 1); assert.is(ctx.after, 0); assert.is(ctx.each, 0); ctx.after++; }); context2.before.each(ctx => { assert.is(ctx.before, 1); assert.is(ctx.after, 0); assert.is(ctx.each, 0); ctx.each++; }); context2.after.each(ctx => { assert.is(ctx.before, 1); assert.is(ctx.after, 0); assert.is(ctx.each, 1); ctx.each--; }); context2('test #1', ctx => { assert.is(ctx.before, 1); assert.is(ctx.after, 0); assert.is(ctx.each, 1); }); context2('test #2', ctx => { assert.is(ctx.before, 1); assert.is(ctx.after, 0); assert.is(ctx.each, 1); }); context2.run(); context2('ensure after() ran', ctx => { assert.is(ctx.before, 1); assert.is(ctx.after, 1); assert.is(ctx.each, 0); }); context2.run(); // --- const input = { a: 1, b: [2, 3, 4], c: { foo: 5 }, set: new Set([1, 2]), date: new Date(), map: new Map, }; const context3 = suite('context #3', input); context3('should access keys', ctx => { assert.is(ctx.a, input.a); assert.equal(ctx.b, input.b); assert.equal(ctx.c, input.c); }); context3('should allow context modifications', ctx => { ctx.a++; assert.is(ctx.a, 2); assert.is(input.a, 2); ctx.b.push(999); assert.equal(ctx.b, [2, 3, 4, 999]); assert.equal(input.b, [2, 3, 4, 999]); ctx.c.foo++; assert.is(ctx.c.foo, 6); assert.is(input.c.foo, 6); ctx.c.bar = 6; assert.equal(ctx.c, { foo: 6, bar: 6 }); assert.equal(input.c, { foo: 6, bar: 6 }); }); context3('should allow self-referencing instance(s) within context', ctx => { const { date, set, map } = ctx; assert.type(date.getTime(), 'number'); assert.equal([...set.values()], [1, 2]); assert.equal([...map.entries()], []); }); context3.run(); // --- const breadcrumbs = suite('breadcrumbs', { count: 1, }); breadcrumbs.before(ctx => { assert.is(ctx.__suite__, 'breadcrumbs'); assert.is(ctx.__test__, ''); }); breadcrumbs.after(ctx => { assert.is(ctx.__suite__, 'breadcrumbs'); assert.is(ctx.__test__, ''); }); breadcrumbs.before.each(ctx => { assert.is(ctx.__suite__, 'breadcrumbs'); assert.is(ctx.__test__, `test #${ctx.count}`); }); breadcrumbs.after.each(ctx => { assert.is(ctx.__suite__, 'breadcrumbs'); assert.is(ctx.__test__, `test #${ctx.count++}`); }); breadcrumbs('test #1', (ctx) => { assert.is(ctx.__suite__, 'breadcrumbs'); assert.is(ctx.__test__, 'test #1'); }); breadcrumbs('test #2', (ctx) => { assert.is(ctx.__suite__, 'breadcrumbs'); assert.is(ctx.__test__, 'test #2'); }); breadcrumbs.run(); uvu-0.5.3/test/uvu.js000066400000000000000000000007371416513034700145050ustar00rootroot00000000000000import { suite } from 'uvu'; import * as assert from 'uvu/assert'; import * as uvu from '../src/index'; const ste = suite('suite'); ste('should be a function', () => { assert.type(uvu.suite, 'function'); }); ste.run(); // --- const test = suite('test'); test('should be a function', () => { assert.type(uvu.test, 'function'); }); test.run(); // --- const exec = suite('exec'); exec('should be a function', () => { assert.type(uvu.exec, 'function'); }); exec.run();