-
Notifications
You must be signed in to change notification settings - Fork 779
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
Separate module options from test environment #923
Comments
<3 This is not easy to handle, but I can't bring anything better as well. I'll add this as priority before the next major release (at least a ready PR) |
👍 This approach makes sense to me and if we land other breaking changes to the declarative hash (e.g., #919), then now seems like a good time to introduce this. |
Moving conversation from #1200 here (as it seems directly related to this issue). In #1200 (comment) @trentmwillis mentioned:
I am happy to start working on this, but would like to confirm on specifics before getting started. Here is my take away from this issue and the convo over in #1200 as it relates to nested modules:
Does that path sound reasonable? |
This seems good to me. I'm not sold on the
I'm 👍 for this when using the nested form, but we'll need to keep it for the non-nested form. In the non-nested form we could add an
Seems reasonable. Doing anything on |
Circling back to this. I'lll try to summarise the various links to and from this discussion and what changed in the meanwhile. Current state (as of QUnit 2.10)Every test has an environment object as its This environment is created fresh for each test, and we also copy over any properties from the module object over to it as a start, which can be used for any shared fixtures etc. Example: QUnit.module('example', {
abc: 123,
beforeEach: function () {
this.thing = new Letters(this.abc);
}
});
QUnit.test('foo', function (assert) {
assert.equal(this.abc, 123);
assert.equal(this.thing.constructor, Letters);
}); At this point it's worth noting that properties like The above is identical to the below which uses the nested modules API (#543, QUnit 1.12). QUnit.module('example', (hooks) => {
hooks.beforeEach(function () {
this.abc = 123;
this.thing = new Date(this.abc);
});
QUnit.test('foo', function (assert) {
assert.equal(this.abc, 123);
assert.equal(this.thing.constructor, Date);
});
}); The above doesn't involve any literal module object, and doesn't make use of the fact that hook-named functions are consumed into the hooks and then removed. Note that QUnit.module('example', (hooks) => {
var abc = 123;
hooks.beforeEach(function () {
this.thing = new Date(abc);
});
QUnit.test('foo', function (assert) {
assert.equal(abc, 123);
assert.equal(this.thing.constructor, Date);
});
}); ProblemI suppose the problem statement is that it is isn't obvious that the Proposal AThe original proposal by @gibson042 from 2016 (from the issue descrption):
Proposal BI'd like to expose the possibility of deprecating and removing this behaviour rather than embracing it fully with a new module option. Per the examples in the status quo section, I think the use of such property is trivially replaced by either performing assignments from a Note that QUnit did not yet have a As such, my proposal would be to deprecate this in a 2.x release (e.g. emit warnings for unknown keys in the module options object), and remove in 3.0. |
Am I reading your suggestion to mean that the following, which currently works, would not be valid in QUnit 3.0?
|
@raycohen No, your example would continue to work! The proposal I wrote above would deprecate the ability to mix test hooks and test context in the module // The below might be deprecated in QUnit 2.x and removed in QUnit 3.0
QUnit.module('example', {
abc: 123,
beforeEach: function () {
this.thing = new Date(this.abc);
})
});
QUnit.test('foo', function (assert) {
assert.equal(this.abc, 123);
assert.equal(this.thing.constructor, Date);
}); Your example, already separates the test environment and test hooks, so that's fine! Note that it might be appealing to you to use JavaScript's own scope. This mean you no longer have to use the // Classic syntax
QUnit.module('example', function (hooks) {
var abc = 123;
var thing;
hooks.beforeEach(function () {
thing = new Date(abc);
});
QUnit.test('foo', function (assert) {
assert.equal(abc, 123);
assert.equal(thing.constructor, Date);
});
}); // Modern syntax
module('example', hooks => {
const abc = 123;
let thing;
hooks.beforeEach(() => {
thing = new Date(abc);
});
test('foo', assert => {
assert.equal(abc, 123);
assert.equal(thing.constructor, Date);
});
}); The above is what I would personally recommend, however that's entirely optional. If you prefer to use the test environment "this" object, or are worried about having to change old code, then don't worry! I see no reason to deprecate this feature. This issue is merely about how that feature is mixed up with the module options parameter. I hope that helps! |
Thanks @Krinkle that is reassuring. I have found that lexical scoping can run into issues with nested modules depending on where you reassign, while using the |
@raycohen OK, I wouldn't mind discussing here as well. It sounds like you might have a use case where migrating to lexical scope would break or behave differently. If so, I might reconsider deprecating this. Could you elaborate on this with an example? |
when using nested modules to override variables from a parent module, using lexical scope doesn't always have the desired behavior. Many tests we write have the pattern where a top-level beforeEach does a bunch of shared work (some of it async) and then nested modules use the module context to override feature flags and other inputs to that shared work. Here's a simplified example that I hope illustrates this - the test passes as intended when module context is used to override the top-level value but not when lexical scope is used: |
@raycohen Thanks, that makes more sense. If I understand, the issue here is that the module scope function runs immediately since we first process modules recursively to register the tests, and then execute them in the second pass. This means that, when the first module executes for real, the later function was already executed. The reason the "context" version is working is because QUnit made a copy of the If this assignment was inside a The module function should generally not have any code inside of it, I think, unless used for dynamically generating test() cases. A further modified version where both use the same approach consistently: http://jsfiddle.net/ream736w/1/ Having said that, I think you've convinced me that we should not hard-deprecate and remove for context variables in general. I would personally not recommend it at all, but I see not reason to discontinue supporting it. It's easy to keep working as it works today. If it already works for people, there is no reason to change that. What I do think we should deprecate, is the ability to set variables on |
Ok, I see how One other concern I had with the lexical version is the possibility for "leaks" - i.e. that inserting/deleting/re-ordering nested modules that run before another nested module that does not itself set a lexical variable could impact it. Example: http://jsfiddle.net/v94pze5k/ - granted the developer will see this error at dev time (or via CI) but understanding why a leaky breakage like this happened in a large test file can be a lot of work. As for the |
Related:
testEnvironment
to be accessed within atest
withoutthis
#894 (comment)Note that there are two ways to set before/after hooks (which are currently the only module options): as properties on an options object, and as methods on the argument passed to a callback. Exposing environment procedurally is easy (e.g., a writable
environment
property on the callback argument), but the declarative side makes backcompat tough. I'm not particularly thrilled with the thought of adding a fourth parameter, though... we could instead look for anenvironment
property on the second argument, using it exclusively when present (technically backwards incompatible, but on the same scale as #919) and otherwise generating warnings whenever there's any non-before non-after property. All of which, of course, is assuming that keeping the declarative/procedural duality is valuable, about which I'm not fully persuaded but am inclined to keep for now (in part since the procedural interface is so new).For example, all of these would generate the same environment for their tests:
But the last would issue warnings about deprecated options/environment mixing.
I'm not in love with this, but it's the best I've got. Other suggestions welcome.
The text was updated successfully, but these errors were encountered: