Skip to content

Commit cb1157b

Browse files
Jessica SachsJessica Sachs
authored andcommitted
docs: add code block switcher
1 parent c944c0c commit cb1157b

File tree

2 files changed

+195
-27
lines changed

2 files changed

+195
-27
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue';
3+
const activeId = ref('vtu-api')
4+
5+
const testingLangs = [
6+
{
7+
label: 'Vue Test Utils',
8+
className: 'vtu-api'
9+
},
10+
{
11+
label: 'Cypress',
12+
className: 'cypress-api'
13+
},
14+
{
15+
label: 'Testing Library',
16+
className: 'testing-library-api'
17+
},
18+
]
19+
20+
</script>
21+
22+
<template>
23+
<div class="testing-code-examples" :class="`prefers-${activeId}`">
24+
<div class="tabs">
25+
<div
26+
v-for="lang in testingLangs"
27+
:key="lang.className"
28+
class="tab"
29+
:class="{ active: lang.className === activeId }"
30+
@click="activeId = lang.className"
31+
>{{ lang.label }}</div>
32+
</div>
33+
<div class="code-example">
34+
<slot/>
35+
</div>
36+
</div>
37+
</template>
38+
39+
<style scoped>
40+
/* TODO: Replace with a VTCodeGroup when available */
41+
/* Layout */
42+
.code-examples {
43+
display: flex;
44+
flex-direction: column;
45+
}
46+
47+
:slotted([class*=language]) {
48+
margin-top: 0;
49+
border-top-left-radius: 0;
50+
}
51+
52+
/* Tab Styles */
53+
.tabs {
54+
display: flex;
55+
}
56+
57+
.tab {
58+
color: white;
59+
background: #292d3ef0;
60+
border-bottom-color: rgba(255,255,255,0.3);
61+
padding: 6px 24px;
62+
border-width: 2px;
63+
border-style: solid;
64+
border-top: transparent;
65+
border-right: transparent;
66+
border-left: transparent;
67+
cursor: pointer;
68+
transition: border, background-color .2s;
69+
transition-property: border, background-color;
70+
transition-duration: 0.2s, 0.2s;
71+
transition-timing-function: ease, ease;
72+
transition-delay: 0s, 0s;
73+
}
74+
75+
.tab.active {
76+
background: #292d3e;
77+
border-bottom: 2px solid var(--vt-c-brand);
78+
}
79+
80+
.tab:first-child {
81+
border-top-left-radius: 8px;
82+
}
83+
84+
.tab:last-child {
85+
border-top-right-radius: 8px;
86+
}
87+
88+
/* When the sm media query hits, make sure the
89+
tabs line up with the negative margins of the
90+
code blocks. Should also remove the border radius.
91+
*/
92+
@media screen and (max-width: 639px) {
93+
.tabs {
94+
margin: 0 -24px;
95+
}
96+
97+
.tab, .tab:first-child, .tab:last-child {
98+
flex-grow: 1;
99+
text-align: center;
100+
border-radius: 0;
101+
}
102+
}
103+
104+
:global(.dark .testing-code-examples .tab:not(.active)) {
105+
border-bottom: 2px solid rgba(255,255,255,.2);
106+
background: #2f2f2f;
107+
color: inherit;
108+
109+
}
110+
111+
:global(.dark .testing-code-examples .tab.active) {
112+
background: var(--vt-c-black-soft);
113+
}
114+
115+
/* Show/Hide logic for codeblocks */
116+
:slotted([class$="api"]) {
117+
display: none;
118+
}
119+
120+
.prefers-cypress-api :slotted(.cypress-api),
121+
.prefers-testing-library-api :slotted(.testing-library-api),
122+
.prefers-vtu-api :slotted(.vtu-api) {
123+
display: block;
124+
}
125+
126+
</style>

src/guide/scaling-up/testing.md

Lines changed: 69 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
<script setup>
2+
import TestingApiSwitcher from './TestingApiSwitcher.vue'
3+
</script>
4+
15
# Testing
26

37
## Why Test?
@@ -53,15 +57,15 @@ If any of these assertions fail, it's clear that the issue is contained within t
5357
import { increment } from './helpers'
5458
5559
describe('increment', () => {
56-
it('increments the current number by 1', () => {
60+
test('increments the current number by 1', () => {
5761
expect(increment(0, 10)).toBe(1)
5862
})
5963
60-
it('does not increment the current number over the max', () => {
64+
test('does not increment the current number over the max', () => {
6165
expect(increment(10, 10)).toBe(10)
6266
})
6367
64-
it('has a default max of 10', () => {
68+
test('has a default max of 10', () => {
6569
expect(increment(10)).toBe(10)
6670
})
6771
})
@@ -87,7 +91,7 @@ A component can be tested in two ways:
8791

8892
1. Whitebox: Unit Testing
8993

90-
Tests that are "Whitebox tests" are aware of the implementation details and dependencies of a component. Components be unit tested by using [`@vue/test-utils`'s](https://test-utils.vuejs.org) `shallowMount` command instead of the `mount` command. You can also make use of the `global.stubs` API. Please read the Vue Test Utils docs for some help on [how to decide](https://test-utils.vuejs.org/guide/advanced/stubs-shallow-mount.html#mount-shallow-and-stubs-which-one-and-when) if you want to Unit Test or Component Test your components. As stated above, unit tests may mock initial state and large parts of your application, when unit testing components, this includes 3rd party components, libraries, and all child components.
94+
Tests that are "Whitebox tests" are aware of the implementation details and dependencies of a component. Components mustmbe unit tested by using [`@vue/test-utils`'s](https://test-utils.vuejs.org) `shallowMount` command instead of the `mount` command. You can also make use of the `global.stubs` API. Please read the Vue Test Utils docs for some help on [how to decide](https://test-utils.vuejs.org/guide/advanced/stubs-shallow-mount.html#mount-shallow-and-stubs-which-one-and-when) if you want to Unit Test or Component Test your components. As stated above, unit tests may mock initial state and large parts of your application, when unit testing components, this includes 3rd party components, libraries, and all child components.
9195

9296
2. Blackbox: Component Testing
9397
Tests that are "Blackbox tests" are unaware of the implementation details of a component. These tests do not mock anything. They render all child components and are considered more of an "integration test" in which Components are the Subject Under Test. We will cover this in the next section.
@@ -127,6 +131,56 @@ In the below example, we demonstrate a Stepper component that has a DOM element
127131

128132
We know nothing about the implementation of Stepper, only that the "input" is the `max` prop and the "output" is the state of the DOM as the user will see it.
129133

134+
<TestingApiSwitcher>
135+
136+
<div class="testing-library-api">
137+
138+
```js
139+
render(Stepper, {
140+
props: {
141+
max: 1
142+
}
143+
})
144+
145+
const { getByText } = render(Component)
146+
147+
getByText('0') // Implicit assertion that "0" is within the component
148+
149+
const button = getByText('increment')
150+
151+
// Dispatch a click event to our increment button.
152+
await fireEvent.click(button)
153+
154+
getByText('1')
155+
156+
await fireEvent.click(button)
157+
```
158+
159+
</div>
160+
161+
<div class="vtu-api">
162+
163+
```js
164+
const valueSelector = '[data-testid=stepper-value]'
165+
const buttonSelector = '[data-testid=increment]'
166+
167+
const wrapper = mount(Stepper, {
168+
props: {
169+
max: 1
170+
}
171+
})
172+
173+
expect(wrapper.find(valueSelector).text()).toContain('0')
174+
175+
await wrapper.find(buttonSelector).trigger('click')
176+
177+
expect(wrapper.find(valueSelector).text()).toContain('1')
178+
```
179+
180+
</div>
181+
182+
<div class="cypress-api">
183+
130184
```js
131185
const valueSelector = '[data-testid=stepper-value]'
132186
const buttonSelector = '[data-testid=increment]'
@@ -137,19 +191,15 @@ mount(Stepper, {
137191
}
138192
})
139193

140-
// Each of these commands does implicit assertions
141-
// that the button and values are visible
142-
// and that the button can be clicked.
143-
cy.get(valueSelector)
144-
.should('be.visible')
145-
.and('contain.text', '0')
146-
.get(buttonSelector)
147-
.click()
148-
.get(valueSelector)
149-
.should('contain.text', '1')
150-
.click() // Should still be "1" because of the max prop
194+
cy.get(valueSelector).should('be.visible').and('contain.text', '0')
195+
.get(buttonSelector).click()
196+
.get(valueSelector).should('contain.text', '1')
151197
```
152198

199+
</div>
200+
201+
</TestingApiSwitcher>
202+
153203
- **DON'T**
154204

155205
Assert the private state of a component instance or test the private methods of a component. Testing implementation details makes the tests brittle, as they are more likely to break and require updates when the implementation changes.
@@ -162,21 +212,13 @@ cy.get(valueSelector)
162212

163213
### Recommendation
164214

165-
- [Vitest](https://vitest.dev/) for components or composables that render headlessly. (e.g. the [`useFavicon`](https://vueuse.org/core/useFavicon/#usefavicon) function in VueUse.
215+
- [Vitest](https://vitest.dev/) for components or composables that render headlessly. (e.g. the [`useFavicon`](https://vueuse.org/core/useFavicon/#usefavicon) function in VueUse. Components and DOM can be tested using [@testing-library/vue](https://testing-library.com/docs/vue-testing-library/intro).
166216

167-
- [Cypress Component Testing](https://on.cypress.io/component) for components whose expected behavior depends on properly rendering styles or triggering native DOM events.
217+
- [Cypress Component Testing](https://on.cypress.io/component) for components whose expected behavior depends on properly rendering styles or triggering native DOM events. Can be used with Testing Library via [@testing-library/cypress](https://testing-library.com/docs/cypress-testing-library/intro).
168218

169-
Cypress relies on Vue's `@vue/test-utils` library under the hood and is easily integrated with the Testing Library API via `@testing-library/cypress`. It works for both Vite-based and Webpack-based applications.
170-
171-
The main differences between Vitest and Cypress are speed and execution context. In short, Cypress can catch issues that Vitest cannot (style issues, real native DOM events, cookies, local storage, and network failures), but is *orders of magnitude slower than Vitest* because it opens a browser and compiles your stylesheets. **The Cypress team recommends Vitest if you want to trade speed for coverage.**
172-
173-
Please read [Vitest's comparison page](https://vitest.dev/guide/comparisons.html#cypress) for the latest information.
174-
175-
:::warning In Active Development
176-
Cypress Component Testing is still undergoing development. The Mount API is identical to [@vue/test-utils](https://github.com/vuejs/vue-test-utils) and has been stable for over a year now, but the Cypress team has not taken the project out of Alpha yet. Like Vitest, it can utilize your Vite config. It also has a Webpack adapter for use with Vue CLI or other build setups.
177-
:::
219+
The main differences between Vitest and browser-based runners are speed and execution context. In short, browser-based runners, like Cypress, can catch issues that node-based runners, like Vitest, cannot (e.g. style issues, real native DOM events, cookies, local storage, and network failures), but browser-based runners are *orders of magnitude slower than Vitest* because they doopen a browser, compile your stylesheets, and more. Cypress is a browser-based runner that supports component testing. Please read [Vitest's comparison page](https://vitest.dev/guide/comparisons.html#cypress) for the latest information comparing Vitest and Cypress.
178220

179-
### Libraries
221+
### Mounting Libraries
180222

181223
Component testing often involves mounting the component being tested in isolation, triggering simulated user input events, and asserting on the rendered DOM output. There are dedicated utility libraries that make these tasks simpler.
182224

0 commit comments

Comments
 (0)