eslint-plugin-task-needs-wait-for
v0.1.0
Published
Helps to prevent writing ember-concurrency tasks that would create flaky tests.
Downloads
1
Maintainers
Readme
eslint plugin task-needs-wait-for
Why
As per ember-test-waiters documentation:
... the waitFor function can be use to wait for async behavior. It can be used with async functions, and in decorator form to wrap an async function so calls to it are registered with the test waiter system. It can also be used with generator functions such as those used in ember-concurrency.
This hints that it's possible to write an ember-concurrency task in a way so that ember testing won't be aware of it and it will create a race condition.
Examples
Consider following component:
import Component from "@glimmer/component";
import { task, timeout } from "ember-concurrency";
import { tracked } from "@glimmer/tracking";
export default class FooComponent extends Component {
@tracked output = "nothing";
@task
*goodButton() {
this.output = "bad";
yield timeout(1000);
this.output = "good";
}
}
Thanks to using timeout
from ember-concurrency
we have the guarantee that tests will wait for the task to finish and the this.output
will have value good
.
Compare it to following:
import Component from "@glimmer/component";
import { task, timeout } from "ember-concurrency";
import { tracked } from "@glimmer/tracking";
export default class FooComponent extends Component {
@tracked output = "nothing";
@task
*badButton() {
this.output = "bad";
yield new Promise((resolve) => {
setTimeout(resolve, 1000);
});
this.output = "good";
}
}
This task will also wait 1000ms till it proceeds, but ember testing will not wait for it to complete, because yielding a simple Promise
won't register it with the ember test waiter system.
- This type of error is very easy to make.
- It leads to flaky tests. Imagine that the
setTimeout
(or whatever async behaviour) is very fast to resolve. Will the value ofoutput
begood
orbad
? Sometimes this, sometimes that. - It is an issue that is extremely hard to fix, because:
- It flakes only sometimes.
- It will fail on CI, but work perfectly fine on developer's machine.
- Can point at completely unrelated pieces of code.
This issue can be fixed simply by adding @waitFor
:
import Component from "@glimmer/component";
import { task, timeout } from "ember-concurrency";
import { tracked } from "@glimmer/tracking";
import { waitFor } from "@ember/test-waiters";
export default class FooComponent extends Component {
@tracked output = "nothing";
@task
@waitFor
*badButtonWithWaitFor() {
this.output = "bad";
yield new Promise((resolve) => {
setTimeout(resolve, 1000);
});
this.output = "good";
}
}
Conclusion
And since preventing people from stepping on a rake is a good idea, this plugin is trying to make sure that folks will have a good time.
Functionality
decorator-presence
Linter with auto-fix that makes sure that every @task
has @waitFor
after it:
// bad
class Foo {
@task *example() {}
}
// good
import { waitFor } from "@ember/test-waiters";
class Foo {
@task @waitFor *example() {}
}
decorator-order
Linter with auto-fix that makes sure as per ember-test-waiters documentation that we have certain order of the decorators:
waitFor acts as a wrapper for the generator function, producing another generator function that is registered with the test waiter system, suitable for wrapping in an ember-concurrency task. So @waitFor/waitFor() needs to be applied directly to the generator function, and @task/task() applied to the result.
//bad
class Foo {
@waitFor @task *example() {}
}
// good
class Foo {
@task @waitFor *example() {}
}
Contributing
Prerequisities
Setup
pnpm install
Testing
Automated tests
pnpm test
Running locally in a project
- Clone this repo next to your project
- Add following line to your project
package.json
:
"eslint-plugin-task-needs-wait-for": "file:../eslint-plugin-task-needs-wait-for",
- Add following line to your project
.eslintrc.js
:
plugins: ['task-needs-wait-for'],
extends: {
'plugin:task-needs-wait-for/recommended',
}
- Run in a console:
pnpm build --watch
- Then inside your project you can run following to test:
npx eslint .