npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

eslint-plugin-task-needs-wait-for

v0.1.0

Published

Helps to prevent writing ember-concurrency tasks that would create flaky tests.

Downloads

1

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 of output be good or bad? 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

  1. pnpm

Setup

pnpm install

Testing

Automated tests

pnpm test

Running locally in a project

  1. Clone this repo next to your project
  2. Add following line to your project package.json:
  "eslint-plugin-task-needs-wait-for": "file:../eslint-plugin-task-needs-wait-for",
  1. Add following line to your project .eslintrc.js:
  plugins: ['task-needs-wait-for'],
  extends: {
    'plugin:task-needs-wait-for/recommended',
  }
  1. Run in a console:
pnpm build --watch
  1. Then inside your project you can run following to test:
npx eslint .