import { GlDrawer, GlAlert, GlSkeletonLoader } from '@gitlab/ui'; import Vue, { 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: `
test body
`, }), })); 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`, async () => { wrapper.vm.getDrawerTop(); await Vue.nextTick(); 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(() => ({ dataset: { page: 'test', }, })); contentTop.mockReturnValue(100); 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('triggers renderGLFM in openDrawer', async () => { wrapper.vm.fetchMarkdown(); wrapper.vm.openDrawer(); await nextTick(); expect(renderGLFMSpy).toHaveBeenCalled(); }); it('triggers height calculation in openDrawer', async () => { expect(findDrawer().attributes('headerheight')).toBe(`${0}px`); wrapper.vm.fetchMarkdown(); wrapper.vm.openDrawer(); await nextTick(); expect(findDrawer().attributes('headerheight')).toBe(`${100}px`); }); it('triggers height calculation in toggleDrawer', async () => { expect(findDrawer().attributes('headerheight')).toBe(`${0}px`); wrapper.vm.fetchMarkdown(); wrapper.vm.toggleDrawer(); await nextTick(); expect(findDrawer().attributes('headerheight')).toBe(`${100}px`); }); }); 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); }); }); });