// bundle.js
import todoComponent from 'todos_main_component.vue';
new Vue({
el: '.js-todo-app',
components: {
todoComponent,
},
render: createElement => createElement('todo-component' {
props: {
someProp: [],
}
}),
});
```
The [issue boards service][issue-boards-service] is a good example of this pattern.
## Information hiding
To make components more reusable, readable, and performant, aim to only pass props that
the child component needs.
Instead of this:
```
const ParentComponent = {
data: bigGiantObject,
template: `
`,
}
const ChildComponent = {
props: {
bigGiantObject: { type: Object }
},
template: `
{{ bigGiantObject.littlePropertyOne }}
{{ bigGiantObject.littlePropertyTwo }}
{{ bigGiantObject.littlePropertyThree }}
`
};
```
Component props should be passed explicitly, like this:
```
const BetterParentComponent = {
data: bigGiantObject,
template: `
`,
};
const BetterChildComponent = {
props: {
littlePropertyOne: { type: String },
littlePropertyTwo: { type: String },
},
template: `
{{ littlePropertyOne }} {{ littlePropertyTwo }}
`
};
```
For deeper models, still try to encapsulate the data conservatively. An example
would look like this:
```
const DeepModelParentComponent = {
data: bigGiantObject,
template: `
`,
};
const DeepModelChildComponent = {
props: {
tierTwo: { type: Object },
tierThree: { type: Object },
},
template: `
{{ tierTwo.littlePropertyOne }} {{ tierTwo: littlePropertyTwo }}
{{ tierThree.littlePropertyOne }} {{ tierThree: littlePropertyTwo }}
`
};
```
If the number of props you need to pass is very large or you need to pass a group of
properties down many layers, it may be simpler and more maintainable to just pass
a single object. If you're not sure, ask a Frontend Architecture expert.
## Style guide
Please refer to the Vue section of our [style guide](style_guide_js.md#vuejs)
for best practices while writing your Vue components and templates.
## Testing Vue Components
Each Vue component has a unique output. This output is always present in the render function.
Although we can test each method of a Vue component individually, our goal must be to test the output
of the render/template function, which represents the state at all times.
Make use of Vue Resource Interceptors to mock data returned by the service.
Here's how we would test the Todo App above:
```javascript
import component from 'todos_main_component';
describe('Todos App', () => {
it('should render the loading state while the request is being made', () => {
const Component = Vue.extend(component);
const vm = new Component().$mount();
expect(vm.$el.querySelector('i.fa-spin')).toBeDefined();
});
describe('with data', () => {
// Mock the service to return data
const interceptor = (request, next) => {
next(request.respondWith(JSON.stringify([{
title: 'This is a todo',
body: 'This is the text'
}]), {
status: 200,
}));
};
let vm;
beforeEach(() => {
Vue.http.interceptors.push(interceptor);
const Component = Vue.extend(component);
vm = new Component().$mount();
});
afterEach(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
});
it('should render todos', (done) => {
setTimeout(() => {
expect(vm.$el.querySelectorAll('.js-todo-list div').length).toBe(1);
done();
}, 0);
});
});
describe('add todo', () => {
let vm;
beforeEach(() => {
const Component = Vue.extend(component);
vm = new Component().$mount();
});
it('should add a todos', (done) => {
setTimeout(() => {
vm.$el.querySelector('.js-add-todo').click();
// Add a new interceptor to mock the add Todo request
Vue.nextTick(() => {
expect(vm.$el.querySelectorAll('.js-todo-list div').length).toBe(2);
});
}, 0);
});
});
});
```
### Stubbing API responses
[Vue Resource Interceptors][vue-resource-interceptor] allow us to add a interceptor with
the response we need:
```javascript
// Mock the service to return data
const interceptor = (request, next) => {
next(request.respondWith(JSON.stringify([{
title: 'This is a todo',
body: 'This is the text'
}]), {
status: 200,
}));
};
beforeEach(() => {
Vue.http.interceptors.push(interceptor);
});
afterEach(() => {
Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
});
it('should do something', (done) => {
setTimeout(() => {
// Test received data
done();
}, 0);
});
```
[vue-docs]: http://vuejs.org/guide/index.html
[issue-boards]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/boards
[environments-table]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/environments
[page_specific_javascript]: https://docs.gitlab.com/ce/development/frontend.html#page-specific-javascript
[component-system]: https://vuejs.org/v2/guide/#Composing-with-Components
[state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
[one-way-data-flow]: https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow
[vue-resource-repo]: https://github.com/pagekit/vue-resource
[vue-resource-interceptor]: https://github.com/pagekit/vue-resource/blob/develop/docs/http.md#interceptors
[issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6
[flux]: https://facebook.github.io/flux