diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-27 00:11:25 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-27 00:11:25 +0300 |
commit | 9f6c0ac9fd6921bc0b5190ed4d4eaf0ab1e1f2d7 (patch) | |
tree | 92f397ea19f1fec56491c99ce3b7862bb86fe8ce /spec/frontend | |
parent | a56971e97f0385640c2f3568017bd221897b78ef (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend')
9 files changed, 513 insertions, 37 deletions
diff --git a/spec/frontend/content_editor/extensions/attachment_spec.js b/spec/frontend/content_editor/extensions/attachment_spec.js index 97a33b28cdd..9e7c31cca72 100644 --- a/spec/frontend/content_editor/extensions/attachment_spec.js +++ b/spec/frontend/content_editor/extensions/attachment_spec.js @@ -7,9 +7,17 @@ import Image from '~/content_editor/extensions/image'; import Link from '~/content_editor/extensions/link'; import Loading from '~/content_editor/extensions/loading'; import httpStatus from '~/lib/utils/http_status'; -import { loadMarkdownApiResult } from '../markdown_processing_examples'; import { createTestEditor, createDocBuilder } from '../test_utils'; +const PROJECT_WIKI_ATTACHMENT_IMAGE_HTML = `<p data-sourcepos="1:1-1:27" dir="auto"> + <a class="no-attachment-icon" href="/group1/project1/-/wikis/test-file.png" target="_blank" rel="noopener noreferrer" data-canonical-src="test-file.png"> + <img alt="test-file" class="lazy" data-src="/group1/project1/-/wikis/test-file.png" data-canonical-src="test-file.png"> + </a> +</p>`; +const PROJECT_WIKI_ATTACHMENT_LINK_HTML = `<p data-sourcepos="1:1-1:26" dir="auto"> + <a href="/group1/project1/-/wikis/test-file.zip" data-canonical-src="test-file.zip">test-file</a> +</p>`; + describe('content_editor/extensions/attachment', () => { let tiptapEditor; let eq; @@ -76,7 +84,7 @@ describe('content_editor/extensions/attachment', () => { const base64EncodedFile = 'data:image/png;base64,Zm9v'; beforeEach(() => { - renderMarkdown.mockResolvedValue(loadMarkdownApiResult('project_wiki_attachment_image')); + renderMarkdown.mockResolvedValue(PROJECT_WIKI_ATTACHMENT_IMAGE_HTML); }); describe('when uploading succeeds', () => { @@ -151,7 +159,7 @@ describe('content_editor/extensions/attachment', () => { }); describe('when the file has a zip (or any other attachment) mime type', () => { - const markdownApiResult = loadMarkdownApiResult('project_wiki_attachment_link'); + const markdownApiResult = PROJECT_WIKI_ATTACHMENT_LINK_HTML; beforeEach(() => { renderMarkdown.mockResolvedValue(markdownApiResult); diff --git a/spec/frontend/content_editor/extensions/blockquote_spec.js b/spec/frontend/content_editor/extensions/blockquote_spec.js new file mode 100644 index 00000000000..c5b5044352d --- /dev/null +++ b/spec/frontend/content_editor/extensions/blockquote_spec.js @@ -0,0 +1,19 @@ +import { multilineInputRegex } from '~/content_editor/extensions/blockquote'; + +describe('content_editor/extensions/blockquote', () => { + describe.each` + input | matches + ${'>>> '} | ${true} + ${' >>> '} | ${true} + ${'\t>>> '} | ${true} + ${'>> '} | ${false} + ${'>>>x '} | ${false} + ${'> '} | ${false} + `('multilineInputRegex', ({ input, matches }) => { + it(`${matches ? 'matches' : 'does not match'}: "${input}"`, () => { + const match = new RegExp(multilineInputRegex).test(input); + + expect(match).toBe(matches); + }); + }); +}); diff --git a/spec/frontend/content_editor/extensions/code_block_highlight_spec.js b/spec/frontend/content_editor/extensions/code_block_highlight_spec.js index 828fdb224fc..6a0a0c76825 100644 --- a/spec/frontend/content_editor/extensions/code_block_highlight_spec.js +++ b/spec/frontend/content_editor/extensions/code_block_highlight_spec.js @@ -1,9 +1,15 @@ import CodeBlockHighlight from '~/content_editor/extensions/code_block_highlight'; -import { loadMarkdownApiResult } from '../markdown_processing_examples'; import { createTestEditor } from '../test_utils'; +const CODE_BLOCK_HTML = `<pre class="code highlight js-syntax-highlight language-javascript" lang="javascript" v-pre="true"> + <code> + <span id="LC1" class="line" lang="javascript"> + <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello world</span><span class="dl">'</span><span class="p">)</span> + </span> + </code> +</pre>`; + describe('content_editor/extensions/code_block_highlight', () => { - let codeBlockHtmlFixture; let parsedCodeBlockHtmlFixture; let tiptapEditor; @@ -12,10 +18,9 @@ describe('content_editor/extensions/code_block_highlight', () => { beforeEach(() => { tiptapEditor = createTestEditor({ extensions: [CodeBlockHighlight] }); - codeBlockHtmlFixture = loadMarkdownApiResult('code_block'); - parsedCodeBlockHtmlFixture = parseHTML(codeBlockHtmlFixture); + parsedCodeBlockHtmlFixture = parseHTML(CODE_BLOCK_HTML); - tiptapEditor.commands.setContent(codeBlockHtmlFixture); + tiptapEditor.commands.setContent(CODE_BLOCK_HTML); }); it('extracts language and params attributes from Markdown API output', () => { diff --git a/spec/frontend/content_editor/services/markdown_serializer_spec.js b/spec/frontend/content_editor/services/markdown_serializer_spec.js index 868473faa14..cd4560677f8 100644 --- a/spec/frontend/content_editor/services/markdown_serializer_spec.js +++ b/spec/frontend/content_editor/services/markdown_serializer_spec.js @@ -8,6 +8,7 @@ import HardBreak from '~/content_editor/extensions/hard_break'; import Heading from '~/content_editor/extensions/heading'; import HorizontalRule from '~/content_editor/extensions/horizontal_rule'; import Image from '~/content_editor/extensions/image'; +import InlineDiff from '~/content_editor/extensions/inline_diff'; import Italic from '~/content_editor/extensions/italic'; import Link from '~/content_editor/extensions/link'; import ListItem from '~/content_editor/extensions/list_item'; @@ -18,6 +19,8 @@ import Table from '~/content_editor/extensions/table'; import TableCell from '~/content_editor/extensions/table_cell'; import TableHeader from '~/content_editor/extensions/table_header'; import TableRow from '~/content_editor/extensions/table_row'; +import TaskItem from '~/content_editor/extensions/task_item'; +import TaskList from '~/content_editor/extensions/task_list'; import Text from '~/content_editor/extensions/text'; import markdownSerializer from '~/content_editor/services/markdown_serializer'; import { createTestEditor, createDocBuilder } from '../test_utils'; @@ -40,6 +43,7 @@ const tiptapEditor = createTestEditor({ Heading, HorizontalRule, Image, + InlineDiff, Italic, Link, ListItem, @@ -50,6 +54,8 @@ const tiptapEditor = createTestEditor({ TableCell, TableHeader, TableRow, + TaskItem, + TaskList, Text, ], }); @@ -67,6 +73,7 @@ const { hardBreak, horizontalRule, image, + inlineDiff, italic, link, listItem, @@ -77,6 +84,8 @@ const { tableCell, tableHeader, tableRow, + taskItem, + taskList, }, } = createDocBuilder({ tiptapEditor, @@ -91,6 +100,7 @@ const { heading: { nodeType: Heading.name }, horizontalRule: { nodeType: HorizontalRule.name }, image: { nodeType: Image.name }, + inlineDiff: { markType: InlineDiff.name }, italic: { nodeType: Italic.name }, link: { markType: Link.name }, listItem: { nodeType: ListItem.name }, @@ -101,6 +111,8 @@ const { tableCell: { nodeType: TableCell.name }, tableHeader: { nodeType: TableHeader.name }, tableRow: { nodeType: TableRow.name }, + taskItem: { nodeType: TaskItem.name }, + taskList: { nodeType: TaskList.name }, }, }); @@ -111,6 +123,25 @@ const serialize = (...content) => }); describe('markdownSerializer', () => { + it('correctly serializes bold', () => { + expect(serialize(paragraph(bold('bold')))).toBe('**bold**'); + }); + + it('correctly serializes italics', () => { + expect(serialize(paragraph(italic('italics')))).toBe('_italics_'); + }); + + it('correctly serializes inline diff', () => { + expect( + serialize( + paragraph( + inlineDiff({ type: 'addition' }, '+30 lines'), + inlineDiff({ type: 'deletion' }, '-10 lines'), + ), + ), + ).toBe('{++30 lines+}{--10 lines-}'); + }); + it('correctly serializes a line break', () => { expect(serialize(paragraph('hello', hardBreak(), 'world'))).toBe('hello\\\nworld'); }); @@ -121,6 +152,12 @@ describe('markdownSerializer', () => { ); }); + it('correctly serializes a plain URL link', () => { + expect(serialize(paragraph(link({ href: 'https://example.com' }, 'https://example.com')))).toBe( + '<https://example.com>', + ); + }); + it('correctly serializes a link with a title', () => { expect( serialize( @@ -129,6 +166,16 @@ describe('markdownSerializer', () => { ).toBe('[example url](https://example.com "click this link")'); }); + it('correctly serializes a plain URL link with a title', () => { + expect( + serialize( + paragraph( + link({ href: 'https://example.com', title: 'link title' }, 'https://example.com'), + ), + ), + ).toBe('[https://example.com](https://example.com "link title")'); + }); + it('correctly serializes a link with a canonicalSrc', () => { expect( serialize( @@ -146,6 +193,115 @@ describe('markdownSerializer', () => { ).toBe('[download file](file.zip "click here to download")'); }); + it('correctly serializes strikethrough', () => { + expect(serialize(paragraph(strike('deleted content')))).toBe('~~deleted content~~'); + }); + + it('correctly serializes blockquotes with hard breaks', () => { + expect(serialize(blockquote('some text', hardBreak(), hardBreak(), 'new line'))).toBe( + ` +> some text\\ +> \\ +> new line + `.trim(), + ); + }); + + it('correctly serializes blockquote with multiple block nodes', () => { + expect(serialize(blockquote(paragraph('some paragraph'), codeBlock('var x = 10;')))).toBe( + ` +> some paragraph +> +> \`\`\` +> var x = 10; +> \`\`\` + `.trim(), + ); + }); + + it('correctly serializes a multiline blockquote', () => { + expect( + serialize( + blockquote( + { multiline: true }, + paragraph('some paragraph with ', bold('bold')), + codeBlock('var y = 10;'), + ), + ), + ).toBe( + ` +>>> +some paragraph with **bold** + +\`\`\` +var y = 10; +\`\`\` + +>>> + `.trim(), + ); + }); + + it('correctly serializes a code block with language', () => { + expect( + serialize( + codeBlock( + { language: 'json' }, + 'this is not really json but just trying out whether this case works or not', + ), + ), + ).toBe( + ` +\`\`\`json +this is not really json but just trying out whether this case works or not +\`\`\` + `.trim(), + ); + }); + + it('correctly serializes emoji', () => { + expect(serialize(paragraph(emoji({ name: 'dog' })))).toBe(':dog:'); + }); + + it('correctly serializes headings', () => { + expect( + serialize( + heading({ level: 1 }, 'Heading 1'), + heading({ level: 2 }, 'Heading 2'), + heading({ level: 3 }, 'Heading 3'), + heading({ level: 4 }, 'Heading 4'), + heading({ level: 5 }, 'Heading 5'), + heading({ level: 6 }, 'Heading 6'), + ), + ).toBe( + ` +# Heading 1 + +## Heading 2 + +### Heading 3 + +#### Heading 4 + +##### Heading 5 + +###### Heading 6 + `.trim(), + ); + }); + + it('correctly serializes horizontal rule', () => { + expect(serialize(horizontalRule(), horizontalRule(), horizontalRule())).toBe( + ` +--- + +--- + +--- + `.trim(), + ); + }); + it('correctly serializes an image', () => { expect(serialize(paragraph(image({ src: 'img.jpg', alt: 'foo bar' })))).toBe( '![foo bar](img.jpg)', @@ -173,6 +329,210 @@ describe('markdownSerializer', () => { ).toBe('![this is an image](file.png "foo bar baz")'); }); + it('correctly serializes bullet list', () => { + expect( + serialize( + bulletList( + listItem(paragraph('list item 1')), + listItem(paragraph('list item 2')), + listItem(paragraph('list item 3')), + ), + ), + ).toBe( + ` +* list item 1 +* list item 2 +* list item 3 + `.trim(), + ); + }); + + it('correctly serializes bullet list with different bullet styles', () => { + expect( + serialize( + bulletList( + { bullet: '+' }, + listItem(paragraph('list item 1')), + listItem(paragraph('list item 2')), + listItem( + paragraph('list item 3'), + bulletList( + { bullet: '-' }, + listItem(paragraph('sub-list item 1')), + listItem(paragraph('sub-list item 2')), + ), + ), + ), + ), + ).toBe( + ` ++ list item 1 ++ list item 2 ++ list item 3 + - sub-list item 1 + - sub-list item 2 + `.trim(), + ); + }); + + it('correctly serializes a numeric list', () => { + expect( + serialize( + orderedList( + listItem(paragraph('list item 1')), + listItem(paragraph('list item 2')), + listItem(paragraph('list item 3')), + ), + ), + ).toBe( + ` +1. list item 1 +2. list item 2 +3. list item 3 + `.trim(), + ); + }); + + it('correctly serializes a numeric list with parens', () => { + expect( + serialize( + orderedList( + { parens: true }, + listItem(paragraph('list item 1')), + listItem(paragraph('list item 2')), + listItem(paragraph('list item 3')), + ), + ), + ).toBe( + ` +1) list item 1 +2) list item 2 +3) list item 3 + `.trim(), + ); + }); + + it('correctly serializes a numeric list with a different start order', () => { + expect( + serialize( + orderedList( + { start: 17 }, + listItem(paragraph('list item 1')), + listItem(paragraph('list item 2')), + listItem(paragraph('list item 3')), + ), + ), + ).toBe( + ` +17. list item 1 +18. list item 2 +19. list item 3 + `.trim(), + ); + }); + + it('correctly serializes a numeric list with an invalid start order', () => { + expect( + serialize( + orderedList( + { start: NaN }, + listItem(paragraph('list item 1')), + listItem(paragraph('list item 2')), + listItem(paragraph('list item 3')), + ), + ), + ).toBe( + ` +1. list item 1 +2. list item 2 +3. list item 3 + `.trim(), + ); + }); + + it('correctly serializes a bullet list inside an ordered list', () => { + expect( + serialize( + orderedList( + { start: 17 }, + listItem(paragraph('list item 1')), + listItem(paragraph('list item 2')), + listItem( + paragraph('list item 3'), + bulletList( + listItem(paragraph('sub-list item 1')), + listItem(paragraph('sub-list item 2')), + ), + ), + ), + ), + ).toBe( + // notice that 4 space indent works fine in this case, + // when it usually wouldn't + ` +17. list item 1 +18. list item 2 +19. list item 3 + * sub-list item 1 + * sub-list item 2 + `.trim(), + ); + }); + + it('correctly serializes a task list', () => { + expect( + serialize( + taskList( + taskItem({ checked: true }, paragraph('list item 1')), + taskItem(paragraph('list item 2')), + taskItem( + paragraph('list item 3'), + taskList( + taskItem({ checked: true }, paragraph('sub-list item 1')), + taskItem(paragraph('sub-list item 2')), + ), + ), + ), + ), + ).toBe( + ` +* [x] list item 1 +* [ ] list item 2 +* [ ] list item 3 + * [x] sub-list item 1 + * [ ] sub-list item 2 + `.trim(), + ); + }); + + it('correctly serializes a numeric task list + with start order', () => { + expect( + serialize( + taskList( + { numeric: true }, + taskItem({ checked: true }, paragraph('list item 1')), + taskItem(paragraph('list item 2')), + taskItem( + paragraph('list item 3'), + taskList( + { numeric: true, start: 1351, parens: true }, + taskItem({ checked: true }, paragraph('sub-list item 1')), + taskItem(paragraph('sub-list item 2')), + ), + ), + ), + ), + ).toBe( + ` +1. [x] list item 1 +2. [ ] list item 2 +3. [ ] list item 3 + 1351) [x] sub-list item 1 + 1352) [ ] sub-list item 2 + `.trim(), + ); + }); + it('correctly serializes a table with inline content', () => { expect( serialize( diff --git a/spec/frontend/content_editor/services/markdown_sourcemap_spec.js b/spec/frontend/content_editor/services/markdown_sourcemap_spec.js index 0ef822942ea..a6ebe204078 100644 --- a/spec/frontend/content_editor/services/markdown_sourcemap_spec.js +++ b/spec/frontend/content_editor/services/markdown_sourcemap_spec.js @@ -4,9 +4,20 @@ import ListItem from '~/content_editor/extensions/list_item'; import Paragraph from '~/content_editor/extensions/paragraph'; import markdownSerializer from '~/content_editor/services/markdown_serializer'; import { getMarkdownSource } from '~/content_editor/services/markdown_sourcemap'; -import { loadMarkdownApiResult, loadMarkdownApiExample } from '../markdown_processing_examples'; import { createTestEditor, createDocBuilder } from '../test_utils'; +const BULLET_LIST_MARKDOWN = `+ list item 1 ++ list item 2 + - embedded list item 3`; +const BULLET_LIST_HTML = `<ul data-sourcepos="1:1-3:24" dir="auto"> + <li data-sourcepos="1:1-1:13">list item 1</li> + <li data-sourcepos="2:1-3:24">list item 2 + <ul data-sourcepos="3:3-3:24"> + <li data-sourcepos="3:3-3:24">embedded list item 3</li> + </ul> + </li> +</ul>`; + const SourcemapExtension = Extension.create({ // lets add `source` attribute to every element using `getMarkdownSource` addGlobalAttributes() { @@ -44,11 +55,11 @@ const { describe('content_editor/services/markdown_sourcemap', () => { it('gets markdown source for a rendered HTML element', async () => { const deserialized = await markdownSerializer({ - render: () => loadMarkdownApiResult('bullet_list_style_3'), + render: () => BULLET_LIST_HTML, serializerConfig: {}, }).deserialize({ schema: tiptapEditor.schema, - content: loadMarkdownApiExample('bullet_list_style_3'), + content: BULLET_LIST_MARKDOWN, }); const expected = doc( diff --git a/spec/frontend/environments/edit_environment_spec.js b/spec/frontend/environments/edit_environment_spec.js index 3e7f5dd5ff4..2c8c054ccbd 100644 --- a/spec/frontend/environments/edit_environment_spec.js +++ b/spec/frontend/environments/edit_environment_spec.js @@ -15,15 +15,12 @@ const DEFAULT_OPTS = { projectEnvironmentsPath: '/projects/environments', updateEnvironmentPath: '/proejcts/environments/1', }, - propsData: { environment: { name: 'foo', externalUrl: 'https://foo.example.com' } }, + propsData: { environment: { id: '0', name: 'foo', external_url: 'https://foo.example.com' } }, }; describe('~/environments/components/edit.vue', () => { let wrapper; let mock; - let name; - let url; - let form; const createWrapper = (opts = {}) => mountExtended(EditEnvironment, { @@ -34,9 +31,6 @@ describe('~/environments/components/edit.vue', () => { beforeEach(() => { mock = new MockAdapter(axios); wrapper = createWrapper(); - name = wrapper.findByLabelText('Name'); - url = wrapper.findByLabelText('External URL'); - form = wrapper.findByRole('form', { name: 'Edit environment' }); }); afterEach(() => { @@ -44,19 +38,22 @@ describe('~/environments/components/edit.vue', () => { wrapper.destroy(); }); + const findNameInput = () => wrapper.findByLabelText('Name'); + const findExternalUrlInput = () => wrapper.findByLabelText('External URL'); + const findForm = () => wrapper.findByRole('form', { name: 'Edit environment' }); + const showsLoading = () => wrapper.find(GlLoadingIcon).exists(); const submitForm = async (expected, response) => { mock .onPut(DEFAULT_OPTS.provide.updateEnvironmentPath, { - name: expected.name, external_url: expected.url, + id: '0', }) .reply(...response); - await name.setValue(expected.name); - await url.setValue(expected.url); + await findExternalUrlInput().setValue(expected.url); - await form.trigger('submit'); + await findForm().trigger('submit'); await waitForPromises(); }; @@ -65,18 +62,8 @@ describe('~/environments/components/edit.vue', () => { expect(header.exists()).toBe(true); }); - it.each` - input | value - ${() => name} | ${'test'} - ${() => url} | ${'https://example.org'} - `('it changes the value of the input to $value', async ({ input, value }) => { - await input().setValue(value); - - expect(input().element.value).toBe(value); - }); - it('shows loader after form is submitted', async () => { - const expected = { name: 'test', url: 'https://google.ca' }; + const expected = { url: 'https://google.ca' }; expect(showsLoading()).toBe(false); @@ -86,7 +73,7 @@ describe('~/environments/components/edit.vue', () => { }); it('submits the updated environment on submit', async () => { - const expected = { name: 'test', url: 'https://google.ca' }; + const expected = { url: 'https://google.ca' }; await submitForm(expected, [200, { path: '/test' }]); @@ -94,11 +81,24 @@ describe('~/environments/components/edit.vue', () => { }); it('shows errors on error', async () => { - const expected = { name: 'test', url: 'https://google.ca' }; + const expected = { url: 'https://google.ca' }; - await submitForm(expected, [400, { message: ['name taken'] }]); + await submitForm(expected, [400, { message: ['uh oh!'] }]); - expect(createFlash).toHaveBeenCalledWith({ message: 'name taken' }); + expect(createFlash).toHaveBeenCalledWith({ message: 'uh oh!' }); expect(showsLoading()).toBe(false); }); + + it('renders a disabled "Name" field', () => { + const nameInput = findNameInput(); + + expect(nameInput.attributes().disabled).toBe('disabled'); + expect(nameInput.element.value).toBe('foo'); + }); + + it('renders an "External URL" field', () => { + const urlInput = findExternalUrlInput(); + + expect(urlInput.element.value).toBe('https://foo.example.com'); + }); }); diff --git a/spec/frontend/environments/environment_form_spec.js b/spec/frontend/environments/environment_form_spec.js index ed8fda71dab..f1af08bcf32 100644 --- a/spec/frontend/environments/environment_form_spec.js +++ b/spec/frontend/environments/environment_form_spec.js @@ -102,4 +102,52 @@ describe('~/environments/components/form.vue', () => { wrapper = createWrapper({ loading: true }); expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); }); + describe('when a new environment is being created', () => { + beforeEach(() => { + wrapper = createWrapper({ + environment: { + name: '', + externalUrl: '', + }, + }); + }); + + it('renders an enabled "Name" field', () => { + const nameInput = wrapper.findByLabelText('Name'); + + expect(nameInput.attributes().disabled).toBeUndefined(); + expect(nameInput.element.value).toBe(''); + }); + + it('renders an "External URL" field', () => { + const urlInput = wrapper.findByLabelText('External URL'); + + expect(urlInput.element.value).toBe(''); + }); + }); + + describe('when an existing environment is being edited', () => { + beforeEach(() => { + wrapper = createWrapper({ + environment: { + id: 1, + name: 'test', + externalUrl: 'https://example.com', + }, + }); + }); + + it('renders a disabled "Name" field', () => { + const nameInput = wrapper.findByLabelText('Name'); + + expect(nameInput.attributes().disabled).toBe('disabled'); + expect(nameInput.element.value).toBe('test'); + }); + + it('renders an "External URL" field', () => { + const urlInput = wrapper.findByLabelText('External URL'); + + expect(urlInput.element.value).toBe('https://example.com'); + }); + }); }); diff --git a/spec/frontend/fixtures/api_markdown.yml b/spec/frontend/fixtures/api_markdown.yml index ff9da33c116..32bbb5db745 100644 --- a/spec/frontend/fixtures/api_markdown.yml +++ b/spec/frontend/fixtures/api_markdown.yml @@ -99,6 +99,11 @@ 1. list item 1 2. list item 2 3. list item 3 +- name: ordered_list_with_start_order + markdown: |- + 134. list item 1 + 135. list item 2 + 136. list item 3 - name: task_list markdown: |- * [x] hello @@ -115,6 +120,11 @@ 1. [ ] of nested 1. [x] task list 2. [ ] items +- name: ordered_task_list_with_order + markdown: |- + 4893. [x] hello + 4894. [x] world + 4895. [ ] example - name: image markdown: '![alt text](https://gitlab.com/logo.png)' - name: hard_break diff --git a/spec/frontend/lib/utils/dom_utils_spec.js b/spec/frontend/lib/utils/dom_utils_spec.js index 7c4c20e651f..cb8b1c7ca9a 100644 --- a/spec/frontend/lib/utils/dom_utils_spec.js +++ b/spec/frontend/lib/utils/dom_utils_spec.js @@ -5,6 +5,7 @@ import { parseBooleanDataAttributes, isElementVisible, isElementHidden, + getParents, } from '~/lib/utils/dom_utils'; const TEST_MARGIN = 5; @@ -193,4 +194,18 @@ describe('DOM Utils', () => { }); }, ); + + describe('getParents', () => { + it('gets all parents of an element', () => { + const el = document.createElement('div'); + el.innerHTML = '<p><span><strong><mark>hello world'; + + expect(getParents(el.querySelector('mark'))).toEqual([ + el.querySelector('strong'), + el.querySelector('span'), + el.querySelector('p'), + el, + ]); + }); + }); }); |