Skip to content

Commit

Permalink
break: remove context read-only guard;
Browse files Browse the repository at this point in the history
- Closes #39
- Closes #58
  • Loading branch information
lukeed committed Nov 3, 2020
1 parent 1772435 commit 3d3076d
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 65 deletions.
21 changes: 5 additions & 16 deletions docs/api.uvu.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ The name of your suite. <br>This groups all console output together and will pre
Type: `any`<br>
Default: `{}`

The suite's initial [context](#context-1) value, if any. <br>This will be passed to every [hook](#hooks) for modification and to every test block within the suite for read-only access.
The suite's initial [context](#context-1) value, if any. <br>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`
Expand All @@ -49,8 +51,6 @@ Type: `Function<any>` or `Promise<any>`

The callback that contains your test code. <br>Your callback may be asynchronous and may `return` any value, although returned values are discarded completely and have no effect.

> **Note:** Tests' callbacks have **read-only access** to the [suite's context](#context-1).

## Suites

Expand Down Expand Up @@ -146,7 +146,7 @@ It may be appropriate to use `suite.before.each` and `suite.after.each` to reset

> **Important:** Any `after` and `after.each` hooks will _always_ be invoked – including after failed assertions.
Additionally, as of `uvu@0.3.0`, hooks will receive the suite's context value. Unlike the suite's tests, hooks 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.
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***

Expand Down Expand Up @@ -295,7 +295,7 @@ User.after.each(async context => {
User.run()
```

Individual tests will receive a **read-only** snapshot of the context. This is how tests can access the HTTP client or database fixture you've set up, for example. However tests **cannot modify context** so as to preserve the environment and prevent tests from affecting sequential tests.
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:

Expand All @@ -312,17 +312,6 @@ User('should not have Teams initially', async context => {
});
```

Now, to enforce read-only access, `uvu` will emit a warning whenever a test attempts to set or mutate a context value:

```js
User('this will do nothing', context => {
context.hello = 'world';
// stdout => [WARN] Cannot modify context within tests!
assert.is(context.hello, 'world'); // FAIL!
assert.is(context.hello, undefined); // PASS!
});
```

***TypeScript***

Finally, TypeScript users can easily define their suites' contexts on a suite-by-suite basis.
Expand Down
24 changes: 1 addition & 23 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,29 +56,8 @@ function format(name, err, suite = '') {
return str + '\n';
}

function toProxy(cache) {
return {
get(obj, key) {
let tmp = obj[key];
if (!tmp || typeof tmp !== 'object') return tmp;

let nxt = cache.get(tmp);
if (nxt) return nxt;

nxt = new Proxy(tmp, this);
cache.set(tmp, nxt);
return nxt;
},
set() {
write('\n' + kleur.yellow('[WARN]') + ' Cannot modify context within tests!\n');
return true;
}
}
}

async function runner(ctx, name) {
let { only, tests, before, after, bEach, aEach, state } = ctx;
let reader = Proxy.revocable(state, toProxy(new Map));
let hook, test, arr = only.length ? only : tests;
let num=0, errors='', total=arr.length;

Expand All @@ -89,7 +68,7 @@ async function runner(ctx, name) {
for (test of arr) {
try {
for (hook of bEach) await hook(state);
await test.handler(reader.proxy);
await test.handler(state);
for (hook of aEach) await hook(state);
write(PASS);
num++;
Expand All @@ -101,7 +80,6 @@ async function runner(ctx, name) {
}
}
} finally {
reader.revoke();
for (hook of after) await hook(state);
let msg = ` (${num} / ${total})\n`;
let skipped = (only.length ? tests.length : 0) + ctx.skips;
Expand Down
46 changes: 20 additions & 26 deletions test/suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,6 @@ only.run();

// ---

// changes should be ignored
function test_mutation(ctx) {
ctx.hello = 'world';
assert.is(ctx.hello, undefined);

let old = ctx.before++;
assert.is(ctx.before, old);
}

const context1 = suite('context #1');

context1.before(ctx => {
Expand Down Expand Up @@ -175,8 +166,6 @@ context1('test #2', ctx => {
assert.is(ctx.before, 1);
assert.is(ctx.after, 0);
assert.is(ctx.each, 1);

test_mutation(ctx);
});

context1.run();
Expand All @@ -185,8 +174,6 @@ context1('ensure after() ran', ctx => {
assert.is(ctx.before, 1);
assert.is(ctx.after, 1);
assert.is(ctx.each, 0);

test_mutation(ctx);
});

context1.run();
Expand Down Expand Up @@ -231,16 +218,12 @@ context2('test #1', ctx => {
assert.is(ctx.before, 1);
assert.is(ctx.after, 0);
assert.is(ctx.each, 1);

test_mutation(ctx);
});

context2('test #2', ctx => {
assert.is(ctx.before, 1);
assert.is(ctx.after, 0);
assert.is(ctx.each, 1);

test_mutation(ctx);
});

context2.run();
Expand All @@ -259,6 +242,9 @@ 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);
Expand All @@ -269,22 +255,30 @@ context3('should access keys', ctx => {
assert.equal(ctx.c, input.c);
});

context3('should ignore modifications', ctx => {
context3('should allow context modifications', ctx => {
ctx.a++;
assert.is(ctx.a, 1);
assert.is(input.a, 1);
assert.is(ctx.a, 2);
assert.is(input.a, 2);

ctx.b.push(999);
assert.equal(ctx.b, [2, 3, 4]);
assert.equal(input.b, [2, 3, 4]);
assert.equal(ctx.b, [2, 3, 4, 999]);
assert.equal(input.b, [2, 3, 4, 999]);

ctx.c.foo++;
assert.is(ctx.c.foo, 5);
assert.is(input.c.foo, 5);
assert.is(ctx.c.foo, 6);
assert.is(input.c.foo, 6);

ctx.c.bar = 6;
assert.equal(ctx.c, { foo: 5 });
assert.equal(input.c, { foo: 5 });
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();

0 comments on commit 3d3076d

Please sign in to comment.