Skip to content

Commit

Permalink
Add runtime assertion that async fn transpiled (#476)
Browse files Browse the repository at this point in the history
  • Loading branch information
machty authored Aug 24, 2022
1 parent c3ec012 commit 3671a64
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ jobs:
timeout-minutes: 15

strategy:
fail-fast: true
fail-fast: false
matrix:
ember-try-scenario:
- ember-lts-3.8
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

### master
- Add runtime assertion to detect transpilation failures with the new async arrow fn API

### 2.3.0
- Introduce async-arrow task() API as the new universal JS/TS API, e.g.
`myTask = task(this, { drop: true }, async (arg: string) => {})`. This new API
Expand Down
10 changes: 10 additions & 0 deletions addon/-private/task-public-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
task as taskDecorator,
taskGroup as taskGroupDecorator,
} from './task-decorators';
import { assert } from '@ember/debug';

/**
* TODO: update docs to reflect both old and new ES6 styles
Expand Down Expand Up @@ -58,6 +59,11 @@ import {
* @returns {TaskProperty}
*/
export function task(taskFnOrProtoOrDecoratorOptions, key, descriptor) {
assert(
`It appears you're attempting to use the new task(this, async () => { ... }) syntax, but the async arrow task function you've provided is not being properly compiled by Babel.\n\nPossible causes / remedies:\n\n1. You must pass the async function expression directly to the task() function (it is not currently supported to pass in a variable containing the async arrow fn, or any other kind of indirection)\n2. If this code is in an addon, please ensure the addon specificies ember-concurrency "2.3.0" or higher in "dependencies" (not "devDependencies")\n3. Ensure that there is only one version of ember-concurrency v2.3.0+ being used in your project (including nested dependencies) and consider using npm/yarn/pnpm resolutions to enforce a single version is used`,
!isUntranspiledAsyncFn(arguments[arguments.length - 1])
);

if (
isDecoratorOptions(taskFnOrProtoOrDecoratorOptions) ||
(key && descriptor)
Expand All @@ -68,6 +74,10 @@ export function task(taskFnOrProtoOrDecoratorOptions, key, descriptor) {
}
}

function isUntranspiledAsyncFn(obj) {
return obj && obj.constructor && obj.constructor.name === 'AsyncFunction';
}

/**
* Build and return a "classic" TaskProperty, which is essentially a subclass of a Computed Property
* descriptor that can be used to define Tasks on classic Ember.Objects.
Expand Down
24 changes: 2 additions & 22 deletions tests/acceptance/helpers-test.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,8 @@
import { click, visit, currentURL } from '@ember/test-helpers';
import Ember from 'ember';
import { setupApplicationTest } from 'ember-qunit';
import { module, skip, test } from 'qunit';
import {
macroCondition,
dependencySatisfies,
importSync,
} from '@embroider/macros';

function getDebugFunction(type) {
if (macroCondition(dependencySatisfies('ember-source', '^3.26.0'))) {
return importSync('@ember/debug').getDebugFunction(type);
} else {
return Ember[type];
}
}

function setDebugFunction(type, fn) {
if (macroCondition(dependencySatisfies('ember-source', '^3.26.0'))) {
return importSync('@ember/debug').setDebugFunction(type, fn);
} else {
Ember[type] = fn;
}
}
import { macroCondition, dependencySatisfies } from '@embroider/macros';
import { getDebugFunction, setDebugFunction } from '../helpers/helpers';

const originalAssert = getDebugFunction('assert');

Expand Down
21 changes: 21 additions & 0 deletions tests/helpers/helpers.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
import { skip, test } from 'qunit';
import { gte } from 'ember-compatibility-helpers';
import Ember from 'ember';
import {
importSync,
macroCondition,
dependencySatisfies,
} from '@embroider/macros';

export const decoratorTest = gte('3.10.0') ? test : skip;

export function makeAsyncError(hooks) {
hooks.afterEach(() => (Ember.onerror = null));
return () => new window.Promise((r) => (Ember.onerror = r));
}

export function getDebugFunction(type) {
if (macroCondition(dependencySatisfies('ember-source', '>3.26.0'))) {
return importSync('@ember/debug').getDebugFunction(type);
} else {
return Ember[type];
}
}

export function setDebugFunction(type, fn) {
if (macroCondition(dependencySatisfies('ember-source', '>3.26.0'))) {
return importSync('@ember/debug').setDebugFunction(type, fn);
} else {
Ember[type] = fn;
}
}
29 changes: 29 additions & 0 deletions tests/integration/async-arrow-task-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
enqueueTask,
} from 'ember-concurrency';
import Component from '@glimmer/component';
import { getDebugFunction, setDebugFunction } from '../helpers/helpers';

function defer() {
let resolve, reject;
Expand All @@ -24,6 +25,8 @@ function defer() {
return { promise, resolve, reject };
}

const originalAssert = getDebugFunction('assert');

class TestComponent extends Component {
resolved = null;

Expand Down Expand Up @@ -89,6 +92,10 @@ module('Integration | async-arrow-task', function (hooks) {
);
});

hooks.afterEach(function () {
setDebugFunction('assert', originalAssert);
});

test('two args - task(this, async () => {})', async function (assert) {
assert.expect(13);

Expand Down Expand Up @@ -167,4 +174,26 @@ module('Integration | async-arrow-task', function (hooks) {

await finishTest(assert);
});

test('runtime assertion to detect improper task() use or transpilation errors', async function (assert) {
assert.expect(2);

let assertionDidFire = false;
setDebugFunction('assert', function (msg, test) {
if (!test) {
// eslint-disable-next-line qunit/no-conditional-assertions
assert.ok(
msg.includes(
"the async arrow task function you've provided is not being properly compiled by Babel"
),
'expected assertion message'
);
assertionDidFire = true;
}
});

const asyncArrowFn = async () => {};
task(this, asyncArrowFn);
assert.ok(assertionDidFire);
});
});

0 comments on commit 3671a64

Please sign in to comment.