summaryrefslogtreecommitdiff
path: root/spec/frontend/content_editor/components
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/content_editor/components')
-rw-r--r--spec/frontend/content_editor/components/content_editor_spec.js31
-rw-r--r--spec/frontend/content_editor/components/editor_state_observer_spec.js11
-rw-r--r--spec/frontend/content_editor/components/suggestions_dropdown_spec.js286
-rw-r--r--spec/frontend/content_editor/components/wrappers/label_spec.js36
4 files changed, 356 insertions, 8 deletions
diff --git a/spec/frontend/content_editor/components/content_editor_spec.js b/spec/frontend/content_editor/components/content_editor_spec.js
index ae52cb05eaf..c1c2a125515 100644
--- a/spec/frontend/content_editor/components/content_editor_spec.js
+++ b/spec/frontend/content_editor/components/content_editor_spec.js
@@ -13,6 +13,7 @@ import MediaBubbleMenu from '~/content_editor/components/bubble_menus/media_bubb
import TopToolbar from '~/content_editor/components/top_toolbar.vue';
import LoadingIndicator from '~/content_editor/components/loading_indicator.vue';
import waitForPromises from 'helpers/wait_for_promises';
+import { KEYDOWN_EVENT } from '~/content_editor/constants';
jest.mock('~/emoji');
@@ -26,12 +27,13 @@ describe('ContentEditor', () => {
const findEditorStateObserver = () => wrapper.findComponent(EditorStateObserver);
const findLoadingIndicator = () => wrapper.findComponent(LoadingIndicator);
const findContentEditorAlert = () => wrapper.findComponent(ContentEditorAlert);
- const createWrapper = ({ markdown } = {}) => {
+ const createWrapper = ({ markdown, autofocus } = {}) => {
wrapper = shallowMountExtended(ContentEditor, {
propsData: {
renderMarkdown,
uploadsPath,
markdown,
+ autofocus,
},
stubs: {
EditorStateObserver,
@@ -70,14 +72,22 @@ describe('ContentEditor', () => {
expect(editorContent.classes()).toContain('md');
});
- it('renders ContentEditorProvider component', async () => {
- await createWrapper();
+ it('allows setting the tiptap editor to autofocus', async () => {
+ createWrapper({ autofocus: 'start' });
+
+ await nextTick();
+
+ expect(findEditorContent().props().editor.options.autofocus).toBe('start');
+ });
+
+ it('renders ContentEditorProvider component', () => {
+ createWrapper();
expect(wrapper.findComponent(ContentEditorProvider).exists()).toBe(true);
});
- it('renders top toolbar component', async () => {
- await createWrapper();
+ it('renders top toolbar component', () => {
+ createWrapper();
expect(wrapper.findComponent(TopToolbar).exists()).toBe(true);
});
@@ -213,6 +223,17 @@ describe('ContentEditor', () => {
});
});
+ describe('when editorStateObserver emits keydown event', () => {
+ it('bubbles up event', () => {
+ const event = new Event('keydown');
+
+ createWrapper();
+
+ findEditorStateObserver().vm.$emit(KEYDOWN_EVENT, event);
+ expect(wrapper.emitted(KEYDOWN_EVENT)).toEqual([[event]]);
+ });
+ });
+
it.each`
name | component
${'formatting'} | ${FormattingBubbleMenu}
diff --git a/spec/frontend/content_editor/components/editor_state_observer_spec.js b/spec/frontend/content_editor/components/editor_state_observer_spec.js
index e8c2d8c8793..9b42f61c98c 100644
--- a/spec/frontend/content_editor/components/editor_state_observer_spec.js
+++ b/spec/frontend/content_editor/components/editor_state_observer_spec.js
@@ -4,7 +4,7 @@ import EditorStateObserver, {
tiptapToComponentMap,
} from '~/content_editor/components/editor_state_observer.vue';
import eventHubFactory from '~/helpers/event_hub_factory';
-import { ALERT_EVENT } from '~/content_editor/constants';
+import { ALERT_EVENT, KEYDOWN_EVENT } from '~/content_editor/constants';
import { createTestEditor } from '../test_utils';
describe('content_editor/components/editor_state_observer', () => {
@@ -14,6 +14,7 @@ describe('content_editor/components/editor_state_observer', () => {
let onSelectionUpdateListener;
let onTransactionListener;
let onAlertListener;
+ let onKeydownListener;
let eventHub;
const buildEditor = () => {
@@ -30,6 +31,7 @@ describe('content_editor/components/editor_state_observer', () => {
selectionUpdate: onSelectionUpdateListener,
transaction: onTransactionListener,
[ALERT_EVENT]: onAlertListener,
+ [KEYDOWN_EVENT]: onKeydownListener,
},
});
};
@@ -39,6 +41,7 @@ describe('content_editor/components/editor_state_observer', () => {
onSelectionUpdateListener = jest.fn();
onTransactionListener = jest.fn();
onAlertListener = jest.fn();
+ onKeydownListener = jest.fn();
buildEditor();
});
@@ -67,8 +70,9 @@ describe('content_editor/components/editor_state_observer', () => {
});
it.each`
- event | listener
- ${ALERT_EVENT} | ${() => onAlertListener}
+ event | listener
+ ${ALERT_EVENT} | ${() => onAlertListener}
+ ${KEYDOWN_EVENT} | ${() => onKeydownListener}
`('listens to $event event in the eventBus object', ({ event, listener }) => {
const args = {};
@@ -97,6 +101,7 @@ describe('content_editor/components/editor_state_observer', () => {
it.each`
event
${ALERT_EVENT}
+ ${KEYDOWN_EVENT}
`('removes $event event hook from eventHub', ({ event }) => {
jest.spyOn(eventHub, '$off');
jest.spyOn(eventHub, '$on');
diff --git a/spec/frontend/content_editor/components/suggestions_dropdown_spec.js b/spec/frontend/content_editor/components/suggestions_dropdown_spec.js
new file mode 100644
index 00000000000..e72eb892e74
--- /dev/null
+++ b/spec/frontend/content_editor/components/suggestions_dropdown_spec.js
@@ -0,0 +1,286 @@
+import { GlAvatarLabeled, GlDropdownItem } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import SuggestionsDropdown from '~/content_editor/components/suggestions_dropdown.vue';
+
+describe('~/content_editor/components/suggestions_dropdown', () => {
+ let wrapper;
+
+ const buildWrapper = ({ propsData } = {}) => {
+ wrapper = extendedWrapper(
+ shallowMount(SuggestionsDropdown, {
+ propsData: {
+ nodeType: 'reference',
+ command: jest.fn(),
+ ...propsData,
+ },
+ }),
+ );
+ };
+
+ const exampleUser = { username: 'root', avatar_url: 'root_avatar.png', type: 'User' };
+ const exampleIssue = { iid: 123, title: 'Test Issue' };
+ const exampleMergeRequest = { iid: 224, title: 'Test MR' };
+ const exampleMilestone1 = { iid: 21, title: '13' };
+ const exampleMilestone2 = { iid: 24, title: 'Milestone with spaces' };
+
+ const exampleCommand = {
+ name: 'due',
+ description: 'Set due date',
+ params: ['<in 2 days | this Friday | December 31st>'],
+ };
+ const exampleEpic = {
+ iid: 8884,
+ title: '❓ Remote Development | Solution validation',
+ reference: 'gitlab-org&8884',
+ };
+ const exampleLabel1 = {
+ title: 'Create',
+ color: '#E44D2A',
+ type: 'GroupLabel',
+ textColor: '#FFFFFF',
+ };
+ const exampleLabel2 = {
+ title: 'Weekly Team Announcement',
+ color: '#E44D2A',
+ type: 'GroupLabel',
+ textColor: '#FFFFFF',
+ };
+ const exampleLabel3 = {
+ title: 'devops::create',
+ color: '#E44D2A',
+ type: 'GroupLabel',
+ textColor: '#FFFFFF',
+ };
+ const exampleVulnerability = {
+ id: 60850147,
+ title: 'System procs network activity',
+ };
+ const exampleSnippet = {
+ id: 2420859,
+ title: 'Project creation QueryRecorder logs',
+ };
+ const exampleEmoji = {
+ c: 'people',
+ e: '😃',
+ d: 'smiling face with open mouth',
+ u: '6.0',
+ name: 'smiley',
+ };
+
+ const insertedEmojiProps = {
+ name: 'smiley',
+ title: 'smiling face with open mouth',
+ moji: '😃',
+ unicodeVersion: '6.0',
+ };
+
+ describe('on item select', () => {
+ it.each`
+ nodeType | referenceType | char | reference | insertedText | insertedProps
+ ${'reference'} | ${'user'} | ${'@'} | ${exampleUser} | ${`@root`} | ${{}}
+ ${'reference'} | ${'issue'} | ${'#'} | ${exampleIssue} | ${`#123`} | ${{}}
+ ${'reference'} | ${'merge_request'} | ${'!'} | ${exampleMergeRequest} | ${`!224`} | ${{}}
+ ${'reference'} | ${'milestone'} | ${'%'} | ${exampleMilestone1} | ${`%13`} | ${{}}
+ ${'reference'} | ${'milestone'} | ${'%'} | ${exampleMilestone2} | ${`%Milestone with spaces`} | ${{ originalText: '%"Milestone with spaces"' }}
+ ${'reference'} | ${'command'} | ${'/'} | ${exampleCommand} | ${'/due'} | ${{}}
+ ${'reference'} | ${'epic'} | ${'&'} | ${exampleEpic} | ${`gitlab-org&8884`} | ${{}}
+ ${'reference'} | ${'label'} | ${'~'} | ${exampleLabel1} | ${`Create`} | ${{}}
+ ${'reference'} | ${'label'} | ${'~'} | ${exampleLabel2} | ${`Weekly Team Announcement`} | ${{ originalText: '~"Weekly Team Announcement"' }}
+ ${'reference'} | ${'label'} | ${'~'} | ${exampleLabel3} | ${`devops::create`} | ${{ originalText: '~"devops::create"', text: 'devops::create' }}
+ ${'reference'} | ${'vulnerability'} | ${'[vulnerability:'} | ${exampleVulnerability} | ${`[vulnerability:60850147]`} | ${{}}
+ ${'reference'} | ${'snippet'} | ${'$'} | ${exampleSnippet} | ${`$2420859`} | ${{}}
+ ${'emoji'} | ${'emoji'} | ${':'} | ${exampleEmoji} | ${`😃`} | ${insertedEmojiProps}
+ `(
+ 'runs a command to insert the selected $referenceType',
+ ({ char, nodeType, referenceType, reference, insertedText, insertedProps }) => {
+ const commandSpy = jest.fn();
+
+ buildWrapper({
+ propsData: {
+ char,
+ command: commandSpy,
+ nodeType,
+ nodeProps: {
+ referenceType,
+ test: 'prop',
+ },
+ items: [reference],
+ },
+ });
+
+ wrapper.findComponent(GlDropdownItem).vm.$emit('click');
+
+ expect(commandSpy).toHaveBeenCalledWith(
+ expect.objectContaining({
+ text: insertedText,
+ test: 'prop',
+ ...insertedProps,
+ }),
+ );
+ },
+ );
+ });
+
+ describe('rendering user references', () => {
+ it('displays avatar labeled component', () => {
+ buildWrapper({
+ propsData: {
+ char: '@',
+ nodeProps: {
+ referenceType: 'user',
+ },
+ items: [exampleUser],
+ },
+ });
+
+ expect(wrapper.findComponent(GlAvatarLabeled).attributes()).toEqual(
+ expect.objectContaining({
+ label: exampleUser.username,
+ shape: 'circle',
+ src: exampleUser.avatar_url,
+ }),
+ );
+ });
+
+ describe.each`
+ referenceType | char | reference | displaysID
+ ${'issue'} | ${'#'} | ${exampleIssue} | ${true}
+ ${'merge_request'} | ${'!'} | ${exampleMergeRequest} | ${true}
+ ${'milestone'} | ${'%'} | ${exampleMilestone1} | ${false}
+ `('rendering $referenceType references', ({ referenceType, char, reference, displaysID }) => {
+ it(`displays ${referenceType} ID and title`, () => {
+ buildWrapper({
+ propsData: {
+ char,
+ nodeType: 'reference',
+ nodeProps: {
+ referenceType,
+ },
+ items: [reference],
+ },
+ });
+
+ if (displaysID) expect(wrapper.text()).toContain(`${reference.iid}`);
+ else expect(wrapper.text()).not.toContain(`${reference.iid}`);
+ expect(wrapper.text()).toContain(`${reference.title}`);
+ });
+ });
+
+ describe.each`
+ referenceType | char | reference
+ ${'snippet'} | ${'$'} | ${exampleSnippet}
+ ${'vulnerability'} | ${'[vulnerability:'} | ${exampleVulnerability}
+ `('rendering $referenceType references', ({ referenceType, char, reference }) => {
+ it(`displays ${referenceType} ID and title`, () => {
+ buildWrapper({
+ propsData: {
+ char,
+ nodeProps: {
+ referenceType,
+ },
+ items: [reference],
+ },
+ });
+
+ expect(wrapper.text()).toContain(`${reference.id}`);
+ expect(wrapper.text()).toContain(`${reference.title}`);
+ });
+ });
+
+ describe('rendering label references', () => {
+ it.each`
+ label | displayedTitle | displayedColor
+ ${exampleLabel1} | ${'Create'} | ${'rgb(228, 77, 42)' /* #E44D2A */}
+ ${exampleLabel2} | ${'Weekly Team Announcement'} | ${'rgb(228, 77, 42)' /* #E44D2A */}
+ ${exampleLabel3} | ${'devops::create'} | ${'rgb(228, 77, 42)' /* #E44D2A */}
+ `('displays label title and color', ({ label, displayedTitle, displayedColor }) => {
+ buildWrapper({
+ propsData: {
+ char: '~',
+ nodeProps: {
+ referenceType: 'label',
+ },
+ items: [label],
+ },
+ });
+
+ expect(wrapper.text()).toContain(displayedTitle);
+ expect(wrapper.text()).not.toContain('"'); // no quotes in the dropdown list
+ expect(wrapper.findByTestId('label-color-box').attributes().style).toEqual(
+ `background-color: ${displayedColor};`,
+ );
+ });
+ });
+
+ describe('rendering epic references', () => {
+ it('displays epic title and reference', () => {
+ buildWrapper({
+ propsData: {
+ char: '&',
+ nodeProps: {
+ referenceType: 'epic',
+ },
+ items: [exampleEpic],
+ },
+ });
+
+ expect(wrapper.text()).toContain(`${exampleEpic.reference}`);
+ expect(wrapper.text()).toContain(`${exampleEpic.title}`);
+ });
+ });
+
+ describe('rendering a command (quick action)', () => {
+ it('displays command name with a slash', () => {
+ buildWrapper({
+ propsData: {
+ char: '/',
+ nodeProps: {
+ referenceType: 'command',
+ },
+ items: [exampleCommand],
+ },
+ });
+
+ expect(wrapper.text()).toContain(`${exampleCommand.name} `);
+ });
+ });
+
+ describe('rendering emoji references', () => {
+ it('displays emoji', () => {
+ const testEmojis = [
+ {
+ c: 'people',
+ e: '😄',
+ d: 'smiling face with open mouth and smiling eyes',
+ u: '6.0',
+ name: 'smile',
+ },
+ {
+ c: 'people',
+ e: '😸',
+ d: 'grinning cat face with smiling eyes',
+ u: '6.0',
+ name: 'smile_cat',
+ },
+ { c: 'people', e: '😃', d: 'smiling face with open mouth', u: '6.0', name: 'smiley' },
+ ];
+
+ buildWrapper({
+ propsData: {
+ char: ':',
+ nodeType: 'emoji',
+ nodeProps: {},
+ items: testEmojis,
+ },
+ });
+
+ testEmojis.forEach((testEmoji) => {
+ expect(wrapper.text()).toContain(testEmoji.e);
+ expect(wrapper.text()).toContain(testEmoji.d);
+ expect(wrapper.text()).toContain(testEmoji.name);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/content_editor/components/wrappers/label_spec.js b/spec/frontend/content_editor/components/wrappers/label_spec.js
new file mode 100644
index 00000000000..9e58669b0ea
--- /dev/null
+++ b/spec/frontend/content_editor/components/wrappers/label_spec.js
@@ -0,0 +1,36 @@
+import { GlLabel } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import LabelWrapper from '~/content_editor/components/wrappers/label.vue';
+
+describe('content/components/wrappers/label', () => {
+ let wrapper;
+
+ const createWrapper = async (node = {}) => {
+ wrapper = shallowMountExtended(LabelWrapper, {
+ propsData: { node },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it("renders a GlLabel with the node's text and color", () => {
+ createWrapper({ attrs: { color: '#ff0000', text: 'foo bar', originalText: '~"foo bar"' } });
+
+ const glLabel = wrapper.findComponent(GlLabel);
+
+ expect(glLabel.props()).toMatchObject(
+ expect.objectContaining({
+ title: 'foo bar',
+ backgroundColor: '#ff0000',
+ }),
+ );
+ });
+
+ it('renders a scoped label if there is a "::" in the label', () => {
+ createWrapper({ attrs: { color: '#ff0000', text: 'foo::bar', originalText: '~"foo::bar"' } });
+
+ expect(wrapper.findComponent(GlLabel).props().scoped).toBe(true);
+ });
+});