summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/releases/list/components/release_block.vue34
-rw-r--r--app/controllers/projects/releases_controller.rb3
-rw-r--r--app/models/concerns/issuable.rb4
-rw-r--r--changelogs/unreleased/nfriend-add-edit-button-to-release-blocks.yml5
-rw-r--r--doc/ci/multi_project_pipelines.md3
-rw-r--r--doc/ci/yaml/README.md40
-rw-r--r--doc/development/api_graphql_styleguide.md5
-rw-r--r--doc/development/issuable-like-models.md4
-rw-r--r--doc/development/rake_tasks.md23
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/frontend/releases/list/components/__snapshots__/release_block_spec.js.snap332
-rw-r--r--spec/frontend/releases/list/components/release_block_spec.js135
-rw-r--r--spec/frontend/releases/mock_data.js3
-rw-r--r--spec/models/concerns/issuable_spec.rb4
-rw-r--r--spec/support/shared_examples/models/concern/issuable_shared_examples.rb8
15 files changed, 536 insertions, 70 deletions
diff --git a/app/assets/javascripts/releases/list/components/release_block.vue b/app/assets/javascripts/releases/list/components/release_block.vue
index c9c5a6db303..8d4b32e9dc0 100644
--- a/app/assets/javascripts/releases/list/components/release_block.vue
+++ b/app/assets/javascripts/releases/list/components/release_block.vue
@@ -1,7 +1,7 @@
<script>
/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import _ from 'underscore';
-import { GlTooltipDirective, GlLink, GlBadge } from '@gitlab/ui';
+import { GlTooltipDirective, GlLink, GlBadge, GlButton } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
@@ -9,19 +9,21 @@ import { __, n__, sprintf } from '~/locale';
import { slugify } from '~/lib/utils/text_utility';
import { getLocationHash } from '~/lib/utils/url_utility';
import { scrollToElement } from '~/lib/utils/common_utils';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
name: 'ReleaseBlock',
components: {
GlLink,
GlBadge,
+ GlButton,
Icon,
UserAvatarLink,
},
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [timeagoMixin],
+ mixins: [timeagoMixin, glFeatureFlagsMixin()],
props: {
release: {
type: Object,
@@ -72,6 +74,11 @@ export default {
labelText() {
return n__('Milestone', 'Milestones', this.release.milestones.length);
},
+ shouldShowEditButton() {
+ return Boolean(
+ this.glFeatures.releaseEditPage && this.release._links && this.release._links.edit,
+ );
+ },
},
mounted() {
const hash = getLocationHash();
@@ -89,12 +96,23 @@ export default {
<template>
<div :id="id" :class="{ 'bg-line-target-blue': isHighlighted }" class="card release-block">
<div class="card-body">
- <h2 class="card-title mt-0">
- {{ release.name }}
- <gl-badge v-if="release.upcoming_release" variant="warning" class="align-middle">{{
- __('Upcoming Release')
- }}</gl-badge>
- </h2>
+ <div class="d-flex align-items-start">
+ <h2 class="card-title mt-0 mr-auto">
+ {{ release.name }}
+ <gl-badge v-if="release.upcoming_release" variant="warning" class="align-middle">{{
+ __('Upcoming Release')
+ }}</gl-badge>
+ </h2>
+ <gl-link
+ v-if="shouldShowEditButton"
+ v-gl-tooltip
+ class="btn btn-default js-edit-button ml-2"
+ :title="__('Edit this release')"
+ :href="release._links.edit"
+ >
+ <icon name="pencil" />
+ </gl-link>
+ </div>
<div class="card-subtitle d-flex flex-wrap text-secondary">
<div class="append-right-8">
diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb
index 4c39ee4045f..717df9f09e0 100644
--- a/app/controllers/projects/releases_controller.rb
+++ b/app/controllers/projects/releases_controller.rb
@@ -4,6 +4,9 @@ class Projects::ReleasesController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project
before_action :authorize_read_release!
+ before_action do
+ push_frontend_feature_flag(:release_edit_page, project)
+ end
def index
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 74cbdb29abd..94a0de556f4 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -28,8 +28,8 @@ module Issuable
TITLE_LENGTH_MAX = 255
TITLE_HTML_LENGTH_MAX = 800
- DESCRIPTION_LENGTH_MAX = 16000
- DESCRIPTION_HTML_LENGTH_MAX = 48000
+ DESCRIPTION_LENGTH_MAX = 1.megabyte
+ DESCRIPTION_HTML_LENGTH_MAX = 5.megabytes
# This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests
diff --git a/changelogs/unreleased/nfriend-add-edit-button-to-release-blocks.yml b/changelogs/unreleased/nfriend-add-edit-button-to-release-blocks.yml
new file mode 100644
index 00000000000..ac0439f9f63
--- /dev/null
+++ b/changelogs/unreleased/nfriend-add-edit-button-to-release-blocks.yml
@@ -0,0 +1,5 @@
+---
+title: Add edit button to release blocks on Releases page
+merge_request: 18411
+author:
+type: added
diff --git a/doc/ci/multi_project_pipelines.md b/doc/ci/multi_project_pipelines.md
index 762980a977c..d153e74ba37 100644
--- a/doc/ci/multi_project_pipelines.md
+++ b/doc/ci/multi_project_pipelines.md
@@ -119,7 +119,8 @@ Use:
- The `project` keyword to specify the full path to a downstream project.
- The `branch` keyword to specify the name of a branch in the project specified by `project`.
- Variable expansion is supported.
+ [From GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/issues/10126), variable expansion is
+ supported.
GitLab will use a commit that is currently on the HEAD of the branch when
creating a downstream pipeline.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 1a7c358eb91..114581e1e5d 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1086,13 +1086,51 @@ Manual actions are considered to be write actions, so permissions for
[protected branches](../../user/project/protected_branches.md) are used when
a user wants to trigger an action. In other words, in order to trigger a manual
action assigned to a branch that the pipeline is running for, the user needs to
-have the ability to merge to this branch.
+have the ability to merge to this branch. It is possible to use protected environments
+to more strictly [protect manual deployments](#protecting-manual-jobs) from being
+run by unauthorized users.
NOTE: **Note:**
Using `when:manual` and `trigger` together results in the error `jobs:#{job-name} when
should be on_success, on_failure or always`, because `when:manual` prevents triggers
being used.
+##### Protecting manual jobs
+
+It's possible to use [protected environments](../environments/protected_environments.md)
+to define a precise list of users authorized to run a manual job. By allowing only
+users associated with a protected environment to trigger manual jobs, it is possible
+to implement some special use cases, such as:
+
+- more precisely limiting who can deploy to an environment.
+- enabling a pipeline to be blocked until an approved user "approves" it.
+
+To do this, you must add an environment to the job. For example:
+
+```yaml
+deploy_prod:
+ stage: deploy
+ script:
+ - echo "Deploy to production server"
+ environment:
+ name: production
+ url: https://example.com
+ when: manual
+ only:
+ - master
+```
+
+Then, in the [protected environments settings](../environments/protected_environments.md#protecting-environments),
+select the environment (`production` in the example above) and add the users, roles or groups
+that are authorized to trigger the manual job to the **Allowed to Deploy** list. Only those in
+this list will be able to trigger this manual job, as well as GitLab admins who are always able
+to use protected environments.
+
+Additionally, if a manual job is defined as blocking by adding `allow_failure: false`,
+the next stages of the pipeline will not run until the manual job is triggered. This
+can be used as a way to have a defined list of users allowed to "approve" later pipeline
+stages by triggering the blocking manual job.
+
#### `when:delayed`
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/21767) in GitLab 11.4.
diff --git a/doc/development/api_graphql_styleguide.md b/doc/development/api_graphql_styleguide.md
index 793b1fbb2f5..cdd0e9b2a7b 100644
--- a/doc/development/api_graphql_styleguide.md
+++ b/doc/development/api_graphql_styleguide.md
@@ -539,3 +539,8 @@ it 'returns a successful response' do
expect(graphql_mutation_response(:merge_request_set_wip)['errors']).to be_empty
end
```
+
+## Documentation
+
+For information on generating GraphQL documentation, see
+[Rake tasks related to GraphQL](rake_tasks.md#update-graphql-documentation).
diff --git a/doc/development/issuable-like-models.md b/doc/development/issuable-like-models.md
index 27cac825b7f..ce19fd77496 100644
--- a/doc/development/issuable-like-models.md
+++ b/doc/development/issuable-like-models.md
@@ -11,8 +11,8 @@ There are max length constraints for the most important text fields for `Issuabl
- `title`: 255 chars
- `title_html`: 800 chars
-- `description`: 16000 chars
-- `description_html`: 48000 chars
+- `description`: 1 megabyte
+- `description_html`: 5 megabytes
[Issue]: https://docs.gitlab.com/ee/user/project/issues
[Merge Requests]: https://docs.gitlab.com/ee/user/project/merge_requests
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index 20604cce9c6..ea4acf7083a 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -220,3 +220,26 @@ bundle exec rake db:obsolete_ignored_columns
```
Feel free to remove their definitions from their `ignored_columns` definitions.
+
+## Update GraphQL Documentation
+
+To generate GraphQL documentation based on the GitLab schema, run:
+
+```shell
+bundle exec rake gitlab:graphql:compile_docs
+```
+
+In its current state, the rake task:
+
+- Generates output for GraphQL objects.
+- Places the output at `docs/api/graphql/reference/index.md`.
+
+This uses some features from `graphql-docs` gem like its schema parser and helper methods.
+The docs generator code comes from our side giving us more flexibility, like using Haml templates and generating Markdown files.
+
+To edit the template used, please take a look at `lib/gitlab/graphql/docs/templates/default.md.haml`.
+The actual renderer is at `Gitlab::Graphql::Docs::Renderer`.
+
+`@parsed_schema` is an instance variable that the `graphql-docs` gem expects to have available.
+`Gitlab::Graphql::Docs::Helper` defines the `object` method we currently use. This is also where you should implement any
+new methods for new types you'd like to display.
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 562f1fc5acc..01d4765833e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5805,6 +5805,9 @@ msgstr ""
msgid "Edit stage"
msgstr ""
+msgid "Edit this release"
+msgstr ""
+
msgid "Edit wiki page"
msgstr ""
diff --git a/spec/frontend/releases/list/components/__snapshots__/release_block_spec.js.snap b/spec/frontend/releases/list/components/__snapshots__/release_block_spec.js.snap
new file mode 100644
index 00000000000..8f2c0427c83
--- /dev/null
+++ b/spec/frontend/releases/list/components/__snapshots__/release_block_spec.js.snap
@@ -0,0 +1,332 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Release block with default props matches the snapshot 1`] = `
+<div
+ class="card release-block"
+ id="v0.3"
+>
+ <div
+ class="card-body"
+ >
+ <div
+ class="d-flex align-items-start"
+ >
+ <h2
+ class="card-title mt-0 mr-auto"
+ >
+
+ New release
+
+ <!---->
+ </h2>
+
+ <a
+ class="btn btn-default js-edit-button ml-2"
+ data-original-title="Edit this release"
+ href="http://0.0.0.0:3001/root/release-test/-/releases/v0.3/edit"
+ title=""
+ >
+ <svg
+ aria-hidden="true"
+ class="s16 ic-pencil"
+ >
+ <use
+ xlink:href="#pencil"
+ />
+ </svg>
+ </a>
+ </div>
+
+ <div
+ class="card-subtitle d-flex flex-wrap text-secondary"
+ >
+ <div
+ class="append-right-8"
+ >
+ <svg
+ aria-hidden="true"
+ class="align-middle s16 ic-commit"
+ >
+ <use
+ xlink:href="#commit"
+ />
+ </svg>
+
+ <span
+ data-original-title="Initial commit"
+ title=""
+ >
+ c22b0728
+ </span>
+ </div>
+
+ <div
+ class="append-right-8"
+ >
+ <svg
+ aria-hidden="true"
+ class="align-middle s16 ic-tag"
+ >
+ <use
+ xlink:href="#tag"
+ />
+ </svg>
+
+ <span
+ data-original-title="Tag"
+ title=""
+ >
+ v0.3
+ </span>
+ </div>
+
+ <div
+ class="js-milestone-list-label"
+ >
+ <svg
+ aria-hidden="true"
+ class="align-middle s16 ic-flag"
+ >
+ <use
+ xlink:href="#flag"
+ />
+ </svg>
+
+ <span
+ class="js-label-text"
+ >
+ Milestones
+ </span>
+ </div>
+
+ <a
+ class="append-right-4 prepend-left-4 js-milestone-link"
+ data-original-title="The 13.6 milestone!"
+ href="http://0.0.0.0:3001/root/release-test/-/milestones/2"
+ title=""
+ >
+
+ 13.6
+
+ </a>
+
+ •
+
+ <a
+ class="append-right-4 prepend-left-4 js-milestone-link"
+ data-original-title="The 13.5 milestone!"
+ href="http://0.0.0.0:3001/root/release-test/-/milestones/1"
+ title=""
+ >
+
+ 13.5
+
+ </a>
+
+ <!---->
+
+ <div
+ class="append-right-4"
+ >
+
+ •
+
+ <span
+ data-original-title="Aug 26, 2019 5:54pm GMT+0000"
+ title=""
+ >
+
+ released 1 month ago
+
+ </span>
+ </div>
+
+ <div
+ class="d-flex"
+ >
+
+ by
+
+ <a
+ class="user-avatar-link prepend-left-4"
+ href=""
+ >
+ <span>
+ <img
+ alt="root's avatar"
+ class="avatar s20 "
+ data-original-title=""
+ data-src="https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon"
+ height="20"
+ src="https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon"
+ title=""
+ width="20"
+ />
+
+ <div
+ aria-hidden="true"
+ class="js-user-avatar-image-toolip d-none"
+ style="display: none;"
+ >
+ <div>
+ root
+ </div>
+ </div>
+ </span>
+ <!---->
+ </a>
+ </div>
+ </div>
+
+ <div
+ class="card-text prepend-top-default"
+ >
+ <b>
+
+ Assets
+
+ <span
+ class="js-assets-count badge badge-pill"
+ >
+ 5
+ </span>
+ </b>
+
+ <ul
+ class="pl-0 mb-0 prepend-top-8 list-unstyled js-assets-list"
+ >
+ <li
+ class="append-bottom-8"
+ >
+ <a
+ class=""
+ data-original-title="Download asset"
+ href="https://google.com"
+ title=""
+ >
+ <svg
+ aria-hidden="true"
+ class="align-middle append-right-4 align-text-bottom s16 ic-package"
+ >
+ <use
+ xlink:href="#package"
+ />
+ </svg>
+
+ my link
+
+ <span>
+ (external source)
+ </span>
+ </a>
+ </li>
+ <li
+ class="append-bottom-8"
+ >
+ <a
+ class=""
+ data-original-title="Download asset"
+ href="https://gitlab.com/gitlab-org/gitlab-foss/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50"
+ title=""
+ >
+ <svg
+ aria-hidden="true"
+ class="align-middle append-right-4 align-text-bottom s16 ic-package"
+ >
+ <use
+ xlink:href="#package"
+ />
+ </svg>
+
+ my second link
+
+ <!---->
+ </a>
+ </li>
+ </ul>
+
+ <div
+ class="dropdown"
+ >
+ <button
+ aria-expanded="false"
+ aria-haspopup="true"
+ class="btn btn-link"
+ data-toggle="dropdown"
+ type="button"
+ >
+ <svg
+ aria-hidden="true"
+ class="align-top append-right-4 s16 ic-doc-code"
+ >
+ <use
+ xlink:href="#doc-code"
+ />
+ </svg>
+
+ Source code
+
+ <svg
+ aria-hidden="true"
+ class="s16 ic-arrow-down"
+ >
+ <use
+ xlink:href="#arrow-down"
+ />
+ </svg>
+ </button>
+
+ <div
+ class="js-sources-dropdown dropdown-menu"
+ >
+ <li>
+ <a
+ class=""
+ href="http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.zip"
+ >
+ Download zip
+ </a>
+ </li>
+ <li>
+ <a
+ class=""
+ href="http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.tar.gz"
+ >
+ Download tar.gz
+ </a>
+ </li>
+ <li>
+ <a
+ class=""
+ href="http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.tar.bz2"
+ >
+ Download tar.bz2
+ </a>
+ </li>
+ <li>
+ <a
+ class=""
+ href="http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.tar"
+ >
+ Download tar
+ </a>
+ </li>
+ </div>
+ </div>
+ </div>
+
+ <div
+ class="card-text prepend-top-default"
+ >
+ <div>
+ <p
+ data-sourcepos="1:1-1:21"
+ dir="auto"
+ >
+ A super nice release!
+ </p>
+ </div>
+ </div>
+ </div>
+</div>
+`;
diff --git a/spec/frontend/releases/list/components/release_block_spec.js b/spec/frontend/releases/list/components/release_block_spec.js
index eae0025feac..0b908d7d6bc 100644
--- a/spec/frontend/releases/list/components/release_block_spec.js
+++ b/spec/frontend/releases/list/components/release_block_spec.js
@@ -19,46 +19,53 @@ jest.mock('~/lib/utils/common_utils', () => ({
describe('Release block', () => {
let wrapper;
+ let releaseClone;
- const factory = releaseProp => {
+ const factory = (releaseProp, releaseEditPageFeatureFlag = true) => {
wrapper = mount(ReleaseBlock, {
propsData: {
release: releaseProp,
},
+ provide: {
+ glFeatures: {
+ releaseEditPage: releaseEditPageFeatureFlag,
+ },
+ },
+ sync: false,
});
+
+ return wrapper.vm.$nextTick();
};
const milestoneListLabel = () => wrapper.find('.js-milestone-list-label');
+ const editButton = () => wrapper.find('.js-edit-button');
+
+ beforeEach(() => {
+ releaseClone = JSON.parse(JSON.stringify(release));
+ });
afterEach(() => {
wrapper.destroy();
});
describe('with default props', () => {
- beforeEach(() => {
- factory(release);
+ beforeEach(() => factory(release));
+
+ it('matches the snapshot', () => {
+ expect(wrapper.element).toMatchSnapshot();
});
it("renders the block with an id equal to the release's tag name", () => {
expect(wrapper.attributes().id).toBe('v0.3');
});
- it('renders release name', () => {
- expect(wrapper.text()).toContain(release.name);
- });
-
- it('renders commit sha', () => {
- expect(wrapper.text()).toContain(release.commit.short_id);
-
- wrapper.setProps({ release: { ...release, commit_path: '/commit/example' } });
- expect(wrapper.find('a[href="/commit/example"]').exists()).toBe(true);
+ it('renders an edit button that links to the "Edit release" page', () => {
+ expect(editButton().exists()).toBe(true);
+ expect(editButton().attributes('href')).toBe(release._links.edit);
});
- it('renders tag name', () => {
- expect(wrapper.text()).toContain(release.tag_name);
-
- wrapper.setProps({ release: { ...release, tag_path: '/tag/example' } });
- expect(wrapper.find('a[href="/tag/example"]').exists()).toBe(true);
+ it('renders release name', () => {
+ expect(wrapper.text()).toContain(release.name);
});
it('renders release date', () => {
@@ -141,44 +148,73 @@ describe('Release block', () => {
});
});
+ it('renders commit sha', () => {
+ releaseClone.commit_path = '/commit/example';
+
+ return factory(releaseClone).then(() => {
+ expect(wrapper.text()).toContain(release.commit.short_id);
+
+ expect(wrapper.find('a[href="/commit/example"]').exists()).toBe(true);
+ });
+ });
+
+ it('renders tag name', () => {
+ releaseClone.tag_path = '/tag/example';
+
+ return factory(releaseClone).then(() => {
+ expect(wrapper.text()).toContain(release.tag_name);
+
+ expect(wrapper.find('a[href="/tag/example"]').exists()).toBe(true);
+ });
+ });
+
+ it("does not render an edit button if release._links.edit isn't a string", () => {
+ delete releaseClone._links;
+
+ return factory(releaseClone).then(() => {
+ expect(editButton().exists()).toBe(false);
+ });
+ });
+
+ it('does not render an edit button if the releaseEditPage feature flag is disabled', () =>
+ factory(releaseClone, false).then(() => {
+ expect(editButton().exists()).toBe(false);
+ }));
+
it('does not render the milestone list if no milestones are associated to the release', () => {
- const releaseClone = JSON.parse(JSON.stringify(release));
delete releaseClone.milestones;
- factory(releaseClone);
-
- expect(milestoneListLabel().exists()).toBe(false);
+ return factory(releaseClone).then(() => {
+ expect(milestoneListLabel().exists()).toBe(false);
+ });
});
it('renders the label as "Milestone" if only a single milestone is passed in', () => {
- const releaseClone = JSON.parse(JSON.stringify(release));
releaseClone.milestones = releaseClone.milestones.slice(0, 1);
- factory(releaseClone);
-
- expect(
- milestoneListLabel()
- .find('.js-label-text')
- .text(),
- ).toEqual('Milestone');
+ return factory(releaseClone).then(() => {
+ expect(
+ milestoneListLabel()
+ .find('.js-label-text')
+ .text(),
+ ).toEqual('Milestone');
+ });
});
it('renders upcoming release badge', () => {
- const releaseClone = JSON.parse(JSON.stringify(release));
releaseClone.upcoming_release = true;
- factory(releaseClone);
-
- expect(wrapper.text()).toContain('Upcoming Release');
+ return factory(releaseClone).then(() => {
+ expect(wrapper.text()).toContain('Upcoming Release');
+ });
});
it('slugifies the tag_name before setting it as the elements ID', () => {
- const releaseClone = JSON.parse(JSON.stringify(release));
releaseClone.tag_name = 'a dangerous tag name <script>alert("hello")</script>';
- factory(releaseClone);
-
- expect(wrapper.attributes().id).toBe('a-dangerous-tag-name-script-alert-hello-script-');
+ return factory(releaseClone).then(() => {
+ expect(wrapper.attributes().id).toBe('a-dangerous-tag-name-script-alert-hello-script-');
+ });
});
describe('anchor scrolling', () => {
@@ -190,40 +226,39 @@ describe('Release block', () => {
it('does not attempt to scroll the page if no anchor tag is included in the URL', () => {
mockLocationHash = '';
- factory(release);
-
- expect(scrollToElement).not.toHaveBeenCalled();
+ return factory(release).then(() => {
+ expect(scrollToElement).not.toHaveBeenCalled();
+ });
});
it("does not attempt to scroll the page if the anchor tag doesn't match the release's tag name", () => {
mockLocationHash = 'v0.4';
- factory(release);
-
- expect(scrollToElement).not.toHaveBeenCalled();
+ return factory(release).then(() => {
+ expect(scrollToElement).not.toHaveBeenCalled();
+ });
});
it("attempts to scroll itself into view if the anchor tag matches the release's tag name", () => {
mockLocationHash = release.tag_name;
- factory(release);
+ return factory(release).then(() => {
+ expect(scrollToElement).toHaveBeenCalledTimes(1);
- expect(scrollToElement).toHaveBeenCalledTimes(1);
- expect(scrollToElement).toHaveBeenCalledWith(wrapper.element);
+ expect(scrollToElement).toHaveBeenCalledWith(wrapper.element);
+ });
});
it('renders with a light blue background if it is the target of the anchor', () => {
mockLocationHash = release.tag_name;
- factory(release);
- return wrapper.vm.$nextTick().then(() => {
+ return factory(release).then(() => {
expect(hasTargetBlueBackground()).toBe(true);
});
});
it('does not render with a light blue background if it is not the target of the anchor', () => {
mockLocationHash = '';
- factory(release);
- return wrapper.vm.$nextTick().then(() => {
+ return factory(release).then(() => {
expect(hasTargetBlueBackground()).toBe(false);
});
});
diff --git a/spec/frontend/releases/mock_data.js b/spec/frontend/releases/mock_data.js
index 328199343f5..b2ebf1174d4 100644
--- a/spec/frontend/releases/mock_data.js
+++ b/spec/frontend/releases/mock_data.js
@@ -94,4 +94,7 @@ export const release = {
},
],
},
+ _links: {
+ edit: 'http://0.0.0.0:3001/root/release-test/-/releases/v0.3/edit',
+ },
};
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index e64af9206f6..e8116f0a301 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -45,8 +45,8 @@ describe Issuable do
it { is_expected.to validate_presence_of(:iid) }
it { is_expected.to validate_presence_of(:author) }
it { is_expected.to validate_presence_of(:title) }
- it { is_expected.to validate_length_of(:title).is_at_most(255) }
- it { is_expected.to validate_length_of(:description).is_at_most(16_000).on(:create) }
+ it { is_expected.to validate_length_of(:title).is_at_most(described_class::TITLE_LENGTH_MAX) }
+ it { is_expected.to validate_length_of(:description).is_at_most(described_class::DESCRIPTION_LENGTH_MAX).on(:create) }
it_behaves_like 'validates description length with custom validation'
it_behaves_like 'truncates the description to its allowed maximum length on import'
diff --git a/spec/support/shared_examples/models/concern/issuable_shared_examples.rb b/spec/support/shared_examples/models/concern/issuable_shared_examples.rb
index 13ab29e2ca6..4ebb5e35e0e 100644
--- a/spec/support/shared_examples/models/concern/issuable_shared_examples.rb
+++ b/spec/support/shared_examples/models/concern/issuable_shared_examples.rb
@@ -10,7 +10,7 @@ shared_examples_for 'matches_cross_reference_regex? fails fast' do
end
shared_examples_for 'validates description length with custom validation' do
- let(:issuable) { build(:issue, description: 'x' * 16_001) }
+ let(:issuable) { build(:issue, description: 'x' * (::Issuable::DESCRIPTION_LENGTH_MAX + 1)) }
let(:context) { :update }
subject { issuable.validate(context) }
@@ -18,7 +18,7 @@ shared_examples_for 'validates description length with custom validation' do
context 'when Issuable is a new record' do
it 'validates the maximum description length' do
subject
- expect(issuable.errors[:description]).to eq(["is too long (maximum is 16000 characters)"])
+ expect(issuable.errors[:description]).to eq(["is too long (maximum is #{::Issuable::DESCRIPTION_LENGTH_MAX} characters)"])
end
context 'on create' do
@@ -53,14 +53,14 @@ shared_examples_for 'truncates the description to its allowed maximum length on
allow(issuable).to receive(:importing?).and_return(true)
end
- let(:issuable) { build(:issue, description: 'x' * 16_001) }
+ let(:issuable) { build(:issue, description: 'x' * (::Issuable::DESCRIPTION_LENGTH_MAX + 1)) }
subject { issuable.validate(:create) }
it 'truncates the description to its allowed maximum length' do
subject
- expect(issuable.description).to eq('x' * 16_000)
+ expect(issuable.description).to eq('x' * ::Issuable::DESCRIPTION_LENGTH_MAX)
expect(issuable.errors[:description]).to be_empty
end
end