debian-mirror-gitlab/spec/frontend/ide/components/repo_editor_spec.js

772 lines
23 KiB
JavaScript
Raw Normal View History

2021-04-17 20:07:23 +05:30
import { shallowMount } from '@vue/test-utils';
2020-06-23 00:09:42 +05:30
import MockAdapter from 'axios-mock-adapter';
2021-04-17 20:07:23 +05:30
import { editor as monacoEditor, Range } from 'monaco-editor';
2021-03-11 19:13:27 +05:30
import Vue from 'vue';
import Vuex from 'vuex';
import '~/behaviors/markdown/render_gfm';
2020-10-24 23:57:45 +05:30
import waitForPromises from 'helpers/wait_for_promises';
import waitUsingRealTimer from 'helpers/wait_using_real_timer';
2021-04-17 20:07:23 +05:30
import { exampleConfigs, exampleFiles } from 'jest/ide/lib/editorconfig/mock_data';
import { EDITOR_CODE_INSTANCE_FN, EDITOR_DIFF_INSTANCE_FN } from '~/editor/constants';
2021-11-11 11:23:49 +05:30
import { EditorMarkdownExtension } from '~/editor/extensions/source_editor_markdown_ext';
2022-01-26 12:08:38 +05:30
import { EditorMarkdownPreviewExtension } from '~/editor/extensions/source_editor_markdown_livepreview_ext';
2021-09-30 23:02:18 +05:30
import SourceEditor from '~/editor/source_editor';
2020-06-23 00:09:42 +05:30
import RepoEditor from '~/ide/components/repo_editor.vue';
2020-07-28 23:09:34 +05:30
import {
leftSidebarViews,
FILE_VIEW_MODE_EDITOR,
FILE_VIEW_MODE_PREVIEW,
viewerTypes,
} from '~/ide/constants';
2021-04-17 20:07:23 +05:30
import ModelManager from '~/ide/lib/common/model_manager';
2021-03-11 19:13:27 +05:30
import service from '~/ide/services';
import { createStoreOptions } from '~/ide/stores';
import axios from '~/lib/utils/axios_utils';
2021-04-17 20:07:23 +05:30
import ContentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue';
2022-01-26 12:08:38 +05:30
import SourceEditorInstance from '~/editor/source_editor_instance';
import { spyOnApi } from 'jest/editor/helpers';
2020-06-23 00:09:42 +05:30
import { file } from '../helpers';
2021-04-17 20:07:23 +05:30
2021-10-27 15:23:28 +05:30
const PREVIEW_MARKDOWN_PATH = '/foo/bar/preview_markdown';
2021-11-11 11:23:49 +05:30
const CURRENT_PROJECT_ID = 'gitlab-org/gitlab';
2021-10-27 15:23:28 +05:30
2021-04-17 20:07:23 +05:30
const defaultFileProps = {
...file('file.txt'),
content: 'hello world',
active: true,
tempFile: true,
};
const createActiveFile = (props) => {
return {
...defaultFileProps,
...props,
};
};
const dummyFile = {
markdown: (() =>
createActiveFile({
projectId: 'namespace/project',
path: 'sample.md',
name: 'sample.md',
}))(),
binary: (() =>
createActiveFile({
name: 'file.dat',
content: '🐱', // non-ascii binary content,
}))(),
empty: (() =>
createActiveFile({
tempFile: false,
content: '',
raw: '',
}))(),
};
const prepareStore = (state, activeFile) => {
const localState = {
openFiles: [activeFile],
projects: {
2021-11-11 11:23:49 +05:30
[CURRENT_PROJECT_ID]: {
2021-04-17 20:07:23 +05:30
branches: {
2021-09-04 01:27:46 +05:30
main: {
name: 'main',
2021-04-17 20:07:23 +05:30
commit: {
id: 'abcdefgh',
},
},
},
},
},
2021-11-11 11:23:49 +05:30
currentProjectId: CURRENT_PROJECT_ID,
2021-09-04 01:27:46 +05:30
currentBranchId: 'main',
2021-04-17 20:07:23 +05:30
entries: {
[activeFile.path]: activeFile,
},
2021-10-27 15:23:28 +05:30
previewMarkdownPath: PREVIEW_MARKDOWN_PATH,
2021-04-17 20:07:23 +05:30
};
const storeOptions = createStoreOptions();
return new Vuex.Store({
...createStoreOptions(),
state: {
...storeOptions.state,
...localState,
...state,
},
});
};
2020-06-23 00:09:42 +05:30
describe('RepoEditor', () => {
2021-04-17 20:07:23 +05:30
let wrapper;
2020-06-23 00:09:42 +05:30
let vm;
2021-04-17 20:07:23 +05:30
let createInstanceSpy;
let createDiffInstanceSpy;
let createModelSpy;
2021-11-11 11:23:49 +05:30
let applyExtensionSpy;
2022-01-26 12:08:38 +05:30
let extensionsStore;
2020-06-23 00:09:42 +05:30
const waitForEditorSetup = () =>
2021-03-08 18:12:59 +05:30
new Promise((resolve) => {
2020-06-23 00:09:42 +05:30
vm.$once('editorSetup', resolve);
});
2021-04-17 20:07:23 +05:30
const createComponent = async ({ state = {}, activeFile = defaultFileProps } = {}) => {
const store = prepareStore(state, activeFile);
wrapper = shallowMount(RepoEditor, {
store,
propsData: {
file: store.state.openFiles[0],
},
mocks: {
ContentViewer,
},
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
await waitForPromises();
vm = wrapper.vm;
2022-01-26 12:08:38 +05:30
extensionsStore = wrapper.vm.globalEditor.extensionsStore;
2020-07-28 23:09:34 +05:30
jest.spyOn(vm, 'getFileData').mockResolvedValue();
jest.spyOn(vm, 'getRawFileData').mockResolvedValue();
2020-06-23 00:09:42 +05:30
};
2021-04-17 20:07:23 +05:30
const findEditor = () => wrapper.find('[data-testid="editor-container"]');
const findTabs = () => wrapper.findAll('.ide-mode-tabs .nav-links li');
const findPreviewTab = () => wrapper.find('[data-testid="preview-tab"]');
2020-06-23 00:09:42 +05:30
beforeEach(() => {
2021-09-30 23:02:18 +05:30
createInstanceSpy = jest.spyOn(SourceEditor.prototype, EDITOR_CODE_INSTANCE_FN);
createDiffInstanceSpy = jest.spyOn(SourceEditor.prototype, EDITOR_DIFF_INSTANCE_FN);
2021-04-17 20:07:23 +05:30
createModelSpy = jest.spyOn(monacoEditor, 'createModel');
2022-01-26 12:08:38 +05:30
applyExtensionSpy = jest.spyOn(SourceEditorInstance.prototype, 'use');
2021-04-17 20:07:23 +05:30
jest.spyOn(service, 'getFileData').mockResolvedValue();
jest.spyOn(service, 'getRawFileData').mockResolvedValue();
2020-06-23 00:09:42 +05:30
});
afterEach(() => {
2021-04-17 20:07:23 +05:30
jest.clearAllMocks();
// create a new model each time, otherwise tests conflict with each other
// because of same model being used in multiple tests
// eslint-disable-next-line no-undef
monaco.editor.getModels().forEach((model) => model.dispose());
wrapper.destroy();
wrapper = null;
2020-06-23 00:09:42 +05:30
});
describe('default', () => {
2021-04-17 20:07:23 +05:30
it.each`
boolVal | textVal
${true} | ${'all'}
${false} | ${'none'}
`('sets renderWhitespace to "$textVal"', async ({ boolVal, textVal } = {}) => {
await createComponent({
state: {
renderWhitespaceInCode: boolVal,
},
});
expect(vm.editorOptions.renderWhitespace).toEqual(textVal);
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
it('renders an ide container', async () => {
await createComponent();
expect(findEditor().isVisible()).toBe(true);
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
it('renders only an edit tab', async () => {
await createComponent();
const tabs = findTabs();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(tabs).toHaveLength(1);
expect(tabs.at(0).text()).toBe('Edit');
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
describe('when file is markdown', () => {
let mock;
let activeFile;
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
beforeEach(() => {
activeFile = dummyFile.markdown;
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
mock = new MockAdapter(axios);
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
mock.onPost(/(.*)\/preview_markdown/).reply(200, {
body: `<p>${defaultFileProps.content}</p>`,
2020-06-23 00:09:42 +05:30
});
});
2021-04-17 20:07:23 +05:30
afterEach(() => {
mock.restore();
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('renders an Edit and a Preview Tab', async () => {
await createComponent({ activeFile });
const tabs = findTabs();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(tabs).toHaveLength(2);
expect(tabs.at(0).text()).toBe('Edit');
expect(tabs.at(1).text()).toBe('Preview Markdown');
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('renders markdown for tempFile', async () => {
// by default files created in the spec are temp: no need for explicitly sending the param
await createComponent({ activeFile });
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
findPreviewTab().trigger('click');
await waitForPromises();
expect(wrapper.find(ContentViewer).html()).toContain(defaultFileProps.content);
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('shows no tabs when not in Edit mode', async () => {
await createComponent({
state: {
currentActivityView: leftSidebarViews.review.name,
},
activeFile,
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
expect(findTabs()).toHaveLength(0);
});
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
describe('when file is binary and not raw', () => {
beforeEach(async () => {
const activeFile = dummyFile.binary;
await createComponent({ activeFile });
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('does not render the IDE', () => {
expect(findEditor().isVisible()).toBe(false);
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('does not create an instance', () => {
expect(createInstanceSpy).not.toHaveBeenCalled();
expect(createDiffInstanceSpy).not.toHaveBeenCalled();
});
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
describe('createEditorInstance', () => {
it.each`
viewer | diffInstance
${viewerTypes.edit} | ${undefined}
${viewerTypes.diff} | ${true}
${viewerTypes.mr} | ${true}
`(
'creates instance of correct type when viewer is $viewer',
async ({ viewer, diffInstance }) => {
await createComponent({
state: { viewer },
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
const isDiff = () => {
return diffInstance ? { isDiff: true } : {};
};
expect(createInstanceSpy).toHaveBeenCalledWith(expect.objectContaining(isDiff()));
expect(createDiffInstanceSpy).toHaveBeenCalledTimes((diffInstance && 1) || 0);
},
);
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('installs the WebIDE extension', async () => {
await createComponent();
2022-01-26 12:08:38 +05:30
expect(applyExtensionSpy).toHaveBeenCalled();
const ideExtensionApi = extensionsStore.get('EditorWebIde').api;
Reflect.ownKeys(ideExtensionApi).forEach((fn) => {
expect(vm.editor[fn]).toBeDefined();
expect(vm.editor.methods[fn]).toBe('EditorWebIde');
});
2020-06-23 00:09:42 +05:30
});
2021-10-27 15:23:28 +05:30
it.each`
prefix | activeFile | viewer | shouldHaveMarkdownExtension
${'Should not'} | ${createActiveFile()} | ${viewerTypes.edit} | ${false}
${'Should'} | ${dummyFile.markdown} | ${viewerTypes.edit} | ${true}
${'Should not'} | ${dummyFile.empty} | ${viewerTypes.edit} | ${false}
${'Should not'} | ${createActiveFile()} | ${viewerTypes.diff} | ${false}
${'Should not'} | ${dummyFile.markdown} | ${viewerTypes.diff} | ${false}
${'Should not'} | ${dummyFile.empty} | ${viewerTypes.diff} | ${false}
${'Should not'} | ${createActiveFile()} | ${viewerTypes.mr} | ${false}
${'Should not'} | ${dummyFile.markdown} | ${viewerTypes.mr} | ${false}
${'Should not'} | ${dummyFile.empty} | ${viewerTypes.mr} | ${false}
`(
'$prefix install markdown extension for $activeFile.name in $viewer viewer',
async ({ activeFile, viewer, shouldHaveMarkdownExtension } = {}) => {
await createComponent({ state: { viewer }, activeFile });
2021-11-11 11:23:49 +05:30
2022-01-26 12:08:38 +05:30
if (shouldHaveMarkdownExtension) {
expect(applyExtensionSpy).toHaveBeenCalledWith({
definition: EditorMarkdownPreviewExtension,
setupOptions: { previewMarkdownPath: PREVIEW_MARKDOWN_PATH },
});
// TODO: spying on extensions causes Jest to blow up, so we have to assert on
// the public property the extension adds, as opposed to the args passed to the ctor
expect(wrapper.vm.editor.markdownPreview.path).toBe(PREVIEW_MARKDOWN_PATH);
} else {
expect(applyExtensionSpy).not.toHaveBeenCalledWith(
wrapper.vm.editor,
expect.any(EditorMarkdownExtension),
);
}
2021-10-27 15:23:28 +05:30
},
);
2021-04-17 20:07:23 +05:30
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
describe('setupEditor', () => {
beforeEach(async () => {
await createComponent();
});
2021-03-08 18:12:59 +05:30
2021-04-17 20:07:23 +05:30
it('creates new model on load', () => {
// We always create two models per file to be able to build a diff of changes
expect(createModelSpy).toHaveBeenCalledTimes(2);
// The model with the most recent changes is the last one
const [content] = createModelSpy.mock.calls[1];
expect(content).toBe(defaultFileProps.content);
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
it('does not create a new model on subsequent calls to setupEditor and re-uses the already-existing model', () => {
const existingModel = vm.model;
createModelSpy.mockClear();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
vm.setupEditor();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(createModelSpy).not.toHaveBeenCalled();
expect(vm.model).toBe(existingModel);
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('updates state with the value of the model', () => {
const newContent = 'As Gregor Samsa\n awoke one morning\n';
vm.model.setValue(newContent);
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
vm.setupEditor();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(vm.file.content).toBe(newContent);
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('sets head model as staged file', () => {
vm.modelManager.dispose();
const addModelSpy = jest.spyOn(ModelManager.prototype, 'addModel');
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
vm.$store.state.stagedFiles.push({ ...vm.file, key: 'staged' });
vm.file.staged = true;
vm.file.key = `unstaged-${vm.file.key}`;
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
vm.setupEditor();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(addModelSpy).toHaveBeenCalledWith(vm.file, vm.$store.state.stagedFiles[0]);
});
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
describe('editor updateDimensions', () => {
let updateDimensionsSpy;
beforeEach(async () => {
await createComponent();
2022-01-26 12:08:38 +05:30
const ext = extensionsStore.get('EditorWebIde');
updateDimensionsSpy = jest.fn();
spyOnApi(ext, {
updateDimensions: updateDimensionsSpy,
});
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
it('calls updateDimensions only when panelResizing is false', async () => {
expect(updateDimensionsSpy).not.toHaveBeenCalled();
expect(vm.$store.state.panelResizing).toBe(false); // default value
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
vm.$store.state.panelResizing = true;
await vm.$nextTick();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(updateDimensionsSpy).not.toHaveBeenCalled();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
vm.$store.state.panelResizing = false;
await vm.$nextTick();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(updateDimensionsSpy).toHaveBeenCalledTimes(1);
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
vm.$store.state.panelResizing = true;
await vm.$nextTick();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(updateDimensionsSpy).toHaveBeenCalledTimes(1);
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('calls updateDimensions when rightPane is toggled', async () => {
expect(updateDimensionsSpy).not.toHaveBeenCalled();
expect(vm.$store.state.rightPane.isOpen).toBe(false); // default value
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
vm.$store.state.rightPane.isOpen = true;
await vm.$nextTick();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(updateDimensionsSpy).toHaveBeenCalledTimes(1);
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
vm.$store.state.rightPane.isOpen = false;
await vm.$nextTick();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(updateDimensionsSpy).toHaveBeenCalledTimes(2);
});
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
describe('editor tabs', () => {
beforeEach(async () => {
await createComponent();
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it.each`
mode | isVisible
${'edit'} | ${true}
${'review'} | ${false}
${'commit'} | ${false}
`('tabs in $mode are $isVisible', async ({ mode, isVisible } = {}) => {
vm.$store.state.currentActivityView = leftSidebarViews[mode].name;
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
await vm.$nextTick();
expect(wrapper.find('.nav-links').exists()).toBe(isVisible);
});
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
describe('files in preview mode', () => {
let updateDimensionsSpy;
const changeViewMode = (viewMode) =>
vm.$store.dispatch('editor/updateFileEditor', {
path: vm.file.path,
data: { viewMode },
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
beforeEach(async () => {
await createComponent({
activeFile: dummyFile.markdown,
2020-06-23 00:09:42 +05:30
});
2022-01-26 12:08:38 +05:30
const ext = extensionsStore.get('EditorWebIde');
updateDimensionsSpy = jest.fn();
spyOnApi(ext, {
updateDimensions: updateDimensionsSpy,
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
changeViewMode(FILE_VIEW_MODE_PREVIEW);
await vm.$nextTick();
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('do not show the editor', () => {
expect(vm.showEditor).toBe(false);
expect(findEditor().isVisible()).toBe(false);
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('updates dimensions when switching view back to edit', async () => {
expect(updateDimensionsSpy).not.toHaveBeenCalled();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
changeViewMode(FILE_VIEW_MODE_EDITOR);
await vm.$nextTick();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(updateDimensionsSpy).toHaveBeenCalled();
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
describe('initEditor', () => {
const hideEditorAndRunFn = async () => {
jest.clearAllMocks();
jest.spyOn(vm, 'shouldHideEditor', 'get').mockReturnValue(true);
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
vm.initEditor();
await vm.$nextTick();
};
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('does not fetch file information for temp entries', async () => {
await createComponent({
activeFile: createActiveFile(),
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
expect(vm.getFileData).not.toHaveBeenCalled();
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
it('is being initialised for files without content even if shouldHideEditor is `true`', async () => {
await createComponent({
activeFile: dummyFile.empty,
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
await hideEditorAndRunFn();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(vm.getFileData).toHaveBeenCalled();
expect(vm.getRawFileData).toHaveBeenCalled();
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('does not initialize editor for files already with content when shouldHideEditor is `true`', async () => {
await createComponent({
activeFile: createActiveFile(),
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
await hideEditorAndRunFn();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(vm.getFileData).not.toHaveBeenCalled();
expect(vm.getRawFileData).not.toHaveBeenCalled();
expect(createInstanceSpy).not.toHaveBeenCalled();
});
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
describe('updates on file changes', () => {
beforeEach(async () => {
await createComponent({
activeFile: createActiveFile({
content: 'foo', // need to prevent full cycle of initEditor
}),
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
jest.spyOn(vm, 'initEditor').mockImplementation();
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
it('calls removePendingTab when old file is pending', async () => {
jest.spyOn(vm, 'shouldHideEditor', 'get').mockReturnValue(true);
jest.spyOn(vm, 'removePendingTab').mockImplementation();
2020-11-24 15:15:51 +05:30
2021-04-17 20:07:23 +05:30
const origFile = vm.file;
vm.file.pending = true;
await vm.$nextTick();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
wrapper.setProps({
file: file('testing'),
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
vm.file.content = 'foo'; // need to prevent full cycle of initEditor
await vm.$nextTick();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(vm.removePendingTab).toHaveBeenCalledWith(origFile);
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
it('does not call initEditor if the file did not change', async () => {
Vue.set(vm, 'file', vm.file);
await vm.$nextTick();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(vm.initEditor).not.toHaveBeenCalled();
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('calls initEditor when file key is changed', async () => {
expect(vm.initEditor).not.toHaveBeenCalled();
2020-07-28 23:09:34 +05:30
2021-04-17 20:07:23 +05:30
wrapper.setProps({
file: {
...vm.file,
key: 'new',
},
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
await vm.$nextTick();
2021-06-08 01:23:25 +05:30
await vm.$nextTick();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
expect(vm.initEditor).toHaveBeenCalled();
});
});
describe('populates editor with the fetched content', () => {
const createRemoteFile = (name) => ({
...file(name),
tmpFile: false,
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
beforeEach(async () => {
await createComponent();
vm.getRawFileData.mockRestore();
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('after switching viewer from edit to diff', async () => {
const f = createRemoteFile('newFile');
Vue.set(vm.$store.state.entries, f.path, f);
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
jest.spyOn(service, 'getRawFileData').mockImplementation(async () => {
expect(vm.file.loading).toBe(true);
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
// switching from edit to diff mode usually triggers editor initialization
vm.$store.state.viewer = viewerTypes.diff;
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
// we delay returning the file to make sure editor doesn't initialize before we fetch file content
await waitUsingRealTimer(30);
return 'rawFileData123\n';
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
wrapper.setProps({
file: f,
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
await waitForEditorSetup();
expect(vm.model.getModel().getValue()).toBe('rawFileData123\n');
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('after opening multiple files at the same time', async () => {
const fileA = createRemoteFile('fileA');
const aContent = 'fileA-rawContent\n';
const bContent = 'fileB-rawContent\n';
const fileB = createRemoteFile('fileB');
Vue.set(vm.$store.state.entries, fileA.path, fileA);
Vue.set(vm.$store.state.entries, fileB.path, fileB);
jest
.spyOn(service, 'getRawFileData')
.mockImplementation(async () => {
// opening fileB while the content of fileA is still being fetched
wrapper.setProps({
file: fileB,
});
return aContent;
})
.mockImplementationOnce(async () => {
// we delay returning fileB content to make sure the editor doesn't initialize prematurely
await waitUsingRealTimer(30);
return bContent;
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
wrapper.setProps({
file: fileA,
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
await waitForEditorSetup();
expect(vm.model.getModel().getValue()).toBe(bContent);
});
});
2020-07-28 23:09:34 +05:30
2021-04-17 20:07:23 +05:30
describe('onPaste', () => {
const setFileName = (name) =>
createActiveFile({
content: 'hello world\n',
name,
path: `foo/${name}`,
key: 'new',
2020-07-28 23:09:34 +05:30
});
2021-04-17 20:07:23 +05:30
const pasteImage = () => {
window.dispatchEvent(
Object.assign(new Event('paste'), {
clipboardData: {
files: [new File(['foo'], 'foo.png', { type: 'image/png' })],
},
}),
);
};
2020-07-28 23:09:34 +05:30
2021-04-17 20:07:23 +05:30
const watchState = (watched) =>
new Promise((resolve) => {
const unwatch = vm.$store.watch(watched, () => {
unwatch();
resolve();
2020-07-28 23:09:34 +05:30
});
});
2021-04-17 20:07:23 +05:30
// Pasting an image does a lot of things like using the FileReader API,
// so, waitForPromises isn't very reliable (and causes a flaky spec)
// Read more about state.watch: https://vuex.vuejs.org/api/#watch
const waitForFileContentChange = () => watchState((s) => s.entries['foo/bar.md'].content);
2020-07-28 23:09:34 +05:30
2021-04-17 20:07:23 +05:30
beforeEach(async () => {
await createComponent({
state: {
trees: {
'gitlab-org/gitlab': { tree: [] },
},
currentProjectId: 'gitlab-org',
currentBranchId: 'gitlab',
},
activeFile: setFileName('bar.md'),
2020-07-28 23:09:34 +05:30
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
vm.setupEditor();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
await waitForPromises();
// set cursor to line 2, column 1
vm.editor.setSelection(new Range(2, 1, 2, 1));
vm.editor.focus();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
jest.spyOn(vm.editor, 'hasTextFocus').mockReturnValue(true);
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it('adds an image entry to the same folder for a pasted image in a markdown file', async () => {
pasteImage();
2020-07-28 23:09:34 +05:30
2021-04-17 20:07:23 +05:30
await waitForFileContentChange();
2021-09-30 23:02:18 +05:30
expect(vm.$store.state.entries['foo/foo.png'].rawPath.startsWith('blob:')).toBe(true);
2021-04-17 20:07:23 +05:30
expect(vm.$store.state.entries['foo/foo.png']).toMatchObject({
path: 'foo/foo.png',
type: 'blob',
2021-09-30 23:02:18 +05:30
content: 'foo',
rawPath: vm.$store.state.entries['foo/foo.png'].rawPath,
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it("adds a markdown image tag to the file's contents", async () => {
pasteImage();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
await waitForFileContentChange();
expect(vm.file.content).toBe('hello world\n![foo.png](./foo.png)');
});
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
it("does not add file to state or set markdown image syntax if the file isn't markdown", async () => {
2021-09-04 01:27:46 +05:30
await wrapper.setProps({
2021-04-17 20:07:23 +05:30
file: setFileName('myfile.txt'),
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
pasteImage();
2020-06-23 00:09:42 +05:30
2021-04-17 20:07:23 +05:30
await waitForPromises();
expect(vm.$store.state.entries['foo/foo.png']).toBeUndefined();
expect(vm.file.content).toBe('hello world\n');
2020-06-23 00:09:42 +05:30
});
});
describe('fetchEditorconfigRules', () => {
it.each(exampleFiles)(
'does not fetch content from remote for .editorconfig files present locally (case %#)',
2021-04-17 20:07:23 +05:30
async ({ path, monacoRules }) => {
await createComponent({
state: {
entries: (() => {
const res = {};
exampleConfigs.forEach(({ path: configPath, content }) => {
res[configPath] = { ...file(), path: configPath, content };
});
return res;
})(),
},
activeFile: createActiveFile({
path,
key: path,
name: 'myfile.txt',
content: 'hello world',
}),
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
expect(vm.rules).toEqual(monacoRules);
expect(vm.model.options).toMatchObject(monacoRules);
expect(vm.getFileData).not.toHaveBeenCalled();
expect(vm.getRawFileData).not.toHaveBeenCalled();
2020-06-23 00:09:42 +05:30
},
);
2021-04-17 20:07:23 +05:30
it('fetches content from remote for .editorconfig files not available locally', async () => {
const activeFile = createActiveFile({
path: 'foo/bar/baz/test/my_spec.js',
key: 'foo/bar/baz/test/my_spec.js',
name: 'myfile.txt',
content: 'hello world',
});
const expectations = [
'foo/bar/baz/.editorconfig',
'foo/bar/.editorconfig',
'foo/.editorconfig',
'.editorconfig',
];
await createComponent({
state: {
entries: (() => {
const res = {
[activeFile.path]: activeFile,
};
exampleConfigs.forEach(({ path: configPath }) => {
const f = { ...file(), path: configPath };
delete f.content;
delete f.raw;
res[configPath] = f;
});
return res;
})(),
},
activeFile,
2020-06-23 00:09:42 +05:30
});
2021-04-17 20:07:23 +05:30
expect(service.getFileData.mock.calls.map(([args]) => args)).toEqual(
expectations.map((expectation) => expect.stringContaining(expectation)),
);
expect(service.getRawFileData.mock.calls.map(([args]) => args)).toEqual(
expectations.map((expectation) => expect.objectContaining({ path: expectation })),
);
2020-06-23 00:09:42 +05:30
});
});
});