fix: vue 3 types, beta suffix & component name (#17508)

* fix: update test-utils dependency & remove beta suffix

* test: add typescript in the tests

* chore: add setup example to cypress vue

* build: remove noEmit from tsconfig.json

* fix: make bigger types

* test: make functinoal and definecompoennt tested

* ci: make circle run typechek on vue

* test: typescript files

* test: remove usage of regenerator-runtime

* style: avoid using internal types of vtu + comment
This commit is contained in:
Barthélémy Ledoux
2021-07-30 16:30:19 -05:00
committed by GitHub
parent 13f792c70e
commit b4733a6179
15 changed files with 1077 additions and 234 deletions
+4
View File
@@ -1338,6 +1338,10 @@ jobs:
- run:
name: Build
command: yarn workspace @cypress/vue build
- run:
name: Type Check
command: yarn typecheck
working_directory: npm/vue
- run:
name: Run component tests
command: yarn test:ci:ct
+1 -1
View File
@@ -7,6 +7,6 @@ module.exports = {
// this line forces releasing 2.X releases on the latest channel
{ name: 'npm/vue/v2', range: '2.X.X' },
// this one releases v3 on master as beta on the next channel
{ name: 'master', channel: 'next', prerelease: 'beta' },
{ name: 'master', channel: 'next' },
],
}
+21 -1
View File
@@ -1,12 +1,32 @@
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"browsers": [
"last 1 Chrome version",
"last 1 Electron version",
"last 1 Firefox version"
]
},
"modules": "cjs"
}
],
"babel-preset-typescript-vue3",
"@babel/preset-typescript"
],
"plugins": [
"@babel/plugin-transform-modules-commonjs",
[
"babel-plugin-istanbul",
{
"extension": [
".js",
".vue"
],
"exclude": [
"**/*.spec.{js,ts}",
"**/setup/*.vue"
]
}
]
+1 -1
View File
@@ -4,6 +4,6 @@
"video": false,
"responseTimeout": 2500,
"projectId": "134ej7",
"testFiles": "**/*spec.js",
"testFiles": "**/*spec.{js,ts,tsx}",
"experimentalFetchPolyfill": true
}
@@ -1,4 +1,3 @@
/// <reference types="cypress" />
import Hello from './Hello.vue'
import { mount } from '@cypress/vue'
@@ -10,12 +10,12 @@ describe('Props', () => {
it('has props', () => {
const messages = ['one 🍎', 'two 🍌']
mount(MessageList, { propsData: { messages } })
mount(MessageList, { props: { messages } })
getItems()
.should('have.length', 2)
.then((list) => {
expect(list[0].textContent.trim()).to.equal(messages[0])
expect(list[1].textContent.trim()).to.equal(messages[1])
expect(list[0].textContent).to.contain(messages[0])
expect(list[1].textContent).to.contain(messages[1])
})
})
})
@@ -0,0 +1,27 @@
import { h } from 'vue'
import { mount } from '@cypress/vue'
import HelloWorld from './HelloWorld.vue'
describe('HelloWorld', () => {
it('normal mount', () => {
mount(HelloWorld, { props: { msg: 'Hello Cypress' } })
})
it('functional mount', () => {
mount(() => h(HelloWorld, { msg: 'Hello Cypress' }))
})
it('renders properly', () => {
mount(HelloWorld, { props: { msg: 'Hello Cypress' } })
cy.get('h1').should('contain', 'Hello Cypress')
})
it('adds 1 when clicking the plus button', () => {
mount(HelloWorld, { props: { msg: 'Hello Cypress' } })
cy.get('button')
.should('contain', '0')
.click()
.should('contain', '1')
})
})
@@ -0,0 +1,12 @@
<template>
<h1>{{ msg }}</h1>
<button type="button" @click="count++">count is: {{ count }}</button>
</template>
<script setup lang="ts">
import { defineProps, ref } from 'vue'
defineProps<{
msg: String,
}>()
const count = ref(0)
</script>
+11 -8
View File
@@ -8,19 +8,21 @@
"build-prod": "yarn build",
"cy:open": "node ../../scripts/cypress.js open-ct --project ${PWD}",
"cy:run": "node ../../scripts/cypress.js run-ct --project ${PWD}",
"test:ci:e2e": "node ../../scripts/cypress.js run --project ${PWD}",
"typecheck": "vue-tsc --noEmit",
"test": "yarn cy:run",
"watch": "yarn build --watch --watch.exclude ./dist/**/*",
"test:ci:e2e": "node ../../scripts/cypress.js run --project ${PWD}",
"test:ci:ct": "node ../../scripts/run-ct-examples.js --examplesList=./examples.env"
},
"dependencies": {
"@cypress/mount-utils": "0.0.0-development",
"@vue/test-utils": "^2.0.0-rc.9"
"@vue/test-utils": "^2.0.0-rc.10"
},
"devDependencies": {
"@babel/core": "7.9.0",
"@babel/plugin-transform-modules-commonjs": "7.10.4",
"@babel/plugin-transform-modules-commonjs": "7.9.6",
"@babel/preset-env": "7.9.5",
"@babel/preset-typescript": "7.10.1",
"@cypress/code-coverage": "3.8.1",
"@cypress/webpack-dev-server": "0.0.0-development",
"@intlify/vue-i18n-loader": "2.0.0-rc.1",
@@ -28,10 +30,11 @@
"@rollup/plugin-node-resolve": "^11.1.1",
"@vue/cli-plugin-babel": "~4.4.0",
"@vue/cli-service": "~4.4.0",
"@vue/compiler-sfc": "^3.0.5",
"@vue/compiler-sfc": "3.1.5",
"axios": "0.19.2",
"babel-loader": "8.1.0",
"babel-plugin-istanbul": "6.0.0",
"babel-plugin-istanbul": "^6.0.0",
"babel-preset-typescript-vue3": "^2.0.14",
"css-loader": "3.4.2",
"cypress": "0.0.0-development",
"debug": "4.3.2",
@@ -44,12 +47,13 @@
"rollup-plugin-istanbul": "2.0.1",
"rollup-plugin-typescript2": "^0.29.0",
"tailwindcss": "1.1.4",
"typescript": "3.9.6",
"vue": "3.0.11",
"typescript": "^4.2.3",
"vue": "3.1.5",
"vue-i18n": "9.0.0-rc.6",
"vue-loader": "16.1.2",
"vue-router": "^4.0.0",
"vue-style-loader": "4.1.2",
"vue-tsc": "0.2.2",
"vuex": "^4.0.0",
"webpack": "4.42.0"
},
@@ -77,7 +81,6 @@
"cypress",
"vue"
],
"unpkg": "dist/cypress-vue.browser.js",
"module": "dist/cypress-vue.esm-bundler.js",
"peerDependenciesMeta": {
"@cypress/webpack-dev-server": {
+5 -11
View File
@@ -16,7 +16,6 @@ function createEntry (options) {
const {
format,
input,
isBrowser,
} = options
const config = {
@@ -46,9 +45,6 @@ function createEntry (options) {
if (input === 'src/index.ts') {
if (format === 'es') {
config.output.file = pkg.module
if (isBrowser) {
config.output.file = pkg.unpkg
}
}
if (format === 'cjs') {
@@ -62,14 +58,14 @@ function createEntry (options) {
config.plugins.push(
ts({
check: format === 'es' && isBrowser,
check: false,
tsconfigOverride: {
compilerOptions: {
declaration: format === 'es',
target: 'es5', // not sure what this should be?
noEmit: false,
module: format === 'cjs' ? 'es2015' : 'esnext',
},
exclude: ['tests'],
exclude: ['cypress/component'],
},
}),
)
@@ -78,8 +74,6 @@ function createEntry (options) {
}
export default [
createEntry({ format: 'es', input: 'src/index.ts', isBrowser: false }),
createEntry({ format: 'es', input: 'src/index.ts', isBrowser: true }),
createEntry({ format: 'iife', input: 'src/index.ts', isBrowser: true }),
createEntry({ format: 'cjs', input: 'src/index.ts', isBrowser: false }),
createEntry({ format: 'es', input: 'src/index.ts' }),
createEntry({ format: 'cjs', input: 'src/index.ts' }),
]
+207 -19
View File
@@ -1,5 +1,9 @@
/* eslint-disable no-redeclare */
/// <reference types="cypress" />
import { Component, ComponentPublicInstance } from 'vue'
import { ComponentPublicInstance, VNodeProps, AllowedComponentProps,
ComponentCustomProps, ExtractPropTypes, ExtractDefaultPropTypes,
Component, DefineComponent, FunctionalComponent, ComputedOptions,
MethodOptions, ComponentOptionsMixin, EmitsOptions, ComponentOptionsWithObjectProps, ComponentPropsOptions, ComponentOptionsWithArrayProps, ComponentOptionsWithoutProps } from 'vue'
import { MountingOptions, VueWrapper, mount as VTUmount } from '@vue/test-utils'
import {
injectStylesBeforeElement,
@@ -10,6 +14,8 @@ import {
const DEFAULT_COMP_NAME = 'unknown'
type GlobalMountOptions = Required<MountingOptions<any>>['global']
// when we mount a Vue component, we add it to the global Cypress object
// so here we extend the global Cypress namespace and its Cypress interface
declare global {
@@ -22,14 +28,14 @@ declare global {
}
}
type CyMountOptions<Props> = Omit<MountingOptions<Props>, 'attachTo'> & {
type CyMountOptions<Props, Data = {}> = Omit<MountingOptions<Props, Data>, 'attachTo'> & {
log?: boolean
/**
* @deprecated use vue-test-utils `global` instead
*/
extensions?: MountingOptions<Props>['global'] & {
use?: MountingOptions<Props>['global']['plugins']
mixin?: MountingOptions<Props>['global']['mixins']
extensions?: GlobalMountOptions & {
use?: GlobalMountOptions['plugins']
mixin?: GlobalMountOptions['mixins']
}
} & Partial<StyleOptions>
@@ -53,17 +59,169 @@ Cypress.on('run:start', () => {
const document: Document = cy.state('document')
let el = document.getElementById(ROOT_ID)
if (!el) {
throw Error(`no element found at query #${ROOT_ID}. Please use the mount utils to mount it properly`)
}
el.innerHTML = ''
document.head.innerHTML = initialInnerHtml
})
})
export function mount<Props = any> (
comp: Component<Props>,
options: CyMountOptions<Props> = {},
/**
* the types for mount have been copied directly from the VTU mount
* https://github.com/vuejs/vue-test-utils-next/blob/master/src/mount.ts
*
* If they are updated please copy and pase them again here.
*/
type PublicProps = VNodeProps & AllowedComponentProps & ComponentCustomProps;
// Class component - no props
export function mount<V>(
originalComponent: {
new (...args: any[]): V
registerHooks(keys: string[]): void
},
options?: MountingOptions<any>
): Cypress.Chainable
// Class component - props
export function mount<V, P>(
originalComponent: {
new (...args: any[]): V
props(Props: P): any
registerHooks(keys: string[]): void
},
options?: CyMountOptions<P & PublicProps>
): Cypress.Chainable
// Functional component with emits
export function mount<Props, E extends EmitsOptions = {}>(
originalComponent: FunctionalComponent<Props, E>,
options?: CyMountOptions<Props & PublicProps>
): Cypress.Chainable
// Component declared with defineComponent
export function mount<
PropsOrPropOptions = {},
RawBindings = {},
D = {},
C extends ComputedOptions = ComputedOptions,
M extends MethodOptions = MethodOptions,
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>,
EE extends string = string,
PP = PublicProps,
Props = Readonly<ExtractPropTypes<PropsOrPropOptions>>,
Defaults = ExtractDefaultPropTypes<PropsOrPropOptions>
>(
component: DefineComponent<
PropsOrPropOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
PP,
Props,
Defaults
>,
options?: CyMountOptions<
Partial<Defaults> & Omit<Props & PublicProps, keyof Defaults>,
D
>
): Cypress.Chainable
// Component declared with no props
export function mount<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends Record<string, Function> = {},
E extends EmitsOptions = Record<string, any>,
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
EE extends string = string
>(
componentOptions: ComponentOptionsWithoutProps<
Props,
RawBindings,
D
>,
options?: CyMountOptions<Props & PublicProps, D>
): Cypress.Chainable
// Component declared with { props: [] }
export function mount<
PropNames extends string,
RawBindings,
D,
C extends ComputedOptions = {},
M extends Record<string, Function> = {},
E extends EmitsOptions = Record<string, any>,
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
EE extends string = string,
Props extends Readonly<{ [key in PropNames]?: any }> = Readonly<
{ [key in PropNames]?: any }
>
>(
componentOptions: ComponentOptionsWithArrayProps<
PropNames,
RawBindings,
D,
C,
M,
E,
Mixin,
Extends,
EE,
Props
>,
options?: CyMountOptions<Props & PublicProps, D>
): Cypress.Chainable
// Component declared with { props: { ... } }
export function mount<
// the Readonly constraint allows TS to treat the type of { required: true }
// as constant instead of boolean.
PropsOptions extends Readonly<ComponentPropsOptions>,
RawBindings,
D,
C extends ComputedOptions = {},
M extends Record<string, Function> = {},
E extends EmitsOptions = Record<string, any>,
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
EE extends string = string
>(
componentOptions: ComponentOptionsWithObjectProps<
PropsOptions,
RawBindings,
D,
C,
M,
E,
Mixin,
Extends,
EE
>,
options?: CyMountOptions<ExtractPropTypes<PropsOptions> & PublicProps, D>
): Cypress.Chainable
// implementation
export function mount (
componentOptions: any,
options: CyMountOptions<any> = {},
) {
// TODO: get the real displayName and props from VTU shallowMount
const componentName = DEFAULT_COMP_NAME
const componentName = getComponentDisplayName(componentOptions)
const message = `<${componentName} ... />`
let logInstance: Cypress.Log
@@ -82,12 +240,16 @@ export function mount<Props = any> (
let el = document.getElementById(ROOT_ID)
if (!el) {
throw Error(`no element found at query #${ROOT_ID}. Please use the mount utils to mount it properly`)
}
injectStylesBeforeElement(options, document, el)
// merge the extensions with global
if (options.extensions) {
options.extensions.plugins = [].concat(options.extensions.plugins || [], options.extensions.use || [])
options.extensions.mixins = [].concat(options.extensions.mixins || [], options.extensions.mixin || [])
options.extensions.plugins = ([] as GlobalMountOptions['plugins'])?.concat(options.extensions.plugins || [], options.extensions.use || [])
options.extensions.mixins = ([] as GlobalMountOptions['mixins'])?.concat(options.extensions.mixins || [], options.extensions.mixin || [])
options.global = { ...options.extensions, ...options.global }
}
@@ -98,10 +260,10 @@ export function mount<Props = any> (
el.append(componentNode)
// mount the component using VTU and return the wrapper in Cypress.VueWrapper
const wrapper = VTUmount(comp as any, { attachTo: componentNode, ...options })
const wrapper = VTUmount(componentOptions, { attachTo: componentNode, ...options })
Cypress.vueWrapper = wrapper
Cypress.vue = wrapper.vm
Cypress.vueWrapper = wrapper as VueWrapper<ComponentPublicInstance>
Cypress.vue = wrapper.vm as ComponentPublicInstance
return cy
.wrap(wrapper, { log: false })
@@ -119,17 +281,43 @@ export function mount<Props = any> (
})
}
/**
* Extract the compoennt name from the object passed to mount
* @param componentOptions the compoennt passed to mount
* @returns name of the component
*/
function getComponentDisplayName (componentOptions: any): string {
if (componentOptions.name) {
return componentOptions.name
}
if (componentOptions.__file) {
const filepathSplit = componentOptions.__file.split('/')
const fileName = filepathSplit[filepathSplit.length - 1]
// remove the extension .js, .ts or .vue from the filename to get the name of the component
const baseFileName = fileName.replace(/\.(js|ts|vue)?$/, '')
// if the filename is index, then we can use the direct parent foldername, else use the name itself
return (baseFileName === 'index' ? filepathSplit[filepathSplit.length - 2] : baseFileName)
}
return DEFAULT_COMP_NAME
}
/**
* Helper function for mounting a component quickly in test hooks.
* @example
* import {mountCallback} from '@cypress/vue'
* beforeEach(mountVue(component, options))
*/
export function mountCallback<Props = any> (
component: Component<Props>,
options: CyMountOptions<Props> = {},
): () => void {
return () => mount<Props>(component, options)
export function mountCallback (
component: any,
options: any = {},
): () => Cypress.Chainable {
return () => {
return mount(component, options)
}
}
setupHooks()
+5
View File
@@ -0,0 +1,5 @@
declare module '*.vue' {
import { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
+12 -12
View File
@@ -7,15 +7,15 @@
"lib": [
"es2015",
"dom"
] /* Specify library files to be included in the compilation: */,
"allowJs": true /* Allow javascript files to be compiled. */,
] /* Specify library files to be included in the compilation: */,
"allowJs": true /* Allow javascript files to be compiled. */,
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true /* Generates corresponding '.d.ts' file. */,
"jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true /* Generates corresponding '.d.ts' file. */,
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "dist" /* Redirect output structure to the directory. */,
// "rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"outDir": "dist" /* Redirect output structure to the directory. */,
// "rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
@@ -23,8 +23,8 @@
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": false /* Enable all strict type-checking options. */,
"noImplicitAny": false,
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true,
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
@@ -33,8 +33,8 @@
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
"types": [
"cypress"
] /* Type declaration files to be included in compilation. */,
"cypress", "vue"
] /* Type declaration files to be included in compilation. */,
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
@@ -49,6 +49,6 @@
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
"esModuleInterop": true
},
"include": ["src/**/*.*"],
"exclude": ["src/**/*-spec.*"]
"include": ["src", "cypress/component/**/*.{ts,tsx}", "cypress/component/**/*.vue"],
"exclude": ["**/Translated*.vue"]
}
+1 -1
View File
@@ -28,7 +28,7 @@ module.exports = {
loader: 'vue-loader',
},
{
test: /\.js$/,
test: /\.[j,t]s$/,
loader: 'babel-loader',
},
// this will apply to both plain `.css` files
+767 -176
View File
File diff suppressed because it is too large Load Diff