From 141b5ee15ac50a0d5e2fee742b367304322fb2a2 Mon Sep 17 00:00:00 2001 From: psjakubowska Date: Fri, 17 May 2024 16:03:37 +0200 Subject: [PATCH 1/9] Create ProtectionToggle component for Branch rules Displays a toggle for anyone who can manage certain protection and icon for users who do not have edit rights. EE: true --- .../branch_rules/components/view/constants.js | 18 ++-- .../branch_rules/components/view/index.vue | 69 +++++------- .../components/view/protection_toggle.vue | 101 ++++++++++++++++++ .../components/view/index_spec.js | 45 +++----- locale/gitlab.pot | 17 ++- .../components/view/index_spec.js | 26 ++--- .../components/view/protection_toggle_spec.js | 78 ++++++++++++++ 7 files changed, 245 insertions(+), 109 deletions(-) create mode 100644 app/assets/javascripts/projects/settings/branch_rules/components/view/protection_toggle.vue create mode 100644 spec/frontend/projects/settings/branch_rules/components/view/protection_toggle_spec.js diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js b/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js index 051e56c94f36d6..b3c4d6bbe4b9f1 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js +++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/constants.js @@ -27,18 +27,18 @@ export const I18N = { statusChecksHeader: s__('BranchRules|Status checks (%{total})'), allowedToPushHeader: s__('BranchRules|Allowed to push and merge (%{total})'), allowedToMergeHeader: s__('BranchRules|Allowed to merge (%{total})'), + allowForcePushLabel: s__('BranchRules|Allow force push'), allowForcePushTitle: s__('BranchRules|Allows force push'), doesNotAllowForcePushTitle: s__('BranchRules|Does not allow force push'), - forcePushDescription: s__('BranchRules|From users with push access.'), - requiresCodeOwnerApprovalTitle: s__('BranchRules|Requires approval from code owners'), + forcePushDescription: s__( + 'BranchRules|Allow all users with push access to %{linkStart}force push%{linkEnd}.', + ), + requiresCodeOwnerApprovalTitle: s__('BranchRules|Requires code owner approval'), doesNotRequireCodeOwnerApprovalTitle: s__( 'BranchRules|Does not require approval from code owners', ), - requiresCodeOwnerApprovalDescription: s__( - 'BranchRules|Also rejects code pushes that change files listed in CODEOWNERS file.', - ), - doesNotRequireCodeOwnerApprovalDescription: s__( - 'BranchRules|Also accepts code pushes that change files listed in CODEOWNERS file.', + codeOwnerApprovalDescription: s__( + 'BranchRules|Changed files listed in %{linkStart}CODEOWNERS%{linkEnd} will require an approval for merge requests and will be rejected for code pushes.', ), noData: s__('BranchRules|No data to display'), deleteRuleModalTitle: s__('BranchRules|Delete branch rule?'), @@ -70,6 +70,10 @@ export const APPROVALS_HELP_PATH = 'user/project/merge_requests/approvals/index. export const STATUS_CHECKS_HELP_PATH = 'user/project/merge_requests/status_checks.md'; +export const CODE_OWNERS_HELP_PATH = 'user/project/code_owners.md'; + +export const PUSH_RULES_HELP_PATH = 'user/project/repository/push_rules.md'; + export const REQUIRED_ICON = 'check-circle-filled'; export const NOT_REQUIRED_ICON = 'status-failed'; diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue index 3cdb3f5c7d4b43..eb47ac59952990 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue +++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue @@ -5,7 +5,6 @@ import { GlSprintf, GlLink, GlLoadingIcon, - GlIcon, GlCard, GlButton, GlModal, @@ -28,36 +27,39 @@ import { getAccessLevels } from '../../../utils'; import BranchRuleModal from '../../../components/branch_rule_modal.vue'; import Protection from './protection.vue'; import RuleDrawer from './rule_drawer.vue'; +import ProtectionToggle from './protection_toggle.vue'; import { I18N, ALL_BRANCHES_WILDCARD, BRANCH_PARAM_NAME, PROTECTED_BRANCHES_HELP_PATH, - REQUIRED_ICON, - NOT_REQUIRED_ICON, - REQUIRED_ICON_CLASS, - NOT_REQUIRED_ICON_CLASS, + CODE_OWNERS_HELP_PATH, + PUSH_RULES_HELP_PATH, DELETE_RULE_MODAL_ID, EDIT_RULE_MODAL_ID, } from './constants'; const protectedBranchesHelpDocLink = helpPagePath(PROTECTED_BRANCHES_HELP_PATH); +const codeOwnersHelpDocLink = helpPagePath(CODE_OWNERS_HELP_PATH); +const pushRulesHelpDocLink = helpPagePath(PUSH_RULES_HELP_PATH); export default { name: 'RuleView', i18n: I18N, deleteModalId: DELETE_RULE_MODAL_ID, protectedBranchesHelpDocLink, + codeOwnersHelpDocLink, + pushRulesHelpDocLink, directives: { GlModal: GlModalDirective, }, editModalId: EDIT_RULE_MODAL_ID, components: { Protection, + ProtectionToggle, GlSprintf, GlLink, GlLoadingIcon, - GlIcon, GlCard, GlModal, GlButton, @@ -124,26 +126,12 @@ export default { computed: { forcePushAttributes() { const { allowForcePush } = this.branchProtection || {}; - const icon = allowForcePush ? REQUIRED_ICON : NOT_REQUIRED_ICON; - const iconClass = allowForcePush ? REQUIRED_ICON_CLASS : NOT_REQUIRED_ICON_CLASS; const title = allowForcePush ? this.$options.i18n.allowForcePushTitle : this.$options.i18n.doesNotAllowForcePushTitle; + const description = allowForcePush ? this.$options.i18n.forcePushDescription : ''; - return { icon, iconClass, title }; - }, - codeOwnersApprovalAttributes() { - const { codeOwnerApprovalRequired } = this.branchProtection || {}; - const icon = codeOwnerApprovalRequired ? REQUIRED_ICON : NOT_REQUIRED_ICON; - const iconClass = codeOwnerApprovalRequired ? REQUIRED_ICON_CLASS : NOT_REQUIRED_ICON_CLASS; - const title = codeOwnerApprovalRequired - ? this.$options.i18n.requiresCodeOwnerApprovalTitle - : this.$options.i18n.doesNotRequireCodeOwnerApprovalTitle; - const description = codeOwnerApprovalRequired - ? this.$options.i18n.requiresCodeOwnerApprovalDescription - : this.$options.i18n.doesNotRequireCodeOwnerApprovalDescription; - - return { icon, iconClass, title, description }; + return { title, description }; }, mergeAccessLevels() { const { mergeAccessLevels } = this.branchProtection || {}; @@ -353,32 +341,25 @@ export default { /> -
- - {{ forcePushAttributes.title }} -
- -
{{ $options.i18n.forcePushDescription }}
+
-
- - {{ codeOwnersApprovalAttributes.title }} -
- -
{{ codeOwnersApprovalAttributes.description }}
+
diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_toggle.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_toggle.vue new file mode 100644 index 00000000000000..e409a690d84721 --- /dev/null +++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_toggle.vue @@ -0,0 +1,101 @@ + + + diff --git a/ee/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js b/ee/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js index a9e17b5537e52f..85604b6f3dc9e2 100644 --- a/ee/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js +++ b/ee/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js @@ -14,14 +14,9 @@ import deleteBranchRuleMutation from '~/projects/settings/branch_rules/mutations import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { - I18N, - REQUIRED_ICON, - NOT_REQUIRED_ICON, - REQUIRED_ICON_CLASS, - NOT_REQUIRED_ICON_CLASS, -} from '~/projects/settings/branch_rules/components/view/constants'; +import { I18N } from '~/projects/settings/branch_rules/components/view/constants'; import Protection from '~/projects/settings/branch_rules/components/view/protection.vue'; +import ProtectionToggle from '~/projects/settings/branch_rules/components/view/protection_toggle.vue'; import { sprintf } from '~/locale'; import { deleteBranchRuleMockResponse, @@ -99,9 +94,7 @@ describe('View branch rules in enterprise edition', () => { const findApprovalsApp = () => wrapper.findComponent(ApprovalRulesApp); const findProjectRules = () => wrapper.findComponent(ProjectRules); const findStatusChecksTitle = () => wrapper.findByText(I18N.statusChecksTitle); - const findCodeOwnerApprovalIcon = () => wrapper.findByTestId('code-owners-icon'); - const findCodeOwnerApprovalTitle = (title) => wrapper.findByText(title); - const findCodeOwnerApprovalDescription = (description) => wrapper.findByText(description); + const findProtectionToggles = () => wrapper.findAllComponents(ProtectionToggle); it('renders a branch protection component for push rules', () => { expect(findBranchProtections().at(0).props()).toMatchObject({ @@ -119,30 +112,18 @@ describe('View branch rules in enterprise edition', () => { describe('Code owner approvals', () => { it('does not render a code owner approval section by default', () => { - expect(findCodeOwnerApprovalIcon().exists()).toBe(false); - expect(findCodeOwnerApprovalTitle(I18N.requiresCodeOwnerApprovalTitle).exists()).toBe(false); - expect( - findCodeOwnerApprovalDescription(I18N.requiresCodeOwnerApprovalDescription).exists(), - ).toBe(false); + expect(findProtectionToggles().length).toBe(1); }); - it.each` - codeOwnerApprovalRequired | iconName | iconClass | title | description - ${true} | ${REQUIRED_ICON} | ${REQUIRED_ICON_CLASS} | ${I18N.requiresCodeOwnerApprovalTitle} | ${I18N.requiresCodeOwnerApprovalDescription} - ${false} | ${NOT_REQUIRED_ICON} | ${NOT_REQUIRED_ICON_CLASS} | ${I18N.doesNotRequireCodeOwnerApprovalTitle} | ${I18N.doesNotRequireCodeOwnerApprovalDescription} - `( - 'code owners with the correct icon, title and description', - async ({ codeOwnerApprovalRequired, iconName, iconClass, title, description }) => { - const mockResponse = branchProtectionsMockResponse; - mockResponse.data.project.branchRules.nodes[0].branchProtection.codeOwnerApprovalRequired = codeOwnerApprovalRequired; - await createComponent({ showCodeOwners: true }, mockResponse); - - expect(findCodeOwnerApprovalIcon().props('name')).toBe(iconName); - expect(findCodeOwnerApprovalIcon().attributes('class')).toBe(iconClass); - expect(findCodeOwnerApprovalTitle(title).exists()).toBe(true); - expect(findCodeOwnerApprovalTitle(description).exists()).toBe(true); - }, - ); + it('renders renders a code owner approval section when showCodeOwners is true', async () => { + const mockResponse = branchProtectionsMockResponse; + await createComponent({ showCodeOwners: true }, mockResponse); + + expect(findProtectionToggles().at(1).exists()).toBe(true); + expect(findProtectionToggles().at(1).props('label')).toEqual( + I18N.requiresCodeOwnerApprovalTitle, + ); + }); }); it('does not render approvals and status checks sections by default', () => { diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 29cc5a54a01d25..14af9f397a33f2 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -9023,6 +9023,9 @@ msgstr "" msgid "BranchRules|Allow all users with push access to %{linkStart}force push%{linkEnd}." msgstr "" +msgid "BranchRules|Allow force push" +msgstr "" + msgid "BranchRules|Allowed to force push" msgstr "" @@ -9044,12 +9047,6 @@ msgstr "" msgid "BranchRules|Allows force push" msgstr "" -msgid "BranchRules|Also accepts code pushes that change files listed in CODEOWNERS file." -msgstr "" - -msgid "BranchRules|Also rejects code pushes that change files listed in CODEOWNERS file." -msgstr "" - msgid "BranchRules|An error occurred while fetching branches." msgstr "" @@ -9074,6 +9071,9 @@ msgstr "" msgid "BranchRules|Cancel" msgstr "" +msgid "BranchRules|Changed files listed in %{linkStart}CODEOWNERS%{linkEnd} will require an approval for merge requests and will be rejected for code pushes." +msgstr "" + msgid "BranchRules|Changes require a merge request. The following users can push and merge directly." msgstr "" @@ -9113,9 +9113,6 @@ msgstr "" msgid "BranchRules|Edit allowed to merge" msgstr "" -msgid "BranchRules|From users with push access." -msgstr "" - msgid "BranchRules|Groups" msgstr "" @@ -9164,7 +9161,7 @@ msgstr "" msgid "BranchRules|Requires CODEOWNERS approval" msgstr "" -msgid "BranchRules|Requires approval from code owners" +msgid "BranchRules|Requires code owner approval" msgstr "" msgid "BranchRules|Roles" diff --git a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js index 84ebeb3de046aa..fd699b970ef881 100644 --- a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js +++ b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js @@ -13,16 +13,13 @@ import RuleView from '~/projects/settings/branch_rules/components/view/index.vue import RuleDrawer from '~/projects/settings/branch_rules/components/view/rule_drawer.vue'; import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; import Protection from '~/projects/settings/branch_rules/components/view/protection.vue'; +import ProtectionToggle from '~/projects/settings/branch_rules/components/view/protection_toggle.vue'; import BranchRuleModal from '~/projects/settings/components/branch_rule_modal.vue'; import getProtectableBranches from '~/projects/settings/graphql/queries/protectable_branches.query.graphql'; import { I18N, ALL_BRANCHES_WILDCARD, - REQUIRED_ICON, - NOT_REQUIRED_ICON, - REQUIRED_ICON_CLASS, - NOT_REQUIRED_ICON_CLASS, DELETE_RULE_MODAL_ID, EDIT_RULE_MODAL_ID, } from '~/projects/settings/branch_rules/components/view/constants'; @@ -99,6 +96,7 @@ describe('View branch rules', () => { provide: { projectPath, protectedBranchesPath, branchRulesPath, glFeatures }, stubs: { Protection, + ProtectionToggle, BranchRuleModal, RuleDrawer, GlCard: stubComponent(GlCard, { template: RENDER_ALL_SLOTS_TEMPLATE }), @@ -119,9 +117,7 @@ describe('View branch rules', () => { const findAllBranches = () => wrapper.findByTestId('all-branches'); const findBranchProtectionTitle = () => wrapper.findByText(I18N.protectBranchTitle); const findBranchProtections = () => wrapper.findAllComponents(Protection); - const findForcePushIcon = () => wrapper.findByTestId('force-push-icon'); - const findForcePushTitle = (title) => wrapper.findByText(title); - const findForcePushDescription = () => wrapper.findByText(I18N.forcePushDescription); + const findProtectionToggles = () => wrapper.findAllComponents(ProtectionToggle); const findApprovalsTitle = () => wrapper.findByText(I18N.approvalsTitle); const findpageTitle = () => wrapper.findByText(I18N.pageTitle); const findStatusChecksTitle = () => wrapper.findByText(I18N.statusChecksTitle); @@ -192,20 +188,18 @@ describe('View branch rules', () => { }); it.each` - allowForcePush | iconName | iconClass | title - ${true} | ${REQUIRED_ICON} | ${REQUIRED_ICON_CLASS} | ${I18N.allowForcePushTitle} - ${false} | ${NOT_REQUIRED_ICON} | ${NOT_REQUIRED_ICON_CLASS} | ${I18N.doesNotAllowForcePushTitle} + allowForcePush | title | description + ${true} | ${I18N.allowForcePushTitle} | ${I18N.forcePushDescription} + ${false} | ${I18N.doesNotAllowForcePushTitle} | ${''} `( - 'renders force push section with the correct icon, title and description', - async ({ allowForcePush, iconName, iconClass, title }) => { + 'renders force push section with the correct title and description', + async ({ allowForcePush, title, description }) => { const mockResponse = branchProtectionsMockResponse; mockResponse.data.project.branchRules.nodes[0].branchProtection.allowForcePush = allowForcePush; await createComponent(mockResponse); - expect(findForcePushIcon().props('name')).toBe(iconName); - expect(findForcePushIcon().attributes('class')).toBe(iconClass); - expect(findForcePushTitle(title).exists()).toBe(true); - expect(findForcePushDescription().exists()).toBe(true); + expect(findProtectionToggles().at(0).props('iconTitle')).toEqual(title); + expect(findProtectionToggles().at(0).props('description')).toEqual(description); }, ); diff --git a/spec/frontend/projects/settings/branch_rules/components/view/protection_toggle_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/protection_toggle_spec.js new file mode 100644 index 00000000000000..9d6cf7e9773b25 --- /dev/null +++ b/spec/frontend/projects/settings/branch_rules/components/view/protection_toggle_spec.js @@ -0,0 +1,78 @@ +import { GlToggle, GlIcon, GlSprintf, GlLink } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ProtectionToggle from '~/projects/settings/branch_rules/components/view/protection_toggle.vue'; + +describe('ProtectionToggle', () => { + let wrapper; + + const createComponent = (props = {}) => { + wrapper = shallowMountExtended(ProtectionToggle, { + stubs: { + GlToggle, + GlIcon, + GlLink, + GlSprintf, + }, + propsData: { + dataTestId: 'force-push', + label: 'Force Push', + isProtected: false, + canEdit: true, + ...props, + }, + }); + }; + + const findProtectionToggle = () => wrapper.findComponent(GlToggle); + const findProtectionIcon = () => wrapper.findByTestId('force-push-icon'); + + describe('when user can edit', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders the toggle with the correct data', () => { + expect(findProtectionToggle().exists()).toBe(true); + }); + + it('does not render the protection icon', () => { + expect(findProtectionIcon().exists()).toBe(false); + }); + }); + + describe('when user cannot edit', () => { + beforeEach(() => { + createComponent({ canEdit: false }); + }); + + it('does not render the toggle', () => { + expect(findProtectionToggle().exists()).toBe(false); + }); + + it('renders the protection icon', () => { + expect(findProtectionIcon().exists()).toBe(true); + }); + + it('renders label if iconTitle is not provided', () => { + expect(wrapper.text()).toContain('Force Push'); + }); + + it('renders iconTitle if provided', () => { + createComponent({ canEdit: false, iconTitle: 'icon title' }); + + expect(wrapper.text()).toContain('icon title'); + }); + + it('renders the correct icon when the protection is off', () => { + expect(findProtectionIcon().props('name')).toBe('status-failed'); + expect(findProtectionIcon().attributes('class')).toContain('gl-text-red-500'); + }); + + it('renders the correct icon when the protection is on', () => { + createComponent({ canEdit: false, isProtected: true }); + + expect(findProtectionIcon().props('name')).toBe('check-circle-filled'); + expect(findProtectionIcon().attributes('class')).toContain('gl-fill-green-500'); + }); + }); +}); -- GitLab From 199de301e485a9161e2dcd1e87d29f6e9ee30c75 Mon Sep 17 00:00:00 2001 From: psjakubowska Date: Mon, 20 May 2024 20:13:42 +0200 Subject: [PATCH 2/9] Inject caEdit data for Branch rule details --- .../branch_rules/components/view/index.vue | 1 + .../components/view/protection_toggle.vue | 6 +----- .../settings/branch_rules/mount_branch_rules.js | 2 ++ .../branch_rules/components/view/index_spec.js | 3 ++- .../components/view/protection_toggle_spec.js | 14 +++++++++----- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue index eb47ac59952990..6bdc9de80e1156 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue +++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue @@ -83,6 +83,7 @@ export default { showStatusChecks: { default: false }, showApprovers: { default: false }, showCodeOwners: { default: false }, + canEdit: { default: false }, }, apollo: { project: { diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_toggle.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_toggle.vue index e409a690d84721..a53f4260e6bf67 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_toggle.vue +++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_toggle.vue @@ -14,6 +14,7 @@ export default { GlSprintf, GlLink, }, + inject: ['canEdit'], props: { dataTestId: { type: String, @@ -48,11 +49,6 @@ export default { type: Boolean, required: true, }, - canEdit: { - type: Boolean, - required: false, - default: false, - }, }, computed: { iconName() { diff --git a/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js b/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js index d010be97688c75..2c44224d416c7a 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js +++ b/app/assets/javascripts/projects/settings/branch_rules/mount_branch_rules.js @@ -25,6 +25,7 @@ export default function mountBranchRules(el, store) { showStatusChecks, showApprovers, showCodeOwners, + canEdit, } = el.dataset; return new Vue({ @@ -41,6 +42,7 @@ export default function mountBranchRules(el, store) { showStatusChecks: parseBoolean(showStatusChecks), showApprovers: parseBoolean(showApprovers), showCodeOwners: parseBoolean(showCodeOwners), + canEdit: parseBoolean(canEdit), }, render(h) { return h(View); diff --git a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js index fd699b970ef881..e7331ee0a3f9a1 100644 --- a/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js +++ b/spec/frontend/projects/settings/branch_rules/components/view/index_spec.js @@ -66,6 +66,7 @@ describe('View branch rules', () => { const projectPath = 'test/testing'; const protectedBranchesPath = 'protected/branches'; const branchRulesPath = '/-/settings/repository#branch_rules'; + const canEdit = true; const branchRulesMockRequestHandler = jest.fn().mockResolvedValue(branchProtectionsMockResponse); const predefinedBranchRulesMockRequestHandler = jest .fn() @@ -93,7 +94,7 @@ describe('View branch rules', () => { wrapper = shallowMountExtended(RuleView, { apolloProvider: fakeApollo, - provide: { projectPath, protectedBranchesPath, branchRulesPath, glFeatures }, + provide: { projectPath, protectedBranchesPath, branchRulesPath, canEdit, glFeatures }, stubs: { Protection, ProtectionToggle, diff --git a/spec/frontend/projects/settings/branch_rules/components/view/protection_toggle_spec.js b/spec/frontend/projects/settings/branch_rules/components/view/protection_toggle_spec.js index 9d6cf7e9773b25..e4284e58090ba6 100644 --- a/spec/frontend/projects/settings/branch_rules/components/view/protection_toggle_spec.js +++ b/spec/frontend/projects/settings/branch_rules/components/view/protection_toggle_spec.js @@ -5,7 +5,7 @@ import ProtectionToggle from '~/projects/settings/branch_rules/components/view/p describe('ProtectionToggle', () => { let wrapper; - const createComponent = (props = {}) => { + const createComponent = (props = {}, provided = {}) => { wrapper = shallowMountExtended(ProtectionToggle, { stubs: { GlToggle, @@ -13,6 +13,10 @@ describe('ProtectionToggle', () => { GlLink, GlSprintf, }, + provide: { + canEdit: true, + ...provided, + }, propsData: { dataTestId: 'force-push', label: 'Force Push', @@ -31,7 +35,7 @@ describe('ProtectionToggle', () => { createComponent(); }); - it('renders the toggle with the correct data', () => { + it('renders the toggle', () => { expect(findProtectionToggle().exists()).toBe(true); }); @@ -42,7 +46,7 @@ describe('ProtectionToggle', () => { describe('when user cannot edit', () => { beforeEach(() => { - createComponent({ canEdit: false }); + createComponent({}, { canEdit: false }); }); it('does not render the toggle', () => { @@ -58,7 +62,7 @@ describe('ProtectionToggle', () => { }); it('renders iconTitle if provided', () => { - createComponent({ canEdit: false, iconTitle: 'icon title' }); + createComponent({ iconTitle: 'icon title' }, { canEdit: false }); expect(wrapper.text()).toContain('icon title'); }); @@ -69,7 +73,7 @@ describe('ProtectionToggle', () => { }); it('renders the correct icon when the protection is on', () => { - createComponent({ canEdit: false, isProtected: true }); + createComponent({ isProtected: true }, { canEdit: false }); expect(findProtectionIcon().props('name')).toBe('check-circle-filled'); expect(findProtectionIcon().attributes('class')).toContain('gl-fill-green-500'); -- GitLab From 286b4fdd58149657c5c7713190d8e98fa04652a7 Mon Sep 17 00:00:00 2001 From: psjakubowska Date: Mon, 20 May 2024 20:56:23 +0200 Subject: [PATCH 3/9] Hide editing for protection toggle behind a feature flag --- .../components/view/protection_toggle.vue | 4 +++- .../components/view/protection_toggle_spec.js | 24 +++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_toggle.vue b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_toggle.vue index a53f4260e6bf67..fbe6142784ea4a 100644 --- a/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_toggle.vue +++ b/app/assets/javascripts/projects/settings/branch_rules/components/view/protection_toggle.vue @@ -1,5 +1,6 @@