-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Make test file extensions configurable #1746
Make test file extensions configurable #1746
Conversation
Referencing #631 |
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.
Thanks for doing the PR! It's not quite the right approach yet, see #631 (comment) for a fuller explanation.
Gotcha, I'll make the updates and update this PR. Thanks for the filler! |
@novemberborn Looking at the code its a bit above my current knowledge with ava. |
@Jaden-Giordano no worries. I'm happy to give pointers if you want? |
@novemberborn that would be wonderful! I was looking through the code for awhile and I understand how the pipeline is initiated. I'm just having trouble determining which tests should have the precompile ran and which tests shouldnt. Edit: Edit 2: |
2e68b41
to
b54d2c9
Compare
There are currently errors I'm attempting to resolve. I keep getting Edit: There are other issues as you can see in the CI, I'm still trying to figure out why this is happening if you can help maybe figure that out. After this next steps would be the watcher side of things. |
@novemberborn You think you can help me with that? |
Hi @Jaden-Giordano, thanks for continuing to work on this. I've been a bit busy lately so it may take a few more days before I can get back to you on this, sorry. |
No worries, I assumed so, thanks for being so helpful so far. |
Update:So I managed to move past those errors, but now the cli and api tests are not passing, Im assuming this is happening since I added two properties to the config ( Next steps after fixes:After this fix, we should be ready to move on to notifying the watch. |
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.
Wow nice work @Jaden-Giordano! Thanks for persisting with this. I've left some hopefully helpful comments.
api.js
Outdated
@@ -43,6 +43,12 @@ class Api extends EventEmitter { | |||
const apiOptions = this.options; | |||
runtimeOptions = runtimeOptions || {}; | |||
|
|||
const doNotCompileExtensions = apiOptions.extensions || []; | |||
const babelExtensions = apiOptions.babelConfig.extensions || []; |
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.
FYI apiOptions.babelConfig
can be null
. See:
Line 145 in 977cf4f
babelConfig: babelConfigHelper.validate(conf.babel), |
and:
Line 18 in 977cf4f
return null; |
That said when it's not null
it'd be good if it could have a default extensions
value of ['js']
. The validate()
function in babel-config
should take care of that.
Since these extensions won't change during test runs, perhaps compute them in the constructor?
You may still run into some CI problems since not all tests properly set up the Api
instance. It'd be OK to set a default value if fixing the tests proves too much effort.
api.js
Outdated
const babelExtensions = apiOptions.babelConfig.extensions || []; | ||
|
||
// Combine all extensions possible for testing. | ||
const allExts = [...doNotCompileExtensions, ...babelExtensions]; |
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 fail CI in Node.js 4, but don't worry about that. We're dropping support since it's pretty much end of life anyway. I just need to open the necessary issues.
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.
Gotcha
lib/ava-files.js
Outdated
@@ -115,9 +116,12 @@ class AvaFiles { | |||
}); | |||
|
|||
if (files.length === 0) { | |||
files = defaultIncludePatterns(); | |||
files = defaultIncludePatterns(options.extensions || ['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.
You've changed defaultIncludePatterns
to take a single pattern, but here you're calling it with an array. That's probably why CI is failing. Looks like you need the this.extensionPattern
value from below.
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.
Thanks for this catch!
lib/ava-files.js
Outdated
|
||
if (process.platform === 'win32') { | ||
// Always use `/` in patterns, harmonizing matching across platforms | ||
pattern = slash(pattern); | ||
} | ||
|
||
return handlePaths([pattern], excludePatterns, globOptions); | ||
return handlePaths([pattern], extensions, excludePatterns, globOptions, true); |
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.
That last true
parameter seems unused?
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.
Alright I removed that.
lib/babel-config.js
Outdated
@@ -22,7 +22,7 @@ function validate(conf) { | |||
return {testOptions: {}}; | |||
} | |||
|
|||
if (!conf || typeof conf !== 'object' || !conf.testOptions || typeof conf.testOptions !== 'object' || Array.isArray(conf.testOptions) || Object.keys(conf).length > 1) { | |||
if (!conf || typeof conf !== 'object' || !conf.testOptions || typeof conf.testOptions !== 'object' || Array.isArray(conf.testOptions)) { |
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 Object.keys()
bit was there to ensure only known keys were used. So we still need that, but it's gotten a bit trickier since more than 1 key is allowed.
It should be possible to provide extensions
and not testOptions
.
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 add another function that checks the keys and return a boolean for whether the keys are valid or not and place it in that checks place.
lib/caching-precompiler.js
Outdated
@@ -30,7 +30,11 @@ class CachingPrecompiler { | |||
this.transform = this._createTransform(); | |||
} | |||
|
|||
precompileFile(filePath) { | |||
precompileFile(filePath, excludeExtensions) { | |||
if (excludeExtensions.includes(path.extname(filePath))) { |
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 should be guarded against in the calling code.
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 idea moving this to be checked here https://github.com/avajs/ava/blob/master/api.js#L134
@novemberborn Thank you for the insight it helped immensely! That fixed most of the issues. It is working if I install from my git repo with this current branch and run it with babel.extensions = ['jsx'] too. Maybe you have some idea what is going on in the CI? With that being said I'm going to go ahead and move on to informing the watcher. |
A newer Babel 7 is being picked up in one of the test jobs, and it's causing test failures. I'm a little surprised at that, to be honest! Feel free to ignore the Travis run with FRESH_DEPS=true, and AppVeyor as well since it doesn't run any other tests after the fresh-deps one fails. It's going to be a few days before I'll have a chance to look at this. Thanks for your hard work @Jaden-Giordano! |
Sounds good, take you're time. |
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 shaping up nicely @Jaden-Giordano!
I'm sure it's on your to-do list but we'll need some tests for the new behavior, too.
api.js
Outdated
@@ -43,6 +43,12 @@ class Api extends EventEmitter { | |||
const apiOptions = this.options; | |||
runtimeOptions = runtimeOptions || {}; | |||
|
|||
const doNotCompileExtensions = apiOptions.extensions || []; | |||
const babelExtensions = apiOptions.babelConfig ? apiOptions.babelConfig.extensions || ['js'] : ['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.
Oh! Since we're no longer targeting Node.js 4, this could be:
const {extensions: doNotCompileExtensions = []} = apiOptions;
I wouldn't set the default 'js
' value here, since it depends on whether apiOptions.babelConfig === null
. Instead:
const {extensions: babelExtensions = []} = apiOptions.babelConfig || {};
if (!apiOptions.babelConfig && doNotCompileExtensions.length === 0) {
doNotCompileExtensions.push('js');
}
if (apiOptions.babelConfig && babelExtensions.length === 0) {
babelExtensions.push('js');
}
Some of this could be done through destructuring but it gets rather difficult to understand.
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.
Oh alright I see.
api.js
Outdated
const babelExtensions = apiOptions.babelConfig ? apiOptions.babelConfig.extensions || ['js'] : ['js']; | ||
|
||
// Combine all extensions possible for testing. | ||
const allExts = [...doNotCompileExtensions, ...babelExtensions]; |
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 needs a check to ensure any extension in doNotCompileExtensions
is not also listed in babelExtensions
.
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.
For this im just using concat
then filter
to filter out any duplicate entries:
const allExtensions = doNotCompileExtensions
.concat(babelExtensions)
.filter((ext, i, self) => self.indexOf(ext) === i);
lib/babel-config.js
Outdated
return false; | ||
} | ||
} | ||
return true; |
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.
How about:
return Object.keys(conf).every(key => key === 'extensions' || key === 'testOptions')`
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 elegant!
lib/babel-config.js
Outdated
throw new Error(`${colors.error(figures.cross)} Unexpected Babel configuration for AVA. See ${chalk.underline('https://github.com/avajs/ava/blob/master/docs/recipes/babel.md')} for allowed values.`); | ||
} | ||
|
||
if (conf.extensions === undefined) { | ||
conf.extensions = ['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 think it might be easier not to set a default here, but always ensure extensions
is an array. Then the code in api.js
could be simplified further.
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 just going to strip away this code, this shouldn't even be needed with the recommendations you gave for api.js
.
lib/babel-config.js
Outdated
} | ||
|
||
if (!conf || typeof conf !== 'object' || !conf.testOptions || typeof conf.testOptions !== 'object' || Array.isArray(conf.testOptions) || Object.keys(conf).length > 1) { | ||
if (!conf || typeof conf !== 'object' || !conf.testOptions || typeof conf.testOptions !== 'object' || Array.isArray(conf.testOptions) || !validateKeys(conf)) { |
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 needs to check whether extensions
, if present, is an array.
It might be worthwhile creating a let valid = true
variable and then splitting the checks up into multiple if
conditions, each of which could assign false
, before throwing the error only if !valid
.
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.
Instead of multiple if
conditions we could use invalid
instead then split them up by doing:
let invalid = false;
invalid = !conf || typeof conf !== 'object' || ...;
invalid = ...
if (invalid) {
throw ...
}
This should be a bit shorter than a bunch of if statements.
test/api.js
Outdated
const Api = require('../api'); | ||
|
||
const test = tap.test; |
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 this change? (Same for the other files.)
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 was an oops, I was running tap.only to test specific files and forgot to revert this line.
@Jaden-Giordano if you could merge in (or rebase on) master, that should fix some of the CI issues. |
Yeah will do! |
This is an update of a previous commit removing a change that was just being used for testing and shouldn't have been pushed
98c978c
to
90de180
Compare
ffd673b
to
226b42b
Compare
So the test would just end up precompiling and checking if the test passed but thats what the test I wrote already does so I just left that the same but added a test to ava files to look for files with a specified extension. Also what is the |
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.
You actually need to use transformSpy
I think, see the inline comment. We also need to exit with an error if the same extension is defined multiple times.
Now something I feel really bad about — we need a separate compilation path for the "do not compile" extensions. Really what that setting means is "don't apply AVA's Babel presets", except that we still want to compile some other enhancements (the compileEnhancements
setting), and currently we do that through Babel. I've left a bit more detail inline, below. Please let me know if you want me to push a commit for that, I realize this PR is starting to drag on a bit.
Finally, would you want to add some documentation for this new feature?
Thanks for your hard work on this @Jaden-Giordano. Again apologies for missing some of this issues before. Happy to push some commits if you'd like.
api.js
Outdated
} | ||
|
||
// Combine all extensions possible for testing. Removing duplicate extensions. | ||
const allExtensions = [...new Set([...doNotCompileExtensions, ...babelExtensions])]; |
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 just needs to ensure there is no overlap between both arrays. You could compare the length of allExtensions
to the combined length of doNotCompileExtensions
and babelExtensions
: it should be equal.
We should probably include the ambiguous extensions in the error message though. Some applications of lodash.difference
should do the trick.
Given that we're now throwing an error, could you move this logic into the constructor? Feel free to save allExtensions
on the instance.
test/caching-precompiler.js
Outdated
precompiler.precompileFile(fixture('es2015-source-maps.js')); | ||
const options = transformSpy.lastCall.args[1]; | ||
precompiler.precompileFile(fixture('es2015-source-maps.js'), []); | ||
const options = babel.transform.lastCall.args[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.
I suspect you may have had a merge conflict here? Use transformSpy
rather than babel.transform
in this line.
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.
Oh alright gotcha
api.js
Outdated
try { | ||
const realpath = fs.realpathSync(file); | ||
let hash = realpath; | ||
if (!doNotCompileExtensions.includes(path.extname(realpath))) { |
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.
OK so the tricky thing is we still want to compile enhancements, unless that's also disabled. This currently does use Babel, but it won't apply AVA's regular presets. I think this means instantiating a second precompilation
object, and doing a second build of Babel configuration.
I feel bad for not spotting this earlier, and it's a bit tricky to line all this up correctly. Let me know if you want me to push a commit for this instead.
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.
@novemberborn If you could on this one I can handle the rest, work is a bit busy so I don't have alot of time to work on this. Thanks for all the help!
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.
If you could on this one I can handle the rest, work is a bit busy so I don't have a lot of time to work on this. Thanks for all the help
@Jaden-Giordano I'm a bit time constrained as well but will tackle the separate "compile enhancements" code path for the regular extensions
👍
I've left some other comments below.
api.js
Outdated
this.allExtensions = [...new Set([...doNotCompileExtensions, ...babelExtensions])]; | ||
|
||
if (this.allExtensions.length !== doNotCompileExtensions.length + babelExtensions.length) { | ||
const duplicates = difference([...doNotCompileExtensions, ...babelExtensions]); |
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.
You need to compare two arrays for difference
to return anything. But then it actually gives the extensions that are not repeated, so:
const notRepeated = difference(doNotCompileExtensions, babelExtensions);
const duplicates = this.allExtensions.filter(ext => !notRepeated.includes(ext));
I suppose this still needs a test?
readme.md
Outdated
@@ -262,6 +262,7 @@ To ignore a file or directory, prefix the pattern with an `!` (exclamation mark) | |||
"@babel/register" | |||
], | |||
"babel": { | |||
"extensions": ["jsx"] |
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.
Missing comma.
readme.md
Outdated
@@ -282,8 +283,10 @@ Arguments passed to the CLI will always take precedence over the configuration i | |||
- `tap`: if `true`, enables the [TAP reporter](#tap-reporter) | |||
- `snapshotDir`: specifies a fixed location for storing snapshot files. Use this if your snapshots are ending up in the wrong location | |||
- `compileEnhancements`: if `false`, disables [power-assert](https://github.com/power-assert-js/power-assert) — which otherwise helps provide more descriptive error messages — and detection of improper use of the `t.throws()` assertion | |||
- `extensions`: Extensions to opt of of precompilation. |
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 needs some elaboration. How about:
Extensions of test files that are not precompiled using AVA's Babel presets. Note that files are still compiled to enable power-assert and other features, so you may also need to set
compileEnhancements
tofalse
if your files are not valid JavaScript.
readme.md
Outdated
- `require`: extra modules to require before tests are run. Modules are required in the [worker processes](#process-isolation) | ||
- `babel`: test file specific Babel options. See our [Babel recipe] for more details | ||
- `babel.extensions`: Extensions to include in precompilation. |
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.
How about:
Extensions of test files that will be precompiled using AVA's Babel presets.
@Jaden-Giordano I haven't had a chance yet to look into this. I just realized we need some changes with Babel (#1789) so I might try and land that before doing my work here. Anyway this is super close so once that unblocks we should be good to go. |
@Jaden-Giordano just wanted to say I haven't forgotten about this 😄 #1789 is pretty much done so I'm hoping to land that next weekend. I'll then try and get this in as well before cutting a release. |
Phew! This is ready now, @Jaden-Giordano please have a look at the commits I just pushed. I've updated the Babel and ES module recipes. I've fixed the top-level extensions / enhancements-only code path, and we can now handle extensions that have periods in them. |
I don't think |
Support periods in extensions.
e04c3c7
to
fd477f1
Compare
Whoo! This is exciting! |
Allows for the use of extensions like
jsx
,ts
, etc..Fixes #631