-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
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
pass testConsole to TestEnvironment #5227
Conversation
Codecov Report
@@ Coverage Diff @@
## master #5227 +/- ##
=======================================
Coverage 61.18% 61.18%
=======================================
Files 202 202
Lines 6765 6765
Branches 3 3
=======================================
Hits 4139 4139
Misses 2625 2625
Partials 1 1
Continue to review full report at Codecov.
|
packages/jest-runner/src/run_test.js
Outdated
@@ -98,6 +93,13 @@ async function runTestInternal( | |||
testConsole = new BufferedConsole(); | |||
} | |||
|
|||
const environment = new TestEnvironment( | |||
Object.assign({}, config, {console: testConsole}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you split this up into separate options and pass two arguments instead? Let's not add the console to the main config object of Jest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't we add an environmentOptions recently? Can we reuse that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought you might ask that (proof: watch the video on my twitch, I live streamed myself making this pr 😄). But decided against it because I thought that an other argument might be less desirable considering the TestEnvironment can be many things, not just the jsdom one. However, if there's an environmentOptions
thing already, I'm assuming that would be an object as a second argument. If so, I don't think that's currently part of the type signature of a test environment constructor, but I can definitely add it and it makes more sense to me. Just let me know what you'd prefer :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's the PR: #5003
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's still on the config though, so I guess @cpojer's original objection still stands
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooooh, yeah, that's how my workaround works. I'm fine to do it either way. Though I think an extra argument might be better because using testEnvironmentOptions
to add a virtualConsole
would only make sense if the test environment is the jsdom one...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot add this as a config option.
const leakDetector = config.detectLeaks | ||
? new LeakDetector(environment) | ||
: null; | ||
|
||
const cacheFS = {[path]: testSource}; | ||
setGlobal(environment.global, 'console', testConsole); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you saying that this function doesn't work anymore in the latest version of jsdom? Can we just fix that instead (using Object.defineProperty?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer using the offered API rather than trying to patch it. The way it's done here is a bit ad hoc though. Saying that all custom envs must attach console themselves is pretty breaking, so not sure about best way forward
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is that VirtualConsole
gets called with sendTo
and is passed the console
object and holds a reference to that (calling it anyConsole
) rather than referencing global.console
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand how either of your comments relate to my question about the setGlobal
call.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mine is: "JSDOM has an API for setting the console, we should use it rather than just assigning to the global".
Why it stopped working I don't know, though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SimenB but then it would only work for jsdom and every other environment will have to do something different.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll provide a link when I get to my computer. But I hoped my comment above would explain why the current solution wouldn't ever work...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, it's not a regression. It just never appeared before now. It happened now because of the changes in React. Look closer at my original issue and I explain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When JSDOM is initialized, if we don't provide our own implementation of VirtualConsole, then it uses one which references the global console
:
https://github.com/tmpvar/jsdom/blob/8a6894c34f14b9131d0fe4acadb71dae4f49daca/lib/api.js#L291-L293
This is all happening outside the test environment, so using setGlobal
wouldn't work anyway. On top of that, the sendTo
method called accepts an argument called anyConsole
. It uses anyConsole.error
instead of console.error
. That's here in the VirtualConsole code:
This is why we need to initialize JSDOM with our own instance of VirtualConsole
. One which uses the testConsole
rather than the global console
.
I hope that makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Anyway, I'm fine adding this as an additional parameter to the TestEnvironment constructor, but because of how JSDOM constructs a VirtualConsole with a reference to console
rather than using the global console, we do have to provide our own that references the right console.
What would you prefer?
Thanks for submitting a fix. Can you check out the integration tests folder and add a test there to confirm that this is working properly again? |
Why not use https://github.com/tmpvar/jsdom/blob/master/README.md#virtual-consoles? EDIT: which is what this PR is doing, that's what I get for just reading the comments and not the diff |
I'll be able to make changes in a few hours 👍 Thanks for the quick feedback! |
I think we should strive to make it work for people doing class MyEnv extends JestTestEnvironment {
constructor(config) {
super(config);
}
} And not just those doing class MyEnv extends JestTestEnvironment {
constructor(...args) {
super(...args);
}
} And maybe try to make |
a901f36
to
ab1bdd4
Compare
Alrighty, I just pushed up an integration test. Hopefully that will help clear up any confusion. If you try to add this integration test to |
If you've not had a chance to run the example repo, here's a build of the downshift project where it shows the problem. Some of our tests assert that validation errors are thrown during |
Getting this merged would probably fix the logging I found when testing #4400 (comment) as well. |
Yes, that stack trace is familiar 😉
I think this will fix that. It wont hide it mind you, it'll just allow you to mock the implementation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this makes sense as the only way to pass through a console without making a breaking change to the api of testEnvironments.
I think we should look into how we can pass testEnvironment config in general, separately from this PR.
Note: if we do want to hide the logs, then we can do this: new VirtualConsole().sendTo(config.console || console, {
// this config will prevent the JSDOM errors from being logged
omitJSDOMErrors: true,
}), But I don't think that's what we want. |
I agree with @SimenB. In addition, the |
I think we might want to, as we listen to error ourselves. You can see the filtered stack below. Maybe we only get that when there are emitted errors instead of all the time? It would be bad if we hid errors. Anyways, this will make it part of jest's logging, so you can do |
One problem I see with not hiding it is that now instead of console being called once like it will in the normal app/browser (by react) it'll be called twice, once by jsdom and then by the browser. So you're right, it might be a good idea to set |
Actually, on that note, we could make that the only change and not worry about forwarding on the test console to JSDOM. I'm not sure what situation would cause the JSDOM console to log though. |
I think our Our code for it (from #4669 and #4767): If we do pass |
types/Config.js
Outdated
@@ -210,6 +210,7 @@ export type ProjectConfig = {| | |||
cache: boolean, | |||
cacheDirectory: Path, | |||
clearMocks: boolean, | |||
console?: Object, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you just do console?: console
? Flow has it typed out: https://github.com/facebook/flow/blob/e2325d0e216e0cc14d17f00fc96624b364a27902/lib/core.js#L812-L835
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm... When I try that I get:
object type. Ineligible value used in/as type annotation (did you forget 'typeof'?)
I'm still pretty new to flow so maybe I'm doing something wrong... I did:
console?: console,
Is that wrong?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm really bad at flow myself :P Try the suggestion of console?: typeof console
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha, that gives me a crazy amount of errors. Most of them look unrelated, but some are:
Error: packages/jest-runner/src/run_test.js:97
97: Object.assign({}, config, {console: testConsole}),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ object literal. This type is incompatible with the expected param type of
16: constructor(config: ProjectConfig): void;
^^^^^^^^^^^^^ object type. See: types/Environment.js:16
Property `console` is incompatible:
97: Object.assign({}, config, {console: testConsole}),
^^^^^^^^^^^ BufferedConsole. This type is incompatible with
213: console?: typeof console,
^^^^^^^^^^^^^^ object type. See: types/Config.js:213
Property `dirxml` is incompatible:
213: console?: typeof console,
^^^^^^^^^^^^^^ property `dirxml`. Property not found in. See: types/Config.js:213
97: Object.assign({}, config, {console: testConsole}),
^^^^^^^^^^^ BufferedConsole
🤷♂️
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh, yeah that's a bit too strict I guess. Thanks for checking!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yap, whether adding this property when it's passed over a worker or not doesn't really change the purpose of the type though. Another solution would be to use a union type but I definitely would prefer adding the console prop as an argument to the TestEnvironment constructor. Since we still have the setGlobal
call, this should be backward compatible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. Do you think I should make it an object that has a console
property? That would provide the flexibility to adding more properties if other test environments need something like this in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
anybody extending JSDOMEnvironment would then not get this improvement for free. Might be edge-casey enough to ignore, I guess.
Could add a // $FlowIgnoreMe
:D
EDIT: An object makes sense, so we can add other stuff later
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I think passing the "config" object together with an "options" object is my preferred solution here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I'll do that tomorrow then. It's 1:30 AM where I am 😅 Thanks!
fakeNode.dispatchEvent(evt); | ||
window.removeEventListener('error', onError); | ||
|
||
expect(console.error).toHaveBeenCalledTimes(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could do expect(console.error).toMatchSnapshot();
, then you should see in the snapshot that 'this is an error in an event callback'
is passed as well. Not needed, but even more explicit
If it contains the whole stack, don't bother
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it does. It's kinda big :)
window === window.document.defaultView does not have an .on() method, so I don't know what this will do. If you mean addEventListener, no, jsdomErrors are fired for many other cases as well, besides JavaScript errors. E.g. failed CSS parsing or image loading. |
Thanks @domenic Hmmm... Ok, I think that this is probably the best we can do... Actually, my initial concern/annoyance was that when writing a test like this: const React = require('react')
const ReactDOM = require('react-dom')
class ThrowingComp extends React.Component {
render() {
throw new Error('this is an error in react')
}
}
beforeEach(() => {
jest.spyOn(console, 'error')
console.error.mockImplementation(() => {})
})
afterEach(() => {
console.error.mockRestore()
})
test('fails', () => {
expect(() => {
ReactDOM.render(
React.createElement(ThrowingComp),
document.createElement('div')
)
}).toThrow()
// What should "x" be?
expect(console.error).toHaveBeenCalledTimes(x)
}) Initially I assumed that So that said, I'm totally fine with this as it is. The PR accomplishes the initial goal of making the console that JSDOM uses be the same one that our tests have access to in their environment. So we're good 👍 |
ab1bdd4
to
b781c68
Compare
Ok, I've made some changes. I think this is good to go :) |
b781c68
to
d62e9ab
Compare
@@ -24,6 +24,12 @@ | |||
* `[docs]` Add documentation for .toHaveProperty matcher to accept the keyPath | |||
argument as an array of properties/indices. | |||
([#5220](https://github.com/facebook/jest/pull/5220)) | |||
* `[jest-runner]` test environments are now passed a new `options` parameter. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved these to the Features
section because whether this is a bug or a new feature is debatable, so it made more sense as a feature to me.
declare class $JestEnvironment { | ||
constructor(config: ProjectConfig): void; | ||
constructor(config: ProjectConfig, options?: EnvironmentOptions): void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expect in the next major version we can make this a required argument, but thought it best to leave it as an optional argument for now. I default to an empty object in the jsdom environment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! Thanks for being patient with us!
Awesome, thank you so much Kent! |
@@ -22,14 +23,17 @@ class JSDOMEnvironment { | |||
errorEventListener: ?Function; | |||
moduleMocker: ?ModuleMocker; | |||
|
|||
constructor(config: ProjectConfig) { | |||
constructor(config: ProjectConfig, options: EnvironmentOptions = {}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small nit: options?: EnvironmentOptions
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Actually, I'm curious to know why it's necessary to add type information to the constructor. Isn't it already defined in types/Environment.js?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not so sure, but I guess it's for the ease of writing your own custom envs and updating flow-typed
defs, as Jest doesn't expose flow types generated from its source.
But you're right that we should strive for easier type management; this is not ideal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy to make a PR to add the ?
👍
As suggested by @thymikee in jestjs#5227 (comment)
Errors still appearing on the console |
This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Summary
This ensures the console used in jsdom's virtualConsole is the same as the one used in the tests.
Closes #5223
Test plan
I'm not sure of the best way to test this. Help appreciated :) I did make this work by manually changing the code in my
node_modules
of the reproduction repo and things worked as expected :)Thanks!