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

Deprecate QUnit.load #1084

Closed
3 tasks done
leobalter opened this issue Dec 14, 2016 · 9 comments · Fixed by #1743
Closed
3 tasks done

Deprecate QUnit.load #1084

leobalter opened this issue Dec 14, 2016 · 9 comments · Fixed by #1743
Assignees
Labels
Category: Docs Component: Core For module, test, hooks, and reporters. Type: Enhancement New idea or feature request.
Milestone

Comments

@leobalter
Copy link
Member

leobalter commented Dec 14, 2016

Tip

Migration guide has been published at: https://api.qunitjs.com/QUnit/load/


This is not even documented, but it's used everywhere.

We need to make it an unnecessary call and then warn if it's called.

Subtasks

  • Understand the practical use cases for this undocumented method.
  • Create a documentation page for QUnit.load and add examples of how to accomplish these use cases instead.
  • In QUnit 2.x, deprecate the method with a warning and link to these examples.
@trentmwillis
Copy link
Member

I agree with this in general, because I get tripped up on load and start all the time, but it does open up one question: what happens with QUnit.config.autostart? Will that be revamped to just call QUnit.start?

@leobalter
Copy link
Member Author

We need to discuss this, but I believe calling start() on every run is not a bad idea compared to the magic of autostarting in a browser. The responsibility to catch the document.ready or the window.load events are not for a test runner.

@trentmwillis
Copy link
Member

FWIW, the reporter is actually what currently handles the autostart on load behavior. We could just update this to call QUnit.start instead. I think that makes for a pretty clear path forward.

@leobalter
Copy link
Member Author

I thought about this, but this might break suites using config.autostart and user defined start

@leobalter
Copy link
Member Author

I guess we have this fixed. Checking.

@trentmwillis
Copy link
Member

I've begun working on this. I'd like to deprecate it so that we can remove it in 3.0.

However, the interplay between QUnit.config.autostart, QUnit.load(), and QUnit.start() is difficult to unwind. There's a much simpler end-state, but getting there in a way that we don't break existing setups is going to be tricky.

@Krinkle Krinkle added Component: Core For module, test, hooks, and reporters. and removed help welcome labels Jun 20, 2020
@Krinkle Krinkle added this to the 3.0 milestone Sep 15, 2020
@Krinkle Krinkle modified the milestones: 3.0 release, 2.x release Nov 7, 2020
@Krinkle Krinkle mentioned this issue Nov 7, 2020
14 tasks
smcclure15 added a commit to smcclure15/qunit that referenced this issue Nov 9, 2020
Fixes qunitjs#1084
Modularize and export load implementation
The load impl remains as-is
The QUnit.load entry point issues deprecation warning
@smcclure15
Copy link
Member

I took a pass at untangling the start/load logic:

master...smcclure15:untangle-load-logic

Most of that endeavor was for my own understanding, getting my head around what the valid sequences were among these APIs. I hope by sharing that it can benefit others as well.

It's a dense change in the end, but I tried to get each of the 3 commits fairly clean:

1, it flattens some of the if/else logic in start. Fewer nested checks.
2, I got rid of some redundant state given prior validations up to that point.
3, I created a state machine to modularize responsibility. Maybe the "beauty is in the eye of the beholder" for this design pattern, but I prefer short, encapsulated implementations over chains of if/else's and re-entry checks

I believe everything is still intact - all tests pass and I bashed it quite it bit. I do worry about edge cases in this, and wouldn't PR it until I hit it really hard.

I think what this cleaner (I hope!) logic is telling me is that our recommendations for what to do when QUnit.load is deprecated is for users to use QUnit.start exactly once OR set config.autostart = true. I think that could be it.
The load mechanism is very forgiving, as it's kind of a backdoor to "start if you haven't started yet", which is boarded up nicely with QUnit.start already.

This is all independent of #1502, which is just a matter of issuing a warning and making the reporter use a non-warning version. Once the QUnit.load frontdoor is gone, then it's just implementation detail for us to refactor (with would-be incompatibilities).

Open to your thoughts and discussion

Krinkle pushed a commit that referenced this issue Dec 26, 2020
Flattening logic and pushing local vars down in scope.

Incremental cleanup towards deprecating `QUnit.load`.

Closes #1526.
Ref #1084.
@Krinkle
Copy link
Member

Krinkle commented Jan 2, 2021

@smcclure15 Thanks for that analysis. I agree with the objective and conclusion, one either uses the default autostart, or call start exactly once.

My worry is that abiding by that may be non-trivial for end-users to figure out within their current systems. I'd like us to cover a good chunk of what QUnit.load is used for today and document here (and later in the QUnit 3 migration guide) in an easy-to-digest form what we prescribe going forward for some of those higher-level stories.

It's been a while I looked at this, so maybe its obvious and mostly covered here already. Just wanted to clarify that goal.

@smcclure15
Copy link
Member

smcclure15 commented Jan 13, 2021

The following analysis starts with a trivial passing test:

QUnit.test( "example", assert => {
    assert.true( true );
} );

and adds various combinations of calls to QUnit.load, QUnit.start, and QUnit.config.autostart to study behavior.

Node environment with QUnit CLI

Synchronous run execution

Adding QUnit.config.autostart = false; does not impact the run at all; it still passes:

QUnit.config.autostart = false;
QUnit.test( "example", assert => {
    assert.true( true );
} );

Adding one (or more!) QUnit.load calls (before or after!) doesn't change anything either (still passes):

QUnit.config.autostart = false;
QUnit.load();
QUnit.load();
QUnit.load();
QUnit.test( "example", assert => {
    assert.true( true );
} );
QUnit.load();
QUnit.load();
QUnit.load();

But using QUnit.load with_out_ the autostart = false will error:

// QUnit.config.autostart = false;
QUnit.load();
QUnit.test( "example", assert => {
    assert.true( true );
} );
% qunit demo.js
TAP version 13
ok 1 example
not ok 2 global failure
  ---
  message: "Called start() while test already started running"

Similarly, using any QUnit.start calls will give this same error:

QUnit.test( "example", assert => {
    assert.true( true );
} );
QUnit.start();
% qunit demo.js
TAP version 13
ok 1 example
not ok 2 global failure
  ---
  message: "Called start() while test already started running"

TAKEAWAY: Don't use autostart = false, QUnit.load, or QUnit.start. They were either ignored or already erroring.

Asynchronous run execution

This actually fails with both QUnit.load and QUnit.start:

QUnit.config.autostart = false;
setTimeout(() => {
    QUnit.test( "example", assert => {
        assert.true( true );
    });
    QUnit.start();
}, 100)
% qunit demo.js
TAP version 13
not ok 1 global failure
  ---
  message: "No tests were run."
  severity: failed
  actual  : {}
  expected: undefined
  stack: Error: No tests were run.

and has a trailing exception from within the framework 100ms later:

/<SNIP>/dist/qunit.js:2312
          if (module.hooks[handler].length) {
                                    ^

TypeError: Cannot read property 'length' of undefined

This has no impact on deprecating QUnit.load, and this might be a separable issue to fix (re: autostart and start).

Writing your own CLI with js-reporters

Users can leverage the js-reporters project and build their own CLI runner, via something like the following:

const JsReporters = require("js-reporters");
const runner = JsReporters.autoRegister();
JsReporters.TapReporter.init(runner);

// load tests
require(test);

// kick off test execution
QUnit.load();

If the test file that is required is the "trivial" test case, replacing QUnit.load with QUnit.start is a straightforward (and forward-compatible) fix.

If the test that is required uses QUnit.start or QUnit.load within its content, the "outer" QUnit.load in the above runner wouldn't be impacted, since subsequent QUnit.load calls don't do any harm.
When converting the "outer" call to QUnit.start, this makes the system more strict, and errors if the test uses either QUnit.start or QUnit.load within it.
The fix is to remove any QUnit.load or QUnit.start calls within the test content, and leave that run-execution management to the "runner" layer to make just one call to QUnit.start.

Browser environment

Synchronous run execution

Using one or more QUnit.load calls doesn't change any behavior, and can be removed:

QUnit.load();
QUnit.load();
QUnit.load();
QUnit.test( "example", assert => {
    assert.true( true );
});
QUnit.load();
QUnit.load();
QUnit.load();

Asynchronous run execution

Unlike the Node example that failed with no tests run, the following successfully passes:

QUnit.config.autostart = false;
setTimeout(() => {
    QUnit.test( "example", assert => {
        assert.true( true );
    });
    QUnit.start();
}, 100)

Here, the QUnit.start call is (and has been) required to begin running.

Krinkle added a commit that referenced this issue Mar 26, 2022
This is an ancient left-over from when the async test API consisted
of `QUnit.stop` and `QUnit.start` (as opposed to `assert.async()` and
the built-in test promise await), where `QUnit.start` was also at the
same time responsible for the initial starting of executing the test
run for cases where autostart=false (aka "begin").

Part of this legacy remains at #1084.

== Rollup, before

> (!) Circular dependencies
> src/core.js -> src/test.js -> src/core.js
> src/test.js -> src/core/processing-queue.js -> src/test.js

== Rollup, after

> (!) Circular dependency
> src/test.js -> src/core/processing-queue.js -> src/test.js
Krinkle added a commit to Krinkle/qunit that referenced this issue Oct 16, 2023
Krinkle added a commit to Krinkle/qunit that referenced this issue Mar 2, 2024
@Krinkle Krinkle self-assigned this Mar 2, 2024
Krinkle added a commit that referenced this issue Mar 2, 2024
Krinkle added a commit that referenced this issue May 31, 2024
* Move scheduleBegin() code into QUnit.start().
* Remove internal config.pageLoaded, unused.
* Remove redundant `config.blocking` assignment, already set by begin().

== Reasoning for DOM-ready delay in QUnit.start ==

After the above changes, /test/startError.html failed when run
via grunt-contrib-qunit (e.g. CI) due to a Chrome timeout with no
test results. It passed in a browser. I realised that this is because
the bridge is injected after QUnit.start() and our tiny test were
already finishesd.

I believe this would affect even simple cases where someone sets
autostart=false and correctly calls QUnit.start(). This worked before
because legacy QUnit.load/QUnit.autostart was implicitly delaying the
begin() call even if you don't use autostart or call load+start both.
The reason is the `config.pageLoaded`. If the user disables autostart,
it is common for them to use async code loading (e.g. AMD) and thus
start manually after DOM-ready. But, it is entirely valid for them
to be ready before DOM-ready in some cases. In that case, our legacy
logic was still kicking in by re-enabling autostart and then waiting
for the original html.js event handler for window.onload to call
QUnit.start() before we "really" begin.

I don't remember if that was a lazy way to resolve conflicts between
the two, or a deliberate feature. We deprecated the conflict handling
part of this in QUnit 2 and this patch removes that. But, it seems
this was also serving as a feature to always ensure we wait for
DOM-ready no matter what, which is actually very useful to making sure
that integrations and reporter plugins have a chance to listen for
"runStart".

Solution: If you call QUnit.start(), and there is a document, and
we've not reached `document.readyStart=complete` (i.e. you called it
manually, most likely with autostart=false), then we will now still
explicitly wait for window.onload before calling begin().

For all intents and purposes, this delay is now invisible and internal
to QUnit. We do consider QUnit to have "started" during this delay.
We were already using setTimeout previously between `start` and `begin`
and this is effectively and extension of that.

Ref #1084
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Category: Docs Component: Core For module, test, hooks, and reporters. Type: Enhancement New idea or feature request.
4 participants