summaryrefslogtreecommitdiff
path: root/doc/development/fe_guide/style/vue.md
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-11-19 08:27:35 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-11-19 08:27:35 +0000
commit7e9c479f7de77702622631cff2628a9c8dcbc627 (patch)
treec8f718a08e110ad7e1894510980d2155a6549197 /doc/development/fe_guide/style/vue.md
parente852b0ae16db4052c1c567d9efa4facc81146e88 (diff)
downloadgitlab-ce-7e9c479f7de77702622631cff2628a9c8dcbc627.tar.gz
Add latest changes from gitlab-org/gitlab@13-6-stable-eev13.6.0-rc42
Diffstat (limited to 'doc/development/fe_guide/style/vue.md')
-rw-r--r--doc/development/fe_guide/style/vue.md261
1 files changed, 260 insertions, 1 deletions
diff --git a/doc/development/fe_guide/style/vue.md b/doc/development/fe_guide/style/vue.md
index 1a64db443bc..6b6ca9a6c71 100644
--- a/doc/development/fe_guide/style/vue.md
+++ b/doc/development/fe_guide/style/vue.md
@@ -1,3 +1,9 @@
+---
+stage: none
+group: unassigned
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
+---
+
# Vue.js style guide
## Linting
@@ -51,6 +57,66 @@ Please check this [rules](https://github.com/vuejs/eslint-plugin-vue#bulb-rules)
1. Use `.vue` for Vue templates. Do not use `%template` in HAML.
+1. Explicitly define data being passed into the Vue app
+
+ ```javascript
+ // bad
+ return new Vue({
+ el: '#element',
+ components: {
+ componentName
+ },
+ provide: {
+ ...someDataset
+ },
+ props: {
+ ...anotherDataset
+ },
+ render: createElement => createElement('component-name'),
+ }));
+
+ // good
+ const { foobar, barfoo } = someDataset;
+ const { foo, bar } = anotherDataset;
+
+ return new Vue({
+ el: '#element',
+ components: {
+ componentName
+ },
+ provide: {
+ foobar,
+ barfoo
+ },
+ props: {
+ foo,
+ bar
+ },
+ render: createElement => createElement('component-name'),
+ }));
+ ```
+
+ We discourage the use of the spread operator in this specific case in
+ order to keep our codebase explicit, discoverable, and searchable.
+ This applies in any place where we'll benefit from the above, such as
+ when [initializing Vuex state](../vuex.md#why-not-just-spread-the-initial-state).
+ The pattern above also enables us to easily parse non scalar values during
+ instantiation.
+
+ ```javascript
+ return new Vue({
+ el: '#element',
+ components: {
+ componentName
+ },
+ props: {
+ foo,
+ bar: parseBoolean(bar)
+ },
+ render: createElement => createElement('component-name'),
+ }));
+ ```
+
## Naming
1. **Extensions**: Use `.vue` extension for Vue components. Do not use `.js` as file extension ([#34371](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/34371)).
@@ -184,7 +250,7 @@ Please check this [rules](https://github.com/vuejs/eslint-plugin-vue#bulb-rules)
```
1. Default key should be provided if the prop is not required.
- _Note:_ There are some scenarios where we need to check for the existence of the property.
+ There are some scenarios where we need to check for the existence of the property.
On those a default key should not be provided.
```javascript
@@ -400,6 +466,199 @@ Useful links:
$('span').tooltip('_fixTitle');
```
+## Vue testing
+
+Over time, a number of programming patterns and style preferences have emerged in our efforts to effectively test Vue components.
+The following guide describes some of these. **These are not strict guidelines**, but rather a collection of suggestions and
+good practices that aim to provide insight into how we write Vue tests at GitLab.
+
+### Mounting a component
+
+Typically, when testing a Vue component, the component should be "re-mounted" in every test block.
+
+To achieve this:
+
+1. Create a mutable `wrapper` variable inside the top-level `describe` block.
+1. Mount the component using [`mount`](https://vue-test-utils.vuejs.org/api/#mount)/[`shallowMount`](https://vue-test-utils.vuejs.org/api/#shallowMount).
+1. Reassign the resulting [`Wrapper`](https://vue-test-utils.vuejs.org/api/wrapper/#wrapper) instance to our `wrapper` variable.
+
+Creating a global, mutable wrapper provides a number of advantages, including the ability to:
+
+- Define common functions for finding components/DOM elements:
+
+ ```javascript
+ import MyComponent from '~/path/to/my_component.vue';
+ describe('MyComponent', () => {
+ let wrapper;
+
+ // this can now be reused across tests
+ const findMyComponent = wrapper.find(MyComponent);
+ // ...
+ })
+ ```
+
+- Use a `beforeEach` block to mount the component (see [the `createComponent` factory](#the-createcomponent-factory) for more information).
+- Use an `afterEach` block to destroy the component, for example, `wrapper.destroy()`.
+
+#### The `createComponent` factory
+
+To avoid duplicating our mounting logic, it's useful to define a `createComponent` factory function
+that we can reuse in each test block. This is a closure which should reassign our `wrapper` variable
+to the result of [`mount`](https://vue-test-utils.vuejs.org/api/#mount) and [`shallowMount`](https://vue-test-utils.vuejs.org/api/#shallowMount):
+
+```javascript
+import MyComponent from '~/path/to/my_component.vue';
+import { shallowMount } from '@vue/test-utils';
+
+describe('MyComponent', () => {
+ // Initiate the "global" wrapper variable. This will be used throughout our test:
+ let wrapper;
+
+ // Define our `createComponent` factory:
+ function createComponent() {
+ // Mount component and reassign `wrapper`:
+ wrapper = shallowMount(MyComponent);
+ }
+
+ it('mounts', () => {
+ createComponent();
+
+ expect(wrapper.exists()).toBe(true);
+ });
+
+ it('`isLoading` prop defaults to `false`', () => {
+ createComponent();
+
+ expect(wrapper.props('isLoading')).toBe(false);
+ });
+})
+```
+
+Similarly, we could further de-duplicate our test by calling `createComponent` in a `beforeEach` block:
+
+```javascript
+import MyComponent from '~/path/to/my_component.vue';
+import { shallowMount } from '@vue/test-utils';
+
+describe('MyComponent', () => {
+ // Initiate the "global" wrapper variable. This will be used throughout our test
+ let wrapper;
+
+ // define our `createComponent` factory
+ function createComponent() {
+ // mount component and reassign `wrapper`
+ wrapper = shallowMount(MyComponent);
+ }
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('mounts', () => {
+ expect(wrapper.exists()).toBe(true);
+ });
+
+ it('`isLoading` prop defaults to `false`', () => {
+ expect(wrapper.props('isLoading')).toBe(false);
+ });
+})
+```
+
+#### `createComponent` best practices
+
+1. Consider using a single (or a limited number of) object arguments over many arguments.
+ Defining single parameters for common data like `props` is okay,
+ but keep in mind our [JavaScript style guide](javascript.md#limit-number-of-parameters) and stay within the parameter number limit:
+
+ ```javascript
+ // bad
+ function createComponent(data, props, methods, isLoading, mountFn) { }
+
+ // good
+ function createComponent({ data, props, methods, stubs, isLoading } = {}) { }
+
+ // good
+ function createComponent(props = {}, { data, methods, stubs, isLoading } = {}) { }
+ ```
+
+1. If you require both `mount` _and_ `shallowMount` within the same set of tests, it
+can be useful define a `mountFn` parameter for the `createComponent` factory that accepts
+the mounting function (`mount` or `shallowMount`) to be used to mount the component:
+
+ ```javascript
+ import { shallowMount } from '@vue/test-utils';
+
+ function createComponent({ mountFn = shallowMount } = {}) { }
+ ```
+
+### Setting component state
+
+1. Avoid using [`setProps`](https://vue-test-utils.vuejs.org/api/wrapper/#setprops) to set
+component state wherever possible. Instead, set the component's
+[`propsData`](https://vue-test-utils.vuejs.org/api/options.html#propsdata) when mounting the component:
+
+ ```javascript
+ // bad
+ wrapper = shallowMount(MyComponent);
+ wrapper.setProps({
+ myProp: 'my cool prop'
+ });
+
+ // good
+ wrapper = shallowMount({ propsData: { myProp: 'my cool prop' } });
+ ```
+
+ The exception here is when you wish to test component reactivity in some way.
+ For example, you may want to test the output of a component when after a particular watcher has executed.
+ Using `setProps` to test such behavior is okay.
+
+### Accessing component state
+
+1. When accessing props or attributes, prefer the `wrapper.props('myProp')` syntax over `wrapper.props().myProp`:
+
+ ```javascript
+ // good
+ expect(wrapper.props().myProp).toBe(true);
+ expect(wrapper.attributes().myAttr).toBe(true);
+
+ // better
+ expect(wrapper.props('myProp').toBe(true);
+ expect(wrapper.attributes('myAttr')).toBe(true);
+ ```
+
+1. When asserting multiple props, check the deep equality of the `props()` object with [`toEqual`](https://jestjs.io/docs/en/expect#toequalvalue):
+
+ ```javascript
+ // good
+ expect(wrapper.props('propA')).toBe('valueA');
+ expect(wrapper.props('propB')).toBe('valueB');
+ expect(wrapper.props('propC')).toBe('valueC');
+
+ // better
+ expect(wrapper.props()).toEqual({
+ propA: 'valueA',
+ propB: 'valueB',
+ propC: 'valueC',
+ });
+ ```
+
+1. If you are only interested in some of the props, you can use [`toMatchObject`](https://jestjs.io/docs/en/expect#tomatchobjectobject).
+Prefer `toMatchObject` over [`expect.objectContaining`](https://jestjs.io/docs/en/expect#expectobjectcontainingobject):
+
+ ```javascript
+ // good
+ expect(wrapper.props()).toEqual(expect.objectContaining({
+ propA: 'valueA',
+ propB: 'valueB',
+ }));
+
+ // better
+ expect(wrapper.props()).toMatchObject({
+ propA: 'valueA',
+ propB: 'valueB',
+ });
+ ```
+
## The JavaScript/Vue Accord
The goal of this accord is to make sure we are all on the same page.