diff options
27 files changed, 544 insertions, 123 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index f279a57105c..e075de055e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 8.17.2 (2017-03-01) + +- Expire all webpack assets after 8.17.1 included a badly compiled asset. !9602 + ## 8.17.1 (2017-02-28) - Replace setInterval with setTimeout to prevent highly frequent requests. !9271 (Takuya Noguchi) diff --git a/app/assets/javascripts/boards/components/board_card.js b/app/assets/javascripts/boards/components/board_card.js index 52f61d84517..795b3cf2ec0 100644 --- a/app/assets/javascripts/boards/components/board_card.js +++ b/app/assets/javascripts/boards/components/board_card.js @@ -3,7 +3,7 @@ require('./issue_card_inner'); const Store = gl.issueBoards.BoardsStore; -module.exports = { +export default { name: 'BoardsIssueCard', template: ` <li class="card" diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6 index d92047cc0f8..2d52e96e7fb 100644 --- a/app/assets/javascripts/boards/components/board_list.js.es6 +++ b/app/assets/javascripts/boards/components/board_list.js.es6 @@ -2,8 +2,8 @@ /* global Vue */ /* global Sortable */ -const boardCard = require('./board_card'); -require('./board_new_issue'); +import boardNewIssue from './board_new_issue'; +import boardCard from './board_card'; (() => { const Store = gl.issueBoards.BoardsStore; @@ -15,7 +15,7 @@ require('./board_new_issue'); template: '#js-board-list-template', components: { boardCard, - 'board-new-issue': gl.issueBoards.BoardNewIssue + boardNewIssue, }, props: { disabled: Boolean, @@ -81,6 +81,12 @@ require('./board_new_issue'); }); } }, + toggleForm() { + this.showIssueForm = !this.showIssueForm; + }, + }, + created() { + gl.IssueBoardsApp.$on(`hide-issue-form-${this.list.id}`, this.toggleForm); }, mounted () { const options = gl.issueBoards.getBoardSortableDefaultOptions({ @@ -115,6 +121,9 @@ require('./board_new_issue'); this.loadNextPage(); } }; - } + }, + beforeDestroy() { + gl.IssueBoardsApp.$off(`hide-issue-form-${this.list.id}`, this.toggleForm); + }, }); })(); diff --git a/app/assets/javascripts/boards/components/board_new_issue.js b/app/assets/javascripts/boards/components/board_new_issue.js new file mode 100644 index 00000000000..b88f59dd6d4 --- /dev/null +++ b/app/assets/javascripts/boards/components/board_new_issue.js @@ -0,0 +1,92 @@ +/* global ListIssue */ +const Store = gl.issueBoards.BoardsStore; + +export default { + name: 'BoardNewIssue', + props: { + list: Object, + }, + data() { + return { + title: '', + error: false, + }; + }, + methods: { + submit(e) { + e.preventDefault(); + if (this.title.trim() === '') return; + + this.error = false; + + const labels = this.list.label ? [this.list.label] : []; + const issue = new ListIssue({ + title: this.title, + labels, + subscribed: true, + }); + + this.list.newIssue(issue) + .then(() => { + // Need this because our jQuery very kindly disables buttons on ALL form submissions + $(this.$refs.submitButton).enable(); + + Store.detail.issue = issue; + Store.detail.list = this.list; + }) + .catch(() => { + // Need this because our jQuery very kindly disables buttons on ALL form submissions + $(this.$refs.submitButton).enable(); + + // Remove the issue + this.list.removeIssue(issue); + + // Show error message + this.error = true; + }); + + this.cancel(); + }, + cancel() { + this.title = ''; + gl.IssueBoardsApp.$emit(`hide-issue-form-${this.list.id}`); + }, + }, + mounted() { + this.$refs.input.focus(); + }, + template: ` + <div class="card board-new-issue-form"> + <form @submit="submit($event)"> + <div class="flash-container" + v-if="error"> + <div class="flash-alert"> + An error occured. Please try again. + </div> + </div> + <label class="label-light" + :for="list.id + '-title'"> + Title + </label> + <input class="form-control" + type="text" + v-model="title" + ref="input" + :id="list.id + '-title'" /> + <div class="clearfix prepend-top-10"> + <button class="btn btn-success pull-left" + type="submit" + :disabled="title === ''" + ref="submit-button"> + Submit issue + </button> + <button class="btn btn-default pull-right" + type="button" + @click="cancel"> + Cancel + </button> + </div> + </form> + </div> + `, +}; diff --git a/app/assets/javascripts/boards/components/board_new_issue.js.es6 b/app/assets/javascripts/boards/components/board_new_issue.js.es6 deleted file mode 100644 index b5c14a198ba..00000000000 --- a/app/assets/javascripts/boards/components/board_new_issue.js.es6 +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-disable comma-dangle, no-unused-vars */ -/* global Vue */ -/* global ListIssue */ - -(() => { - const Store = gl.issueBoards.BoardsStore; - - window.gl = window.gl || {}; - - gl.issueBoards.BoardNewIssue = Vue.extend({ - props: { - list: Object, - }, - data() { - return { - title: '', - error: false - }; - }, - methods: { - submit(e) { - e.preventDefault(); - if (this.title.trim() === '') return; - - this.error = false; - - const labels = this.list.label ? [this.list.label] : []; - const issue = new ListIssue({ - title: this.title, - labels, - subscribed: true - }); - - this.list.newIssue(issue) - .then((data) => { - // Need this because our jQuery very kindly disables buttons on ALL form submissions - $(this.$refs.submitButton).enable(); - - Store.detail.issue = issue; - Store.detail.list = this.list; - }) - .catch(() => { - // Need this because our jQuery very kindly disables buttons on ALL form submissions - $(this.$refs.submitButton).enable(); - - // Remove the issue - this.list.removeIssue(issue); - - // Show error message - this.error = true; - }); - - this.cancel(); - }, - cancel() { - this.title = ''; - this.$parent.showIssueForm = false; - } - }, - mounted() { - this.$refs.input.focus(); - }, - }); -})(); diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index fb8ea18d122..9a0f7a14e57 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -1,6 +1,7 @@ .calender-block { padding-left: 0; padding-right: 0; + border-top: 0; direction: rtl; @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 9c76e58dfc8..09951fe3d3e 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -21,6 +21,7 @@ $dark-highlight-color: $black; $dark-pre-hll-bg: #373b41; $dark-hll-bg: #373b41; $dark-over-bg: #9f9ab5; +$dark-expanded-bg: #3e3e3e; $dark-c: #969896; $dark-err: #c66; $dark-k: #b294bb; @@ -155,6 +156,22 @@ $dark-il: #de935f; .line_content.match { @include dark-diff-match-line; } + + &:not(.diff-expanded) + .diff-expanded, + &.diff-expanded + .line_holder:not(.diff-expanded) { + > .diff-line-num, + > .line_content { + border-top: 1px solid $black; + } + } + + &.diff-expanded { + > .diff-line-num, + > .line_content { + background: $dark-expanded-bg; + border-color: $dark-expanded-bg; + } + } } // highlight line via anchor diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index 6488a099c74..b6a6d298adf 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -14,6 +14,7 @@ $monokai-line-empty-border: darken($monokai-line-empty-bg, 15%); $monokai-diff-border: #808080; $monokai-highlight-bg: #ffe792; $monokai-over-bg: #9f9ab5; +$monokai-expanded-bg: #3e3e3e; $monokai-new-bg: rgba(166, 226, 46, 0.1); $monokai-new-idiff: rgba(166, 226, 46, 0.15); @@ -155,6 +156,22 @@ $monokai-gi: #a6e22e; .line_content.match { @include dark-diff-match-line; } + + &:not(.diff-expanded) + .diff-expanded, + &.diff-expanded + .line_holder:not(.diff-expanded) { + > .diff-line-num, + > .line_content { + border-top: 1px solid $black; + } + } + + &.diff-expanded { + > .diff-line-num, + > .line_content { + background: $monokai-expanded-bg; + border-color: $monokai-expanded-bg; + } + } } // highlight line via anchor diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index 00079cc2b5b..4f7a50dcb4f 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -18,6 +18,7 @@ $solarized-dark-line-color-old: #7a6c71; $solarized-dark-highlight: #094554; $solarized-dark-hll-bg: #174652; $solarized-dark-over-bg: #9f9ab5; +$solarized-dark-expanded-bg: #010d10; $solarized-dark-c: #586e75; $solarized-dark-err: #93a1a1; $solarized-dark-g: #93a1a1; @@ -159,6 +160,22 @@ $solarized-dark-il: #2aa198; .line_content.match { @include dark-diff-match-line; } + + &:not(.diff-expanded) + .diff-expanded, + &.diff-expanded + .line_holder:not(.diff-expanded) { + > .diff-line-num, + > .line_content { + border-top: 1px solid $black; + } + } + + &.diff-expanded { + > .diff-line-num, + > .line_content { + background: $solarized-dark-expanded-bg; + border-color: $solarized-dark-expanded-bg; + } + } } // highlight line via anchor diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index 2e3b68f1a58..6463fe96c1b 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -19,6 +19,8 @@ $solarized-light-line-color-old: #ad9186; $solarized-light-highlight: #eee8d5; $solarized-light-hll-bg: #ddd8c5; $solarized-light-over-bg: #ded7fc; +$solarized-light-expanded-border: #d2cdbd; +$solarized-light-expanded-bg: #ece6d4; $solarized-light-c: #93a1a1; $solarized-light-err: #586e75; $solarized-light-g: #586e75; @@ -166,6 +168,22 @@ $solarized-light-il: #2aa198; .line_content.match { @include matchLine; } + + &:not(.diff-expanded) + .diff-expanded, + &.diff-expanded + .line_holder:not(.diff-expanded) { + > .diff-line-num, + > .line_content { + border-top: 1px solid $solarized-light-expanded-border; + } + } + + &.diff-expanded { + > .diff-line-num, + > .line_content { + background: $solarized-light-expanded-bg; + border-color: $solarized-light-expanded-bg; + } + } } // highlight line via anchor diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index 0eca30e570f..ab2018bfbca 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -8,6 +8,8 @@ $white-highlight: #fafe3d; $white-pre-hll-bg: #f8eec7; $white-hll-bg: #f8f8f8; $white-over-bg: #ded7fc; +$white-expanded-border: #e0e0e0; +$white-expanded-bg: #f7f7f7; $white-c: #998; $white-err: #a61717; $white-err-bg: #e3d2d2; @@ -140,6 +142,22 @@ $white-gc-bg: #eaf2f5; } } + &:not(.diff-expanded) + .diff-expanded, + &.diff-expanded + .line_holder:not(.diff-expanded) { + > .diff-line-num, + > .line_content { + border-top: 1px solid $white-expanded-border; + } + } + + &.diff-expanded { + > .diff-line-num, + > .line_content { + background: $white-expanded-bg; + border-color: $white-expanded-bg; + } + } + .line_content { &.old { background-color: $line-removed; diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 339cdcde480..5d0c247dea8 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -133,8 +133,13 @@ width: 35px; font-weight: normal; - &:hover { - text-decoration: underline; + &[disabled] { + cursor: default; + + &:hover, + &:active { + text-decoration: none; + } } } } diff --git a/app/models/user.rb b/app/models/user.rb index 6fb5ac4a4ef..8443594c055 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -346,7 +346,11 @@ class User < ActiveRecord::Base # Return (create if necessary) the ghost user. The ghost user # owns records previously belonging to deleted users. def ghost - User.find_by_ghost(true) || create_ghost_user + unique_internal(where(ghost: true), 'ghost', 'ghost%s@example.com') do |u| + u.bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.' + u.state = :blocked + u.name = 'Ghost User' + end end end @@ -1017,10 +1021,14 @@ class User < ActiveRecord::Base end end - def self.create_ghost_user - # Since we only want a single ghost user in an instance, we use an + def self.unique_internal(scope, username, email_pattern, &b) + scope.first || create_unique_internal(scope, username, email_pattern, &b) + end + + def self.create_unique_internal(scope, username, email_pattern, &creation_block) + # Since we only want a single one of these in an instance, we use an # exclusive lease to ensure than this block is never run concurrently. - lease_key = "ghost_user_creation" + lease_key = "user:unique_internal:#{username}" lease = Gitlab::ExclusiveLease.new(lease_key, timeout: 1.minute.to_i) until uuid = lease.try_obtain @@ -1029,25 +1037,25 @@ class User < ActiveRecord::Base sleep(1) end - # Recheck if a ghost user is already present. One might have been + # Recheck if the user is already present. One might have been # added between the time we last checked (first line of this method) # and the time we acquired the lock. - ghost_user = User.find_by_ghost(true) - return ghost_user if ghost_user.present? + existing_user = uncached { scope.first } + return existing_user if existing_user.present? uniquify = Uniquify.new - username = uniquify.string("ghost") { |s| User.find_by_username(s) } + username = uniquify.string(username) { |s| User.find_by_username(s) } - email = uniquify.string(-> (n) { "ghost#{n}@example.com" }) do |s| + email = uniquify.string(-> (n) { Kernel.sprintf(email_pattern, n) }) do |s| User.find_by_email(s) end - bio = 'This is a "Ghost User", created to hold all issues authored by users that have since been deleted. This user cannot be removed.' - - User.create( - username: username, password: Devise.friendly_token, bio: bio, - email: email, name: "Ghost User", state: :blocked, ghost: true + scope.create( + username: username, + password: Devise.friendly_token, + email: email, + &creation_block ) ensure Gitlab::ExclusiveLease.cancel(lease_key, uuid) diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index c48d9dd982c..d1d448f0d4c 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -9,20 +9,20 @@ - line_old = line_new - @form.offset - line_content = capture do %td.line_content.noteable_line{ class: line_class }==#{' ' * @form.indent}#{line} - %tr.line_holder{ id: line_old, class: line_class } + %tr.line_holder.diff-expanded{ id: line_old, class: line_class } - case diff_view - when :inline %td.old_line.diff-line-num{ data: { linenumber: line_old } } - %a{ href: "##{line_old}", data: { linenumber: line_old } } + %a{ href: "#", data: { linenumber: line_old }, disabled: true } %td.new_line.diff-line-num{ data: { linenumber: line_new } } - %a{ href: "##{line_new}", data: { linenumber: line_new } } + %a{ href: "#", data: { linenumber: line_new }, disabled: true } = line_content - when :parallel %td.old_line.diff-line-num{ data: { linenumber: line_old } } - %a{ href: "##{line_old}", data: { linenumber: line_old } } + %a{ href: "##{line_old}", data: { linenumber: line_old }, disabled: true } = line_content %td.new_line.diff-line-num{ data: { linenumber: line_new } } - %a{ href: "##{line_new}", data: { linenumber: line_new } } + %a{ href: "##{line_new}", data: { linenumber: line_new }, disabled: true } = line_content - if @form.unfold? && @form.bottom? && @form.to < @blob.lines.size diff --git a/app/views/projects/boards/components/_board_list.html.haml b/app/views/projects/boards/components/_board_list.html.haml index f413a5e94c1..0993e880da9 100644 --- a/app/views/projects/boards/components/_board_list.html.haml +++ b/app/views/projects/boards/components/_board_list.html.haml @@ -2,28 +2,8 @@ .board-list-loading.text-center{ "v-if" => "loading" } = icon("spinner spin") - if can? current_user, :create_issue, @project - %board-new-issue{ "inline-template" => true, - ":list" => "list", + %board-new-issue{ ":list" => "list", "v-if" => 'list.type !== "done" && showIssueForm' } - .card.board-new-issue-form - %form{ "@submit" => "submit($event)" } - .flash-container{ "v-if" => "error" } - .flash-alert - An error occured. Please try again. - %label.label-light{ ":for" => 'list.id + "-title"' } - Title - %input.form-control{ type: "text", - "v-model" => "title", - "ref" => "input", - ":id" => 'list.id + "-title"' } - .clearfix.prepend-top-10 - %button.btn.btn-success.pull-left{ type: "submit", - ":disabled" => 'title === ""', - "ref" => "submit-button" } - Submit issue - %button.btn.btn-default.pull-right{ type: "button", - "@click" => "cancel" } - Cancel %ul.board-list{ "ref" => "list", "v-show" => "!loading", ":data-board" => "list.id", diff --git a/changelogs/unreleased/28850-fix-broken-migration.yml b/changelogs/unreleased/28850-fix-broken-migration.yml new file mode 100644 index 00000000000..7f59a7708bc --- /dev/null +++ b/changelogs/unreleased/28850-fix-broken-migration.yml @@ -0,0 +1,4 @@ +--- +title: Fix broken migration when upgrading straight to 8.17.1 +merge_request: 9613 +author: diff --git a/changelogs/unreleased/diff-make-obvious-cant-comment.yml b/changelogs/unreleased/diff-make-obvious-cant-comment.yml new file mode 100644 index 00000000000..2cb95947939 --- /dev/null +++ b/changelogs/unreleased/diff-make-obvious-cant-comment.yml @@ -0,0 +1,4 @@ +--- +title: Visually show expanded diff lines cant have comments +merge_request: +author: diff --git a/changelogs/unreleased/user-calendar-border.yml b/changelogs/unreleased/user-calendar-border.yml new file mode 100644 index 00000000000..8ebcca83256 --- /dev/null +++ b/changelogs/unreleased/user-calendar-border.yml @@ -0,0 +1,4 @@ +--- +title: Removed top border from user contribution calendar +merge_request: +author: diff --git a/db/post_migrate/20170211073944_disable_invalid_service_templates.rb b/db/post_migrate/20170211073944_disable_invalid_service_templates.rb index 84954b1ef64..603efc43782 100644 --- a/db/post_migrate/20170211073944_disable_invalid_service_templates.rb +++ b/db/post_migrate/20170211073944_disable_invalid_service_templates.rb @@ -1,10 +1,8 @@ class DisableInvalidServiceTemplates < ActiveRecord::Migration DOWNTIME = false - unless defined?(Service) - class Service < ActiveRecord::Base - self.inheritance_column = nil - end + class Service < ActiveRecord::Base + self.inheritance_column = nil end def up diff --git a/doc/api/tags.md b/doc/api/tags.md index abeb4bfb40e..4a2c9720b8a 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -26,7 +26,7 @@ Parameters: "committer_email": "jack@example.com", "id": "2695effb5807a22ff3d138d593fd856244e155e7", "message": "Initial commit", - "parents_ids": [ + "parent_ids": [ "2a4b78934375d7f53875269ffd4f45fd83a84ebe" ] }, @@ -110,7 +110,7 @@ Parameters: "committer_email": "jack@example.com", "id": "2695effb5807a22ff3d138d593fd856244e155e7", "message": "Initial commit", - "parents_ids": [ + "parent_ids": [ "2a4b78934375d7f53875269ffd4f45fd83a84ebe" ] }, diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 620d4744685..04c0af44237 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -131,6 +131,16 @@ job_name: variables: [] ``` +You are able to use other variables inside your variable definition (or escape them with `$$`): + +```yaml +variables: + LS_CMD: 'ls $FLAGS $$TMP_DIR' + FLAGS: '-al' +script: + - 'eval $LS_CMD' # will execute 'ls -al $TMP_DIR' +``` + ## Secret variables >**Notes:** diff --git a/doc/gitlab-basics/img/create_new_project_button.png b/doc/gitlab-basics/img/create_new_project_button.png Binary files differindex a19f0e57b56..8d7a69e55ed 100644 --- a/doc/gitlab-basics/img/create_new_project_button.png +++ b/doc/gitlab-basics/img/create_new_project_button.png diff --git a/doc/install/installation.md b/doc/install/installation.md index 5ba338ba7d1..bb4141c6cd3 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -155,10 +155,9 @@ page](https://golang.org/dl). ## 4. Node Since GitLab 8.17, GitLab requires the use of node >= v4.3.0 to compile -javascript assets, and starting in GitLab 9.0, yarn >= v0.17.0 is required to -manage javascript dependencies. In many distros the versions provided by the -official package repositories are out of date, so we'll need to install through -the following commands: +javascript assets, and yarn >= v0.17.0 to manage javascript dependencies. +In many distros the versions provided by the official package repositories +are out of date, so we'll need to install through the following commands: # install node v7.x curl --location https://deb.nodesource.com/setup_7.x | bash - diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index 101b1b80c1e..9c384069661 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -1,7 +1,7 @@ module Gitlab module GonHelper def add_gon_variables - gon.api_version = API::API.version + gon.api_version = 'v3' # v4 Is not officially released yet, therefore can't be considered as "frozen" gon.default_avatar_url = URI.join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s gon.max_file_size = current_application_settings.max_attachment_size gon.relative_url_root = Gitlab.config.gitlab.relative_url_root diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js index 192916fbc6a..be31f644e20 100644 --- a/spec/javascripts/boards/board_card_spec.js +++ b/spec/javascripts/boards/board_card_spec.js @@ -8,7 +8,7 @@ require('~/boards/models/list'); require('~/boards/models/label'); require('~/boards/stores/boards_store'); -const boardCard = require('~/boards/components/board_card'); +const boardCard = require('~/boards/components/board_card').default; require('./mock_data'); describe('Issue card', () => { diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js new file mode 100644 index 00000000000..22c9f12951b --- /dev/null +++ b/spec/javascripts/boards/board_new_issue_spec.js @@ -0,0 +1,191 @@ +/* global boardsMockInterceptor */ +/* global BoardService */ +/* global List */ +/* global listObj */ + +import Vue from 'vue'; +import boardNewIssue from '~/boards/components/board_new_issue'; + +require('~/boards/models/list'); +require('./mock_data'); +require('es6-promise').polyfill(); + +describe('Issue boards new issue form', () => { + let vm; + let list; + const promiseReturn = { + json() { + return { + iid: 100, + }; + }, + }; + const submitIssue = () => { + vm.$el.querySelector('.btn-success').click(); + }; + + beforeEach((done) => { + const BoardNewIssueComp = Vue.extend(boardNewIssue); + + Vue.http.interceptors.push(boardsMockInterceptor); + gl.boardService = new BoardService('/test/issue-boards/board', '', '1'); + gl.issueBoards.BoardsStore.create(); + gl.IssueBoardsApp = new Vue(); + + setTimeout(() => { + list = new List(listObj); + + spyOn(gl.boardService, 'newIssue').and.callFake(() => new Promise((resolve, reject) => { + if (vm.title === 'error') { + reject(); + } else { + resolve(promiseReturn); + } + })); + + vm = new BoardNewIssueComp({ + propsData: { + list, + }, + }).$mount(); + + done(); + }, 0); + }); + + afterEach(() => { + Vue.http.interceptors = _.without(Vue.http.interceptors, boardsMockInterceptor); + }); + + it('disables submit button if title is empty', () => { + expect(vm.$el.querySelector('.btn-success').disabled).toBe(true); + }); + + it('enables submit button if title is not empty', (done) => { + vm.title = 'Testing Title'; + + setTimeout(() => { + expect(vm.$el.querySelector('.form-control').value).toBe('Testing Title'); + expect(vm.$el.querySelector('.btn-success').disabled).not.toBe(true); + + done(); + }, 0); + }); + + it('clears title after clicking cancel', (done) => { + vm.$el.querySelector('.btn-default').click(); + + setTimeout(() => { + expect(vm.title).toBe(''); + done(); + }, 0); + }); + + it('does not create new issue if title is empty', (done) => { + submitIssue(); + + setTimeout(() => { + expect(gl.boardService.newIssue).not.toHaveBeenCalled(); + done(); + }, 0); + }); + + describe('submit success', () => { + it('creates new issue', (done) => { + vm.title = 'submit title'; + + setTimeout(() => { + submitIssue(); + + expect(gl.boardService.newIssue).toHaveBeenCalled(); + done(); + }, 0); + }); + + it('enables button after submit', (done) => { + vm.title = 'submit issue'; + + setTimeout(() => { + submitIssue(); + + expect(vm.$el.querySelector('.btn-success').disbled).not.toBe(true); + done(); + }, 0); + }); + + it('clears title after submit', (done) => { + vm.title = 'submit issue'; + + setTimeout(() => { + submitIssue(); + + expect(vm.title).toBe(''); + done(); + }, 0); + }); + + it('adds new issue to list after submit', (done) => { + vm.title = 'submit issue'; + + setTimeout(() => { + submitIssue(); + + expect(list.issues.length).toBe(2); + expect(list.issues[1].title).toBe('submit issue'); + expect(list.issues[1].subscribed).toBe(true); + done(); + }, 0); + }); + + it('sets detail issue after submit', (done) => { + vm.title = 'submit issue'; + + setTimeout(() => { + submitIssue(); + + expect(gl.issueBoards.BoardsStore.detail.issue.title).toBe('submit issue'); + done(); + }); + }); + + it('sets detail list after submit', (done) => { + vm.title = 'submit issue'; + + setTimeout(() => { + submitIssue(); + + expect(gl.issueBoards.BoardsStore.detail.list.id).toBe(list.id); + done(); + }, 0); + }); + }); + + describe('submit error', () => { + it('removes issue', (done) => { + vm.title = 'error'; + + setTimeout(() => { + submitIssue(); + + setTimeout(() => { + expect(list.issues.length).toBe(1); + done(); + }, 500); + }, 0); + }); + + it('shows error', (done) => { + vm.title = 'error'; + submitIssue(); + + setTimeout(() => { + submitIssue(); + + setTimeout(() => { + expect(vm.error).toBe(true); + done(); + }, 500); + }, 0); + }); + }); +}); diff --git a/spec/views/ci/status/_badge.html.haml_spec.rb b/spec/views/ci/status/_badge.html.haml_spec.rb new file mode 100644 index 00000000000..c62450fb8e2 --- /dev/null +++ b/spec/views/ci/status/_badge.html.haml_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +describe 'ci/status/_badge', :view do + let(:user) { create(:user) } + let(:project) { create(:empty_project, :private) } + let(:pipeline) { create(:ci_pipeline, project: project) } + + context 'when rendering status for build' do + let(:build) do + create(:ci_build, :success, pipeline: pipeline) + end + + context 'when user has ability to see details' do + before do + project.add_developer(user) + end + + it 'has link to build details page' do + details_path = namespace_project_build_path( + project.namespace, project, build) + + render_status(build) + + expect(rendered).to have_link 'passed', href: details_path + end + end + + context 'when user do not have ability to see build details' do + before do + render_status(build) + end + + it 'contains build status text' do + expect(rendered).to have_content 'passed' + end + + it 'does not contain links' do + expect(rendered).not_to have_link 'passed' + end + end + end + + context 'when rendering status for external job' do + context 'when user has ability to see commit status details' do + before do + project.add_developer(user) + end + + context 'status has external target url' do + before do + external_job = create(:generic_commit_status, + status: :running, + pipeline: pipeline, + target_url: 'http://gitlab.com') + + render_status(external_job) + end + + it 'contains valid commit status text' do + expect(rendered).to have_content 'running' + end + + it 'has link to external status page' do + expect(rendered).to have_link 'running', href: 'http://gitlab.com' + end + end + + context 'status do not have external target url' do + before do + external_job = create(:generic_commit_status, status: :canceled) + + render_status(external_job) + end + + it 'contains valid commit status text' do + expect(rendered).to have_content 'canceled' + end + + it 'has link to external status page' do + expect(rendered).not_to have_link 'canceled' + end + end + end + end + + def render_status(resource) + render 'ci/status/badge', status: resource.detailed_status(user) + end +end |