-
-
Notifications
You must be signed in to change notification settings - Fork 6.6k
[Proposal] Watch plugins API #5399
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
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) |
SimenB
left a comment
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 joining 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 awaits in an async function)
| }); | ||
| 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
| .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?
| ); | ||
| 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(...)toJestHooksbut 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 aupdateConfigAndRunfunction 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:
registerHooksThis 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
testRunCompleteandshouldRunTestSuite. 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,TestPathWatchPluginor even 'trivial' stuff likeQuitPlugin.watch.js.Pending items
Which files should I focus on to better understand the proposal?
jest-cli/src/pluginsdirectory, speciallyplugins/test_path_pattern.jsandplugins/update_snapthots_interactive.jsjest-cli/src/watch.jsPlugin 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 aupdateConfigAndRunfunction 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:
registerHooksNOTE: 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,testFailby leveragingwebpack/tapable. This will allow us to have well defined, extension points.shouldRunTest(...): booleanorshouldRunTestSuite(...): boolean.Improvements & Questions
tap,tapPromise, etc. Also to prevent from calling hooks.