Commit 783795bb authored by Paul Slaughter's avatar Paul Slaughter

Update snippet and blob component to use global id

- Also use `onChangeContent`
- And dispose on component destroy
parent a3f33674
...@@ -20,6 +20,13 @@ export default { ...@@ -20,6 +20,13 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
// This is used to help uniquely create a monaco model
// even if two blob's share a file path.
fileGlobalId: {
type: String,
required: false,
default: '',
},
}, },
data() { data() {
return { return {
...@@ -36,7 +43,11 @@ export default { ...@@ -36,7 +43,11 @@ export default {
el: this.$refs.editor, el: this.$refs.editor,
blobPath: this.fileName, blobPath: this.fileName,
blobContent: this.value, blobContent: this.value,
blobGlobalId: this.fileGlobalId,
}); });
this.editor.onChangeContent(debounce(this.onFileChange.bind(this), 250));
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
if (!performance.getEntriesByName(SNIPPET_MARK_BLOBS_CONTENT).length) { if (!performance.getEntriesByName(SNIPPET_MARK_BLOBS_CONTENT).length) {
performance.mark(SNIPPET_MARK_BLOBS_CONTENT); performance.mark(SNIPPET_MARK_BLOBS_CONTENT);
...@@ -45,16 +56,19 @@ export default { ...@@ -45,16 +56,19 @@ export default {
} }
}); });
}, },
beforeDestroy() {
this.editor.dispose();
},
methods: { methods: {
triggerFileChange: debounce(function debouncedFileChange() { onFileChange() {
this.$emit('input', this.editor.getValue()); this.$emit('input', this.editor.getValue());
}, 250), },
}, },
}; };
</script> </script>
<template> <template>
<div class="file-content code"> <div class="file-content code">
<div id="editor" ref="editor" data-editor-loading @keyup="triggerFileChange"> <div id="editor" ref="editor" data-editor-loading>
<pre class="editor-loading-content">{{ value }}</pre> <pre class="editor-loading-content">{{ value }}</pre>
</div> </div>
</div> </div>
......
...@@ -101,7 +101,7 @@ export default { ...@@ -101,7 +101,7 @@ export default {
size="lg" size="lg"
class="loading-animation prepend-top-20 append-bottom-20" class="loading-animation prepend-top-20 append-bottom-20"
/> />
<blob-content-edit v-else v-model="content" :file-name="filePath" /> <blob-content-edit v-else v-model="content" :file-global-id="id" :file-name="filePath" />
</div> </div>
</div> </div>
</template> </template>
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import BlobEditContent from '~/blob/components/blob_edit_content.vue'; import BlobEditContent from '~/blob/components/blob_edit_content.vue';
import { initEditorLite } from '~/blob/utils'; import * as utils from '~/blob/utils';
import Editor from '~/editor/editor_lite';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
jest.mock('~/blob/utils', () => ({ jest.mock('~/editor/editor_lite');
initEditorLite: jest.fn(),
}));
describe('Blob Header Editing', () => { describe('Blob Header Editing', () => {
let wrapper; let wrapper;
const value = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'; const value = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
const fileName = 'lorem.txt'; const fileName = 'lorem.txt';
const fileGlobalId = 'snippet_777';
function createComponent(props = {}) { function createComponent(props = {}) {
wrapper = shallowMount(BlobEditContent, { wrapper = shallowMount(BlobEditContent, {
propsData: { propsData: {
value, value,
fileName, fileName,
fileGlobalId,
...props, ...props,
}, },
}); });
} }
beforeEach(() => { beforeEach(() => {
jest.spyOn(utils, 'initEditorLite');
createComponent(); createComponent();
}); });
...@@ -30,6 +33,15 @@ describe('Blob Header Editing', () => { ...@@ -30,6 +33,15 @@ describe('Blob Header Editing', () => {
wrapper.destroy(); wrapper.destroy();
}); });
const triggerChangeContent = val => {
jest.spyOn(Editor.prototype, 'getValue').mockReturnValue(val);
const [cb] = Editor.prototype.onChangeContent.mock.calls[0];
cb();
jest.runOnlyPendingTimers();
};
describe('rendering', () => { describe('rendering', () => {
it('matches the snapshot', () => { it('matches the snapshot', () => {
expect(wrapper.element).toMatchSnapshot(); expect(wrapper.element).toMatchSnapshot();
...@@ -51,18 +63,15 @@ describe('Blob Header Editing', () => { ...@@ -51,18 +63,15 @@ describe('Blob Header Editing', () => {
it('initialises Editor Lite', () => { it('initialises Editor Lite', () => {
const el = wrapper.find({ ref: 'editor' }).element; const el = wrapper.find({ ref: 'editor' }).element;
expect(initEditorLite).toHaveBeenCalledWith({ expect(utils.initEditorLite).toHaveBeenCalledWith({
el, el,
blobPath: fileName, blobPath: fileName,
blobGlobalId: fileGlobalId,
blobContent: value, blobContent: value,
}); });
}); });
it('reacts to the changes in fileName', () => { it('reacts to the changes in fileName', () => {
wrapper.vm.editor = {
updateModelLanguage: jest.fn(),
};
const newFileName = 'ipsum.txt'; const newFileName = 'ipsum.txt';
wrapper.setProps({ wrapper.setProps({
...@@ -70,21 +79,20 @@ describe('Blob Header Editing', () => { ...@@ -70,21 +79,20 @@ describe('Blob Header Editing', () => {
}); });
return nextTick().then(() => { return nextTick().then(() => {
expect(wrapper.vm.editor.updateModelLanguage).toHaveBeenCalledWith(newFileName); expect(Editor.prototype.updateModelLanguage).toHaveBeenCalledWith(newFileName);
}); });
}); });
it('registers callback with editor onChangeContent', () => {
expect(Editor.prototype.onChangeContent).toHaveBeenCalledWith(expect.any(Function));
});
it('emits input event when the blob content is changed', () => { it('emits input event when the blob content is changed', () => {
const editorEl = wrapper.find({ ref: 'editor' }); expect(wrapper.emitted().input).toBeUndefined();
wrapper.vm.editor = {
getValue: jest.fn().mockReturnValue(value),
};
editorEl.trigger('keyup'); triggerChangeContent(value);
return nextTick().then(() => { expect(wrapper.emitted().input).toEqual([[value]]);
expect(wrapper.emitted().input[0]).toEqual([value]);
});
}); });
}); });
}); });
...@@ -17,6 +17,7 @@ exports[`Snippet Blob Edit component rendering matches the snapshot 1`] = ` ...@@ -17,6 +17,7 @@ exports[`Snippet Blob Edit component rendering matches the snapshot 1`] = `
/> />
<blob-content-edit-stub <blob-content-edit-stub
fileglobalid="0a3d"
filename="lorem.txt" filename="lorem.txt"
value="Lorem ipsum dolor sit amet, consectetur adipiscing elit." value="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
/> />
......
...@@ -51,6 +51,10 @@ describe('Snippet Blob Edit component', () => { ...@@ -51,6 +51,10 @@ describe('Snippet Blob Edit component', () => {
} }
beforeEach(() => { beforeEach(() => {
// This component generates a random id. Soon this will be abstracted away, but for now let's make this deterministic.
// see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38855
jest.spyOn(Math, 'random').mockReturnValue(0.04);
axiosMock = new AxiosMockAdapter(axios); axiosMock = new AxiosMockAdapter(axios);
createComponent(); createComponent();
}); });
...@@ -68,7 +72,11 @@ describe('Snippet Blob Edit component', () => { ...@@ -68,7 +72,11 @@ describe('Snippet Blob Edit component', () => {
it('renders required components', () => { it('renders required components', () => {
expect(findComponent(BlobHeaderEdit).exists()).toBe(true); expect(findComponent(BlobHeaderEdit).exists()).toBe(true);
expect(findComponent(BlobContentEdit).exists()).toBe(true); expect(findComponent(BlobContentEdit).props()).toEqual({
fileGlobalId: expect.any(String),
fileName: '',
value: '',
});
}); });
it('renders loader if existing blob is supplied but no content is fetched yet', () => { it('renders loader if existing blob is supplied but no content is fetched yet', () => {
......
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