Commit 4319f961 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 4f6c3fea fb32853a
......@@ -46,7 +46,7 @@ export default {
blobGlobalId: this.fileGlobalId,
});
this.editor.onChangeContent(debounce(this.onFileChange.bind(this), 250));
this.editor.onDidChangeModelContent(debounce(this.onFileChange.bind(this), 250));
window.requestAnimationFrame(() => {
if (!performance.getEntriesByName(SNIPPET_MARK_BLOBS_CONTENT).length) {
......
import $ from 'jquery';
import Api from '~/api';
import toast from '~/vue_shared/plugins/global_toast';
import { __ } from '~/locale';
import initPopover from '~/blob/suggest_gitlab_ci_yml';
import { deprecatedCreateFlash as Flash } from '../flash';
import FileTemplateTypeSelector from './template_selectors/type_selector';
import BlobCiYamlSelector from './template_selectors/ci_yaml_selector';
import DockerfileSelector from './template_selectors/dockerfile_selector';
import GitignoreSelector from './template_selectors/gitignore_selector';
import LicenseSelector from './template_selectors/license_selector';
import MetricsDashboardSelector from './template_selectors/metrics_dashboard_selector';
import toast from '~/vue_shared/plugins/global_toast';
import { __ } from '~/locale';
import initPopover from '~/blob/suggest_gitlab_ci_yml';
export default class FileTemplateMediator {
constructor({ editor, currentAction, projectId }) {
......
......@@ -6,12 +6,11 @@ export function initEditorLite({ el, ...args }) {
alwaysConsumeMouseWheel: false,
},
});
editor.createInstance({
return editor.createInstance({
el,
...args,
});
return editor;
}
export default () => ({});
......@@ -40,9 +40,10 @@ export default class EditBlob {
const MarkdownExtensionPromise = this.options.isMarkdown
? import('~/editor/editor_markdown_ext')
: Promise.resolve(false);
const FileTemplateExtensionPromise = import('~/editor/editor_file_template_ext');
return Promise.all([EditorPromise, MarkdownExtensionPromise])
.then(([EditorModule, MarkdownExtension]) => {
return Promise.all([EditorPromise, MarkdownExtensionPromise, FileTemplateExtensionPromise])
.then(([EditorModule, MarkdownExtension, FileTemplateExtension]) => {
const EditorLite = EditorModule.default;
const editorEl = document.getElementById('editor');
const fileNameEl =
......@@ -50,18 +51,16 @@ export default class EditBlob {
const fileContentEl = document.getElementById('file-content');
const form = document.querySelector('.js-edit-blob-form');
this.editor = new EditorLite();
const rootEditor = new EditorLite();
if (MarkdownExtension) {
this.editor.use(MarkdownExtension.default);
}
this.editor.createInstance({
this.editor = rootEditor.createInstance({
el: editorEl,
blobPath: fileNameEl.value,
blobContent: editorEl.innerText,
});
rootEditor.use([MarkdownExtension.default, FileTemplateExtension.default], this.editor);
fileNameEl.addEventListener('change', () => {
this.editor.updateModelLanguage(fileNameEl.value);
});
......
import { Position } from 'monaco-editor';
export default {
navigateFileStart() {
this.setPosition(new Position(1, 1));
},
};
import { editor as monacoEditor, languages as monacoLanguages, Position, Uri } from 'monaco-editor';
import { editor as monacoEditor, languages as monacoLanguages, Uri } from 'monaco-editor';
import { DEFAULT_THEME, themes } from '~/ide/lib/themes';
import languages from '~/ide/lib/languages';
import { defaultEditorOptions } from '~/ide/lib/editor_options';
......@@ -73,6 +73,7 @@ export default class Editor {
this.instances.splice(index, 1);
model.dispose();
});
instance.updateModelLanguage = path => this.updateModelLanguage(path);
// Reference to the model on the editor level will go away in
// https://gitlab.com/gitlab-org/gitlab/-/issues/241023
......@@ -92,10 +93,6 @@ export default class Editor {
delete this.editorEl.dataset.editorLoading;
}
onChangeContent(fn) {
return this.model.onDidChangeContent(fn);
}
updateModelLanguage(path) {
if (path === this.blobPath) return;
this.blobPath = path;
......@@ -107,46 +104,6 @@ export default class Editor {
monacoEditor.setModelLanguage(this.model, id);
}
/**
* @deprecated do not use .getValue() directly on the editor.
* This proxy-method will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/241025
* Rather use it on the exact instance
*/
getValue() {
return this.instances[0].getValue();
}
/**
* @deprecated do not use .setValue() directly on the editor.
* This proxy-method will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/241025
* Rather use it on the exact instance
*/
setValue(val) {
this.instances[0].setValue(val);
}
/**
* @deprecated do not use .focus() directly on the editor.
* This proxy-method will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/241025
* Rather use it on the exact instance
*/
focus() {
this.instances[0].focus();
}
/**
* @deprecated do not use .updateOptions() directly on the editor.
* This proxy-method will be removed in https://gitlab.com/gitlab-org/gitlab/-/issues/241025
* Rather use it on the exact instance
*/
updateOptions(options = {}) {
this.instances[0].updateOptions(options);
}
navigateFileStart() {
this.instances[0].setPosition(new Position(1, 1));
}
use(exts = [], instance = null) {
const extensions = Array.isArray(exts) ? exts : [exts];
if (instance) {
......
......@@ -53,8 +53,10 @@ module SendFileUpload
private
def image_scaling_request?(file_upload)
Feature.enabled?(:dynamic_image_resizing, current_user) &&
avatar_safe_for_scaling?(file_upload) && valid_image_scaling_width? && current_user
avatar_safe_for_scaling?(file_upload) &&
scaling_allowed_by_feature_flags?(file_upload) &&
valid_image_scaling_width? &&
current_user
end
def avatar_safe_for_scaling?(file_upload)
......@@ -68,4 +70,17 @@ module SendFileUpload
def valid_image_scaling_width?
Avatarable::ALLOWED_IMAGE_SCALER_WIDTHS.include?(params[:width]&.to_i)
end
# We use two separate feature gates to allow image resizing.
# The first, `:dynamic_image_resizing_requester`, based on the content requester.
# Enabling it for the user would allow that user to send resizing requests for any avatar.
# The second, `:dynamic_image_resizing_owner`, based on the content owner.
# Enabling it for the user would allow anyone to send resizing requests against the mentioned user avatar only.
# This flag allows us to operate on trusted data only, more in https://gitlab.com/gitlab-org/gitlab/-/issues/241533.
# Because of this, you need to enable BOTH to serve resized image,
# as you would need at least one allowed requester and at least one allowed avatar.
def scaling_allowed_by_feature_flags?(file_upload)
Feature.enabled?(:dynamic_image_resizing_requester, current_user) &&
Feature.enabled?(:dynamic_image_resizing_owner, file_upload.model)
end
end
......@@ -12,7 +12,7 @@ module MergedAtFilter
mr_metrics_scope = mr_metrics_scope.merged_before(merged_before) if merged_before.present?
scope = items.joins(:metrics).merge(mr_metrics_scope)
scope = target_project_id_filter_on_metrics(scope) if Feature.enabled?(:improved_mr_merged_at_queries)
scope = target_project_id_filter_on_metrics(scope) if Feature.enabled?(:improved_mr_merged_at_queries, default_enabled: true)
scope
end
# rubocop: enable CodeReuse/ActiveRecord
......
---
title: Format Conan package manager in Dependency List
merge_request: 40811
author:
type: added
---
title: Retrieve security dashboard URL used on Project Severity status report from
backend
merge_request: 40801
author: Kev @KevSlashNull
type: changed
---
title: Improve Productivity Analytics and Merge Request Analytics database queries
merge_request: 40838
author:
type: performance
---
name: dynamic_image_resizing_owner
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40606
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/241533
group: group::memory
type: development
default_enabled: false
---
name: dynamic_image_resizing
name: dynamic_image_resizing_requester
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37342
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/233704
group: group::memory
type: development
default_enabled: false
\ No newline at end of file
default_enabled: false
......@@ -4,4 +4,4 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/39329
rollout_issue_url:
group: group::analytics
type: development
default_enabled: false
default_enabled: true
......@@ -142,6 +142,8 @@ The chosen OmniAuth provider is now active and can be used to sign in to GitLab
## Automatically Link Existing Users to OmniAuth Users
> [Introduced in GitLab 13.4.](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36664)
You can automatically link OmniAuth users with existing GitLab users if their email addresses match by adding the following setting:
**For Omnibus installations**
......
......@@ -61,6 +61,7 @@ The following languages and dependency managers are supported:
| Language (package managers) | Supported files | Scan tool(s) |
|----------------------------- | --------------- | ------------ |
| C# .NET ([NuGet](https://www.nuget.org/) 4.9+) | [`packages.lock.json`](https://docs.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files#enabling-lock-file) | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
| C/C++ ([Conan](https://conan.io/)) | [`conan.lock`](https://docs.conan.io/en/latest/versioning/lockfiles.html) | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
| Java ([Gradle](https://gradle.org/), [Maven](https://maven.apache.org/)) | `build.gradle`, `build.gradle.kts`, `pom.xml` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
| JavaScript ([npm](https://www.npmjs.com/), [yarn](https://classic.yarnpkg.com/en/)) | `package-lock.json`, `npm-shrinkwrap.json`, `yarn.lock` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [Retire.js](https://retirejs.github.io/retire.js/) |
| Go ([Golang](https://golang.org/)) | `go.sum` | [Gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
......
......@@ -25,7 +25,7 @@ export const computeMonthRangeData = (startDate, endDate, format = dateFormats.i
for (
let dateCursor = new Date(endDate);
dateCursor >= startDate;
dateCursor.setMonth(dateCursor.getMonth() - 1)
dateCursor.setMonth(dateCursor.getMonth(), 0)
) {
const monthIndex = dateCursor.getMonth();
const year = dateCursor.getFullYear();
......
......@@ -194,7 +194,7 @@ export default {
<li v-for="project in severityGroup.projects" :key="project.id" class="gl-py-3">
<gl-link
target="_blank"
:href="`/${project.fullPath}/-/security/dashboard`"
:href="project.securityDashboardPath"
data-qa-selector="project_name_text"
>{{ project.nameWithNamespace }}</gl-link
>
......
......@@ -2,6 +2,7 @@ fragment Project on Project {
id
name
nameWithNamespace
securityDashboardPath
fullPath
avatarUrl
path
......
......@@ -62,6 +62,8 @@ module Gitlab
'Java (Maven)'
when 'composer'
'PHP (Composer)'
when 'conan'
'C/C++ (Conan)'
else
package_manager
end
......
......@@ -2,10 +2,13 @@ import * as utils from 'ee/analytics/merge_request_analytics/utils';
import { expectedMonthData } from './mock_data';
describe('computeMonthRangeData', () => {
it('returns the data as expected', () => {
const startDate = new Date('2020-05-17T00:00:00.000Z');
const endDate = new Date('2020-07-17T00:00:00.000Z');
const start = new Date('2020-05-17T00:00:00.000Z');
it.each`
startDate | endDate
${start} | ${new Date('2020-07-17T00:00:00.000Z')}
${start} | ${new Date('2020-07-31T00:00:00.000Z')}
`('returns the data as expected', ({ startDate, endDate }) => {
const monthData = utils.computeMonthRangeData(startDate, endDate);
expect(monthData).toStrictEqual(expectedMonthData);
......
......@@ -136,7 +136,7 @@ describe('Vulnerability Severity component', () => {
relatedProjects.forEach((project, i) => {
const projectLink = findProjectName(accordion).at(i);
expect(projectLink.text()).toBe(project.nameWithNamespace);
expect(projectLink.attributes('href')).toBe(`/${project.fullPath}/-/security/dashboard`);
expect(projectLink.attributes('href')).toBe(project.securityDashboardPath);
});
});
......
......@@ -100,6 +100,7 @@ export const generateProjectsWithSeverityCounts = () => [
name: 'Gitlab Test 1',
nameWithNamespace: 'Gitlab Org / Gitlab Test 1',
fullPath: 'gitlab-org/gitlab-test-1',
securityDashboardPath: '/gitlab-org/gitlab-test-1/-/security/dashboard',
vulnerabilitySeveritiesCount: {
critical: 2,
high: 0,
......@@ -114,6 +115,7 @@ export const generateProjectsWithSeverityCounts = () => [
name: 'Gitlab Test 2',
nameWithNamespace: 'Gitlab Org / Gitlab Test 2',
fullPath: 'gitlab-org/gitlab-test-2',
securityDashboardPath: '/gitlab-org/gitlab-test-2/-/security/dashboard',
vulnerabilitySeveritiesCount: {
critical: 0,
high: 1,
......@@ -128,6 +130,7 @@ export const generateProjectsWithSeverityCounts = () => [
name: 'Gitlab Test 3',
nameWithNamespace: 'Gitlab Org / Gitlab Test 3',
fullPath: 'gitlab-org/gitlab-test-3',
securityDashboardPath: '/gitlab-org/gitlab-test-3/-/security/dashboard',
vulnerabilitySeveritiesCount: {
critical: 0,
high: 0,
......@@ -142,6 +145,7 @@ export const generateProjectsWithSeverityCounts = () => [
name: 'Gitlab Test 4',
nameWithNamespace: 'Gitlab Org / Gitlab Test 4',
fullPath: 'gitlab-org/gitlab-test-4',
securityDashboardPath: '/gitlab-org/gitlab-test-4/-/security/dashboard',
vulnerabilitySeveritiesCount: {
critical: 0,
high: 0,
......@@ -156,6 +160,7 @@ export const generateProjectsWithSeverityCounts = () => [
name: 'Gitlab Test 5',
nameWithNamespace: 'Gitlab Org / Gitlab Test 5',
fullPath: 'gitlab-org/gitlab-test-5',
securityDashboardPath: '/gitlab-org/gitlab-test-5/-/security/dashboard',
vulnerabilitySeveritiesCount: {
critical: 0,
high: 0,
......@@ -170,6 +175,7 @@ export const generateProjectsWithSeverityCounts = () => [
name: 'Gitlab Test 6',
nameWithNamespace: 'Gitlab Org / Gitlab Test 6',
fullPath: 'gitlab-org/gitlab-test-6',
securityDashboardPath: '/gitlab-org/gitlab-test-6/-/security/dashboard',
vulnerabilitySeveritiesCount: {
critical: 0,
high: 0,
......@@ -182,8 +188,9 @@ export const generateProjectsWithSeverityCounts = () => [
{
id: 'gid://gitlab/Project/7',
name: 'Gitlab Test 7',
nameWithNamespace: 'Gitlab Org / Gitlab Test 7',
fullPath: 'gitlab-org/gitlab-test-7',
nameWithNamespace: 'Gitlab Org / Subgroup / Gitlab Test 7',
fullPath: 'gitlab-org/subgroup/gitlab-test-7',
securityDashboardPath: '/gitlab-org/subgroup/gitlab-test-7/-/security/dashboard',
vulnerabilitySeveritiesCount: {
critical: 0,
high: 0,
......
......@@ -60,6 +60,7 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Formatters::DependencyList do
'pip' | 'Python (pip)'
'maven' | 'Java (Maven)'
'composer' | 'PHP (Composer)'
'conan' | 'C/C++ (Conan)'
'' | ''
end
......
......@@ -71,7 +71,7 @@ module Backup
end
report_success(success)
abort Backup::Error, 'Restore failed' unless success
abort 'Restore failed' unless success
end
protected
......
......@@ -49,12 +49,13 @@ RSpec.describe SendFileUpload do
end
shared_examples 'handles image resize requests' do
let(:headers) { double }
let(:image_requester) { build(:user) }
let(:image_owner) { build(:user) }
let(:params) do
{ attachment: 'avatar.png' }
end
let(:headers) { double }
before do
allow(uploader).to receive(:image_safe_for_scaling?).and_return(true)
allow(uploader).to receive(:mounted_as).and_return(:avatar)
......@@ -64,81 +65,110 @@ RSpec.describe SendFileUpload do
# local or remote files
allow(controller).to receive(:send_file)
allow(controller).to receive(:redirect_to)
allow(controller).to receive(:current_user).and_return(image_requester)
allow(uploader).to receive(:model).and_return(image_owner)
end
context 'when feature is enabled for current user' do
let(:user) { build(:user) }
context 'when boths FFs are enabled' do
before do
stub_feature_flags(dynamic_image_resizing_requester: image_requester)
stub_feature_flags(dynamic_image_resizing_owner: image_owner)
end
it_behaves_like 'handles image resize requests allowed by FFs'
end
context 'when only FF based on content requester is enabled for current user' do
before do
stub_feature_flags(dynamic_image_resizing: user)
allow(controller).to receive(:current_user).and_return(user)
stub_feature_flags(dynamic_image_resizing_requester: image_requester)
stub_feature_flags(dynamic_image_resizing_owner: false)
end
context 'with valid width parameter' do
it 'renders OK with workhorse command header' do
expect(controller).not_to receive(:send_file)
expect(controller).to receive(:params).at_least(:once).and_return(width: '64')
expect(controller).to receive(:head).with(:ok)
expect(Gitlab::Workhorse).to receive(:send_scaled_image).with(a_string_matching('^(/.+|https://.+)'), 64, 'image/png').and_return([
Gitlab::Workhorse::SEND_DATA_HEADER, "send-scaled-img:faux"
])
expect(headers).to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, "send-scaled-img:faux")
subject
end
it_behaves_like 'bypasses image resize requests not allowed by FFs'
end
context 'when only FF based on content owner is enabled for requested avatar owner' do
before do
stub_feature_flags(dynamic_image_resizing_requester: false)
stub_feature_flags(dynamic_image_resizing_owner: image_owner)
end
context 'with missing width parameter' do
it 'does not write workhorse command header' do
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
it_behaves_like 'bypasses image resize requests not allowed by FFs'
end
subject
end
context 'when both FFs are disabled' do
before do
stub_feature_flags(dynamic_image_resizing_requester: false)
stub_feature_flags(dynamic_image_resizing_owner: false)
end
context 'with invalid width parameter' do
it 'does not write workhorse command header' do
expect(controller).to receive(:params).at_least(:once).and_return(width: 'not a number')
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
it_behaves_like 'bypasses image resize requests not allowed by FFs'
end
end
shared_examples 'bypasses image resize requests not allowed by FFs' do
it 'does not write workhorse command header' do
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
subject
end
subject
end
end
shared_examples 'handles image resize requests allowed by FFs' do
context 'with valid width parameter' do
it 'renders OK with workhorse command header' do
expect(controller).not_to receive(:send_file)
expect(controller).to receive(:params).at_least(:once).and_return(width: '64')
expect(controller).to receive(:head).with(:ok)
expect(Gitlab::Workhorse).to receive(:send_scaled_image).with(a_string_matching('^(/.+|https://.+)'), 64, 'image/png').and_return([
Gitlab::Workhorse::SEND_DATA_HEADER, "send-scaled-img:faux"
])
expect(headers).to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, "send-scaled-img:faux")
subject
end
end
context 'with width that is not allowed' do
it 'does not write workhorse command header' do
expect(controller).to receive(:params).at_least(:once).and_return(width: '63')
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
context 'with missing width parameter' do
it 'does not write workhorse command header' do
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
subject
end
subject
end
end
context 'when image file is not an avatar' do
it 'does not write workhorse command header' do
expect(uploader).to receive(:mounted_as).and_return(nil) # FileUploader is not mounted
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
context 'with invalid width parameter' do
it 'does not write workhorse command header' do
expect(controller).to receive(:params).at_least(:once).and_return(width: 'not a number')
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
subject
end
subject
end
end
context 'when image file type is not considered safe for scaling' do
it 'does not write workhorse command header' do
expect(uploader).to receive(:image_safe_for_scaling?).and_return(false)
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
context 'with width that is not allowed' do
it 'does not write workhorse command header' do
expect(controller).to receive(:params).at_least(:once).and_return(width: '63')
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
subject
end
subject
end
end
context 'when feature is disabled' do
before do
stub_feature_flags(dynamic_image_resizing: false)
context 'when image file is not an avatar' do
it 'does not write workhorse command header' do
expect(uploader).to receive(:mounted_as).and_return(nil) # FileUploader is not mounted
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
subject
end
end
context 'when image file type is not considered safe for scaling' do
it 'does not write workhorse command header' do
expect(uploader).to receive(:image_safe_for_scaling?).and_return(false)
expect(headers).not_to receive(:store).with(Gitlab::Workhorse::SEND_DATA_HEADER, /^send-scaled-img:/)
subject
......
......@@ -2,12 +2,14 @@ import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import BlobEditContent from '~/blob/components/blob_edit_content.vue';
import * as utils from '~/blob/utils';
import Editor from '~/editor/editor_lite';
jest.mock('~/editor/editor_lite');
describe('Blob Header Editing', () => {
let wrapper;
const onDidChangeModelContent = jest.fn();
const updateModelLanguage = jest.fn();
const getValue = jest.fn();
const value = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
const fileName = 'lorem.txt';
const fileGlobalId = 'snippet_777';
......@@ -24,7 +26,12 @@ describe('Blob Header Editing', () => {
}
beforeEach(() => {
jest.spyOn(utils, 'initEditorLite');
jest.spyOn(utils, 'initEditorLite').mockImplementation(() => ({
onDidChangeModelContent,
updateModelLanguage,
getValue,
dispose: jest.fn(),
}));
createComponent();
});
......@@ -34,8 +41,8 @@ describe('Blob Header Editing', () => {
});
const triggerChangeContent = val => {
jest.spyOn(Editor.prototype, 'getValue').mockReturnValue(val);
const [cb] = Editor.prototype.onChangeContent.mock.calls[0];
getValue.mockReturnValue(val);
const [cb] = onDidChangeModelContent.mock.calls[0];
cb();
......@@ -79,12 +86,12 @@ describe('Blob Header Editing', () => {
});
return nextTick().then(() => {
expect(Editor.prototype.updateModelLanguage).toHaveBeenCalledWith(newFileName);
expect(updateModelLanguage).toHaveBeenCalledWith(newFileName);
});
});
it('registers callback with editor onChangeContent', () => {
expect(Editor.prototype.onChangeContent).toHaveBeenCalledWith(expect.any(Function));
expect(onDidChangeModelContent).toHaveBeenCalledWith(expect.any(Function));
});
it('emits input event when the blob content is changed', () => {
......
import EditBlob from '~/blob_edit/edit_blob';
import EditorLite from '~/editor/editor_lite';
import MarkdownExtension from '~/editor/editor_markdown_ext';
import FileTemplateExtension from '~/editor/editor_file_template_ext';
jest.mock('~/editor/editor_lite');
jest.mock('~/editor/editor_markdown_ext');
describe('Blob Editing', () => {
const mockInstance = 'foo';
beforeEach(() => {
setFixtures(
`<div class="js-edit-blob-form"><div id="file_path"></div><div id="iditor"></div><input id="file-content"></div>`,
`<div class="js-edit-blob-form"><div id="file_path"></div><div id="editor"></div><input id="file-content"></div>`,
);
jest.spyOn(EditorLite.prototype, 'createInstance').mockReturnValue(mockInstance);
});
const initEditor = (isMarkdown = false) => {
......@@ -19,13 +22,29 @@ describe('Blob Editing', () => {
});
};
it('does not load MarkdownExtension by default', async () => {
it('loads FileTemplateExtension by default', async () => {
await initEditor();
expect(EditorLite.prototype.use).not.toHaveBeenCalled();
expect(EditorLite.prototype.use).toHaveBeenCalledWith(
expect.arrayContaining([FileTemplateExtension]),
mockInstance,
);
});
it('loads MarkdownExtension only for the markdown files', async () => {
await initEditor(true);
expect(EditorLite.prototype.use).toHaveBeenCalledWith(MarkdownExtension);
describe('Markdown', () => {
it('does not load MarkdownExtension by default', async () => {
await initEditor();
expect(EditorLite.prototype.use).not.toHaveBeenCalledWith(
expect.arrayContaining([MarkdownExtension]),
mockInstance,
);
});
it('loads MarkdownExtension only for the markdown files', async () => {
await initEditor(true);
expect(EditorLite.prototype.use).toHaveBeenCalledWith(
[MarkdownExtension, FileTemplateExtension],
mockInstance,
);
});
});
});
......@@ -78,18 +78,18 @@ describe('Markdown Extension for Editor Lite', () => {
selectSecondString();
instance.replaceSelectedText(expectedStr);
expect(editor.getValue()).toBe(`${firstLine}\n${expectedStr}\n${thirdLine}`);
expect(instance.getValue()).toBe(`${firstLine}\n${expectedStr}\n${thirdLine}`);
});
it('prepends the supplied text if no text is selected', () => {
instance.replaceSelectedText(expectedStr);
expect(editor.getValue()).toBe(`${expectedStr}${firstLine}\n${secondLine}\n${thirdLine}`);
expect(instance.getValue()).toBe(`${expectedStr}${firstLine}\n${secondLine}\n${thirdLine}`);
});
it('replaces selection with empty string if no text is supplied', () => {
selectSecondString();
instance.replaceSelectedText();
expect(editor.getValue()).toBe(`${firstLine}\n\n${thirdLine}`);
expect(instance.getValue()).toBe(`${firstLine}\n\n${thirdLine}`);
});
it('puts cursor at the end of the new string and collapses selection by default', () => {
......
......@@ -15,11 +15,13 @@ describe('Snippet editor', () => {
const updatedMockContent = 'New Foo Bar';
const mockEditor = {
createInstance: jest.fn(),
updateModelLanguage: jest.fn(),
getValue: jest.fn().mockReturnValueOnce(updatedMockContent),
};
Editor.mockImplementation(() => mockEditor);
const createInstance = jest.fn().mockImplementation(() => ({ ...mockEditor }));
Editor.mockImplementation(() => ({
createInstance,
}));
function setUpFixture(name, content) {
setHTMLFixture(`
......@@ -56,7 +58,7 @@ describe('Snippet editor', () => {
});
it('correctly initializes Editor', () => {
expect(mockEditor.createInstance).toHaveBeenCalledWith({
expect(createInstance).toHaveBeenCalledWith({
el: editorEl,
blobPath: mockName,
blobContent: mockContent,
......
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