diff options
-rw-r--r-- | app/assets/javascripts/releases/list/components/release_block.vue | 34 | ||||
-rw-r--r-- | app/controllers/projects/releases_controller.rb | 3 | ||||
-rw-r--r-- | app/models/concerns/issuable.rb | 4 | ||||
-rw-r--r-- | changelogs/unreleased/nfriend-add-edit-button-to-release-blocks.yml | 5 | ||||
-rw-r--r-- | doc/ci/multi_project_pipelines.md | 3 | ||||
-rw-r--r-- | doc/ci/yaml/README.md | 40 | ||||
-rw-r--r-- | doc/development/api_graphql_styleguide.md | 5 | ||||
-rw-r--r-- | doc/development/issuable-like-models.md | 4 | ||||
-rw-r--r-- | doc/development/rake_tasks.md | 23 | ||||
-rw-r--r-- | locale/gitlab.pot | 3 | ||||
-rw-r--r-- | spec/frontend/releases/list/components/__snapshots__/release_block_spec.js.snap | 332 | ||||
-rw-r--r-- | spec/frontend/releases/list/components/release_block_spec.js | 135 | ||||
-rw-r--r-- | spec/frontend/releases/mock_data.js | 3 | ||||
-rw-r--r-- | spec/models/concerns/issuable_spec.rb | 4 | ||||
-rw-r--r-- | spec/support/shared_examples/models/concern/issuable_shared_examples.rb | 8 |
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 |