-
Notifications
You must be signed in to change notification settings - Fork 668
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
How to use typescript correctly? #255
Comments
@blake-newman would you be able to write a guide on using vue-test-utils with TypeScript? |
Would love this. I'm following a blog post by Alex Joverm but failing to get it to work at all. |
@RehanSaeed the blog post of Alex Joverm doesn't use TypeScript. |
The main issue, is that Everything is as you would expect, following the guides. |
Just following up @blake-newman's explanation, Vetur (and probably WebStorm) have their own language service which can deal with To be available typed components in I think it would be the best if Vetur supports TypeScript plugin so that we can use its feature in |
I have recently started writing Vue unit tests using Jest + Typescript + Vue test utils, however i'm facing an issue where passing in my component object into the shallow function appears differently when using vue with typescript. I believe the reason is because it's using the default constructor of vue (vue-shims.d.ts) and not the typed component. As explained by @ktsn. Is there a way to pass in the component object correctly using typescript? Source code to reproduce the issue: https://github.com/mikeli11/Vue-Typescript-Jest Using typescript: Without typescript (https://github.com/vuejs/vue-test-utils-jest-example) |
@kimond What does your Jest configuration look like? My setup cannot even resolve the modules in tests. I have |
@tlaak Here is my jest configuration. However, I did nothing special in order to make it works. "jest": {
"moduleFileExtensions": [
"ts",
"js",
"json",
"vue"
],
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest",
"^.+\\.ts$": "<rootDir>/node_modules/ts-jest/preprocessor"
},
"verbose": true
} I forgot to mention that I needed to revert to |
@kimond That looks like the same I have. I noticed that the module resolution fails if I have my tests in the |
@tlaak It seems like your {
"compilerOptions": {
"target": "es5",
"module": "es2015",
"strict": true,
"jsx": "preserve",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"node",
"jest"
],
"paths": {
"@/*": [
"src/*"
]
}
},
"include": [
"src/**/*.ts",
"src/**/*.vue",
"tests/**/*.ts"
],
"exclude": [
"node_modules"
]
} |
My imports are working just fine when I'm running the app in browser. I have the |
@tlaak
With the config above you will be able to use |
@kimond Thanks a million! That helped. I think this conversation verifies that proper 'how to' documentation is really needed :) |
Yes we definitely need a how to guide. Would you like to help make a guide? If a TypeScript user would like to write a guide, you can make a PR and I will help you with the process :) |
I think the guide could take setup parts from Jest, Typescript then Vue-test-utils from their own guide then combine everything into a guide. The second option is quickest but less detailed than the first one. |
I would like to give it a go. |
@elevatebart you could make a pull request in this repo, we have a guides section in the docs. |
Is there a guide available here? |
I've finally had some time to write a guide on using with TypeScript—https://deploy-preview-876--vue-test-utils.netlify.com/guides/using-with-typescript.html. Le me know what you think, and if there's any information missing that you would like me to add! |
If I can add my two cents I would say TypeScript users would benefit more from the guide in the sense of how to use the library, not how to configure it, as Vue CLI already handles that. Some pitfalls to have into consideration from vue-test-utils + TypeScript:
it('foo', () => {
const wrapper = mount(Component)
const foundComponent = wrapper.find({ name: 'SomeComponent' })
foundComponent.vm.$emit('event')
// We don't know the signature of wrapper.vm, so TS fails when we try to access bar
expect((wrapper.vm as any).bar).toBeFalsy()
})
describe('actions', () => {
let store: Store<RootState>
beforeEach(() => {
const localVue = createLocalVue()
localVue.use(Vuex)
store = new Vuex.Store({ modules: { langs: langsStore } })
})
it('should load a default language', async () => {
store.state.langs.language = 'es'
store.dispatch('langs/loadDefaultLanguage')
await flushPromises()
expect(store.state.langs.language).toEqual('en')
})
})
describe('mutations', () => {
let store: Store<GlobalState>
beforeEach(() => {
const localVue = createLocalVue()
localVue.use(Vuex)
// We need to cast to any :(
store = new Vuex.Store(globalStore as any)
})
it('should change the globlal state loading to true when enable loading mutation is called', () => {
store.commit(globalMutationTypes.LOADING.ENABLE)
expect(store.state.loading).toBeTruthy()
})
})
interface CustomProperties extends CSSStyleDeclaration {
Color: string
Size: string
}
describe('Icon', () => {
snapshotTest({ component: Icon })
it('should propagate correctly the color of the icon', () => {
const wrapper = shallowMount(Icon, {
propsData: {
name: 'ui-edit',
color: 'red'
}
})
expect((wrapper.find('.icon').element.style as CustomProperties).Color).toEqual('var(--red)')
})
}) I would say in general the guide should be focused in how to avoid at all costs casting to any, and when there is no other way. Maybe when the base guide is completed I could add more to it 🙂 |
@cesalberca I've added everything that I can to the guide, so if you can add more info/pitfalls that would be great!
That sounds like a problem with our types that we should fix? |
Yeah, it can be done @eddyerburgh. Although I don't know where can I make a PR. Perhaps next week 👍 |
@cesalberca @eddyerburgh for now, I personally have solved it by using the $data property to access |
@cesalberca Why do we have to do this:
Both |
@RehanSaeed yes I expect it should be, but currently I still got error on the TSLint of my VSCode: Could you help clarify if this happened to you as well? |
From what I know, it's difficult to extract this information from an SFC .vue file. @ znck is doing God's work regarding this in typescript-plugin-vue, though it's currently experimental I assume this is why all declare module '*.vue' { //for every .vue file
import Vue from 'vue';
export default Vue; //assume it's default import is of type Vue (generic interface without type information)
}
There's no difference. Both |
I get the same errors while compiling as @vegerot and I'm also looking for a proper solution to accessing computed props and methods with TypeScript. Has there been any update or workable solution to this issue in the meantime? |
I worked around it like this: const wrapper = mount<Vue & {
someDataValue: number[];
}>(usage);
wrapper.vm.someDataValue // number[] |
I still have the same issue :/ |
This approach works by also casting import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'
describe('HelloWorld.vue', () => {
it('computes [enhancedMsg]', () => {
const msg = 'new message'
const wrapper = shallowMount<HelloWorld>(HelloWorld, {
propsData: { msg }
})
expect(wrapper.vm.enhancedMsg).toBe(`${msg} is nice`)
})
}) |
@tehciolo for me this doesn't work: Cannot find name 'HelloWorld'. Did you do anything more to get this to build? |
@brmdbr These are the steps I followed:
|
Getting real type information from .vue files that are imported in .spec.ts files is now possible in Vue 3 & Vite (maybe Vue 2 but I didn't try), but it probably requires significant changes to your/your team's workflow. If you really want it, here are the instructions:
The above two steps should make autosuggestions work in example repo where this works — just clone, run |
I cannot easily move to Vue3 (and cannot move to Vite at all). I also cannot expect every dev to use VSCode, so I need a solution that works not only in the editor. Unfortunately, there does not seem to be a (good) solution for my use case to get full type inference. |
@tehciolo Like Vetur, Volar is a language server so it's editor agnostic (though both are optionally available as VS Code extensions). There's a IDE support list in Volar's readme. As you can see, my screenshots are from (neo)vim, not vscode. Volar also works with Vue 2 but requires additional setup but I see there've been some problems with vue-test-utils, volar and vue 2 With Vue 2 another roadblock could be getting Vue CLI to use vue-tsc instead of just tsc — would probably require messing with the webpack configuration or disabling vue-cli-plugin-typescript |
@sethidden I've got the type information working for the props by using Volar's Take Over Mode. But I can't seem to get any information on my methods or data variables in the component. Just wondering if this is something you have managed to get working? I have managed to get everything working (computed, methods, variables etc) if I don't use the setup attribute on the script tag, but using that only props seem to work |
@jayEvans89 Well I have a repo whose aim is to show how to publish Vue NPM packages with Volar's Take Over Mode typings included in the template, but it also has a main.spec.ts file, see https://github.com/sethidden/vue-tsc-component-library-example/blob/master/src/main.spec.ts My guess is that since computeds, methods and data properties are not the component's public interface, there's no reason to expose the types to them (ie. you shouldn't test them because that makes the unit tests rely test the implementation and that's wrong etc. etc.). Props on the other side work, because they're the input output/public interface of the component |
I have tried your linked repo but if you add a method or computed prop to the component you can't access them in the typings in the spec file either. So guessing this is either not currently possible with the setup attribute or theres a bug somewhere |
After reading this discussion and trying out some of the workarounds, I've come to the conclusion that there are 4 viable solutions. In order of my own preference:
Separate If I had to stick with a single file, I'd go with the relaxed vue type. The global solution suggested by @sethidden didn't work, so instead I had to make a library type. // test/vue-types.ts
import { Wrapper } from "@vue/test-utils";
import Vue from "vue";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type RelaxedVue = Vue & { [key: string]: any };
export type RelaxedWrapper = Wrapper<VueRelaxed, Element>;
// Hello.spec.ts
import { mount } from "@vue/test-utils";
import type { RelaxedWrapper } from "@/test/vue-types.ts";
import Hello from "@/components/Hello.vue";
describe("Hello", () => {
it("hi", () => {
const helloWrapper = mount(Hello) as RelaxedWrapper;
const hello = helloWrapper.vm;
// or = mount(Hello).vm as RelaxedVue;
});
}); |
Going through What do you think about this? // test/vue-types.ts
import Vue from "vue";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type RelaxedVue = Vue & { [key: string]: any };
// Hello.spec.ts
import { mount } from "@vue/test-utils";
import type { RelaxedVue } from "@/test/vue-types.ts";
import Hello from "@/components/Hello.vue";
describe("Hello", () => {
it("hi", () => {
const helloWrapper = mount<RelaxedVue>(Hello);
const hello = helloWrapper.vm;
});
}); |
You can just write
No need Any types!! |
@Ge6ben No, that breaks intellisense and linting with Vue 2 and Vetur. As someone said above:
So, you must use a type that can accept arbitrary property/method names, which is why However, I discovered that using a class component with a separate <!-- hello.vue -->
<template>
<div>
<p>{{ greeting }}</p>
<button @click="world">To the World</button>
</div>
</template>
<script lang="ts" src="./Hello.ts" /> // Hello.ts
import Component from "vue-class-component";
import Vue from "vue";
@Component
export default class Hello extends Vue {
name = "Hello";
greeting = "Hello";
world() {
this.greeting = "Hello, world!"
}
} // Hello.spec.ts
import { mount } from "@vue/test-utils";
import Hello from "@/components/Hello.vue";
describe("Hello", () => {
let component;
beforeEach(() => {
component = mount(Hello).vm;
});
it("world click", () => {
expect(component.greeting).toContain("Hello");
component.world();
expect(component.greeting).toContain("Hello, world!");
});
}); As side note, in your comment these are equivalent: (but dont' work with Vue 2) const helloWrapper = mount<Hello>(Hello);
const helloWrapper = mount(Hello); |
@tehciolo a mild improvement would be to declare as: // test/test-util.ts
import Vue from "vue";
import { mount as baseMount, Wrapper, ThisTypedMountOptions } from "@vue/test-utils";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type RelaxedVue = Vue & { [key: string]: any };
// taken from @vue/test-utils with Vue replaced with RelaxedVue
export function mount<V extends RelaxedVue> (
component: VueClass<V>,
options?: ThisTypedMountOptions<V>
): Wrapper<V> {
return baseMount(component, options)
} With usage as: // Hello.spec.ts
import { mount } from "@/test/test-util.ts";
import Hello from "@/components/Hello.vue";
describe("Hello", () => {
it("hi", () => {
// type will be RelaxedVue. Intellisense will not work, but there won't be warnings.
const helloWrapper = mount(Hello);
const hello = helloWrapper.vm;
});
}); ( Untested. I may have the type syntax wrong. ) |
@mikeslattery relaxation baked in. I like it. |
Thanks! |
I'm using Vue 2 with wonderful-panda/vue-tsx-support and I got a little win: import { shallowMount, Wrapper } from "@vue/test-utils";
const wrapper = shallowMount(MyComponent) as Wrapper<typeof MyComponent>;
wrapper.vm.myMethod("arg"); and import { VNode } from "vue";
import * as tsx from "vue-tsx-support";
export default tsx.component({
name: "MyComponent",
methods: {
myMethod(arg: string) {
console.log(arg);
},
},
render(): VNode {
return <h1>hi</h1>;
},
}); This way in test wrapper has all the methods from the component, and what is great - VS Code refactoring can recognize and sync method renames across sources and tests. Not entirely sure why I have to use Also this requires a patch for diff --git a/node_modules/vue-tsx-support/lib/api.d.ts b/node_modules/vue-tsx-support/lib/api.d.ts
index 40e92ce..20b098c 100644
--- a/node_modules/vue-tsx-support/lib/api.d.ts
+++ b/node_modules/vue-tsx-support/lib/api.d.ts
@@ -6,7 +6,7 @@ export declare type _TsxComponentInstanceV3<V extends Vue, Attributes, Props, Pr
_tsx: TsxComponentTypeInfo<Attributes, Props, PrefixedEvents, On>;
$scopedSlots: InnerScopedSlots<ScopedSlotArgs>;
} & V;
-export declare type _TsxComponentV3<V extends Vue, Attributes, Props, PrefixedEvents, On, ScopedSlotArgs> = VueConstructor<_TsxComponentInstanceV3<V, Attributes, Props, PrefixedEvents, On, ScopedSlotArgs>>;
+export declare type _TsxComponentV3<V extends Vue, Attributes, Props, PrefixedEvents, On, ScopedSlotArgs> = VueConstructor<_TsxComponentInstanceV3<V, Attributes, Props, PrefixedEvents, On, ScopedSlotArgs>> & V;
export declare class Component<Props, PrefixedEvents = {}, ScopedSlotArgs = {}> extends Vue {
_tsx: TsxComponentTypeInfo<{}, Props, PrefixedEvents, {}>;
$scopedSlots: InnerScopedSlots<ScopedSlotArgs>; |
Still struggling on this one with vue 2, any updates ? |
For those coming here and using Vue 3, the answer you're looking for is most likely to use |
I'm not sure if this issue belongs to this project. However, I'm using vue-test-utils since the beginning (even when its name was Avoriaz). But I have some issue to use SFC with typescript and Jest. I was wondering if you planned to write more documentation about which test runners or libs we should use to work properly with Typescript + Jest + Vue-test-utils?
Thank you!
The text was updated successfully, but these errors were encountered: