2020-10-30 14:08:56 -04:00
---
stage: none
group: unassigned
2020-11-26 01:09:20 -05:00
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/#assignments
2020-10-30 14:08:56 -04:00
---
2017-03-22 15:30:54 -04:00
# Vue
2019-09-27 08:06:07 -04:00
To get started with Vue, read through [their documentation ](https://vuejs.org/v2/guide/ ).
2017-03-22 15:30:54 -04:00
2018-07-12 08:10:53 -04:00
## Examples
2017-03-22 15:30:54 -04:00
2018-07-12 08:10:53 -04:00
What is described in the following sections can be found in these examples:
2017-09-05 03:39:52 -04:00
2020-06-10 14:09:15 -04:00
- [Web IDE ](https://gitlab.com/gitlab-org/gitlab-foss/tree/master/app/assets/javascripts/ide/stores )
2021-06-08 14:10:23 -04:00
- [Security products ](https://gitlab.com/gitlab-org/gitlab/-/tree/master/ee/app/assets/javascripts/vue_shared/security_reports )
2020-06-10 14:09:15 -04:00
- [Registry ](https://gitlab.com/gitlab-org/gitlab-foss/tree/master/app/assets/javascripts/registry/stores )
2017-04-20 07:24:40 -04:00
2018-07-12 08:10:53 -04:00
## Vue architecture
2017-04-20 07:24:40 -04:00
2020-04-21 11:21:10 -04:00
All new features built with Vue.js must follow a [Flux architecture ](https://facebook.github.io/flux/ ).
2018-07-12 08:10:53 -04:00
The main goal we are trying to achieve is to have only one data flow and only one data entry.
2021-02-10 22:09:06 -05:00
In order to achieve this goal we use [Vuex ](#vuex ).
2017-04-20 07:24:40 -04:00
2021-01-25 16:09:03 -05:00
You can also read about this architecture in Vue documentation about
2020-12-22 10:09:51 -05:00
[state management ](https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch )
2020-04-21 11:21:10 -04:00
and about [one way data flow ](https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow ).
2017-04-20 07:24:40 -04:00
2018-07-12 08:10:53 -04:00
### Components and Store
2017-03-22 15:30:54 -04:00
2020-04-21 11:21:10 -04:00
In some features implemented with Vue.js, like the [issue board ](https://gitlab.com/gitlab-org/gitlab-foss/tree/master/app/assets/javascripts/boards )
or [environments table ](https://gitlab.com/gitlab-org/gitlab-foss/tree/master/app/assets/javascripts/environments )
2017-03-22 15:30:54 -04:00
you can find a clear separation of concerns:
2020-03-25 02:07:58 -04:00
```plaintext
2017-03-22 15:30:54 -04:00
new_feature
├── components
2018-03-12 06:08:58 -04:00
│ └── component.vue
2017-03-22 15:30:54 -04:00
│ └── ...
2018-07-12 08:10:53 -04:00
├── store
2018-03-12 06:08:58 -04:00
│ └── new_feature_store.js
2018-03-22 06:25:03 -04:00
├── index.js
2017-03-22 15:30:54 -04:00
```
2019-07-12 04:09:23 -04:00
2017-03-22 15:30:54 -04:00
_For consistency purposes, we recommend you to follow the same structure._
Let's look into each of them:
2019-04-09 05:35:09 -04:00
### An `index.js` file
2017-03-22 15:30:54 -04:00
This is the index file of your new feature. This is where the root Vue instance
of the new feature should be.
2017-04-20 07:24:40 -04:00
The Store and the Service should be imported and initialized in this file and
provided as a prop to the main component.
2017-03-22 15:30:54 -04:00
2020-11-08 22:09:03 -05:00
Be sure to read about [page-specific JavaScript ](performance.md#page-specific-javascript ).
2017-03-22 15:30:54 -04:00
2017-09-05 03:39:52 -04:00
### Bootstrapping Gotchas
2019-07-02 04:50:00 -04:00
2018-05-27 15:21:12 -04:00
#### Providing data from HAML to JavaScript
2019-07-02 04:50:00 -04:00
2020-07-13 20:09:46 -04:00
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.
2017-09-05 03:39:52 -04:00
2021-01-11 19:10:42 -05:00
You should only do this while initializing the application, because the mounted element is replaced
2020-12-22 10:09:51 -05:00
with a Vue-generated DOM.
2017-09-05 03:39:52 -04:00
2021-08-09 20:10:29 -04:00
The advantage of providing data from the DOM to the Vue instance through `props` or
`provide` in the `render` function, instead of querying the DOM inside the main Vue
component, is that you avoid the need to create a fixture or an HTML element in the unit test.
2020-11-11 13:09:10 -05:00
2021-08-09 20:10:29 -04:00
##### provide/inject
Vue supports dependency injection through [provide/inject ](https://vuejs.org/v2/api/#provide-inject ).
2021-12-03 10:10:36 -05:00
In the component the `inject` configuration accesses the values `provide` passes down.
2021-10-20 14:12:31 -04:00
This example of a Vue app initialization shows how the `provide` configuration passes a value from HAML to the component:
2021-08-09 20:10:29 -04:00
```javascript
#js-vue-app{ data: { endpoint: 'foo' }}
// index.js
const el = document.getElementById('js-vue-app');
if (!el) return false;
const { endpoint } = el.dataset;
return new Vue({
el,
render(createElement) {
return createElement('my-component', {
provide: {
endpoint
},
});
},
});
```
The component, or any of its child components, can access the property through `inject` as:
```vue
< script >
export default {
name: 'MyComponent',
inject: ['endpoint'],
...
...
};
< / script >
< template >
...
...
< / template >
```
Using dependency injection to provide values from HAML is ideal when:
- The injected value doesn't need an explicit validation against its data type or contents.
- The value doesn't need to be reactive.
- There are multiple components in the hierarchy that need access to this value where
prop-drilling becomes an inconvenience. Prop-drilling when the same prop is passed
through all components in the hierarchy until the component that is genuinely using it.
2022-04-14 08:09:31 -04:00
Dependency injection can potentially break a child component (either an immediate child or multiple levels deep) if the value declared in the `inject` configuration doesn't have defaults defined and the parent component has not provided the value using the `provide` configuration.
- A [default value ](https://vuejs.org/guide/components/provide-inject.html#injection-default-values ) might be useful in contexts where it makes sense.
2021-08-09 20:10:29 -04:00
##### props
If the value from HAML doesn't fit the criteria of dependency injection, use `props` .
See the following example.
2017-09-05 03:39:52 -04:00
```javascript
// haml
2020-07-30 14:09:39 -04:00
#js-vue-app{ data: { endpoint: 'foo' }}
2017-09-05 03:39:52 -04:00
2018-05-27 15:21:12 -04:00
// index.js
2020-11-11 13:09:10 -05:00
const el = document.getElementById('js-vue-app');
if (!el) return false;
const { endpoint } = el.dataset;
return new Vue({
el,
2017-09-05 03:39:52 -04:00
render(createElement) {
return createElement('my-component', {
props: {
2020-11-11 13:09:10 -05:00
endpoint
2017-09-05 03:39:52 -04:00
},
});
},
2020-12-01 16:09:29 -05:00
});
2017-09-05 03:39:52 -04:00
```
2021-01-11 19:10:42 -05:00
> When adding an `id` attribute to mount a Vue application, please make sure this `id` is unique
2020-12-22 10:09:51 -05:00
across the codebase.
2020-07-30 14:09:39 -04:00
2021-08-09 20:10:29 -04:00
For more information on why we explicitly declare the data being passed into the Vue app,
refer to our [Vue style guide ](style/vue.md#basic-rules ).
2021-04-01 14:13:56 -04:00
#### Providing Rails form fields to Vue applications
When composing a form with Rails, the `name` , `id` , and `value` attributes of form inputs are generated
2021-04-22 23:09:40 -04:00
to match the backend. It can be helpful to have access to these generated attributes when converting
2021-04-01 14:13:56 -04:00
a Rails form to Vue, or when [integrating components (datepicker, project selector, etc) ](https://gitlab.com/gitlab-org/gitlab/-/blob/8956ad767d522f37a96e03840595c767de030968/app/assets/javascripts/access_tokens/index.js#L15 ) into it.
2021-04-22 23:09:40 -04:00
The [`parseRailsFormFields` ](https://gitlab.com/gitlab-org/gitlab/-/blob/fe88797f682c7ff0b13f2c2223a3ff45ada751c1/app/assets/javascripts/lib/utils/forms.js#L107 ) utility can be used to parse the generated form input attributes so they can be passed to the Vue application.
2021-04-01 14:13:56 -04:00
This allows us to easily integrate Vue components without changing how the form submits.
```haml
-# form.html.haml
= form_for user do |form|
.js-user-form
= form.text_field :name, class: 'form-control gl-form-input', data: { js_name: 'name' }
= form.text_field :email, class: 'form-control gl-form-input', data: { js_name: 'email' }
```
> The `js_name` data attribute is used as the key in the resulting JavaScript object.
2021-04-22 23:09:40 -04:00
For example `= form.text_field :email, data: { js_name: 'fooBarBaz' }` would be translated
2021-04-01 14:13:56 -04:00
to `{ fooBarBaz: { name: 'user[email]', id: 'user_email', value: '' } }`
```javascript
// index.js
import Vue from 'vue';
import { parseRailsFormFields } from '~/lib/utils/forms';
import UserForm from './components/user_form.vue';
export const initUserForm = () => {
const el = document.querySelector('.js-user-form');
if (!el) {
return null;
}
const fields = parseRailsFormFields(el);
return new Vue({
el,
render(h) {
return h(UserForm, {
props: {
fields,
},
});
},
});
};
```
```vue
< script >
// user_form.vue
import { GlButton, GlFormGroup, GlFormInput } from '@gitlab/ui';
export default {
name: 'UserForm',
components: { GlButton, GlFormGroup, GlFormInput },
props: {
fields: {
type: Object,
required: true,
},
},
};
< / script >
< template >
< div >
< gl-form-group :label-for = "fields.name.id" :label = "__('Name')" >
< gl-form-input v-bind = "fields.name" size = "lg" / >
< / gl-form-group >
< gl-form-group :label-for = "fields.email.id" :label = "__('Email')" >
< gl-form-input v-bind = "fields.email" type = "email" size = "lg" / >
< / gl-form-group >
< gl-button type = "submit" category = "primary" variant = "confirm" > {{ __ ('Update') }}</ gl-button >
< / div >
< / template >
```
2017-09-05 03:39:52 -04:00
#### Accessing the `gl` object
2019-07-02 04:50:00 -04:00
2021-01-25 16:09:03 -05:00
We query the `gl` object for data that doesn't change during the application's life
cycle in the same place we query the DOM. By following this practice, we can
2021-01-11 19:10:42 -05:00
avoid the need to mock the `gl` object, which makes tests easier. It should be done while
2020-12-22 10:09:51 -05:00
initializing our Vue instance, and the data should be provided as `props` to the main component:
2017-09-05 03:39:52 -04:00
```javascript
2020-11-11 13:09:10 -05:00
return new Vue({
2017-09-05 03:39:52 -04:00
el: '.js-vue-app',
render(createElement) {
return createElement('my-component', {
props: {
2021-10-21 08:10:30 -04:00
avatarUrl: gl.avatarUrl,
2017-09-05 03:39:52 -04:00
},
});
},
2020-11-11 13:09:10 -05:00
});
2017-09-05 03:39:52 -04:00
```
2019-09-24 14:06:05 -04:00
#### Accessing feature flags
Use Vue's [provide/inject ](https://vuejs.org/v2/api/#provide-inject ) mechanism
to make feature flags available to any descendant components in a Vue
application. The `glFeatures` object is already provided in `commons/vue.js` , so
2020-11-17 19:09:02 -05:00
only the mixin is required to use the flags:
2019-09-24 14:06:05 -04:00
```javascript
// An arbitrary descendant component
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
// ...
mixins: [glFeatureFlagsMixin()],
// ...
created() {
if (this.glFeatures.myFlag) {
// ...
}
},
}
```
This approach has a few benefits:
- Arbitrarily deeply nested components can opt-in and access the flag without
intermediate components being aware of it (c.f. passing the flag down via
props).
2021-01-25 16:09:03 -05:00
- Good testability, because the flag can be provided to `mount` /`shallowMount`
from `vue-test-utils` as a prop.
2019-09-24 14:06:05 -04:00
```javascript
import { shallowMount } from '@vue/test-utils';
shallowMount(component, {
provide: {
glFeatures: { myFlag: true },
},
});
```
- No need to access a global variable, except in the application's
[entry point ](#accessing-the-gl-object ).
2017-04-20 07:24:40 -04:00
### A folder for Components
2017-03-22 15:30:54 -04:00
2020-12-14 13:09:48 -05:00
This folder holds all components that are specific to this new feature.
If you need to use or create a component that is likely to be used somewhere
2017-03-22 15:30:54 -04:00
else, please refer to `vue_shared/components` .
2021-12-15 07:10:49 -05:00
A good guideline to know when you should create a component is to think if
2020-12-14 13:09:48 -05:00
it could be reusable elsewhere.
2017-03-22 15:30:54 -04:00
For example, tables are used in a quite amount of places across GitLab, a table
would be a good fit for a component. On the other hand, a table cell used only
in one table would not be a good use of this pattern.
2020-04-21 11:21:10 -04:00
You can read more about components in Vue.js site, [Component System ](https://vuejs.org/v2/guide/#Composing-with-Components ).
2017-03-22 15:30:54 -04:00
2017-04-20 07:24:40 -04:00
### A folder for the Store
2017-03-22 15:30:54 -04:00
2018-03-22 06:25:03 -04:00
#### Vuex
2019-07-02 04:50:00 -04:00
2018-03-22 06:25:03 -04:00
Check this [page ](vuex.md ) for more details.
2019-03-14 04:19:13 -04:00
### Mixing Vue and jQuery
- Mixing Vue and jQuery is not recommended.
2019-07-01 00:50:10 -04:00
- If you need to use a specific jQuery plugin in Vue, [create a wrapper around it ](https://vuejs.org/v2/examples/select2.html ).
2019-03-14 04:19:13 -04:00
- It is acceptable for Vue to listen to existing jQuery events using jQuery event listeners.
- It is not recommended to add new jQuery events for Vue to interact with jQuery.
2020-11-16 10:09:23 -05:00
### Mixing Vue and JavaScript classes (in the data function)
In the [Vue documentation ](https://vuejs.org/v2/api/#Options-Data ) the Data function/object is defined as follows:
2021-01-11 19:10:42 -05:00
> The data object for the Vue instance. Vue recursively converts its properties into getter/setters
2021-03-18 02:11:52 -04:00
to make it "reactive". The object must be plain: native objects such as browser API objects and
2021-12-15 07:10:49 -05:00
prototype properties are ignored. A guideline is that data should just be data - it is not
2020-12-22 10:09:51 -05:00
recommended to observe objects with their own stateful behavior.
2020-11-16 10:09:23 -05:00
Based on the Vue guidance:
2021-01-11 19:10:42 -05:00
- **Do not** use or create a JavaScript class in your [data function ](https://vuejs.org/v2/api/#data ),
2020-12-22 10:09:51 -05:00
such as `user: new User()` .
2020-11-16 10:09:23 -05:00
- **Do not** add new JavaScript class implementations.
2021-01-11 19:10:42 -05:00
- **Do** use [GraphQL ](../api_graphql_styleguide.md ), [Vuex ](vuex.md ) or a set of components if
2021-01-25 16:09:03 -05:00
cannot use primitives or objects.
2020-11-16 10:09:23 -05:00
- **Do** maintain existing implementations using such approaches.
- **Do** Migrate components to a pure object model when there are substantial changes to it.
2021-01-25 16:09:03 -05:00
- **Do** add business logic to helpers or utilities, so you can test them separately from your component.
2020-11-16 10:09:23 -05:00
#### Why
There are additional reasons why having a JavaScript class presents maintainability issues on a huge codebase:
2021-01-25 16:09:03 -05:00
- After a class is created, it can be extended in a way that can infringe Vue reactivity and best practices.
2020-11-16 10:09:23 -05:00
- A class adds a layer of abstraction, which makes the component API and its inner workings less clear.
2021-01-25 16:09:03 -05:00
- It makes it harder to test. Because the class is instantiated by the component data function, it is
2020-12-22 10:09:51 -05:00
harder to 'manage' component and class separately.
2021-01-25 16:09:03 -05:00
- Adding Object Oriented Principles (OOP) to a functional codebase adds yet another way of writing code, reducing consistency and clarity.
2020-11-16 10:09:23 -05:00
2017-03-22 15:30:54 -04:00
## Style guide
2019-11-29 13:06:24 -05:00
Please refer to the Vue section of our [style guide ](style/vue.md )
2020-10-30 14:08:56 -04:00
for best practices while writing and testing your Vue components and templates.
2017-03-22 15:30:54 -04:00
2017-04-20 07:24:40 -04:00
## Testing Vue Components
2020-10-30 14:08:56 -04:00
Please refer to the [Vue testing style guide ](style/vue.md#vue-testing )
for guidelines and best practices for testing your Vue components.
2017-04-20 07:24:40 -04:00
Each Vue component has a unique output. This output is always present in the render function.
2021-01-25 16:09:03 -05:00
Although each method of a Vue component can be tested individually, our goal is to test the output
of the render function, which represents the state at all times.
2017-04-20 07:24:40 -04:00
2021-03-04 16:08:59 -05:00
Visit the [Vue testing guide ](https://vuejs.org/v2/guide/testing.html#Unit-Testing ) for help
testing the rendered output.
2020-05-12 14:07:54 -04:00
Here's an example of a well structured unit test for [this Vue component ](#appendix---vue-component-subject-under-test ):
2017-04-20 07:24:40 -04:00
```javascript
2020-05-12 14:07:54 -04:00
import { GlLoadingIcon } from '@gitlab/ui';
2018-05-27 15:21:12 -04:00
import MockAdapter from 'axios-mock-adapter';
2021-06-02 11:09:59 -04:00
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
2020-05-12 14:07:54 -04:00
import axios from '~/lib/utils/axios_utils';
import App from '~/todos/app.vue';
2017-04-20 07:24:40 -04:00
2021-06-02 11:09:59 -04:00
const TEST_TODOS = [{ text: 'Lorem ipsum test text' }, { text: 'Lorem ipsum 2' }];
2020-05-12 14:07:54 -04:00
const TEST_NEW_TODO = 'New todo title';
const TEST_TODO_PATH = '/todos';
describe('~/todos/app.vue', () => {
let wrapper;
2018-05-27 15:21:12 -04:00
let mock;
beforeEach(() => {
2020-05-12 14:07:54 -04:00
// IMPORTANT: Use axios-mock-adapter for stubbing axios API requests
2018-05-27 15:21:12 -04:00
mock = new MockAdapter(axios);
2020-05-12 14:07:54 -04:00
mock.onGet(TEST_TODO_PATH).reply(200, TEST_TODOS);
mock.onPost(TEST_TODO_PATH).reply(200);
2018-05-27 15:21:12 -04:00
});
afterEach(() => {
2020-05-12 14:07:54 -04:00
// IMPORTANT: Clean up the component instance and axios mock adapter
wrapper.destroy();
mock.restore();
2017-04-20 07:24:40 -04:00
});
2020-11-03 19:09:12 -05:00
// It is very helpful to separate setting up the component from
2020-12-22 10:09:51 -05:00
// its collaborators (for example, Vuex and axios).
2020-05-12 14:07:54 -04:00
const createWrapper = (props = {}) => {
2021-06-02 11:09:59 -04:00
wrapper = shallowMountExtended(App, {
propsData: {
path: TEST_TODO_PATH,
...props,
},
});
2020-05-12 14:07:54 -04:00
};
2020-11-03 19:09:12 -05:00
// Helper methods greatly help test maintainability and readability.
2021-06-02 11:09:59 -04:00
const findLoader = () => wrapper.findComponent(GlLoadingIcon);
2020-12-22 10:09:51 -05:00
const findAddButton = () => wrapper.findByTestId('add-button');
const findTextInput = () => wrapper.findByTestId('text-input');
2021-06-02 11:09:59 -04:00
const findTodoData = () =>
wrapper
.findAllByTestId('todo-item')
.wrappers.map((item) => ({ text: item.text() }));
2020-05-12 14:07:54 -04:00
describe('when mounted and loading', () => {
beforeEach(() => {
// Create request which will never resolve
mock.onGet(TEST_TODO_PATH).reply(() => new Promise(() => {}));
createWrapper();
});
2017-04-20 07:24:40 -04:00
2020-05-12 14:07:54 -04:00
it('should render the loading state', () => {
expect(findLoader().exists()).toBe(true);
2017-04-20 07:24:40 -04:00
});
2018-05-27 15:21:12 -04:00
});
2017-04-20 07:24:40 -04:00
2020-05-12 14:07:54 -04:00
describe('when todos are loaded', () => {
beforeEach(() => {
createWrapper();
// IMPORTANT: This component fetches data asynchronously on mount, so let's wait for the Vue template to update
return wrapper.vm.$nextTick();
});
2017-04-20 07:24:40 -04:00
2020-05-12 14:07:54 -04:00
it('should not show loading', () => {
expect(findLoader().exists()).toBe(false);
});
2017-04-20 07:24:40 -04:00
2020-05-12 14:07:54 -04:00
it('should render todos', () => {
expect(findTodoData()).toEqual(TEST_TODOS);
2017-04-20 07:24:40 -04:00
});
2021-06-02 11:09:59 -04:00
it('when todo is added, should post new todo', async () => {
findTextInput().vm.$emit('update', TEST_NEW_TODO);
2020-05-12 14:07:54 -04:00
findAddButton().vm.$emit('click');
2017-04-20 07:24:40 -04:00
2021-06-02 11:09:59 -04:00
await wrapper.vm.$nextTick();
expect(mock.history.post.map((x) => JSON.parse(x.data))).toEqual([{ text: TEST_NEW_TODO }]);
2017-04-20 07:24:40 -04:00
});
});
});
```
2018-05-27 15:21:12 -04:00
2020-12-22 10:09:51 -05:00
### Child components
1. Test any directive that defines if/how child component is rendered (for example, `v-if` and `v-for` ).
2021-01-11 19:10:42 -05:00
1. Test any props we are passing to child components (especially if the prop is calculated in the
2020-12-22 10:09:51 -05:00
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');
2021-02-10 22:09:06 -05:00
2020-12-22 10:09:51 -05:00
expect(checkbox.attributes('disabled')).not.toBeDefined();
2021-02-10 22:09:06 -05:00
2020-12-22 10:09:51 -05:00
findChildComponent().vm.$emit('primary');
await nextTick();
2021-02-10 22:09:06 -05:00
2020-12-22 10:09:51 -05:00
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);
2021-02-10 22:09:06 -05:00
2020-12-22 10:09:51 -05:00
// good
expect(findChildComponent().props('withAlertContainer')).toBe(false);
```
2020-05-06 11:09:42 -04:00
### Events
2021-01-25 16:09:03 -05:00
We should test for events emitted in response to an action in our component. This is used to
2020-12-22 10:09:51 -05:00
verify the correct events are being fired with the correct arguments.
2020-05-06 11:09:42 -04:00
2022-03-30 08:09:14 -04:00
For any DOM events we should use [`trigger` ](https://v1.test-utils.vuejs.org/api/wrapper/#trigger )
2020-12-22 10:09:51 -05:00
to fire out event.
2020-05-06 11:09:42 -04:00
```javascript
// Assuming SomeButton renders: < button > Some button< / button >
wrapper = mount(SomeButton);
...
it('should fire the click event', () => {
const btn = wrapper.find('button')
btn.trigger('click');
...
})
```
2021-01-11 19:10:42 -05:00
When we need to fire a Vue event, we should use [`emit` ](https://vuejs.org/v2/guide/components-custom-events.html )
2020-12-22 10:09:51 -05:00
to fire our event.
2020-05-06 11:09:42 -04:00
```javascript
wrapper = shallowMount(DropdownItem);
...
it('should fire the itemClicked event', () => {
DropdownItem.vm.$emit('itemClicked');
...
})
```
2021-01-11 19:10:42 -05:00
We should verify an event has been fired by asserting against the result of the
2022-03-30 08:09:14 -04:00
[`emitted()` ](https://v1.test-utils.vuejs.org/api/wrapper/#emitted ) method.
2020-05-06 11:09:42 -04:00
2018-09-26 06:36:44 -04:00
## Vue.js Expert Role
2019-07-02 04:50:00 -04:00
2020-07-13 20:09:46 -04:00
You should only apply to be a Vue.js expert when your own merge requests and your reviews show:
2019-02-22 08:17:10 -05:00
2019-11-26 01:06:35 -05:00
- Deep understanding of Vue and Vuex reactivity
2018-09-26 06:36:44 -04:00
- Vue and Vuex code are structured according to both official and our guidelines
2018-09-26 08:26:19 -04:00
- Full understanding of testing a Vue and Vuex application
2020-05-07 05:09:51 -04:00
- Vuex code follows the [documented pattern ](vuex.md#naming-pattern-request-and-receive-namespaces )
2018-09-26 08:26:19 -04:00
- Knowledge about the existing Vue and Vuex applications and existing reusable components
2020-04-29 08:10:00 -04:00
## Vue 2 -> Vue 3 Migration
> This section is added temporarily to support the efforts to migrate the codebase from Vue 2.x to Vue 3.x
2021-01-25 16:09:03 -05:00
We recommend to minimize adding certain features to the codebase to prevent increasing
2020-12-22 10:09:51 -05:00
the tech debt for the eventual migration:
2020-04-29 08:10:00 -04:00
- filters;
- event buses;
- functional templated
- `slot` attributes
You can find more details on [Migration to Vue 3 ](vue3_migration.md )
2020-05-12 14:07:54 -04:00
## Appendix - Vue component subject under test
2021-01-11 19:10:42 -05:00
This is the template for the example component which is tested in the
2020-12-22 10:09:51 -05:00
[Testing Vue components ](#testing-vue-components ) section:
2020-05-12 14:07:54 -04:00
```html
< template >
< div class = "content" >
< gl-loading-icon v-if = "isLoading" / >
< template v-else >
< div
v-for="todo in todos"
:key="todo.id"
:class="{ 'gl-strike': todo.isDone }"
data-testid="todo-item"
>{{ toddo.text }}< / div >
< footer class = "gl-border-t-1 gl-mt-3 gl-pt-3" >
< gl-form-input
type="text"
v-model="todoText"
data-testid="text-input"
>
< gl-button
2022-05-29 23:08:55 -04:00
variant="confirm"
2020-05-12 14:07:54 -04:00
data-testid="add-button"
@click ="addTodo"
>Add< / gl-button >
< / footer >
< / template >
< / div >
< / template >
```