├── .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 | 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 | 5 | 6 | 7 | 8 | Vite App 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "moduleFileExtensions": ["js", "json", "vue"], 3 | "transform": { 4 | "^.+\\.js$": "babel-jest", 5 | "^.+\\.vue$": "vue-jest" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cypress-loves-vite", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "cy": "cypress open-ct", 8 | "cy:ci": "cypress run-ct --quiet --reporter spec", 9 | "jest": "jest" 10 | }, 11 | "dependencies": { 12 | "@babel/preset-env": "^7.13.12", 13 | "vue": "^3.0.4" 14 | }, 15 | "devDependencies": { 16 | "@babel/core": "^7.13.14", 17 | "@babel/plugin-transform-regenerator": "^7.12.13", 18 | "@babel/plugin-transform-runtime": "^7.13.10", 19 | "@cypress/vite-dev-server": "^1.2.0", 20 | "@cypress/vue": "^3.0.0-alpha.4", 21 | "@vitejs/plugin-vue": "1.2.0", 22 | "@vue/compiler-sfc": "3.0.9", 23 | "babel-jest": "^26.6.3", 24 | "cypress": "^7.0.0", 25 | "jest": "^26.6.3", 26 | "ts-jest": "^26.5.4", 27 | "typescript": "^4.2.3", 28 | "vite": "^2.1.5", 29 | "vite-plugin-windicss": "^0.12.2", 30 | "vue-jest": "^5.0.0-alpha.7" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JessicaSachs/cypress-loves-vite/83815382d5aa8884a3edf8103c501fd5ca55ea15/public/favicon.ico -------------------------------------------------------------------------------- /src/App.cy.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@cypress/vue' 2 | import App from '../App.vue' 3 | 4 | describe('', () => { 5 | it('renders the homepage link', () => { 6 | mount(App) 7 | }) 8 | }) -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JessicaSachs/cypress-loves-vite/83815382d5aa8884a3edf8103c501fd5ca55ea15/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/BaseButton.cy.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@cypress/vue' 2 | import ExampleBaseButton from './ExampleBaseButton.vue' 3 | import BaseButton from './BaseButton.vue' 4 | 5 | describe('BaseButton', () => { 6 | it('playground', () => { 7 | mount(ExampleBaseButton) 8 | }) 9 | 10 | it('takes in the default slot', () => { 11 | mount(BaseButton, { slots: { default: 'Hello' } 12 | }).get('button').should('have.text', 'Hello') 13 | }) 14 | }) -------------------------------------------------------------------------------- /src/components/BaseButton.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 33 | 34 | -------------------------------------------------------------------------------- /src/components/ExampleBaseButton.vue: -------------------------------------------------------------------------------- 1 | 80 | 81 | 108 | 109 | -------------------------------------------------------------------------------- /src/components/FoodCard.cy.js: -------------------------------------------------------------------------------- 1 | import { h } from 'vue' 2 | import { mount } from '@cypress/vue' 3 | import FoodCard from './FoodCard.vue' 4 | 5 | const mountWithMargin = (comp, props = {}) => { 6 | return mount(() => h('div', { style: 'margin: 10px' }, h(comp, props))) 7 | } 8 | 9 | describe('FoodCard', () => { 10 | it('render a culinary delight', () => { 11 | const availability = 'Closed. Next delivery 11:30am tomorrow.' 12 | mountWithMargin(FoodCard, { 13 | availability, 14 | restaurant: "Carl's Jr", 15 | isAvailable: false, 16 | genre: 'American', 17 | stars: 3 18 | }) 19 | 20 | cy.get('[data-cy=genre]').contains('American') 21 | cy.get('[data-cy=restaurant]').contains("Carl's Jr") 22 | cy.get('[data-cy=stars]').should('have.length', 3) 23 | cy.get('[data-cy=status]').contains(availability) 24 | }) 25 | }) -------------------------------------------------------------------------------- /src/components/FoodCard.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/components/HelloWorld.cy.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@cypress/vue' 2 | import HelloWorld from './HelloWorld.vue' 3 | 4 | describe('HelloWorld', () => { 5 | it('renders properly', () => { 6 | mount(HelloWorld) 7 | }) 8 | }) -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/PerksModal.cy.js: -------------------------------------------------------------------------------- 1 | import { mount } from '@cypress/vue' 2 | import PerksModal from './PerksModal.vue' 3 | 4 | const data = { 5 | email: 'hello@world.com', 6 | zipCode: '10036' 7 | } 8 | 9 | const wrapper = { 10 | template: ` 11 |

My page

12 | 13 | 14 |

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 | //

The web page

124 | // 125 | // 126 | // `, 127 | // data() { 128 | // return { 129 | // visible: true 130 | // } 131 | // }, 132 | // components: { PerksModal } 133 | // } 134 | 135 | // mount(wrapper) 136 | // }) 137 | 138 | 139 | // it('displays the modal when the visible prop is set to true', () => { 140 | // mount(PerksModal, { props: { visible: true } }) 141 | // .get('form').should('be.visible') 142 | // }) 143 | 144 | // it('does not display the modal when the visible prop is set to false', () => { 145 | // mount(PerksModal, { props: { visible: false } }) 146 | // .get('form').should('not.be.visible') 147 | // }) 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | const formData = { 181 | email: 'jessica@cypress.io', 182 | zipCode: '10036' 183 | } 184 | 185 | 186 | 187 | // const wrapper = { 188 | // template: ` 189 | //

Hello world

190 | // 192 | // 193 | //

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 | 64 | 65 | 77 | 78 | 100 | 101 | 187 | 188 | 199 | 200 | 208 | 209 | 214 | -------------------------------------------------------------------------------- /src/components/__snapshots__/PerksModal.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`mounts 1`] = `"

A $10 off Perk for your first order

Get $10 off your first order of $15+. Now, that's tasty!

"`; 4 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | #cypress-root, #app { 2 | font-family: Avenir, Helvetica, Arial, sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | text-align: center; 6 | color: #2c3e50; 7 | padding: 2rem; 8 | } 9 | 10 | h2 { 11 | @apply text-2xl text-left pb-10; 12 | } 13 | 14 | button { 15 | @apply rounded-sm font-black text-white p-3 w-100 mt-2 bg-emerald-700; 16 | } 17 | 18 | p { 19 | @apply my-4; 20 | } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import './index.css' 4 | 5 | createApp(App).mount('#app') 6 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | const vue = require('@vitejs/plugin-vue') 2 | const { defineConfig } = require('vite') 3 | const WindiCSS = require('vite-plugin-windicss').default 4 | 5 | module.exports = defineConfig({ 6 | plugins: [ 7 | vue(), 8 | WindiCSS() 9 | ], 10 | }); 11 | -------------------------------------------------------------------------------- /windi.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('vite-plugin-windicss') 2 | const colors = require('windicss/colors') 3 | const typography = require('windicss/plugin/typography') 4 | 5 | module.exports = defineConfig({ 6 | darkMode: 'class', 7 | plugins: [ 8 | typography(), 9 | ], 10 | theme: { 11 | extend: { 12 | typography: { 13 | DEFAULT: { 14 | css: { 15 | maxWidth: '65ch', 16 | color: 'inherit', 17 | a: { 18 | 'color': 'inherit', 19 | 'opacity': 0.75, 20 | 'fontWeight': '500', 21 | 'textDecoration': 'underline', 22 | '&:hover': { 23 | opacity: 1, 24 | color: colors.teal[600], 25 | }, 26 | }, 27 | b: { color: 'inherit' }, 28 | strong: { color: 'inherit' }, 29 | em: { color: 'inherit' }, 30 | h1: { color: 'inherit' }, 31 | h2: { color: 'inherit' }, 32 | h3: { color: 'inherit' }, 33 | h4: { color: 'inherit' }, 34 | code: { color: 'inherit' }, 35 | }, 36 | }, 37 | }, 38 | }, 39 | }, 40 | }) 41 | --------------------------------------------------------------------------------