├── .gitignore
├── README.md
├── babel.config.js
├── cypress.json
├── cypress
├── fixtures
│ └── example.json
├── plugins
│ └── index.js
└── support
│ └── index.js
├── index.html
├── jest.config.js
├── package.json
├── public
└── favicon.ico
├── src
├── App.cy.js
├── App.vue
├── assets
│ └── logo.png
├── components
│ ├── BaseButton.cy.js
│ ├── BaseButton.vue
│ ├── ExampleBaseButton.vue
│ ├── FoodCard.cy.js
│ ├── FoodCard.vue
│ ├── HelloWorld.cy.js
│ ├── HelloWorld.vue
│ ├── PerksModal.cy.js
│ ├── PerksModal.test.js
│ ├── PerksModal.vue
│ └── __snapshots__
│ │ └── PerksModal.test.js.snap
├── index.css
└── main.js
├── vite.config.js
├── windi.config.js
├── yarn-error.log
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cypress 💖 Vite
2 |
3 | Hello! This repo is for my talk at [VueConfUS on April 14th](https://us.vuejs.org/). Tickets are free, so if you like this repo, you'll probably want to watch my talk.
4 |
5 | It is _very_ likely that some of the tests in this repo will break. Its goal is to give you a bundler setup and dependencies that work.
6 |
7 | The latest guides are here:
8 |
9 | [https://on.cypress.io/component-testing](https://on.cypress.io/component-testing)
10 |
11 | ### Getting Started
12 |
13 | #### Install dependencies
14 |
15 | ```bash
16 | yarn
17 | ```
18 |
19 | #### Open component testing
20 |
21 | ```bash
22 | yarn cy
23 | ```
24 |
25 | #### Make some new files
26 |
27 | **MyNewComponent.vue**
28 |
29 | ```vue
30 |
31 | Hello world! 👋
32 |
33 | ```
34 |
35 | **MyNewComponent.cy.js**
36 |
37 | ```js
38 | import { mount } from '@cypress/vue'
39 | import MyNewComponent from './MyNewComponent.vue' // keep the .vue, Vite needs it
40 |
41 | describe('MyNewComponent', () => {
42 | it('renders', () => {
43 | mount(MyNewComponent)
44 | .get('h1')
45 | .should('have.text', 'Hello world')
46 | })
47 | })
48 | ```
49 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@babel/env'
4 | ],
5 | plugins: [["@babel/transform-runtime", {
6 | "regenerator": true
7 | }]]
8 | }
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "testFiles": "**/*cy.*",
3 | "componentFolder": "src"
4 | }
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
6 |
--------------------------------------------------------------------------------
/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | const { startDevServer } = require('@cypress/vite-dev-server')
2 | const WindiCSS = require('vite-plugin-windicss').default
3 |
4 |
5 | module.exports = (on, config) => {
6 | const viteConfig = {
7 | plugins: [
8 | WindiCSS()
9 | ]
10 | }
11 |
12 | viteConfig.esbuild = viteConfig.esbuild || {}
13 | viteConfig.esbuild.jsxFactory = 'h'
14 | viteConfig.esbuild.jsxFragment = 'Fragment'
15 | viteConfig.logLevel = 'error'
16 | viteConfig.resolve = {
17 | alias: {
18 | 'vue': 'vue/dist/vue.esm-bundler.js'
19 | }
20 | }
21 |
22 | on('dev-server:start', (options) => {
23 | return startDevServer({ options, viteConfig })
24 | })
25 | return config
26 | }
27 |
--------------------------------------------------------------------------------
/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | import 'virtual:windi.css'
2 | import 'virtual:windi-devtools'
3 | import '../../src/index.css'
4 |
5 | beforeEach(() => {
6 | cy.viewport(600, 600, { log: false })
7 | })
8 |
9 | Cypress.Commands.overwrite('submit', function (original, $form) {
10 | const spy = cy.spy(() => {
11 | })
12 | if ($form) {
13 | $form[0].submit = spy
14 | }
15 |
16 | return cy.wrap(original(...Array.from(arguments).slice(1)), { log: false }).wrap(spy, { log: false })
17 | })
18 |
19 | Cypress.Commands.add('vue', () => {
20 | return cy.wrap(Cypress.vueWrapper)
21 | })
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
25 | {{ availability }} 26 |
27 | 28 | 29 | 35 | ⭐ 36 | 37 | ️️ 38 |5 | Vite Documentation 8 | | 9 | Vue 3 Documentation 10 |
11 | 12 | 13 |
14 | Edit
15 | components/HelloWorld.vue
to test hot module replacement.
16 |
15 | {{ formData }} 16 |
17 | `, 18 | data() { 19 | return { 20 | visible: false, 21 | formData: {} 22 | } 23 | }, 24 | methods: { 25 | onSave(newFormData) { 26 | this.formData = newFormData 27 | } 28 | }, 29 | components: { PerksModal } 30 | } 31 | 32 | it('Playground', () => { 33 | mount(wrapper, { 34 | global: { 35 | stubs: { 36 | transition: false 37 | } 38 | } 39 | }) 40 | }) 41 | 42 | it('does not save the form values unless the submit button is clicked', () => { 43 | mount(PerksModal, { props: { visible: true } }) 44 | .get('[data-testid=email]') 45 | .type(data.email) 46 | .get('.x-button') 47 | .click() 48 | .vue() 49 | .then((wrapper) => { 50 | expect(wrapper.emitted('save')).to.be.undefined 51 | }) 52 | }) 53 | 54 | it('is not visible when visible is set to false', () => { 55 | mount(PerksModal, { props: { visible: false } }) 56 | .get('form') 57 | .should('not.be.visible') 58 | .get('.overlay') 59 | .should('not.be.visible') 60 | }) 61 | 62 | it('is visible when visible is set to true', () => { 63 | mount(PerksModal, { props: { visible: true } }) 64 | .get('form') 65 | .should('be.visible') 66 | .get('.overlay') 67 | .should('be.visible') 68 | }) 69 | 70 | it('emits a save event with the form data', () => { 71 | mount(PerksModal, { props: { visible: true } }) 72 | .get('[data-testid=email]') 73 | .type(data.email) 74 | .get('[data-testid=zip-code]') 75 | .type(data.zipCode) 76 | .get('button[type=submit]') 77 | .click() 78 | .vue() 79 | .then((wrapper) => { 80 | const { email, zipCode } = wrapper.emitted('save')[0][0] 81 | expect(email).to.equal(data.email) 82 | expect(zipCode).to.equal(data.zipCode) 83 | 84 | expect(wrapper.emitted('submit')).to.have.length(1) 85 | }) 86 | }) 87 | 88 | it('can be closed with the x button', () => { 89 | 90 | }) 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | // it.only('Playground', () => { 121 | // const wrapper = { 122 | // template: ` 123 | //Data:
194 | //{{ formData }}
195 | // `, 196 | 197 | // data() { 198 | // return { 199 | // visible: false, 200 | // formData: {} 201 | // } 202 | // }, 203 | // methods: { 204 | // onSubmit(newFormData) { 205 | // debugger; 206 | // this.formData = newFormData 207 | // } 208 | // }, 209 | // components: { PerksModal } 210 | // } 211 | 212 | // it('Playground', () => { 213 | // mount(PerksModal, { props: { visible: true }}) 214 | // }) 215 | 216 | // it('closes the modal when the x button is clicked', () => { 217 | // mount(wrapper).get('.modal-open') 218 | // .click() 219 | // .get('.x-button').click() 220 | // .get('form').should('not.be.visible') 221 | // }) 222 | 223 | 224 | // it('the save event emits the form data entered', () => { 225 | // mount(PerksModal, { props: { visible: true } }) 226 | // .get('[data-testid=email]') 227 | // .type(formData.email) 228 | // .get('[data-testid=zip-code]') 229 | // .type(formData.zipCode) 230 | // .get('button[type=submit]').click() 231 | // .vue() 232 | // .then((wrapper) => { 233 | // const value = wrapper.emitted('save')[0][0] 234 | // expect(value.email).to.equal(formData.email) 235 | // expect(value.zipCode).to.equal(formData.zipCode) 236 | 237 | // expect(wrapper.emitted('close')).to.have.length(1) 238 | // }) 239 | // }) 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | // it('renders the modal when visible is set to true', () => { 254 | // mount(PerksModal, { props: { visible: true } }) 255 | // .get('form') 256 | // .should('be.visible') 257 | // }) 258 | 259 | // it('should not render the modal when visible is set to false', () => { 260 | // mount(PerksModal, { props: { visible: false } }) 261 | // .get('form') 262 | // .should('not.be.visible') 263 | // }) 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | // const fillForm = (options, component = PerksModal) => { 292 | // let { visible, email, zipCode } = options 293 | // visible = visible || true 294 | // email = email || '' 295 | // zipCode = zipCode || '' 296 | 297 | // return mount(component, { props: { visible } }) 298 | // .get('[data-testid=email]') 299 | // .type(email || ' ') 300 | // .get('[data-testid=zip-code]') 301 | // .type(zipCode || ' ') 302 | // .get('form') 303 | // } 304 | 305 | // const checkValidity = function (elements) { 306 | // return Array.from(elements).map(el => el.checkValidity()) 307 | // } 308 | 309 | 310 | // xdescribe('PerksModal', () => { 311 | // it('renders properly', () => { 312 | // mount(PerksModal, { props: { visible: true } }).get('form').should('be.visible') 313 | // }) 314 | 315 | // describe('valid submission', () => { 316 | // it('can be filled out', () => { 317 | // fillForm(formData) 318 | // }) 319 | 320 | // it('doesnt navigate when the submit button is pressed', () => { 321 | // fillForm(formData) 322 | // .submit() 323 | // .should('not.have.been.called') 324 | // }) 325 | 326 | // it('emits the form data on submit', () => { 327 | // const stub = cy.stub().as('submit') 328 | // mount(PerksModal, { methods: { submit: stub } }) 329 | // fillForm(formData) 330 | // .get('button[type="submit"]') 331 | // .click() 332 | // .vue() 333 | // .should((wrapper) => { 334 | // const emitted = wrapper.emitted('submit') 335 | // expect(emitted).to.exist.and.have.length(2) 336 | // expect(emitted[0][0].email).to.include(formData.email) 337 | // expect(emitted[0][0].zipCode).to.include(formData.zipCode) 338 | // }) 339 | // }) 340 | // }) 341 | 342 | // describe('invalid submission', () => { 343 | // it('prevents submission with invalid email', () => { 344 | // const invalidEmail = 'vue-conf-us-is-awesome.com' 345 | 346 | // fillForm({ email: invalidEmail }) 347 | // .get('[data-testid=email]') 348 | // .then(checkValidity).should('contain', false) 349 | // .get('input').clear() 350 | // .then(() => { 351 | // return fillForm({ zipCode: '100000000' }) 352 | // .get('[data-testid=zip-code]') 353 | // .checkValidity().should('contain', false) 354 | // }) 355 | // }) 356 | // }) 357 | 358 | // describe('visibility', () => { 359 | // it('is controlled by the parent', () => { 360 | // mount(PerksModal, { props: { visible: false } }) 361 | // .vue() 362 | // .get('form') 363 | // .should('not.exist') 364 | // .vue() 365 | // .then((wrapper) => { 366 | // expect(wrapper.find().exists()).to.be.false 367 | // wrapper.setProps({ visible: true }) 368 | // }) 369 | // .get('form').should('exist').and('be.visible') 370 | // }) 371 | // }) 372 | // }) -------------------------------------------------------------------------------- /src/components/PerksModal.test.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import PerksModal from './PerksModal.vue' 3 | 4 | it('mounts', async () => { 5 | const wrapper = await mount(PerksModal, { props: { visible: true }}) 6 | expect(wrapper.find('form').isVisible()).toBeTruthy() 7 | }) 8 | 9 | it('mounts', async () => { 10 | const wrapper = await mount(PerksModal, { props: { visible: false }}) 11 | expect(wrapper.find('form').isVisible()).toBeFalsy() 12 | expect(wrapper.element.innerHTML).toMatchSnapshot() 13 | }) 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | // it('can fill out the form', async () => { 44 | // // const email = 'jessica@cypress.io' 45 | // // const zipCode = 10036 46 | // const wrapper = await mount(PerksModal) 47 | // // await wrapper.find('[data-testid=email]').setValue(email) 48 | // // await wrapper.find('[data-testid=zip-code]').setValue(zipCode) 49 | // // wrapper.find('form').trigger('submit') 50 | 51 | // // const emitted = wrapper.emitted('submit') 52 | // // expect(emitted).toHaveLength(2) 53 | // // expect(emitted[0][0].email).toBe(email) 54 | // // expect(emitted[0][0].zipCode).toBe("" + zipCode) 55 | 56 | // // expect(wrapper.element.innerHTML).toMatchSnapshot() 57 | // }) 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | // // await wrapper.find('[data-testid=email]').setValue(email) 74 | // // await wrapper.find('[data-testid=zip-code]').setValue(zipCode) 75 | 76 | // // wrapper.trigger('submit') 77 | 78 | // // await wrapper.find('form').trigger('submit') 79 | 80 | 81 | // // }) 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | // // const formData = { 103 | // // email: 'jessica@cypress.io', 104 | // // zipCode: 32084 105 | // // } 106 | 107 | // // const fillForm = async (options, component = PerksModal) => { 108 | // // const { email, zipCode } = options 109 | // // const wrapper = await mount(component, { props: { visible: true }}) 110 | // // await wrapper.find('[data-testid=email]').trigger('input', email) 111 | // // await wrapper.find('[data-testid=zip-code]').trigger('input', zipCode) 112 | // // return wrapper 113 | // // } 114 | 115 | // // describe('PerksModal', () => { 116 | // // it('renders properly', async () => { 117 | // // const wrapper = await mount(PerksModal, { props: { visible: true }}) 118 | // // const form = await wrapper.find('form') 119 | // // expect(form.isVisible()).toBeTruthy() 120 | // // }) 121 | 122 | // // describe('valid submission', () => { 123 | // // it('can be filled out', async () => { 124 | // // const wrapper = await fillForm(formData) 125 | // // expect(wrapper.find('form').isVisible()).toBeTruthy() 126 | // // }) 127 | 128 | // // it('doesnt navigate when the submit button is pressed', async () => { 129 | // // const spy = jest.fn() 130 | // // const wrapper = await fillForm(formData) 131 | // // const form = await wrapper.find('form') 132 | // // form.submit = spy 133 | // // await form.trigger('submit') 134 | // // expect(spy).not.toHaveBeenCalled() 135 | // // }) 136 | 137 | // // it.only('emits the form data on submit', async () => { 138 | // // const wrapper = await fillForm(formData) 139 | // // const form = wrapper.find('form') 140 | 141 | 142 | // // await wrapper.vm.$nextTick() 143 | // // await button.trigger('click') 144 | 145 | 146 | // // const emitted = wrapper.emitted('submit') 147 | // // // debugger; 148 | // // // console.log(wrapper.emitted()) 149 | // // // expect(emitted).toHaveLength(2) 150 | // // // expect(emitted[0][1]).toEqual(expect.objectContaining(formData)) 151 | // // }) 152 | // // }) 153 | // // }) -------------------------------------------------------------------------------- /src/components/PerksModal.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 |