Commit 288c393c authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 8a64580c b148b97b
......@@ -4,7 +4,7 @@ import VueDraggable from 'vuedraggable';
import permissionsQuery from 'shared_queries/design_management/design_permissions.query.graphql';
import getDesignListQuery from 'shared_queries/design_management/get_design_list.query.graphql';
import createFlash, { FLASH_TYPES } from '~/flash';
import { getFilename } from '~/lib/utils/file_upload';
import { getFilename, validateImageName } from '~/lib/utils/file_upload';
import { __, s__, sprintf } from '~/locale';
import DesignDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
import DeleteButton from '../components/delete_button.vue';
......@@ -284,12 +284,16 @@ export default {
return;
}
event.preventDefault();
let filename = getFilename(event);
if (!filename || filename === 'image.png') {
filename = `design_${Date.now()}.png`;
}
const newFile = new File([files[0]], filename);
this.onUploadDesign([newFile]);
const fileList = [...files];
fileList.forEach((file) => {
let filename = getFilename(file);
filename = validateImageName(file);
if (!filename || filename === 'image.png') {
filename = `design_${Date.now()}.png`;
}
const newFile = new File([file], filename);
this.onUploadDesign([newFile]);
});
}
},
toggleOnPasteListener() {
......
......@@ -43,7 +43,6 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
let pasteText;
let addFileToForm;
let updateAttachingMessage;
let isImage;
let uploadFile;
formTextarea.wrap('<div class="div-dropzone"></div>');
......@@ -173,7 +172,7 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
return dropzoneInstance.addFile(file);
});
});
// eslint-disable-next-line consistent-return
handlePaste = (event) => {
const pasteEvent = event.originalEvent;
const { clipboardData } = pasteEvent;
......@@ -186,32 +185,22 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
const text = converter.convertToTableMarkdown();
pasteText(text);
} else {
const image = isImage(pasteEvent);
if (image) {
event.preventDefault();
const MAX_FILE_NAME_LENGTH = 246;
const filename = getFilename(pasteEvent) || 'image.png';
const truncateFilename = truncate(filename, MAX_FILE_NAME_LENGTH);
const text = `{{${truncateFilename}}}`;
pasteText(text);
return uploadFile(image.getAsFile(), truncateFilename);
}
}
}
};
isImage = (data) => {
let i = 0;
while (i < data.clipboardData.items.length) {
const item = data.clipboardData.items[i];
if (item.type.indexOf('image') !== -1) {
return item;
const fileList = [...clipboardData.files];
fileList.forEach((file) => {
if (file.type.indexOf('image') !== -1) {
event.preventDefault();
const MAX_FILE_NAME_LENGTH = 246;
const filename = getFilename(file) || 'image.png';
const truncateFilename = truncate(filename, MAX_FILE_NAME_LENGTH);
const text = `{{${truncateFilename}}}`;
pasteText(text);
uploadFile(file, truncateFilename);
}
});
}
i += 1;
}
return false;
};
pasteText = (text, shouldPad) => {
......
......@@ -5,6 +5,7 @@ import {
VALIDATE_INTEGRATION_FORM_EVENT,
GET_JIRA_ISSUE_TYPES_EVENT,
} from '~/integrations/constants';
import { s__, __ } from '~/locale';
import eventHub from '../event_hub';
import JiraUpgradeCta from './jira_upgrade_cta.vue';
......@@ -94,33 +95,38 @@ export default {
eventHub.$emit(GET_JIRA_ISSUE_TYPES_EVENT);
},
},
i18n: {
sectionTitle: s__('JiraService|View Jira issues in GitLab'),
sectionDescription: s__(
'JiraService|Work on Jira issues without leaving GitLab. Adds a Jira menu to access your list of Jira issues and view any issue as read-only.',
),
enableCheckboxLabel: s__('JiraService|Enable Jira issues'),
enableCheckboxHelp: s__(
'JiraService|Warning: All GitLab users that have access to this GitLab project are able to view all issues from the Jira project specified below.',
),
projectKeyLabel: s__('JiraService|Jira project key'),
projectKeyPlaceholder: s__('JiraService|For example, AB'),
requiredFieldFeedback: __('This field is required.'),
issueTrackerConflictWarning: s__(
'JiraService|Displaying Jira issues while leaving the GitLab issue functionality enabled might be confusing. Consider %{linkStart}disabling GitLab issues%{linkEnd} if they won’t otherwise be used.',
),
},
};
</script>
<template>
<div>
<gl-form-group
:label="s__('JiraService|View Jira issues in GitLab')"
label-for="jira-issue-settings"
>
<gl-form-group :label="$options.i18n.sectionTitle" label-for="jira-issue-settings">
<div id="jira-issue-settings">
<p>
{{
s__(
'JiraService|Work on Jira issues without leaving GitLab. Adds a Jira menu to access your list of Jira issues and view any issue as read-only.',
)
}}
{{ $options.i18n.sectionDescription }}
</p>
<template v-if="showJiraIssuesIntegration">
<input name="service[issues_enabled]" type="hidden" :value="enableJiraIssues || false" />
<gl-form-checkbox v-model="enableJiraIssues" :disabled="isInheriting">
{{ s__('JiraService|Enable Jira issues') }}
{{ $options.i18n.enableCheckboxLabel }}
<template #help>
{{
s__(
'JiraService|Warning: All GitLab users that have access to this GitLab project are able to view all issues from the Jira project specified below.',
)
}}
{{ $options.i18n.enableCheckboxHelp }}
</template>
</gl-form-checkbox>
<template v-if="enableJiraIssues">
......@@ -152,30 +158,25 @@ export default {
</gl-form-group>
<template v-if="showJiraIssuesIntegration">
<gl-form-group
:label="s__('JiraService|Jira project key')"
:label="$options.i18n.projectKeyLabel"
label-for="service_project_key"
:invalid-feedback="__('This field is required.')"
:invalid-feedback="$options.i18n.requiredFieldFeedback"
:state="validProjectKey"
data-testid="project-key-form-group"
>
<gl-form-input
id="service_project_key"
v-model="projectKey"
name="service[project_key]"
:placeholder="s__('JiraService|For example, AB')"
:placeholder="$options.i18n.projectKeyPlaceholder"
:required="enableJiraIssues"
:state="validProjectKey"
:disabled="!enableJiraIssues"
:readonly="isInheriting"
/>
</gl-form-group>
<p v-if="gitlabIssuesEnabled">
<gl-sprintf
:message="
s__(
'JiraService|Displaying Jira issues while leaving the GitLab issue functionality enabled might be confusing. Consider %{linkStart}disabling GitLab issues%{linkEnd} if they won’t otherwise be used.',
)
"
>
<p v-if="gitlabIssuesEnabled" data-testid="conflict-warning-text">
<gl-sprintf :message="$options.i18n.issueTrackerConflictWarning">
<template #link="{ content }">
<gl-link :href="editProjectPath" target="_blank">{{ content }}</gl-link>
</template>
......
......@@ -15,13 +15,17 @@ export default (buttonSelector, fileSelector) => {
});
};
export const getFilename = ({ clipboardData }) => {
let value;
if (window.clipboardData && window.clipboardData.getData) {
value = window.clipboardData.getData('Text');
} else if (clipboardData && clipboardData.getData) {
value = clipboardData.getData('text/plain');
export const getFilename = (file) => {
let fileName;
if (file) {
fileName = file.name;
}
value = value.split('\r');
return value[0];
return fileName;
};
export const validateImageName = (file) => {
const fileName = file.name ? file.name : 'image.png';
const legalImageRegex = /^[\w.\-+]+\.(png|jpg|jpeg|gif|bmp|tiff|ico|webp)$/;
return legalImageRegex.test(fileName) ? fileName : 'image.png';
};
......@@ -9,7 +9,7 @@ class Admin::HooksController < Admin::ApplicationController
urgency :low, [:test]
def index
@hooks = SystemHook.all
@hooks = SystemHook.all.load
@hook = SystemHook.new
end
......
......@@ -16,7 +16,7 @@ class Projects::HooksController < Projects::ApplicationController
urgency :low, [:test]
def index
@hooks = @project.hooks
@hooks = @project.hooks.load
@hook = ProjectHook.new
end
......
......@@ -104,8 +104,7 @@ module Projects
CreatePipelineWorker.perform_async(project.id, current_user.id, project.default_branch, :web, ignore_skip_ci: true, save_on_errors: false)
# rubocop:enable CodeReuse/Worker
pipelines_link_start = '<a href="%{url}">'.html_safe % { url: project_pipelines_path(@project) }
flash[:toast] = _("A new Auto DevOps pipeline has been created, go to %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details") % { pipelines_link_start: pipelines_link_start, pipelines_link_end: "</a>".html_safe }
flash[:toast] = _("A new Auto DevOps pipeline has been created, go to the Pipelines page for details")
end
def define_variables
......
......@@ -3,7 +3,7 @@
.card-header
%h5
= hook_class.underscore.humanize.titleize.pluralize
(#{hooks.load.size})
(#{hooks.size})
- if hooks.any?
%ul.content-list
......
......@@ -350,7 +350,7 @@ GITLAB_PASSWORD=<GDK root password> bundle exec bin/qa Test::Instance::All http:
Where `<test_file>` is:
- `qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb` when running the Login example.
- `qa/specs/features/browser_ui/2_plan/issues/create_issue_spec.rb` when running the Issue example.
- `qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb` when running the Issue example.
## End-to-end test merge request template
......
......@@ -66,7 +66,12 @@ export default {
PolicyEditorLayout,
DimDisableContainer,
},
inject: ['networkDocumentationPath', 'noEnvironmentSvgPath', 'projectId', 'policiesPath'],
inject: [
'networkDocumentationPath',
'policyEditorEmptyStateSvgPath',
'projectId',
'policiesPath',
],
props: {
existingPolicy: {
type: Object,
......@@ -303,7 +308,7 @@ export default {
:description="$options.i18n.noEnvironmentDescription"
:primary-button-link="networkDocumentationPath"
:primary-button-text="$options.i18n.noEnvironmentButton"
:svg-path="noEnvironmentSvgPath"
:svg-path="policyEditorEmptyStateSvgPath"
title=""
/>
</template>
<script>
import { GlEmptyState } from '@gitlab/ui';
import { joinPaths, visitUrl } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { __, s__ } from '~/locale';
import { EDITOR_MODES, EDITOR_MODE_YAML } from '../constants';
import PolicyEditorLayout from '../policy_editor_layout.vue';
import {
......@@ -18,11 +19,22 @@ export default {
EDITOR_MODES: [EDITOR_MODES[1]],
i18n: {
createMergeRequest: __('Create merge request'),
notOwnerButtonText: __('Learn more'),
notOwnerDescription: s__(
'SecurityOrchestration|Scan execution policies can only be created by project owners.',
),
},
components: {
GlEmptyState,
PolicyEditorLayout,
},
inject: ['disableScanExecutionUpdate', 'projectId', 'projectPath'],
inject: [
'disableScanExecutionUpdate',
'policyEditorEmptyStateSvgPath',
'projectId',
'projectPath',
'scanExecutionDocumentationPath',
],
props: {
assignedPolicyProject: {
type: Object,
......@@ -110,9 +122,9 @@ export default {
<template>
<policy-editor-layout
v-if="!disableScanExecutionUpdate"
:custom-save-button-text="$options.i18n.createMergeRequest"
:default-editor-mode="$options.DEFAULT_EDITOR_MODE"
:disable-update="disableScanExecutionUpdate"
:editor-modes="$options.EDITOR_MODES"
:is-editing="isEditing"
:is-removing-policy="isRemovingPolicy"
......@@ -123,4 +135,12 @@ export default {
@save-policy="handleModifyPolicy()"
@update-yaml="updateYaml"
/>
<gl-empty-state
v-else
:description="$options.i18n.notOwnerDescription"
:primary-button-link="scanExecutionDocumentationPath"
:primary-button-text="$options.i18n.notOwnerButtonText"
:svg-path="policyEditorEmptyStateSvgPath"
title=""
/>
</template>
......@@ -21,15 +21,16 @@ export default () => {
environmentsEndpoint,
configureAgentHelpPath,
createAgentHelpPath,
networkDocumentationPath,
networkPoliciesEndpoint,
noEnvironmentSvgPath,
networkDocumentationPath,
policiesPath,
policy,
policyEditorEmptyStateSvgPath,
policyType,
projectPath,
projectId,
environmentId,
scanExecutionDocumentationPath,
} = el.dataset;
// We require the project to have at least one available environment.
......@@ -66,12 +67,13 @@ export default () => {
configureAgentHelpPath,
createAgentHelpPath,
disableScanExecutionUpdate: parseBoolean(disableScanExecutionUpdate),
policyType,
networkDocumentationPath,
noEnvironmentSvgPath,
policyEditorEmptyStateSvgPath,
policyType,
projectId,
projectPath,
policiesPath,
scanExecutionDocumentationPath,
},
store,
render(createElement) {
......
......@@ -18,7 +18,7 @@ class Groups::HooksController < Groups::ApplicationController
urgency :low, [:test]
def index
@hooks = @group.hooks
@hooks = @group.hooks.load
@hook = GroupHook.new
end
......
......@@ -29,13 +29,14 @@ module Projects::Security::PoliciesHelper
create_agent_help_path: help_page_url('user/clusters/agent/index.md', anchor: 'create-an-agent-record-in-gitlab'),
environments_endpoint: project_environments_path(project),
environment_id: environment&.id,
network_documentation_path: help_page_path('user/application_security/policies/index'),
no_environment_svg_path: image_path('illustrations/monitoring/unable_to_connect.svg'),
network_documentation_path: help_page_path('user/application_security/policies/index', anchor: 'container-network-policy'),
policy: policy&.to_json,
policy_editor_empty_state_svg_path: image_path('illustrations/monitoring/unable_to_connect.svg'),
policy_type: policy_type,
project_path: project.full_path,
project_id: project.id,
policies_path: project_security_policies_path(project)
policies_path: project_security_policies_path(project),
scan_execution_documentation_path: help_page_path('user/application_security/policies/index', anchor: 'scan-execution-policy-editor')
}
end
end
......@@ -52,7 +52,7 @@ describe('NetworkPolicyEditor component', () => {
},
provide: {
networkDocumentationPath: 'path/to/docs',
noEnvironmentSvgPath: 'path/to/svg',
policyEditorEmptyStateSvgPath: 'path/to/svg',
policiesPath: '/threat-monitoring',
projectId: '21',
...provide,
......
import { shallowMount } from '@vue/test-utils';
import { GlEmptyState } from '@gitlab/ui';
import PolicyEditorLayout from 'ee/threat_monitoring/components/policy_editor/policy_editor_layout.vue';
import {
DEFAULT_SCAN_EXECUTION_POLICY,
......@@ -40,8 +41,10 @@ jest.mock('ee/threat_monitoring/components/policy_editor/scan_execution_policy/l
describe('ScanExecutionPolicyEditor', () => {
let wrapper;
const defaultProjectPath = 'path/to/project';
const policyEditorEmptyStateSvgPath = 'path/to/svg';
const scanExecutionDocumentationPath = 'path/to/docs';
const factory = ({ propsData = {} } = {}) => {
const factory = ({ propsData = {}, provide = {} } = {}) => {
wrapper = shallowMount(ScanExecutionPolicyEditor, {
propsData: {
assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT,
......@@ -49,8 +52,11 @@ describe('ScanExecutionPolicyEditor', () => {
},
provide: {
disableScanExecutionUpdate: false,
policyEditorEmptyStateSvgPath,
projectId: 1,
projectPath: defaultProjectPath,
scanExecutionDocumentationPath,
...provide,
},
});
};
......@@ -59,45 +65,59 @@ describe('ScanExecutionPolicyEditor', () => {
return factory({ propsData: { existingPolicy: mockDastScanExecutionObject, isEditing: true } });
};
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const findPolicyEditorLayout = () => wrapper.findComponent(PolicyEditorLayout);
afterEach(() => {
wrapper.destroy();
});
it('updates the policy yaml when "update-yaml" is emitted', async () => {
factory();
await wrapper.vm.$nextTick();
const newManifest = 'new yaml!';
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(
DEFAULT_SCAN_EXECUTION_POLICY,
describe('default', () => {
it('updates the policy yaml when "update-yaml" is emitted', async () => {
factory();
await wrapper.vm.$nextTick();
const newManifest = 'new yaml!';
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(
DEFAULT_SCAN_EXECUTION_POLICY,
);
await findPolicyEditorLayout().vm.$emit('update-yaml', newManifest);
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(newManifest);
});
it.each`
status | action | event | factoryFn | yamlEditorValue
${'to save a new policy'} | ${SECURITY_POLICY_ACTIONS.APPEND} | ${'save-policy'} | ${factory} | ${DEFAULT_SCAN_EXECUTION_POLICY}
${'to update an existing policy'} | ${SECURITY_POLICY_ACTIONS.REPLACE} | ${'save-policy'} | ${factoryWithExistingPolicy} | ${mockDastScanExecutionManifest}
${'to delete an existing policy'} | ${SECURITY_POLICY_ACTIONS.REMOVE} | ${'remove-policy'} | ${factoryWithExistingPolicy} | ${mockDastScanExecutionManifest}
`(
'navigates to the new merge request when "modifyPolicy" is emitted $status',
async ({ action, event, factoryFn, yamlEditorValue }) => {
factoryFn();
await wrapper.vm.$nextTick();
findPolicyEditorLayout().vm.$emit(event);
await wrapper.vm.$nextTick();
expect(modifyPolicy).toHaveBeenCalledTimes(1);
expect(modifyPolicy).toHaveBeenCalledWith({
action,
assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT,
projectPath: defaultProjectPath,
yamlEditorValue,
});
await wrapper.vm.$nextTick();
expect(visitUrl).toHaveBeenCalled();
expect(visitUrl).toHaveBeenCalledWith('/tests/-/merge_requests/2');
},
);
await findPolicyEditorLayout().vm.$emit('update-yaml', newManifest);
expect(findPolicyEditorLayout().attributes('yamleditorvalue')).toBe(newManifest);
});
it.each`
status | action | event | factoryFn | yamlEditorValue
${'to save a new policy'} | ${SECURITY_POLICY_ACTIONS.APPEND} | ${'save-policy'} | ${factory} | ${DEFAULT_SCAN_EXECUTION_POLICY}
${'to update an existing policy'} | ${SECURITY_POLICY_ACTIONS.REPLACE} | ${'save-policy'} | ${factoryWithExistingPolicy} | ${mockDastScanExecutionManifest}
${'to delete an existing policy'} | ${SECURITY_POLICY_ACTIONS.REMOVE} | ${'remove-policy'} | ${factoryWithExistingPolicy} | ${mockDastScanExecutionManifest}
`(
'navigates to the new merge request when "modifyPolicy" is emitted $status',
async ({ action, event, factoryFn, yamlEditorValue }) => {
factoryFn();
await wrapper.vm.$nextTick();
findPolicyEditorLayout().vm.$emit(event);
describe('when a user is not an owner of the project', () => {
it('displays the empty state with the appropriate properties', async () => {
factory({ provide: { disableScanExecutionUpdate: true } });
await wrapper.vm.$nextTick();
expect(modifyPolicy).toHaveBeenCalledTimes(1);
expect(modifyPolicy).toHaveBeenCalledWith({
action,
assignedPolicyProject: DEFAULT_ASSIGNED_POLICY_PROJECT,
projectPath: defaultProjectPath,
yamlEditorValue,
expect(findEmptyState().props()).toMatchObject({
primaryButtonLink: scanExecutionDocumentationPath,
svgPath: policyEditorEmptyStateSvgPath,
});
await wrapper.vm.$nextTick();
expect(visitUrl).toHaveBeenCalled();
expect(visitUrl).toHaveBeenCalledWith('/tests/-/merge_requests/2');
},
);
});
});
});
......@@ -46,13 +46,14 @@ RSpec.describe Projects::Security::PoliciesHelper do
create_agent_help_path: kind_of(String),
environments_endpoint: kind_of(String),
network_documentation_path: kind_of(String),
no_environment_svg_path: kind_of(String),
policy_editor_empty_state_svg_path: kind_of(String),
project_path: project.full_path,
project_id: project.id,
policies_path: kind_of(String),
environment_id: environment&.id,
policy: policy&.to_json,
policy_type: policy_type
policy_type: policy_type,
scan_execution_documentation_path: kind_of(String)
}
end
......
......@@ -1471,7 +1471,7 @@ msgstr ""
msgid "A merge request hasn't yet been merged"
msgstr ""
msgid "A new Auto DevOps pipeline has been created, go to %{pipelines_link_start}Pipelines page%{pipelines_link_end} for details"
msgid "A new Auto DevOps pipeline has been created, go to the Pipelines page for details"
msgstr ""
msgid "A new Release %{tag} for %{name} was published. Visit the %{release_link_start}Releases page%{release_link_end} to read more about it."
......@@ -30439,6 +30439,9 @@ msgstr ""
msgid "SecurityOrchestration|Scan execution"
msgstr ""
msgid "SecurityOrchestration|Scan execution policies can only be created by project owners."
msgstr ""
msgid "SecurityOrchestration|Scan to be performed every %{cadence} on the %{branches}"
msgstr ""
......
......@@ -669,6 +669,20 @@ describe('Design management index page', () => {
expect(variables.files).toEqual(event.clipboardData.files.map((f) => new File([f], '')));
});
it('display original file name', () => {
event.clipboardData.files = [new File([new Blob()], 'test.png', { type: 'image/png' })];
document.dispatchEvent(event);
const [{ mutation, variables }] = mockMutate.mock.calls[0];
expect(mutation).toBe(uploadDesignMutation);
expect(variables).toStrictEqual({
files: expect.any(Array),
iid: '1',
projectPath: 'project-path',
});
expect(variables.files[0].name).toEqual('test.png');
});
it('renames a design if it has an image.png filename', () => {
event.clipboardData.getData = () => 'image.png';
document.dispatchEvent(event);
......
......@@ -71,6 +71,7 @@ describe('dropzone_input', () => {
triggerPasteEvent({
types: ['text/plain', 'text/html', 'text/rtf', 'Files'],
getData: () => longFileName,
files: [new File([new Blob()], longFileName, { type: 'image/png' })],
items: [
{
kind: 'file',
......@@ -84,6 +85,24 @@ describe('dropzone_input', () => {
await waitForPromises();
expect(axiosMock.history.post[0].data.get('file').name).toHaveLength(246);
});
it('display original file name in comment box', async () => {
const axiosMock = new MockAdapter(axios);
triggerPasteEvent({
types: ['Files'],
files: [new File([new Blob()], 'test.png', { type: 'image/png' })],
items: [
{
kind: 'file',
type: 'image/png',
getAsFile: () => new Blob(),
},
],
});
axiosMock.onPost().reply(httpStatusCodes.OK, { link: { markdown: 'foo' } });
await waitForPromises();
expect(axiosMock.history.post[0].data.get('file').name).toEqual('test.png');
});
});
describe('shows error message', () => {
......
import { GlFormCheckbox, GlFormInput } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { GET_JIRA_ISSUE_TYPES_EVENT } from '~/integrations/constants';
import {
GET_JIRA_ISSUE_TYPES_EVENT,
VALIDATE_INTEGRATION_FORM_EVENT,
} from '~/integrations/constants';
import JiraIssuesFields from '~/integrations/edit/components/jira_issues_fields.vue';
import eventHub from '~/integrations/edit/event_hub';
import { createStore } from '~/integrations/edit/store';
......@@ -17,12 +20,17 @@ describe('JiraIssuesFields', () => {
upgradePlanPath: 'https://gitlab.com',
};
const createComponent = ({ isInheriting = false, props, ...options } = {}) => {
const createComponent = ({
isInheriting = false,
mountFn = mountExtended,
props,
...options
} = {}) => {
store = createStore({
defaultState: isInheriting ? {} : undefined,
});
wrapper = mountExtended(JiraIssuesFields, {
wrapper = mountFn(JiraIssuesFields, {
propsData: { ...defaultProps, ...props },
store,
stubs: ['jira-issue-creation-vulnerabilities'],
......@@ -38,12 +46,19 @@ describe('JiraIssuesFields', () => {
const findEnableCheckboxDisabled = () =>
findEnableCheckbox().find('[type=checkbox]').attributes('disabled');
const findProjectKey = () => wrapper.findComponent(GlFormInput);
const findProjectKeyFormGroup = () => wrapper.findByTestId('project-key-form-group');
const findPremiumUpgradeCTA = () => wrapper.findByTestId('premium-upgrade-cta');
const findUltimateUpgradeCTA = () => wrapper.findByTestId('ultimate-upgrade-cta');
const findJiraForVulnerabilities = () => wrapper.findByTestId('jira-for-vulnerabilities');
const findConflictWarning = () => wrapper.findByTestId('conflict-warning-text');
const setEnableCheckbox = async (isEnabled = true) =>
findEnableCheckbox().vm.$emit('input', isEnabled);
const assertProjectKeyState = (expectedStateValue) => {
expect(findProjectKey().attributes('state')).toBe(expectedStateValue);
expect(findProjectKeyFormGroup().attributes('state')).toBe(expectedStateValue);
};
describe('template', () => {
describe.each`
showJiraIssuesIntegration | showJiraVulnerabilitiesIntegration
......@@ -151,19 +166,18 @@ describe('JiraIssuesFields', () => {
});
describe('GitLab issues warning', () => {
const expectedText = 'Consider disabling GitLab issues';
it('contains warning when GitLab issues is enabled', () => {
createComponent();
expect(wrapper.text()).toContain(expectedText);
});
it('does not contain warning when GitLab issues is disabled', () => {
createComponent({ props: { gitlabIssuesEnabled: false } });
expect(wrapper.text()).not.toContain(expectedText);
});
it.each`
gitlabIssuesEnabled | scenario
${true} | ${'displays conflict warning'}
${false} | ${'does not display conflict warning'}
`(
'$scenario when `gitlabIssuesEnabled` is `$gitlabIssuesEnabled`',
({ gitlabIssuesEnabled }) => {
createComponent({ props: { gitlabIssuesEnabled } });
expect(findConflictWarning().exists()).toBe(gitlabIssuesEnabled);
},
);
});
describe('Vulnerabilities creation', () => {
......@@ -211,5 +225,44 @@ describe('JiraIssuesFields', () => {
expect(eventHubEmitSpy).toHaveBeenCalledWith(GET_JIRA_ISSUE_TYPES_EVENT);
});
});
describe('Project key input field', () => {
beforeEach(() => {
createComponent({
props: {
initialProjectKey: '',
initialEnableJiraIssues: true,
},
mountFn: shallowMountExtended,
});
});
it('sets Project Key `state` attribute to `true` by default', () => {
assertProjectKeyState('true');
});
describe('when event hub recieves `VALIDATE_INTEGRATION_FORM_EVENT` event', () => {
describe('with no project key', () => {
it('sets Project Key `state` attribute to `undefined`', async () => {
eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
await wrapper.vm.$nextTick();
assertProjectKeyState(undefined);
});
});
describe('when project key is set', () => {
it('sets Project Key `state` attribute to `true`', async () => {
eventHub.$emit(VALIDATE_INTEGRATION_FORM_EVENT);
// set the project key
await findProjectKey().vm.$emit('input', 'AB');
await wrapper.vm.$nextTick();
assertProjectKeyState('true');
});
});
});
});
});
});
import fileUpload, { getFilename } from '~/lib/utils/file_upload';
import fileUpload, { getFilename, validateImageName } from '~/lib/utils/file_upload';
describe('File upload', () => {
beforeEach(() => {
......@@ -64,13 +64,23 @@ describe('File upload', () => {
});
describe('getFilename', () => {
it('returns first value correctly', () => {
const event = {
clipboardData: {
getData: () => 'test.png\rtest.txt',
},
};
expect(getFilename(event)).toBe('test.png');
it('returns file name', () => {
const file = new File([], 'test.jpg');
expect(getFilename(file)).toBe('test.jpg');
});
});
describe('file name validator', () => {
it('validate file name', () => {
const file = new File([], 'test.jpg');
expect(validateImageName(file)).toBe('test.jpg');
});
it('illegal file name should be rename to image.png', () => {
const file = new File([], 'test<.png');
expect(validateImageName(file)).toBe('image.png');
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment