-
-
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
[Proposal] Watch plugins API #5399
Conversation
packages/jest-cli/src/watch.js
Outdated
|
||
let hasExitListener = false; | ||
|
||
const internalPlugins = [ |
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.
This will get cleaned up
packages/jest-cli/src/watch.js
Outdated
}; | ||
|
||
const watchPlugins: Array<WatchPlugin> = internalPlugins | ||
.map(pluginPath => require(pluginPath).default) |
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.
All of this will get cleaned up
packages/jest-cli/src/watch.js
Outdated
.map(p => p.getUsageRow(globalConfig, hasSnapshotFailure)) | ||
.map(p => (p ? p.key : undefined)) | ||
.filter(p => p !== undefined) | ||
.sort((a, b) => a - b) |
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.
Clean up
Woah, this is extremely exciting, I didn't know your changes were going to be so extensive. This really makes Jest's watch mode a general purpose system that we should extract into I think the fact that you are able to support all existing features of watch mode with plugins now is a good sign that this API design solves our needs well. I would recommend going with that, polishing it up, and shipping it before we consider further changes. This PR unlocks a new set of capabilities that we haven't thought of yet so I wanna be cautious that we don't waste too much time bikeshedding on the API. We can always change it later. The only concern I have is around |
@cpojer hehe awesome, I'll start by polishing it. I like the power that tapable provides, but I agree that it might be too early to justify it. |
This has conflicts, and failing appveyor CI (I think that one just needs a restart, 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.
Mostly just questions, this is looking really awesome!
}); | ||
|
||
it('prevents Jest from handling keys when active and returns control when end is called', () => { | ||
// TODO: Fix or remove before merging | ||
xit('prevents Jest from handling keys when active and returns control when end is called', () => { |
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.
seems like something we'd want, isn't it?
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.
Correct, this is one of the pending items that I have at the top.
['t', 'e', 's', 't'] | ||
await nextTick(); | ||
|
||
[('t', 'e', 's', 't')] |
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.
why the parens? this makes it just return the last one, and we get an array of ['t']
(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comma_Operator)
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 have no idea how that happened
? chalk.dim('test name ') + chalk.yellow('/' + testNamePattern + '/') | ||
: null, | ||
] | ||
.filter(f => !!f) |
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.
.filter(Boolean)
? Or just remove the double bang, not really needed (as you check against null
)
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.
Sorry, I just moved that from watch.js, changing it now
.filter(f => !!f) | ||
.join(', '); | ||
|
||
const messages = ['\n' + chalk.bold('Active Filters: ') + filters]; |
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.
Why in an array? Did you mean to concat
with filters
instead of join
ing on line 27?
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 this doesn't seem to be needed, same as above just moved to from watch.js
showPrompt(): Promise<void> { | ||
this._stdout.write('\n'); | ||
process.exit(0); | ||
return Promise.resolve(); |
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 just mark the function as async
and drop the return
, although the transpiled code is pretty ugly. Up to to you 🙂
(would be cool if babel just wrapped the return value in Promise.resolve
if there were no await
s in an async function
)
testNamePattern: '', | ||
testPathPattern: '', | ||
}); | ||
startRun(globalConfig); | ||
break; | ||
case KEYS.F: |
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.
should this be a plugin as well?
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.
Correct, this one should be a plugin... I can do it in this PR of as follow up PRs
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 also want to wait for it because it will require some refactor
packages/jest-cli/src/watch.js
Outdated
.map(p => p.getUsageRow(globalConfig, hasSnapshotFailure)) | ||
.filter(usage => usage && !usage.hide) | ||
// $FlowFixMe a and b will not be null or undefined | ||
.sort((a, b) => a.key - b.key); |
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 like q
being at the bottom, no biggie, though 🙂
packages/jest-cli/src/watch.js
Outdated
|
||
const thirdPartyPlugins = watchPlugins | ||
.slice(INTERNAL_PLUGINS.length) | ||
.map(p => p.getUsageRow(globalConfig, hasSnapshotFailure)) |
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.
hasSnapshotFailure
seems really specific, IDK if there's a better place to stick it, 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.
testResults
array would maybe make more sense, possible with some properties like hasSnapshotFailure: boolean
on it for ease of use
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.
Agree!
This is something that I was thinking over the weekend, I'm glad that you felt the same way!
My thoughts are that getUsageRow()
should only take in the globalFilter
.
Stuff like hasSnapshotFailure
(which as you mentioned only a small chunk of plugins will use) can be accessed from thetestRunComplete
hook.
hooks.testRunComplete(results => {
this._hasSnapshotFailure = !!results.snapshot.failure;
});
Unrelated comment
Which makes me think that we could take this approach even further and not even pass globalConfig
as a param of getUsageRow
, but instead if the plugin needs access to the most up to date globalConfig
it can get it from a globalConfigChanged
hook
hooks.globalConfigChanged(globalConfig => {
this._globalConfig = globalConfig;
});
What are your thoughts on this?
onCancelPatternPrompt, | ||
{header: activeFilters(globalConfig)}, | ||
); | ||
break; | ||
case KEYS.QUESTION_MARK: | ||
break; |
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.
not for this PR, but should this break
be here? Seems to make sense for w
and ?
to behave the same here.
packages/jest-cli/src/watch.js
Outdated
plugin => | ||
chalk.dim(' \u203A Press') + | ||
' ' + | ||
// $FlowFixMe plugin will be present |
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't we fix the type definition so the ignore isn't needed?
I'm still worried about how hard it is going to be to communicate the current API for watch plugins.
|
Codecov Report
@@ Coverage Diff @@
## master #5399 +/- ##
==========================================
+ Coverage 62.23% 62.24% +<.01%
==========================================
Files 205 212 +7
Lines 6932 6955 +23
Branches 4 3 -1
==========================================
+ Hits 4314 4329 +15
- Misses 2617 2625 +8
Partials 1 1
Continue to review full report at Codecov.
|
Awesome, I hit the merge button but I do think there are some follow-ups, like you mentioned yourself.
What's your concrete next step? I think the current API looks good as a start and we can bring back the typeahead plugin with this. Are you planning on making this happen? :) |
Could consider: WatchPlugin {
apply: (hooks: JestHookSubscriber) => void,
run: (globalConfig: GlobalConfig, updateConfigAndRun: Function) => Promise<*>,
onKey: (value: string) => void,
getPrompt: (globalConfig: GlobalConfig, hasFailedSnapshots: boolean) => ?{
key: number,
prompt: string,
},
}
I took |
@cpojer I just created #5478 to keep track of that. @rickhanlonii I like that idea! What do you think @cpojer? |
I'm on board with that. Do you wanna change it? |
|
||
const messages = ['\n' + chalk.bold('Active Filters: ') + filters]; | ||
|
||
return messages.filter(message => !!message).join(delimiter); |
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.
this is still weird - messages
will always be an array of size 1
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. |
This PR adds watch plugins to Jest... Feedback is more than welcome!
Pending Items
shouldRunTestSuite(...)
toJestHooks
but I still need to implement it.NOTE: once
shouldRunTestSuite(...)
it would be interesting if we can reimplement the "failed tests mode" as a plugin that hooks intoonTestComplete(...)
for keeping track of the results andshouldRunTestSuite(...)
to prevent running test suites that didn't failOpen questions
Plugin API
Feedback on the API is more than welcome.
First part: Watch mode usage interactions.
showPrompt(...)
: This gives control to the plugin to manage test execution. It provides aupdateConfigAndRun
function and returns a promise that can be resolved when the control needs to be given back to Jest.onData(...)
: A helper function for capturing keystrokes when the plugin is active.getUsageRow(...)
: Returns the usage row information, and allows for dynamically updating the text, hiding, etc. Example:{ key: S, prompt: 'filter by @tags' }
Second part:
registerHooks
This is how WatchPlugins will hook into different parts of the Jest.
How this could be expanded to support different plugin needs?
Right now the only hook that I created is
testRunComplete
andshouldRunTestSuite
. If needed we could have hooks likeonTestSuiteStart
,onTestSuiteComplete
, etc. This will allow us to have well defined, extension points.Hooks with a return value can also be supported, for example
shouldRunTestSuite(...): Promise<boolean>
.Click here to see original proposal
[WIP] Plugin Proposal
cc: @cpojer, @orta , @thymikee
It is still in a super rough spot, but I think this architecture might allow us to have a simple yet powerful API.
Context and current state of proposal
This PR is still dirty and WIP.
UpdateSnapshotsInteractivePlugin
,TestPathWatchPlugin
or even 'trivial' stuff likeQuitPlugin
.watch.js
.Pending items
Which files should I focus on to better understand the proposal?
jest-cli/src/plugins
directory, speciallyplugins/test_path_pattern.js
andplugins/update_snapthots_interactive.js
jest-cli/src/watch.js
Plugin API
Currently, there are two parts of the plugin system: which maybe could benefit from better names.
First part: Watch mode usage interactions.
showPrompt(...)
: This gives control to the plugin to manage test execution. It provides aupdateConfigAndRun
function and returns a promise that can be resolved when the control needs to be given back to Jest.onData(...)
: A helper function for capturing keystrokes when the plugin is active.getUsageRow(...)
: Returns the usage row information, and allows for dynamically updating the text, hiding, etc. Example:{ key: S, prompt: 'filter by @tags' }
Second part:
registerHooks
NOTE: This is my first time using
webpack/tapable
, so let me know if I'm thinking it at the completely wrong level.This is how WatchPlugins will hook into different parts of the Jest. It is powered by webpack/tapable which is what Webpack uses for their plugin system. This is inspired by how Webpack plugins work.
How this could be expanded to support different plugin needs?
testRunComplete
.f needed we could have hooks liketestSuiteStart
,testSuiteComplete
,testPass
,testFail
by leveragingwebpack/tapable
. This will allow us to have well defined, extension points.shouldRunTest(...): boolean
orshouldRunTestSuite(...): boolean
.Improvements & Questions
tap
,tapPromise
, etc. Also to prevent from calling hooks.