Skip to content
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

Spread syntax on iterables unsupported within <script lang="ts"> blocks in Vue files when running tests #445

Open
jessevanassen opened this issue Jan 27, 2022 · 3 comments
Labels

Comments

@jessevanassen
Copy link

Using the spread syntax to convert a non-array iterable (like a Map or Set) to an array doesn't work within a <script lang="ts"> block within a Vue file.
Depending on the TypeScript version, it will either produce the "RangeError: Invalid array length" error in TypeScript <= 4.1 (which is the default for @vue/cli projects) or an empty array when using a more recent version.

Reproduction

I created an empty project with the @vue/cli, and enabled Vue 3, TypeScript and Jest support.

/src/example.vue:

<script lang="ts">
import { defineComponent, h } from 'vue';

export default defineComponent({
	setup: () => ({
		values: [...new Set([1, 2, 3])],
	}),
	render: () => h('div'),
})
</script>

/test/unit/example.spec.ts:

import { mount } from '@vue/test-utils';
import Example from '../../src/example.vue';

test('Using a spread iterable from a Vue file', () => {
	const { vm } = mount(Example);
	expect(vm.values).toEqual([1, 2, 3]);
});

Running the test fails, either on a runtime error with an older TypeScript version, or on the assertion on newer TypeScript versions.

Full reproduction in reproduction.zip


The problem is caused by the TypeScript compiler running in ES5 mode for <script lang="ts"> blocks. This produces TypeScript code that is unaware of iterables and causes the seem behavior.
The same behavior can be reproduced in the TypeScript playground by targeting ES5.

Possible workaround

Next to targeting ES5 mode in lib/utils.js, also enable the downlevelIteration property, so iterables are properly spreadable:

diff --git a/packages/vue3-jest/lib/utils.js b/packages/vue3-jest/lib/utils.js
index 8ac12ee..f16b7b4 100644
--- a/packages/vue3-jest/lib/utils.js
+++ b/packages/vue3-jest/lib/utils.js
@@ -83,7 +83,7 @@ const getTsJestConfig = function getTsJestConfig(config) {
   const tsConfig = configSet.typescript || configSet.parsedTsConfig
   // Force es5 to prevent const vue_1 = require('vue') from conflicting
   return {
-    compilerOptions: { ...tsConfig.options, target: 'es5', module: 'commonjs' }
+    compilerOptions: { ...tsConfig.options, target: 'es5', module: 'commonjs', downlevelIteration: true }
   }
 }

However, that still leaves the underlying issue that <script lang="ts"> blocks are compiled to ES5, which produces huge amounts of code for constructs like async / await, the above-mentioned spreads, etc. which shouldn't be needed when targeting modern node versions.

@lmiller1990
Copy link
Member

I encountered this issue recently, too.

Basically, even if we fix this, there's no guarantee that the code in your test env. is actually the same as your production code - this is a good example, we know ... works in the browser, but it's not passed correctly here.

I wonder what the fix is. Ideally we should be requiring and using the user's tsconfig.json, right? Isn't that what we already do with ...tsConfig.options? In this case, couldn't you add the downlevelIteration to your tsconfig.json?

I've basically moved away from Jest for my Vue tests for this reason - Jest has a pretty good DX, but the ideal of using a different, duplicate build config for my tests is too problematic and a deal breaker. On top of that and jsdom differences, I feel like I can't rely on my tests any more.

What's the correct action here? Any suggestions?

@jessevanassen
Copy link
Author

jessevanassen commented May 9, 2022

I wonder what the fix is. Ideally we should be requiring and using the user's tsconfig.json, right? Isn't that what we already do with ...tsConfig.options? In this case, couldn't you add the downlevelIteration to your tsconfig.json?

That is what I use as a workaround now. However, that has a few downsides:

  • I (and everyone else that uses vue-jest) have to do this in all my projects, which can easily be forgotten. I would expect this to be the responsibility of vue-jest, especially because vue-jest already changes the TypeScript target to ES5 which is the cause of this bug.
  • By changing it in my tsconfig.json, it does not only affect the tests, but my production build configuration as well.

@lmiller1990
Copy link
Member

I'd love to see this somehow fixed, I'm not entirely sure what we should do. IIRC (could be incorrect) if we do not downgrade to es5 during the transformation, we end up with conflicts because of how naive the code-gen part of this code base is (sometimes you end up with multiple const Vue = require("vue") in the code-gen, due to multiple components, by downgrading to es5 we use var instead which does not conflict).

An ideal solution would be a more intelligence compiler where we actually parse with something like babel before creating the final code, but it's a lot of work to revamp. If someone wants to take a stab at making vue-jest less naive, it would likely allow us to remove the hard-coding of es5 and everything would "just work".

Happy to entertain other suggestions/ideals, too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants