diff options
Diffstat (limited to 'spec/frontend/static_site_editor')
12 files changed, 335 insertions, 26 deletions
diff --git a/spec/frontend/static_site_editor/components/edit_area_spec.js b/spec/frontend/static_site_editor/components/edit_area_spec.js index f4be911171e..7e90b53dd07 100644 --- a/spec/frontend/static_site_editor/components/edit_area_spec.js +++ b/spec/frontend/static_site_editor/components/edit_area_spec.js @@ -6,11 +6,13 @@ import { EDITOR_TYPES } from '~/vue_shared/components/rich_content_editor/consta import EditArea from '~/static_site_editor/components/edit_area.vue'; import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue'; import EditHeader from '~/static_site_editor/components/edit_header.vue'; +import EditDrawer from '~/static_site_editor/components/edit_drawer.vue'; import UnsavedChangesConfirmDialog from '~/static_site_editor/components/unsaved_changes_confirm_dialog.vue'; import { sourceContentTitle as title, - sourceContent as content, + sourceContentYAML as content, + sourceContentHeaderObjYAML as headerSettings, sourceContentBody as body, returnUrl, } from '../mock_data'; @@ -36,6 +38,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => { }; const findEditHeader = () => wrapper.find(EditHeader); + const findEditDrawer = () => wrapper.find(EditDrawer); const findRichContentEditor = () => wrapper.find(RichContentEditor); const findPublishToolbar = () => wrapper.find(PublishToolbar); const findUnsavedChangesConfirmDialog = () => wrapper.find(UnsavedChangesConfirmDialog); @@ -46,6 +49,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => { afterEach(() => { wrapper.destroy(); + wrapper = null; }); it('renders edit header', () => { @@ -53,6 +57,10 @@ describe('~/static_site_editor/components/edit_area.vue', () => { expect(findEditHeader().props('title')).toBe(title); }); + it('renders edit drawer', () => { + expect(findEditDrawer().exists()).toBe(true); + }); + it('renders rich content editor with a format pass', () => { expect(findRichContentEditor().exists()).toBe(true); expect(findRichContentEditor().props('content')).toBe(formattedBody); @@ -81,7 +89,7 @@ describe('~/static_site_editor/components/edit_area.vue', () => { it('updates parsedSource with new content', () => { const newContent = 'New content'; - const spySyncParsedSource = jest.spyOn(wrapper.vm.parsedSource, 'sync'); + const spySyncParsedSource = jest.spyOn(wrapper.vm.parsedSource, 'syncContent'); findRichContentEditor().vm.$emit('input', newContent); @@ -148,11 +156,88 @@ describe('~/static_site_editor/components/edit_area.vue', () => { }); }); + describe('when content has front matter', () => { + it('renders a closed edit drawer', () => { + expect(findEditDrawer().exists()).toBe(true); + expect(findEditDrawer().props('isOpen')).toBe(false); + }); + + it('opens the edit drawer', () => { + findPublishToolbar().vm.$emit('editSettings'); + + return wrapper.vm.$nextTick().then(() => { + expect(findEditDrawer().props('isOpen')).toBe(true); + }); + }); + + it('closes the edit drawer', () => { + findEditDrawer().vm.$emit('close'); + + return wrapper.vm.$nextTick().then(() => { + expect(findEditDrawer().props('isOpen')).toBe(false); + }); + }); + + it('forwards the matter settings when the drawer is open', () => { + findPublishToolbar().vm.$emit('editSettings'); + + jest.spyOn(wrapper.vm.parsedSource, 'matter').mockReturnValueOnce(headerSettings); + + return wrapper.vm.$nextTick().then(() => { + expect(findEditDrawer().props('settings')).toEqual(headerSettings); + }); + }); + + it('enables toolbar submit button', () => { + expect(findPublishToolbar().props('hasSettings')).toBe(true); + }); + + it('syncs matter changes regardless of edit mode', () => { + const newSettings = { title: 'test' }; + const spySyncParsedSource = jest.spyOn(wrapper.vm.parsedSource, 'syncMatter'); + + findEditDrawer().vm.$emit('updateSettings', newSettings); + + expect(spySyncParsedSource).toHaveBeenCalledWith(newSettings); + }); + + it('syncs matter changes to content in markdown mode', () => { + wrapper.setData({ editorMode: EDITOR_TYPES.markdown }); + + const newSettings = { title: 'test' }; + + findEditDrawer().vm.$emit('updateSettings', newSettings); + + return wrapper.vm.$nextTick().then(() => { + expect(findRichContentEditor().props('content')).toContain('title: test'); + }); + }); + }); + + describe('when content lacks front matter', () => { + beforeEach(() => { + buildWrapper({ content: body }); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('does not render edit drawer', () => { + expect(findEditDrawer().exists()).toBe(false); + }); + + it('does not enable toolbar submit button', () => { + expect(findPublishToolbar().props('hasSettings')).toBe(false); + }); + }); + describe('when content is submitted', () => { it('should format the content', () => { findPublishToolbar().vm.$emit('submit', content); expect(wrapper.emitted('submit')[0][0].content).toBe(`${content} format-pass format-pass`); + expect(wrapper.emitted('submit').length).toBe(1); }); }); }); diff --git a/spec/frontend/static_site_editor/components/edit_drawer_spec.js b/spec/frontend/static_site_editor/components/edit_drawer_spec.js new file mode 100644 index 00000000000..c47eef59997 --- /dev/null +++ b/spec/frontend/static_site_editor/components/edit_drawer_spec.js @@ -0,0 +1,68 @@ +import { shallowMount } from '@vue/test-utils'; + +import { GlDrawer } from '@gitlab/ui'; + +import EditDrawer from '~/static_site_editor/components/edit_drawer.vue'; +import FrontMatterControls from '~/static_site_editor/components/front_matter_controls.vue'; + +describe('~/static_site_editor/components/edit_drawer.vue', () => { + let wrapper; + + const buildWrapper = (propsData = {}) => { + wrapper = shallowMount(EditDrawer, { + propsData: { + isOpen: false, + settings: { title: 'Some title' }, + ...propsData, + }, + }); + }; + + const findFrontMatterControls = () => wrapper.find(FrontMatterControls); + const findGlDrawer = () => wrapper.find(GlDrawer); + + beforeEach(() => { + buildWrapper(); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + it('renders the GlDrawer', () => { + expect(findGlDrawer().exists()).toBe(true); + }); + + it('renders the FrontMatterControls', () => { + expect(findFrontMatterControls().exists()).toBe(true); + }); + + it('forwards the settings to FrontMatterControls', () => { + expect(findFrontMatterControls().props('settings')).toBe(wrapper.props('settings')); + }); + + it('is closed by default', () => { + expect(findGlDrawer().props('open')).toBe(false); + }); + + it('can open', () => { + buildWrapper({ isOpen: true }); + + expect(findGlDrawer().props('open')).toBe(true); + }); + + it.each` + event | payload | finderFn + ${'close'} | ${undefined} | ${findGlDrawer} + ${'updateSettings'} | ${{ some: 'data' }} | ${findFrontMatterControls} + `( + 'forwards the emitted $event event from the $finderFn with $payload', + ({ event, payload, finderFn }) => { + finderFn().vm.$emit(event, payload); + + expect(wrapper.emitted(event)[0][0]).toBe(payload); + expect(wrapper.emitted(event).length).toBe(1); + }, + ); +}); diff --git a/spec/frontend/static_site_editor/components/front_matter_controls_spec.js b/spec/frontend/static_site_editor/components/front_matter_controls_spec.js new file mode 100644 index 00000000000..82e8fad643e --- /dev/null +++ b/spec/frontend/static_site_editor/components/front_matter_controls_spec.js @@ -0,0 +1,78 @@ +import { shallowMount } from '@vue/test-utils'; + +import { GlFormGroup } from '@gitlab/ui'; +import { humanize } from '~/lib/utils/text_utility'; + +import FrontMatterControls from '~/static_site_editor/components/front_matter_controls.vue'; + +describe('~/static_site_editor/components/front_matter_controls.vue', () => { + let wrapper; + + // TODO Refactor and update `sourceContentHeaderObjYAML` in mock_data when !41230 lands + const settings = { + layout: 'handbook-page-toc', + title: 'Handbook', + twitter_image: '/images/tweets/handbook-gitlab.png', + suppress_header: true, + extra_css: ['sales-and-free-trial-common.css', 'form-to-resource.css'], + }; + + const buildWrapper = (propsData = {}) => { + wrapper = shallowMount(FrontMatterControls, { + propsData: { + settings, + ...propsData, + }, + }); + }; + + beforeEach(() => { + buildWrapper(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + it('should render only the supported GlFormGroup types', () => { + expect(wrapper.findAll(GlFormGroup)).toHaveLength(3); + }); + + it.each` + key + ${'layout'} + ${'title'} + ${'twitter_image'} + `('renders field when key is $key', ({ key }) => { + const glFormGroup = wrapper.find(`#sse-front-matter-form-group-${key}`); + const glFormInput = wrapper.find(`#sse-front-matter-control-${key}`); + + expect(glFormGroup.exists()).toBe(true); + expect(glFormGroup.attributes().label).toBe(humanize(key)); + + expect(glFormInput.exists()).toBe(true); + expect(glFormInput.attributes().value).toBe(settings[key]); + }); + + it.each` + key + ${'suppress_header'} + ${'extra_css'} + `('does not render field when key is $key', ({ key }) => { + const glFormInput = wrapper.find(`#sse-front-matter-control-${key}`); + + expect(glFormInput.exists()).toBe(false); + }); + + it('emits updated settings when nested control updates', () => { + const elId = `#sse-front-matter-control-title`; + const glFormInput = wrapper.find(elId); + const newTitle = 'New title'; + + glFormInput.vm.$emit('input', newTitle); + + const newSettings = { ...settings, title: newTitle }; + + expect(wrapper.emitted('updateSettings')[0][0]).toMatchObject(newSettings); + }); +}); diff --git a/spec/frontend/static_site_editor/components/publish_toolbar_spec.js b/spec/frontend/static_site_editor/components/publish_toolbar_spec.js index 5428ed23266..9ba7e4a94d1 100644 --- a/spec/frontend/static_site_editor/components/publish_toolbar_spec.js +++ b/spec/frontend/static_site_editor/components/publish_toolbar_spec.js @@ -1,5 +1,4 @@ import { shallowMount } from '@vue/test-utils'; -import { GlButton } from '@gitlab/ui'; import PublishToolbar from '~/static_site_editor/components/publish_toolbar.vue'; @@ -11,6 +10,7 @@ describe('Static Site Editor Toolbar', () => { const buildWrapper = (propsData = {}) => { wrapper = shallowMount(PublishToolbar, { propsData: { + hasSettings: false, saveable: false, ...propsData, }, @@ -18,7 +18,8 @@ describe('Static Site Editor Toolbar', () => { }; const findReturnUrlLink = () => wrapper.find({ ref: 'returnUrlLink' }); - const findSaveChangesButton = () => wrapper.find(GlButton); + const findSaveChangesButton = () => wrapper.find({ ref: 'submit' }); + const findEditSettingsButton = () => wrapper.find({ ref: 'settings' }); beforeEach(() => { buildWrapper(); @@ -28,6 +29,10 @@ describe('Static Site Editor Toolbar', () => { wrapper.destroy(); }); + it('does not render Settings button', () => { + expect(findEditSettingsButton().exists()).toBe(false); + }); + it('renders Submit Changes button', () => { expect(findSaveChangesButton().exists()).toBe(true); }); @@ -51,6 +56,14 @@ describe('Static Site Editor Toolbar', () => { expect(findReturnUrlLink().attributes('href')).toBe(returnUrl); }); + describe('when providing settings CTA', () => { + it('enables Submit Changes button', () => { + buildWrapper({ hasSettings: true }); + + expect(findEditSettingsButton().exists()).toBe(true); + }); + }); + describe('when saveable', () => { it('enables Submit Changes button', () => { buildWrapper({ saveable: true }); diff --git a/spec/frontend/static_site_editor/graphql/resolvers/file_spec.js b/spec/frontend/static_site_editor/graphql/resolvers/file_spec.js index 8504d09e0f1..24651543650 100644 --- a/spec/frontend/static_site_editor/graphql/resolvers/file_spec.js +++ b/spec/frontend/static_site_editor/graphql/resolvers/file_spec.js @@ -5,7 +5,7 @@ import { projectId, sourcePath, sourceContentTitle as title, - sourceContent as content, + sourceContentYAML as content, } from '../../mock_data'; jest.mock('~/static_site_editor/services/load_source_content', () => jest.fn()); diff --git a/spec/frontend/static_site_editor/graphql/resolvers/submit_content_changes_spec.js b/spec/frontend/static_site_editor/graphql/resolvers/submit_content_changes_spec.js index 515b5394594..750b777cf5d 100644 --- a/spec/frontend/static_site_editor/graphql/resolvers/submit_content_changes_spec.js +++ b/spec/frontend/static_site_editor/graphql/resolvers/submit_content_changes_spec.js @@ -6,7 +6,7 @@ import { projectId as project, sourcePath, username, - sourceContent as content, + sourceContentYAML as content, savedContentMeta, } from '../../mock_data'; diff --git a/spec/frontend/static_site_editor/mock_data.js b/spec/frontend/static_site_editor/mock_data.js index 96de9b73af0..d861f6c9cd7 100644 --- a/spec/frontend/static_site_editor/mock_data.js +++ b/spec/frontend/static_site_editor/mock_data.js @@ -1,19 +1,22 @@ -export const sourceContentHeader = `--- +export const sourceContentHeaderYAML = `--- layout: handbook-page-toc title: Handbook -twitter_image: '/images/tweets/handbook-gitlab.png' +twitter_image: /images/tweets/handbook-gitlab.png ---`; -export const sourceContentSpacing = ` -`; +export const sourceContentHeaderObjYAML = { + layout: 'handbook-page-toc', + title: 'Handbook', + twitter_image: '/images/tweets/handbook-gitlab.png', +}; +export const sourceContentSpacing = `\n`; export const sourceContentBody = `## On this page {:.no_toc .hidden-md .hidden-lg} - TOC {:toc .hidden-md .hidden-lg} -![image](path/to/image1.png) -`; -export const sourceContent = `${sourceContentHeader}${sourceContentSpacing}${sourceContentBody}`; +![image](path/to/image1.png)`; +export const sourceContentYAML = `${sourceContentHeaderYAML}${sourceContentSpacing}${sourceContentBody}`; export const sourceContentTitle = 'Handbook'; export const username = 'gitlabuser'; diff --git a/spec/frontend/static_site_editor/pages/home_spec.js b/spec/frontend/static_site_editor/pages/home_spec.js index c5473596df8..41f8a1075c0 100644 --- a/spec/frontend/static_site_editor/pages/home_spec.js +++ b/spec/frontend/static_site_editor/pages/home_spec.js @@ -13,7 +13,7 @@ import { TRACKING_ACTION_INITIALIZE_EDITOR } from '~/static_site_editor/constant import { projectId as project, returnUrl, - sourceContent as content, + sourceContentYAML as content, sourceContentTitle as title, sourcePath, username, diff --git a/spec/frontend/static_site_editor/services/formatter_spec.js b/spec/frontend/static_site_editor/services/formatter_spec.js index b7600798db9..9e9c4bbd171 100644 --- a/spec/frontend/static_site_editor/services/formatter_spec.js +++ b/spec/frontend/static_site_editor/services/formatter_spec.js @@ -1,6 +1,6 @@ import formatter from '~/static_site_editor/services/formatter'; -describe('formatter', () => { +describe('static_site_editor/services/formatter', () => { const source = `Some text <br> @@ -23,4 +23,17 @@ And even more text`; it('removes extraneous <br> tags', () => { expect(formatter(source)).toMatch(sourceWithoutBrTags); }); + + describe('ordered lists with incorrect content indentation', () => { + it.each` + input | result + ${'12. ordered list item\n13.Next ordered list item'} | ${'12. ordered list item\n13.Next ordered list item'} + ${'12. ordered list item\n - Next ordered list item'} | ${'12. ordered list item\n - Next ordered list item'} + ${'12. ordered list item\n - Next ordered list item'} | ${'12. ordered list item\n - Next ordered list item'} + ${'12. ordered list item\n Next ordered list item'} | ${'12. ordered list item\n Next ordered list item'} + ${'1. ordered list item\n Next ordered list item'} | ${'1. ordered list item\n Next ordered list item'} + `('\ntransforms\n$input \nto\n$result', ({ input, result }) => { + expect(formatter(input)).toBe(result); + }); + }); }); diff --git a/spec/frontend/static_site_editor/services/load_source_content_spec.js b/spec/frontend/static_site_editor/services/load_source_content_spec.js index 87893bb7a6e..54061b7a503 100644 --- a/spec/frontend/static_site_editor/services/load_source_content_spec.js +++ b/spec/frontend/static_site_editor/services/load_source_content_spec.js @@ -2,7 +2,12 @@ import Api from '~/api'; import loadSourceContent from '~/static_site_editor/services/load_source_content'; -import { sourceContent, sourceContentTitle, projectId, sourcePath } from '../mock_data'; +import { + sourceContentYAML as sourceContent, + sourceContentTitle, + projectId, + sourcePath, +} from '../mock_data'; describe('loadSourceContent', () => { describe('requesting source content succeeds', () => { diff --git a/spec/frontend/static_site_editor/services/parse_source_file_spec.js b/spec/frontend/static_site_editor/services/parse_source_file_spec.js index 4588548e614..ab9e63f4cd2 100644 --- a/spec/frontend/static_site_editor/services/parse_source_file_spec.js +++ b/spec/frontend/static_site_editor/services/parse_source_file_spec.js @@ -1,14 +1,29 @@ -import { sourceContent as content, sourceContentBody as body } from '../mock_data'; +import { + sourceContentYAML as content, + sourceContentHeaderYAML as yamlFrontMatter, + sourceContentHeaderObjYAML as yamlFrontMatterObj, + sourceContentBody as body, +} from '../mock_data'; import parseSourceFile from '~/static_site_editor/services/parse_source_file'; -describe('parseSourceFile', () => { +describe('static_site_editor/services/parse_source_file', () => { const contentComplex = [content, content, content].join(''); const complexBody = [body, content, content].join(''); const edit = 'and more'; const newContent = `${content} ${edit}`; const newContentComplex = `${contentComplex} ${edit}`; + describe('unmodified front matter', () => { + it.each` + parsedSource + ${parseSourceFile(content)} + ${parseSourceFile(contentComplex)} + `('returns $targetFrontMatter when frontMatter queried', ({ parsedSource }) => { + expect(parsedSource.matter()).toEqual(yamlFrontMatterObj); + }); + }); + describe('unmodified content', () => { it.each` parsedSource @@ -34,21 +49,50 @@ describe('parseSourceFile', () => { ); }); + describe('modified front matter', () => { + const newYamlFrontMatter = '---\nnewKey: newVal\n---'; + const newYamlFrontMatterObj = { newKey: 'newVal' }; + const contentWithNewFrontMatter = content.replace(yamlFrontMatter, newYamlFrontMatter); + const contentComplexWithNewFrontMatter = contentComplex.replace( + yamlFrontMatter, + newYamlFrontMatter, + ); + + it.each` + parsedSource | targetContent + ${parseSourceFile(content)} | ${contentWithNewFrontMatter} + ${parseSourceFile(contentComplex)} | ${contentComplexWithNewFrontMatter} + `( + 'returns the correct front matter and modified content', + ({ parsedSource, targetContent }) => { + expect(parsedSource.matter()).toMatchObject(yamlFrontMatterObj); + + parsedSource.syncMatter(newYamlFrontMatterObj); + + expect(parsedSource.matter()).toMatchObject(newYamlFrontMatterObj); + expect(parsedSource.content()).toBe(targetContent); + }, + ); + }); + describe('modified content', () => { const newBody = `${body} ${edit}`; const newComplexBody = `${complexBody} ${edit}`; it.each` - parsedSource | isModified | targetRaw | targetBody - ${parseSourceFile(content)} | ${false} | ${content} | ${body} - ${parseSourceFile(content)} | ${true} | ${newContent} | ${newBody} - ${parseSourceFile(contentComplex)} | ${false} | ${contentComplex} | ${complexBody} - ${parseSourceFile(contentComplex)} | ${true} | ${newContentComplex} | ${newComplexBody} + parsedSource | hasMatter | isModified | targetRaw | targetBody + ${parseSourceFile(content)} | ${true} | ${false} | ${content} | ${body} + ${parseSourceFile(content)} | ${true} | ${true} | ${newContent} | ${newBody} + ${parseSourceFile(contentComplex)} | ${true} | ${false} | ${contentComplex} | ${complexBody} + ${parseSourceFile(contentComplex)} | ${true} | ${true} | ${newContentComplex} | ${newComplexBody} + ${parseSourceFile(body)} | ${false} | ${false} | ${body} | ${body} + ${parseSourceFile(body)} | ${false} | ${true} | ${newBody} | ${newBody} `( 'returns $isModified after a $targetRaw sync', - ({ parsedSource, isModified, targetRaw, targetBody }) => { - parsedSource.sync(targetRaw); + ({ parsedSource, hasMatter, isModified, targetRaw, targetBody }) => { + parsedSource.syncContent(targetRaw); + expect(parsedSource.hasMatter()).toBe(hasMatter); expect(parsedSource.isModified()).toBe(isModified); expect(parsedSource.content()).toBe(targetRaw); expect(parsedSource.content(true)).toBe(targetBody); diff --git a/spec/frontend/static_site_editor/services/submit_content_changes_spec.js b/spec/frontend/static_site_editor/services/submit_content_changes_spec.js index 645ccedf7e7..d464e6b1895 100644 --- a/spec/frontend/static_site_editor/services/submit_content_changes_spec.js +++ b/spec/frontend/static_site_editor/services/submit_content_changes_spec.js @@ -20,7 +20,7 @@ import { commitMultipleResponse, createMergeRequestResponse, sourcePath, - sourceContent as content, + sourceContentYAML as content, trackingCategory, images, } from '../mock_data'; |