qunitjs / qunit

🔮 An easy-to-use JavaScript unit testing framework.
https://qunitjs.com
MIT License
4.02k stars 783 forks source link

Core: Error when a module callback has a promise as a return value #1761

Closed raycohen closed 1 month ago

raycohen commented 1 month ago

Ref https://github.com/qunitjs/qunit/issues/1600.

For the QUnit version 3.0 release.

I'd have moved the tests for this into test/main/modules.js but I am not able to use async/await syntax nor promises in that file.

Recreation of #1616

linux-foundation-easycla[bot] commented 1 month ago

CLA Signed

The committers listed above are authorized under a signed CLA.

Krinkle commented 1 month ago

@raycohen I suggest adding it there with a thenable as return instead.

Like so:

QUnit.module('example', function () {
  QUnit.test('test', function (assert) {
    assert.true(true);
  });

  return { then: function () {} };
});

By the way, upon first reading the test, I was hoping you'd make it a failing test, e.g. assert.true(false) on the expectation that the test doesn't actually run since we throw right away. However... despite the seemingly "fast" failure through this uncaught error, the tests would still get defined and run regardless (with any async ones going to an unrelated module in all likelihood). This seems unavoidable without more changes, perhaps best outside the scope of this PR. But it did make me think if there's a way we could detect an AsyncFunction without calling it. It looks like there is?

I haven't yet looked at what, if any, established idiom there is for this, but, if nothing else, one could probably use Object.prototype.toString(fn) === "[object AsyncFunction]" and skip the call in those cases to avoid starting an untracked asynchronous call stack. What do you think about that? The downside would be that we'd only do that for true/native async functions and not those that are compiled to (or written as) regular functions that merely return promises. Given this is error handling, maybe the inconsistency isn't too bad, for the benefit of less noise/other issues being produced.

raycohen commented 1 month ago

I added an additional { then: function () {} } fixture, renamed the other two fixtures to "error" instead of "warning", and added the early bail when the async function can be detected.

I'm not sure what's happening with the linter on the Linux ci builds saying Logger is unused.

Krinkle commented 1 month ago

I'm not sure what's happening with the linter on the Linux ci builds saying Logger is unused.

You may have to rebase this locally first. The main branch has seen a few commits since, leaving the call to Logger.warn() that your patch removes, as the only one remaining. Thus it is unused from the state of the repo that CI sees (which is, simulate a merge of the PR into latest main, then run the CI jobs).

raycohen commented 1 month ago

Given this is error handling, maybe the inconsistency isn't too bad, for the benefit of less noise/other issues being produced.

I agree the Async function deserves special treatment since we can detect it. I'm guessing it's the most common one the most users will try doing as well