From 184f60a06f828ccbc9264d40e6daa48d60dca629 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda <filipa@gitlab.com> Date: Sun, 29 Jan 2017 15:30:04 +0000 Subject: [PATCH] Moves pagination to shared folder Document and remove unused code Declare components in a consistent way; Use " instead of ' to improve consistency; Update documentation; Fix commit author verification to match the use cases; Adds tests for the added components Fix paths in pagination spec Adds tests to pipelines table used in merge requests and commits Use same resource interceptor Fix eslint error --- .../commit/pipelines/pipelines_bundle.js.es6 | 100 +--------------- .../commit/pipelines/pipelines_service.js.es6 | 5 + .../commit/pipelines/pipelines_table.js.es6 | 104 +++++++++++++++++ .../environments/environments_bundle.js.es6 | 2 +- .../vue_resource_interceptor.js.es6 | 12 -- .../vue_pipelines_index/pipelines.js.es6 | 4 +- .../vue_pipelines_index/time_ago.js.es6 | 2 + .../components/pipelines_table.js.es6 | 24 ++-- .../components/pipelines_table_row.js.es6 | 102 ++++++++++------- .../components/table_pagination.js.es6} | 0 .../vue_resource_interceptor.js.es6 | 11 +- app/controllers/projects/commit_controller.rb | 1 - .../projects/merge_requests_controller.rb | 1 - .../merge_requests/_new_submit.html.haml | 6 +- .../projects/merge_requests/_show.html.haml | 1 - .../commit/pipelines/mock_data.js.es6 | 90 +++++++++++++++ .../commit/pipelines/pipelines_spec.js.es6 | 107 ++++++++++++++++++ .../pipelines/pipelines_store_spec.js.es6 | 31 +++++ .../fixtures/pipelines_table.html.haml | 2 + .../pipelines_table_row_spec.js.es6 | 90 +++++++++++++++ .../components/pipelines_table_spec.js.es6 | 67 +++++++++++ .../components/table_pagination_spec.js.es6} | 3 +- 22 files changed, 591 insertions(+), 174 deletions(-) create mode 100644 app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 delete mode 100644 app/assets/javascripts/environments/vue_resource_interceptor.js.es6 rename app/assets/javascripts/{vue_pagination/index.js.es6 => vue_shared/components/table_pagination.js.es6} (100%) create mode 100644 spec/javascripts/commit/pipelines/mock_data.js.es6 create mode 100644 spec/javascripts/commit/pipelines/pipelines_spec.js.es6 create mode 100644 spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6 create mode 100644 spec/javascripts/fixtures/pipelines_table.html.haml create mode 100644 spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es6 create mode 100644 spec/javascripts/vue_shared/components/pipelines_table_spec.js.es6 rename spec/javascripts/{vue_pagination/pagination_spec.js.es6 => vue_shared/components/table_pagination_spec.js.es6} (98%) diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 index a06aad17824..b21d13842a4 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 +++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 @@ -3,10 +3,6 @@ //= require vue //= require_tree . -//= require vue -//= require vue-resource -//= require vue_shared/vue_resource_interceptor -//= require vue_shared/components/pipelines_table /** * Commits View > Pipelines Tab > Pipelines Table. @@ -14,13 +10,6 @@ * * Renders Pipelines table in pipelines tab in the commits show view. * Renders Pipelines table in pipelines tab in the merge request show view. - * - * Uses `pipelines-table-component` to render Pipelines table with an API call. - * Endpoint is provided in HTML and passed as scope. - * We need a store to make the request and store the received environemnts. - * - * Necessary SVG in the table are provided as props. This should be refactored - * as soon as we have Webpack and can load them directly into JS files. */ $(() => { @@ -28,94 +17,11 @@ $(() => { gl.commits = gl.commits || {}; gl.commits.pipelines = gl.commits.pipelines || {}; - if (gl.commits.PipelinesTableView) { - gl.commits.PipelinesTableView.$destroy(true); + if (gl.commits.PipelinesTableBundle) { + gl.commits.PipelinesTableBundle.$destroy(true); } - gl.commits.pipelines.PipelinesTableView = new Vue({ - + gl.commits.pipelines.PipelinesTableBundle = new gl.commits.pipelines.PipelinesTableView({ el: document.querySelector('#commit-pipeline-table-view'), - - components: { - 'pipelines-table-component': gl.pipelines.PipelinesTableComponent, - }, - - /** - * Accesses the DOM to provide the needed data. - * Returns the necessary props to render `pipelines-table-component` component. - * - * @return {Object} Props for `pipelines-table-component` - */ - data() { - const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset; - const svgsData = document.querySelector('.pipeline-svgs').dataset; - const store = gl.commits.pipelines.PipelinesStore.create(); - - // Transform svgs DOMStringMap to a plain Object. - const svgsObject = Object.keys(svgsData).reduce((acc, element) => { - acc[element] = svgsData[element]; - return acc; - }, {}); - - return { - endpoint: pipelinesTableData.endpoint, - svgs: svgsObject, - store, - state: store.state, - isLoading: false, - error: false, - }; - }, - - /** - * When the component is created the service to fetch the data will be - * initialized with the correct endpoint. - * - * A request to fetch the pipelines will be made. - * In case of a successfull response we will store the data in the provided - * store, in case of a failed response we need to warn the user. - * - */ - created() { - gl.pipelines.pipelinesService = new PipelinesService(this.endpoint); - - this.isLoading = true; - - return gl.pipelines.pipelinesService.all() - .then(response => response.json()) - .then((json) => { - this.store.store(json); - this.isLoading = false; - this.error = false; - }).catch(() => { - this.error = true; - this.isLoading = false; - new Flash('An error occurred while fetching the pipelines.', 'alert'); - }); - }, - - template: ` - <div> - <div class="pipelines realtime-loading" v-if='isLoading'> - <i class="fa fa-spinner fa-spin"></i> - </div> - - <div class="blank-state blank-state-no-icon" - v-if="!isLoading && !error && state.pipelines.length === 0"> - <h2 class="blank-state-title js-blank-state-title"> - You don't have any pipelines. - </h2> - </div> - - <div - class="table-holder pipelines" - v-if='!isLoading && state.pipelines.length > 0'> - <pipelines-table-component - :pipelines='state.pipelines' - :svgs='svgs'> - </pipelines-table-component> - </div> - </div> - `, }); }); diff --git a/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 index 1e6aa73d9cf..f4ed986b0c5 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 +++ b/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 @@ -32,3 +32,8 @@ class PipelinesService { return this.pipelines.get(); } } + +window.gl = window.gl || {}; +gl.commits = gl.commits || {}; +gl.commits.pipelines = gl.commits.pipelines || {}; +gl.commits.pipelines.PipelinesService = PipelinesService; diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 new file mode 100644 index 00000000000..df7a6455eed --- /dev/null +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 @@ -0,0 +1,104 @@ +/* eslint-disable no-new, no-param-reassign */ +/* global Vue, CommitsPipelineStore, PipelinesService, Flash */ + +//= require vue +//= require vue-resource +//= require vue_shared/vue_resource_interceptor +//= require vue_shared/components/pipelines_table + +/** + * + * Uses `pipelines-table-component` to render Pipelines table with an API call. + * Endpoint is provided in HTML and passed as `endpoint`. + * We need a store to store the received environemnts. + * We need a service to communicate with the server. + * + * Necessary SVG in the table are provided as props. This should be refactored + * as soon as we have Webpack and can load them directly into JS files. + */ + +(() => { + window.gl = window.gl || {}; + gl.commits = gl.commits || {}; + gl.commits.pipelines = gl.commits.pipelines || {}; + + gl.commits.pipelines.PipelinesTableView = Vue.component('pipelines-table', { + + components: { + 'pipelines-table-component': gl.pipelines.PipelinesTableComponent, + }, + + /** + * Accesses the DOM to provide the needed data. + * Returns the necessary props to render `pipelines-table-component` component. + * + * @return {Object} + */ + data() { + const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset; + const svgsData = document.querySelector('.pipeline-svgs').dataset; + const store = gl.commits.pipelines.PipelinesStore.create(); + + // Transform svgs DOMStringMap to a plain Object. + const svgsObject = Object.keys(svgsData).reduce((acc, element) => { + acc[element] = svgsData[element]; + return acc; + }, {}); + + return { + endpoint: pipelinesTableData.endpoint, + svgs: svgsObject, + store, + state: store.state, + isLoading: false, + }; + }, + + /** + * When the component is created the service to fetch the data will be + * initialized with the correct endpoint. + * + * A request to fetch the pipelines will be made. + * In case of a successfull response we will store the data in the provided + * store, in case of a failed response we need to warn the user. + * + */ + created() { + gl.pipelines.pipelinesService = new PipelinesService(this.endpoint); + + this.isLoading = true; + return gl.pipelines.pipelinesService.all() + .then(response => response.json()) + .then((json) => { + this.store.store(json); + this.isLoading = false; + }).catch(() => { + this.isLoading = false; + new Flash('An error occurred while fetching the pipelines.', 'alert'); + }); + }, + + template: ` + <div> + <div class="pipelines realtime-loading" v-if="isLoading"> + <i class="fa fa-spinner fa-spin"></i> + </div> + + <div class="blank-state blank-state-no-icon" + v-if="!isLoading && state.pipelines.length === 0"> + <h2 class="blank-state-title js-blank-state-title"> + No pipelines to show + </h2> + </div> + + <div class="table-holder pipelines" + v-if="!isLoading && state.pipelines.length > 0"> + <pipelines-table-component + :pipelines="state.pipelines" + :svgs="svgs"> + </pipelines-table-component> + </div> + </div> + `, + }); +})(); diff --git a/app/assets/javascripts/environments/environments_bundle.js.es6 b/app/assets/javascripts/environments/environments_bundle.js.es6 index 3b003f6f661..cd205617a97 100644 --- a/app/assets/javascripts/environments/environments_bundle.js.es6 +++ b/app/assets/javascripts/environments/environments_bundle.js.es6 @@ -1,7 +1,7 @@ //= require vue //= require_tree ./stores/ //= require ./components/environment -//= require ./vue_resource_interceptor +//= require vue_shared/vue_resource_interceptor $(() => { window.gl = window.gl || {}; diff --git a/app/assets/javascripts/environments/vue_resource_interceptor.js.es6 b/app/assets/javascripts/environments/vue_resource_interceptor.js.es6 deleted file mode 100644 index 406bdbc1c7d..00000000000 --- a/app/assets/javascripts/environments/vue_resource_interceptor.js.es6 +++ /dev/null @@ -1,12 +0,0 @@ -/* global Vue */ -Vue.http.interceptors.push((request, next) => { - Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; - - next((response) => { - if (typeof response.data === 'string') { - response.data = JSON.parse(response.data); // eslint-disable-line - } - - Vue.activeResources--; // eslint-disable-line - }); -}); diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 index 34d93ce1b7f..c1daf816060 100644 --- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 @@ -1,7 +1,7 @@ /* global Vue, Turbolinks, gl */ /* eslint-disable no-param-reassign */ -//= require vue_pagination/index +//= require vue_shared/components/table_pagination //= require ./store.js.es6 //= require vue_shared/components/pipelines_table @@ -9,7 +9,7 @@ gl.VuePipelines = Vue.extend({ components: { - glPagination: gl.VueGlPagination, + 'gl-pagination': gl.VueGlPagination, 'pipelines-table-component': gl.pipelines.PipelinesTableComponent, }, diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 index 655110feba1..61417b28630 100644 --- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 +++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 @@ -1,6 +1,8 @@ /* global Vue, gl */ /* eslint-disable no-param-reassign */ +//= require lib/utils/datetime_utility + ((gl) => { gl.VueTimeAgo = Vue.extend({ data() { diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 index 4b6bba461d7..9bc1ea65e53 100644 --- a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 +++ b/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 @@ -4,10 +4,9 @@ //= require ./pipelines_table_row /** - * Pipelines Table Component - * - * Given an array of pipelines, renders a table. + * Pipelines Table Component. * + * Given an array of objects, renders a table. */ (() => { @@ -20,11 +19,11 @@ pipelines: { type: Array, required: true, - default: [], + default: () => ([]), }, /** - * Remove this. Find a better way to do this. don't want to provide this 3 times. + * TODO: Remove this when we have webpack. */ svgs: { type: Object, @@ -41,19 +40,18 @@ <table class="table ci-table"> <thead> <tr> - <th class="pipeline-status">Status</th> - <th class="pipeline-info">Pipeline</th> - <th class="pipeline-commit">Commit</th> - <th class="pipeline-stages">Stages</th> - <th class="pipeline-date"></th> - <th class="pipeline-actions hidden-xs"></th> + <th class="js-pipeline-status pipeline-status">Status</th> + <th class="js-pipeline-info pipeline-info">Pipeline</th> + <th class="js-pipeline-commit pipeline-commit">Commit</th> + <th class="js-pipeline-stages pipeline-stages">Stages</th> + <th class="js-pipeline-date pipeline-date"></th> + <th class="js-pipeline-actions pipeline-actions hidden-xs"></th> </tr> </thead> <tbody> <template v-for="model in pipelines" v-bind:model="model"> - <tr - is="pipelines-table-row-component" + <tr is="pipelines-table-row-component" :pipeline="model" :svgs="svgs"></tr> </template> diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 index c0ff0c90e4e..375516e3804 100644 --- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 +++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 @@ -7,6 +7,12 @@ //= require vue_shared/components/commit //= require vue_pipelines_index/pipeline_actions //= require vue_pipelines_index/time_ago + +/** + * Pipeline table row. + * + * Given the received object renders a table row in the pipelines' table. + */ (() => { window.gl = window.gl || {}; gl.pipelines = gl.pipelines || {}; @@ -21,7 +27,7 @@ }, /** - * Remove this. Find a better way to do this. don't want to provide this 3 times. + * TODO: Remove this when we have webpack; */ svgs: { type: Object, @@ -32,12 +38,10 @@ components: { 'commit-component': gl.CommitComponent, - runningPipeline: gl.VueRunningPipeline, - pipelineActions: gl.VuePipelineActions, - 'vue-stage': gl.VueStage, - pipelineUrl: gl.VuePipelineUrl, - pipelineHead: gl.VuePipelineHead, - statusScope: gl.VueStatusScope, + 'pipeline-actions': gl.VuePipelineActions, + 'dropdown-stage': gl.VueStage, + 'pipeline-url': gl.VuePipelineUrl, + 'status-scope': gl.VueStatusScope, 'time-ago': gl.VueTimeAgo, }, @@ -46,48 +50,48 @@ * If provided, returns the commit tag. * Needed to render the commit component column. * - * TODO: Document this logic, need to ask @grzesiek and @selfup + * This field needs a lot of verification, because of different possible cases: + * + * 1. person who is an author of a commit might be a GitLab user + * 2. if person who is an author of a commit is a GitLab user he/she can have a GitLab avatar + * 3. If GitLab user does not have avatar he/she might have a Gravatar + * 4. If committer is not a GitLab User he/she can have a Gravatar + * 5. We do not have consistent API object in this case + * 6. We should improve API and the code * * @returns {Object|Undefined} */ commitAuthor() { - if (!this.pipeline.commit) { - return { avatar_url: '', web_url: '', username: '' }; - } + let commitAuthorInformation; + // 1. person who is an author of a commit might be a GitLab user if (this.pipeline && this.pipeline.commit && this.pipeline.commit.author) { - return this.pipeline.commit.author; + // 2. if person who is an author of a commit is a GitLab user + // he/she can have a GitLab avatar + if (this.pipeline.commit.author.avatar_url) { + commitAuthorInformation = this.pipeline.commit.author; + + // 3. If GitLab user does not have avatar he/she might have a Gravatar + } else if (this.pipeline.commit.author_gravatar_url) { + commitAuthorInformation = Object.assign({}, this.pipeline.commit.author, { + avatar_url: this.pipeline.commit.author_gravatar_url, + }); + } } + // 4. If committer is not a GitLab User he/she can have a Gravatar if (this.pipeline && - this.pipeline.commit && - this.pipeline.commit.author_gravatar_url && - this.pipeline.commit.author_name && - this.pipeline.commit.author_email) { - return { + this.pipeline.commit) { + commitAuthorInformation = { avatar_url: this.pipeline.commit.author_gravatar_url, web_url: `mailto:${this.pipeline.commit.author_email}`, username: this.pipeline.commit.author_name, }; } - return undefined; - }, - - /** - * Figure this out! - * Needed to render the commit component column. - */ - author(pipeline) { - if (!pipeline.commit) return { avatar_url: '', web_url: '', username: '' }; - if (pipeline.commit.author) return pipeline.commit.author; - return { - avatar_url: pipeline.commit.author_gravatar_url, - web_url: `mailto:${pipeline.commit.author_email}`, - username: pipeline.commit.author_name, - }; + return commitAuthorInformation; }, /** @@ -108,6 +112,9 @@ * If provided, returns the commit ref. * Needed to render the commit component column. * + * Matched `url` prop sent in the API to `path` prop needed + * in the commit component. + * * @returns {Object|Undefined} */ commitRef() { @@ -169,6 +176,17 @@ }, methods: { + /** + * FIXME: This should not be in this component but in the components that + * need this function. + * + * Used to render SVGs in the following components: + * - status-scope + * - dropdown-stage + * + * @param {String} string + * @return {String} + */ match(string) { return string.replace(/_([a-z])/g, (m, w) => w.toUpperCase()); }, @@ -177,12 +195,12 @@ template: ` <tr class="commit"> <status-scope - :pipeline='pipeline' - :svgs='svgs' + :pipeline="pipeline" + :svgs="svgs" :match="match"> </status-scope> - <pipeline-url :pipeline='pipeline'></pipeline-url> + <pipeline-url :pipeline="pipeline"></pipeline-url> <td> <commit-component @@ -197,14 +215,20 @@ </td> <td class="stage-cell"> - <div class="stage-container dropdown js-mini-pipeline-graph" v-for='stage in pipeline.details.stages'> - <vue-stage :stage='stage' :svgs='svgs' :match='match'></vue-stage> + <div class="stage-container dropdown js-mini-pipeline-graph" + v-if="pipeline.details.stages.length > 0" + v-for="stage in pipeline.details.stages"> + <dropdown-stage + :stage="stage" + :svgs="svgs" + :match="match"> + </dropdown-stage> </div> </td> - <time-ago :pipeline='pipeline' :svgs='svgs'></time-ago> + <time-ago :pipeline="pipeline" :svgs="svgs"></time-ago> - <pipeline-actions :pipeline='pipeline' :svgs='svgs'></pipeline-actions> + <pipeline-actions :pipeline="pipeline" :svgs="svgs"></pipeline-actions> </tr> `, }); diff --git a/app/assets/javascripts/vue_pagination/index.js.es6 b/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 similarity index 100% rename from app/assets/javascripts/vue_pagination/index.js.es6 rename to app/assets/javascripts/vue_shared/components/table_pagination.js.es6 diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 index 54c2b4ad369..d627fa2b88a 100644 --- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 +++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 @@ -1,10 +1,15 @@ -/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars */ +/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars, +no-param-reassign, no-plusplus */ /* global Vue */ Vue.http.interceptors.push((request, next) => { Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1; - next(function (response) { - Vue.activeResources -= 1; + next((response) => { + if (typeof response.data === 'string') { + response.data = JSON.parse(response.data); + } + + Vue.activeResources--; }); }); diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index b5a7078a3a1..f880a9862c6 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -37,7 +37,6 @@ class Projects::CommitController < Projects::ApplicationController format.json do render json: PipelineSerializer .new(project: @project, user: @current_user) - .with_pagination(request, response) .represent(@pipelines) end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index deb084c2e91..68f6208c2be 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -218,7 +218,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController format.json do render json: PipelineSerializer .new(project: @project, user: @current_user) - .with_pagination(request, response) .represent(@pipelines) end end diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index c1f48837e0e..e00ae629e4b 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -44,9 +44,9 @@ = render "projects/merge_requests/show/commits" #diffs.diffs.tab-pane -# This tab is always loaded via AJAX - #pipelines.pipelines.tab-pane - //TODO: This needs to make a new request every time is opened! - = render "projects/merge_requests/show/pipelines", endpoint: link_to url_for(params) + - if @pipelines.any? + #pipelines.pipelines.tab-pane + = render "projects/merge_requests/show/pipelines", endpoint: link_to url_for(params) .mr-loading-status = spinner diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 8dfe967a937..f131836058b 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -94,7 +94,6 @@ #commits.commits.tab-pane -# This tab is always loaded via AJAX #pipelines.pipelines.tab-pane - //TODO: This needs to make a new request every time is opened! = render 'projects/commit/pipelines_list', endpoint: pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) #diffs.diffs.tab-pane -# This tab is always loaded via AJAX diff --git a/spec/javascripts/commit/pipelines/mock_data.js.es6 b/spec/javascripts/commit/pipelines/mock_data.js.es6 new file mode 100644 index 00000000000..5f0f26a013c --- /dev/null +++ b/spec/javascripts/commit/pipelines/mock_data.js.es6 @@ -0,0 +1,90 @@ +/* eslint-disable no-unused-vars */ +const pipeline = { + id: 73, + user: { + name: 'Administrator', + username: 'root', + id: 1, + state: 'active', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + web_url: 'http://localhost:3000/root', + }, + path: '/root/review-app/pipelines/73', + details: { + status: { + icon: 'icon_status_failed', + text: 'failed', + label: 'failed', + group: 'failed', + has_details: true, + details_path: '/root/review-app/pipelines/73', + }, + duration: null, + finished_at: '2017-01-25T00:00:17.130Z', + stages: [{ + name: 'build', + title: 'build: failed', + status: { + icon: 'icon_status_failed', + text: 'failed', + label: 'failed', + group: 'failed', + has_details: true, + details_path: '/root/review-app/pipelines/73#build', + }, + path: '/root/review-app/pipelines/73#build', + dropdown_path: '/root/review-app/pipelines/73/stage.json?stage=build', + }], + artifacts: [], + manual_actions: [ + { + name: 'stop_review', + path: '/root/review-app/builds/1463/play', + }, + { + name: 'name', + path: '/root/review-app/builds/1490/play', + }, + ], + }, + flags: { + latest: true, + triggered: false, + stuck: false, + yaml_errors: false, + retryable: true, + cancelable: false, + }, + ref: + { + name: 'master', + path: '/root/review-app/tree/master', + tag: false, + branch: true, + }, + commit: { + id: 'fbd79f04fa98717641deaaeb092a4d417237c2e4', + short_id: 'fbd79f04', + title: 'Update .gitlab-ci.yml', + author_name: 'Administrator', + author_email: 'admin@example.com', + created_at: '2017-01-16T12:13:57.000-05:00', + committer_name: 'Administrator', + committer_email: 'admin@example.com', + message: 'Update .gitlab-ci.yml', + author: { + name: 'Administrator', + username: 'root', + id: 1, + state: 'active', + avatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + web_url: 'http://localhost:3000/root', + }, + author_gravatar_url: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', + commit_url: 'http://localhost:3000/root/review-app/commit/fbd79f04fa98717641deaaeb092a4d417237c2e4', + commit_path: '/root/review-app/commit/fbd79f04fa98717641deaaeb092a4d417237c2e4', + }, + retry_path: '/root/review-app/pipelines/73/retry', + created_at: '2017-01-16T17:13:59.800Z', + updated_at: '2017-01-25T00:00:17.132Z', +}; diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js.es6 b/spec/javascripts/commit/pipelines/pipelines_spec.js.es6 new file mode 100644 index 00000000000..3bcc0d1eb18 --- /dev/null +++ b/spec/javascripts/commit/pipelines/pipelines_spec.js.es6 @@ -0,0 +1,107 @@ +/* global pipeline, Vue */ + +//= require vue +//= require vue-resource +//= require flash +//= require commit/pipelines/pipelines_store +//= require commit/pipelines/pipelines_service +//= require commit/pipelines/pipelines_table +//= require vue_shared/vue_resource_interceptor +//= require ./mock_data + +describe('Pipelines table in Commits and Merge requests', () => { + preloadFixtures('pipelines_table'); + + beforeEach(() => { + loadFixtures('pipelines_table'); + }); + + describe('successfull request', () => { + describe('without pipelines', () => { + const pipelinesEmptyResponse = (request, next) => { + next(request.respondWith(JSON.stringify([]), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(pipelinesEmptyResponse); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, pipelinesEmptyResponse, + ); + }); + + it('should render the empty state', (done) => { + const component = new gl.commits.pipelines.PipelinesTableView({ + el: document.querySelector('#commit-pipeline-table-view'), + }); + + setTimeout(() => { + expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain('No pipelines to show'); + done(); + }, 1); + }); + }); + + describe('with pipelines', () => { + const pipelinesResponse = (request, next) => { + next(request.respondWith(JSON.stringify([pipeline]), { + status: 200, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(pipelinesResponse); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, pipelinesResponse, + ); + }); + + it('should render a table with the received pipelines', (done) => { + const component = new gl.commits.pipelines.PipelinesTableView({ + el: document.querySelector('#commit-pipeline-table-view'), + }); + + setTimeout(() => { + expect(component.$el.querySelectorAll('table > tbody > tr').length).toEqual(1); + done(); + }, 0); + }); + }); + }); + + describe('unsuccessfull request', () => { + const pipelinesErrorResponse = (request, next) => { + next(request.respondWith(JSON.stringify([]), { + status: 500, + })); + }; + + beforeEach(() => { + Vue.http.interceptors.push(pipelinesErrorResponse); + }); + + afterEach(() => { + Vue.http.interceptors = _.without( + Vue.http.interceptors, pipelinesErrorResponse, + ); + }); + + it('should render empty state', (done) => { + const component = new gl.commits.pipelines.PipelinesTableView({ + el: document.querySelector('#commit-pipeline-table-view'), + }); + + setTimeout(() => { + expect(component.$el.querySelector('.js-blank-state-title').textContent).toContain('No pipelines to show'); + done(); + }, 0); + }); + }); +}); diff --git a/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6 b/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6 new file mode 100644 index 00000000000..46a7df3bb21 --- /dev/null +++ b/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6 @@ -0,0 +1,31 @@ +//= require vue +//= require commit/pipelines/pipelines_store + +describe('Store', () => { + const store = gl.commits.pipelines.PipelinesStore; + + beforeEach(() => { + store.create(); + }); + + it('should start with a blank state', () => { + expect(store.state.pipelines.length).toBe(0); + }); + + it('should store an array of pipelines', () => { + const pipelines = [ + { + id: '1', + name: 'pipeline', + }, + { + id: '2', + name: 'pipeline_2', + }, + ]; + + store.store(pipelines); + + expect(store.state.pipelines.length).toBe(pipelines.length); + }); +}); diff --git a/spec/javascripts/fixtures/pipelines_table.html.haml b/spec/javascripts/fixtures/pipelines_table.html.haml new file mode 100644 index 00000000000..fbe4a434f76 --- /dev/null +++ b/spec/javascripts/fixtures/pipelines_table.html.haml @@ -0,0 +1,2 @@ +#commit-pipeline-table-view{ data: { endpoint: "endpoint" } } +.pipeline-svgs{ data: { "commit_icon_svg": "svg"} } diff --git a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es6 b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es6 new file mode 100644 index 00000000000..6825de069e4 --- /dev/null +++ b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es6 @@ -0,0 +1,90 @@ +/* global pipeline */ + +//= require vue +//= require vue_shared/components/pipelines_table_row +//= require commit/pipelines/mock_data + +describe('Pipelines Table Row', () => { + let component; + preloadFixtures('static/environments/element.html.raw'); + + beforeEach(() => { + loadFixtures('static/environments/element.html.raw'); + + component = new gl.pipelines.PipelinesTableRowComponent({ + el: document.querySelector('.test-dom-element'), + propsData: { + pipeline, + svgs: {}, + }, + }); + }); + + it('should render a table row', () => { + expect(component.$el).toEqual('TR'); + }); + + describe('status column', () => { + it('should render a pipeline link', () => { + expect( + component.$el.querySelector('td.commit-link a').getAttribute('href'), + ).toEqual(pipeline.path); + }); + + it('should render status text', () => { + expect( + component.$el.querySelector('td.commit-link a').textContent, + ).toContain(pipeline.details.status.text); + }); + }); + + describe('information column', () => { + it('should render a pipeline link', () => { + expect( + component.$el.querySelector('td:nth-child(2) a').getAttribute('href'), + ).toEqual(pipeline.path); + }); + + it('should render pipeline ID', () => { + expect( + component.$el.querySelector('td:nth-child(2) a > span').textContent, + ).toEqual(`#${pipeline.id}`); + }); + + describe('when a user is provided', () => { + it('should render user information', () => { + expect( + component.$el.querySelector('td:nth-child(2) a:nth-child(3)').getAttribute('href'), + ).toEqual(pipeline.user.web_url); + + expect( + component.$el.querySelector('td:nth-child(2) img').getAttribute('title'), + ).toEqual(pipeline.user.name); + }); + }); + }); + + describe('commit column', () => { + it('should render link to commit', () => { + expect( + component.$el.querySelector('td:nth-child(3) .commit-id').getAttribute('href'), + ).toEqual(pipeline.commit.commit_path); + }); + }); + + describe('stages column', () => { + it('should render an icon for each stage', () => { + expect( + component.$el.querySelectorAll('td:nth-child(4) .js-builds-dropdown-button').length, + ).toEqual(pipeline.details.stages.length); + }); + }); + + describe('actions column', () => { + it('should render the provided actions', () => { + expect( + component.$el.querySelectorAll('td:nth-child(6) ul li').length, + ).toEqual(pipeline.details.manual_actions.length); + }); + }); +}); diff --git a/spec/javascripts/vue_shared/components/pipelines_table_spec.js.es6 b/spec/javascripts/vue_shared/components/pipelines_table_spec.js.es6 new file mode 100644 index 00000000000..cb1006d44dc --- /dev/null +++ b/spec/javascripts/vue_shared/components/pipelines_table_spec.js.es6 @@ -0,0 +1,67 @@ +/* global pipeline */ + +//= require vue +//= require vue_shared/components/pipelines_table +//= require commit/pipelines/mock_data +//= require lib/utils/datetime_utility + +describe('Pipelines Table', () => { + preloadFixtures('static/environments/element.html.raw'); + + beforeEach(() => { + loadFixtures('static/environments/element.html.raw'); + }); + + describe('table', () => { + let component; + beforeEach(() => { + component = new gl.pipelines.PipelinesTableComponent({ + el: document.querySelector('.test-dom-element'), + propsData: { + pipelines: [], + svgs: {}, + }, + }); + }); + + it('should render a table', () => { + expect(component.$el).toEqual('TABLE'); + }); + + it('should render table head with correct columns', () => { + expect(component.$el.querySelector('th.js-pipeline-status').textContent).toEqual('Status'); + expect(component.$el.querySelector('th.js-pipeline-info').textContent).toEqual('Pipeline'); + expect(component.$el.querySelector('th.js-pipeline-commit').textContent).toEqual('Commit'); + expect(component.$el.querySelector('th.js-pipeline-stages').textContent).toEqual('Stages'); + expect(component.$el.querySelector('th.js-pipeline-date').textContent).toEqual(''); + expect(component.$el.querySelector('th.js-pipeline-actions').textContent).toEqual(''); + }); + }); + + describe('without data', () => { + it('should render an empty table', () => { + const component = new gl.pipelines.PipelinesTableComponent({ + el: document.querySelector('.test-dom-element'), + propsData: { + pipelines: [], + svgs: {}, + }, + }); + expect(component.$el.querySelectorAll('tbody tr').length).toEqual(0); + }); + }); + + describe('with data', () => { + it('should render rows', () => { + const component = new gl.pipelines.PipelinesTableComponent({ + el: document.querySelector('.test-dom-element'), + propsData: { + pipelines: [pipeline], + svgs: {}, + }, + }); + + expect(component.$el.querySelectorAll('tbody tr').length).toEqual(1); + }); + }); +}); diff --git a/spec/javascripts/vue_pagination/pagination_spec.js.es6 b/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6 similarity index 98% rename from spec/javascripts/vue_pagination/pagination_spec.js.es6 rename to spec/javascripts/vue_shared/components/table_pagination_spec.js.es6 index efb11211ce2..6a0fec43d2e 100644 --- a/spec/javascripts/vue_pagination/pagination_spec.js.es6 +++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6 @@ -1,6 +1,7 @@ //= require vue //= require lib/utils/common_utils -//= require vue_pagination/index +//= require vue_shared/components/table_pagination +/* global fixture, gl */ describe('Pagination component', () => { let component; -- GitLab