├── .babelrc
├── .circleci
└── config.yml
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .nvmrc
├── README.ko.md
├── README.md
├── README.ru.md
├── README.zh-CN.md
├── demo-app-vue-3
├── .browserslistrc
├── .gitignore
├── README.md
├── babel.config.js
├── jest.config.js
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── bust-cache.js
│ ├── components
│ │ ├── Bilingual.vue
│ │ ├── Child.vue
│ │ ├── ComponentWithAsyncCall.vue
│ │ ├── ComponentWithButtons.vue
│ │ ├── ComponentWithGetters.vue
│ │ ├── ComponentWithVuex.vue
│ │ ├── CompositionApi.vue
│ │ ├── Emitter.vue
│ │ ├── FormSubmitter.vue
│ │ ├── Greeting.vue
│ │ ├── HelloWorld.vue
│ │ ├── NestedRoute.vue
│ │ ├── NumberRenderer.vue
│ │ ├── Parent.vue
│ │ ├── ParentWithAPICallChild.vue
│ │ ├── ParentWithManyChildren.vue
│ │ ├── Posts.vue
│ │ └── SubmitButton.vue
│ ├── createRouter.js
│ ├── createStore.js
│ ├── main.js
│ ├── router.js
│ ├── router
│ │ └── index.js
│ ├── routes.js
│ ├── store
│ │ ├── actions.js
│ │ ├── getters.js
│ │ ├── index.js
│ │ └── mutations.js
│ ├── translations.js
│ └── views
│ │ ├── About.vue
│ │ └── Home.vue
├── tests
│ └── unit
│ │ ├── App.spec.js
│ │ ├── Bilingual.spec.js
│ │ ├── ComponentWithButtons.spec.js
│ │ ├── ComponentWithVuex.spec.js
│ │ ├── CompositionApi.spec.js
│ │ ├── Emitter.spec.js
│ │ ├── FormSubmitter.spec.js
│ │ ├── Greeting.spec.js
│ │ ├── NestedRoute.spec.js
│ │ ├── NumberRenderer.spec.js
│ │ ├── Parent.spec.js
│ │ ├── ParentWithAPICallChild.spec.js
│ │ ├── Posts.spec.js
│ │ ├── SubmitButton.spec.js
│ │ ├── actions.spec.js
│ │ ├── example.spec.js
│ │ ├── getters.spec.js
│ │ ├── mutations.spec.js
│ │ └── router-hooks.spec.js
└── yarn.lock
├── demo-app
├── .gitignore
├── .nvmrc
├── .postcssrc.js
├── babel.config.js
├── jest.config.js
├── jest.init.js
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── bust-cache.js
│ ├── components
│ │ ├── Bilingual.vue
│ │ ├── Child.vue
│ │ ├── ComponentWithAsyncCall.vue
│ │ ├── ComponentWithButtons.vue
│ │ ├── ComponentWithGetters.vue
│ │ ├── ComponentWithVuex.vue
│ │ ├── CompositionApi.vue
│ │ ├── Emitter.vue
│ │ ├── FormSubmitter.vue
│ │ ├── Greeting.vue
│ │ ├── NestedRoute.vue
│ │ ├── NumberRenderer.vue
│ │ ├── Parent.vue
│ │ ├── ParentWithAPICallChild.vue
│ │ ├── ParentWithManyChildren.vue
│ │ ├── Posts.vue
│ │ └── SubmitButton.vue
│ ├── createRouter.js
│ ├── createStore.js
│ ├── main.js
│ ├── router.js
│ ├── routes.js
│ ├── store
│ │ ├── actions.js
│ │ ├── getters.js
│ │ ├── index.js
│ │ └── mutations.js
│ └── translations.js
├── tests
│ └── unit
│ │ ├── App.spec.js
│ │ ├── Bilingual.spec.js
│ │ ├── ComponentWithButtons.spec.js
│ │ ├── ComponentWithVuex.spec.js
│ │ ├── CompositionApi.spec.js
│ │ ├── Emitter.spec.js
│ │ ├── FormSubmitter.spec.js
│ │ ├── Greeting.spec.js
│ │ ├── NestedRoute.spec.js
│ │ ├── NumberRenderer.spec.js
│ │ ├── Parent.spec.js
│ │ ├── ParentWithAPICallChild.spec.js
│ │ ├── Posts.spec.js
│ │ ├── SubmitButton.spec.js
│ │ ├── actions.spec.js
│ │ ├── getters.spec.js
│ │ ├── mutations.spec.js
│ │ └── router-hooks.spec.js
└── yarn.lock
├── deploy.sh
├── docs
├── 404.html
├── ad.png
├── assets
│ ├── css
│ │ └── 0.styles.99f2ca3b.css
│ ├── img
│ │ └── search.83621669.svg
│ └── js
│ │ ├── 10.f3a1c469.js
│ │ ├── 100.60db6a6e.js
│ │ ├── 101.1158b189.js
│ │ ├── 102.9577d84f.js
│ │ ├── 103.11a93f18.js
│ │ ├── 104.1b67de7c.js
│ │ ├── 105.ad2c2441.js
│ │ ├── 106.26903253.js
│ │ ├── 107.f6145328.js
│ │ ├── 108.42df0903.js
│ │ ├── 109.70dd9ce0.js
│ │ ├── 11.70c52248.js
│ │ ├── 110.7f8bd663.js
│ │ ├── 111.89c14f05.js
│ │ ├── 112.99b57508.js
│ │ ├── 113.fc6ad776.js
│ │ ├── 114.92e6f97d.js
│ │ ├── 115.173c88d4.js
│ │ ├── 116.6ee47e24.js
│ │ ├── 117.a244df75.js
│ │ ├── 118.29eb143d.js
│ │ ├── 119.ee4f18dc.js
│ │ ├── 12.8ec4d737.js
│ │ ├── 120.ec21edb1.js
│ │ ├── 121.c6f30ea0.js
│ │ ├── 122.4deda3a5.js
│ │ ├── 123.ef37ff5e.js
│ │ ├── 124.efdfc905.js
│ │ ├── 125.65b52754.js
│ │ ├── 126.ff8fcaf5.js
│ │ ├── 127.f99a4c04.js
│ │ ├── 128.2c2c85c4.js
│ │ ├── 129.91445de9.js
│ │ ├── 13.3a3f3209.js
│ │ ├── 130.f908d7c1.js
│ │ ├── 131.e2a4b2ca.js
│ │ ├── 132.1f62e9b6.js
│ │ ├── 133.b458b87c.js
│ │ ├── 134.55871cf0.js
│ │ ├── 135.020a111e.js
│ │ ├── 136.8aab4165.js
│ │ ├── 137.4ccfc9be.js
│ │ ├── 138.db701ce6.js
│ │ ├── 139.d1644c47.js
│ │ ├── 14.449a2c61.js
│ │ ├── 140.b856e167.js
│ │ ├── 141.d2163430.js
│ │ ├── 142.c04f6cda.js
│ │ ├── 143.cef89f0d.js
│ │ ├── 144.c69a73ac.js
│ │ ├── 15.a348c542.js
│ │ ├── 16.9e1d398c.js
│ │ ├── 17.7b555f73.js
│ │ ├── 18.b6c257a5.js
│ │ ├── 19.2c96d19e.js
│ │ ├── 2.f869874b.js
│ │ ├── 20.91ecbec9.js
│ │ ├── 21.e6041a2f.js
│ │ ├── 22.d161cff0.js
│ │ ├── 23.a04a99b1.js
│ │ ├── 24.a7c75161.js
│ │ ├── 25.0b58e091.js
│ │ ├── 26.d90dbb3e.js
│ │ ├── 27.a4846e5f.js
│ │ ├── 28.daa69ad2.js
│ │ ├── 29.27e3b439.js
│ │ ├── 3.4a72be3d.js
│ │ ├── 30.8ecd7b0a.js
│ │ ├── 31.fddfc262.js
│ │ ├── 32.9acf1a26.js
│ │ ├── 33.79612a74.js
│ │ ├── 34.da326325.js
│ │ ├── 35.778422b5.js
│ │ ├── 36.41446629.js
│ │ ├── 37.393c8eb2.js
│ │ ├── 38.a855807c.js
│ │ ├── 39.e3ee570b.js
│ │ ├── 4.991f7431.js
│ │ ├── 40.82e26e71.js
│ │ ├── 41.1943d2c1.js
│ │ ├── 42.f18bf590.js
│ │ ├── 43.33654f3c.js
│ │ ├── 44.de49488d.js
│ │ ├── 45.0740ca4d.js
│ │ ├── 46.fd679183.js
│ │ ├── 47.76a9fd57.js
│ │ ├── 48.2d6de7c8.js
│ │ ├── 49.37e4ba45.js
│ │ ├── 5.bb45a9d8.js
│ │ ├── 50.631b7692.js
│ │ ├── 51.7eab9889.js
│ │ ├── 52.afdeda6f.js
│ │ ├── 53.58beea7c.js
│ │ ├── 54.416c7bd4.js
│ │ ├── 55.3603dee1.js
│ │ ├── 56.6ff9fbab.js
│ │ ├── 57.92ce13c6.js
│ │ ├── 58.ad666013.js
│ │ ├── 59.cb0133e9.js
│ │ ├── 6.7e1b87fc.js
│ │ ├── 60.c0533858.js
│ │ ├── 61.35da37e6.js
│ │ ├── 62.90cec726.js
│ │ ├── 63.dad56ba7.js
│ │ ├── 64.bdaabfed.js
│ │ ├── 65.067fd59b.js
│ │ ├── 66.7c472cde.js
│ │ ├── 67.054e25f3.js
│ │ ├── 68.a802bc75.js
│ │ ├── 69.7723feb0.js
│ │ ├── 7.8452aa15.js
│ │ ├── 70.9189a29b.js
│ │ ├── 71.8a0c9d50.js
│ │ ├── 72.4be47274.js
│ │ ├── 73.f83838e1.js
│ │ ├── 74.0aada042.js
│ │ ├── 75.c972a1fa.js
│ │ ├── 76.c86534c4.js
│ │ ├── 77.2ba0254c.js
│ │ ├── 78.c5a9bf2b.js
│ │ ├── 79.e251837b.js
│ │ ├── 8.397a331b.js
│ │ ├── 80.59c96601.js
│ │ ├── 81.87516f38.js
│ │ ├── 82.a2ae65be.js
│ │ ├── 83.9aad7de3.js
│ │ ├── 84.567f2a5a.js
│ │ ├── 85.27e56c93.js
│ │ ├── 86.32ae60b8.js
│ │ ├── 87.bf3b4420.js
│ │ ├── 88.cbc4d9c9.js
│ │ ├── 89.b390a611.js
│ │ ├── 9.7ae173a7.js
│ │ ├── 90.7d724383.js
│ │ ├── 91.ff97b5a2.js
│ │ ├── 92.7d1724c2.js
│ │ ├── 93.b6825306.js
│ │ ├── 94.c422466e.js
│ │ ├── 95.6ffc114b.js
│ │ ├── 96.2aebd78e.js
│ │ ├── 97.1c35176d.js
│ │ ├── 98.45250c47.js
│ │ ├── 99.d513795a.js
│ │ └── app.d88f1968.js
├── components-with-props.html
├── composition-api.html
├── composition.png
├── computed-properties.html
├── finding-elements-and-components.html
├── img
│ ├── favicon.png
│ └── og.png
├── index.html
├── ja
│ ├── components-with-props.html
│ ├── composition-api.html
│ ├── computed-properties.html
│ ├── finding-elements-and-components.html
│ ├── index.html
│ ├── jest-mocking-modules.html
│ ├── mocking-global-objects.html
│ ├── rendering-a-component.html
│ ├── setting-up-for-tdd.html
│ ├── simulating-user-input.html
│ ├── stubbing-components.html
│ ├── testing-emitted-events.html
│ ├── testing-vuex.html
│ ├── vue-router.html
│ ├── vuex-actions.html
│ ├── vuex-getters.html
│ ├── vuex-in-components-mutations-and-actions.html
│ ├── vuex-in-components.html
│ └── vuex-mutations.html
├── jest-mocking-modules.html
├── ko
│ ├── components-with-props.html
│ ├── composition-api.html
│ ├── computed-properties.html
│ ├── finding-elements-and-components.html
│ ├── index.html
│ ├── jest-mocking-modules.html
│ ├── mocking-global-objects.html
│ ├── reducing-boilerplate-in-tests.html
│ ├── rendering-a-component.html
│ ├── setting-up-for-tdd.html
│ ├── simulating-user-input.html
│ ├── stubbing-components.html
│ ├── testing-emitted-events.html
│ ├── testing-vuex.html
│ ├── vue-router.html
│ ├── vuex-actions.html
│ ├── vuex-getters.html
│ ├── vuex-in-components-mutations-and-actions.html
│ ├── vuex-in-components.html
│ └── vuex-mutations.html
├── mini.png
├── mocking-global-objects.html
├── patterns.png
├── reducing-boilerplate-in-tests.html
├── rendering-a-component.html
├── ru
│ ├── components-with-props.html
│ ├── composition-api.html
│ ├── computed-properties.html
│ ├── finding-elements-and-components.html
│ ├── index.html
│ ├── jest-mocking-modules.html
│ ├── mocking-global-objects.html
│ ├── reducing-boilerplate-in-tests.html
│ ├── rendering-a-component.html
│ ├── setting-up-for-tdd.html
│ ├── simulating-user-input.html
│ ├── stubbing-components.html
│ ├── testing-emitted-events.html
│ ├── testing-vuex.html
│ ├── vue-router.html
│ ├── vuex-actions.html
│ ├── vuex-getters.html
│ ├── vuex-in-components-mutations-and-actions.html
│ ├── vuex-in-components.html
│ └── vuex-mutations.html
├── setting-up-for-tdd.html
├── simulating-user-input.html
├── stubbing-components.html
├── testing-emitted-events.html
├── testing-vuex.html
├── v3
│ ├── components-with-props.html
│ ├── composition-api.html
│ ├── computed-properties.html
│ ├── finding-elements-and-components.html
│ ├── index.html
│ ├── jest-mocking-modules.html
│ ├── mocking-global-objects.html
│ ├── reducing-boilerplate-in-tests.html
│ ├── rendering-a-component.html
│ ├── ru
│ │ ├── components-with-props.html
│ │ ├── composition-api.html
│ │ ├── computed-properties.html
│ │ ├── finding-elements-and-components.html
│ │ ├── index.html
│ │ ├── jest-mocking-modules.html
│ │ ├── mocking-global-objects.html
│ │ ├── reducing-boilerplate-in-tests.html
│ │ ├── rendering-a-component.html
│ │ ├── setting-up-for-tdd.html
│ │ ├── simulating-user-input.html
│ │ ├── stubbing-components.html
│ │ ├── testing-emitted-events.html
│ │ ├── testing-vuex.html
│ │ ├── vue-router.html
│ │ ├── vuex-actions.html
│ │ ├── vuex-getters.html
│ │ ├── vuex-in-components-mutations-and-actions.html
│ │ ├── vuex-in-components.html
│ │ └── vuex-mutations.html
│ ├── setting-up-for-tdd.html
│ ├── simulating-user-input.html
│ ├── stubbing-components.html
│ ├── testing-emitted-events.html
│ ├── testing-vuex.html
│ ├── vue-router.html
│ ├── vuex-actions.html
│ ├── vuex-getters.html
│ ├── vuex-in-components-mutations-and-actions.html
│ ├── vuex-in-components.html
│ └── vuex-mutations.html
├── vue-crash-course.png
├── vue-router.html
├── vue-school.png
├── vuejs-course.png
├── vueschool-banner-small.png
├── vuex-actions.html
├── vuex-getters.html
├── vuex-in-components-mutations-and-actions.html
├── vuex-in-components.html
├── vuex-mutations.html
└── zh-CN
│ ├── components-with-props.html
│ ├── composition-api.html
│ ├── computed-properties.html
│ ├── finding-elements-and-components.html
│ ├── index.html
│ ├── jest-mocking-modules.html
│ ├── mocking-global-objects.html
│ ├── rendering-a-component.html
│ ├── setting-up-for-tdd.html
│ ├── simulating-user-input.html
│ ├── stubbing-components.html
│ ├── testing-emitted-events.html
│ ├── testing-vuex.html
│ ├── vue-router.html
│ ├── vuex-actions.html
│ ├── vuex-getters.html
│ ├── vuex-in-components-mutations-and-actions.html
│ ├── vuex-in-components.html
│ └── vuex-mutations.html
├── package.json
├── src
├── .vuepress
│ ├── config.js
│ ├── public
│ │ ├── ad.png
│ │ ├── composition.png
│ │ ├── img
│ │ │ ├── favicon.png
│ │ │ └── og.png
│ │ ├── mini.png
│ │ ├── patterns.png
│ │ ├── vue-crash-course.png
│ │ ├── vue-school.png
│ │ ├── vuejs-course.png
│ │ └── vueschool-banner-small.png
│ ├── styles
│ │ └── index.styl
│ └── theme
│ │ ├── LICENSE
│ │ ├── components
│ │ ├── AlgoliaSearchBox.vue
│ │ ├── CarbonAds.vue
│ │ ├── DropdownLink.vue
│ │ ├── DropdownTransition.vue
│ │ ├── Home.vue
│ │ ├── NavLink.vue
│ │ ├── NavLinks.vue
│ │ ├── Navbar.vue
│ │ ├── Page.vue
│ │ ├── PageEdit.vue
│ │ ├── PageNav.vue
│ │ ├── Sidebar.vue
│ │ ├── SidebarButton.vue
│ │ ├── SidebarGroup.vue
│ │ ├── SidebarLink.vue
│ │ ├── SidebarLinks.vue
│ │ ├── VueSchool.vue
│ │ └── ad.png
│ │ ├── global-components
│ │ └── Badge.vue
│ │ ├── index.js
│ │ ├── layouts
│ │ ├── 404.vue
│ │ └── Layout.vue
│ │ ├── noopModule.js
│ │ ├── styles
│ │ ├── arrow.styl
│ │ ├── code.styl
│ │ ├── config.styl
│ │ ├── custom-blocks.styl
│ │ ├── index.styl
│ │ ├── mobile.styl
│ │ ├── toc.styl
│ │ └── wrapper.styl
│ │ └── util
│ │ └── index.js
├── README.md
├── components-with-props.md
├── composition-api.md
├── computed-properties.md
├── finding-elements-and-components.md
├── ja
│ ├── README.md
│ ├── components-with-props.md
│ ├── composition-api.md
│ ├── computed-properties.md
│ ├── finding-elements-and-components.md
│ ├── jest-mocking-modules.md
│ ├── mocking-global-objects.md
│ ├── rendering-a-component.md
│ ├── setting-up-for-tdd.md
│ ├── simulating-user-input.md
│ ├── stubbing-components.md
│ ├── testing-emitted-events.md
│ ├── testing-vuex.md
│ ├── vue-router.md
│ ├── vuex-actions.md
│ ├── vuex-getters.md
│ ├── vuex-in-components-mutations-and-actions.md
│ ├── vuex-in-components.md
│ └── vuex-mutations.md
├── jest-mocking-modules.md
├── ko
│ ├── README.md
│ ├── components-with-props.md
│ ├── composition-api.md
│ ├── computed-properties.md
│ ├── finding-elements-and-components.md
│ ├── jest-mocking-modules.md
│ ├── mocking-global-objects.md
│ ├── reducing-boilerplate-in-tests.md
│ ├── rendering-a-component.md
│ ├── setting-up-for-tdd.md
│ ├── simulating-user-input.md
│ ├── stubbing-components.md
│ ├── testing-emitted-events.md
│ ├── testing-vuex.md
│ ├── vue-router.md
│ ├── vuex-actions.md
│ ├── vuex-getters.md
│ ├── vuex-in-components-mutations-and-actions.md
│ ├── vuex-in-components.md
│ └── vuex-mutations.md
├── mocking-global-objects.md
├── reducing-boilerplate-in-tests.md
├── rendering-a-component.md
├── ru
│ ├── README.md
│ ├── components-with-props.md
│ ├── composition-api.md
│ ├── computed-properties.md
│ ├── finding-elements-and-components.md
│ ├── jest-mocking-modules.md
│ ├── mocking-global-objects.md
│ ├── reducing-boilerplate-in-tests.md
│ ├── rendering-a-component.md
│ ├── setting-up-for-tdd.md
│ ├── simulating-user-input.md
│ ├── stubbing-components.md
│ ├── testing-emitted-events.md
│ ├── testing-vuex.md
│ ├── vue-router.md
│ ├── vuex-actions.md
│ ├── vuex-getters.md
│ ├── vuex-in-components-mutations-and-actions.md
│ ├── vuex-in-components.md
│ └── vuex-mutations.md
├── setting-up-for-tdd.md
├── simulating-user-input.md
├── stubbing-components.md
├── testing-emitted-events.md
├── testing-vuex.md
├── v3
│ ├── README.md
│ ├── components-with-props.md
│ ├── composition-api.md
│ ├── computed-properties.md
│ ├── finding-elements-and-components.md
│ ├── jest-mocking-modules.md
│ ├── mocking-global-objects.md
│ ├── reducing-boilerplate-in-tests.md
│ ├── rendering-a-component.md
│ ├── ru
│ │ ├── README.md
│ │ ├── components-with-props.md
│ │ ├── composition-api.md
│ │ ├── computed-properties.md
│ │ ├── finding-elements-and-components.md
│ │ ├── jest-mocking-modules.md
│ │ ├── mocking-global-objects.md
│ │ ├── reducing-boilerplate-in-tests.md
│ │ ├── rendering-a-component.md
│ │ ├── setting-up-for-tdd.md
│ │ ├── simulating-user-input.md
│ │ ├── stubbing-components.md
│ │ ├── testing-emitted-events.md
│ │ ├── testing-vuex.md
│ │ ├── vue-router.md
│ │ ├── vuex-actions.md
│ │ ├── vuex-getters.md
│ │ ├── vuex-in-components-mutations-and-actions.md
│ │ ├── vuex-in-components.md
│ │ └── vuex-mutations.md
│ ├── setting-up-for-tdd.md
│ ├── simulating-user-input.md
│ ├── stubbing-components.md
│ ├── testing-emitted-events.md
│ ├── testing-vuex.md
│ ├── vue-router.md
│ ├── vuex-actions.md
│ ├── vuex-getters.md
│ ├── vuex-in-components-mutations-and-actions.md
│ ├── vuex-in-components.md
│ └── vuex-mutations.md
├── vue-router.md
├── vuex-actions.md
├── vuex-getters.md
├── vuex-in-components-mutations-and-actions.md
├── vuex-in-components.md
├── vuex-mutations.md
├── zh-CN
│ ├── README.md
│ ├── components-with-props.md
│ ├── composition-api.md
│ ├── computed-properties.md
│ ├── finding-elements-and-components.md
│ ├── jest-mocking-modules.md
│ ├── mocking-global-objects.md
│ ├── rendering-a-component.md
│ ├── setting-up-for-tdd.md
│ ├── simulating-user-input.md
│ ├── stubbing-components.md
│ ├── testing-emitted-events.md
│ ├── testing-vuex.md
│ ├── vue-router.md
│ ├── vuex-actions.md
│ ├── vuex-getters.md
│ ├── vuex-in-components-mutations-and-actions.md
│ ├── vuex-in-components.md
│ └── vuex-mutations.md
└── zh
│ └── .gitkeep
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env"]
3 | }
4 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker: # See https://docs.docker.com/get-started/#docker-concepts if you are new to Docker.
5 | - image: node:8.12.0
6 |
7 | steps:
8 | - checkout
9 | - run:
10 | name: install dependencies
11 | command: |
12 | cd demo-app
13 | yarn install
14 |
15 | - run:
16 | name: lint
17 | command: |
18 | yarn install
19 | yarn lint
20 |
21 | - run:
22 | name: unit tests
23 | command: |
24 | cd demo-app
25 | yarn test:unit
26 |
27 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | docs
2 | node_modules
3 | demo-app/jest.config.js
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "expect": true,
4 | "describe": true,
5 | "it": true,
6 | "jest": true,
7 | "afterEach": true
8 | },
9 | "env": {
10 | "node": true
11 | },
12 | "parserOptions": {
13 | "parser": "babel-eslint",
14 | "ecmaVersion": 2017,
15 | "sourceType": "module"
16 | },
17 | "root": true,
18 | "rules": {
19 | "no-debugger": 2,
20 | "no-proto": 0,
21 | "max-len": 2,
22 | "indent": ["error", 2],
23 | "no-multi-spaces": "error"
24 | },
25 | "extends": [
26 | "eslint:recommended",
27 | "plugin:vue/base",
28 | "plugin:vue/essential"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .DS_Store
3 | .python-version
4 | .coverage
5 | .vscode*
6 | .vs/
7 | *.log
8 | !.gitkeep
9 | npm-debug.log*
10 | .npm
11 | .eslintcache
12 | .cache-loader
13 | .idea
14 | .nyc_output
15 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v10.16.3
2 |
--------------------------------------------------------------------------------
/README.ko.md:
--------------------------------------------------------------------------------
1 | [English](https://github.com/lmiller1990/vue-testing-handbook#vue-testing-handbook) | [日本語](https://github.com/lmiller1990/vue-testing-handbook/blob/master/src/ja/README.md) | [Русский](https://github.com/webistomin/vue-testing-handbook/blob/master/README.ru.md) | [简体中文](https://github.com/tonylua/vue-testing-handbook/blob/master/README.zh-CN.md) | [한국어](https://github.com/webistomin/vue-testing-handbook/blob/master/README.ko.md)
2 |
3 | ## Vue 테스팅 핸드북
4 |
5 | Vue.js 테스팅 핸드북에 방문한 것을 환영합니다!
6 |
7 | ## 이 프로젝트는 무엇인가요?
8 |
9 | 이 프로젝트는 Vue 컴포넌트를 테스트 하는 방법에 집중한 예제를 짧게 모아 놓았습니다. 이 문서에서는 Vue 컴포넌트를 테스트 하기 위한 공식 라이브러리인 [`vue-test-utils`](https://github.com/vuejs/vue-test-utils)와 근래에 자주 쓰이는 테스팅 프레임워크인 [Jest](https://jestjs.io/)를 사용합니다. 컴포넌트를 테스트 하는 모범 사례들 뿐만 아니라, `vue-test-utils`의 API도 다룹니다. 예제를 가진 작업 데모 프로젝트도 포함되는데, 여러분이 직접 내려 받고 운영해 볼 수 있습니다.
10 |
11 | ## 언어
12 |
13 | 이 핸드북은 영어로 쓰여져 있습니다. 일본어, 러시아어, 중국어 번역본이 작업 진행중입니다. 여러분 나라의 언어로 이 핸드북을 번역하고 싶다면 이슈를 남겨주세요!
14 |
15 | ## 기여
16 |
17 | ### 개발
18 |
19 | 정적 웹사이트를 생성하기 위해서 [Vuepress](https://vuepress.vuejs.org/)가 사용됐습니다. 아티클은 마크다운 문법으로 작성됐습니다.
20 |
21 | 레포지토리를 클론하고 `yarn`을 실행해서 의존성을 설치하세요. 그러고 나서 개발 서버를 열기 위해 `yarn dev` 실행하세요. `localhost:80801`으로 접속할 수 있습니다.
22 |
23 | 이 가이드를 수정하고 싶다면, [src](https://github.com/lmiller1990/vue-testing-handbook/tree/master/src) 디렉토리에 있는 코드를 업데이트 하세요. 마크다운 파일은 배포 했을 때 HTML로 변환됩니다. 직접 수정할 필요가 없습니다.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [English](https://github.com/lmiller1990/vue-testing-handbook#vue-testing-handbook) | [日本語](https://github.com/lmiller1990/vue-testing-handbook/blob/master/src/ja/README.md) | [Русский](https://github.com/webistomin/vue-testing-handbook/blob/master/README.ru.md) | [简体中文](https://github.com/tonylua/vue-testing-handbook/blob/master/README.zh-CN.md)
2 |
3 | ## Vue Testing Handbook
4 |
5 | Welcome to the Vue Testing Handbook!
6 |
7 | ## What is this?
8 |
9 | This is a collection of short, focused examples on how to test Vue components. It uses [`vue-test-utils`](https://github.com/vuejs/vue-test-utils), the official library for testing Vue components, and [Jest](https://jestjs.io/), a modern testing framework. It covers the `vue-test-utils` API, as well as best practices and useful parts of the Jest API for testing Vue components. A working demo project with the examples is also included, which you can pull down and run yourself.
10 |
11 | ## Languages
12 |
13 | The handbook is written in English, but there are a number of translations. I prefer not to merge translations until the entire document is completed.
14 |
15 | If you like an article, feel free to translate it and post it on your own blog. If you can leave a link to the original, that'd be great, too! Let's spread the good word of Vue.js component testing.
16 |
17 | ## Contributing
18 |
19 | ### Development
20 |
21 | [Vuepress](https://vuepress.vuejs.org/) is used to generate the static website. Articles are written in markdown.
22 |
23 | Clone the repo and run `yarn` to install the dependencies. Then run `yarn dev` to open the dev server. Access it on `localhost:8080`.
24 |
25 | To edit a guide, update the code in the [src](https://github.com/lmiller1990/vue-testing-handbook/tree/master/src) directory. The markdown files are converted to HTML when deployed – no need to edit those.
26 |
--------------------------------------------------------------------------------
/README.ru.md:
--------------------------------------------------------------------------------
1 | [English](https://github.com/lmiller1990/vue-testing-handbook#vue-testing-handbook) | [日本語](https://github.com/lmiller1990/vue-testing-handbook/blob/master/src/ja/README.md) | [Русский](https://github.com/lmiller1990/vue-testing-handbook/blob/master/README.ru.md) | [简体中文](https://github.com/lmiller1990/vue-testing-handbook/blob/master/README.zh-CN.md)
2 |
3 | ## Руководство по тестированию Vue приложений
4 |
5 | Добро пожаловать в руководство по тестированию Vue приложений!
6 |
7 | ## Что это?
8 |
9 | Это коллекция коротких примеров тестирования Vue компонентов с использованием [`vue-test-utils`](https://github.com/vuejs/vue-test-utils) – официальной библиотекой для тестирования Vue компонентов и [`Jest`](https://jestjs.io/ru/) – современного фреймворка для тестирования. В руководстве рассматривается `vue-test-utils` API, лучшие практики и польза Jest API для тестирования Vue компонентов. Также есть демонстрационный проект с примерами кода, который вы можете скачать и запустить самостоятельно.
10 |
11 | ## Переводы
12 |
13 | Руководство написано на английском языке, но также существует целый ряд переводов. Я предпочитаю не мёржить перевод, пока он не будет закончен.
14 |
15 | Если вам понравилась статья, не стесняйтесь перевести её и выложить в своём блоге. Если вы оставите ссылку на оригинал – это будет здорово. Давайте расскажем всем как нужно тестировать Vue компоненты.
16 |
17 | ## Участие в разработке
18 |
19 | ### Разработка
20 |
21 | Для генерации статичного сайта используется [Vuepress](https://vuepress.vuejs.org/). Статьи пишутся в формате `.md`.
22 |
23 | Клонируйте репозиторий и запустите `yarn` для установки зависимостей. Затем введите `yarn dev` для запуска сервера разработки, доступного по адресу `localhost:8080`.
24 |
25 | Для редактирования руководства используется код из папки [src](https://github.com/lmiller1990/vue-testing-handbook/tree/master/src). Файлы разметки конвертируются в HTML при деплое, об этом не нужно беспокоиться.
26 |
--------------------------------------------------------------------------------
/README.zh-CN.md:
--------------------------------------------------------------------------------
1 | [English](https://github.com/lmiller1990/vue-testing-handbook#vue-testing-handbook) | [日本語](https://github.com/lmiller1990/vue-testing-handbook/blob/master/src/ja/README.md) | [Русский](https://github.com/webistomin/vue-testing-handbook/blob/master/README.ru.md) | [简体中文](https://github.com/tonylua/vue-testing-handbook/blob/master/README.zh-CN.md)
2 |
3 | ## Vue 测试指南
4 |
5 | 欢迎来到 Vue.js 测试指南!
6 |
7 | ## 这是什么?
8 |
9 | 这是一系列关于如何测试 Vue 组件的短小而目标明确的示例。它使用了测试 Vue 组件的官方库 `vue-test-utils`,以及一个现代测试框架 [Jest](https://jestjs.io/)。本书不但涵盖了 `vue-test-utils` 的 API,而且也是用于测试 Vue 组件的 Jest API 的最佳实践和有用资料。仓库中也包含了一个可运行的例子 demo 项目,你可以将代码 pull 下来并自己运行它。
10 |
11 | ## 语言
12 |
13 | 这本指南最初由英文编写而成。目前也有 [简体中文](https://github.com/tonylua/vue-testing-handbook/blob/master/src/zh-CN/README.md)、[俄文](https://github.com/webistomin/vue-testing-handbook/blob/master/src/ru/README.md) 和 [日文](https://github.com/lmiller1990/vue-testing-handbook/blob/master/src/ja/README.md) 版本可以阅读。如果你有意将本书翻译为你自己的语言请新建一个 issue !
14 |
15 | ## 作出贡献
16 |
17 | ### 开发
18 |
19 | [Vuepress](https://vuepress.vuejs.org/) 被用于生成静态站点。文章均由 markdown 写成。
20 |
21 | clone 仓库并且运行 `yarn` 以安装依赖。然后运行 `yarn dev` 启动开发服务器。通过 `localhost:8080` 它。
22 |
23 | 要编辑手册、更新代码请在 [src](https://github.com/lmiller1990/vue-testing-handbook/tree/master/src) 目录中进行。markdown 会在部署时被转换为 HTML -- 不需要手动编辑后者。
24 |
--------------------------------------------------------------------------------
/demo-app-vue-3/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 |
--------------------------------------------------------------------------------
/demo-app-vue-3/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/demo-app-vue-3/README.md:
--------------------------------------------------------------------------------
1 | # demo-app-vue-3
2 |
3 | ## Project setup
4 | ```
5 | yarn install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | yarn serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | yarn build
16 | ```
17 |
18 | ### Run your unit tests
19 | ```
20 | yarn test:unit
21 | ```
22 |
23 | ### Customize configuration
24 | See [Configuration Reference](https://cli.vuejs.org/config/).
25 |
--------------------------------------------------------------------------------
/demo-app-vue-3/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/demo-app-vue-3/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: '@vue/cli-plugin-unit-jest',
3 | transform: {
4 | '^.+\\.vue$': 'vue-jest'
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/demo-app-vue-3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo-app-vue-3",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "test:unit": "vue-cli-service test:unit"
9 | },
10 | "dependencies": {
11 | "@testing-library/jest-dom": "^5.11.4",
12 | "core-js": "^3.6.5",
13 | "flush-promises": "^1.0.2",
14 | "vue": "^3.0.0-0",
15 | "vue-router": "^4.0.0-0",
16 | "vuex": "^4.0.0-0"
17 | },
18 | "devDependencies": {
19 | "@vue/cli-plugin-babel": "~4.5.0",
20 | "@vue/cli-plugin-router": "~4.5.0",
21 | "@vue/cli-plugin-unit-jest": "~4.5.0",
22 | "@vue/cli-plugin-vuex": "~4.5.0",
23 | "@vue/cli-service": "~4.5.0",
24 | "@vue/compiler-sfc": "^3.0.0-0",
25 | "@vue/test-utils": "^2.0.0-0",
26 | "typescript": "~3.9.3",
27 | "vue-jest": "^5.0.0-0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/demo-app-vue-3/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/demo-app-vue-3/public/favicon.ico
--------------------------------------------------------------------------------
/demo-app-vue-3/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
12 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | asdf
4 |
5 |
6 |
7 |
8 |
14 |
15 |
17 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/demo-app-vue-3/src/assets/logo.png
--------------------------------------------------------------------------------
/demo-app-vue-3/src/bust-cache.js:
--------------------------------------------------------------------------------
1 | export function bustCache() {
2 | }
3 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/Bilingual.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ $t("helloWorld") }}
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/Child.vue:
--------------------------------------------------------------------------------
1 |
2 | Child
3 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/ComponentWithAsyncCall.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
24 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/ComponentWithButtons.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | Commit
7 |
8 |
9 |
12 | Dispatch
13 |
14 |
15 |
18 | Namespaced Dispatch
19 |
20 |
21 |
22 |
23 |
45 |
46 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/ComponentWithGetters.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ fullname }}
4 |
5 |
6 |
7 |
27 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/ComponentWithVuex.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ username }}
5 |
6 |
7 |
8 |
9 |
20 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/CompositionApi.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ uppercasedMessage }}
4 |
5 | Count: {{ state.count }}
6 |
7 |
Increment
8 |
9 |
10 |
11 |
40 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/Emitter.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
18 |
20 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/FormSubmitter.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
12 |
16 | Thank you for your submission, {{ username }}.
17 |
18 |
19 |
20 |
21 |
50 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/Greeting.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ greeting }}
4 |
5 |
6 |
7 |
18 |
19 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 | For a guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation .
8 |
9 |
Installed CLI Plugins
10 |
16 |
Essential Links
17 |
24 |
Ecosystem
25 |
32 |
33 |
34 |
35 |
43 |
44 |
45 |
61 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/NestedRoute.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Nested Route
4 |
5 | {{ $route.params.username }}
6 |
7 |
8 |
9 |
10 |
21 |
22 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/NumberRenderer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ numbers }}
4 |
5 |
6 |
7 |
42 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/Parent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | Parent Component
8 |
9 |
10 |
11 |
12 |
13 |
29 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/ParentWithAPICallChild.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/ParentWithManyChildren.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
19 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/Posts.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ message }}
4 |
5 |
6 |
10 | New Post
11 |
12 |
13 |
14 |
Posts
15 |
20 |
21 | {{ post.title }}
22 |
23 |
24 |
25 |
26 |
27 |
51 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/components/SubmitButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Admin Privileges
4 | Not Authorized
5 |
6 | {{ msg }}
7 |
8 |
9 |
10 |
11 |
27 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/createRouter.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import { createRouter, createMemoryHistory } from 'vue-router'
3 |
4 | const createVueRouter = () => {
5 | return createRouter({
6 | history: createMemoryHistory(),
7 | routes: []
8 | })
9 | }
10 |
11 | export { createVueRouter }
12 |
13 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/createStore.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import { createStore } from 'vuex'
3 |
4 | const createVuexStore = (initialState = {}) => createStore({
5 | state() {
6 | return {
7 | authenticated: false,
8 | posts: [],
9 | ...initialState,
10 | }
11 | },
12 | mutations: {
13 | ADD_POSTS(state, posts) {
14 | state.posts = [...state.posts, ...posts]
15 | },
16 |
17 | SET_AUTH(state, authenticated) {
18 | state.authenticated = authenticated
19 | },
20 | }
21 | })
22 |
23 | export { createVuexStore }
24 |
25 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueCompositionApi from '@vue/composition-api'
3 |
4 | import App from './App.vue'
5 | import router from "./router.js"
6 | import store from "./store/index.js"
7 |
8 | Vue.use(VueCompositionApi)
9 |
10 | Vue.config.productionTip = false
11 |
12 | new Vue({
13 | router,
14 | store,
15 | render: h => h(App)
16 | }).$mount('#app')
17 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/router.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createMemoryHistory } from "vue-router"
2 | import routes from "./routes.js"
3 | import { bustCache } from "@/bust-cache.js"
4 |
5 | const router = createRouter({
6 | history: createMemoryHistory(),
7 | routes
8 | })
9 |
10 | export function beforeEach(to, from, next) {
11 | if (to.matched.some(record => record.meta.shouldBustCache)) {
12 | bustCache()
13 | }
14 | next()
15 | }
16 |
17 | router.beforeEach((to, from, next) => beforeEach(to, from, next))
18 |
19 | export default router
20 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/router/index.js:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHashHistory } from 'vue-router'
2 | import Home from '../views/Home.vue'
3 |
4 | const routes = [
5 | {
6 | path: '/',
7 | name: 'Home',
8 | component: Home
9 | },
10 | {
11 | path: '/about',
12 | name: 'About',
13 | // route level code-splitting
14 | // this generates a separate chunk (about.[hash].js) for this route
15 | // which is lazy-loaded when the route is visited.
16 | component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
17 | }
18 | ]
19 |
20 | const router = createRouter({
21 | history: createWebHashHistory(),
22 | routes
23 | })
24 |
25 | export default router
26 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/routes.js:
--------------------------------------------------------------------------------
1 | import NestedRoute from "@/components/NestedRoute.vue"
2 |
3 | export default [
4 | {
5 | path: "/nested-route",
6 | component: NestedRoute,
7 | meta: {
8 | shouldBustCache: true
9 | }
10 | }
11 | ]
12 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/store/actions.js:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 |
3 | export default {
4 | async authenticate({ commit }, { username, password }) {
5 | try {
6 | const authenticated = await axios.post("/api/authenticate", {
7 | username, password
8 | })
9 |
10 | commit("SET_AUTHENTICATED", authenticated)
11 | } catch (e) {
12 | throw Error("API Error occurred.")
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/store/getters.js:
--------------------------------------------------------------------------------
1 | export default {
2 | poodles: (state) => {
3 | return state.dogs.filter(dog => dog.breed === "poodle")
4 | },
5 |
6 | poodlesByAge: (state, getters) => (age) => {
7 | return getters.poodles.filter(dog => dog.age === age)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue"
2 | import Vuex from "vuex"
3 | import getters from "./getters.js"
4 |
5 | Vue.use(Vuex)
6 |
7 | const state = {
8 | dogs: [
9 | { name: "lucky", breed: "poodle", age: 1 },
10 | { name: "pochy", breed: "dalmatian", age: 2 },
11 | { name: "blackie", breed: "poodle", age: 4 }
12 | ]
13 | }
14 |
15 | export default new Vuex.Store({
16 | state, getters
17 | })
18 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/store/mutations.js:
--------------------------------------------------------------------------------
1 | export default {
2 | SET_POST(state, { post }) {
3 | state.postIds.push(post.id)
4 | state.posts = { ...state.posts, [post.id]: post }
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/translations.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "en": {
3 | helloWorld: "Hello world!"
4 | },
5 | "ja": {
6 | helloWorld: "こんにちは、世界!"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/views/About.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
This is an about page
4 |
5 |
6 |
--------------------------------------------------------------------------------
/demo-app-vue-3/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
19 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/App.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import App from "../../src/App.vue"
3 | import { createRouter, createMemoryHistory } from "vue-router"
4 | import NestedRoute from "../../src/components/NestedRoute.vue"
5 | import routes from "../../src/routes.js"
6 |
7 | jest.mock("../../src/components/NestedRoute.vue", () => ({
8 | name: "NestedRoute",
9 | template: "
"
10 | }))
11 |
12 | describe("App", () => {
13 | it("renders a child component via routing", async () => {
14 | const router = createRouter({
15 | history: createMemoryHistory(),
16 | routes
17 | })
18 | router.push("/nested-route")
19 | await router.isReady()
20 | const wrapper = mount(App, {
21 | global: {
22 | plugins: [router]
23 | }
24 | })
25 |
26 | expect(wrapper.findComponent(NestedRoute).exists()).toBe(true)
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/Bilingual.spec.js:
--------------------------------------------------------------------------------
1 | import { mount, config } from "@vue/test-utils"
2 | import Bilingual from "../../src/components/Bilingual.vue"
3 |
4 | describe("Bilingual", () => {
5 | it("renders successfully", () => {
6 | config.global.mocks = {
7 | '$t': () => 'blah'
8 | }
9 | const wrapper = mount(Bilingual)
10 |
11 | expect(wrapper.find(".hello").text()).not.toBe("")
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/ComponentWithButtons.spec.js:
--------------------------------------------------------------------------------
1 | import { createStore } from "vuex"
2 | import { mount } from "@vue/test-utils"
3 | import ComponentWithButtons from "../../src/components/ComponentWithButtons.vue"
4 |
5 | const mutations = {
6 | testMutation: jest.fn()
7 | }
8 |
9 | const store = createStore({
10 | mutations
11 | })
12 |
13 | describe("ComponentWithButtons", () => {
14 |
15 | it("commits a mutation when a button is clicked", async () => {
16 | const wrapper = mount(ComponentWithButtons, {
17 | global: {
18 | plugins: [store]
19 | }
20 | })
21 |
22 | wrapper.find(".commit").trigger("click")
23 | await wrapper.vm.$nextTick()
24 |
25 | expect(mutations.testMutation).toHaveBeenCalledWith(
26 | {},
27 | { msg: "Test Commit" }
28 | )
29 | })
30 |
31 | it("dispatch a namespaced action when button is clicked", async () => {
32 | const store = createStore()
33 | store.dispatch = jest.fn()
34 |
35 | const wrapper = mount(ComponentWithButtons, {
36 | global: {
37 | plugins: [store]
38 | }
39 | })
40 |
41 | wrapper.find(".namespaced-dispatch").trigger("click")
42 | await wrapper.vm.$nextTick()
43 |
44 | expect(store.dispatch).toHaveBeenCalledWith(
45 | 'namespaced/very/deeply/testAction',
46 | { msg: "Test Namespaced Dispatch" }
47 | )
48 | })
49 |
50 |
51 | it("dispatches an action when a button is clicked", async () => {
52 | const mockStore = { dispatch: jest.fn() }
53 | const wrapper = mount(ComponentWithButtons, {
54 | global: {
55 | mocks: {
56 | $store: mockStore
57 | }
58 | }
59 | })
60 |
61 | wrapper.find(".dispatch").trigger("click")
62 | await wrapper.vm.$nextTick()
63 |
64 | expect(mockStore.dispatch).toHaveBeenCalledWith(
65 | "testAction" , { msg: "Test Dispatch" })
66 | })
67 |
68 | })
69 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/ComponentWithVuex.spec.js:
--------------------------------------------------------------------------------
1 | import { createStore } from "vuex"
2 | import { mount } from "@vue/test-utils"
3 | import ComponentWithVuex from "../../src/components/ComponentWithVuex.vue"
4 | import ComponentWithGetters from "../../src/components/ComponentWithGetters.vue"
5 |
6 | const store = createStore({
7 | state() {
8 | return {
9 | username: "alice",
10 | firstName: "Alice",
11 | lastName: "Doe"
12 | }
13 | },
14 |
15 | getters: {
16 | fullname: (state) => state.firstName + " " + state.lastName
17 | }
18 | })
19 |
20 | describe("ComponentWithVuex", () => {
21 | it("renders a username using a real Vuex store", () => {
22 | const wrapper = mount(ComponentWithVuex, {
23 | global: {
24 | plugins: [store]
25 | }
26 | })
27 |
28 | expect(wrapper.find(".username").text()).toBe("alice")
29 | })
30 |
31 | it("renders a username using a mock store", () => {
32 | const wrapper = mount(ComponentWithVuex, {
33 | global: {
34 | mocks: {
35 | $store: {
36 | state: { username: "alice" }
37 | }
38 | }
39 | }
40 | })
41 |
42 | expect(wrapper.find(".username").text()).toBe("alice")
43 | })
44 | })
45 |
46 | describe("ComponentWithGetters", () => {
47 | it("renders a username using a real Vuex getter", () => {
48 | const wrapper = mount(ComponentWithGetters, {
49 | global: {
50 | plugins: [store]
51 | }
52 | })
53 |
54 | expect(wrapper.find(".fullname").text()).toBe("Alice Doe")
55 | })
56 |
57 | it("renders a username using computed mounting options", () => {
58 | const wrapper = mount(ComponentWithGetters, {
59 | global: {
60 | mocks: {
61 | $store: {
62 | getters: {
63 | fullname: "Alice Doe"
64 | }
65 | }
66 | }
67 | }
68 | })
69 |
70 | expect(wrapper.find(".fullname").text()).toBe("Alice Doe")
71 | })
72 | })
73 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/CompositionApi.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount } from "@vue/test-utils"
2 |
3 | import CompositionApi from "../../src/components/CompositionApi.vue"
4 |
5 | describe("CompositionApi", () => {
6 | it("increments a count when button is clicked", async () => {
7 | const wrapper = shallowMount(CompositionApi, {
8 | props: { message: '' }
9 | })
10 |
11 | wrapper.find('button').trigger('click')
12 | await wrapper.vm.$nextTick()
13 |
14 | expect(wrapper.find(".count").text()).toBe("Count: 1")
15 | })
16 |
17 | it("renders a message", async () => {
18 | const wrapper = shallowMount(CompositionApi, {
19 | props: {
20 | message: "Testing the composition API"
21 | }
22 | })
23 |
24 | expect(wrapper.find(".message").text()).toBe("TESTING THE COMPOSITION API")
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/Emitter.spec.js:
--------------------------------------------------------------------------------
1 | import Emitter from "../../src/components/Emitter.vue"
2 | import { mount } from "@vue/test-utils"
3 |
4 | describe("Emitter", () => {
5 | it("emits an event with two arguments", () => {
6 | const wrapper = mount(Emitter)
7 |
8 | wrapper.vm.emitEvent()
9 |
10 | expect(wrapper.emitted().myEvent[0]).toEqual(["name", "password"])
11 | })
12 |
13 | it("emits an event without mounting the component", () => {
14 | const events = {}
15 | const $emit = (event, ...args) => { events[event] = [...args] }
16 |
17 | Emitter.methods.emitEvent.call({ $emit })
18 |
19 | expect(events.myEvent).toEqual(["name", "password"])
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/FormSubmitter.spec.js:
--------------------------------------------------------------------------------
1 | import flushPromises from "flush-promises"
2 | import { mount } from "@vue/test-utils"
3 | import FormSubmitter from "../../src/components/FormSubmitter.vue"
4 |
5 | let url = ''
6 | let data = ''
7 |
8 | const mockHttp = {
9 | get: (_url, _data) => {
10 | return new Promise((resolve) => {
11 | url = _url
12 | data = _data
13 | resolve()
14 | })
15 | }
16 | }
17 |
18 | describe("FormSubmitter", () => {
19 | it("reveals a notification when submitted", async () => {
20 | const wrapper = mount(FormSubmitter)
21 |
22 | wrapper.find("[data-username]").setValue("alice")
23 | wrapper.find("form").trigger("submit.prevent")
24 | await wrapper.vm.$nextTick()
25 |
26 | expect(wrapper.find(".message").text())
27 | .toBe("Thank you for your submission, alice.")
28 | })
29 |
30 | it("reveals a notification when submitted", async () => {
31 | const wrapper = mount(FormSubmitter, {
32 | data() {
33 | return {
34 | asyncTest: true
35 | }
36 | },
37 | global: {
38 | mocks: {
39 | $http: mockHttp
40 | }
41 | }
42 | })
43 |
44 | wrapper.find("[data-username]").setValue("alice")
45 | wrapper.find("form").trigger("submit.prevent")
46 |
47 | /*
48 | await flushPromises()
49 |
50 | expect(wrapper.find(".message").text())
51 | .toBe("Thank you for your submission, alice.")
52 | expect(url).toBe("/api/v1/register")
53 | expect(data).toEqual({ username: "alice" })
54 | */
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/Greeting.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from '@vue/test-utils'
2 | import Greeting from '../../src/components/Greeting.vue'
3 |
4 | describe('Greeting.vue', () => {
5 | it('renders a greeting', () => {
6 | const wrapper = mount(Greeting)
7 |
8 | expect(wrapper.text()).toMatch("Vue and TDD")
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/NestedRoute.spec.js:
--------------------------------------------------------------------------------
1 | import { mount, createLocalVue } from "@vue/test-utils"
2 | import NestedRoute from "../../src/components/NestedRoute.vue"
3 | import mockModule from "../../src/bust-cache.js"
4 |
5 | jest.mock("../../src/bust-cache.js", () => ({ bustCache: jest.fn() }))
6 |
7 | describe("NestedRoute", () => {
8 | it("renders a username from query string", () => {
9 | const username = "alice"
10 | const wrapper = mount(NestedRoute, {
11 | global: {
12 | mocks: {
13 | $route: {
14 | params: { username }
15 | }
16 | }
17 | }
18 | })
19 |
20 | expect(wrapper.find(".username").text()).toBe(username)
21 | })
22 |
23 | it("calls bustCache and next when leaving the route", () => {
24 | const next = jest.fn()
25 | NestedRoute.beforeRouteLeave(undefined, undefined, next)
26 |
27 | expect(mockModule.bustCache).toHaveBeenCalled()
28 | expect(next).toHaveBeenCalled()
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/NumberRenderer.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import NumberRenderer from "../../src/components/NumberRenderer.vue"
3 |
4 | describe("NumberRenderer", () => {
5 | it("renders even numbers", () => {
6 | const wrapper = mount(NumberRenderer, {
7 | props: {
8 | even: true
9 | }
10 | })
11 |
12 | expect(wrapper.text()).toBe("2, 4, 6, 8")
13 | })
14 |
15 | it("renders odd numbers", () => {
16 | const localThis = { even: false }
17 |
18 | expect(NumberRenderer.computed.numbers.call(localThis))
19 | .toBe("1, 3, 5, 7, 9")
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/Parent.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import '@testing-library/jest-dom'
3 | import Parent from "../../src/components/Parent.vue"
4 | import ParentWithManyChildren from "../../src/components/ParentWithManyChildren.vue"
5 | import Child from "../../src/components/Child.vue"
6 |
7 | describe("Parent", () => {
8 | it("does not render a span", () => {
9 | const wrapper = mount(Parent)
10 |
11 | expect(wrapper.find("span").isVisible()).toBe(false)
12 | })
13 |
14 | it("does render a span", () => {
15 | const wrapper = mount(Parent, {
16 | data() {
17 | return { showSpan: true }
18 | }
19 | })
20 |
21 | expect(wrapper.find("span").isVisible()).toBe(true)
22 | })
23 |
24 | it("does not render a Child component", () => {
25 | const wrapper = mount(Parent)
26 |
27 | expect(wrapper.findComponent(Child).exists()).toBe(false)
28 | })
29 |
30 | it("renders a Child component", () => {
31 | const wrapper = mount(Parent, {
32 | data() {
33 | return { showChild: true }
34 | }
35 | })
36 |
37 | expect(wrapper.findComponent({ name: "Child" }).exists()).toBe(true)
38 | })
39 | })
40 |
41 | describe("ParentWithManyChildren", () => {
42 | it("renders many children", () => {
43 | const wrapper = mount(ParentWithManyChildren)
44 |
45 | expect(wrapper.findAllComponents(Child).length).toBe(3)
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/ParentWithAPICallChild.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount, mount } from '@vue/test-utils';
2 | import ParentWithAPICallChild from '../../src/components/ParentWithAPICallChild.vue';
3 | import ComponentWithAsyncCall from '../../src/components/ComponentWithAsyncCall.vue';
4 |
5 | describe('ParentWithAPICallChild.vue', () => {
6 | it('renders with mount and does initialize API call', () => {
7 | const wrapper = mount(ParentWithAPICallChild, {
8 | global: {
9 | stubs: {
10 | ComponentWithAsyncCall: true,
11 | },
12 | },
13 | });
14 |
15 | expect(wrapper.findComponent(ComponentWithAsyncCall).exists()).toBe(true);
16 | });
17 |
18 | it('renders with shallowMount and does not initialize API call', () => {
19 | const wrapper = shallowMount(ParentWithAPICallChild);
20 |
21 | expect(wrapper.findComponent(ComponentWithAsyncCall).exists()).toBe(true);
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/Posts.spec.js:
--------------------------------------------------------------------------------
1 | import Vuex from 'vuex'
2 | import VueRouter from 'vue-router'
3 | import { mount, createLocalVue } from '@vue/test-utils'
4 |
5 | import Posts from '../../src/components/Posts.vue'
6 | import { createVueRouter } from '../../src/createRouter'
7 | import { createVuexStore } from '../../src/createStore'
8 |
9 | const createWrapper = (component, options = {}, storeState = {}) => {
10 | const store = createVuexStore(storeState)
11 | const router = createVueRouter()
12 |
13 | return mount(component, {
14 | global: {
15 | plugins: [
16 | store, router
17 | ]
18 | },
19 | ...options
20 | })
21 | }
22 |
23 | describe('Posts.vue', () => {
24 | it('renders a message if passed', () => {
25 | const message = 'New content coming soon!'
26 | const wrapper = createWrapper(Posts, {
27 | props: { message },
28 | })
29 |
30 | expect(wrapper.find("#message").text()).toBe('New content coming soon!')
31 | })
32 |
33 | it('renders posts', async () => {
34 | const wrapper = createWrapper(Posts, {}, {
35 | posts: [{ id: 1, title: 'Post' }]
36 | })
37 |
38 | expect(wrapper.findAll('.post').length).toBe(1)
39 | })
40 |
41 | it('renders new post link if authenticated', async () => {
42 | const wrapper = createWrapper(Posts, {}, {
43 | authenticated: true
44 | })
45 |
46 | expect(wrapper.find('.new-post').exists()).toBe(true)
47 | })
48 |
49 | it('does not render new post link if not authenticated', async () => {
50 | const wrapper = createWrapper(Posts, {}, {
51 | authenticated: false
52 | })
53 |
54 | expect(wrapper.find('.new-post').exists()).toBe(false)
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/SubmitButton.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount } from '@vue/test-utils'
2 | import SubmitButton from '../../src/components/SubmitButton.vue'
3 |
4 | const msg = "submit"
5 | const factory = (props) => {
6 | return shallowMount(SubmitButton, {
7 | props: {
8 | msg,
9 | ...props
10 | }
11 | })
12 | }
13 |
14 | describe("SubmitButton", () => {
15 | describe("does not have admin privileges", ()=> {
16 | it("renders a message", () => {
17 | const wrapper = factory()
18 |
19 | expect(wrapper.find("span").text()).toBe("Not Authorized")
20 | expect(wrapper.find("button").text()).toBe("submit")
21 | })
22 | })
23 |
24 | describe("has admin privileges", ()=> {
25 | it("renders a message", () => {
26 | const wrapper = factory({ isAdmin: true })
27 |
28 | expect(wrapper.find("span").text()).toBe("Admin Privileges")
29 | expect(wrapper.find("button").text()).toBe("submit")
30 | })
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/actions.spec.js:
--------------------------------------------------------------------------------
1 | import actions from "../../src/store/actions.js"
2 |
3 | let url = ''
4 | let body = {}
5 | let mockError = false
6 |
7 | jest.mock("axios", () => ({
8 | post: (_url, _body) => {
9 | return new Promise((resolve) => {
10 | if (mockError)
11 | throw Error("Mock error")
12 |
13 | url = _url
14 | body = _body
15 | resolve(true)
16 | })
17 | }
18 | }))
19 |
20 |
21 | describe("authenticate", () => {
22 | it("authenticated a user", async () => {
23 | const commit = jest.fn()
24 | const username = "alice"
25 | const password = "password"
26 |
27 | await actions.authenticate({ commit }, { username, password })
28 |
29 | expect(url).toBe("/api/authenticate")
30 | expect(body).toEqual({ username, password })
31 | expect(commit).toHaveBeenCalledWith(
32 | "SET_AUTHENTICATED", true)
33 | })
34 |
35 | it("catches an error", async () => {
36 | mockError = true
37 |
38 | await expect(actions.authenticate({ commit: jest.fn() }, {}))
39 | .rejects.toThrow("API Error occurred.")
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/example.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount } from '@vue/test-utils'
2 | import HelloWorld from '../../src/components/HelloWorld.vue'
3 |
4 | describe('HelloWorld.vue', () => {
5 | it('renders props.msg when passed', () => {
6 | const msg = 'new message'
7 | const wrapper = shallowMount(HelloWorld, {
8 | props: { msg }
9 | })
10 | expect(wrapper.text()).toMatch(msg)
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/getters.spec.js:
--------------------------------------------------------------------------------
1 | import getters from "../../src/store/getters.js"
2 |
3 | const dogs = [
4 | { name: "lucky", breed: "poodle", age: 1 },
5 | { name: "pochy", breed: "dalmatian", age: 2 },
6 | { name: "blackie", breed: "poodle", age: 4 }
7 | ]
8 | const state = { dogs }
9 |
10 | describe("poodles", () => {
11 | it("returns poodles", () => {
12 | const actual = getters.poodles(state)
13 |
14 | expect(actual).toEqual([ dogs[0], dogs[2] ])
15 | })
16 | })
17 |
18 | describe("poodlesByAge", () => {
19 | it("returns poodles by age", () => {
20 | const poodles = [ dogs[0], dogs[2] ]
21 | const actual = getters.poodlesByAge(state, { poodles })(1)
22 |
23 | expect(actual).toEqual([ dogs[0] ])
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/mutations.spec.js:
--------------------------------------------------------------------------------
1 | import mutations from "../../src/store/mutations.js"
2 |
3 | describe("SET_POST", () => {
4 | it("adds a post to the state", () => {
5 | const post = { id: 1, title: "Post" }
6 | const state = {
7 | postIds: [],
8 | posts: {}
9 | }
10 |
11 | mutations.SET_POST(state, { post })
12 |
13 | expect(state).toEqual({
14 | postIds: [1],
15 | posts: { "1": post }
16 | })
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/demo-app-vue-3/tests/unit/router-hooks.spec.js:
--------------------------------------------------------------------------------
1 | import { beforeEach } from "../../src/router.js"
2 | import mockModule from "../../src/bust-cache.js"
3 |
4 | jest.mock("../../src/bust-cache.js", () => ({ bustCache: jest.fn() }))
5 |
6 | describe("beforeEach", () => {
7 | afterEach(() => {
8 | mockModule.bustCache.mockClear()
9 | })
10 |
11 | it("busts the cache when going to /user", () => {
12 | const to = {
13 | matched: [{ meta: { shouldBustCache: true } }]
14 | }
15 | const next = jest.fn()
16 |
17 | beforeEach(to, undefined, next)
18 |
19 | expect(mockModule.bustCache).toHaveBeenCalled()
20 | expect(next).toHaveBeenCalled()
21 | })
22 |
23 | it("busts the cache when going to /user", () => {
24 | const to = {
25 | matched: [{ meta: { shouldBustCache: false } }]
26 | }
27 | const next = jest.fn()
28 |
29 | beforeEach(to, undefined, next)
30 |
31 | expect(mockModule.bustCache).not.toHaveBeenCalled()
32 | expect(next).toHaveBeenCalled()
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/demo-app/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw*
22 |
--------------------------------------------------------------------------------
/demo-app/.nvmrc:
--------------------------------------------------------------------------------
1 | v10.16.3
2 |
--------------------------------------------------------------------------------
/demo-app/.postcssrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | }
--------------------------------------------------------------------------------
/demo-app/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
--------------------------------------------------------------------------------
/demo-app/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | moduleFileExtensions: [
3 | 'js',
4 | 'jsx',
5 | 'json',
6 | 'vue'
7 | ],
8 | transform: {
9 | '^.+\\.vue$': 'vue-jest',
10 | '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
11 | '^.+\\.jsx?$': 'babel-jest'
12 | },
13 | moduleNameMapper: {
14 | '^@/(.*)$': '/src/$1'
15 | },
16 | snapshotSerializers: [
17 | 'jest-serializer-vue'
18 | ],
19 | testMatch: [
20 | '/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
21 | ],
22 | setupFiles: ["/jest.init.js"]
23 | }
24 |
--------------------------------------------------------------------------------
/demo-app/jest.init.js:
--------------------------------------------------------------------------------
1 | import { config } from "@vue/test-utils"
2 | import translations from "./src/translations.js"
3 |
4 | const locale = "en"
5 |
6 | config.mocks["$t"] = (msg) => translations[locale][msg]
7 |
--------------------------------------------------------------------------------
/demo-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "demo-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "test:unit": "vue-cli-service test:unit"
9 | },
10 | "dependencies": {
11 | "@vue/composition-api": "^0.3.2",
12 | "axios": "^0.19.0",
13 | "flush-promises": "^1.0.0",
14 | "vue": "^2.6.11",
15 | "vue-router": "^3.1.3"
16 | },
17 | "devDependencies": {
18 | "@testing-library/jest-dom": "^5.11.3",
19 | "@vue/cli-plugin-babel": "^4.0.4",
20 | "@vue/cli-plugin-unit-jest": "^4.0.4",
21 | "@vue/cli-service": "^4.0.4",
22 | "@vue/test-utils": "^1.0.4",
23 | "babel-core": "7.0.0-bridge.0",
24 | "babel-jest": "^24.9.0",
25 | "vue-template-compiler": "^2.6.11",
26 | "vuex": "^3.1.1"
27 | },
28 | "browserslist": [
29 | "> 1%",
30 | "last 2 versions",
31 | "not ie <= 8"
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/demo-app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/demo-app/public/favicon.ico
--------------------------------------------------------------------------------
/demo-app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | demo-app
9 |
10 |
11 |
12 | We're sorry but demo-app doesn't work properly without JavaScript enabled. Please enable it to continue.
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/demo-app/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | asdf
4 |
5 |
6 |
7 |
8 |
14 |
15 |
17 |
--------------------------------------------------------------------------------
/demo-app/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/demo-app/src/assets/logo.png
--------------------------------------------------------------------------------
/demo-app/src/bust-cache.js:
--------------------------------------------------------------------------------
1 | export function bustCache() {
2 | }
3 |
--------------------------------------------------------------------------------
/demo-app/src/components/Bilingual.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ $t("helloWorld") }}
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/demo-app/src/components/Child.vue:
--------------------------------------------------------------------------------
1 |
2 | Child
3 |
4 |
5 |
10 |
--------------------------------------------------------------------------------
/demo-app/src/components/ComponentWithAsyncCall.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
24 |
--------------------------------------------------------------------------------
/demo-app/src/components/ComponentWithButtons.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | Commit
7 |
8 |
9 |
12 | Dispatch
13 |
14 |
15 |
18 | Namespaced Dispatch
19 |
20 |
21 |
22 |
23 |
45 |
46 |
--------------------------------------------------------------------------------
/demo-app/src/components/ComponentWithGetters.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ fullname }}
4 |
5 |
6 |
7 |
27 |
--------------------------------------------------------------------------------
/demo-app/src/components/ComponentWithVuex.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ username }}
5 |
6 |
7 |
8 |
9 |
20 |
--------------------------------------------------------------------------------
/demo-app/src/components/CompositionApi.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ uppercasedMessage }}
4 |
5 | Count: {{ state.count }}
6 |
7 |
Increment
8 |
9 |
10 |
11 |
48 |
--------------------------------------------------------------------------------
/demo-app/src/components/Emitter.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
18 |
20 |
--------------------------------------------------------------------------------
/demo-app/src/components/FormSubmitter.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
12 |
16 | Thank you for your submission, {{ username }}.
17 |
18 |
19 |
20 |
21 |
50 |
--------------------------------------------------------------------------------
/demo-app/src/components/Greeting.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ greeting }}
4 |
5 |
6 |
7 |
18 |
19 |
--------------------------------------------------------------------------------
/demo-app/src/components/NestedRoute.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Nested Route
4 |
5 | {{ $route.params.username }}
6 |
7 |
8 |
9 |
10 |
21 |
22 |
--------------------------------------------------------------------------------
/demo-app/src/components/NumberRenderer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ numbers }}
4 |
5 |
6 |
7 |
42 |
--------------------------------------------------------------------------------
/demo-app/src/components/Parent.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 | Parent Component
8 |
9 |
10 |
11 |
12 |
13 |
29 |
--------------------------------------------------------------------------------
/demo-app/src/components/ParentWithAPICallChild.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
--------------------------------------------------------------------------------
/demo-app/src/components/ParentWithManyChildren.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
19 |
--------------------------------------------------------------------------------
/demo-app/src/components/Posts.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ message }}
4 |
5 |
6 |
10 | New Post
11 |
12 |
13 |
14 |
Posts
15 |
20 |
21 | {{ post.title }}
22 |
23 |
24 |
25 |
26 |
27 |
51 |
--------------------------------------------------------------------------------
/demo-app/src/components/SubmitButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | Admin Privileges
4 | Not Authorized
5 |
6 | {{ msg }}
7 |
8 |
9 |
10 |
11 |
27 |
--------------------------------------------------------------------------------
/demo-app/src/createRouter.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 |
4 | Vue.use(VueRouter)
5 |
6 | const createRouter = () => {
7 | return new VueRouter({
8 | mode: 'history',
9 | base: process.env.BASE_URL,
10 | routes: []
11 | })
12 | }
13 |
14 | export { createRouter }
15 |
16 |
--------------------------------------------------------------------------------
/demo-app/src/createStore.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 |
4 | Vue.use(Vuex)
5 |
6 | const createStore = (initialState = {}) => new Vuex.Store({
7 | state: {
8 | authenticated: false,
9 | posts: [],
10 | ...initialState,
11 | },
12 | mutations: {
13 | ADD_POSTS(state, posts) {
14 | state.posts = [...state.posts, ...posts]
15 | },
16 |
17 | SET_AUTH(state, authenticated) {
18 | state.authenticated = authenticated
19 | },
20 | }
21 | })
22 |
23 | export { createStore }
24 |
25 |
26 |
--------------------------------------------------------------------------------
/demo-app/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueCompositionApi from '@vue/composition-api'
3 |
4 | import App from './App.vue'
5 | import router from "./router.js"
6 | import store from "./store/index.js"
7 |
8 | Vue.use(VueCompositionApi)
9 |
10 | Vue.config.productionTip = false
11 |
12 | new Vue({
13 | router,
14 | store,
15 | render: h => h(App)
16 | }).$mount('#app')
17 |
--------------------------------------------------------------------------------
/demo-app/src/router.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue"
2 | import VueRouter from "vue-router"
3 | import routes from "./routes.js"
4 | import { bustCache } from "@/bust-cache.js"
5 |
6 | Vue.use(VueRouter)
7 |
8 | const router = new VueRouter({ routes })
9 |
10 | export function beforeEach(to, from, next) {
11 | if (to.matched.some(record => record.meta.shouldBustCache)) {
12 | bustCache()
13 | }
14 | next()
15 | }
16 |
17 | router.beforeEach((to, from, next) => beforeEach(to, from, next))
18 |
19 |
20 | export default router
21 |
--------------------------------------------------------------------------------
/demo-app/src/routes.js:
--------------------------------------------------------------------------------
1 | import NestedRoute from "@/components/NestedRoute.vue"
2 |
3 | export default [
4 | {
5 | path: "/nested-route",
6 | component: NestedRoute,
7 | meta: {
8 | shouldBustCache: true
9 | }
10 | }
11 | ]
12 |
--------------------------------------------------------------------------------
/demo-app/src/store/actions.js:
--------------------------------------------------------------------------------
1 | import axios from "axios"
2 |
3 | export default {
4 | async authenticate({ commit }, { username, password }) {
5 | try {
6 | const authenticated = await axios.post("/api/authenticate", {
7 | username, password
8 | })
9 |
10 | commit("SET_AUTHENTICATED", authenticated)
11 | } catch (e) {
12 | throw Error("API Error occurred.")
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/demo-app/src/store/getters.js:
--------------------------------------------------------------------------------
1 | export default {
2 | poodles: (state) => {
3 | return state.dogs.filter(dog => dog.breed === "poodle")
4 | },
5 |
6 | poodlesByAge: (state, getters) => (age) => {
7 | return getters.poodles.filter(dog => dog.age === age)
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/demo-app/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue"
2 | import Vuex from "vuex"
3 | import getters from "./getters.js"
4 |
5 | Vue.use(Vuex)
6 |
7 | const state = {
8 | dogs: [
9 | { name: "lucky", breed: "poodle", age: 1 },
10 | { name: "pochy", breed: "dalmatian", age: 2 },
11 | { name: "blackie", breed: "poodle", age: 4 }
12 | ]
13 | }
14 |
15 | export default new Vuex.Store({
16 | state, getters
17 | })
18 |
--------------------------------------------------------------------------------
/demo-app/src/store/mutations.js:
--------------------------------------------------------------------------------
1 | export default {
2 | SET_POST(state, { post }) {
3 | state.postIds.push(post.id)
4 | state.posts = { ...state.posts, [post.id]: post }
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/demo-app/src/translations.js:
--------------------------------------------------------------------------------
1 | export default {
2 | "en": {
3 | helloWorld: "Hello world!"
4 | },
5 | "ja": {
6 | helloWorld: "こんにちは、世界!"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/App.spec.js:
--------------------------------------------------------------------------------
1 | import { mount, createLocalVue } from "@vue/test-utils"
2 | import App from "@/App.vue"
3 | import VueRouter from "vue-router"
4 | import NestedRoute from "@/components/NestedRoute.vue"
5 | import routes from "@/routes.js"
6 |
7 | const localVue = createLocalVue()
8 | localVue.use(VueRouter)
9 |
10 | jest.mock("@/components/NestedRoute.vue", () => ({
11 | name: "NestedRoute",
12 | render: h => h("div")
13 | }))
14 |
15 | describe("App", () => {
16 | it("renders a child component via routing", async () => {
17 | const router = new VueRouter({ routes })
18 | const wrapper = mount(App, {
19 | localVue,
20 | router
21 | })
22 |
23 | router.push("/nested-route")
24 | await wrapper.vm.$nextTick()
25 |
26 | expect(wrapper.findComponent(NestedRoute).exists()).toBe(true)
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/Bilingual.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import Bilingual from "@/components/Bilingual.vue"
3 |
4 | describe("Bilingual", () => {
5 | it("renders successfully", () => {
6 | const wrapper = mount(Bilingual)
7 |
8 | expect(wrapper.find(".hello").text()).not.toBe("")
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/ComponentWithButtons.spec.js:
--------------------------------------------------------------------------------
1 | import Vuex from "vuex"
2 | import { createLocalVue, mount } from "@vue/test-utils"
3 | import ComponentWithButtons from "@/components/ComponentWithButtons.vue"
4 |
5 | const localVue = createLocalVue()
6 | localVue.use(Vuex)
7 |
8 | const mutations = {
9 | testMutation: jest.fn()
10 | }
11 |
12 | const store = new Vuex.Store({
13 | mutations,
14 | })
15 |
16 | describe("ComponentWithButtons", () => {
17 |
18 | it("commits a mutation when a button is clicked", async () => {
19 | const wrapper = mount(ComponentWithButtons, {
20 | store, localVue
21 | })
22 |
23 | wrapper.find(".commit").trigger("click")
24 | await wrapper.vm.$nextTick()
25 |
26 | expect(mutations.testMutation).toHaveBeenCalledWith(
27 | {},
28 | { msg: "Test Commit" }
29 | )
30 | })
31 |
32 | it("dispatch a namespaced action when button is clicked", async () => {
33 | const store = new Vuex.Store()
34 | store.dispatch = jest.fn()
35 |
36 | const wrapper = mount(ComponentWithButtons, {
37 | store, localVue
38 | })
39 |
40 | wrapper.find(".namespaced-dispatch").trigger("click")
41 | await wrapper.vm.$nextTick()
42 |
43 | expect(store.dispatch).toHaveBeenCalledWith(
44 | 'namespaced/very/deeply/testAction',
45 | { msg: "Test Namespaced Dispatch" }
46 | )
47 | })
48 |
49 |
50 | it("dispatches an action when a button is clicked", async () => {
51 | const mockStore = { dispatch: jest.fn() }
52 | const wrapper = mount(ComponentWithButtons, {
53 | mocks: {
54 | $store: mockStore
55 | }
56 | })
57 |
58 | wrapper.find(".dispatch").trigger("click")
59 | await wrapper.vm.$nextTick()
60 |
61 | expect(mockStore.dispatch).toHaveBeenCalledWith(
62 | "testAction" , { msg: "Test Dispatch" })
63 | })
64 |
65 | })
66 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/ComponentWithVuex.spec.js:
--------------------------------------------------------------------------------
1 | import Vuex from "vuex"
2 | import { mount, createLocalVue } from "@vue/test-utils"
3 | import ComponentWithVuex from "@/components/ComponentWithVuex.vue"
4 | import ComponentWithGetters from "@/components/ComponentWithGetters.vue"
5 |
6 | const localVue = createLocalVue()
7 | localVue.use(Vuex)
8 |
9 | const store = new Vuex.Store({
10 | state: {
11 | username: "alice",
12 | firstName: "Alice",
13 | lastName: "Doe"
14 | },
15 |
16 | getters: {
17 | fullname: (state) => state.firstName + " " + state.lastName
18 | }
19 | })
20 |
21 | describe("ComponentWithVuex", () => {
22 | it("renders a username using a real Vuex store", () => {
23 | const wrapper = mount(ComponentWithVuex, { store, localVue })
24 |
25 | expect(wrapper.find(".username").text()).toBe("alice")
26 | })
27 |
28 | it("renders a username using a mock store", () => {
29 | const wrapper = mount(ComponentWithVuex, {
30 | mocks: {
31 | $store: {
32 | state: { username: "alice" }
33 | }
34 | }
35 | })
36 |
37 | expect(wrapper.find(".username").text()).toBe("alice")
38 | })
39 | })
40 |
41 | describe("ComponentWithGetters", () => {
42 | it("renders a username using a real Vuex getter", () => {
43 | const wrapper = mount(ComponentWithGetters, { store, localVue })
44 |
45 | expect(wrapper.find(".fullname").text()).toBe("Alice Doe")
46 | })
47 |
48 | it("renders a username using computed mounting options", () => {
49 | const wrapper = mount(ComponentWithGetters, {
50 | mocks: {
51 | $store: {
52 | getters: {
53 | fullname: "Alice Doe"
54 | }
55 | }
56 | }
57 | })
58 |
59 | expect(wrapper.find(".fullname").text()).toBe("Alice Doe")
60 | })
61 |
62 | it("renders a username using computed mounting options", () => {
63 | const wrapper = mount(ComponentWithGetters, {
64 | computed: {
65 | fullname: () => "Alice Doe"
66 | }
67 | })
68 |
69 | expect(wrapper.find(".fullname").text()).toBe("Alice Doe")
70 | })
71 | })
72 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/CompositionApi.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount } from "@vue/test-utils"
2 |
3 | import CompositionApi from "@/components/CompositionApi.vue"
4 |
5 | describe("CompositionApi", () => {
6 | it("increments a count when button is clicked", async () => {
7 | const wrapper = shallowMount(CompositionApi, {
8 | propsData: { message: '' }
9 | })
10 |
11 | wrapper.find('button').trigger('click')
12 | await wrapper.vm.$nextTick()
13 |
14 | expect(wrapper.find(".count").text()).toBe("Count: 1")
15 | })
16 |
17 | it("renders a message", async () => {
18 | const wrapper = shallowMount(CompositionApi, {
19 | propsData: {
20 | message: "Testing the composition API"
21 | }
22 | })
23 |
24 | expect(wrapper.find(".message").text()).toBe("TESTING THE COMPOSITION API")
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/Emitter.spec.js:
--------------------------------------------------------------------------------
1 | import Emitter from "@/components/Emitter.vue"
2 | import { mount } from "@vue/test-utils"
3 |
4 | describe("Emitter", () => {
5 | it("emits an event with two arguments", () => {
6 | const wrapper = mount(Emitter)
7 |
8 | wrapper.vm.emitEvent()
9 |
10 | expect(wrapper.emitted().myEvent[0]).toEqual(["name", "password"])
11 | })
12 |
13 | it("emits an event without mounting the component", () => {
14 | const events = {}
15 | const $emit = (event, ...args) => { events[event] = [...args] }
16 |
17 | Emitter.methods.emitEvent.call({ $emit })
18 |
19 | expect(events.myEvent).toEqual(["name", "password"])
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/FormSubmitter.spec.js:
--------------------------------------------------------------------------------
1 | import flushPromises from "flush-promises"
2 | import { mount } from "@vue/test-utils"
3 | import FormSubmitter from "@/components/FormSubmitter.vue"
4 |
5 | let url = ''
6 | let data = ''
7 |
8 | const mockHttp = {
9 | get: (_url, _data) => {
10 | return new Promise((resolve) => {
11 | url = _url
12 | data = _data
13 | resolve()
14 | })
15 | }
16 | }
17 |
18 | describe("FormSubmitter", () => {
19 | it("reveals a notification when submitted", async () => {
20 | const wrapper = mount(FormSubmitter)
21 |
22 | wrapper.find("[data-username]").setValue("alice")
23 | wrapper.find("form").trigger("submit.prevent")
24 | await wrapper.vm.$nextTick()
25 |
26 | expect(wrapper.find(".message").text())
27 | .toBe("Thank you for your submission, alice.")
28 | })
29 |
30 | it("reveals a notification when submitted", async () => {
31 | const wrapper = mount(FormSubmitter, {
32 | data() {
33 | return {
34 | asyncTest: true
35 | }
36 | },
37 | mocks: {
38 | $http: mockHttp
39 | }
40 | })
41 |
42 | wrapper.find("[data-username]").setValue("alice")
43 | wrapper.find("form").trigger("submit.prevent")
44 |
45 | await flushPromises()
46 |
47 | expect(wrapper.find(".message").text())
48 | .toBe("Thank you for your submission, alice.")
49 | expect(url).toBe("/api/v1/register")
50 | expect(data).toEqual({ username: "alice" })
51 | })
52 | })
53 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/Greeting.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from '@vue/test-utils'
2 | import Greeting from '@/components/Greeting.vue'
3 |
4 | describe('Greeting.vue', () => {
5 | it('renders a greeting', () => {
6 | const wrapper = mount(Greeting)
7 |
8 | expect(wrapper.text()).toMatch("Vue and TDD")
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/NestedRoute.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount, createLocalVue } from "@vue/test-utils"
2 | import VueRouter from "vue-router"
3 | import NestedRoute from "@/components/NestedRoute.vue"
4 | import mockModule from "@/bust-cache.js"
5 |
6 | jest.mock("@/bust-cache.js", () => ({ bustCache: jest.fn() }))
7 | const localVue = createLocalVue()
8 | localVue.use(VueRouter)
9 |
10 | describe("NestedRoute", () => {
11 | it("renders a username from query string", () => {
12 | const username = "alice"
13 | const wrapper = shallowMount(NestedRoute, {
14 | mocks: {
15 | $route: {
16 | params: { username }
17 | }
18 | }
19 | })
20 |
21 | expect(wrapper.find(".username").text()).toBe(username)
22 | })
23 |
24 | it("calls bustCache and next when leaving the route", () => {
25 | const next = jest.fn()
26 | NestedRoute.beforeRouteLeave(undefined, undefined, next)
27 |
28 | expect(mockModule.bustCache).toHaveBeenCalled()
29 | expect(next).toHaveBeenCalled()
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/NumberRenderer.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import NumberRenderer from "@/components/NumberRenderer.vue"
3 |
4 | describe("NumberRenderer", () => {
5 | it("renders even numbers", () => {
6 | const wrapper = mount(NumberRenderer, {
7 | propsData: {
8 | even: true
9 | }
10 | })
11 |
12 | expect(wrapper.text()).toBe("2, 4, 6, 8")
13 | })
14 |
15 | it("renders odd numbers", () => {
16 | const localThis = { even: false }
17 |
18 | expect(NumberRenderer.computed.numbers.call(localThis))
19 | .toBe("1, 3, 5, 7, 9")
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/Parent.spec.js:
--------------------------------------------------------------------------------
1 | import { mount } from "@vue/test-utils"
2 | import '@testing-library/jest-dom'
3 | import Parent from "@/components/Parent.vue"
4 | import ParentWithManyChildren from "@/components/ParentWithManyChildren.vue"
5 | import Child from "@/components/Child.vue"
6 |
7 | describe("Parent", () => {
8 | it("does not render a span", () => {
9 | const wrapper = mount(Parent)
10 |
11 | expect(wrapper.find("span").element).not.toBeVisible()
12 | })
13 |
14 | it("does render a span", () => {
15 | const wrapper = mount(Parent, {
16 | data() {
17 | return { showSpan: true }
18 | }
19 | })
20 |
21 | expect(wrapper.find("span").element).toBeVisible()
22 | })
23 |
24 | it("does not render a Child component", () => {
25 | const wrapper = mount(Parent)
26 |
27 | expect(wrapper.findComponent(Child).exists()).toBe(false)
28 | })
29 |
30 | it("renders a Child component", () => {
31 | const wrapper = mount(Parent, {
32 | data() {
33 | return { showChild: true }
34 | }
35 | })
36 |
37 | expect(wrapper.findComponent({ name: "Child" }).exists()).toBe(true)
38 | })
39 | })
40 |
41 | describe("ParentWithManyChildren", () => {
42 | it("renders many children", () => {
43 | const wrapper = mount(ParentWithManyChildren)
44 |
45 | expect(wrapper.findAllComponents(Child).length).toBe(3)
46 | })
47 | })
48 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/ParentWithAPICallChild.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount, mount } from '@vue/test-utils'
2 | import ParentWithAPICallChild from '@/components/ParentWithAPICallChild.vue'
3 | import ComponentWithAsyncCall from '@/components/ComponentWithAsyncCall.vue'
4 |
5 | describe('ParentWithAPICallChild.vue', () => {
6 | it('renders with mount and does initialize API call', () => {
7 | const wrapper = mount(ParentWithAPICallChild, {
8 | stubs: {
9 | ComponentWithAsyncCall: true
10 | }
11 | })
12 |
13 | expect(wrapper.findComponent(ComponentWithAsyncCall).exists()).toBe(true)
14 | })
15 |
16 | it('renders with shallowMount and does not initialize API call', () => {
17 | const wrapper = shallowMount(ParentWithAPICallChild)
18 |
19 | expect(wrapper.findComponent(ComponentWithAsyncCall).exists()).toBe(true)
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/Posts.spec.js:
--------------------------------------------------------------------------------
1 | import Vuex from 'vuex'
2 | import VueRouter from 'vue-router'
3 | import { mount, createLocalVue } from '@vue/test-utils'
4 |
5 | import Posts from '@/components/Posts.vue'
6 | import { createRouter } from '@/createRouter'
7 | import { createStore } from '@/createStore'
8 |
9 | const createWrapper = (component, options = {}, storeState = {}) => {
10 | const localVue = createLocalVue()
11 | localVue.use(VueRouter)
12 | localVue.use(Vuex)
13 | const store = createStore(storeState)
14 | const router = createRouter()
15 |
16 | return mount(component, {
17 | store,
18 | router,
19 | localVue,
20 | ...options
21 | })
22 | }
23 |
24 | describe('Posts.vue', () => {
25 | it('renders a message if passed', () => {
26 | const message = 'New content coming soon!'
27 | const wrapper = createWrapper(Posts, {
28 | propsData: { message },
29 | })
30 |
31 | expect(wrapper.find("#message").text()).toBe('New content coming soon!')
32 | })
33 |
34 | it('renders posts', async () => {
35 | const wrapper = createWrapper(Posts, {}, {
36 | posts: [{ id: 1, title: 'Post' }]
37 | })
38 |
39 | expect(wrapper.findAll('.post').length).toBe(1)
40 | })
41 |
42 | it('renders new post link if authenticated', async () => {
43 | const wrapper = createWrapper(Posts, {}, {
44 | authenticated: true
45 | })
46 |
47 | expect(wrapper.find('.new-post').exists()).toBe(true)
48 | })
49 |
50 | it('does not render new post link if not authenticated', async () => {
51 | const wrapper = createWrapper(Posts, {}, {
52 | authenticated: false
53 | })
54 |
55 | expect(wrapper.find('.new-post').exists()).toBe(false)
56 | })
57 | })
58 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/SubmitButton.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount } from '@vue/test-utils'
2 | import SubmitButton from '@/components/SubmitButton.vue'
3 |
4 | const msg = "submit"
5 | const factory = (propsData) => {
6 | return shallowMount(SubmitButton, {
7 | propsData: {
8 | msg,
9 | ...propsData
10 | }
11 | })
12 | }
13 |
14 | describe("SubmitButton", () => {
15 | describe("does not have admin privileges", ()=> {
16 | it("renders a message", () => {
17 | const wrapper = factory()
18 |
19 | expect(wrapper.find("span").text()).toBe("Not Authorized")
20 | expect(wrapper.find("button").text()).toBe("submit")
21 | })
22 | })
23 |
24 | describe("has admin privileges", ()=> {
25 | it("renders a message", () => {
26 | const wrapper = factory({ isAdmin: true })
27 |
28 | expect(wrapper.find("span").text()).toBe("Admin Privileges")
29 | expect(wrapper.find("button").text()).toBe("submit")
30 | })
31 | })
32 | })
33 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/actions.spec.js:
--------------------------------------------------------------------------------
1 | import actions from "@/store/actions.js"
2 |
3 | let url = ''
4 | let body = {}
5 | let mockError = false
6 |
7 | jest.mock("axios", () => ({
8 | post: (_url, _body) => {
9 | return new Promise((resolve) => {
10 | if (mockError)
11 | throw Error("Mock error")
12 |
13 | url = _url
14 | body = _body
15 | resolve(true)
16 | })
17 | }
18 | }))
19 |
20 |
21 | describe("authenticate", () => {
22 | it("authenticated a user", async () => {
23 | const commit = jest.fn()
24 | const username = "alice"
25 | const password = "password"
26 |
27 | await actions.authenticate({ commit }, { username, password })
28 |
29 | expect(url).toBe("/api/authenticate")
30 | expect(body).toEqual({ username, password })
31 | expect(commit).toHaveBeenCalledWith(
32 | "SET_AUTHENTICATED", true)
33 | })
34 |
35 | it("catches an error", async () => {
36 | mockError = true
37 |
38 | await expect(actions.authenticate({ commit: jest.fn() }, {}))
39 | .rejects.toThrow("API Error occurred.")
40 | })
41 | })
42 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/getters.spec.js:
--------------------------------------------------------------------------------
1 | import getters from "../../src/store/getters.js"
2 |
3 | const dogs = [
4 | { name: "lucky", breed: "poodle", age: 1 },
5 | { name: "pochy", breed: "dalmatian", age: 2 },
6 | { name: "blackie", breed: "poodle", age: 4 }
7 | ]
8 | const state = { dogs }
9 |
10 | describe("poodles", () => {
11 | it("returns poodles", () => {
12 | const actual = getters.poodles(state)
13 |
14 | expect(actual).toEqual([ dogs[0], dogs[2] ])
15 | })
16 | })
17 |
18 | describe("poodlesByAge", () => {
19 | it("returns poodles by age", () => {
20 | const poodles = [ dogs[0], dogs[2] ]
21 | const actual = getters.poodlesByAge(state, { poodles })(1)
22 |
23 | expect(actual).toEqual([ dogs[0] ])
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/mutations.spec.js:
--------------------------------------------------------------------------------
1 | import mutations from "@/store/mutations.js"
2 |
3 | describe("SET_POST", () => {
4 | it("adds a post to the state", () => {
5 | const post = { id: 1, title: "Post" }
6 | const state = {
7 | postIds: [],
8 | posts: {}
9 | }
10 |
11 | mutations.SET_POST(state, { post })
12 |
13 | expect(state).toEqual({
14 | postIds: [1],
15 | posts: { "1": post }
16 | })
17 | })
18 | })
19 |
--------------------------------------------------------------------------------
/demo-app/tests/unit/router-hooks.spec.js:
--------------------------------------------------------------------------------
1 | import { beforeEach } from "@/router.js"
2 | import mockModule from "@/bust-cache.js"
3 |
4 | jest.mock("@/bust-cache.js", () => ({ bustCache: jest.fn() }))
5 |
6 | describe("beforeEach", () => {
7 | afterEach(() => {
8 | mockModule.bustCache.mockClear()
9 | })
10 |
11 | it("busts the cache when going to /user", () => {
12 | const to = {
13 | matched: [{ meta: { shouldBustCache: true } }]
14 | }
15 | const next = jest.fn()
16 |
17 | beforeEach(to, undefined, next)
18 |
19 | expect(mockModule.bustCache).toHaveBeenCalled()
20 | expect(next).toHaveBeenCalled()
21 | })
22 |
23 | it("busts the cache when going to /user", () => {
24 | const to = {
25 | matched: [{ meta: { shouldBustCache: false } }]
26 | }
27 | const next = jest.fn()
28 |
29 | beforeEach(to, undefined, next)
30 |
31 | expect(mockModule.bustCache).not.toHaveBeenCalled()
32 | expect(next).toHaveBeenCalled()
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | # abort on errors
4 | set -e
5 |
6 | # build
7 | yarn docs:build
8 |
9 | # navigate into the build output directory
10 | rm -rf docs
11 |
12 | mv src/.vuepress/dist docs
13 |
14 | # if you are deploying to a custom domain
15 | # echo 'www.example.com' > CNAME
16 |
17 | git add -A
18 | git commit -m 'deploy'
19 |
20 | # if you are deploying to https://.github.io/
21 | git push
22 |
--------------------------------------------------------------------------------
/docs/ad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/docs/ad.png
--------------------------------------------------------------------------------
/docs/assets/img/search.83621669.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/assets/js/101.1158b189.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[101],{328:function(t,e,v){"use strict";v.r(e);var s=v(0),a=Object(s.a)({},(function(){var t=this,e=t.$createElement,v=t._self._c||e;return v("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[v("div",{staticClass:"custom-block tip"},[v("p",{staticClass:"custom-block-title"},[t._v("Это руководство было написано для Vue.js 3 и Vue Test Utils v2.")]),t._v(" "),v("p",[t._v("Версия для Vue.js 2 "),v("a",{attrs:{href:"/ru"}},[t._v("здесь")]),t._v(".")])]),t._v(" "),v("h2",{attrs:{id:"тестирование-vuex"}},[v("a",{staticClass:"header-anchor",attrs:{href:"#тестирование-vuex"}},[t._v("#")]),t._v(" Тестирование Vuex")]),t._v(" "),v("p",[t._v("В следующих статьях обсудим как тестировать Vuex.")]),t._v(" "),v("h2",{attrs:{id:"две-стороны-тестирования-vuex"}},[v("a",{staticClass:"header-anchor",attrs:{href:"#две-стороны-тестирования-vuex"}},[t._v("#")]),t._v(" Две стороны тестирования Vuex")]),t._v(" "),v("p",[t._v("Обычно компоненты взаимодействуют с Vuex так:")]),t._v(" "),v("ol",[v("li",[t._v("вызываем мутацию через "),v("code",[t._v("commit")])]),t._v(" "),v("li",[t._v("запускаем действия через "),v("code",[t._v("dispatch")])]),t._v(" "),v("li",[t._v("обращаемся к хранилищу через "),v("code",[t._v("$store.state")]),t._v(" или геттеры")])]),t._v(" "),v("p",[t._v("В тестах проверяется, что компонент ведёт себя правильно при текущем состоянии хранилища Vuex. Они не должны знать о реализациях мутаций, действий, геттеров.")]),t._v(" "),v("p",[t._v("Любая логика хранилища, такая как мутация или использование геттеров, должна тестироваться в изоляции. Для Vuex достаточно просто писать модульные тесты, так как он хранит в себе простые JavaScript функции.")]),t._v(" "),v("p",[t._v("В следующей статье разберём некоторые техники тестирования компонентов с использованием Vuex, убедимся, что их поведение зависит от состояния хранилища. Чуть позже обсудим тестирование Vuex в изоляции.")])])}),[],!1,null,null,null);e.default=a.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/11.70c52248.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[11],{219:function(e,t,v){"use strict";v.r(t);var _=v(0),s=Object(_.a)({},(function(){var e=this,t=e.$createElement,v=e._self._c||t;return v("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[v("h2",{attrs:{id:"vue-jsテストハンドブック"}},[v("a",{staticClass:"header-anchor",attrs:{href:"#vue-jsテストハンドブック"}},[e._v("#")]),e._v(" Vue.jsテストハンドブック")]),e._v(" "),v("p",[e._v("Vue.jsテストハンドブックにようこそ!")]),e._v(" "),v("p",[e._v("このハンドブックはVueコンポーネントをどうテストするか簡単な例の集めたものです。コンポーネントをテストする公式ライブラリーの"),v("code",[e._v("vue-test-utils")]),e._v("とモダーンテストフレームワークのJestを使います。"),v("code",[e._v("vue-test-utils")]),e._v("のAPIとコンポーネントのテストの最適な実践を紹介します。")]),e._v(" "),v("p",[e._v("各セクションはその他のセクションとは独立してます。最初は"),v("code",[e._v("vue-cli")]),e._v("をインストールしてテスト環境を準備してから最初のテストを書きます。そしてコンポーネントをレンダーする"),v("code",[e._v("mount")]),e._v("と"),v("code",[e._v("shallowMount")]),e._v("の2つの方法とそれぞれの違いを説明します。")]),e._v(" "),v("p",[e._v("続いてコンポーネントをテストするときによくある場面を紹介します。例えば:")]),e._v(" "),v("ul",[v("li",[v("code",[e._v("props")]),e._v("を受け取る")]),e._v(" "),v("li",[e._v("算出プロパティ")]),e._v(" "),v("li",[e._v("別のコンポーネントをレンダーする")]),e._v(" "),v("li",[e._v("イベントを"),v("code",[e._v("emit")]),e._v("する")])]),e._v(" "),v("p",[e._v("そのあと、次でもっと興味深いケースを見てみます。例えば:")]),e._v(" "),v("ul",[v("li",[e._v("Vuexをテストするベストプラクティス(コンポーネントと、コンポーネント以外)")]),e._v(" "),v("li",[e._v("Vueルーターのテスト")]),e._v(" "),v("li",[e._v("第三者のコンポーネントのテスト")])]),e._v(" "),v("p",[e._v("JestのAPIでテストをもっと安定させる方法も紹介します。例えば:")]),e._v(" "),v("ul",[v("li",[e._v("APIレスポンスをモックする")]),e._v(" "),v("li",[e._v("モジュールのモックとスパイ")]),e._v(" "),v("li",[e._v("スナップショット")])]),e._v(" "),v("h2",{attrs:{id:"他の参考"}},[v("a",{staticClass:"header-anchor",attrs:{href:"#他の参考"}},[e._v("#")]),e._v(" 他の参考")]),e._v(" "),v("ul",[v("li",[v("a",{attrs:{href:"https://vue-test-utils.vuejs.org/",target:"_blank",rel:"noopener noreferrer"}},[e._v("公式のドキュメント"),v("OutboundLink")],1)]),e._v(" "),v("li",[v("a",{attrs:{href:"https://www.manning.com/books/testing-vue-js-applications",target:"_blank",rel:"noopener noreferrer"}},[v("code",[e._v("vue-test-utils")]),e._v("を作った人が書いた本"),v("OutboundLink")],1),e._v(" (英語)")]),e._v(" "),v("li",[v("a",{attrs:{href:"https://vueschool.io/courses/learn-how-to-test-vuejs-components?friend=vth",target:"_blank",rel:"noopener noreferrer"}},[e._v("VueSchoolの"),v("code",[e._v("vue-test-utils")]),e._v("のコース"),v("OutboundLink")],1),e._v(" (英語)")])])])}),[],!1,null,null,null);t.default=s.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/112.99b57508.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[112],{320:function(t,e,s){"use strict";s.r(e);var o=s(0),i=Object(o.a)({},(function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[s("div",{staticClass:"custom-block tip"},[s("p",{staticClass:"custom-block-title"},[t._v("This book is written for Vue.js 3 and Vue Test Utils v2.")]),t._v(" "),s("p",[t._v("Find the Vue.js 2 version "),s("router-link",{attrs:{to:"/"}},[t._v("here")]),t._v(".")],1)]),t._v(" "),s("h2",{attrs:{id:"testing-vuex"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#testing-vuex"}},[t._v("#")]),t._v(" Testing Vuex")]),t._v(" "),s("p",[t._v("The next few guides discuss testing Vuex.")]),t._v(" "),s("h2",{attrs:{id:"two-sides-of-testing-vuex"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#two-sides-of-testing-vuex"}},[t._v("#")]),t._v(" Two Sides of Testing Vuex")]),t._v(" "),s("p",[t._v("Generally components will interact with Vuex by")]),t._v(" "),s("ol",[s("li",[t._v("committing a mutation")]),t._v(" "),s("li",[t._v("dispatching an action")]),t._v(" "),s("li",[t._v("access the state via "),s("code",[t._v("$store.state")]),t._v(" or getters")])]),t._v(" "),s("p",[t._v("These tests are to assert that the component behaves correctly based on the current state of the Vuex store. They do not need to know about the implementation of the mutators, actions or getters.")]),t._v(" "),s("p",[t._v("Any logic performed by the store, such as mutations and getters, can be tested in isolation. Since Vuex stores are comprised of regular JavaScript functions, they are easily unit tested.")]),t._v(" "),s("p",[t._v("The first few guides discuss techniques to test Vuex in isolation considering mutations, actions and getters. Following guides introduce some techniques to test components that use a Vuex store, and ensure they behave correctly based on the store's state.")])])}),[],!1,null,null,null);e.default=i.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/125.65b52754.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[125],{332:function(e,t,r){"use strict";r.r(t);var v=r(0),o=Object(v.a)({},(function(){var e=this,t=e.$createElement,r=e._self._c||t;return r("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[r("h2",{attrs:{id:"这本指南是什么?"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#这本指南是什么?"}},[e._v("#")]),e._v(" 这本指南是什么?")]),e._v(" "),r("p",[e._v("欢迎来到 Vue.js 测试指南!")]),e._v(" "),r("p",[e._v("这是一系列关于如何测试 Vue 组件的短小而目标明确的示例。它使用了测试 Vue 组件的官方库 "),r("a",{attrs:{href:"https://github.com/vuejs/vue-test-utils",target:"_blank",rel:"noopener noreferrer"}},[r("code",[e._v("vue-test-utils")]),r("OutboundLink")],1),e._v(",以及一个现代测试框架 "),r("a",{attrs:{href:"https://jestjs.io/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Jest"),r("OutboundLink")],1),e._v("。本书不但涵盖了 "),r("code",[e._v("vue-test-utils")]),e._v(" 的 API,而且也是测试组件的最佳实践。")]),e._v(" "),r("p",[e._v("每个章节都是互相独立的。我们从通过 "),r("code",[e._v("vue-cli")]),e._v(" 设置一个环境并编写一个简单的测试起步。而后,讨论了两种渲染一个组件的方式 -- "),r("code",[e._v("mount")]),e._v(" 和 "),r("code",[e._v("shallowMount")]),e._v("。其区别将被演示和说明。")]),e._v(" "),r("p",[e._v("从那时起,我们覆盖了如何应对测试组件时出现的各种情景,诸如:")]),e._v(" "),r("ul",[r("li",[e._v("接受 props")]),e._v(" "),r("li",[e._v("使用 computed 属性")]),e._v(" "),r("li",[e._v("渲染其他组件")]),e._v(" "),r("li",[e._v("emit 事件")])]),e._v(" "),r("p",[e._v("等等。其后我们继续向更多有趣的场景进发,比如:")]),e._v(" "),r("ul",[r("li",[e._v("测试 Vuex (在组件中,并且是独立的) 的最佳实践")]),e._v(" "),r("li",[e._v("测试 Vue router")]),e._v(" "),r("li",[e._v("测试包含的第三方组件")])]),e._v(" "),r("p",[e._v("我们也将探索如何使用 Jest API 让我们的测试更健壮,如:")]),e._v(" "),r("ul",[r("li",[e._v("mocking API 响应")]),e._v(" "),r("li",[e._v("模块上的 mocking 和 spying")]),e._v(" "),r("li",[e._v("使用 snapshots")])]),e._v(" "),r("h2",{attrs:{id:"延伸阅读"}},[r("a",{staticClass:"header-anchor",attrs:{href:"#延伸阅读"}},[e._v("#")]),e._v(" 延伸阅读")]),e._v(" "),r("p",[e._v("更多有用的资源包括:")]),e._v(" "),r("ul",[r("li",[r("a",{attrs:{href:"https://vue-test-utils.vuejs.org/",target:"_blank",rel:"noopener noreferrer"}},[e._v("Official docs"),r("OutboundLink")],1)]),e._v(" "),r("li",[r("a",{attrs:{href:"https://www.manning.com/books/testing-vue-js-applications",target:"_blank",rel:"noopener noreferrer"}},[e._v("Book"),r("OutboundLink")],1),e._v(" 另一位作者写的 "),r("code",[e._v("vue-test-utils")])]),e._v(" "),r("li",[r("a",{attrs:{href:"https://vueschool.io/courses/learn-how-to-test-vuejs-components?friend=vth",target:"_blank",rel:"noopener noreferrer"}},[e._v("This awesome course on VueSchool"),r("OutboundLink")],1),e._v(" 由多位 Vue 核心贡献者编写")])])])}),[],!1,null,null,null);t.default=o.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/13.3a3f3209.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[13],{222:function(t,e,n){"use strict";n.r(e);var s=n(0),l=Object(s.a)({},(function(){var t=this.$createElement,e=this._self._c||t;return e("ContentSlotsDistributor",{attrs:{"slot-key":this.$parent.slotKey}},[e("p",[this._v("TODO: Translate.")])])}),[],!1,null,null,null);e.default=l.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/130.f908d7c1.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[130],{350:function(e,t,s){"use strict";s.r(t);var o=s(0),i=Object(o.a)({},(function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[s("h2",{attrs:{id:"mocking-modules"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#mocking-modules"}},[e._v("#")]),e._v(" Mocking Modules")]),e._v(" "),s("p",[e._v("TODO: Write this article. Interested? Open an issue, let's chat!")]),e._v(" "),s("p",[e._v("Not directly related to Vue testing, more specific to Jest. Jest is used heavily throughout this handbook, though, and becoming widely adopted in the industry, so I think it deserves a section.")]),e._v(" "),s("p",[e._v("Some ideas:")]),e._v(" "),s("ol",[s("li",[e._v("Reference the sections of the existing guide using Jest")]),e._v(" "),s("li",[e._v("Talk about the different kind of mocks and their use cases for Vue testing")])]),e._v(" "),s("ul",[s("li",[e._v("Manual mocks")]),e._v(" "),s("li",[e._v("ES6 class mocks")]),e._v(" "),s("li",[e._v("automatic mock")]),e._v(" "),s("li",[e._v("calling "),s("code",[e._v("mockImplentation")]),e._v("?")])]),e._v(" "),s("p",[e._v("Ref: https://jestjs.io/docs/en/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock")])])}),[],!1,null,null,null);t.default=i.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/137.4ccfc9be.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[137],{341:function(t,e,s){"use strict";s.r(e);var a=s(0),r=Object(a.a)({},(function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[s("h2",{attrs:{id:"测试-vuex"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#测试-vuex"}},[t._v("#")]),t._v(" 测试 Vuex")]),t._v(" "),s("p",[t._v("下面几篇将会讨论测试 Vuex。")]),t._v(" "),s("h2",{attrs:{id:"测试-vuex-的两方面"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#测试-vuex-的两方面"}},[t._v("#")]),t._v(" 测试 Vuex 的两方面")]),t._v(" "),s("p",[t._v("通常来说组件会在以下方面和 Vuex 发生交互:")]),t._v(" "),s("ol",[s("li",[t._v("commit 一个 mutation")]),t._v(" "),s("li",[t._v("dispatch 一个 action")]),t._v(" "),s("li",[t._v("通过 "),s("code",[t._v("$store.state")]),t._v(" 或 getters 访问 state")])]),t._v(" "),s("p",[t._v("这些测试都是基于 Vuex store 的当前 state 来断言组件行为是否正常的。它们并不需要知道 mutators、actions 或 getters 的实现。")]),t._v(" "),s("p",[t._v("store 所执行的任何逻辑,诸如 mutations 和 getters,都能被单独地测试。因为 Vuex stores 由普通 JavaScript 函数组成,所以它们易于被单元测试。")]),t._v(" "),s("p",[t._v("下一篇介绍了一些测试使用了 Vuex store 的组件、并确保它们按 store 的 state 产生正确行为的技术。其后的章节则讨论隔离地测试 Vuex。")])])}),[],!1,null,null,null);e.default=r.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/144.c69a73ac.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[144],{9:function(n,w,o){}}]);
--------------------------------------------------------------------------------
/docs/assets/js/15.a348c542.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[15],{221:function(n,e,t){"use strict";t.r(e);var o=t(0),i=Object(o.a)({},(function(){var n=this,e=n.$createElement,t=n._self._c||e;return t("ContentSlotsDistributor",{attrs:{"slot-key":n.$parent.slotKey}},[t("h2",{attrs:{id:"finding-elements-and-components"}},[t("a",{staticClass:"header-anchor",attrs:{href:"#finding-elements-and-components"}},[n._v("#")]),n._v(" Finding elements and components")]),n._v(" "),t("p",[n._v("Talk about")]),n._v(" "),t("ul",[t("li",[n._v("how to use "),t("code",[n._v("find")]),n._v(" and "),t("code",[n._v("findAll")])]),n._v(" "),t("li",[t("code",[n._v('find(".class")')])]),n._v(" "),t("li",[t("code",[n._v("find(VueComponent)")])]),n._v(" "),t("li",[t("code",[n._v('find({ name: "component-name" })')])]),n._v(" "),t("li",[n._v("give examples with "),t("code",[n._v("shallowMount")]),n._v(" and "),t("code",[n._v("mount")])])]),n._v(" "),t("p",[n._v("Talk about stubbing briefly, then in more detail in the next section.")])])}),[],!1,null,null,null);e.default=i.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/16.9e1d398c.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[16],{224:function(t,s,e){"use strict";e.r(s);var o=e(0),n=Object(o.a)({},(function(){var t=this.$createElement,s=this._self._c||t;return s("ContentSlotsDistributor",{attrs:{"slot-key":this.$parent.slotKey}},[s("h2",{attrs:{id:"mocking-modules"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#mocking-modules"}},[this._v("#")]),this._v(" Mocking Modules")]),this._v(" "),s("p",[this._v("Todo")])])}),[],!1,null,null,null);s.default=n.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/17.7b555f73.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[17],{223:function(t,e,o){"use strict";o.r(e);var l=o(0),s=Object(l.a)({},(function(){var t=this,e=t.$createElement,o=t._self._c||e;return o("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[o("h2",{attrs:{id:"mocking-global-objects"}},[o("a",{staticClass:"header-anchor",attrs:{href:"#mocking-global-objects"}},[t._v("#")]),t._v(" Mocking global objects")]),t._v(" "),o("p",[t._v("ideas:")]),t._v(" "),o("ul",[o("li",[t._v("$store")]),t._v(" "),o("li",[t._v("$router")]),t._v(" "),o("li",[t._v("$t (vue i18n)")])])])}),[],!1,null,null,null);e.default=s.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/21.e6041a2f.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[21],{228:function(t,n,s){"use strict";s.r(n);var e=s(0),a=Object(e.a)({},(function(){var t=this,n=t.$createElement,s=t._self._c||n;return s("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[s("h2",{attrs:{id:"stubbing-components"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#stubbing-components"}},[t._v("#")]),t._v(" Stubbing components")]),t._v(" "),s("p",[t._v("Talk about")]),t._v(" "),s("ul",[s("li",[t._v("how to use "),s("code",[t._v("stubs")]),t._v(" and the different apis:")])]),t._v(" "),s("div",{staticClass:"language- extra-class"},[s("pre",{pre:!0,attrs:{class:"language-text"}},[s("code",[t._v('const BMock = {\n name: "B",\n render: h => h("div")\n}\n\nconst wrapper = shallowMount(Foo, {\n stubs: {\n A: "
",\n B: BMock,\n C: true\n }\n})\n')])])]),s("ul",[s("li",[t._v("Talk about why/when to use stubs.")])])])}),[],!1,null,null,null);n.default=a.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/23.a04a99b1.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[23],{232:function(t,e,a){"use strict";a.r(e);var s=a(0),r=Object(s.a)({},(function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[a("h2",{attrs:{id:"vuex-のテスト"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#vuex-のテスト"}},[t._v("#")]),t._v(" Vuex のテスト")]),t._v(" "),a("p",[t._v("これ以降しばらくは、Vuex のテストについて解説していきます。")]),t._v(" "),a("h2",{attrs:{id:"vuex-のテストにおける二つの側面"}},[a("a",{staticClass:"header-anchor",attrs:{href:"#vuex-のテストにおける二つの側面"}},[t._v("#")]),t._v(" Vuex のテストにおける二つの側面")]),t._v(" "),a("p",[t._v("一般的にいってコンポーネントは Vuex と以下の方法でやりとりをしています。")]),t._v(" "),a("ol",[a("li",[t._v("mutation へ commit する")]),t._v(" "),a("li",[t._v("action を dispatching する")]),t._v(" "),a("li",[a("code",[t._v("$store.state")]),t._v(" もしくは getters 用いて state にアクセスする")])]),t._v(" "),a("p",[t._v("コンポーネントについてのテストを書く場合には、コンポーネントが Vuex store の現在の state に基づいて正しく動作していてることを assert すればいいわけです。ですから mutation や action や getter がどのように実装されているかを知る必要はありません。(訳注: コンポーネントのテストを書く際に、mutation 等々のテストを含める必要はないということ。)")]),t._v(" "),a("p",[t._v("それにたいして、store が遂行するロジック、例えば mutation や getter に関しては、それ自体を単独でテストをすることができます。なぜなら Vuex の store は通常の JavaScript の関数によって構成されているからで、それゆえユニットテスをするのも簡単です。")]),t._v(" "),a("p",[t._v("まずは Vuex 単独のテストについて説明していきます。後半では、Vuex store を使ったコンポーネントのテスト技法を取り上げます。")])])}),[],!1,null,null,null);e.default=r.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/24.a7c75161.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[24],{229:function(t,e,s){"use strict";s.r(e);var r=s(0),u=Object(r.a)({},(function(){var t=this.$createElement,e=this._self._c||t;return e("ContentSlotsDistributor",{attrs:{"slot-key":this.$parent.slotKey}},[e("h2",{attrs:{id:"vue-router"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#vue-router"}},[this._v("#")]),this._v(" Vue Router")]),this._v(" "),e("p",[this._v("Talk about testing Vue Router")])])}),[],!1,null,null,null);e.default=u.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/27.a4846e5f.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[27],{231:function(t,e,n){"use strict";n.r(e);var s=n(0),l=Object(s.a)({},(function(){var t=this.$createElement;return(this._self._c||t)("ContentSlotsDistributor",{attrs:{"slot-key":this.$parent.slotKey}})}),[],!1,null,null,null);e.default=l.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/28.daa69ad2.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[28],{236:function(t,e,n){"use strict";n.r(e);var s=n(0),a=Object(s.a)({},(function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[n("h2",{attrs:{id:"testing-vuex-in-components"}},[n("a",{staticClass:"header-anchor",attrs:{href:"#testing-vuex-in-components"}},[t._v("#")]),t._v(" Testing Vuex in components")]),t._v(" "),n("ul",[n("li",[t._v("how to mock Vuex")])]),t._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[t._v("const wrapper = shallowMount(Foo, {\n mocks: {\n $store: {\n state: {}\n }\n }\n})\n")])])]),n("ul",[n("li",[t._v("Talk about using localVue.use(Vuex) to test")])]),t._v(" "),n("div",{staticClass:"language- extra-class"},[n("pre",{pre:!0,attrs:{class:"language-text"}},[n("code",[t._v("localVue.use(Vuex)\nconst store = new Vuex.Store({})\n\nconst wrapper = shallowMount(Foo, {\n store\n localVue\n})\n")])])])])}),[],!1,null,null,null);e.default=a.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/30.8ecd7b0a.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[30],{247:function(t,e,s){"use strict";s.r(e);var i=s(0),o=Object(i.a)({},(function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[s("div",{staticClass:"custom-block tip"},[s("p",{staticClass:"custom-block-title"},[t._v("This book is written for Vue.js 2 and Vue Test Utils v1.")]),t._v(" "),s("p",[t._v("Find the Vue.js 3 version "),s("router-link",{attrs:{to:"/v3/"}},[t._v("here")]),t._v(".")],1)]),t._v(" "),s("h2",{attrs:{id:"mocking-modules"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#mocking-modules"}},[t._v("#")]),t._v(" Mocking Modules")]),t._v(" "),s("p",[t._v("TODO: Write this article. Interested? Open an issue, let's chat!")]),t._v(" "),s("p",[t._v("Not directly related to Vue testing, more specific to Jest. Jest is used heavily throughout this handbook, though, and becoming widely adopted in the industry, so I think it deserves a section.")]),t._v(" "),s("p",[t._v("Some ideas:")]),t._v(" "),s("ol",[s("li",[t._v("Reference the sections of the existing guide using Jest")]),t._v(" "),s("li",[t._v("Talk about the different kind of mocks and their use cases for Vue testing")])]),t._v(" "),s("ul",[s("li",[t._v("Manual mocks")]),t._v(" "),s("li",[t._v("ES6 class mocks")]),t._v(" "),s("li",[t._v("automatic mock")]),t._v(" "),s("li",[t._v("calling "),s("code",[t._v("mockImplentation")]),t._v("?")])]),t._v(" "),s("p",[t._v("Ref: https://jestjs.io/docs/en/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock")])])}),[],!1,null,null,null);e.default=o.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/31.fddfc262.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[31],{246:function(e,t,v){"use strict";v.r(t);var _=v(0),r=Object(_.a)({},(function(){var e=this,t=e.$createElement,v=e._self._c||t;return v("ContentSlotsDistributor",{attrs:{"slot-key":e.$parent.slotKey}},[v("h2",{attrs:{id:"이-가이드는-무엇인가요"}},[v("a",{staticClass:"header-anchor",attrs:{href:"#이-가이드는-무엇인가요"}},[e._v("#")]),e._v(" 이 가이드는 무엇인가요?")]),e._v(" "),v("p",[e._v("Vue.js 테스팅 핸드북에 방문한 것을 환영합니다!")]),e._v(" "),v("p",[e._v("이 가이드는 Vue 컴포넌트를 테스트 하는 방법에 집중한 예제를 짧게 모아 놓았습니다. 이 문서에서는 Vue 컴포넌트를 테스트 하기 위한 공식 라이브러리인 "),v("code",[e._v("vue-test-utils")]),e._v("와 근래에 자주 쓰이는 테스팅 프레임워크인 Jest를 사용합니다. 컴포넌트를 테스트 하는 모범 사례들 뿐만 아니라, "),v("code",[e._v("vue-test-utils")]),e._v("의 API도 다룹니다.")]),e._v(" "),v("p",[e._v("각각의 섹션은 독립적으로 구성되어 있습니다. 먼저 "),v("code",[e._v("vue-cli")]),e._v("로 환경을 설정하고 간단한 테스트를 작성하려고 합니다. 다음으로 컴포넌트를 렌더하는 방법인 "),v("code",[e._v("mount")]),e._v("와 "),v("code",[e._v("shallowMount")]),e._v("에 대해서 얘기해보려고 합니다. 두 방법이 어떻게 다른지 증명하고 설명하겠습니다.")]),e._v(" "),v("p",[e._v("이후에는 컴포넌트를 테스트 할 때 발생하는 다양한 시나리오를 테스트 하는 방법을 다룹니다. 테스트 할 컴포넌트는 아래와 같습니다.")]),e._v(" "),v("ul",[v("li",[e._v("props 받기")]),e._v(" "),v("li",[e._v("컴퓨티드 프로퍼티")]),e._v(" "),v("li",[e._v("다른 컴포넌트 렌더")]),e._v(" "),v("li",[e._v("이벤트 방출")])]),e._v(" "),v("p",[e._v("더 나아가서 아래와 같은 좀 더 흥미로운 경우도 살펴보겠습니다.")]),e._v(" "),v("ul",[v("li",[e._v("Vuex 테스트 모범 사례 (컴포넌트에서, 그리고 독립적으로)")]),e._v(" "),v("li",[e._v("Vue 라우터 테스트")]),e._v(" "),v("li",[e._v("3rd 파티 컴포넌트를 포함한 테스트")])]),e._v(" "),v("p",[e._v("아래의 예시와 같이, Jest의 API를 사용해서 작성한 테스트를 좀더 견고하게 만드는 방법도 알아봅니다.")]),e._v(" "),v("ul",[v("li",[e._v("API 응답 모킹하기")]),e._v(" "),v("li",[e._v("모듈 모킹과 스파잉")]),e._v(" "),v("li",[e._v("스냅샷 사용")])]),e._v(" "),v("h2",{attrs:{id:"더-읽을거리"}},[v("a",{staticClass:"header-anchor",attrs:{href:"#더-읽을거리"}},[e._v("#")]),e._v(" 더 읽을거리")]),e._v(" "),v("p",[e._v("아래의 유용한 자료들도 포함하고 있습니다.")]),e._v(" "),v("ul",[v("li",[v("a",{attrs:{href:"https://vue-test-utils.vuejs.org/",target:"_blank",rel:"noopener noreferrer"}},[e._v("공식 문서"),v("OutboundLink")],1)]),e._v(" "),v("li",[e._v("vue-test-utils의 저작자가 쓴 "),v("a",{attrs:{href:"https://www.manning.com/books/testing-vue-js-applications",target:"_blank",rel:"noopener noreferrer"}},[e._v("책"),v("OutboundLink")],1)]),e._v(" "),v("li",[e._v("제가 하는 "),v("a",{attrs:{href:"https://vuejs-course.com",target:"_blank",rel:"noopener noreferrer"}},[e._v("Vue.js 3 + 유닛 테스팅 코스"),v("OutboundLink")],1),e._v(" (2020년 초, 프리뷰/리뷰 부분을 이용할 수 있습니다)")]),e._v(" "),v("li",[e._v("Vue 핵심 기여자 일부가 참여한 "),v("a",{attrs:{href:"https://vueschool.io/courses/learn-how-to-test-vuejs-components?friend=vt%EC%9D%98",target:"_blank",rel:"noopener noreferrer"}},[e._v("VueSchool의 훌륭한 강의"),v("OutboundLink")],1)])])])}),[],!1,null,null,null);t.default=r.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/36.41446629.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[36],{241:function(t,s,e){"use strict";e.r(s);var o=e(0),v=Object(o.a)({},(function(){var t=this,s=t.$createElement,e=t._self._c||s;return e("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[e("h2",{attrs:{id:"모듈-모킹하기"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#모듈-모킹하기"}},[t._v("#")]),t._v(" 모듈 모킹하기")]),t._v(" "),e("p",[t._v("해야할 일: 이 아티클을 작성합니다. 흥미로운가요? 이슈를 열고, 이야기 해주세요!")]),t._v(" "),e("p",[t._v("Vue 테스팅과 직접적으로 관련이 있지는 않지만, Jest에 좀 더 특화된 주제입니다. Jest는 이 핸드북에서 많이 사용되고 있고, 산업 내에서도 폭넓게 채택되고 있습니다. 그래서 이 섹션이 가치가 있다고 생각합니다.")]),t._v(" "),e("p",[t._v("아래와 같은 아이디어가 있습니다.")]),t._v(" "),e("ol",[e("li",[t._v("Jest를 사용한 기존 가이드 섹션의 참조")]),t._v(" "),e("li",[t._v("Vue 테스팅을 위한 Jest의 사용 예제나 다른 종류의 mocks에 대해 얘기하기")])]),t._v(" "),e("ul",[e("li",[t._v("매뉴얼 mocks")]),t._v(" "),e("li",[t._v("ES6 클래스 mocks")]),t._v(" "),e("li",[t._v("자동 mock")]),t._v(" "),e("li",[e("code",[t._v("mockImplementation")]),t._v(" 호출하기?")])]),t._v(" "),e("p",[t._v("참조: https://jestjs.io/docs/en/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock")])])}),[],!1,null,null,null);s.default=v.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/4.991f7431.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[4],{209:function(t,e,n){"use strict";var i=n(83);n.n(i).a},352:function(t,e,n){"use strict";n.r(e);var i={functional:!0,props:{type:{type:String,default:"tip"},text:String,vertical:{type:String,default:"top"}},render:function(t,e){var n=e.props,i=e.slots;return t("span",{class:["badge",n.type],style:{verticalAlign:n.vertical}},n.text||i().default)}},r=(n(209),n(0)),a=Object(r.a)(i,void 0,void 0,!1,null,"2c277d80",null);e.default=a.exports},83:function(t,e,n){}}]);
--------------------------------------------------------------------------------
/docs/assets/js/44.de49488d.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[44],{250:function(t,e,s){"use strict";s.r(e);var a=s(0),r=Object(a.a)({},(function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[s("h2",{attrs:{id:"vuex-테스트하기"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#vuex-테스트하기"}},[t._v("#")]),t._v(" Vuex 테스트하기")]),t._v(" "),s("p",[t._v("다음 몇 개의 가이드에서는 Vuex를 테스트하는 방법에 대해 다룹니다.")]),t._v(" "),s("h2",{attrs:{id:"vuex-테스트의-두-가지-측면"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#vuex-테스트의-두-가지-측면"}},[t._v("#")]),t._v(" Vuex 테스트의 두 가지 측면")]),t._v(" "),s("p",[t._v("일반적으로 컴포넌트는 아래의 경우에 Vuex와 상호작용 합니다.")]),t._v(" "),s("ol",[s("li",[t._v("뮤테이션으로 커밋하기")]),t._v(" "),s("li",[t._v("액션을 디스패치하기")]),t._v(" "),s("li",[s("code",[t._v("$store.state")]),t._v("나 getter를 통해 상태에 접근하기")])]),t._v(" "),s("p",[t._v("이런 테스트는 컴포넌트가 Vuex 스토어(store)의 현재 상태를 바탕으로 정확하게 동작하는지 어설트(assert)합니다. 뮤테이터(mutators)나 액션(actions) 또는 게터(getters)의 구현체에 대해 알 필요는 없습니다.")]),t._v(" "),s("p",[t._v("뮤테이션(mutations)이나 게터처럼 스토어에 의해 수행되는 임의의 로직은 독립적으로 테스트할 수 있습니다. Vuex 스토어가 일반 자바스크립트 함수로 이루어져있어서 쉽게 유닛을 테스트할 수 있기 때문입니다.")]),t._v(" "),s("p",[t._v("다음 가이드는 Vuex 스토어를 사용하는 컴포넌트를 테스트하는 몇 가지 테크닉을 소개합니다. 그리고 스토어의 상태를 바탕으로 올바르게 동작하는지 확인합니다. 이후의 가이드에서는 독립적으로 Vuex를 테스트하는 방법에 대해 다루겠습니다.")])])}),[],!1,null,null,null);e.default=r.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/5.bb45a9d8.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[5],{213:function(t,e,s){"use strict";s.r(e);var o=["There's nothing here.","How did we get here?","That's a Four-Oh-Four.","Looks like we've got some broken links."],n={methods:{getMsg:function(){return o[Math.floor(Math.random()*o.length)]}}},i=s(0),h=Object(i.a)(n,(function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"theme-container"},[e("div",{staticClass:"theme-default-content"},[e("h1",[this._v("404")]),this._v(" "),e("blockquote",[this._v(this._s(this.getMsg()))]),this._v(" "),e("router-link",{attrs:{to:"/"}},[this._v("Take me home.")])],1)])}),[],!1,null,null,null);e.default=h.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/54.416c7bd4.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[54],{263:function(t,e,v){"use strict";v.r(e);var _=v(0),s=Object(_.a)({},(function(){var t=this,e=t.$createElement,v=t._self._c||e;return v("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[v("div",{staticClass:"custom-block tip"},[v("p",{staticClass:"custom-block-title"},[t._v("Это руководство было написано для Vue.js 2 и Vue Test Utils v1.")]),t._v(" "),v("p",[t._v("Версия для Vue.js 3 "),v("a",{attrs:{href:"/v3/ru"}},[t._v("здесь")]),t._v(".")])]),t._v(" "),v("h2",{attrs:{id:"о-чём-это-руководство"}},[v("a",{staticClass:"header-anchor",attrs:{href:"#о-чём-это-руководство"}},[t._v("#")]),t._v(" О чём это руководство?")]),t._v(" "),v("p",[t._v("Добро пожаловать в руководство по тестированию Vue приложений!")]),t._v(" "),v("p",[t._v("Здесь собрана коллекция коротких примеров тестирования компонентов с использованием "),v("code",[t._v("vue-test-utils")]),t._v(" – официальной библиотекой для тестирования vue компонентов. Также используется "),v("code",[t._v("Jest")]),t._v(" – современный фреймворк для тестирования. Рассматривается "),v("code",[t._v("vue-test-utils")]),t._v(" API и лучшие практики тестирования.")]),t._v(" "),v("p",[t._v("Руководство написано так, что его главы независимы одна от другой. Мы начнём с настройки окружения с помощью "),v("code",[t._v("vue-cli")]),t._v(" и написания простейших тестов. Затем изучим два способа отрисовки компонентов – "),v("code",[t._v("mount")]),t._v(" и "),v("code",[t._v("shallowMount")]),t._v(", поймём разницу между ними.")]),t._v(" "),v("p",[t._v("Затем разберём несколько сценариев, которые возникают при тестировании компонентов:")]),t._v(" "),v("ul",[v("li",[t._v("принятие входных параметров")]),t._v(" "),v("li",[t._v("использование вычисляемых свойств")]),t._v(" "),v("li",[t._v("отрисовка других компонентов")]),t._v(" "),v("li",[t._v("пользовательские события")])]),t._v(" "),v("p",[t._v("и так далее. Потренируемся на таких интересных кейсах, как:")]),t._v(" "),v("ul",[v("li",[t._v("тестирование Vuex в компонентах и изоляции")]),t._v(" "),v("li",[t._v("тестирование Vue router")]),t._v(" "),v("li",[t._v("тестирование сторонних компонентов")])]),t._v(" "),v("p",[t._v("Также поработаем с Jest API, сделаем наши тесты более надёжными:")]),t._v(" "),v("ul",[v("li",[t._v("замокаем API ответы")]),t._v(" "),v("li",[t._v("замокаем модули и добавим spies (шпионы)")]),t._v(" "),v("li",[t._v("используем снимки")])]),t._v(" "),v("h2",{attrs:{id:"даnьнейшее-чтение"}},[v("a",{staticClass:"header-anchor",attrs:{href:"#даnьнейшее-чтение"}},[t._v("#")]),t._v(" Дальнейшее чтение")]),t._v(" "),v("p",[t._v("Другие полезные ресурсы:")]),t._v(" "),v("ul",[v("li",[v("a",{attrs:{href:"https://vue-test-utils.vuejs.org/ru/",target:"_blank",rel:"noopener noreferrer"}},[t._v("Официальная документация"),v("OutboundLink")],1)]),t._v(" "),v("li",[t._v("Мой курс "),v("a",{attrs:{href:"https://vuejs-course.com",target:"_blank",rel:"noopener noreferrer"}},[t._v("Vue.js 3 + Composition API + Unit Testing"),v("OutboundLink")],1)]),t._v(" "),v("li",[v("a",{attrs:{href:"https://vueschool.io/courses/learn-how-to-test-vuejs-components?friend=vth",target:"_blank",rel:"noopener noreferrer"}},[t._v("Бесплатный курс на VueSchool"),v("OutboundLink")],1)])])])}),[],!1,null,null,null);e.default=s.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/59.cb0133e9.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[59],{266:function(t,s,e){"use strict";e.r(s);var v=e(0),l=Object(v.a)({},(function(){var t=this,s=t.$createElement,e=t._self._c||s;return e("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[e("div",{staticClass:"custom-block tip"},[e("p",{staticClass:"custom-block-title"},[t._v("Это руководство было написано для Vue.js 2 и Vue Test Utils v1.")]),t._v(" "),e("p",[t._v("Версия для Vue.js 3 "),e("a",{attrs:{href:"/v3/ru"}},[t._v("здесь")]),t._v(".")])]),t._v(" "),e("h2",{attrs:{id:"мокаем-модуnи"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#мокаем-модуnи"}},[t._v("#")]),t._v(" Мокаем модули")]),t._v(" "),e("p",[t._v("Не совсем про Vue тестирование, больше о Jest. Jest много где применяется на протяжении всего руководства, становится популярнее в индустрии, поэтому я считаю, он заслужил отдельной секции.")]),t._v(" "),e("p",[t._v("Некоторые идеи:")]),t._v(" "),e("ol",[e("li",[t._v("Ссылки на секции руководства, где применяется Jest")]),t._v(" "),e("li",[t._v("Поговорить о различных видах мок и их применении во Vue тестировании")])]),t._v(" "),e("ul",[e("li",[t._v("создаваемые вручную моки")]),t._v(" "),e("li",[t._v("моки для ES6 классов")]),t._v(" "),e("li",[t._v("автоматические моки")]),t._v(" "),e("li",[t._v("вызов "),e("code",[t._v("mockImplentation")]),t._v("?")])]),t._v(" "),e("p",[t._v("Источник: https://jestjs.io/docs/en/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock")])])}),[],!1,null,null,null);s.default=l.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/67.054e25f3.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[67],{273:function(t,e,v){"use strict";v.r(e);var s=v(0),a=Object(s.a)({},(function(){var t=this,e=t.$createElement,v=t._self._c||e;return v("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[v("div",{staticClass:"custom-block tip"},[v("p",{staticClass:"custom-block-title"},[t._v("Это руководство было написано для Vue.js 2 и Vue Test Utils v1.")]),t._v(" "),v("p",[t._v("Версия для Vue.js 3 "),v("a",{attrs:{href:"/v3/ru"}},[t._v("здесь")]),t._v(".")])]),t._v(" "),v("h2",{attrs:{id:"тестирование-vuex"}},[v("a",{staticClass:"header-anchor",attrs:{href:"#тестирование-vuex"}},[t._v("#")]),t._v(" Тестирование Vuex")]),t._v(" "),v("p",[t._v("В следующих статьях обсудим как тестировать Vuex.")]),t._v(" "),v("h2",{attrs:{id:"две-стороны-тестирования-vuex"}},[v("a",{staticClass:"header-anchor",attrs:{href:"#две-стороны-тестирования-vuex"}},[t._v("#")]),t._v(" Две стороны тестирования Vuex")]),t._v(" "),v("p",[t._v("Обычно компоненты взаимодействуют с Vuex так:")]),t._v(" "),v("ol",[v("li",[t._v("вызываем мутацию через "),v("code",[t._v("commit")])]),t._v(" "),v("li",[t._v("запускаем действия через "),v("code",[t._v("dispatch")])]),t._v(" "),v("li",[t._v("обращаемся к хранилищу через "),v("code",[t._v("$store.state")]),t._v(" или геттеры")])]),t._v(" "),v("p",[t._v("В тестах проверяется, что компонент ведёт себя правильно при текущем состоянии хранилища Vuex. Они не должны знать о реализациях мутаций, действий, геттеров.")]),t._v(" "),v("p",[t._v("Любая логика хранилища, такая как мутация или использование геттеров, должна тестироваться в изоляции. Для Vuex достаточно просто писать модульные тесты, так как он хранит в себе простые JavaScript функции.")]),t._v(" "),v("p",[t._v("В следующей статье разберём некоторые техники тестирования компонентов с использованием Vuex, убедимся, что их поведение зависит от состояния хранилища. Чуть позже обсудим тестирование Vuex в изоляции.")])])}),[],!1,null,null,null);e.default=a.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/78.c5a9bf2b.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[78],{286:function(t,e,s){"use strict";s.r(e);var o=s(0),i=Object(o.a)({},(function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[s("div",{staticClass:"custom-block tip"},[s("p",{staticClass:"custom-block-title"},[t._v("This book is written for Vue.js 2 and Vue Test Utils v1.")]),t._v(" "),s("p",[t._v("Find the Vue.js 3 version "),s("router-link",{attrs:{to:"/v3/"}},[t._v("here")]),t._v(".")],1)]),t._v(" "),s("h2",{attrs:{id:"testing-vuex"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#testing-vuex"}},[t._v("#")]),t._v(" Testing Vuex")]),t._v(" "),s("p",[t._v("The next few guides discuss testing Vuex.")]),t._v(" "),s("h2",{attrs:{id:"two-sides-of-testing-vuex"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#two-sides-of-testing-vuex"}},[t._v("#")]),t._v(" Two Sides of Testing Vuex")]),t._v(" "),s("p",[t._v("Generally components will interact with Vuex by")]),t._v(" "),s("ol",[s("li",[t._v("committing a mutation")]),t._v(" "),s("li",[t._v("dispatching an action")]),t._v(" "),s("li",[t._v("access the state via "),s("code",[t._v("$store.state")]),t._v(" or getters")])]),t._v(" "),s("p",[t._v("These tests are to assert that the component behaves correctly based on the current state of the Vuex store. They do not need to know about the implementation of the mutators, actions or getters.")]),t._v(" "),s("p",[t._v("Any logic performed by the store, such as mutations and getters, can be tested in isolation. Since Vuex stores are comprised of regular JavaScript functions, they are easily unit tested.")]),t._v(" "),s("p",[t._v("The first few guides discuss techniques to test Vuex in isolation considering mutations, actions and getters. Following guides introduce some techniques to test components that use a Vuex store, and ensure they behave correctly based on the store's state.")])])}),[],!1,null,null,null);e.default=i.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/84.567f2a5a.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[84],{292:function(t,e,s){"use strict";s.r(e);var i=s(0),o=Object(i.a)({},(function(){var t=this,e=t.$createElement,s=t._self._c||e;return s("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[s("div",{staticClass:"custom-block tip"},[s("p",{staticClass:"custom-block-title"},[t._v("This book is written for Vue.js 3 and Vue Test Utils v2.")]),t._v(" "),s("p",[t._v("Find the Vue.js 2 version "),s("router-link",{attrs:{to:"/"}},[t._v("here")]),t._v(".")],1)]),t._v(" "),s("h2",{attrs:{id:"mocking-modules"}},[s("a",{staticClass:"header-anchor",attrs:{href:"#mocking-modules"}},[t._v("#")]),t._v(" Mocking Modules")]),t._v(" "),s("p",[t._v("TODO: Write this article. Interested? Open an issue, let's chat!")]),t._v(" "),s("p",[t._v("Not directly related to Vue testing, more specific to Jest. Jest is used heavily throughout this handbook, though, and becoming widely adopted in the industry, so I think it deserves a section.")]),t._v(" "),s("p",[t._v("Some ideas:")]),t._v(" "),s("ol",[s("li",[t._v("Reference the sections of the existing guide using Jest")]),t._v(" "),s("li",[t._v("Talk about the different kind of mocks and their use cases for Vue testing")])]),t._v(" "),s("ul",[s("li",[t._v("Manual mocks")]),t._v(" "),s("li",[t._v("ES6 class mocks")]),t._v(" "),s("li",[t._v("automatic mock")]),t._v(" "),s("li",[t._v("calling "),s("code",[t._v("mockImplentation")]),t._v("?")])]),t._v(" "),s("p",[t._v("Ref: https://jestjs.io/docs/en/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock")])])}),[],!1,null,null,null);e.default=o.exports}}]);
--------------------------------------------------------------------------------
/docs/assets/js/93.b6825306.js:
--------------------------------------------------------------------------------
1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[93],{302:function(t,s,e){"use strict";e.r(s);var v=e(0),l=Object(v.a)({},(function(){var t=this,s=t.$createElement,e=t._self._c||s;return e("ContentSlotsDistributor",{attrs:{"slot-key":t.$parent.slotKey}},[e("div",{staticClass:"custom-block tip"},[e("p",{staticClass:"custom-block-title"},[t._v("Это руководство было написано для Vue.js 3 и Vue Test Utils v2.")]),t._v(" "),e("p",[t._v("Версия для Vue.js 2 "),e("a",{attrs:{href:"/ru"}},[t._v("здесь")]),t._v(".")])]),t._v(" "),e("h2",{attrs:{id:"мокаем-модуnи"}},[e("a",{staticClass:"header-anchor",attrs:{href:"#мокаем-модуnи"}},[t._v("#")]),t._v(" Мокаем модули")]),t._v(" "),e("p",[t._v("Не совсем про Vue тестирование, больше о Jest. Jest много где применяется на протяжении всего руководства, становится популярнее в индустрии, поэтому я считаю, он заслужил отдельной секции.")]),t._v(" "),e("p",[t._v("Некоторые идеи:")]),t._v(" "),e("ol",[e("li",[t._v("Ссылки на секции руководства, где применяется Jest")]),t._v(" "),e("li",[t._v("Поговорить о различных видах мок и их применении во Vue тестировании")])]),t._v(" "),e("ul",[e("li",[t._v("создаваемые вручную моки")]),t._v(" "),e("li",[t._v("моки для ES6 классов")]),t._v(" "),e("li",[t._v("автоматические моки")]),t._v(" "),e("li",[t._v("вызов "),e("code",[t._v("mockImplentation")]),t._v("?")])]),t._v(" "),e("p",[t._v("Источник: https://jestjs.io/docs/en/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock")])])}),[],!1,null,null,null);s.default=l.exports}}]);
--------------------------------------------------------------------------------
/docs/composition.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/docs/composition.png
--------------------------------------------------------------------------------
/docs/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/docs/img/favicon.png
--------------------------------------------------------------------------------
/docs/img/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/docs/img/og.png
--------------------------------------------------------------------------------
/docs/mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/docs/mini.png
--------------------------------------------------------------------------------
/docs/patterns.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/docs/patterns.png
--------------------------------------------------------------------------------
/docs/vue-crash-course.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/docs/vue-crash-course.png
--------------------------------------------------------------------------------
/docs/vue-school.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/docs/vue-school.png
--------------------------------------------------------------------------------
/docs/vuejs-course.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/docs/vuejs-course.png
--------------------------------------------------------------------------------
/docs/vueschool-banner-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/docs/vueschool-banner-small.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "handbook",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "dev": "yarn vuepress dev src",
8 | "docs:build": "yarn vuepress build src",
9 | "lint": "yarn eslint --ext js,vue ."
10 | },
11 | "dependencies": {
12 | "vuepress": "^1.2.0"
13 | },
14 | "devDependencies": {
15 | "@vuepress/plugin-google-analytics": "^1.2.0",
16 | "babel-eslint": "^10.0.3",
17 | "eslint": "^6.5.1",
18 | "eslint-plugin-vue": "^5.2.3",
19 | "markdown-it-include": "^1.0.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/.vuepress/public/ad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/src/.vuepress/public/ad.png
--------------------------------------------------------------------------------
/src/.vuepress/public/composition.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/src/.vuepress/public/composition.png
--------------------------------------------------------------------------------
/src/.vuepress/public/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/src/.vuepress/public/img/favicon.png
--------------------------------------------------------------------------------
/src/.vuepress/public/img/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/src/.vuepress/public/img/og.png
--------------------------------------------------------------------------------
/src/.vuepress/public/mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/src/.vuepress/public/mini.png
--------------------------------------------------------------------------------
/src/.vuepress/public/patterns.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/src/.vuepress/public/patterns.png
--------------------------------------------------------------------------------
/src/.vuepress/public/vue-crash-course.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/src/.vuepress/public/vue-crash-course.png
--------------------------------------------------------------------------------
/src/.vuepress/public/vue-school.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/src/.vuepress/public/vue-school.png
--------------------------------------------------------------------------------
/src/.vuepress/public/vuejs-course.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/src/.vuepress/public/vuejs-course.png
--------------------------------------------------------------------------------
/src/.vuepress/public/vueschool-banner-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/src/.vuepress/public/vueschool-banner-small.png
--------------------------------------------------------------------------------
/src/.vuepress/styles/index.styl:
--------------------------------------------------------------------------------
1 | a.sidebar-link
2 | font-size: 0.9em !important
3 | line-height: 1 !important
4 | font-weight: normal !important
5 |
6 | .sidebar-links > li
7 | margin-top: 0.25em !important
8 |
9 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018-present, Yuxi (Evan) You
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/components/CarbonAds.vue:
--------------------------------------------------------------------------------
1 |
33 |
34 |
60 |
61 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/components/DropdownTransition.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
28 |
29 |
34 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/components/NavLink.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ item.text }}
9 |
17 | {{ item.text }}
18 |
19 |
20 |
21 |
22 |
55 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/components/Page.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
23 |
24 |
32 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/components/PageNav.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ←
6 | {{ prev.title || prev.path }}
7 |
8 |
9 |
10 | {{ next.title || next.path }}
11 | →
12 |
13 |
14 |
15 |
16 |
101 |
118 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/components/SidebarButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
28 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/components/SidebarLinks.vue:
--------------------------------------------------------------------------------
1 |
2 |
22 |
23 |
24 |
100 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/components/VueSchool.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
25 |
26 |
44 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/components/ad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/src/.vuepress/theme/components/ad.png
--------------------------------------------------------------------------------
/src/.vuepress/theme/global-components/Badge.vue:
--------------------------------------------------------------------------------
1 |
25 |
26 |
45 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | // Theme API.
4 | module.exports = (options, ctx) => {
5 | const { themeConfig, siteConfig } = ctx
6 |
7 | // resolve algolia
8 | const isAlgoliaSearch = (
9 | themeConfig.algolia
10 | || Object
11 | .keys(siteConfig.locales && themeConfig.locales || {})
12 | .some(base => themeConfig.locales[base].algolia)
13 | )
14 |
15 | const enableSmoothScroll = themeConfig.smoothScroll === true
16 |
17 | return {
18 | alias () {
19 | return {
20 | '@AlgoliaSearchBox': isAlgoliaSearch
21 | ? path.resolve(__dirname, 'components/AlgoliaSearchBox.vue')
22 | : path.resolve(__dirname, 'noopModule.js')
23 | }
24 | },
25 |
26 | plugins: [
27 | ['@vuepress/active-header-links', options.activeHeaderLinks],
28 | '@vuepress/search',
29 | '@vuepress/plugin-nprogress',
30 | ['container', {
31 | type: 'tip',
32 | defaultTitle: {
33 | '/': 'TIP',
34 | '/zh/': '提示'
35 | }
36 | }],
37 | ['container', {
38 | type: 'warning',
39 | defaultTitle: {
40 | '/': 'WARNING',
41 | '/zh/': '注意'
42 | }
43 | }],
44 | ['container', {
45 | type: 'danger',
46 | defaultTitle: {
47 | '/': 'WARNING',
48 | '/zh/': '警告'
49 | }
50 | }],
51 | ['smooth-scroll', enableSmoothScroll]
52 | ]
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/layouts/404.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
404
5 |
{{ getMsg() }}
6 |
Take me home.
7 |
8 |
9 |
10 |
11 |
27 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/noopModule.js:
--------------------------------------------------------------------------------
1 | export default {}
2 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/styles/arrow.styl:
--------------------------------------------------------------------------------
1 | @require './config'
2 |
3 | .arrow
4 | display inline-block
5 | width 0
6 | height 0
7 | &.up
8 | border-left 4px solid transparent
9 | border-right 4px solid transparent
10 | border-bottom 6px solid $arrowBgColor
11 | &.down
12 | border-left 4px solid transparent
13 | border-right 4px solid transparent
14 | border-top 6px solid $arrowBgColor
15 | &.right
16 | border-top 4px solid transparent
17 | border-bottom 4px solid transparent
18 | border-left 6px solid $arrowBgColor
19 | &.left
20 | border-top 4px solid transparent
21 | border-bottom 4px solid transparent
22 | border-right 6px solid $arrowBgColor
23 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/styles/config.styl:
--------------------------------------------------------------------------------
1 | $contentClass = '.theme-default-content'
2 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/styles/custom-blocks.styl:
--------------------------------------------------------------------------------
1 | .custom-block
2 | .custom-block-title
3 | font-weight 600
4 | margin-bottom -0.4rem
5 | &.tip, &.warning, &.danger
6 | padding .1rem 1.5rem
7 | border-left-width .5rem
8 | border-left-style solid
9 | margin 1rem 0
10 | &.tip
11 | background-color #f3f5f7
12 | border-color #42b983
13 | &.warning
14 | background-color rgba(255,229,100,.3)
15 | border-color darken(#ffe564, 35%)
16 | color darken(#ffe564, 70%)
17 | .custom-block-title
18 | color darken(#ffe564, 50%)
19 | a
20 | color $textColor
21 | &.danger
22 | background-color #ffe6e6
23 | border-color darken(red, 20%)
24 | color darken(red, 70%)
25 | .custom-block-title
26 | color darken(red, 40%)
27 | a
28 | color $textColor
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/styles/mobile.styl:
--------------------------------------------------------------------------------
1 | @require './config'
2 |
3 | $mobileSidebarWidth = $sidebarWidth * 0.82
4 |
5 | // narrow desktop / iPad
6 | @media (max-width: $MQNarrow)
7 | .sidebar
8 | font-size 15px
9 | width $mobileSidebarWidth
10 | .page
11 | padding-left $mobileSidebarWidth
12 |
13 | // wide mobile
14 | @media (max-width: $MQMobile)
15 | .sidebar
16 | top 0
17 | padding-top $navbarHeight
18 | transform translateX(-100%)
19 | transition transform .2s ease
20 | .page
21 | padding-left 0
22 | .theme-container
23 | &.sidebar-open
24 | .sidebar
25 | transform translateX(0)
26 | &.no-navbar
27 | .sidebar
28 | padding-top: 0
29 |
30 | // narrow mobile
31 | @media (max-width: $MQMobileNarrow)
32 | h1
33 | font-size 1.9rem
34 | {$contentClass}
35 | div[class*="language-"]
36 | margin 0.85rem -1.5rem
37 | border-radius 0
38 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/styles/toc.styl:
--------------------------------------------------------------------------------
1 | .table-of-contents
2 | .badge
3 | vertical-align middle
4 |
--------------------------------------------------------------------------------
/src/.vuepress/theme/styles/wrapper.styl:
--------------------------------------------------------------------------------
1 | $wrapper
2 | max-width $contentWidth
3 | margin 0 auto
4 | padding 2rem 2.5rem
5 | @media (max-width: $MQNarrow)
6 | padding 2rem
7 | @media (max-width: $MQMobileNarrow)
8 | padding 1.5rem
9 |
10 |
--------------------------------------------------------------------------------
/src/README.md:
--------------------------------------------------------------------------------
1 | :::tip This book is written for Vue.js 2 and Vue Test Utils v1.
2 | Find the Vue.js 3 version [here](/v3/).
3 | :::
4 |
5 | ## What is this guide?
6 |
7 | Welcome to the Vue.js testing handbook!
8 |
9 | This is a collection of short, focused examples on how to test Vue components. It uses `vue-test-utils`, the official library for testing Vue components, and Jest, a modern testing framework. It covers the `vue-test-utils` API, as well as best practises for testing components.
10 |
11 | Each section is independent from the others. We start off by setting up an environment with `vue-cli` and writing a simple test. Next, two ways to render a component are discussed - `mount` and `shallowMount`. The differences will be demonstrated and explained.
12 |
13 | From then on, we cover how to test various scenarios that arise when testing components, such as testing components that:
14 |
15 | - receive props
16 | - use computed properties
17 | - render other components
18 | - emit events
19 |
20 | and so forth. We then move on to more interesting cases, such as:
21 |
22 | - best practises for testing Vuex (in components, and independently)
23 | - testing Vue router
24 | - testing involving third party components
25 |
26 | We will also explore how to use the Jest API to make our tests more robust, such as:
27 |
28 | - mocking API responses
29 | - mocking and spying on modules
30 | - using snapshots
31 |
32 | ## Further Reading
33 |
34 | Other useful resources include:
35 |
36 | - [Official docs](https://vue-test-utils.vuejs.org/)
37 | - I made free series on Vue Test Utils + Vue 3: [YouTube playlist](https://www.youtube.com/playlist?list=PLC2LZCNWKL9ahK1IoODqYxKu5aA9T5IOA)
38 | - My [Vue.js 3 + Unit Testing Course](https://vuejs-course.com). VUEJS_COURSE_10_OFF for a $10 discount!
39 | - [This course on VueSchool](https://vueschool.io/courses/learn-how-to-test-vuejs-components?friend=vth) by several Vue core contributors
40 |
--------------------------------------------------------------------------------
/src/ja/README.md:
--------------------------------------------------------------------------------
1 | ## Vue.jsテストハンドブック
2 |
3 | Vue.jsテストハンドブックにようこそ!
4 |
5 | このハンドブックはVueコンポーネントをどうテストするか簡単な例の集めたものです。コンポーネントをテストする公式ライブラリーの`vue-test-utils`とモダーンテストフレームワークのJestを使います。`vue-test-utils`のAPIとコンポーネントのテストの最適な実践を紹介します。
6 |
7 | 各セクションはその他のセクションとは独立してます。最初は`vue-cli`をインストールしてテスト環境を準備してから最初のテストを書きます。そしてコンポーネントをレンダーする`mount`と`shallowMount`の2つの方法とそれぞれの違いを説明します。
8 |
9 | 続いてコンポーネントをテストするときによくある場面を紹介します。例えば:
10 |
11 | - `props`を受け取る
12 | - 算出プロパティ
13 | - 別のコンポーネントをレンダーする
14 | - イベントを`emit`する
15 |
16 |
17 | そのあと、次でもっと興味深いケースを見てみます。例えば:
18 |
19 | - Vuexをテストするベストプラクティス(コンポーネントと、コンポーネント以外)
20 | - Vueルーターのテスト
21 | - 第三者のコンポーネントのテスト
22 |
23 | JestのAPIでテストをもっと安定させる方法も紹介します。例えば:
24 |
25 | - APIレスポンスをモックする
26 | - モジュールのモックとスパイ
27 | - スナップショット
28 |
29 | ## 他の参考
30 |
31 | - [公式のドキュメント](https://vue-test-utils.vuejs.org/)
32 | - [`vue-test-utils`を作った人が書いた本](https://www.manning.com/books/testing-vue-js-applications) (英語)
33 | - [VueSchoolの`vue-test-utils`のコース](https://vueschool.io/courses/learn-how-to-test-vuejs-components?friend=vth) (英語)
34 |
--------------------------------------------------------------------------------
/src/ja/composition-api.md:
--------------------------------------------------------------------------------
1 | TODO: Translate.
2 |
--------------------------------------------------------------------------------
/src/ja/finding-elements-and-components.md:
--------------------------------------------------------------------------------
1 | ## Finding elements and components
2 |
3 | Talk about
4 |
5 | - how to use `find` and `findAll`
6 | - `find(".class")`
7 | - `find(VueComponent)`
8 | - `find({ name: "component-name" })`
9 | - give examples with `shallowMount` and `mount`
10 |
11 | Talk about stubbing briefly, then in more detail in the next section.
12 |
--------------------------------------------------------------------------------
/src/ja/jest-mocking-modules.md:
--------------------------------------------------------------------------------
1 | ## Mocking Modules
2 |
3 | Todo
4 |
--------------------------------------------------------------------------------
/src/ja/mocking-global-objects.md:
--------------------------------------------------------------------------------
1 | ## Mocking global objects
2 |
3 | ideas:
4 |
5 | - $store
6 | - $router
7 | - $t (vue i18n)
8 |
--------------------------------------------------------------------------------
/src/ja/rendering-a-component.md:
--------------------------------------------------------------------------------
1 | ## コンポーネントをレンダーする二つのメソッド - `mount`と`shallowMount`
2 | `vue-test-utils`は、 コンポーネントをレンダー(__マウント__)するのに、二つのメソッドを提供しています - `mount`と`shallowMount`。どのコンポーネントも、この二つのいずれかのメソッドを使い、`wrapper`を返します。`wrapper`は、Vue componentを含むオブジェクトです。また、テストの実行に色々な便利なメソッドを持っています。
3 |
4 | それでは、`Child`と`Parent`、二つシンプルなコンポーネントで例を見てみましょう:
5 |
6 | ```js
7 | const Child = Vue.component("Child", {
8 | name: "Child",
9 |
10 | template: "Child component
"
11 | })
12 |
13 | const Parent = Vue.component("Parent", {
14 | name: "Parent",
15 |
16 | template: "
"
17 | })
18 | ```
19 |
20 | `Child`をレンダーし、`vue-test-utils`が提供するメソッド`html`を呼び出し、出力しましょう。
21 |
22 | ```js
23 | const shallowWrapper = shallowMount(Child)
24 | const mountWrapper = mount(Child)
25 |
26 | console.log(shallowWrapper.html())
27 | console.log(mountWrapper.html())
28 | ```
29 |
30 | `mountWrapper.html()`と`shallowWrapper.html()`、両方も下記のように出力されます。
31 |
32 |
33 | ```html
34 | Child component
35 | ```
36 |
37 | 特に差がありません。では、`Parent`の場合はどうなるでしょうか?
38 |
39 | ```js
40 | const shallowWrapper = shallowMount(Parent)
41 | const mountWrapper = mount(Parent)
42 |
43 | console.log(shallowWrapper.html())
44 | console.log(mountWrapper.html())
45 | ```
46 |
47 | `mountWrapper.html()`は下記のように出力されます:
48 |
49 | ```html
50 |
51 | ```
52 |
53 | `Parent` と`Child`のマークアップはそのまま出力されます。
54 |
55 | その一方、`shallowWrapper.html()`は下記のようになります。
56 |
57 | ```html
58 |
59 | ```
60 |
61 | ` `は` `になっています。`shallowMount` は通常のhtml要素をレンダーしますが、Vue componentsに対しては描画せずスタブに置き換えることが分かりました。
62 |
63 | > スタブとは、実際のオブジェクトを代替する「偽物」のオブジェクトです。
64 |
65 | スタブは便利そうですね。以下の`App.vue`コンポーネントをテストすることを考えましょう:
66 |
67 | ```vue
68 |
69 | My Vue App
70 |
71 |
72 | ```
73 |
74 | `My Vue App `が正確にレンダーされたかテストしたいとします。ただ、` `子コンポーネントもあるので、これは`mounted`ライフサイクルフックの中に、外部APIにリクエストを投げて、レンダーされます。
75 |
76 | `mount`を使った場合、ただ一部の内容だけレンダーしたいとしても、` `は外部APIにリクエストを投げます。その結果、テストが遅くなり、エラーも発生しやすくなります。`shallowMount`を使った場合ですと、` `は` `に置き換えます。スタブは外部依存しないため、APIは呼ばれない。テストがより早くできます。
77 |
--------------------------------------------------------------------------------
/src/ja/stubbing-components.md:
--------------------------------------------------------------------------------
1 | ## Stubbing components
2 |
3 | Talk about
4 |
5 | - how to use `stubs` and the different apis:
6 |
7 | ```
8 | const BMock = {
9 | name: "B",
10 | render: h => h("div")
11 | }
12 |
13 | const wrapper = shallowMount(Foo, {
14 | stubs: {
15 | A: "
",
16 | B: BMock,
17 | C: true
18 | }
19 | })
20 | ```
21 |
22 | - Talk about why/when to use stubs.
23 |
--------------------------------------------------------------------------------
/src/ja/testing-vuex.md:
--------------------------------------------------------------------------------
1 | ## Vuex のテスト
2 |
3 | これ以降しばらくは、Vuex のテストについて解説していきます。
4 |
5 | ## Vuex のテストにおける二つの側面
6 |
7 | 一般的にいってコンポーネントは Vuex と以下の方法でやりとりをしています。
8 |
9 | 1. mutation へ commit する
10 | 2. action を dispatching する
11 | 3. `$store.state` もしくは getters 用いて state にアクセスする
12 |
13 | コンポーネントについてのテストを書く場合には、コンポーネントが Vuex store の現在の state に基づいて正しく動作していてることを assert すればいいわけです。ですから mutation や action や getter がどのように実装されているかを知る必要はありません。(訳注: コンポーネントのテストを書く際に、mutation 等々のテストを含める必要はないということ。)
14 |
15 | それにたいして、store が遂行するロジック、例えば mutation や getter に関しては、それ自体を単独でテストをすることができます。なぜなら Vuex の store は通常の JavaScript の関数によって構成されているからで、それゆえユニットテスをするのも簡単です。
16 |
17 | まずは Vuex 単独のテストについて説明していきます。後半では、Vuex store を使ったコンポーネントのテスト技法を取り上げます。
18 |
--------------------------------------------------------------------------------
/src/ja/vue-router.md:
--------------------------------------------------------------------------------
1 | ## Vue Router
2 |
3 | Talk about testing Vue Router
4 |
--------------------------------------------------------------------------------
/src/ja/vuex-getters.md:
--------------------------------------------------------------------------------
1 | ## ゲッターをテストする
2 | ゲッターのテストだけを独立しておこなう場合には、複雑な手順は全く必要ありません。ゲッターは通常の JavaScript の関数だからです。テストの方法はミューテーションやアクションに対するテストと似ています。ミューテーションに関する詳細な情報は[こちら](https://lmiller1990.github.io/vue-testing-handbook/ja/vuex-mutations.html)をご覧ください。
3 |
4 | このガイドのテストのソースコードは[こちら](https://github.com/lmiller1990/vue-testing-handbook/tree/master/demo-app/tests/unit/getters.spec.js)です。
5 |
6 | 下記のようなストアで動作する2つのゲッターを見ていきましょう。
7 |
8 | ```js
9 | const state = {
10 | dogs: [
11 | { name: "lucky", breed: "poodle", age: 1 },
12 | { name: "pochy", breed: "dalmatian", age: 2 },
13 | { name: "blackie", breed: "poodle", age: 4 }
14 | ]
15 | }
16 | ```
17 |
18 | ゲッターに対するテストは下記の通りです。
19 | 1. `poodles`: 全ての `poodles` が取得できること
20 | 2. `poodlesByAge`: 全ての `poodles` が取得でき、引数として `age` を受け取れること
21 |
22 | ## ゲッターの作成
23 |
24 | まずは、ゲッターを作成します。
25 |
26 | ```js
27 | export default {
28 | poodles: (state) => {
29 | return state.dogs.filter(dog => dog.breed === "poodle")
30 | },
31 |
32 | poodlesByAge: (state, getters) => (age) => {
33 | return getters.poodles.filter(dog => dog.age === age)
34 | }
35 | }
36 | ```
37 |
38 | 変わったことはありません - ゲッターは第2引数として他のゲッターを受け取ることを思い出してください。すでに `poodles` ゲッターが存在するので、`poodlesByAge` の中でそれを使用することができます。`poodlesByAge` の中で引数をとる関数を返すことで、ゲッターに引数を渡すことができます。`poodlesByAge` ゲッターを下記のように使用することができます。
39 |
40 | ```js
41 | computed: {
42 | puppies() {
43 | return this.$store.getters.poodlesByAge(1)
44 | }
45 | }
46 | ```
47 |
48 | `poodles` に対するテストを書いていきましょう。
49 |
50 | ## テストを書く
51 |
52 | ゲッターは第一引数に `state` オブジェクトを取る通常の JavaScript の関数なので、テストはとてもシンプルになります。`getters.spec.js` のテストを下記のように書いていきます。
53 |
54 | ```js
55 | import getters from "../../src/store/getters.js"
56 |
57 | const dogs = [
58 | { name: "lucky", breed: "poodle", age: 1 },
59 | { name: "pochy", breed: "dalmatian", age: 2 },
60 | { name: "blackie", breed: "poodle", age: 4 }
61 | ]
62 | const state = { dogs }
63 |
64 | describe("poodles", () => {
65 | it("returns poodles", () => {
66 | const actual = getters.poodles(state)
67 |
68 | expect(actual).toEqual([ dogs[0], dogs[2] ])
69 | })
70 | })
71 | ```
72 |
73 | Vuexは自動的に `state` をゲッターへ渡します。独立したゲッターのテストを行なっているので、`state` を手動で渡さなければなりません。それ以外にも、通常の JavaScript の関数をテストしています。
74 |
75 | `poodlesByAge` はもう少し興味深いです。ゲッターの第二引数は他のゲッターです。`poodlesByAge` のテストをしているので、`poodles` の実装をテストに含めたくないです。そのため、`poodles` ゲッターをスタブします。これにより、テストをより細かくコントロールすることができます。
76 |
77 | ```js
78 | describe("poodlesByAge", () => {
79 | it("returns poodles by age", () => {
80 | const poodles = [ dogs[0], dogs[2] ]
81 | const actual = getters.poodlesByAge(state, { poodles })(1)
82 |
83 | expect(actual).toEqual([ dogs[0] ])
84 | })
85 | })
86 | ```
87 |
88 | `poodles` ゲッターを引数として渡す代わりに、`poodles` が返す結果を渡します。テストを書いているので、すでに動作することがわかっています。これにより、`poodlesByAge` 固有のロジックのテストに集中することができます。
89 |
90 | 非同期のゲッターを持つことが可能です。非同期のアクションと同じテスト手法でテストができます。それについては[こちら](https://lmiller1990.github.io/vue-testing-handbook/ja/vuex-actions.html)をご覧ください。
91 |
92 | ## 結論
93 |
94 | - `getters` は通常の JavaScript の関数です。
95 | - `getters` のテストを独立して行う場合、state を手動で渡す必要があります。
96 | - ゲッターを他のゲッターで使用する場合、そのゲッターの期待する返り値をスタブするべきです。これにより、テストをよりコントロールすることができ、テスト対象のゲッターのテストに集中することができます。
97 |
98 | この記事のテストの完成形は[こちら](https://github.com/lmiller1990/vue-testing-handbook/tree/master/demo-app/tests/unit/getters.spec.js)です。
99 |
--------------------------------------------------------------------------------
/src/ja/vuex-in-components-mutations-and-actions.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/src/ja/vuex-in-components-mutations-and-actions.md
--------------------------------------------------------------------------------
/src/ja/vuex-in-components.md:
--------------------------------------------------------------------------------
1 | ## Testing Vuex in components
2 |
3 | - how to mock Vuex
4 |
5 | ```
6 | const wrapper = shallowMount(Foo, {
7 | mocks: {
8 | $store: {
9 | state: {}
10 | }
11 | }
12 | })
13 | ```
14 |
15 | - Talk about using localVue.use(Vuex) to test
16 |
17 | ```
18 | localVue.use(Vuex)
19 | const store = new Vuex.Store({})
20 |
21 | const wrapper = shallowMount(Foo, {
22 | store
23 | localVue
24 | })
25 | ```
26 |
--------------------------------------------------------------------------------
/src/jest-mocking-modules.md:
--------------------------------------------------------------------------------
1 | :::tip This book is written for Vue.js 2 and Vue Test Utils v1.
2 | Find the Vue.js 3 version [here](/v3/).
3 | :::
4 |
5 | ## Mocking Modules
6 |
7 | TODO: Write this article. Interested? Open an issue, let's chat!
8 |
9 | Not directly related to Vue testing, more specific to Jest. Jest is used heavily throughout this handbook, though, and becoming widely adopted in the industry, so I think it deserves a section.
10 |
11 | Some ideas:
12 |
13 | 1. Reference the sections of the existing guide using Jest
14 | 2. Talk about the different kind of mocks and their use cases for Vue testing
15 |
16 | - Manual mocks
17 | - ES6 class mocks
18 | - automatic mock
19 | - calling `mockImplentation`?
20 |
21 | Ref: https://jestjs.io/docs/en/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock
22 |
--------------------------------------------------------------------------------
/src/ko/README.md:
--------------------------------------------------------------------------------
1 | ## 이 가이드는 무엇인가요?
2 |
3 | Vue.js 테스팅 핸드북에 방문한 것을 환영합니다!
4 |
5 | 이 가이드는 Vue 컴포넌트를 테스트 하는 방법에 집중한 예제를 짧게 모아 놓았습니다. 이 문서에서는 Vue 컴포넌트를 테스트 하기 위한 공식 라이브러리인 `vue-test-utils`와 근래에 자주 쓰이는 테스팅 프레임워크인 Jest를 사용합니다. 컴포넌트를 테스트 하는 모범 사례들 뿐만 아니라, `vue-test-utils`의 API도 다룹니다.
6 |
7 | 각각의 섹션은 독립적으로 구성되어 있습니다. 먼저 `vue-cli`로 환경을 설정하고 간단한 테스트를 작성하려고 합니다. 다음으로 컴포넌트를 렌더하는 방법인 `mount`와 `shallowMount`에 대해서 얘기해보려고 합니다. 두 방법이 어떻게 다른지 증명하고 설명하겠습니다.
8 |
9 | 이후에는 컴포넌트를 테스트 할 때 발생하는 다양한 시나리오를 테스트 하는 방법을 다룹니다. 테스트 할 컴포넌트는 아래와 같습니다.
10 |
11 | - props 받기
12 | - 컴퓨티드 프로퍼티
13 | - 다른 컴포넌트 렌더
14 | - 이벤트 방출
15 |
16 | 더 나아가서 아래와 같은 좀 더 흥미로운 경우도 살펴보겠습니다.
17 |
18 | - Vuex 테스트 모범 사례 (컴포넌트에서, 그리고 독립적으로)
19 | - Vue 라우터 테스트
20 | - 3rd 파티 컴포넌트를 포함한 테스트
21 |
22 | 아래의 예시와 같이, Jest의 API를 사용해서 작성한 테스트를 좀더 견고하게 만드는 방법도 알아봅니다.
23 |
24 | - API 응답 모킹하기
25 | - 모듈 모킹과 스파잉
26 | - 스냅샷 사용
27 |
28 | ## 더 읽을거리
29 |
30 | 아래의 유용한 자료들도 포함하고 있습니다.
31 |
32 | - [공식 문서](https://vue-test-utils.vuejs.org/)
33 | - vue-test-utils의 저작자가 쓴 [책](https://www.manning.com/books/testing-vue-js-applications)
34 | - 제가 하는 [Vue.js 3 + 유닛 테스팅 코스](https://vuejs-course.com) (2020년 초, 프리뷰/리뷰 부분을 이용할 수 있습니다)
35 | - Vue 핵심 기여자 일부가 참여한 [VueSchool의 훌륭한 강의](https://vueschool.io/courses/learn-how-to-test-vuejs-components?friend=vt의)
36 |
--------------------------------------------------------------------------------
/src/ko/jest-mocking-modules.md:
--------------------------------------------------------------------------------
1 | ## 모듈 모킹하기
2 |
3 | 해야할 일: 이 아티클을 작성합니다. 흥미로운가요? 이슈를 열고, 이야기 해주세요!
4 |
5 | Vue 테스팅과 직접적으로 관련이 있지는 않지만, Jest에 좀 더 특화된 주제입니다. Jest는 이 핸드북에서 많이 사용되고 있고, 산업 내에서도 폭넓게 채택되고 있습니다. 그래서 이 섹션이 가치가 있다고 생각합니다.
6 |
7 | 아래와 같은 아이디어가 있습니다.
8 |
9 | 1. Jest를 사용한 기존 가이드 섹션의 참조
10 | 2. Vue 테스팅을 위한 Jest의 사용 예제나 다른 종류의 mocks에 대해 얘기하기
11 |
12 | - 매뉴얼 mocks
13 | - ES6 클래스 mocks
14 | - 자동 mock
15 | - `mockImplementation` 호출하기?
16 |
17 | 참조: https://jestjs.io/docs/en/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock
18 |
--------------------------------------------------------------------------------
/src/ko/rendering-a-component.md:
--------------------------------------------------------------------------------
1 | ## 두 가지 렌더 방법
2 |
3 | `vue-test-utils`는 컴포넌트를 렌더하거나 __마운트__ 하는 두 가지 방법을 제공합니다. `mount`와 `shallowMount`라는 이름을 가졌습니다. 두 메서드 중 하나를 사용해서 마운트한 컴포넌트는 `wrapper`를 반환합니다. 반환된 wrapper는 Vue 컴포넌트를 포함하는 객체입니다. 덧붙여서 테스트하는데 유용한 몇 가지 메서드를 가지고 있습니다.
4 |
5 | 두 개의 간단한 컴포넌트를 가지고 시작해 보겠습니다.
6 |
7 | ```js
8 | const Child = Vue.component("Child", {
9 | name: "Child",
10 |
11 | template: "Child component
"
12 | })
13 |
14 | const Parent = Vue.component("Parent", {
15 | name: "Parent",
16 |
17 | template: "
"
18 | })
19 | ```
20 |
21 | 먼저 `Child`를 렌더링하고, `vue-test-utils`에서 마크업을 검사하기 위해 제공하는 `html` 메서드를 호출하겠습니다.
22 |
23 | ```js
24 | const shallowWrapper = shallowMount(Child)
25 | const mountWrapper = mount(Child)
26 |
27 | console.log(shallowWrapper.html())
28 | console.log(mountWrapper.html())
29 | ```
30 |
31 | `mountWrapper.html()`과 `shallowWrapper.html()` 양쪽 다 아래와 같은 결과가 나옵니다.
32 |
33 | ```html
34 | Child component
35 | ```
36 |
37 | 결과에 차이가 없습니다. `Parent`는 어떨까요?
38 |
39 | ```js
40 | const shallowWrapper = shallowMount(Parent)
41 | const mountWrapper = mount(Parent)
42 |
43 | console.log(shallowWrapper.html())
44 | console.log(mountWrapper.html())
45 | ```
46 |
47 | `mountWrapper.html()`은 이제 아래의 결과를 산출합니다.
48 |
49 | ```html
50 |
51 | ```
52 |
53 | `Parent`와 `Child`의 마크업이 완전히 렌더 되었습니다. 반면에 `shallowWrapper.html()`은 아래와 같이 나옵니다.
54 |
55 | ```html
56 |
57 | ```
58 |
59 | ` `가 위치한 곳이 `vuecomponent-stub />`으로 대체됐습니다. `shallowMount`는 정규 html 엘리먼트를 렌더하지만, Vue 컴포넌트를 스텁(stub)으로 대체합니다.
60 |
61 | > 스텁은 진짜를 위해서 세워 놓은 '가짜' 객체의 일종입니다.
62 |
63 | 스텁은 매우 유용한 방법입니다. 아래와 같은 `App.vue` 컴포넌트를 테스트하기를 원한다고 상상해보세요.
64 |
65 | ```vue
66 |
67 |
68 |
My Vue App
69 |
70 |
71 |
72 | ```
73 |
74 | 정확하게 렌더된 `My Vue App `을 테스트하려고 합니다. `mounted` 라이프사이클 훅에서 외부 API에 요청을 보내는 `` 컴포넌트도 가지고 있습니다.
75 |
76 | `mount`를 사용하면, 일부 텍스트의 렌더 여부만 확인하고 싶을 뿐이어도, ` `는 API를 요청합니다. 이 요청은 테스트를 느리게 만들거나 실패하기 쉽게 만듭니다. 그래서 외부 의존성을 없앱니다. `shallowMount`를 사용해서 ` `는 ` `으로 대체되고, API 호출은 시작되지 않습니다.
77 |
--------------------------------------------------------------------------------
/src/ko/testing-vuex.md:
--------------------------------------------------------------------------------
1 | ## Vuex 테스트하기
2 |
3 | 다음 몇 개의 가이드에서는 Vuex를 테스트하는 방법에 대해 다룹니다.
4 |
5 | ## Vuex 테스트의 두 가지 측면
6 |
7 | 일반적으로 컴포넌트는 아래의 경우에 Vuex와 상호작용 합니다.
8 |
9 | 1. 뮤테이션으로 커밋하기
10 | 2. 액션을 디스패치하기
11 | 3. `$store.state`나 getter를 통해 상태에 접근하기
12 |
13 | 이런 테스트는 컴포넌트가 Vuex 스토어(store)의 현재 상태를 바탕으로 정확하게 동작하는지 어설트(assert)합니다. 뮤테이터(mutators)나 액션(actions) 또는 게터(getters)의 구현체에 대해 알 필요는 없습니다.
14 |
15 | 뮤테이션(mutations)이나 게터처럼 스토어에 의해 수행되는 임의의 로직은 독립적으로 테스트할 수 있습니다. Vuex 스토어가 일반 자바스크립트 함수로 이루어져있어서 쉽게 유닛을 테스트할 수 있기 때문입니다.
16 |
17 | 다음 가이드는 Vuex 스토어를 사용하는 컴포넌트를 테스트하는 몇 가지 테크닉을 소개합니다. 그리고 스토어의 상태를 바탕으로 올바르게 동작하는지 확인합니다. 이후의 가이드에서는 독립적으로 Vuex를 테스트하는 방법에 대해 다루겠습니다.
18 |
--------------------------------------------------------------------------------
/src/ko/vuex-mutations.md:
--------------------------------------------------------------------------------
1 | ## 뮤테이션 테스트하기
2 |
3 | 독립적으로 뮤테이션(mutations)를 테스트하는 일은 매우 간단합니다. 뮤테이션이 단지 일반 자바스크립트 함수이기 때문입니다. 이 페이지는 독립적으로 뮤테이션을 테스트하는 일에 대해 다룹니다. 뮤테이션을 커밋하는(committing) 컴포넌트의 문맥(context)에서 뮤테이션을 테스트하길 원한다면 [여기](https://lmiller1990.github.io/vue-testing-handbook/vuex-in-components-mutations-and-actions.html)를 보세요.
4 |
5 | 다음 예제에서 사용한 코드는 [여기](https://github.com/lmiller1990/vue-testing-handbook/blob/master/demo-app/tests/unit/mutations.spec.js)서 찾을 수 있습니다.
6 |
7 | ## 뮤테이션 생성하기
8 |
9 | 뮤테이션은 일정한 패턴을 따르는 경향이 있습니다. 몇 가지 데이터를 얻기 위해서, 몇 가지 프로세스를 거치고, 그러고 나서 상태(state)에 데이터를 할당합니다. `ADD_POST` 뮤테이션의 개요는 다음과 같습니다. ADD_POST 뮤테이션은 한번 실행했을 때, 페이로드(payload)에서 `post` 객체를 받습니다. 그리고 `post.id`를 `state.postIds`에 추가합니다. 키 값이 `post.id`인 `state.posts` 객체에 post.id도 추가합니다. 이런 형태는 Vuex를 사용하는 앱의 일반적인 패턴입니다.
10 |
11 | TDD로 개발 해보겠습니다. 뮤테이션의 시작은 아래와 같습니다.
12 |
13 | ```js
14 | export default {
15 | SET_POST(state, { post }) {
16 |
17 | }
18 | }
19 | ```
20 |
21 | 테스트를 작성하겠습니다. 에러 메시지가 어떻게 개발해야 할지 인도해 줄 것입니다.
22 |
23 | ```js
24 | import mutations from "@/store/mutations.js"
25 |
26 | describe("SET_POST", () => {
27 | it("상태에 post를 추가한다", () => {
28 | const post = { id: 1, title: "Post" }
29 | const state = {
30 | postIds: [],
31 | posts: {}
32 | }
33 |
34 | mutations.SET_POST(state, { post })
35 |
36 | expect(state).toEqual({
37 | postIds: [1],
38 | posts: { "1": post }
39 | })
40 | })
41 | })
42 | ```
43 |
44 | `yarn test:unit`으로 테스트를 실행하면 아래와 같은 실패 메세지를 산출합니다.
45 |
46 | ```
47 | FAIL tests/unit/mutations.spec.js
48 | ● SET_POST › adds a post to the state
49 |
50 | expect(received).toEqual(expected)
51 |
52 | Expected value to equal:
53 | {"postIds": [1], "posts": {"1": {"id": 1, "title": "Post"}}}
54 | Received:
55 | {"postIds": [], "posts": {}}
56 | ```
57 |
58 | `state.postIds`에 `post.id`를 추가해보겠습니다.
59 |
60 | ```js
61 | export default {
62 | SET_POST(state, { post }) {
63 | state.postIds.push(post.id)
64 | }
65 | }
66 | ```
67 |
68 | 이제 `yarn test:unit`은 아래와 같은 메세지를 산출합니다.
69 |
70 | ```
71 | Expected value to equal:
72 | {"postIds": [1], "posts": {"1": {"id": 1, "title": "Post"}}}
73 | Received:
74 | {"postIds": [1], "posts": {}}
75 | ```
76 |
77 | `postIds`는 좋아 보입니다. 이제 `state.posts`에 post를 추가해야 합니다. Vue의 반응성 시스템이 작동하는 방법 때문에, 간단하게 `post[post.id] = post`를 작성해서 post를 추가할 수는 없습니다. 좀 더 세부적인 내용은 [여기](https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats)에서 찾을 수 있습니다. 기본적으로 `Object.assign`이나 `...` 연산자를 사용해서 새로운 객체를 만들어야 합니다. 여기서는 `...` 연산자를 사용해서 `state.posts`에 post를 할당하겠습니다.
78 |
79 | ```js
80 | export default {
81 | SET_POST(state, { post }) {
82 | state.postIds.push(post.id)
83 | state.posts = { ...state.posts, [post.id]: post }
84 | }
85 | }
86 | ```
87 |
88 | 이제 테스트가 통과합니다!
89 |
90 | ## 결론
91 |
92 | Vuex 뮤테이션을 테스트 할 때는 Vue나 Vuex에서 어떤 특별한 것도 요구하지 않습니다. Vuex 뮤테이션이 단지 일반적인 자바스크립트 함수이기 때문입니다. 유의해야 하는 유일한 부분은 Vue의 반응성 경고입니다. 이 경고는 Vuex에도 마찬가지로 적용됩니다. 반응성 시스템과 일반적인 경고에 대해 좀 더 자세히 알고 싶다면 [여기](https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats)에서 해당 내용에 대해 읽을 수 있습니다.
93 |
94 | 이 페이지는 아래와 같은 내용에 대해 다뤘습니다.
95 |
96 | - Vuex 뮤테이션은 일반 자바스크립트 함수이다
97 | - 뮤테이션은 main Vue app에서 독립적으로 테스트할 수 있으며, 테스트해야 한다
98 |
99 | 위 예제에서 사용한 테스트는 [여기](https://github.com/lmiller1990/vue-testing-handbook/blob/master/demo-app/tests/unit/mutations.spec.js)에서 찾을 수 있습니다.
100 |
--------------------------------------------------------------------------------
/src/rendering-a-component.md:
--------------------------------------------------------------------------------
1 | :::tip This book is written for Vue.js 2 and Vue Test Utils v1.
2 | Find the Vue.js 3 version [here](/v3/).
3 | :::
4 |
5 | ## Two ways to render
6 |
7 | `vue-test-utils` provides two ways to render, or __mount__ a component - `mount` and `shallowMount`. A component mounted using either of these methods returns a `wrapper`, which is an object containing the Vue component, plus some useful methods for testing.
8 |
9 | Let's start off with two simple components:
10 |
11 | ```js
12 | const Child = Vue.component("Child", {
13 | name: "Child",
14 |
15 | template: "Child component
"
16 | })
17 |
18 | const Parent = Vue.component("Parent", {
19 | name: "Parent",
20 |
21 | template: "
"
22 | })
23 | ```
24 |
25 | Let's start off by rendering `Child` and calling the `html` method `vue-test-utils` provides to inspect the markup.
26 |
27 | ```js
28 | const shallowWrapper = shallowMount(Child)
29 | const mountWrapper = mount(Child)
30 |
31 | console.log(shallowWrapper.html())
32 | console.log(mountWrapper.html())
33 | ```
34 |
35 | Both `mountWrapper.html()` and `shallowWrapper.html()` yield the following output:
36 |
37 | ```html
38 | Child component
39 | ```
40 |
41 | No difference here. How about with `Parent`?
42 |
43 | ```js
44 | const shallowWrapper = shallowMount(Parent)
45 | const mountWrapper = mount(Parent)
46 |
47 | console.log(shallowWrapper.html())
48 | console.log(mountWrapper.html())
49 | ```
50 |
51 | `mountWrapper.html()` now yields:
52 |
53 | ```html
54 |
55 | ```
56 |
57 | Which is the completely rendered markup of `Parent` and `Child`. `shallowWrapper.html()`, on the other hand, produces this:
58 |
59 | ```html
60 |
61 | ```
62 |
63 | The place where ` ` should be has been replaced by ` `. `shallowMount` renders regular html elements, but replaces Vue components with a stub.
64 |
65 | > A stub is kind of a "fake" object that stands in for a real one.
66 |
67 | This can be useful. Imagine you want to test your `App.vue` component, that looks like this:
68 |
69 | ```vue
70 |
71 |
72 |
My Vue App
73 |
74 |
75 |
76 | ```
77 |
78 | And we want to test `My Vue App ` is rendered correctly. We also have a `` component, that makes a request to an external API in its `mounted` lifecycle hook.
79 |
80 | If we use `mount`, although all we want to do is assert some text is rendered, ` ` will make an API request. This will make our test slow and prone to failure. So, we stub out external dependencies. By using `shallowMount`, ` ` will be replaced with a ` `, and the API call will not be initiated.
81 |
82 | As a rule of thumb, you should try to use `mount`, since that will more closely resemble your components and how they will appear in a real environment. That said, if you are having trouble with many API requests firing, or supplying the necessary dependencies to render your component, you can use `shallowMount`.:
83 |
--------------------------------------------------------------------------------
/src/ru/README.md:
--------------------------------------------------------------------------------
1 | :::tip Это руководство было написано для Vue.js 2 и Vue Test Utils v1.
2 | Версия для Vue.js 3 [здесь](/v3/ru).
3 | :::
4 |
5 | ## О чём это руководство?
6 |
7 | Добро пожаловать в руководство по тестированию Vue приложений!
8 |
9 | Здесь собрана коллекция коротких примеров тестирования компонентов с использованием `vue-test-utils` – официальной библиотекой для тестирования vue компонентов. Также используется `Jest` – современный фреймворк для тестирования. Рассматривается `vue-test-utils` API и лучшие практики тестирования.
10 |
11 | Руководство написано так, что его главы независимы одна от другой. Мы начнём с настройки окружения с помощью `vue-cli` и написания простейших тестов. Затем изучим два способа отрисовки компонентов – `mount` и `shallowMount`, поймём разницу между ними.
12 |
13 | Затем разберём несколько сценариев, которые возникают при тестировании компонентов:
14 |
15 | - принятие входных параметров
16 | - использование вычисляемых свойств
17 | - отрисовка других компонентов
18 | - пользовательские события
19 |
20 | и так далее. Потренируемся на таких интересных кейсах, как:
21 |
22 | - тестирование Vuex в компонентах и изоляции
23 | - тестирование Vue router
24 | - тестирование сторонних компонентов
25 |
26 | Также поработаем с Jest API, сделаем наши тесты более надёжными:
27 |
28 | - замокаем API ответы
29 | - замокаем модули и добавим spies (шпионы)
30 | - используем снимки
31 |
32 | ## Дальнейшее чтение
33 |
34 | Другие полезные ресурсы:
35 |
36 | - [Официальная документация](https://vue-test-utils.vuejs.org/ru/)
37 | - Мой курс [Vue.js 3 + Composition API + Unit Testing](https://vuejs-course.com)
38 | - [Бесплатный курс на VueSchool](https://vueschool.io/courses/learn-how-to-test-vuejs-components?friend=vth)
39 |
--------------------------------------------------------------------------------
/src/ru/jest-mocking-modules.md:
--------------------------------------------------------------------------------
1 | :::tip Это руководство было написано для Vue.js 2 и Vue Test Utils v1.
2 | Версия для Vue.js 3 [здесь](/v3/ru).
3 | :::
4 |
5 | ## Мокаем модули
6 |
7 | Не совсем про Vue тестирование, больше о Jest. Jest много где применяется на протяжении всего руководства, становится популярнее в индустрии, поэтому я считаю, он заслужил отдельной секции.
8 |
9 | Некоторые идеи:
10 |
11 | 1. Ссылки на секции руководства, где применяется Jest
12 | 2. Поговорить о различных видах мок и их применении во Vue тестировании
13 |
14 | - создаваемые вручную моки
15 | - моки для ES6 классов
16 | - автоматические моки
17 | - вызов `mockImplentation`?
18 |
19 | Источник: https://jestjs.io/docs/en/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock
20 |
--------------------------------------------------------------------------------
/src/ru/rendering-a-component.md:
--------------------------------------------------------------------------------
1 | :::tip Это руководство было написано для Vue.js 2 и Vue Test Utils v1.
2 | Версия для Vue.js 3 [здесь](/v3/ru).
3 | :::
4 |
5 | ## Два способа отрисовки
6 |
7 | `vue-test-utils` позволяет отрисовывать компонент двумя способами – `mount` и `shallowMount`. В любом из этих случаев, метод вернёт так называемый `wrapper`, который содержит объект с Vue компонентом и некоторыми полезными методами для тестирования.
8 |
9 | Давайте разберёмся на двух простых компонентах:
10 |
11 | ```js
12 | const Child = Vue.component("Child", {
13 | name: "Child",
14 |
15 | template: "Дочерний компонент
"
16 | })
17 |
18 | const Parent = Vue.component("Parent", {
19 | name: "Parent",
20 |
21 | template: "
"
22 | })
23 | ```
24 |
25 | Начнём с отрисовки `Child`, а затем вызовем метод `html`, который предоставляет `vue-test-utils` для тестирования разметки.
26 |
27 | ```js
28 | const shallowWrapper = shallowMount(Child)
29 | const mountWrapper = mount(Child)
30 |
31 | console.log(shallowWrapper.html())
32 | console.log(mountWrapper.html())
33 | ```
34 |
35 |
36 | Оба `mountWrapper.html()` и `shallowWrapper.html()` дают одинаковый результат:
37 |
38 | ```html
39 | Дочерний компонент
40 | ```
41 |
42 | Разницы нет. Но что насчёт `Parent`?
43 |
44 | ```js
45 | const shallowWrapper = shallowMount(Parent)
46 | const mountWrapper = mount(Parent)
47 |
48 | console.log(shallowWrapper.html())
49 | console.log(mountWrapper.html())
50 | ```
51 |
52 | `mountWrapper.html()` теперь выводит:
53 |
54 | ```html
55 |
56 | ```
57 |
58 | Полностью повторяется разметка `Parent` и `Child`. Однако `shallowWrapper.html()` выводит следующее:
59 |
60 | ```html
61 |
62 | ```
63 |
64 | То место, где должен быть ` ` теперь занимает ` `. `shallowMount` отрисовывает обычный html, но заменяет Vue-компоненты на заглушки.
65 |
66 | > Под заглушкой (stub) понимают поддельный объект, который помещается вместо настоящего.
67 |
68 | Это может быть полезно. Представьте, вы хотите протестировать компонент `App.vue`, который выглядит так:
69 |
70 | ```vue
71 |
72 |
73 |
Моё Vue-приложение
74 |
75 |
76 |
77 | ```
78 |
79 | И мы хотим протестировать, что `Моё Vue-приложение ` выводится правильно. У нас также есть компонент ` `, который делает запросы к стороннему API в своём `mounted` хуке.
80 |
81 | Если мы используем `mount`, то ` ` сделает запрос к API, хотя мы хотим всего лишь проверить отрисовку текста. Это сделает наши тесты медленными и ненадёжными. Поэтому мы применяем заглушки для сторонних зависимостей. Используя `shallowMount`, компонент ` ` заменится на ` ` и запроса к API не произойдёт.
82 |
83 | Как правило, вы должны использовать `mount`, поскольку это будет отражать поведение компонента в реальной среде. Тем не менее, если у вас возникли проблемы с выполнением множественных запросов API или с предоставлением необходимых зависимостей для рендеринга вашего компонента – вы можете использовать `shallowMount`.
84 |
--------------------------------------------------------------------------------
/src/ru/testing-vuex.md:
--------------------------------------------------------------------------------
1 | :::tip Это руководство было написано для Vue.js 2 и Vue Test Utils v1.
2 | Версия для Vue.js 3 [здесь](/v3/ru).
3 | :::
4 |
5 | ## Тестирование Vuex
6 |
7 | В следующих статьях обсудим как тестировать Vuex.
8 |
9 | ## Две стороны тестирования Vuex
10 |
11 | Обычно компоненты взаимодействуют с Vuex так:
12 |
13 | 1. вызываем мутацию через `commit`
14 | 2. запускаем действия через `dispatch`
15 | 3. обращаемся к хранилищу через `$store.state` или геттеры
16 |
17 | В тестах проверяется, что компонент ведёт себя правильно при текущем состоянии хранилища Vuex. Они не должны знать о реализациях мутаций, действий, геттеров.
18 |
19 | Любая логика хранилища, такая как мутация или использование геттеров, должна тестироваться в изоляции. Для Vuex достаточно просто писать модульные тесты, так как он хранит в себе простые JavaScript функции.
20 |
21 | В следующей статье разберём некоторые техники тестирования компонентов с использованием Vuex, убедимся, что их поведение зависит от состояния хранилища. Чуть позже обсудим тестирование Vuex в изоляции.
22 |
--------------------------------------------------------------------------------
/src/testing-vuex.md:
--------------------------------------------------------------------------------
1 | :::tip This book is written for Vue.js 2 and Vue Test Utils v1.
2 | Find the Vue.js 3 version [here](/v3/).
3 | :::
4 |
5 | ## Testing Vuex
6 |
7 | The next few guides discuss testing Vuex.
8 |
9 | ## Two Sides of Testing Vuex
10 |
11 | Generally components will interact with Vuex by
12 |
13 | 1. committing a mutation
14 | 2. dispatching an action
15 | 3. access the state via `$store.state` or getters
16 |
17 | These tests are to assert that the component behaves correctly based on the current state of the Vuex store. They do not need to know about the implementation of the mutators, actions or getters.
18 |
19 | Any logic performed by the store, such as mutations and getters, can be tested in isolation. Since Vuex stores are comprised of regular JavaScript functions, they are easily unit tested.
20 |
21 | The first few guides discuss techniques to test Vuex in isolation considering mutations, actions and getters. Following guides introduce some techniques to test components that use a Vuex store, and ensure they behave correctly based on the store's state.
22 |
--------------------------------------------------------------------------------
/src/v3/README.md:
--------------------------------------------------------------------------------
1 | :::tip This book is written for Vue.js 3 and Vue Test Utils v2.
2 | Find the Vue.js 2 version [here](/).
3 | :::
4 |
5 | ## What is this guide?
6 |
7 | Welcome to the Vue.js testing handbook!
8 |
9 | This is a collection of short, focused examples on how to test Vue components. It uses `vue-test-utils`, the official library for testing Vue components, and Jest, a modern testing framework. It covers the `vue-test-utils` API, as well as best practises for testing components.
10 |
11 | Each section is independent from the others. We start off by setting up an environment with `vue-cli` and writing a simple test. Next, two ways to render a component are discussed - `mount` and `shallowMount`. The differences will be demonstrated and explained.
12 |
13 | From then on, we cover how to test various scenarios that arise when testing components, such as testing components that:
14 |
15 | - receive props
16 | - use computed properties
17 | - render other components
18 | - emit events
19 |
20 | and so forth. We then move on to more interesting cases, such as:
21 |
22 | - best practises for testing Vuex (in components, and independently)
23 | - testing Vue router
24 | - testing involving third party components
25 |
26 | We will also explore how to use the Jest API to make our tests more robust, such as:
27 |
28 | - mocking API responses
29 | - mocking and spying on modules
30 | - using snapshots
31 |
32 | ## Further Reading
33 |
34 | Other useful resources include:
35 |
36 | - [Official docs](https://vue-test-utils.vuejs.org/v2/guide/introduction.html)
37 | - I made free series on Vue Test Utils + Vue 3: [YouTube playlist](https://www.youtube.com/playlist?list=PLC2LZCNWKL9ahK1IoODqYxKu5aA9T5IOA)
38 | - My [Vue.js 3 + Unit Testing Course](https://vuejs-course.com). VUEJS_COURSE_10_OFF for a $10 discount!
39 | - [This course on VueSchool](https://vueschool.io/courses/learn-how-to-test-vuejs-components?friend=vth) by several Vue core contributors
40 |
--------------------------------------------------------------------------------
/src/v3/jest-mocking-modules.md:
--------------------------------------------------------------------------------
1 | :::tip This book is written for Vue.js 3 and Vue Test Utils v2.
2 | Find the Vue.js 2 version [here](/).
3 | :::
4 |
5 | ## Mocking Modules
6 |
7 | TODO: Write this article. Interested? Open an issue, let's chat!
8 |
9 | Not directly related to Vue testing, more specific to Jest. Jest is used heavily throughout this handbook, though, and becoming widely adopted in the industry, so I think it deserves a section.
10 |
11 | Some ideas:
12 |
13 | 1. Reference the sections of the existing guide using Jest
14 | 2. Talk about the different kind of mocks and their use cases for Vue testing
15 |
16 | - Manual mocks
17 | - ES6 class mocks
18 | - automatic mock
19 | - calling `mockImplentation`?
20 |
21 | Ref: https://jestjs.io/docs/en/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock
22 |
--------------------------------------------------------------------------------
/src/v3/rendering-a-component.md:
--------------------------------------------------------------------------------
1 | :::tip This book is written for Vue.js 3 and Vue Test Utils v2.
2 | Find the Vue.js 2 version [here](/).
3 | :::
4 |
5 | ## Two ways to render
6 |
7 | `vue-test-utils` provides two ways to render, or __mount__ a component - `mount` and `shallowMount`. A component mounted using either of these methods returns a `wrapper`, which is an object containing the Vue component, plus some useful methods for testing.
8 |
9 | Let's start off with two simple components:
10 |
11 | ```js
12 | const Child = Vue.component("Child", {
13 | name: "Child",
14 |
15 | template: "Child component
"
16 | })
17 |
18 | const Parent = Vue.component("Parent", {
19 | name: "Parent",
20 |
21 | template: "
"
22 | })
23 | ```
24 |
25 | Let's start off by rendering `Child` and calling the `html` method `vue-test-utils` provides to inspect the markup.
26 |
27 | ```js
28 | const shallowWrapper = shallowMount(Child)
29 | const mountWrapper = mount(Child)
30 |
31 | console.log(shallowWrapper.html())
32 | console.log(mountWrapper.html())
33 | ```
34 |
35 | Both `mountWrapper.html()` and `shallowWrapper.html()` yield the following output:
36 |
37 | ```html
38 | Child component
39 | ```
40 |
41 | No difference here. How about with `Parent`?
42 |
43 | ```js
44 | const shallowWrapper = shallowMount(Parent)
45 | const mountWrapper = mount(Parent)
46 |
47 | console.log(shallowWrapper.html())
48 | console.log(mountWrapper.html())
49 | ```
50 |
51 | `mountWrapper.html()` now yields:
52 |
53 | ```html
54 |
55 | ```
56 |
57 | Which is the completely rendered markup of `Parent` and `Child`. `shallowWrapper.html()`, on the other hand, produces this:
58 |
59 | ```html
60 |
61 | ```
62 |
63 | The place where ` ` should be has been replaced by ` `. `shallowMount` renders regular html elements, but replaces Vue components with a stub.
64 |
65 | > A stub is kind of a "fake" object that stands in for a real one.
66 |
67 | This can be useful. Imagine you want to test your `App.vue` component, that looks like this:
68 |
69 | ```vue
70 |
71 |
72 |
My Vue App
73 |
74 |
75 |
76 | ```
77 |
78 | And we want to test `My Vue App ` is rendered correctly. We also have a `` component, that makes a request to an external API in its `mounted` lifecycle hook.
79 |
80 | If we use `mount`, although all we want to do is assert some text is rendered, ` ` will make an API request. This will make our test slow and prone to failure. So, we stub out external dependencies. By using `shallowMount`, ` ` will be replaced with a ` `, and the API call will not be initiated.
81 |
82 | As a rule of thumb, you should try to use `mount`, since that will more closely resemble your components and how they will appear in a real environment. That said, if you are having trouble with many API requests firing, or supplying the necessary dependencies to render your component, you can use `shallowMount`.:
83 |
--------------------------------------------------------------------------------
/src/v3/ru/README.md:
--------------------------------------------------------------------------------
1 | :::tip Это руководство было написано для Vue.js 3 и Vue Test Utils v2.
2 | Версия для Vue.js 2 [здесь](/ru).
3 | :::
4 |
5 | ## О чём это руководство?
6 |
7 | Добро пожаловать в руководство по тестированию Vue приложений!
8 |
9 | Здесь собрана коллекция коротких примеров тестирования компонентов с использованием `vue-test-utils` – официальной библиотекой для тестирования vue компонентов. Также используется `Jest` – современный фреймворк для тестирования. Рассматривается `vue-test-utils` API и лучшие практики тестирования.
10 |
11 | Руководство написано так, что его главы независимы одна от другой. Мы начнём с настройки окружения с помощью `vue-cli` и написания простейших тестов. Затем изучим два способа отрисовки компонентов – `mount` и `shallowMount`, поймём разницу между ними.
12 |
13 | Затем разберём несколько сценариев, которые возникают при тестировании компонентов:
14 |
15 | - принятие входных параметров
16 | - использование вычисляемых свойств
17 | - отрисовка других компонентов
18 | - пользовательские события
19 |
20 | и так далее. Потренируемся на таких интересных кейсах, как:
21 |
22 | - тестирование Vuex в компонентах и изоляции
23 | - тестирование Vue router
24 | - тестирование сторонних компонентов
25 |
26 | Также поработаем с Jest API, сделаем наши тесты более надёжными:
27 |
28 | - замокаем API ответы
29 | - замокаем модули и добавим spies (шпионы)
30 | - используем снимки
31 |
32 | ## Дальнейшее чтение
33 |
34 | Другие полезные ресурсы:
35 |
36 | - [Официальная документация](https://vue-test-utils.vuejs.org/ru/)
37 | - [Книга](https://www.manning.com/books/testing-vue-js-applications), написанная авторами `vue-test-utils`
38 | - Мой курс [Vue.js 3 + Composition API + Unit Testing](https://vuejs-course.com)
39 | - [Бесплатный курс на VueSchool](https://vueschool.io/courses/learn-how-to-test-vuejs-components?friend=vth)
40 |
--------------------------------------------------------------------------------
/src/v3/ru/jest-mocking-modules.md:
--------------------------------------------------------------------------------
1 | :::tip Это руководство было написано для Vue.js 3 и Vue Test Utils v2.
2 | Версия для Vue.js 2 [здесь](/ru).
3 | :::
4 |
5 | ## Мокаем модули
6 |
7 | Не совсем про Vue тестирование, больше о Jest. Jest много где применяется на протяжении всего руководства, становится популярнее в индустрии, поэтому я считаю, он заслужил отдельной секции.
8 |
9 | Некоторые идеи:
10 |
11 | 1. Ссылки на секции руководства, где применяется Jest
12 | 2. Поговорить о различных видах мок и их применении во Vue тестировании
13 |
14 | - создаваемые вручную моки
15 | - моки для ES6 классов
16 | - автоматические моки
17 | - вызов `mockImplentation`?
18 |
19 | Источник: https://jestjs.io/docs/en/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock
20 |
--------------------------------------------------------------------------------
/src/v3/ru/rendering-a-component.md:
--------------------------------------------------------------------------------
1 | :::tip Это руководство было написано для Vue.js 3 и Vue Test Utils v2.
2 | Версия для Vue.js 2 [здесь](/ru).
3 | :::
4 |
5 | ## Два способа отрисовки
6 |
7 | `vue-test-utils` позволяет отрисовывать компонент двумя способами – `mount` и `shallowMount`. В любом из этих случаев, метод вернёт так называемый `wrapper`, который содержит объект с Vue компонентом и некоторыми полезными методами для тестирования.
8 |
9 | Давайте разберёмся на двух простых компонентах:
10 |
11 | ```js
12 | const Child = Vue.component("Child", {
13 | name: "Child",
14 |
15 | template: "Дочерний компонент
"
16 | })
17 |
18 | const Parent = Vue.component("Parent", {
19 | name: "Parent",
20 |
21 | template: "
"
22 | })
23 | ```
24 |
25 | Начнём с отрисовки `Child`, а затем вызовем метод `html`, который предоставляет `vue-test-utils` для тестирования разметки.
26 |
27 | ```js
28 | const shallowWrapper = shallowMount(Child)
29 | const mountWrapper = mount(Child)
30 |
31 | console.log(shallowWrapper.html())
32 | console.log(mountWrapper.html())
33 | ```
34 |
35 |
36 | Оба `mountWrapper.html()` и `shallowWrapper.html()` дают одинаковый результат:
37 |
38 | ```html
39 | Дочерний компонент
40 | ```
41 |
42 | Разницы нет. Но что насчёт `Parent`?
43 |
44 | ```js
45 | const shallowWrapper = shallowMount(Parent)
46 | const mountWrapper = mount(Parent)
47 |
48 | console.log(shallowWrapper.html())
49 | console.log(mountWrapper.html())
50 | ```
51 |
52 | `mountWrapper.html()` теперь выводит:
53 |
54 | ```html
55 |
56 | ```
57 |
58 | Полностью повторяется разметка `Parent` и `Child`. Однако `shallowWrapper.html()` выводит следующее:
59 |
60 | ```html
61 |
62 | ```
63 |
64 | То место, где должен быть ` ` теперь занимает ` `. `shallowMount` отрисовывает обычный html, но заменяет Vue-компоненты на заглушки.
65 |
66 | > Под заглушкой (stub) понимают поддельный объект, который помещается вместо настоящего.
67 |
68 | Это может быть полезно. Представьте, вы хотите протестировать компонент `App.vue`, который выглядит так:
69 |
70 | ```vue
71 |
72 |
73 |
Моё Vue-приложение
74 |
75 |
76 |
77 | ```
78 |
79 | И мы хотим протестировать, что `Моё Vue-приложение ` выводится правильно. У нас также есть компонент ` `, который делаем запросы к стороннему API в своём `mounted` хуке.
80 |
81 | Если мы используем `mount`, то ` ` сделает запрос к API, хотя мы хотим всего лишь проверить отрисовку текста. Это сделаем наши тесты медленными и ненадёжными. Поэтому мы применяем заглушки для сторонних зависимостей. Используя `shallowMount`, компонент ` ` заменится на ` ` и запроса к API не произойдёт.
82 |
83 | Как правило, вы должны использовать `mount`, поскольку это будет отражать поведение компонента в реальной среде. Тем не менее, если у вас возникли проблемы с выполнением множественных запросов API или с предоставлением необходимых зависимостей для рендеринга вашего компонента – вы можете использовать `shallowMount`.
84 |
--------------------------------------------------------------------------------
/src/v3/ru/testing-vuex.md:
--------------------------------------------------------------------------------
1 | :::tip Это руководство было написано для Vue.js 3 и Vue Test Utils v2.
2 | Версия для Vue.js 2 [здесь](/ru).
3 | :::
4 |
5 | ## Тестирование Vuex
6 |
7 | В следующих статьях обсудим как тестировать Vuex.
8 |
9 | ## Две стороны тестирования Vuex
10 |
11 | Обычно компоненты взаимодействуют с Vuex так:
12 |
13 | 1. вызываем мутацию через `commit`
14 | 2. запускаем действия через `dispatch`
15 | 3. обращаемся к хранилищу через `$store.state` или геттеры
16 |
17 | В тестах проверяется, что компонент ведёт себя правильно при текущем состоянии хранилища Vuex. Они не должны знать о реализациях мутаций, действий, геттеров.
18 |
19 | Любая логика хранилища, такая как мутация или использование геттеров, должна тестироваться в изоляции. Для Vuex достаточно просто писать модульные тесты, так как он хранит в себе простые JavaScript функции.
20 |
21 | В следующей статье разберём некоторые техники тестирования компонентов с использованием Vuex, убедимся, что их поведение зависит от состояния хранилища. Чуть позже обсудим тестирование Vuex в изоляции.
22 |
--------------------------------------------------------------------------------
/src/v3/testing-vuex.md:
--------------------------------------------------------------------------------
1 | :::tip This book is written for Vue.js 3 and Vue Test Utils v2.
2 | Find the Vue.js 2 version [here](/).
3 | :::
4 |
5 | ## Testing Vuex
6 |
7 | The next few guides discuss testing Vuex.
8 |
9 | ## Two Sides of Testing Vuex
10 |
11 | Generally components will interact with Vuex by
12 |
13 | 1. committing a mutation
14 | 2. dispatching an action
15 | 3. access the state via `$store.state` or getters
16 |
17 | These tests are to assert that the component behaves correctly based on the current state of the Vuex store. They do not need to know about the implementation of the mutators, actions or getters.
18 |
19 | Any logic performed by the store, such as mutations and getters, can be tested in isolation. Since Vuex stores are comprised of regular JavaScript functions, they are easily unit tested.
20 |
21 | The first few guides discuss techniques to test Vuex in isolation considering mutations, actions and getters. Following guides introduce some techniques to test components that use a Vuex store, and ensure they behave correctly based on the store's state.
22 |
--------------------------------------------------------------------------------
/src/zh-CN/README.md:
--------------------------------------------------------------------------------
1 | ## 这本指南是什么?
2 |
3 | 欢迎来到 Vue.js 测试指南!
4 |
5 | 这是一系列关于如何测试 Vue 组件的短小而目标明确的示例。它使用了测试 Vue 组件的官方库 [`vue-test-utils`](https://github.com/vuejs/vue-test-utils),以及一个现代测试框架 [Jest](https://jestjs.io/)。本书不但涵盖了 `vue-test-utils` 的 API,而且也是测试组件的最佳实践。
6 |
7 | 每个章节都是互相独立的。我们从通过 `vue-cli` 设置一个环境并编写一个简单的测试起步。而后,讨论了两种渲染一个组件的方式 -- `mount` 和 `shallowMount`。其区别将被演示和说明。
8 |
9 | 从那时起,我们覆盖了如何应对测试组件时出现的各种情景,诸如:
10 |
11 | - 接受 props
12 | - 使用 computed 属性
13 | - 渲染其他组件
14 | - emit 事件
15 |
16 | 等等。其后我们继续向更多有趣的场景进发,比如:
17 |
18 | - 测试 Vuex (在组件中,并且是独立的) 的最佳实践
19 | - 测试 Vue router
20 | - 测试包含的第三方组件
21 |
22 | 我们也将探索如何使用 Jest API 让我们的测试更健壮,如:
23 |
24 | - mocking API 响应
25 | - 模块上的 mocking 和 spying
26 | - 使用 snapshots
27 |
28 | ## 延伸阅读
29 |
30 | 更多有用的资源包括:
31 |
32 | - [Official docs](https://vue-test-utils.vuejs.org/)
33 | - [Book](https://www.manning.com/books/testing-vue-js-applications) 另一位作者写的 `vue-test-utils`
34 | - [This awesome course on VueSchool](https://vueschool.io/courses/learn-how-to-test-vuejs-components?friend=vth) 由多位 Vue 核心贡献者编写
35 |
--------------------------------------------------------------------------------
/src/zh-CN/jest-mocking-modules.md:
--------------------------------------------------------------------------------
1 | ## Mocking Modules
2 |
3 | TODO: Write this article. Interested? Open an issue, let's chat!
4 |
5 | Not directly related to Vue testing, more specific to Jest. Jest is used heavily throughout this handbook, though, and becoming widely adopted in the industry, so I think it deserves a section.
6 |
7 | Some ideas:
8 |
9 | 1. Reference the sections of the existing guide using Jest
10 | 2. Talk about the different kind of mocks and their use cases for Vue testing
11 |
12 | - Manual mocks
13 | - ES6 class mocks
14 | - automatic mock
15 | - calling `mockImplentation`?
16 |
17 | Ref: https://jestjs.io/docs/en/es6-class-mocks#the-4-ways-to-create-an-es6-class-mock
18 |
--------------------------------------------------------------------------------
/src/zh-CN/rendering-a-component.md:
--------------------------------------------------------------------------------
1 | ## 两种渲染方式
2 |
3 | `vue-test-utils` 提供了两种方式用于渲染,或者说 __加载(mount)__ 一个组件 -- `mount` 和 `shallowMount`。一个组件无论使用这两种方法的哪个都会返回一个 `wrapper`,也就是一个包含了 Vue 组件的对象,辅以一些对测试有用的方法。
4 |
5 | 让我们从两个简单的组件开始:
6 |
7 | ```js
8 | const Child = Vue.component("Child", {
9 | name: "Child",
10 |
11 | template: "Child component
"
12 | })
13 |
14 | const Parent = Vue.component("Parent", {
15 | name: "Parent",
16 |
17 | template: "
"
18 | })
19 | ```
20 | 先来渲染 `Child` 并调用由 `vue-test-utils` 提供的用以核查置标语言的 `html` 方法。
21 |
22 | ```js
23 | const shallowWrapper = shallowMount(Child)
24 | const mountWrapper = mount(Child)
25 |
26 | console.log(shallowWrapper.html())
27 | console.log(mountWrapper.html())
28 | ```
29 |
30 | `mountWrapper.html()` 和 `shallowWrapper.html()` 都产生了如下输出:
31 |
32 | ```html
33 | Child component
34 | ```
35 |
36 | 此次并没有差别。换作 `Parent` 又如何呢?
37 |
38 | ```js
39 | const shallowWrapper = shallowMount(Parent)
40 | const mountWrapper = mount(Parent)
41 |
42 | console.log(shallowWrapper.html())
43 | console.log(mountWrapper.html())
44 | ```
45 |
46 | `mountWrapper.html()` 现在产生了:
47 |
48 | ```html
49 |
50 | ```
51 |
52 | 这完整地渲染了 `Parent` 和 `Child` 的标记。而 `shallowWrapper.html()` 产生了如下输出:
53 |
54 | ```html
55 |
56 | ```
57 |
58 | 原本 ` ` 应该出现的地方被替换成了 ` `。`shallowMount` 会渲染常规的 HTML 元素,但将用 stub 替换掉 Vue 组件。
59 |
60 | > 一个 stub 就是一种替代真实组件的 “假的” 对象
61 |
62 | 这会很管用。想象一下要测试你的 `App.vue` 组件,看起来是这样的:
63 |
64 | ```vue
65 |
66 |
67 |
My Vue App
68 |
69 |
70 |
71 | ```
72 |
73 | 并且我们只想测试 `My Vue App ` 被正确地渲染了。但同时我们也有一个 `` 组件,该组件在其 `mounted` 生命周期钩子中向外部 API 发起一个请求。
74 |
75 | 如果我们用了 `mount`,尽管我们只想断言一些文本被渲染,但 ` ` 也将发起 API 请求。这将拖慢测试并容易出错。所以,我们 stub 掉外部依赖。通过使用 `shallowMount`,` ` 将会被替换为一个 ` `,并且 API 调用也不会被初始化了。
76 |
--------------------------------------------------------------------------------
/src/zh-CN/testing-vuex.md:
--------------------------------------------------------------------------------
1 | ## 测试 Vuex
2 |
3 | 下面几篇将会讨论测试 Vuex。
4 |
5 | ## 测试 Vuex 的两方面
6 |
7 | 通常来说组件会在以下方面和 Vuex 发生交互:
8 |
9 | 1. commit 一个 mutation
10 | 2. dispatch 一个 action
11 | 3. 通过 `$store.state` 或 getters 访问 state
12 |
13 | 这些测试都是基于 Vuex store 的当前 state 来断言组件行为是否正常的。它们并不需要知道 mutators、actions 或 getters 的实现。
14 |
15 | store 所执行的任何逻辑,诸如 mutations 和 getters,都能被单独地测试。因为 Vuex stores 由普通 JavaScript 函数组成,所以它们易于被单元测试。
16 |
17 | 下一篇介绍了一些测试使用了 Vuex store 的组件、并确保它们按 store 的 state 产生正确行为的技术。其后的章节则讨论隔离地测试 Vuex。
18 |
--------------------------------------------------------------------------------
/src/zh-CN/vuex-getters.md:
--------------------------------------------------------------------------------
1 | ## 测试 getters
2 |
3 | 由于 getters 就是普通的 JavaScript 函数,所以单独地测试它们非常容易。所用技术类似于测试 mutations 或 actions,更多信息查看 [这里](https://lmiller1990.github.io/vue-testing-handbook/vuex-mutations.html)。
4 |
5 | 本页中描述的测试源码可以在 [这里](https://github.com/lmiller1990/vue-testing-handbook/tree/master/demo-app/tests/unit/getters.spec.js) 找到。
6 |
7 | 我们考虑一个用两个 getters 操作一个 store 的案例,看起来是这样的:
8 |
9 | ```js
10 | const state = {
11 | dogs: [
12 | { name: "lucky", breed: "poodle", age: 1 },
13 | { name: "pochy", breed: "dalmatian", age: 2 },
14 | { name: "blackie", breed: "poodle", age: 4 }
15 | ]
16 | }
17 | ```
18 |
19 | 对于 getters 我们将测试:
20 |
21 | 1. `poodles`: 取得所有 `poodles`
22 | 2. `poodlesByAge`: 取得所有 `poodles`,并接受一个年龄参数
23 |
24 | ## 创建 getters
25 |
26 | 首先,创建 getters。
27 |
28 | ```js
29 | export default {
30 | poodles: (state) => {
31 | return state.dogs.filter(dog => dog.breed === "poodle")
32 | },
33 |
34 | poodlesByAge: (state, getters) => (age) => {
35 | return getters.poodles.filter(dog => dog.age === age)
36 | }
37 | }
38 | ```
39 |
40 | 并没有什么特别令人兴奋的 -- 记住 getter 可以接受其他的 getters 作为第二个参数。因为我们已经有一个 `poodles` getter 了,可以在 `poodlesByAge` 中复用它。通过在 `poodlesByAge` 返回一个接受参数的函数,我们可以向 getters 中传入参数。`poodlesByAge` getter 用法是这样的:
41 |
42 | ```js
43 | computed: {
44 | puppies() {
45 | return this.$store.getters.poodlesByAge(1)
46 | }
47 | }
48 | ```
49 |
50 | 让我们从测试 `poodles` 开始吧。
51 |
52 | ## 编写测试
53 |
54 | 鉴于一个 getter 只是一个接收一个 `state` 对象作为首个参数的 JavaScript 函数,所以测试起来非常简单。我将把测试写在 `getters.spec.js` 文件中,代码如下:
55 |
56 | ```js
57 | import getters from "../../src/store/getters.js"
58 |
59 | const dogs = [
60 | { name: "lucky", breed: "poodle", age: 1 },
61 | { name: "pochy", breed: "dalmatian", age: 2 },
62 | { name: "blackie", breed: "poodle", age: 4 }
63 | ]
64 | const state = { dogs }
65 |
66 | describe("poodles", () => {
67 | it("returns poodles", () => {
68 | const actual = getters.poodles(state)
69 |
70 | expect(actual).toEqual([ dogs[0], dogs[2] ])
71 | })
72 | })
73 | ```
74 |
75 | Vuex 会自动将 `state` 传入 getter。因为我们是单独地测试 getters,所以还得手动传入 `state`。除此之外,我们就是在测试一个普通的 JavaScript 函数。
76 |
77 | `poodlesByAge` 则更有趣一点了。传入一个 getter 的第二个参数是其他 `getters`。我们正在测试的是 `poodlesByAge`,所以我们不想将 `poodles` 的实现牵扯进来。我们通过 stub 掉 `getters.poodles` 取而代之。这将给我们对测试更细粒度的控制。
78 |
79 | ```js
80 | describe("poodlesByAge", () => {
81 | it("returns poodles by age", () => {
82 | const poodles = [ dogs[0], dogs[2] ]
83 | const actual = getters.poodlesByAge(state, { poodles })(1)
84 |
85 | expect(actual).toEqual([ dogs[0] ])
86 | })
87 | })
88 | ```
89 |
90 | 不同于向 getter 传入真实的 `poodles`(译注:刚刚测试过的另一个 getter),我们传入的是一个它可能返回的结果。因为之前写过一个测试了,所以我们知道它是工作正常的。这使得我们把测试逻辑单独聚焦于 `poodlesByAge`。
91 |
92 | `async` 的 getters 也是可能的。它们可以通过和测试 `async` actions 的相同技术被测试,参见 [这里](https://lmiller1990.github.io/vue-testing-handbook/vuex-actions.html)。
93 |
94 | ## 总结
95 |
96 | - `getters` 只是普通的 JavaScript 函数。
97 | - 当单独地测试 `getters` 时,你需要手动传入 state
98 | - 如果一个 getter 使用了其他 getters,你应该用符合期望的返回结果 stub 掉后者。这将给我们对测试更细粒度的控制,并让你聚焦于测试中的 getter。
99 |
100 | 本页中描述的测试源码可以在 [这里](https://github.com/lmiller1990/vue-testing-handbook/tree/master/demo-app/tests/unit/getters.spec.js) 找到。
101 |
--------------------------------------------------------------------------------
/src/zh-CN/vuex-mutations.md:
--------------------------------------------------------------------------------
1 | ## 测试 Mutations
2 |
3 | 由于 mutations 就是普通的 JavaScript 函数,所以单独地测试它们非常容易,在本页中讨论了这点。如果你想在组件 commit 一个 mutation 的上下文中测试 mutations,请查看 [这里](https://lmiller1990.github.io/vue-testing-handbook/vuex-in-components-mutations-and-actions.html) 。
4 |
5 | 下面例子中用到的测试可以在 [这里](https://github.com/lmiller1990/vue-testing-handbook/blob/master/demo-app/tests/unit/mutations.spec.js) 找到。
6 |
7 | ## 创建 mutation
8 |
9 | mutations 易于遵循一套模式:取得一些数据,可能进行一些处理,然后将数据赋值给 state。比如一个 `ADD_POST` mutation 的概述如下:一旦被实现,它将从 payload 中获取一个 `post` 对象,并将 `post.id` 添加到 `state.postIds` 中;它也会将那个 post 对象以 `post.id` 为 key 添加到 `state.posts` 对象中。这即是在应用中使用 Vuex 的一个通常的模式。
10 |
11 | 我们将使用 TDD 进行开发。mutation 是这样开头的:
12 |
13 | ```js
14 | export default {
15 | SET_POST(state, { post }) {
16 |
17 | }
18 | }
19 | ```
20 |
21 | 让我们开始写测试,并让报错信息指引我们的开发:
22 |
23 | ```js
24 | import mutations from "@/store/mutations.js"
25 |
26 | describe("SET_POST", () => {
27 | it("adds a post to the state", () => {
28 | const post = { id: 1, title: "Post" }
29 | const state = {
30 | postIds: [],
31 | posts: {}
32 | }
33 |
34 | mutations.SET_POST(state, { post })
35 |
36 | expect(state).toEqual({
37 | postIds: [1],
38 | posts: { "1": post }
39 | })
40 | })
41 | })
42 | ```
43 |
44 | 以 `yarn test:unit` 运行测试将产生以下错误信息:
45 |
46 | ```
47 | FAIL tests/unit/mutations.spec.js
48 | ● SET_POST › adds a post to the state
49 |
50 | expect(received).toEqual(expected)
51 |
52 | Expected value to equal:
53 | {"postIds": [1], "posts": {"1": {"id": 1, "title": "Post"}}}
54 | Received:
55 | {"postIds": [], "posts": {}}
56 | ```
57 |
58 | 让我们从将 `post.id` 加入 `state.postIds` 开始:
59 |
60 | ```js
61 | export default {
62 | SET_POST(state, { post }) {
63 | state.postIds.push(post.id)
64 | }
65 | }
66 | ```
67 |
68 | 现在 `yarn test:unit` 会产生:
69 |
70 | ```
71 | Expected value to equal:
72 | {"postIds": [1], "posts": {"1": {"id": 1, "title": "Post"}}}
73 | Received:
74 | {"postIds": [1], "posts": {}}
75 | ```
76 |
77 | `postIds` 看起来挺好了。现在我们只需要将 post 加入 `state.posts`。限于 Vue 反应式系统的工作方式我们无法简单地写成 `post[post.id] = post` 来添加 post。更多细节可以在 [这里](https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats)找到。基本上,你需要使用 `Object.assign` 或 `...` 操作符创建一个新的对象。此处我们将使用 `...` 操作符将 post 赋值到 `state.posts`:
78 |
79 | ```js
80 | export default {
81 | SET_POST(state, { post }) {
82 | state.postIds.push(post.id)
83 | state.posts = { ...state.posts, [post.id]: post }
84 | }
85 | }
86 | ```
87 |
88 | 现在测试都通过了!
89 |
90 | ## 总结
91 |
92 | 测试 Vuex mutations 不需要什么特殊的 Vue 或 Vuex 功能,因为它们都是普通的 JavaScript 函数。根据需要简单地引入它们并测试就行了。唯一需要留意的事情是 Vue 的反应式注意事项,对于 Vuex 也是一样的。更多关于反应式系统和一般注意事项可以在 [这里](https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats) 读到。
93 |
94 | 本页讨论了:
95 |
96 | - Vuex mutations 是普通的 JavaScript 函数
97 | - mutations 可以、也应该,被区别于主 Vue 应用而单独地测试
98 |
99 | 以上例子中使用的测试可以在 [这里](https://github.com/lmiller1990/vue-testing-handbook/blob/master/demo-app/tests/unit/mutations.spec.js) 找到。
100 |
--------------------------------------------------------------------------------
/src/zh/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lmiller1990/vue-testing-handbook/47f793e8408f3861238c130a477492b04d0e06e7/src/zh/.gitkeep
--------------------------------------------------------------------------------