Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'doc/development/fe_guide/vue.md')
-rw-r--r--doc/development/fe_guide/vue.md107
1 files changed, 77 insertions, 30 deletions
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 41fbd128631..b3fbb9556a9 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -22,7 +22,8 @@ All new features built with Vue.js must follow a [Flux architecture](https://fac
The main goal we are trying to achieve is to have only one data flow and only one data entry.
In order to achieve this goal we use [vuex](#vuex).
-You can also read about this architecture in Vue docs about [state management](https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch)
+You can also read about this architecture in Vue docs about
+[state management](https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch)
and about [one way data flow](https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow).
### Components and Store
@@ -62,14 +63,15 @@ Be sure to read about [page-specific JavaScript](performance.md#page-specific-ja
While mounting a Vue application, you might need to provide data from Rails to JavaScript.
To do that, you can use the `data` attributes in the HTML element and query them while mounting the application.
-You should only do this while initializing the application, because the mounted element is replaced with a Vue-generated DOM.
+You should only do this while initializing the application, because the mounted element is replaced
+with a Vue-generated DOM.
-The advantage of providing data from the DOM to the Vue instance through `props` in the `render` function
-instead of querying the DOM inside the main Vue component is avoiding the need to create a fixture or an HTML element in the unit test,
-which makes the tests easier.
+The advantage of providing data from the DOM to the Vue instance through `props` in the `render`
+function instead of querying the DOM inside the main Vue component is avoiding the need to create a
+fixture or an HTML element in the unit test, which makes the tests easier.
-See the following example, also, please refer to our [Vue style guide](style/vue.md#basic-rules) for additional
-information on why we explicitly declare the data being passed into the Vue app;
+See the following example, also, please refer to our [Vue style guide](style/vue.md#basic-rules) for
+additional information on why we explicitly declare the data being passed into the Vue app;
```javascript
// haml
@@ -94,13 +96,15 @@ return new Vue({
});
```
-> When adding an `id` attribute to mount a Vue application, please make sure this `id` is unique across the codebase
+> When adding an `id` attribute to mount a Vue application, please make sure this `id` is unique
+across the codebase.
#### Accessing the `gl` object
-When we need to query the `gl` object for data that doesn't change during the application's life cycle, we should do it in the same place where we query the DOM.
-By following this practice, we can avoid the need to mock the `gl` object, which makes tests easier.
-It should be done while initializing our Vue instance, and the data should be provided as `props` to the main component:
+When we need to query the `gl` object for data that doesn't change during the application's life
+cycle, we should do it in the same place where we query the DOM. By following this practice, we can
+avoid the need to mock the `gl` object, which makes tests easier. It should be done while
+initializing our Vue instance, and the data should be provided as `props` to the main component:
```javascript
return new Vue({
@@ -192,13 +196,18 @@ Check this [page](vuex.md) for more details.
In the [Vue documentation](https://vuejs.org/v2/api/#Options-Data) the Data function/object is defined as follows:
-> The data object for the Vue instance. Vue recursively converts its properties into getter/setters to make it “reactive”. The object must be plain: native objects such as browser API objects and prototype properties are ignored. A rule of thumb is that data should just be data - it is not recommended to observe objects with their own stateful behavior.
+> The data object for the Vue instance. Vue recursively converts its properties into getter/setters
+to make it “reactive”. The object must be plain: native objects such as browser API objects and
+prototype properties are ignored. A rule of thumb is that data should just be data - it is not
+recommended to observe objects with their own stateful behavior.
Based on the Vue guidance:
-- **Do not** use or create a JavaScript class in your [data function](https://vuejs.org/v2/api/#data), such as `user: new User()`.
+- **Do not** use or create a JavaScript class in your [data function](https://vuejs.org/v2/api/#data),
+such as `user: new User()`.
- **Do not** add new JavaScript class implementations.
-- **Do** use [GraphQL](../api_graphql_styleguide.md), [Vuex](vuex.md) or a set of components if cannot use simple primitives or objects.
+- **Do** use [GraphQL](../api_graphql_styleguide.md), [Vuex](vuex.md) or a set of components if
+cannot use simple primitives or objects.
- **Do** maintain existing implementations using such approaches.
- **Do** Migrate components to a pure object model when there are substantial changes to it.
- **Do** add business logic to helpers or utils, so you can test them separately from your component.
@@ -209,7 +218,8 @@ There are additional reasons why having a JavaScript class presents maintainabil
- Once a class is created, it is easy to extend it in a way that can infringe Vue reactivity and best practices.
- A class adds a layer of abstraction, which makes the component API and its inner workings less clear.
-- It makes it harder to test. Since the class is instantiated by the component data function, it is harder to 'manage' component and class separately.
+- It makes it harder to test. Since the class is instantiated by the component data function, it is
+harder to 'manage' component and class separately.
- Adding OOP to a functional codebase adds yet another way of writing code, reducing consistency and clarity.
## Style guide
@@ -231,6 +241,7 @@ Here's an example of a well structured unit test for [this Vue component](#appen
```javascript
import { shallowMount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { GlLoadingIcon } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
@@ -263,19 +274,21 @@ describe('~/todos/app.vue', () => {
});
// It is very helpful to separate setting up the component from
- // its collaborators (i.e. Vuex, axios, etc.)
+ // its collaborators (for example, Vuex and axios).
const createWrapper = (props = {}) => {
- wrapper = shallowMount(App, {
- propsData: {
- path: TEST_TODO_PATH,
- ...props,
- },
- });
+ wrapper = extendedWrapper(
+ shallowMount(App, {
+ propsData: {
+ path: TEST_TODO_PATH,
+ ...props,
+ },
+ })
+ );
};
// Helper methods greatly help test maintainability and readability.
const findLoader = () => wrapper.find(GlLoadingIcon);
- const findAddButton = () => wrapper.find('[data-testid="add-button"]');
- const findTextInput = () => wrapper.find('[data-testid="text-input"]');
+ const findAddButton = () => wrapper.findByTestId('add-button');
+ const findTextInput = () => wrapper.findByTestId('text-input');
const findTodoData = () => wrapper.findAll('[data-testid="todo-item"]').wrappers.map(wrapper => ({ text: wrapper.text() }));
describe('when mounted and loading', () => {
@@ -323,11 +336,41 @@ describe('~/todos/app.vue', () => {
The main return value of a Vue component is the rendered output. In order to test the component we
need to test the rendered output. Visit the [Vue testing guide](https://vuejs.org/v2/guide/testing.html#Unit-Testing).
+### Child components
+
+1. Test any directive that defines if/how child component is rendered (for example, `v-if` and `v-for`).
+1. Test any props we are passing to child components (especially if the prop is calculated in the
+component under test, with the `computed` property, for example). Remember to use `.props()` and not `.vm.someProp`.
+1. Test we react correctly to any events emitted from child components:
+
+ ```javascript
+ const checkbox = wrapper.findByTestId('checkboxTestId');
+
+ expect(checkbox.attributes('disabled')).not.toBeDefined();
+
+ findChildComponent().vm.$emit('primary');
+ await nextTick();
+
+ expect(checkbox.attributes('disabled')).toBeDefined();
+ ```
+
+1. **Do not** test the internal implementation of the child components:
+
+ ```javascript
+ // bad
+ expect(findChildComponent().find('.error-alert').exists()).toBe(false);
+
+ // good
+ expect(findChildComponent().props('withAlertContainer')).toBe(false);
+ ```
+
### Events
-We should test for events emitted in response to an action within our component, this is useful to verify the correct events are being fired with the correct arguments.
+We should test for events emitted in response to an action within our component, this is useful to
+verify the correct events are being fired with the correct arguments.
-For any DOM events we should use [`trigger`](https://vue-test-utils.vuejs.org/api/wrapper/#trigger) to fire out event.
+For any DOM events we should use [`trigger`](https://vue-test-utils.vuejs.org/api/wrapper/#trigger)
+to fire out event.
```javascript
// Assuming SomeButton renders: <button>Some button</button>
@@ -342,7 +385,8 @@ it('should fire the click event', () => {
})
```
-When we need to fire a Vue event, we should use [`emit`](https://vuejs.org/v2/guide/components-custom-events.html) to fire our event.
+When we need to fire a Vue event, we should use [`emit`](https://vuejs.org/v2/guide/components-custom-events.html)
+to fire our event.
```javascript
wrapper = shallowMount(DropdownItem);
@@ -355,7 +399,8 @@ it('should fire the itemClicked event', () => {
})
```
-We should verify an event has been fired by asserting against the result of the [`emitted()`](https://vue-test-utils.vuejs.org/api/wrapper/#emitted) method
+We should verify an event has been fired by asserting against the result of the
+[`emitted()`](https://vue-test-utils.vuejs.org/api/wrapper/#emitted) method.
## Vue.js Expert Role
@@ -371,7 +416,8 @@ You should only apply to be a Vue.js expert when your own merge requests and you
> This section is added temporarily to support the efforts to migrate the codebase from Vue 2.x to Vue 3.x
-Currently, we recommend to minimize adding certain features to the codebase to prevent increasing the tech debt for the eventual migration:
+Currently, we recommend to minimize adding certain features to the codebase to prevent increasing
+the tech debt for the eventual migration:
- filters;
- event buses;
@@ -382,7 +428,8 @@ You can find more details on [Migration to Vue 3](vue3_migration.md)
## Appendix - Vue component subject under test
-This is the template for the example component which is tested in the [Testing Vue components](#testing-vue-components) section:
+This is the template for the example component which is tested in the
+[Testing Vue components](#testing-vue-components) section:
```html
<template>