386 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
			
		
		
	
	
			386 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
| import { GlLoadingIcon } from '@gitlab/ui';
 | |
| import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
 | |
| import axios from 'axios';
 | |
| import MockAdapter from 'axios-mock-adapter';
 | |
| import { nextTick } from 'vue';
 | |
| import VueApollo from 'vue-apollo';
 | |
| import createMockApollo from 'helpers/mock_apollo_helper';
 | |
| import waitForPromises from 'helpers/wait_for_promises';
 | |
| import BlobContent from '~/blob/components/blob_content.vue';
 | |
| import BlobHeader from '~/blob/components/blob_header.vue';
 | |
| import BlobButtonGroup from '~/repository/components/blob_button_group.vue';
 | |
| import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
 | |
| import BlobEdit from '~/repository/components/blob_edit.vue';
 | |
| import { loadViewer, viewerProps } from '~/repository/components/blob_viewers';
 | |
| import DownloadViewer from '~/repository/components/blob_viewers/download_viewer.vue';
 | |
| import EmptyViewer from '~/repository/components/blob_viewers/empty_viewer.vue';
 | |
| import TextViewer from '~/repository/components/blob_viewers/text_viewer.vue';
 | |
| import blobInfoQuery from '~/repository/queries/blob_info.query.graphql';
 | |
| 
 | |
| jest.mock('~/repository/components/blob_viewers');
 | |
| 
 | |
| let wrapper;
 | |
| const simpleMockData = {
 | |
|   name: 'some_file.js',
 | |
|   size: 123,
 | |
|   rawSize: 123,
 | |
|   rawTextBlob: 'raw content',
 | |
|   type: 'text',
 | |
|   fileType: 'text',
 | |
|   tooLarge: false,
 | |
|   path: 'some_file.js',
 | |
|   webPath: 'some_file.js',
 | |
|   editBlobPath: 'some_file.js/edit',
 | |
|   ideEditPath: 'some_file.js/ide/edit',
 | |
|   storedExternally: false,
 | |
|   rawPath: 'some_file.js',
 | |
|   externalStorageUrl: 'some_file.js',
 | |
|   replacePath: 'some_file.js/replace',
 | |
|   deletePath: 'some_file.js/delete',
 | |
|   canLock: true,
 | |
|   isLocked: false,
 | |
|   lockLink: 'some_file.js/lock',
 | |
|   forkPath: 'some_file.js/fork',
 | |
|   simpleViewer: {
 | |
|     fileType: 'text',
 | |
|     tooLarge: false,
 | |
|     type: 'simple',
 | |
|     renderError: null,
 | |
|   },
 | |
|   richViewer: null,
 | |
| };
 | |
| const richMockData = {
 | |
|   ...simpleMockData,
 | |
|   richViewer: {
 | |
|     fileType: 'markup',
 | |
|     tooLarge: false,
 | |
|     type: 'rich',
 | |
|     renderError: null,
 | |
|   },
 | |
| };
 | |
| 
 | |
| const projectMockData = {
 | |
|   userPermissions: {
 | |
|     pushCode: true,
 | |
|   },
 | |
|   repository: {
 | |
|     empty: false,
 | |
|   },
 | |
| };
 | |
| 
 | |
| const localVue = createLocalVue();
 | |
| const mockAxios = new MockAdapter(axios);
 | |
| 
 | |
| const createComponentWithApollo = (mockData = {}) => {
 | |
|   localVue.use(VueApollo);
 | |
| 
 | |
|   const defaultPushCode = projectMockData.userPermissions.pushCode;
 | |
|   const defaultEmptyRepo = projectMockData.repository.empty;
 | |
|   const { blobs, emptyRepo = defaultEmptyRepo, canPushCode = defaultPushCode } = mockData;
 | |
| 
 | |
|   const mockResolver = jest.fn().mockResolvedValue({
 | |
|     data: {
 | |
|       project: {
 | |
|         userPermissions: { pushCode: canPushCode },
 | |
|         repository: {
 | |
|           empty: emptyRepo,
 | |
|           blobs: {
 | |
|             nodes: [blobs],
 | |
|           },
 | |
|         },
 | |
|       },
 | |
|     },
 | |
|   });
 | |
| 
 | |
|   const fakeApollo = createMockApollo([[blobInfoQuery, mockResolver]]);
 | |
| 
 | |
|   wrapper = shallowMount(BlobContentViewer, {
 | |
|     localVue,
 | |
|     apolloProvider: fakeApollo,
 | |
|     propsData: {
 | |
|       path: 'some_file.js',
 | |
|       projectPath: 'some/path',
 | |
|     },
 | |
|   });
 | |
| };
 | |
| 
 | |
| const createFactory = (mountFn) => (
 | |
|   { props = {}, mockData = {}, stubs = {} } = {},
 | |
|   loading = false,
 | |
| ) => {
 | |
|   wrapper = mountFn(BlobContentViewer, {
 | |
|     propsData: {
 | |
|       path: 'some_file.js',
 | |
|       projectPath: 'some/path',
 | |
|       ...props,
 | |
|     },
 | |
|     mocks: {
 | |
|       $apollo: {
 | |
|         queries: {
 | |
|           project: {
 | |
|             loading,
 | |
|           },
 | |
|         },
 | |
|       },
 | |
|     },
 | |
|     stubs,
 | |
|   });
 | |
| 
 | |
|   wrapper.setData(mockData);
 | |
| };
 | |
| 
 | |
| const factory = createFactory(shallowMount);
 | |
| const fullFactory = createFactory(mount);
 | |
| 
 | |
| describe('Blob content viewer component', () => {
 | |
|   const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
 | |
|   const findBlobHeader = () => wrapper.findComponent(BlobHeader);
 | |
|   const findBlobEdit = () => wrapper.findComponent(BlobEdit);
 | |
|   const findBlobContent = () => wrapper.findComponent(BlobContent);
 | |
|   const findBlobButtonGroup = () => wrapper.findComponent(BlobButtonGroup);
 | |
| 
 | |
|   afterEach(() => {
 | |
|     wrapper.destroy();
 | |
|   });
 | |
| 
 | |
|   it('renders a GlLoadingIcon component', () => {
 | |
|     factory({ mockData: { blobInfo: simpleMockData } }, true);
 | |
| 
 | |
|     expect(findLoadingIcon().exists()).toBe(true);
 | |
|   });
 | |
| 
 | |
|   describe('simple viewer', () => {
 | |
|     beforeEach(() => {
 | |
|       factory({ mockData: { blobInfo: simpleMockData } });
 | |
|     });
 | |
| 
 | |
|     it('renders a BlobHeader component', () => {
 | |
|       expect(findBlobHeader().props('activeViewerType')).toEqual('simple');
 | |
|       expect(findBlobHeader().props('hasRenderError')).toEqual(false);
 | |
|       expect(findBlobHeader().props('hideViewerSwitcher')).toEqual(true);
 | |
|       expect(findBlobHeader().props('blob')).toEqual(simpleMockData);
 | |
|     });
 | |
| 
 | |
|     it('renders a BlobContent component', () => {
 | |
|       expect(findBlobContent().props('loading')).toEqual(false);
 | |
|       expect(findBlobContent().props('content')).toEqual('raw content');
 | |
|       expect(findBlobContent().props('isRawContent')).toBe(true);
 | |
|       expect(findBlobContent().props('activeViewer')).toEqual({
 | |
|         fileType: 'text',
 | |
|         tooLarge: false,
 | |
|         type: 'simple',
 | |
|         renderError: null,
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('rich viewer', () => {
 | |
|     beforeEach(() => {
 | |
|       factory({
 | |
|         mockData: { blobInfo: richMockData, activeViewerType: 'rich' },
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('renders a BlobHeader component', () => {
 | |
|       expect(findBlobHeader().props('activeViewerType')).toEqual('rich');
 | |
|       expect(findBlobHeader().props('hasRenderError')).toEqual(false);
 | |
|       expect(findBlobHeader().props('hideViewerSwitcher')).toEqual(false);
 | |
|       expect(findBlobHeader().props('blob')).toEqual(richMockData);
 | |
|     });
 | |
| 
 | |
|     it('renders a BlobContent component', () => {
 | |
|       expect(findBlobContent().props('loading')).toEqual(false);
 | |
|       expect(findBlobContent().props('content')).toEqual('raw content');
 | |
|       expect(findBlobContent().props('isRawContent')).toBe(true);
 | |
|       expect(findBlobContent().props('activeViewer')).toEqual({
 | |
|         fileType: 'markup',
 | |
|         tooLarge: false,
 | |
|         type: 'rich',
 | |
|         renderError: null,
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('updates viewer type when viewer changed is clicked', async () => {
 | |
|       expect(findBlobContent().props('activeViewer')).toEqual(
 | |
|         expect.objectContaining({
 | |
|           type: 'rich',
 | |
|         }),
 | |
|       );
 | |
|       expect(findBlobHeader().props('activeViewerType')).toEqual('rich');
 | |
| 
 | |
|       findBlobHeader().vm.$emit('viewer-changed', 'simple');
 | |
|       await nextTick();
 | |
| 
 | |
|       expect(findBlobHeader().props('activeViewerType')).toEqual('simple');
 | |
|       expect(findBlobContent().props('activeViewer')).toEqual(
 | |
|         expect.objectContaining({
 | |
|           type: 'simple',
 | |
|         }),
 | |
|       );
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('legacy viewers', () => {
 | |
|     it('does not load a legacy viewer when a rich viewer is not available', async () => {
 | |
|       createComponentWithApollo({ blobs: simpleMockData });
 | |
|       await waitForPromises();
 | |
| 
 | |
|       expect(mockAxios.history.get).toHaveLength(0);
 | |
|     });
 | |
| 
 | |
|     it('loads a legacy viewer when a rich viewer is available', async () => {
 | |
|       createComponentWithApollo({ blobs: richMockData });
 | |
|       await waitForPromises();
 | |
| 
 | |
|       expect(mockAxios.history.get).toHaveLength(1);
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   describe('Blob viewer', () => {
 | |
|     afterEach(() => {
 | |
|       loadViewer.mockRestore();
 | |
|       viewerProps.mockRestore();
 | |
|     });
 | |
| 
 | |
|     it('does not render a BlobContent component if a Blob viewer is available', () => {
 | |
|       loadViewer.mockReturnValueOnce(() => true);
 | |
|       factory({ mockData: { blobInfo: richMockData } });
 | |
| 
 | |
|       expect(findBlobContent().exists()).toBe(false);
 | |
|     });
 | |
| 
 | |
|     it.each`
 | |
|       viewer        | loadViewerReturnValue | viewerPropsReturnValue
 | |
|       ${'empty'}    | ${EmptyViewer}        | ${{}}
 | |
|       ${'download'} | ${DownloadViewer}     | ${{ filePath: '/some/file/path', fileName: 'test.js', fileSize: 100 }}
 | |
|       ${'text'}     | ${TextViewer}         | ${{ content: 'test', fileName: 'test.js', readOnly: true }}
 | |
|     `(
 | |
|       'renders viewer component for $viewer files',
 | |
|       async ({ viewer, loadViewerReturnValue, viewerPropsReturnValue }) => {
 | |
|         loadViewer.mockReturnValue(loadViewerReturnValue);
 | |
|         viewerProps.mockReturnValue(viewerPropsReturnValue);
 | |
| 
 | |
|         factory({
 | |
|           mockData: {
 | |
|             blobInfo: {
 | |
|               ...simpleMockData,
 | |
|               fileType: null,
 | |
|               simpleViewer: {
 | |
|                 ...simpleMockData.simpleViewer,
 | |
|                 fileType: viewer,
 | |
|               },
 | |
|             },
 | |
|           },
 | |
|         });
 | |
| 
 | |
|         await nextTick();
 | |
| 
 | |
|         expect(loadViewer).toHaveBeenCalledWith(viewer);
 | |
|         expect(wrapper.findComponent(loadViewerReturnValue).exists()).toBe(true);
 | |
|       },
 | |
|     );
 | |
|   });
 | |
| 
 | |
|   describe('BlobHeader action slot', () => {
 | |
|     const { ideEditPath, editBlobPath } = simpleMockData;
 | |
| 
 | |
|     it('renders BlobHeaderEdit buttons in simple viewer', async () => {
 | |
|       fullFactory({
 | |
|         mockData: { blobInfo: simpleMockData },
 | |
|         stubs: {
 | |
|           BlobContent: true,
 | |
|           BlobReplace: true,
 | |
|         },
 | |
|       });
 | |
| 
 | |
|       await nextTick();
 | |
| 
 | |
|       expect(findBlobEdit().props()).toMatchObject({
 | |
|         editPath: editBlobPath,
 | |
|         webIdePath: ideEditPath,
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('renders BlobHeaderEdit button in rich viewer', async () => {
 | |
|       fullFactory({
 | |
|         mockData: { blobInfo: richMockData },
 | |
|         stubs: {
 | |
|           BlobContent: true,
 | |
|           BlobReplace: true,
 | |
|         },
 | |
|       });
 | |
| 
 | |
|       await nextTick();
 | |
| 
 | |
|       expect(findBlobEdit().props()).toMatchObject({
 | |
|         editPath: editBlobPath,
 | |
|         webIdePath: ideEditPath,
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     it('does not render BlobHeaderEdit button when viewing a binary file', async () => {
 | |
|       fullFactory({
 | |
|         mockData: { blobInfo: richMockData, isBinary: true },
 | |
|         stubs: {
 | |
|           BlobContent: true,
 | |
|           BlobReplace: true,
 | |
|         },
 | |
|       });
 | |
| 
 | |
|       await nextTick();
 | |
| 
 | |
|       expect(findBlobEdit().exists()).toBe(false);
 | |
|     });
 | |
| 
 | |
|     describe('BlobButtonGroup', () => {
 | |
|       const { name, path, replacePath, webPath } = simpleMockData;
 | |
|       const {
 | |
|         userPermissions: { pushCode },
 | |
|         repository: { empty },
 | |
|       } = projectMockData;
 | |
| 
 | |
|       it('renders component', async () => {
 | |
|         window.gon.current_user_id = 1;
 | |
| 
 | |
|         fullFactory({
 | |
|           mockData: {
 | |
|             blobInfo: simpleMockData,
 | |
|             project: { userPermissions: { pushCode }, repository: { empty } },
 | |
|           },
 | |
|           stubs: {
 | |
|             BlobContent: true,
 | |
|             BlobButtonGroup: true,
 | |
|           },
 | |
|         });
 | |
| 
 | |
|         await nextTick();
 | |
| 
 | |
|         expect(findBlobButtonGroup().props()).toMatchObject({
 | |
|           name,
 | |
|           path,
 | |
|           replacePath,
 | |
|           deletePath: webPath,
 | |
|           canPushCode: pushCode,
 | |
|           emptyRepo: empty,
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       it('does not render if not logged in', async () => {
 | |
|         window.gon.current_user_id = null;
 | |
| 
 | |
|         fullFactory({
 | |
|           mockData: { blobInfo: simpleMockData },
 | |
|           stubs: {
 | |
|             BlobContent: true,
 | |
|             BlobReplace: true,
 | |
|           },
 | |
|         });
 | |
| 
 | |
|         await nextTick();
 | |
| 
 | |
|         expect(findBlobButtonGroup().exists()).toBe(false);
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| });
 |