diff options
Diffstat (limited to 'spec/frontend/vue_shared/components/markdown_drawer')
3 files changed, 290 insertions, 0 deletions
diff --git a/spec/frontend/vue_shared/components/markdown_drawer/markdown_drawer_spec.js b/spec/frontend/vue_shared/components/markdown_drawer/markdown_drawer_spec.js new file mode 100644 index 00000000000..8edcb905096 --- /dev/null +++ b/spec/frontend/vue_shared/components/markdown_drawer/markdown_drawer_spec.js @@ -0,0 +1,205 @@ +import { GlDrawer, GlAlert, GlSkeletonLoader } from '@gitlab/ui'; +import { nextTick } from 'vue'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import MarkdownDrawer, { cache } from '~/vue_shared/components/markdown_drawer/markdown_drawer.vue'; +import { getRenderedMarkdown } from '~/vue_shared/components/markdown_drawer/utils/fetch'; +import { contentTop } from '~/lib/utils/common_utils'; + +jest.mock('~/vue_shared/components/markdown_drawer/utils/fetch', () => ({ + getRenderedMarkdown: jest.fn().mockReturnValue({ + title: 'test title test', + body: `<div id="content-body"> + <div class="documentation md gl-mt-3"> + test body + </div> + </div>`, + }), +})); + +jest.mock('~/lib/utils/common_utils', () => ({ + contentTop: jest.fn(), +})); + +describe('MarkdownDrawer', () => { + let wrapper; + const defaultProps = { + documentPath: 'user/search/global_search/advanced_search_syntax.json', + }; + + const createComponent = (props) => { + wrapper = shallowMountExtended(MarkdownDrawer, { + propsData: { + ...defaultProps, + ...props, + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + Object.keys(cache).forEach((key) => delete cache[key]); + }); + + const findDrawer = () => wrapper.findComponent(GlDrawer); + const findAlert = () => wrapper.findComponent(GlAlert); + const findSkeleton = () => wrapper.findComponent(GlSkeletonLoader); + const findDrawerTitle = () => wrapper.findComponent('[data-testid="title-element"]'); + const findDrawerBody = () => wrapper.findComponent({ ref: 'content-element' }); + + describe('component', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders correctly', () => { + expect(findDrawer().exists()).toBe(true); + expect(findDrawerTitle().text()).toBe('test title test'); + expect(findDrawerBody().text()).toBe('test body'); + }); + }); + + describe.each` + hasNavbar | navbarHeight + ${false} | ${0} + ${true} | ${100} + `('computes offsetTop', ({ hasNavbar, navbarHeight }) => { + beforeEach(() => { + global.document.querySelector = jest.fn(() => + hasNavbar + ? { + dataset: { + page: 'test', + }, + } + : undefined, + ); + contentTop.mockReturnValue(navbarHeight); + createComponent(); + }); + + afterEach(() => { + contentTop.mockClear(); + }); + + it(`computes offsetTop ${hasNavbar ? 'with' : 'without'} .navbar-gitlab`, () => { + expect(findDrawer().attributes('headerheight')).toBe(`${navbarHeight}px`); + }); + }); + + describe('watcher', () => { + let renderGLFMSpy; + let fetchMarkdownSpy; + + beforeEach(async () => { + renderGLFMSpy = jest.spyOn(MarkdownDrawer.methods, 'renderGLFM'); + fetchMarkdownSpy = jest.spyOn(MarkdownDrawer.methods, 'fetchMarkdown'); + global.document.querySelector = jest.fn(() => ({ + getBoundingClientRect: jest.fn(() => ({ bottom: 100 })), + dataset: { + page: 'test', + }, + })); + createComponent(); + await nextTick(); + }); + + afterEach(() => { + renderGLFMSpy.mockClear(); + fetchMarkdownSpy.mockClear(); + }); + + it('for documentPath triggers fetch', async () => { + expect(fetchMarkdownSpy).toHaveBeenCalledTimes(1); + + await wrapper.setProps({ documentPath: '/test/me' }); + await nextTick(); + + expect(fetchMarkdownSpy).toHaveBeenCalledTimes(2); + }); + + it('for open triggers renderGLFM', async () => { + wrapper.vm.fetchMarkdown(); + wrapper.vm.openDrawer(); + await nextTick(); + expect(renderGLFMSpy).toHaveBeenCalled(); + }); + }); + + describe('Markdown fetching', () => { + let renderGLFMSpy; + + beforeEach(async () => { + renderGLFMSpy = jest.spyOn(MarkdownDrawer.methods, 'renderGLFM'); + createComponent(); + await nextTick(); + }); + + afterEach(() => { + renderGLFMSpy.mockClear(); + }); + + it('fetches the Markdown and caches it', async () => { + expect(getRenderedMarkdown).toHaveBeenCalledTimes(1); + expect(Object.keys(cache)).toHaveLength(1); + }); + + it('when the document changes, fetches it and caches it as well', async () => { + expect(getRenderedMarkdown).toHaveBeenCalledTimes(1); + expect(Object.keys(cache)).toHaveLength(1); + + await wrapper.setProps({ documentPath: '/test/me2' }); + await nextTick(); + + expect(getRenderedMarkdown).toHaveBeenCalledTimes(2); + expect(Object.keys(cache)).toHaveLength(2); + }); + + it('when re-using an already fetched document, gets it from the cache', async () => { + await wrapper.setProps({ documentPath: '/test/me2' }); + await nextTick(); + + expect(getRenderedMarkdown).toHaveBeenCalledTimes(2); + expect(Object.keys(cache)).toHaveLength(2); + + await wrapper.setProps({ documentPath: defaultProps.documentPath }); + await nextTick(); + + expect(getRenderedMarkdown).toHaveBeenCalledTimes(2); + expect(Object.keys(cache)).toHaveLength(2); + }); + }); + + describe('Markdown fetching returns error', () => { + beforeEach(async () => { + getRenderedMarkdown.mockReturnValue({ + hasFetchError: true, + }); + + createComponent(); + await nextTick(); + }); + afterEach(() => { + getRenderedMarkdown.mockClear(); + }); + it('shows alert', () => { + expect(findAlert().exists()).toBe(true); + }); + }); + + describe('While Markdown is fetching', () => { + beforeEach(async () => { + getRenderedMarkdown.mockReturnValue(new Promise(() => {})); + + createComponent(); + }); + + afterEach(() => { + getRenderedMarkdown.mockClear(); + }); + + it('shows skeleton', async () => { + expect(findSkeleton().exists()).toBe(true); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/markdown_drawer/mock_data.js b/spec/frontend/vue_shared/components/markdown_drawer/mock_data.js new file mode 100644 index 00000000000..53b40407556 --- /dev/null +++ b/spec/frontend/vue_shared/components/markdown_drawer/mock_data.js @@ -0,0 +1,42 @@ +export const MOCK_HTML = `<!DOCTYPE html> +<html> +<body> + <div id="content-body"> + <h1>test title <strong>test</strong></h1> + <div class="documentation md gl-mt-3"> + <a href="../advanced_search.md">Advanced Search</a> + <a href="../advanced_search2.md">Advanced Search2</a> + <h2>test header h2</h2> + <table class="testClass"> + <tr> + <td>Emil</td> + <td>Tobias</td> + <td>Linus</td> + </tr> + <tr> + <td>16</td> + <td>14</td> + <td>10</td> + </tr> + </table> + </div> + </div> +</body> +</html>`.replace(/\n/g, ''); + +export const MOCK_DRAWER_DATA = { + hasFetchError: false, + title: 'test title test', + body: ` <div id="content-body"> <div class="documentation md gl-mt-3"> <a href="../advanced_search.md">Advanced Search</a> <a href="../advanced_search2.md">Advanced Search2</a> <h2>test header h2</h2> <table class="testClass"> <tbody><tr> <td>Emil</td> <td>Tobias</td> <td>Linus</td> </tr> <tr> <td>16</td> <td>14</td> <td>10</td> </tr> </tbody></table> </div> </div>`, +}; + +export const MOCK_DRAWER_DATA_ERROR = { + hasFetchError: true, +}; + +export const MOCK_TABLE_DATA_BEFORE = `<head></head><body><h1>test</h1></test><table><tbody><tr><td></td></tr></tbody></table></body>`; + +export const MOCK_HTML_DATA_AFTER = { + body: '<table><tbody><tr><td></td></tr></tbody></table>', + title: 'test', +}; diff --git a/spec/frontend/vue_shared/components/markdown_drawer/utils/fetch_spec.js b/spec/frontend/vue_shared/components/markdown_drawer/utils/fetch_spec.js new file mode 100644 index 00000000000..ff07b2cf838 --- /dev/null +++ b/spec/frontend/vue_shared/components/markdown_drawer/utils/fetch_spec.js @@ -0,0 +1,43 @@ +import MockAdapter from 'axios-mock-adapter'; +import { + getRenderedMarkdown, + splitDocument, +} from '~/vue_shared/components/markdown_drawer/utils/fetch'; +import axios from '~/lib/utils/axios_utils'; +import { + MOCK_HTML, + MOCK_DRAWER_DATA, + MOCK_DRAWER_DATA_ERROR, + MOCK_TABLE_DATA_BEFORE, + MOCK_HTML_DATA_AFTER, +} from '../mock_data'; + +describe('utils/fetch', () => { + let mock; + + afterEach(() => { + mock.restore(); + }); + + describe.each` + axiosMock | type | toExpect + ${{ code: 200, res: { html: MOCK_HTML } }} | ${'success'} | ${MOCK_DRAWER_DATA} + ${{ code: 500, res: null }} | ${'error'} | ${MOCK_DRAWER_DATA_ERROR} + `('process markdown data', ({ axiosMock, type, toExpect }) => { + describe(`if api fetch responds with ${type}`, () => { + beforeEach(() => { + mock = new MockAdapter(axios); + mock.onGet().reply(axiosMock.code, axiosMock.res); + }); + it(`should update drawer correctly`, async () => { + expect(await getRenderedMarkdown('/any/path')).toStrictEqual(toExpect); + }); + }); + }); + + describe('splitDocument', () => { + it(`should update tables correctly`, () => { + expect(splitDocument(MOCK_TABLE_DATA_BEFORE)).toStrictEqual(MOCK_HTML_DATA_AFTER); + }); + }); +}); |