From c7b3ba834b80d81521a7e6619276e0920d4af94c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 20 Apr 2017 11:24:40 +0000 Subject: [PATCH] Adds vue js example application and documentation --- .../fe_guide/img/boards_diagram.png | Bin 0 -> 30538 bytes doc/development/fe_guide/img/vue_arch.png | Bin 0 -> 9848 bytes doc/development/fe_guide/index.md | 53 +++ doc/development/fe_guide/style_guide_js.md | 23 +- doc/development/fe_guide/testing.md | 5 + doc/development/fe_guide/vue.md | 343 +++++++++++++++++- 6 files changed, 408 insertions(+), 16 deletions(-) create mode 100644 doc/development/fe_guide/img/boards_diagram.png create mode 100644 doc/development/fe_guide/img/vue_arch.png diff --git a/doc/development/fe_guide/img/boards_diagram.png b/doc/development/fe_guide/img/boards_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..7a2cf972fd019fdf8593a84eaf4f6b6b6ceca689 GIT binary patch literal 30538 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 R`NM!elA>}VCBg;){|7-_=J5ak literal 0 HcmV?d00001 diff --git a/doc/development/fe_guide/img/vue_arch.png b/doc/development/fe_guide/img/vue_arch.png new file mode 100644 index 0000000000000000000000000000000000000000..a67706c7c1e5dce1491e92c576f95d6d8911f4ec GIT binary patch literal 9848 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 + Add User + + + // good + + ``` ### Vue.js @@ -200,6 +213,7 @@ See [our current .eslintrc][eslintrc] for specific rules and patterns. #### Naming - **Extensions**: Use `.vue` extension for Vue components. - **Reference Naming**: Use PascalCase for Vue components and camelCase for their instances: + ```javascript // bad import cardBoard from 'cardBoard'; @@ -217,6 +231,7 @@ See [our current .eslintrc][eslintrc] for specific rules and patterns. cardBoard: CardBoard }; ``` + - **Props Naming:** - Avoid using DOM component prop names. - Use kebab-case instead of camelCase to provide props in templates. @@ -243,12 +258,18 @@ See [our current .eslintrc][eslintrc] for specific rules and patterns. + + // good + + // if props fit in one line then keep it on the same line ``` diff --git a/doc/development/fe_guide/testing.md b/doc/development/fe_guide/testing.md index a4631fd0073..66afbf4db4d 100644 --- a/doc/development/fe_guide/testing.md +++ b/doc/development/fe_guide/testing.md @@ -26,6 +26,10 @@ browser and you will not have access to certain APIs, such as [`Notification`](https://developer.mozilla.org/en-US/docs/Web/API/notification), which will have to be stubbed. +### Writing tests +### Vue.js unit tests +See this [section][vue-test]. + ### Running frontend tests `rake karma` runs the frontend-only (JavaScript) tests. @@ -134,3 +138,4 @@ Scenario: Developer can approve merge request [jasmine-focus]: https://jasmine.github.io/2.5/focused_specs.html [jasmine-jquery]: https://github.com/velesin/jasmine-jquery [karma]: http://karma-runner.github.io/ +[vue-test]:https://docs.gitlab.com/ce/development/fe_guide/vue.html#testing-vue-components diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md index 3e3406e7d6a..45c8300d9de 100644 --- a/doc/development/fe_guide/vue.md +++ b/doc/development/fe_guide/vue.md @@ -19,13 +19,31 @@ We don't want to refactor all GitLab frontend code into Vue.js, here are some gu when not to use Vue.js: - Adding or changing static information; -- Features that highly depend on jQuery will be hard to work with Vue.js +- Features that highly depend on jQuery will be hard to work with Vue.js; +- Features without reactive data; As always, the Frontend Architectural Experts are available to help with any Vue or JavaScript questions. -## How to build a new feature with Vue.js +## Vue architecture -**Components, Stores and Services** +All new features built with Vue.js must follow a [Flux architecture][flux]. +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, each Vue bundle needs a Store - where we keep all the data -, +a Service - that we use to communicate with the server - and a main Vue component. + +Think of the Main Vue Component as the entry point of your application. This is the only smart +component that should exist in each Vue feature. +This component is responsible for: +1. Calling the Service to get data from the server +1. Calling the Store to store the data received +1. Mounting all the other components + + ![Vue Architecture](img/vue_arch.png) + +You can also read about this architecture in vue docs about [state management][state-management] +and about [one way data flow][one-way-data-flow]. + +### Components, Stores and Services In some features implemented with Vue.js, like the [issue board][issue-boards] or [environments table][environments-table] @@ -46,16 +64,17 @@ _For consistency purposes, we recommend you to follow the same structure._ Let's look into each of them: -**A `*_bundle.js` file** +### A `*_bundle.js` file This is the index file of your new feature. This is where the root Vue instance of the new feature should be. -The Store and the Service should be imported and initialized in this file and provided as a prop to the main component. +The Store and the Service should be imported and initialized in this file and +provided as a prop to the main component. Don't forget to follow [these steps.][page_specific_javascript] -**A folder for Components** +### A folder for Components This folder holds all components that are specific of this new feature. If you need to use or create a component that will probably be used somewhere @@ -70,29 +89,320 @@ in one table would not be a good use of this pattern. You can read more about components in Vue.js site, [Component System][component-system] -**A folder for the Store** +### A folder for the Store The Store is a class that allows us to manage the state in a single -source of truth. +source of truth. It is not aware of the service or the components. The concept we are trying to follow is better explained by Vue documentation itself, please read this guide: [State Management][state-management] -**A folder for the Service** +### A folder for the Service -The Service is used only to communicate with the server. -It does not store or manipulate any data. -We use [vue-resource][vue-resource-repo] to -communicate with the server. +The Service is a class used only to communicate with the server. +It does not store or manipulate any data. It is not aware of the store or the components. +We use [vue-resource][vue-resource-repo] to communicate with the server. -The [issue boards service][issue-boards-service] -is a good example of this pattern. +### End Result + +The following example shows an application: + +```javascript +// store.js +export default class Store { + + /** + * This is where we will iniatialize the state of our data. + * Usually in a small SPA you don't need any options when starting the store. In the case you do + * need guarantee it's an Object and it's documented. + * + * @param {Object} options + */ + constructor(options) { + this.options = options; + + // Create a state object to handle all our data in the same place + this.todos = []: + } + + setTodos(todos = []) { + this.todos = todos; + } + + addTodo(todo) { + this.todos.push(todo); + } + + removeTodo(todoID) { + const state = this.todos; + + const newState = state.filter((element) => {element.id !== todoID}); + + this.todos = newState; + } +} + +// service.js +import Vue from 'vue'; +import VueResource from 'vue-resource'; +import 'vue_shared/vue_resource_interceptor'; + +Vue.use(VueResource); + +export default class Service { + constructor(options) { + this.todos = Vue.resource(endpoint.todosEndpoint); + } + + getTodos() { + return this.todos.get(); + } + + addTodo(todo) { + return this.todos.put(todo); + } +} +// todo_component.vue + + + +// todos_main_component.vue + + + +// 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. ## 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 @@ -100,5 +410,8 @@ for best practices while writing your Vue components and templates. [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