Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding constraints around naming test waiters #126

Merged
merged 2 commits into from
May 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This addon implements the design specified in [RFC 581](https://github.com/ember
- [Installation](#installation)
- [Quickstart](#quickstart)
- [buildWaiter function](#buildwaiter-function)
- [Waiter naming conventions](#waiter-naming-conventions)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be down in the best practices section, with a link from here?

- [waitForPromise function](#waitforpromise-function)
- [Waiting on all waiters](#waiting-on-all-waiters)
- [General Design](#general-design)
Expand Down Expand Up @@ -59,17 +60,31 @@ that provides a number of methods. The key methods that allow you to control asy
a pair to _begin_ waiting and _end_ waiting respectively. The `beginAsync` method returns a `token`, which uniquely identifies that async operation. To mark the
async operation as complete, call `endAsync`, passing in the `token` that was returned from the prior `beginAsync` call.

#### Waiter naming conventions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a link to the eslint-plugin-ember rule docs in this section too?


When building your waiter, you should ensure you use a meaningful name. At a minimum, your name needs to be constructed of a `namespace`, and optionally a `descriptor` in the format `namespace[:descriptor]`. Suggestions for naming conventions are as follows:

For apps:

1. `file-name` - if your file has only one waiter, the file name becomes the namespace
1. `file-name:waiter-1`, `file-name:waiter-2`, ... - if your file has more than one waiter, the file name becomes the namespace and we add descriptors

For addons:

1. `addon-name` - if your addon has only one waiter, the addon name becomes the namespace
1. `addon-name:waiter-1`, `addon-name:waiter-2`, ... - if your addon has more than one waiter, the addon name becomes the namespace and we add descriptors

```js
import Component from '@ember/component';
import { buildWaiter } from 'ember-test-waiters';

let waiter = buildWaiter('friend-waiter');
let waiter = buildWaiter('ember-friendz:friend-waiter');

export default class Friendz extends Component {
didInsertElement() {
let token = waiter.beginAsync();

someAsyncWork()
makeFriendz()
.then(() => {
//... some work
})
Expand All @@ -91,8 +106,8 @@ import { waitForPromise } from 'ember-test-waiters';

export default class MoreFriendz extends Component {
didInsertElement() {
waitForPromise(someAsyncWork).then(() => {
doOtherThings();
waitForPromise(makeFriendz).then(() => {
return goForDrinks();
});
}
}
Expand Down
30 changes: 28 additions & 2 deletions addon/build-waiter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import { Primitive, TestWaiter, TestWaiterDebugInfo, WaiterName } from 'ember-te
import { DEBUG } from '@glimmer/env';
import Token from './token';
import { register } from './waiter-manager';
import { warn } from '@ember/debug';

const WAITER_NAME_PATTERN = /^[^:]*:?.*/;
let WAITER_NAMES = DEBUG ? new Set() : undefined;

export function _resetWaiterNames() {
WAITER_NAMES = new Set();
}

function getNextToken(): Token {
return new Token();
Expand Down Expand Up @@ -135,7 +143,25 @@ class NoopTestWaiter implements TestWaiter {
*/
export default function buildWaiter(name: string): TestWaiter {
scalvert marked this conversation as resolved.
Show resolved Hide resolved
if (DEBUG) {
return new TestWaiterImpl(name);
warn(`The waiter name '${name}' is already in use`, !WAITER_NAMES!.has(name), {
id: 'ember-test-waiters.duplicate-waiter-name',
});
WAITER_NAMES!.add(name);
}

if (!DEBUG) {
return new NoopTestWaiter(name);
}
return new NoopTestWaiter(name);

warn(
`You must provide a name that contains a descriptive prefix separated by a colon.

Example: ember-fictitious-addon:some-file

You passed: ${name}`,
WAITER_NAME_PATTERN.test(name),
{ id: 'ember-test-waiters.invalid-waiter-name' }
);

return new TestWaiterImpl(name);
}
2 changes: 1 addition & 1 deletion addon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ export {
hasPendingWaiters,
} from './waiter-manager';

export { default as buildWaiter } from './build-waiter';
export { default as buildWaiter, _resetWaiterNames } from './build-waiter';
export { default as waitForPromise } from './wait-for-promise';
2 changes: 1 addition & 1 deletion addon/wait-for-promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { DEBUG } from '@glimmer/env';
import RSVP from 'rsvp';
import buildWaiter from './build-waiter';

const PROMISE_WAITER = buildWaiter('promise-waiter');
const PROMISE_WAITER = buildWaiter('ember-test-waiters:promise-waiter');

type PromiseType<T> = Promise<T> | RSVP.Promise<T>;

Expand Down
69 changes: 48 additions & 21 deletions tests/unit/build-waiter-test.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,68 @@
import MockStableError, { overrideError, resetError } from './utils/mock-stable-error';
import { _reset, buildWaiter, getPendingWaiterState, getWaiters } from 'ember-test-waiters';
import {
_reset,
_resetWaiterNames,
buildWaiter,
getPendingWaiterState,
getWaiters,
} from 'ember-test-waiters';
import { module, test } from 'qunit';

import { Promise } from 'rsvp';
import Token from 'ember-test-waiters/token';
import { registerWarnHandler } from '@ember/debug';

module('test-waiter', function(hooks) {
module('build-waiter', function(hooks) {
hooks.afterEach(function() {
_reset();
_resetWaiterNames();
resetError();
registerWarnHandler(() => {});
});

test('test waiter can be instantiated with a name', function(assert) {
let name = 'my-waiter';
test('test waiter can be instantiated with a namespace only', function(assert) {
let name = 'my-addon';
let waiter = buildWaiter(name);

assert.equal(waiter.name, name);
});

test('test waiter can be instantiated with a namespace and descriptor', function(assert) {
let name = 'my-addon:my-waiter';
let waiter = buildWaiter(name);

assert.equal(waiter.name, name);
});

test('test waiters will warn when waiter name is used more than once', function(assert) {
registerWarnHandler((message, options) => {
console.log('message', message);
assert.equal(message, "The waiter name 'ember-test-waiters:first' is already in use");
assert.equal(options.id, 'ember-test-waiters.duplicate-waiter-name');
});

buildWaiter('ember-test-waiters:first');
buildWaiter('ember-test-waiters:first');
});

test('test waiters return a token from beginAsync when no token provided', function(assert) {
let waiter = buildWaiter('my-waiter');
let waiter = buildWaiter('my-addon:my-waiter');

let token = waiter.beginAsync();

assert.ok(token instanceof Token, 'A token was returned from beginAsync');
});

test('test waiters return a truthy token from beginAsync when no token provided', function(assert) {
let waiter = buildWaiter('my-waiter');
let waiter = buildWaiter('my-addon:my-waiter');

let token = waiter.beginAsync();

assert.ok(token, 'A token was returned from beginAsync and is truthy');
});

test('test waiters automatically register when beginAsync is invoked when no token provided', function(assert) {
let waiter = buildWaiter('my-waiter');
let waiter = buildWaiter('my-addon:my-waiter');

let token = waiter.beginAsync();

Expand All @@ -50,7 +77,7 @@ module('test-waiter', function(hooks) {
});

test('test waiters automatically register when beginAsync is invoked using a custom token', function(assert) {
let waiter = buildWaiter('my-waiter');
let waiter = buildWaiter('ember-test-waiters:my-waiter');
let waiterItem = {};

waiter.beginAsync(waiterItem);
Expand All @@ -66,7 +93,7 @@ module('test-waiter', function(hooks) {
});

test('test waiters removes item from items map when endAsync is invoked', function(assert) {
let waiter = buildWaiter('my-waiter');
let waiter = buildWaiter('ember-test-waiters:my-waiter');

let token = waiter.beginAsync();
waiter.endAsync(token);
Expand All @@ -76,7 +103,7 @@ module('test-waiter', function(hooks) {
});

test('test waiters removes item from items map when endAsync is invoked using a custom token', function(assert) {
let waiter = buildWaiter('my-waiter');
let waiter = buildWaiter('ember-test-waiters:my-waiter');
let waiterItem = {};

waiter.beginAsync(waiterItem);
Expand All @@ -87,7 +114,7 @@ module('test-waiter', function(hooks) {
});

test('beginAsync will throw if a prior call to beginAsync with the same token occurred', function(assert) {
let waiter = buildWaiter('my-waiter');
let waiter = buildWaiter('ember-test-waiters:my-waiter');

assert.throws(
() => {
Expand All @@ -100,7 +127,7 @@ module('test-waiter', function(hooks) {
});

test('beginAsync will throw if a prior call to beginAsync with the same token occurred', function(assert) {
let waiter = buildWaiter('my-waiter');
let waiter = buildWaiter('ember-test-waiters:my-waiter');
let token = {};

assert.throws(
Expand All @@ -114,7 +141,7 @@ module('test-waiter', function(hooks) {
});

test('endAsync will throw if a prior call to beginAsync with the same token did not occur', function(assert) {
let waiter = buildWaiter('my-waiter');
let waiter = buildWaiter('ember-test-waiters:my-waiter');
let token = 0;

assert.throws(
Expand All @@ -127,7 +154,7 @@ module('test-waiter', function(hooks) {
});

test('endAsync will throw if a prior call to beginAsync with the same token did not occur using custom token', function(assert) {
let waiter = buildWaiter('my-waiter');
let waiter = buildWaiter('ember-test-waiters:my-waiter');
let waiterItem = {};

assert.throws(
Expand All @@ -142,7 +169,7 @@ module('test-waiter', function(hooks) {
test('endAsync will not throw if endAsync called twice in a row with the same token', function(assert) {
assert.expect(0);

let waiter = buildWaiter('my-waiter');
let waiter = buildWaiter('ember-test-waiters:my-waiter');
let token = waiter.beginAsync();

waiter.endAsync(token);
Expand All @@ -152,7 +179,7 @@ module('test-waiter', function(hooks) {
test('endAsync will not throw if endAsync called twice in a row with the same token using custom token', function(assert) {
assert.expect(0);

let waiter = buildWaiter('my-waiter');
let waiter = buildWaiter('ember-test-waiters:my-waiter');
let waiterItem = {};

waiter.beginAsync(waiterItem);
Expand All @@ -161,7 +188,7 @@ module('test-waiter', function(hooks) {
});

test('waitUntil returns the correct value if the waiter should wait', function(assert) {
let waiter = buildWaiter('my-waiter');
let waiter = buildWaiter('ember-test-waiters:my-waiter');
let waiterItem = {};

assert.ok(waiter.waitUntil(), 'waitUntil returns true');
Expand All @@ -176,7 +203,7 @@ module('test-waiter', function(hooks) {
});

test('waiter contains debug info for a waiter item', function(assert) {
let waiter = buildWaiter('my-waiter');
let waiter = buildWaiter('ember-test-waiters:my-waiter');
let waiterItem = {};

overrideError(MockStableError);
Expand All @@ -187,7 +214,7 @@ module('test-waiter', function(hooks) {
});

test('waiter executes beginAsync and endAsync at the correct times in relation to thenables', async function(assert) {
const promiseWaiter = buildWaiter('promise-waiter');
const promiseWaiter = buildWaiter('ember-test-waiters:promise-waiter');
function waitForPromise<T>(promise: Promise<T>, label?: string) {
let result = promise;

Expand Down Expand Up @@ -221,7 +248,7 @@ module('test-waiter', function(hooks) {
assert.deepEqual(getPendingWaiterState(), {
pending: 1,
waiters: {
'promise-waiter': [
'ember-test-waiters:promise-waiter': [
{
label: undefined,
stack: 'STACK',
Expand Down Expand Up @@ -249,7 +276,7 @@ module('test-waiter', function(hooks) {
});

test('waiter can clear items', function(assert) {
let waiter = buildWaiter('my-waiter');
let waiter = buildWaiter('ember-test-waiters:my-waiter');

waiter.beginAsync();

Expand Down
4 changes: 2 additions & 2 deletions tests/unit/wait-for-promise-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ if (DEBUG) {
assert.deepEqual(getPendingWaiterState(), {
pending: 1,
waiters: {
'promise-waiter': [
'ember-test-waiters:promise-waiter': [
{
label: undefined,
stack: 'STACK',
Expand Down Expand Up @@ -87,7 +87,7 @@ if (DEBUG) {
assert.deepEqual(getPendingWaiterState(), {
pending: 1,
waiters: {
'promise-waiter': [
'ember-test-waiters:promise-waiter': [
{
label: undefined,
stack: 'STACK',
Expand Down
10 changes: 5 additions & 5 deletions tests/unit/waiter-manager-noop-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { module, test } from 'qunit';
import { DEBUG } from '@glimmer/env';

if (!DEBUG) {
module('test-waiter | DEBUG: false', function(hooks) {
module('waiter-manager-noop | DEBUG: false', function(hooks) {
hooks.afterEach(function() {
_reset();
});
Expand All @@ -20,16 +20,16 @@ if (!DEBUG) {
});

test('register will correctly add a waiter', function(assert) {
let waiter = buildWaiter('first');
let waiter = buildWaiter('ember-test-waiters:first');

register(waiter);

let waiters = getWaiters().map(w => w.name);
assert.deepEqual(waiters, ['first']);
assert.deepEqual(waiters, ['ember-test-waiters:first']);
});

test('a NoopTestWaiter always returns true from waitUntil', function(assert) {
let waiter = buildWaiter('first');
let waiter = buildWaiter('ember-test-waiters:first');

assert.ok(waiter.waitUntil(), 'waitUntil returns true');
let token = waiter.beginAsync();
Expand All @@ -39,7 +39,7 @@ if (!DEBUG) {
});

test('a NoopTestWaiter always returns true from waitUntil', function(assert) {
let waiter = buildWaiter('first');
let waiter = buildWaiter('ember-test-waiters:first');
let waiterItem = {};

assert.ok(waiter.waitUntil(), 'waitUntil returns true');
Expand Down
Loading