diff options
Diffstat (limited to 'spec/frontend/repository')
6 files changed, 119 insertions, 35 deletions
diff --git a/spec/frontend/repository/commits_service_spec.js b/spec/frontend/repository/commits_service_spec.js index 5fb683bd370..d779abcbfd6 100644 --- a/spec/frontend/repository/commits_service_spec.js +++ b/spec/frontend/repository/commits_service_spec.js @@ -14,7 +14,7 @@ describe('commits service', () => { beforeEach(() => { mock = new MockAdapter(axios); - + window.gon.features = { encodingLogsTree: true }; mock.onGet(url).reply(HTTP_STATUS_OK, [], {}); jest.spyOn(axios, 'get'); @@ -48,14 +48,27 @@ describe('commits service', () => { }); it('encodes the path and ref', async () => { - const encodedRef = encodeURIComponent(refWithSpecialCharMock); - const encodedUrl = `/some-project/-/refs/${encodedRef}/logs_tree/with%20%24peci%40l%20ch%40rs%2F`; + const encodedRef = encodeURI(refWithSpecialCharMock); + const encodedUrl = `/some-project/-/refs/${encodedRef}/logs_tree/with%20$peci@l%20ch@rs/`; await requestCommits(1, 'some-project', 'with $peci@l ch@rs/', refWithSpecialCharMock); expect(axios.get).toHaveBeenCalledWith(encodedUrl, expect.anything()); }); + describe('when encodingLogsTree FF is off', () => { + beforeEach(() => { + window.gon.features = {}; + }); + + it('encodes the path and ref with encodeURIComponent', async () => { + const encodedRef = encodeURIComponent(refWithSpecialCharMock); + const encodedUrl = `/some-project/-/refs/${encodedRef}/logs_tree/with%20%24peci%40l%20ch%40rs%2F`; + await requestCommits(1, 'some-project', 'with $peci@l ch@rs/', refWithSpecialCharMock); + expect(axios.get).toHaveBeenCalledWith(encodedUrl, expect.anything()); + }); + }); + it('calls axios get once per batch', async () => { await Promise.all([requestCommits(0), requestCommits(1), requestCommits(23)]); diff --git a/spec/frontend/repository/components/blob_content_viewer_spec.js b/spec/frontend/repository/components/blob_content_viewer_spec.js index e0d2984893b..cd5bc08faf0 100644 --- a/spec/frontend/repository/components/blob_content_viewer_spec.js +++ b/spec/frontend/repository/components/blob_content_viewer_spec.js @@ -75,6 +75,7 @@ const createComponent = async (mockData = {}, mountFn = shallowMount, mockRoute createMergeRequestIn = userPermissionsMock.createMergeRequestIn, isBinary, inject = {}, + blobBlameInfo = true, } = mockData; const blobInfo = { @@ -138,7 +139,7 @@ const createComponent = async (mockData = {}, mountFn = shallowMount, mockRoute ...inject, glFeatures: { highlightJsWorker: false, - blobBlameInfo: true, + blobBlameInfo, }, }, }), @@ -185,7 +186,7 @@ describe('Blob content viewer component', () => { expect(findBlobHeader().props('hideViewerSwitcher')).toEqual(false); expect(findBlobHeader().props('blob')).toEqual(simpleViewerMock); expect(findBlobHeader().props('showForkSuggestion')).toEqual(false); - expect(findBlobHeader().props('showBlameToggle')).toEqual(false); + expect(findBlobHeader().props('showBlameToggle')).toEqual(true); expect(findBlobHeader().props('projectPath')).toEqual(propsMock.projectPath); expect(findBlobHeader().props('projectId')).toEqual(projectMock.id); expect(mockRouterPush).not.toHaveBeenCalled(); @@ -197,15 +198,15 @@ describe('Blob content viewer component', () => { await nextTick(); }; - it('renders a blame toggle for JSON files', async () => { - await createComponent({ blob: { ...simpleViewerMock, language: 'json' } }); + it('renders a blame toggle', async () => { + await createComponent({ blob: simpleViewerMock }); expect(findBlobHeader().props('showBlameToggle')).toEqual(true); }); it('adds blame param to the URL and passes `showBlame` to the SourceViewer', async () => { loadViewer.mockReturnValueOnce(SourceViewerNew); - await createComponent({ blob: { ...simpleViewerMock, language: 'json' } }); + await createComponent({ blob: simpleViewerMock }); await triggerBlame(); @@ -217,6 +218,25 @@ describe('Blob content viewer component', () => { expect(mockRouterPush).toHaveBeenCalledWith({ query: { blame: '0' } }); expect(findSourceViewerNew().props('showBlame')).toBe(false); }); + + describe('blobBlameInfo feature flag disabled', () => { + it('does not render a blame toggle', async () => { + await createComponent({ blob: simpleViewerMock, blobBlameInfo: false }); + + expect(findBlobHeader().props('showBlameToggle')).toEqual(false); + }); + }); + + describe('when viewing rich content', () => { + it('always shows the blame when clicking on the blame button', async () => { + loadViewer.mockReturnValueOnce(SourceViewerNew); + const query = { plain: '0', blame: '1' }; + await createComponent({ blob: simpleViewerMock }, shallowMount, { query }); + await triggerBlame(); + + expect(findSourceViewerNew().props('showBlame')).toBe(true); + }); + }); }); it('creates an alert when the BlobHeader component emits an error', async () => { @@ -260,6 +280,7 @@ describe('Blob content viewer component', () => { expect(mockAxios.history.get).toHaveLength(1); expect(mockAxios.history.get[0].url).toBe(legacyViewerUrl); + expect(findBlobHeader().props('showBlameToggle')).toEqual(false); }); it('loads a legacy viewer when a viewer component is not available', async () => { diff --git a/spec/frontend/repository/components/blob_controls_spec.js b/spec/frontend/repository/components/blob_controls_spec.js index 3ced5f6c4d2..53ebabebf1d 100644 --- a/spec/frontend/repository/components/blob_controls_spec.js +++ b/spec/frontend/repository/components/blob_controls_spec.js @@ -8,6 +8,7 @@ import blobControlsQuery from '~/repository/queries/blob_controls.query.graphql' import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createRouter from '~/repository/router'; import { updateElementsVisibility } from '~/repository/utils/dom'; +import { resetShortcutsForTests } from '~/behaviors/shortcuts'; import ShortcutsBlob from '~/behaviors/shortcuts/shortcuts_blob'; import BlobLinePermalinkUpdater from '~/blob/blob_line_permalink_updater'; import { blobControlsDataMock, refMock } from '../mock_data'; @@ -32,6 +33,8 @@ const createComponent = async () => { mockResolver = jest.fn().mockResolvedValue({ data: { project } }); + await resetShortcutsForTests(); + wrapper = shallowMountExtended(BlobControls, { router, apolloProvider: createMockApollo([[blobControlsQuery, mockResolver]]), diff --git a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap index e14f41e2ed2..378aacd47fa 100644 --- a/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap +++ b/spec/frontend/repository/components/table/__snapshots__/row_spec.js.snap @@ -47,7 +47,7 @@ exports[`Repository table row component renders a symlink table row 1`] = ` <gl-intersection-observer-stub> <timeago-tooltip-stub cssclass="" - datetimeformat="DATE_WITH_TIME_FORMAT" + datetimeformat="asDateTime" time="2019-01-01" tooltipplacement="top" /> @@ -103,7 +103,7 @@ exports[`Repository table row component renders table row 1`] = ` <gl-intersection-observer-stub> <timeago-tooltip-stub cssclass="" - datetimeformat="DATE_WITH_TIME_FORMAT" + datetimeformat="asDateTime" time="2019-01-01" tooltipplacement="top" /> @@ -159,7 +159,7 @@ exports[`Repository table row component renders table row for path with special <gl-intersection-observer-stub> <timeago-tooltip-stub cssclass="" - datetimeformat="DATE_WITH_TIME_FORMAT" + datetimeformat="asDateTime" time="2019-01-01" tooltipplacement="top" /> diff --git a/spec/frontend/repository/components/tree_content_spec.js b/spec/frontend/repository/components/tree_content_spec.js index c0eb65b28fe..311e5ca86f8 100644 --- a/spec/frontend/repository/components/tree_content_spec.js +++ b/spec/frontend/repository/components/tree_content_spec.js @@ -162,26 +162,19 @@ describe('Repository table component', () => { describe('commit data', () => { const path = ''; - it('loads commit data for both top and bottom batches when row-appear event is emitted', () => { - const rowNumber = 50; - + it('loads commit data for the nearest page', () => { createComponent({ path }); - findFileTable().vm.$emit('row-appear', rowNumber); + findFileTable().vm.$emit('row-appear', 49); + findFileTable().vm.$emit('row-appear', 15); - expect(isRequested).toHaveBeenCalledWith(rowNumber); + expect(isRequested).toHaveBeenCalledWith(49); + expect(isRequested).toHaveBeenCalledWith(15); expect(loadCommits.mock.calls).toEqual([ - ['', path, '', rowNumber, 'heads'], - ['', path, '', rowNumber - 25, 'heads'], + ['', path, '', 25, 'heads'], + ['', path, '', 0, 'heads'], ]); }); - - it('loads commit data once if rowNumber is zero', () => { - createComponent({ path }); - findFileTable().vm.$emit('row-appear', 0); - - expect(loadCommits.mock.calls).toEqual([['', path, '', 0, 'heads']]); - }); }); describe('error handling', () => { diff --git a/spec/frontend/repository/mixins/highlight_mixin_spec.js b/spec/frontend/repository/mixins/highlight_mixin_spec.js index 50cfd71d686..c635c09d1aa 100644 --- a/spec/frontend/repository/mixins/highlight_mixin_spec.js +++ b/spec/frontend/repository/mixins/highlight_mixin_spec.js @@ -1,9 +1,18 @@ import { shallowMount } from '@vue/test-utils'; +import axios from 'axios'; +import MockAdapter from 'axios-mock-adapter'; import { splitIntoChunks } from '~/vue_shared/components/source_viewer/workers/highlight_utils'; import highlightMixin from '~/repository/mixins/highlight_mixin'; import LineHighlighter from '~/blob/line_highlighter'; +import waitForPromises from 'helpers/wait_for_promises'; +import { HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { TEXT_FILE_TYPE } from '~/repository/constants'; -import { LINES_PER_CHUNK } from '~/vue_shared/components/source_viewer/constants'; +import { + LINES_PER_CHUNK, + EVENT_ACTION, + EVENT_LABEL_FALLBACK, +} from '~/vue_shared/components/source_viewer/constants'; +import Tracking from '~/tracking'; const lineHighlighter = new LineHighlighter(); jest.mock('~/blob/line_highlighter', () => jest.fn().mockReturnValue({ highlightHash: jest.fn() })); @@ -11,6 +20,7 @@ jest.mock('~/vue_shared/components/source_viewer/workers/highlight_utils', () => splitIntoChunks: jest.fn().mockResolvedValue([]), })); +const mockAxios = new MockAdapter(axios); const workerMock = { postMessage: jest.fn() }; const onErrorMock = jest.fn(); @@ -21,7 +31,10 @@ describe('HighlightMixin', () => { const rawTextBlob = contentArray.join('\n'); const languageMock = 'json'; - const createComponent = ({ fileType = TEXT_FILE_TYPE, language = languageMock } = {}) => { + const createComponent = ( + { fileType = TEXT_FILE_TYPE, language = languageMock, externalStorageUrl, rawPath } = {}, + isUsingLfs = false, + ) => { const simpleViewer = { fileType }; const dummyComponent = { @@ -32,7 +45,10 @@ describe('HighlightMixin', () => { }, template: '<div>{{chunks[0]?.highlightedContent}}</div>', created() { - this.initHighlightWorker({ rawTextBlob, simpleViewer, language, fileType }); + this.initHighlightWorker( + { rawTextBlob, simpleViewer, language, fileType, externalStorageUrl, rawPath }, + isUsingLfs, + ); }, methods: { onError: onErrorMock }, }; @@ -45,13 +61,6 @@ describe('HighlightMixin', () => { describe('initHighlightWorker', () => { const firstSeventyLines = contentArray.slice(0, LINES_PER_CHUNK).join('\n'); - it('does not instruct worker if file is not a JSON file', () => { - workerMock.postMessage.mockClear(); - createComponent({ language: 'javascript' }); - - expect(workerMock.postMessage).not.toHaveBeenCalled(); - }); - it('generates a chunk for the first 70 lines of raw text', () => { expect(splitIntoChunks).toHaveBeenCalledWith(languageMock, firstSeventyLines); }); @@ -74,6 +83,23 @@ describe('HighlightMixin', () => { }); }); + describe('auto-detects if a language cannot be loaded', () => { + const unknownLanguage = 'some_unknown_language'; + beforeEach(() => { + jest.spyOn(Tracking, 'event'); + createComponent({ language: unknownLanguage }); + }); + + it('emits a tracking event for the fallback', () => { + const eventData = { label: EVENT_LABEL_FALLBACK, property: unknownLanguage }; + expect(Tracking.event).toHaveBeenCalledWith(undefined, EVENT_ACTION, eventData); + }); + + it('calls the onError method', () => { + expect(onErrorMock).toHaveBeenCalled(); + }); + }); + describe('worker message handling', () => { const CHUNK_MOCK = { startingFrom: 0, totalLines: 70, highlightedContent: 'some content' }; @@ -87,4 +113,32 @@ describe('HighlightMixin', () => { expect(lineHighlighter.highlightHash).toHaveBeenCalledWith(hash); }); }); + + describe('LFS blobs', () => { + const rawPath = '/org/project/-/raw/file.xml'; + const externalStorageUrl = 'http://127.0.0.1:9000/lfs-objects/91/12/1341234'; + const mockParams = { content: rawTextBlob, language: languageMock, fileType: TEXT_FILE_TYPE }; + + afterEach(() => mockAxios.reset()); + + it('Uses externalStorageUrl to fetch content if present', async () => { + mockAxios.onGet(externalStorageUrl).replyOnce(HTTP_STATUS_OK, rawTextBlob); + createComponent({ rawPath, externalStorageUrl }, true); + await waitForPromises(); + + expect(mockAxios.history.get).toHaveLength(1); + expect(mockAxios.history.get[0].url).toBe(externalStorageUrl); + expect(workerMock.postMessage).toHaveBeenCalledWith(mockParams); + }); + + it('Falls back to rawPath to fetch content', async () => { + mockAxios.onGet(rawPath).replyOnce(HTTP_STATUS_OK, rawTextBlob); + createComponent({ rawPath }, true); + await waitForPromises(); + + expect(mockAxios.history.get).toHaveLength(1); + expect(mockAxios.history.get[0].url).toBe(rawPath); + expect(workerMock.postMessage).toHaveBeenCalledWith(mockParams); + }); + }); }); |