@@ -119,7 +119,8 @@ Please check this [rules](https://github.com/vuejs/eslint-plugin-vue#bulb-rules)
...
@@ -119,7 +119,8 @@ Please check this [rules](https://github.com/vuejs/eslint-plugin-vue#bulb-rules)
## Naming
## 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)).
1.**Extensions**: Use `.vue` extension for Vue components. Do not use `.js` as file extension
1.**Reference Naming**: Use PascalCase for their instances:
1.**Reference Naming**: Use PascalCase for their instances:
```javascript
```javascript
...
@@ -402,7 +403,8 @@ When using `v-for` you need to provide a *unique* `:key` attribute for each item
...
@@ -402,7 +403,8 @@ When using `v-for` you need to provide a *unique* `:key` attribute for each item
</div>
</div>
```
```
1. When using `v-for` with `template` and there is more than one child element, the `:key` values must be unique. It's advised to use `kebab-case` namespaces.
1. When using `v-for` with `template` and there is more than one child element, the `:key` values
must be unique. It's advised to use `kebab-case` namespaces.
```html
```html
<templatev-for="(item, index) in items">
<templatev-for="(item, index) in items">
...
@@ -468,9 +470,10 @@ Useful links:
...
@@ -468,9 +470,10 @@ Useful links:
## Vue testing
## Vue testing
Over time, a number of programming patterns and style preferences have emerged in our efforts to effectively test Vue components.
Over time, a number of programming patterns and style preferences have emerged in our efforts to
The following guide describes some of these. **These are not strict guidelines**, but rather a collection of suggestions and
effectively test Vue components. The following guide describes some of these.
good practices that aim to provide insight into how we write Vue tests at GitLab.
**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
### Mounting a component
...
@@ -479,8 +482,10 @@ Typically, when testing a Vue component, the component should be "re-mounted" in
...
@@ -479,8 +482,10 @@ Typically, when testing a Vue component, the component should be "re-mounted" in
To achieve this:
To achieve this:
1. Create a mutable `wrapper` variable inside the top-level `describe` block.
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. Mount the component using [`mount`](https://vue-test-utils.vuejs.org/api/#mount)/
1. Reassign the resulting [`Wrapper`](https://vue-test-utils.vuejs.org/api/wrapper/#wrapper) instance to our `wrapper` variable.
over [`expect.objectContaining`](https://jestjs.io/docs/en/expect#expectobjectcontainingobject):
```javascript
```javascript
// good
// good
...
@@ -664,12 +688,24 @@ Prefer `toMatchObject` over [`expect.objectContaining`](https://jestjs.io/docs/e
...
@@ -664,12 +688,24 @@ Prefer `toMatchObject` over [`expect.objectContaining`](https://jestjs.io/docs/e
The goal of this accord is to make sure we are all on the same page.
The goal of this accord is to make sure we are all on the same page.
1. When writing Vue, you may not use jQuery in your application.
1. When writing Vue, you may not use jQuery in your application.
1. If you need to grab data from the DOM, you may query the DOM 1 time while bootstrapping your application to grab data attributes using `dataset`. You can do this without jQuery.
1. If you need to grab data from the DOM, you may query the DOM 1 time while bootstrapping your
application to grab data attributes using `dataset`. You can do this without jQuery.
1. You may use a jQuery dependency in Vue.js following [this example from the docs](https://vuejs.org/v2/examples/select2.html).
1. You may use a jQuery dependency in Vue.js following [this example from the docs](https://vuejs.org/v2/examples/select2.html).
1. If an outside jQuery Event needs to be listen to inside the Vue application, you may use jQuery event listeners.
1. If an outside jQuery Event needs to be listen to inside the Vue application, you may use
1. We avoid adding new jQuery events when they are not required. Instead of adding new jQuery events take a look at [different methods to do the same task](https://vuejs.org/v2/api/#vm-emit).
jQuery event listeners.
1. You may query the `window` object one time, while bootstrapping your application for application specific data (e.g. `scrollTo` is ok to access anytime). Do this access during the bootstrapping of your application.
1. We avoid adding new jQuery events when they are not required. Instead of adding new jQuery
1. You may have a temporary but immediate need to create technical debt by writing code that does not follow our standards, to be refactored later. Maintainers need to be ok with the tech debt in the first place. An issue should be created for that tech debt to evaluate it further and discuss. In the coming months you should fix that tech debt, with its priority to be determined by maintainers.
events take a look at [different methods to do the same task](https://vuejs.org/v2/api/#vm-emit).
1. When creating tech debt you must write the tests for that code before hand and those tests may not be rewritten. e.g. jQuery tests rewritten to Vue tests.
1. You may query the `window` object one time, while bootstrapping your application for application
1. You may choose to use VueX as a centralized state management. If you choose not to use VueX, you must use the *store pattern* which can be found in the [Vue.js documentation](https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch).
specific data (for example, `scrollTo` is ok to access anytime). Do this access during the
1. Once you have chosen a centralized state-management solution you must use it for your entire application. i.e. Don't mix and match your state-management solutions.
bootstrapping of your application.
1. You may have a temporary but immediate need to create technical debt by writing code that does
not follow our standards, to be refactored later. Maintainers need to be ok with the tech debt in
the first place. An issue should be created for that tech debt to evaluate it further and discuss.
In the coming months you should fix that tech debt, with its priority to be determined by maintainers.
1. When creating tech debt you must write the tests for that code before hand and those tests may
not be rewritten. For example, jQuery tests rewritten to Vue tests.
1. You may choose to use VueX as a centralized state management. If you choose not to use VueX, you
must use the *store pattern* which can be found in the
@@ -22,7 +22,8 @@ All new features built with Vue.js must follow a [Flux architecture](https://fac
...
@@ -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.
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).
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
and about [one way data flow](https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow).
and about [one way data flow](https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow).
### Components and Store
### Components and Store
...
@@ -62,14 +63,15 @@ Be sure to read about [page-specific JavaScript](performance.md#page-specific-ja
...
@@ -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.
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.
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
The advantage of providing data from the DOM to the Vue instance through `props` in the `render`
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,
function instead of querying the DOM inside the main Vue component is avoiding the need to create a
which makes the tests easier.
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
See the following example, also, please refer to our [Vue style guide](style/vue.md#basic-rules) for
information on why we explicitly declare the data being passed into the Vue app;
additional information on why we explicitly declare the data being passed into the Vue app;
```javascript
```javascript
// haml
// haml
...
@@ -94,13 +96,15 @@ return new Vue({
...
@@ -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
#### 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.
When we need to query the `gl` object for data that doesn't change during the application's life
By following this practice, we can avoid the need to mock the `gl` object, which makes tests easier.
cycle, we should do it in the same place where we query the DOM. By following this practice, we can
It should be done while initializing our Vue instance, and the data should be provided as `props` to the main component:
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
```javascript
returnnewVue({
returnnewVue({
...
@@ -192,13 +196,18 @@ Check this [page](vuex.md) for more details.
...
@@ -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:
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:
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 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** maintain existing implementations using such approaches.
-**Do** Migrate components to a pure object model when there are substantial changes to it.
-**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.
-**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
...
@@ -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.
- 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.
- 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.
- Adding OOP to a functional codebase adds yet another way of writing code, reducing consistency and clarity.
## Style guide
## Style guide
...
@@ -231,6 +241,7 @@ Here's an example of a well structured unit test for [this Vue component](#appen
...
@@ -231,6 +241,7 @@ Here's an example of a well structured unit test for [this Vue component](#appen
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)
@@ -342,7 +385,8 @@ it('should fire the click event', () => {
...
@@ -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
```javascript
wrapper=shallowMount(DropdownItem);
wrapper=shallowMount(DropdownItem);
...
@@ -355,7 +399,8 @@ it('should fire the itemClicked event', () => {
...
@@ -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