diff --git a/npm/vue/README.md b/npm/vue/README.md index 082e8aa7975f..8fea1bb743e1 100644 --- a/npm/vue/README.md +++ b/npm/vue/README.md @@ -19,6 +19,7 @@ This package allows you to use the [Cypress](https://www.cypress.io/) test runne It uses [Vue Test Utils](https://github.com/vuejs/vue-test-utils) under the hood. This is more of a replacement for node-based testing than it is replacing Vue Test Utils and its API. Instead of running your tests in node (using Jest or Mocha), the Cypress Component Testing Library runs each component in the **real browser** with full power of the Cypress Framework: [live GUI, full API, screen recording, CI support, cross-platform](https://www.cypress.io/features/). One benefit to using Cypress instead of a node-based runner is that limitations of Vue Test Utils in Node (e.g. manually awaiting Vue's internal event loop) are hidden from the user due to Cypress's retry-ability logic. - If you like using `@testing-library/vue`, you can use `@testing-library/cypress` for the same `findBy`, `queryBy` commands, see one of the examples in the list below +- If you need to access the underlying Vue Test Utils API, you can do so by importing it: `import { VueTestUtils } from 'cypress/vue'`. ### How is this different from @cypress/vue2? Cypress packages the current version of Vue under @cypress/vue, and older versions under separate package names. Use [@cypress/vue2](cypress-vue2-npm-url) if you're still using vue@2, and this package if you're on vue@3. diff --git a/npm/vue/cypress/component/test-utils-api/TestUtilsApi.cy.ts b/npm/vue/cypress/component/test-utils-api/TestUtilsApi.cy.ts new file mode 100644 index 000000000000..048aa703ab15 --- /dev/null +++ b/npm/vue/cypress/component/test-utils-api/TestUtilsApi.cy.ts @@ -0,0 +1,23 @@ +import { VueTestUtils, mount } from 'cypress/vue' +import { h } from 'vue' +import TestUtilsApi from './TestUtilsApi.vue' + +const greeting = 'This is a globally registered component!' + +describe('VueTestUtils API', () => { + before(() => { + VueTestUtils.config.global.components = { + 'globally-registered-component': { + setup () { + return () => h('h1', greeting) + }, + }, + } + }) + + it('gains access to underlying Vue Test Utils library', () => { + mount(TestUtilsApi) + + cy.get('h1').contains(greeting) + }) +}) diff --git a/npm/vue/cypress/component/test-utils-api/TestUtilsApi.vue b/npm/vue/cypress/component/test-utils-api/TestUtilsApi.vue new file mode 100644 index 000000000000..a4de3a785559 --- /dev/null +++ b/npm/vue/cypress/component/test-utils-api/TestUtilsApi.vue @@ -0,0 +1,3 @@ + diff --git a/npm/vue/inline-types.ts b/npm/vue/inline-types.ts new file mode 100644 index 000000000000..f7ddbd0c6155 --- /dev/null +++ b/npm/vue/inline-types.ts @@ -0,0 +1,69 @@ +import fs from 'fs-extra' +import globby from 'globby' +import path from 'path' + +// We depend on @vue/test-utils and inline this library in the +// @cypress/vue bundle. +// Unfortunately, although rollup can inline libraries like this, +// TypeScript doesn't do the same for d.ts files - it mains imports +// to the original library, which we do not actually ship in the binary. + +// This script copies the `d.ts` files into the `dist` directory +// then modifies the `index.d.ts` to reference the copied type definitions +// instead of the ones from the local node_modules directory that we don't +// actually bundle in the binary. + +function rewriteSourceForInlineTypes (src: string) { + return src + // Need to modify these lines: + // import type { MountingOptions, VueWrapper } from '@vue/test-utils'; + // import * as _VueTestUtils from '@vue/test-utils'; + // to + // import type { MountingOptions, VueWrapper } from './@vue/test-utils'; + // import * as _VueTestUtils from './@vue/test-utils'; + .replaceAll(`'@vue/test-utils';`, `'./@vue/test-utils';`) + + // Need to modify this line: + // config: import("@vue/test-utils/config).GlobalConfigOptions; + // to + // config: import("./@vue/test-utils/config").GlobalConfigOptions; + .replaceAll(`@vue/test-utils/dist/config`, `./@vue/test-utils/config`) +} + +async function inlineTestUtilsTypes () { + console.log('[npm/vue] Inline type definitions for @vue/test-utils and rewriting source') // eslint-disable-line no-console + + // get the directory with the type definitions we want to inline + const vtuDir = path.dirname(require.resolve('@vue/test-utils')) + + // grab the type definitions + const typeDefs = await globby('**/*.d.ts', { cwd: vtuDir }) + + // make a directory for them in npm/vue/dist + const typeDefDir = path.join(__dirname, 'dist', '@vue', 'test-utils') + + await fs.mkdir(typeDefDir, { recursive: true }) + + // copy the type definitions + await Promise.all( + typeDefs.map((tds) => { + const from = path.join(vtuDir, tds) + const to = path.join(typeDefDir, tds) + + return fs.copy(from, to, { recursive: true }) + }), + ) + + const cypressVueMainTypeDef = path.join(__dirname, 'dist', 'index.d.ts') + + // modify index.d.ts to reference relative type definitions instead of ones from + // node_modules + const updateWithRelativeImports = rewriteSourceForInlineTypes( + await fs.readFile(cypressVueMainTypeDef, 'utf-8'), + ) + + // rewrite index.d.ts, now modified to point at local type definitions. + await fs.writeFile(cypressVueMainTypeDef, updateWithRelativeImports) +} + +inlineTestUtilsTypes() diff --git a/npm/vue/package.json b/npm/vue/package.json index 44cbaa5ff2fd..19460f9eac27 100644 --- a/npm/vue/package.json +++ b/npm/vue/package.json @@ -8,14 +8,12 @@ "cy:open": "node ../../scripts/cypress.js open --component --project ${PWD}", "cy:run": "node ../../scripts/cypress.js run --component --project ${PWD}", "build": "rimraf dist && rollup -c rollup.config.js", - "postbuild": "node ../../scripts/sync-exported-npm-with-cli.js", - "typecheck": "vue-tsc --noEmit", + "postbuild": "node --require @packages/ts/register ./inline-types.ts && node ../../scripts/sync-exported-npm-with-cli.js", + "typecheck": "yarn tsd && vue-tsc --noEmit", "test": "yarn cy:run", + "tsd": "yarn build && yarn tsc -p test-tsd/tsconfig.tsd.json", "watch": "yarn build --watch --watch.exclude ./dist/**/*" }, - "dependencies": { - "@vue/test-utils": "2.0.0-rc.19" - }, "devDependencies": { "@cypress/code-coverage": "3.8.1", "@cypress/mount-utils": "0.0.0-development", @@ -23,9 +21,11 @@ "@rollup/plugin-node-resolve": "^11.1.1", "@vitejs/plugin-vue": "2.3.1", "@vue/compiler-sfc": "3.2.31", + "@vue/test-utils": "2.0.0-rc.19", "axios": "0.21.2", "cypress": "0.0.0-development", "debug": "^4.3.2", + "globby": "^11.0.1", "rollup": "^2.38.5", "rollup-plugin-istanbul": "2.0.1", "rollup-plugin-typescript2": "^0.29.0", @@ -71,6 +71,10 @@ { "name": "Amir Rustamzadeh", "social": "@amirrustam" + }, + { + "name": "Lachlan Miller", + "social": "@Lachlan19900" } ], "module": "dist/cypress-vue.esm-bundler.js", diff --git a/npm/vue/src/index.ts b/npm/vue/src/index.ts index 8e92a9622e54..e00871f66888 100644 --- a/npm/vue/src/index.ts +++ b/npm/vue/src/index.ts @@ -1,10 +1,24 @@ /* eslint-disable no-redeclare */ /// -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 type { + ComponentPublicInstance, + VNodeProps, + AllowedComponentProps, + ComponentCustomProps, + ExtractPropTypes, + ExtractDefaultPropTypes, + DefineComponent, + FunctionalComponent, + ComputedOptions, + MethodOptions, + ComponentOptionsMixin, + EmitsOptions, + ComponentOptionsWithObjectProps, + ComponentPropsOptions, + ComponentOptionsWithArrayProps, + ComponentOptionsWithoutProps, +} from 'vue' +import type { MountingOptions, VueWrapper } from '@vue/test-utils' import { injectStylesBeforeElement, StyleOptions, @@ -12,6 +26,22 @@ import { setupHooks, } from '@cypress/mount-utils' +import * as _VueTestUtils from '@vue/test-utils' + +const { + // We do not expose the `mount` from VueTestUtils, instead, we wrap it and expose a + // Cypress-compatible `mount` API. + mount: VTUmount, + // We do not expose shallowMount. It doesn't make much sense in the context of Cypress. + // It might be useful for people who like to migrate some Test Utils tests to Cypress, + // so if we decide it is useful to expose, just remove the next line, and it will be + // available on the `VueTestUtils` import. + shallowMount, + ...VueTestUtils +} = _VueTestUtils + +export { VueTestUtils } + const DEFAULT_COMP_NAME = 'unknown' type GlobalMountOptions = Required>['global'] diff --git a/npm/vue/test-tsd/index.d.ts b/npm/vue/test-tsd/index.d.ts new file mode 100644 index 000000000000..61aeaea7a3b2 --- /dev/null +++ b/npm/vue/test-tsd/index.d.ts @@ -0,0 +1,4 @@ +export function expectType(value: T): void +export function expectError(value: T): void +export function expectAssignable(value: T2): void + diff --git a/npm/vue/test-tsd/mount-test.ts b/npm/vue/test-tsd/mount-test.ts new file mode 100644 index 000000000000..d787014f2203 --- /dev/null +++ b/npm/vue/test-tsd/mount-test.ts @@ -0,0 +1,17 @@ +import { expectError, expectType } from './index' +import { mount, VueTestUtils } from '../dist' +import * as VTU from '@vue/test-utils' +import { defineComponent } from 'vue' + +const App = defineComponent({ + template: `
`, +}) + +// Returns Chainable - not the `mount` function from @vue/test-utils +expectType( + mount(App), +) + +// Rewritten relative types match those copied from node_modules +// see npm/vue/inline-types.ts for more info. +expectType(VTU['config']['global']) diff --git a/npm/vue/test-tsd/tsconfig.tsd.json b/npm/vue/test-tsd/tsconfig.tsd.json new file mode 100644 index 000000000000..483139f4f7c4 --- /dev/null +++ b/npm/vue/test-tsd/tsconfig.tsd.json @@ -0,0 +1,16 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "noEmit": true, + "skipLibCheck": true, + "experimentalDecorators": true, + "strictNullChecks": false + }, + "exclude": [ + "../src" + ], + "include": [ + "../dist", + "../test-dts" + ] +}