diff --git a/package.json b/package.json
index 1c9c816..55099c3 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
],
"license": "MIT",
"main": "dist/index.js",
+ "types": "dist",
"publishConfig": {
"registry": "http://registry.npmjs.org/"
},
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index 963ebd7..0000000
--- a/src/index.js
+++ /dev/null
@@ -1,195 +0,0 @@
-///
-
-const Vue = require('vue').default
-const { stripIndent } = require('common-tags')
-
-// mountVue options
-const defaultOptions = ['vue', 'extensions', 'style', 'stylesheets']
-
-function checkMountModeEnabled() {
- // @ts-ignore
- if (Cypress.spec.specType !== 'component') {
- throw new Error(
- `In order to use mount or unmount functions please place the spec in component folder`,
- )
- }
-}
-
-const deleteConstructor = (comp) => delete comp._Ctor
-
-const deleteCachedConstructors = (component) => {
- if (!component.components) {
- return
- }
- Cypress._.values(component.components).forEach(deleteConstructor)
-}
-
-const registerGlobalComponents = (Vue, options) => {
- const globalComponents = Cypress._.get(options, 'extensions.components')
- if (Cypress._.isPlainObject(globalComponents)) {
- Cypress._.forEach(globalComponents, (component, id) => {
- Vue.component(id, component)
- })
- }
-}
-
-const installFilters = (Vue, options) => {
- const filters = Cypress._.get(options, 'extensions.filters')
- if (Cypress._.isPlainObject(filters)) {
- Object.keys(filters).forEach((name) => {
- Vue.filter(name, filters[name])
- })
- }
-}
-
-const installPlugins = (Vue, options) => {
- const plugins =
- Cypress._.get(options, 'extensions.use') ||
- Cypress._.get(options, 'extensions.plugins')
- if (Cypress._.isArray(plugins)) {
- plugins.forEach((plugin) => {
- if (Array.isArray(plugin)) {
- const [aPlugin, options] = plugin
- Vue.use(aPlugin, options)
- } else {
- Vue.use(plugin)
- }
- })
- }
-}
-
-const installMixins = (Vue, options) => {
- const mixins =
- Cypress._.get(options, 'extensions.mixin') ||
- Cypress._.get(options, 'extensions.mixins')
- if (Cypress._.isArray(mixins)) {
- mixins.forEach((mixin) => {
- Vue.mixin(mixin)
- })
- }
-}
-
-const isConstructor = (object) => object && object._compiled
-
-const hasStore = ({ store }) => store && store._vm
-
-const forEachValue = (obj, fn) =>
- Object.keys(obj).forEach((key) => fn(obj[key], key))
-
-const resetStoreVM = (Vue, { store }) => {
- // bind store public getters
- store.getters = {}
- const wrappedGetters = store._wrappedGetters
- const computed = {}
- forEachValue(wrappedGetters, (fn, key) => {
- // use computed to leverage its lazy-caching mechanism
- computed[key] = () => fn(store)
- Object.defineProperty(store.getters, key, {
- get: () => store._vm[key],
- enumerable: true, // for local getters
- })
- })
-
- store._watcherVM = new Vue()
- store._vm = new Vue({
- data: {
- $$state: store._vm._data.$$state,
- },
- computed,
- })
- return store
-}
-
-const mountVue = (component, optionsOrProps = {}) => {
- checkMountModeEnabled()
-
- const options = Cypress._.pick(optionsOrProps, defaultOptions)
- const props = Cypress._.omit(optionsOrProps, defaultOptions)
-
- // display deprecation warnings
- if (options.vue) {
- console.warn(stripIndent`
- [DEPRECATION]: 'vue' option has been deprecated.
- 'node_modules/vue/dis/vue' is always used.
- Please remove it from your 'mountVue' options.`)
- }
-
- // set global Vue instance:
- // 1. convenience for debugging in DevTools
- // 2. some libraries might check for this global
- // appIframe.contentWindow.Vue = Vue
-
- // refresh inner Vue instance of Vuex store
- if (hasStore(component)) {
- component.store = resetStoreVM(Vue, component)
- }
-
- // render function components should be market to be properly initialized
- // https://github.com/bahmutov/cypress-vue-unit-test/issues/313
- if (
- Cypress._.isPlainObject(component) &&
- Cypress._.isFunction(component.render)
- ) {
- component._compiled = true
- }
-
- return cy.window({ log: false }).then((win) => {
- win.Vue = Vue
-
- const document = cy.state('document')
- let el = document.getElementById('cypress-jsdom')
-
- // If the target div doesn't exist, create it
- if (!el) {
- const div = document.createElement('div')
- div.id = 'cypress-jsdom'
- document.body.appendChild(div)
- el = div
- }
-
- if (typeof options.stylesheets === 'string') {
- options.stylesheets = [options.stylesheets]
- }
- if (Array.isArray(options.stylesheets)) {
- console.log('adding stylesheets')
- options.stylesheets.forEach((href) => {
- const link = document.createElement('link')
- link.type = 'text/css'
- link.rel = 'stylesheet'
- link.href = href
- el.append(link)
- })
- }
-
- if (options.style) {
- const style = document.createElement('style')
- style.appendChild(document.createTextNode(options.style))
- el.append(style)
- }
-
- const componentNode = document.createElement('div')
- el.append(componentNode)
-
- // setup Vue instance
- installFilters(Vue, options)
- installMixins(Vue, options)
- installPlugins(Vue, options)
- registerGlobalComponents(Vue, options)
- deleteCachedConstructors(component)
-
- // create root Vue component
- // and make it accessible via Cypress.vue
- if (isConstructor(component)) {
- const Cmp = Vue.extend(component)
- Cypress.vue = new Cmp(props).$mount(componentNode)
- } else {
- Cypress.vue = new Vue(component).$mount(componentNode)
- }
- })
-}
-
-// the double function allows mounting a component quickly
-// beforeEach(mountVue(component, options))
-const mountCallback = (...args) => () => mountVue(...args)
-
-module.exports = { mount: mountVue, mountCallback }
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..7bdad12
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,418 @@
+///
+
+import Vue from 'vue'
+const { stripIndent } = require('common-tags')
+
+// mountVue options
+const defaultOptions: (keyof MountOptions)[] = [
+ 'vue',
+ 'extensions',
+ 'style',
+ 'stylesheets',
+]
+
+function checkMountModeEnabled() {
+ // @ts-ignore
+ if (Cypress.spec.specType !== 'component') {
+ throw new Error(
+ `In order to use mount or unmount functions please place the spec in component folder`,
+ )
+ }
+}
+
+const deleteConstructor = (comp) => delete comp._Ctor
+
+const deleteCachedConstructors = (component) => {
+ if (!component.components) {
+ return
+ }
+ Cypress._.values(component.components).forEach(deleteConstructor)
+}
+
+const registerGlobalComponents = (Vue, options) => {
+ const globalComponents = Cypress._.get(options, 'extensions.components')
+ if (Cypress._.isPlainObject(globalComponents)) {
+ Cypress._.forEach(globalComponents, (component, id) => {
+ Vue.component(id, component)
+ })
+ }
+}
+
+const installFilters = (Vue, options) => {
+ const filters: VueFilters | undefined = Cypress._.get(
+ options,
+ 'extensions.filters',
+ )
+ if (Cypress._.isPlainObject(filters)) {
+ Object.keys(filters).forEach((name) => {
+ Vue.filter(name, filters[name])
+ })
+ }
+}
+
+const installPlugins = (Vue, options) => {
+ const plugins: VuePlugins =
+ Cypress._.get(options, 'extensions.use') ||
+ Cypress._.get(options, 'extensions.plugins')
+
+ if (Cypress._.isArray(plugins)) {
+ plugins.forEach((plugin) => {
+ if (Array.isArray(plugin)) {
+ const [aPlugin, options] = plugin
+ Vue.use(aPlugin, options)
+ } else {
+ Vue.use(plugin)
+ }
+ })
+ }
+}
+
+const installMixins = (Vue, options) => {
+ const mixins =
+ Cypress._.get(options, 'extensions.mixin') ||
+ Cypress._.get(options, 'extensions.mixins')
+ if (Cypress._.isArray(mixins)) {
+ mixins.forEach((mixin) => {
+ Vue.mixin(mixin)
+ })
+ }
+}
+
+const isConstructor = (object) => object && object._compiled
+
+// @ts-ignore
+const hasStore = ({ store }: { store: object }) => store && store._vm
+
+const forEachValue = (obj: object, fn: Function) =>
+ Object.keys(obj).forEach((key) => fn(obj[key], key))
+
+const resetStoreVM = (Vue, { store }) => {
+ // bind store public getters
+ store.getters = {}
+ const wrappedGetters = store._wrappedGetters
+ const computed = {}
+ forEachValue(wrappedGetters, (fn, key) => {
+ // use computed to leverage its lazy-caching mechanism
+ computed[key] = () => fn(store)
+ Object.defineProperty(store.getters, key, {
+ get: () => store._vm[key],
+ enumerable: true, // for local getters
+ })
+ })
+
+ store._watcherVM = new Vue()
+ store._vm = new Vue({
+ data: {
+ $$state: store._vm._data.$$state,
+ },
+ computed,
+ })
+ return store
+}
+
+/**
+ * Type for component passed to "mount"
+ *
+ * @interface VueComponent
+ * @example
+ * import Hello from './Hello.vue'
+ * ^^^^^ this type
+ * mount(Hello)
+ */
+type VueComponent = Vue.ComponentOptions
+
+/**
+ * Options to pass to the component when creating it, like
+ * props.
+ *
+ * @interface ComponentOptions
+ */
+interface ComponentOptions {}
+
+// local placeholder types
+type VueLocalComponents = object
+
+type VueFilters = {
+ [key: string]: Function
+}
+
+type VueMixin = unknown
+type VueMixins = VueMixin | VueMixin[]
+
+/**
+ * A Vue plugin to register,
+ * or a plugin + its options pair inside an array
+ */
+type VuePlugin = unknown | [unknown, unknown]
+/**
+ * A single Vue plugin or a list of plugins to register
+ */
+type VuePlugins = VuePlugin | VuePlugin[]
+
+/**
+ * Additional Vue services to register while mounting the component, like
+ * local components, plugins, etc.
+ *
+ * @interface MountOptionsExtensions
+ * @see https://github.com/bahmutov/cypress-vue-unit-test#examples
+ */
+interface MountOptionsExtensions {
+ /**
+ * Extra local components
+ *
+ * @memberof MountOptionsExtensions
+ * @see https://github.com/bahmutov/cypress-vue-unit-test#examples
+ * @example
+ * import Hello from './Hello.vue'
+ * // imagine Hello needs AppComponent
+ * // that it uses in its template like
+ * // during testing we can replace it with a mock component
+ * const appComponent = ...
+ * const components = {
+ * 'app-component': appComponent
+ * },
+ * mount(Hello, { extensions: { components }})
+ */
+ components?: VueLocalComponents
+
+ /**
+ * Optional Vue filters to install while mounting the component
+ *
+ * @memberof MountOptionsExtensions
+ * @see https://github.com/bahmutov/cypress-vue-unit-test#examples
+ * @example
+ * const filters = {
+ * reverse: (s) => s.split('').reverse().join(''),
+ * }
+ * mount(Hello, { extensions: { filters }})
+ */
+ filters?: VueFilters
+
+ /**
+ * Optional Vue mixin(s) to install when mounting the component
+ *
+ * @memberof MountOptionsExtensions
+ * @alias mixins
+ * @see https://github.com/bahmutov/cypress-vue-unit-test#examples
+ */
+ mixin?: VueMixins
+
+ /**
+ * Optional Vue mixin(s) to install when mounting the component
+ *
+ * @memberof MountOptionsExtensions
+ * @alias mixin
+ * @see https://github.com/bahmutov/cypress-vue-unit-test#examples
+ */
+ mixins?: VueMixins
+
+ /**
+ * A single plugin or multiple plugins.
+ *
+ * @see https://github.com/bahmutov/cypress-vue-unit-test#examples
+ * @alias plugins
+ * @memberof MountOptionsExtensions
+ */
+ use?: VuePlugins
+
+ /**
+ * A single plugin or multiple plugins.
+ *
+ * @see https://github.com/bahmutov/cypress-vue-unit-test#examples
+ * @alias use
+ * @memberof MountOptionsExtensions
+ */
+ plugins?: VuePlugins
+}
+
+/**
+ * Options controlling how the component is going to be mounted,
+ * including global Vue plugins and extensions.
+ *
+ * @interface MountOptions
+ */
+interface MountOptions {
+ /**
+ * Vue instance to use.
+ *
+ * @deprecated
+ * @memberof MountOptions
+ */
+ vue: unknown
+
+ /**
+ * CSS style string to inject when mounting the component
+ *
+ * @memberof MountOptions
+ * @example
+ * const style = `
+ * .todo.done {
+ * text-decoration: line-through;
+ * color: gray;
+ * }`
+ * mount(Todo, { style })
+ */
+ style: string
+
+ /**
+ * Stylesheet(s) urls to inject as `` elements when
+ * mounting the component
+ *
+ * @memberof MountOptions
+ * @example
+ * const template = '...'
+ * const stylesheets = '/node_modules/tailwindcss/dist/tailwind.min.css'
+ * mount({ template }, { stylesheets })
+ *
+ * @example
+ * const template = '...'
+ * const stylesheets = ['https://cdn.../lib.css', 'https://lib2.css']
+ * mount({ template }, { stylesheets })
+ */
+ stylesheets: string | string[]
+
+ /**
+ * Extra Vue plugins, mixins, local components to register while
+ * mounting this component
+ *
+ * @memberof MountOptions
+ * @see https://github.com/bahmutov/cypress-vue-unit-test#examples
+ */
+ extensions: MountOptionsExtensions
+}
+
+/**
+ * Utility type for union of options passed to "mount(..., options)"
+ */
+type MountOptionsArgument = Partial
+
+/**
+ * Mounts a Vue component inside Cypress browser.
+ * @param {object} component imported from Vue file
+ * @example
+ * import Greeting from './Greeting.vue'
+ * import { mount } from 'cypress-vue-unit-test'
+ * it('works', () => {
+ * // pass props, additional extensions, etc
+ * mount(Greeting, { ... })
+ * // use any Cypress command to test the component
+ * cy.get('#greeting').should('be.visible')
+ * })
+ */
+export const mount = (
+ component: VueComponent,
+ optionsOrProps: MountOptionsArgument = {},
+) => {
+ checkMountModeEnabled()
+
+ const options: Partial = Cypress._.pick(
+ optionsOrProps,
+ defaultOptions,
+ )
+ const props: Partial = Cypress._.omit(
+ optionsOrProps,
+ defaultOptions,
+ )
+
+ // display deprecation warnings
+ if (options.vue) {
+ console.warn(stripIndent`
+ [DEPRECATION]: 'vue' option has been deprecated.
+ 'node_modules/vue/dis/vue' is always used.
+ Please remove it from your 'mountVue' options.`)
+ }
+
+ // set global Vue instance:
+ // 1. convenience for debugging in DevTools
+ // 2. some libraries might check for this global
+ // appIframe.contentWindow.Vue = Vue
+
+ // refresh inner Vue instance of Vuex store
+ // @ts-ignore
+ if (hasStore(component)) {
+ // @ts-ignore
+ component.store = resetStoreVM(Vue, component)
+ }
+
+ // render function components should be market to be properly initialized
+ // https://github.com/bahmutov/cypress-vue-unit-test/issues/313
+ if (
+ Cypress._.isPlainObject(component) &&
+ Cypress._.isFunction(component.render)
+ ) {
+ // @ts-ignore
+ component._compiled = true
+ }
+
+ return cy
+ .window({
+ log: false,
+ })
+ .then((win) => {
+ // @ts-ignore
+ win.Vue = Vue
+
+ // @ts-ignore
+ const document: Document = cy.state('document')
+ let el = document.getElementById('cypress-jsdom')
+
+ // If the target div doesn't exist, create it
+ if (!el) {
+ const div = document.createElement('div')
+ div.id = 'cypress-jsdom'
+ document.body.appendChild(div)
+ el = div
+ }
+
+ if (typeof options.stylesheets === 'string') {
+ options.stylesheets = [options.stylesheets]
+ }
+ if (Array.isArray(options.stylesheets)) {
+ options.stylesheets.forEach((href) => {
+ const link = document.createElement('link')
+ link.type = 'text/css'
+ link.rel = 'stylesheet'
+ link.href = href
+ el.append(link)
+ })
+ }
+
+ if (options.style) {
+ const style = document.createElement('style')
+ style.appendChild(document.createTextNode(options.style))
+ el.append(style)
+ }
+
+ const componentNode = document.createElement('div')
+ el.append(componentNode)
+
+ // setup Vue instance
+ installFilters(Vue, options)
+ installMixins(Vue, options)
+ installPlugins(Vue, options)
+ registerGlobalComponents(Vue, options)
+ deleteCachedConstructors(component)
+
+ // create root Vue component
+ // and make it accessible via Cypress.vue
+ if (isConstructor(component)) {
+ const Cmp = Vue.extend(component)
+ // @ts-ignore
+ Cypress.vue = new Cmp(props).$mount(componentNode)
+ } else {
+ // @ts-ignore
+ Cypress.vue = new Vue(component).$mount(componentNode)
+ }
+ })
+}
+
+/**
+ * Helper function for mounting a component quickly in test hooks.
+ * @example
+ * import {mountCallback} from 'cypress-vue-unit-test'
+ * beforeEach(mountVue(component, options))
+ */
+export const mountCallback = (
+ component: VueComponent,
+ options?: MountOptionsArgument,
+) => () => mount(component, options)
diff --git a/src/preprocessor/webpack.js b/src/preprocessor/webpack.js
index 04e986e..4f09c16 100644
--- a/src/preprocessor/webpack.js
+++ b/src/preprocessor/webpack.js
@@ -1,5 +1,5 @@
-const webpack = require('webpack')
-const util = require('util')
+import webpack from 'webpack'
+import util from 'util'
// Cypress webpack bundler adaptor
// https://github.com/cypress-io/cypress-webpack-preprocessor
@@ -54,7 +54,7 @@ function compileTemplate(options = {}) {
/**
* Warning: modifies the input object
* @param {Cypress.ConfigOptions} config
- * @param {import('webpack/lib/Compiler').WebpackOptions} options
+ * @param {WebpackOptions} options
*/
function insertBabelLoader(config, options) {
const skipCodeCoverage = config && config.env && config.env.coverage === false
diff --git a/tsconfig.json b/tsconfig.json
index 19d647e..edf0d1e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -11,7 +11,7 @@
"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. */
+ "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. */,
@@ -23,7 +23,8 @@
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
- "strict": true /* Enable all strict type-checking options. */,
+ "strict": false /* Enable all strict type-checking options. */,
+ "noImplicitAny": false,
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */