diff options
Diffstat (limited to 'spec/frontend/vue_shared')
43 files changed, 492 insertions, 301 deletions
diff --git a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap index 3f91591f5cd..c14cf0db370 100644 --- a/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap +++ b/spec/frontend/vue_shared/components/__snapshots__/awards_list_spec.js.snap @@ -7,7 +7,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` <button class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button" data-testid="award-button" - title="Ada, Leonardo, and Marie" + title="Ada, Leonardo, and Marie reacted with :thumbsup:" type="button" > <!----> @@ -37,7 +37,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` <button class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected" data-testid="award-button" - title="You, Ada, and Marie" + title="You, Ada, and Marie reacted with :thumbsdown:" type="button" > <!----> @@ -67,7 +67,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` <button class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button" data-testid="award-button" - title="Ada and Jane" + title="Ada and Jane reacted with :smile:" type="button" > <!----> @@ -97,7 +97,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` <button class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected" data-testid="award-button" - title="You, Ada, Jane, and Leonardo" + title="You, Ada, Jane, and Leonardo reacted with :ok_hand:" type="button" > <!----> @@ -127,7 +127,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` <button class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected" data-testid="award-button" - title="You" + title="You reacted with :cactus:" type="button" > <!----> @@ -157,7 +157,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` <button class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button" data-testid="award-button" - title="Marie" + title="Marie reacted with :a:" type="button" > <!----> @@ -187,7 +187,7 @@ exports[`vue_shared/components/awards_list default matches snapshot 1`] = ` <button class="btn gl-mr-3 gl-my-2 btn-default btn-md gl-button selected" data-testid="award-button" - title="You" + title="You reacted with :b:" type="button" > <!----> diff --git a/spec/frontend/vue_shared/components/__snapshots__/editor_lite_spec.js.snap b/spec/frontend/vue_shared/components/__snapshots__/source_editor_spec.js.snap index 26785855369..7ce155f6a5d 100644 --- a/spec/frontend/vue_shared/components/__snapshots__/editor_lite_spec.js.snap +++ b/spec/frontend/vue_shared/components/__snapshots__/source_editor_spec.js.snap @@ -1,9 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Editor Lite component rendering matches the snapshot 1`] = ` +exports[`Source Editor component rendering matches the snapshot 1`] = ` <div data-editor-loading="" - id="editor-lite-snippet_777" + id="source-editor-snippet_777" > <pre class="editor-loading-content" diff --git a/spec/frontend/vue_shared/components/awards_list_spec.js b/spec/frontend/vue_shared/components/awards_list_spec.js index 55f9eedc169..95e9760c181 100644 --- a/spec/frontend/vue_shared/components/awards_list_spec.js +++ b/spec/frontend/vue_shared/components/awards_list_spec.js @@ -98,43 +98,43 @@ describe('vue_shared/components/awards_list', () => { classes: REACTION_CONTROL_CLASSES, count: 3, html: matchingEmojiTag(EMOJI_THUMBSUP), - title: 'Ada, Leonardo, and Marie', + title: `Ada, Leonardo, and Marie reacted with :${EMOJI_THUMBSUP}:`, }, { classes: [...REACTION_CONTROL_CLASSES, 'selected'], count: 3, html: matchingEmojiTag(EMOJI_THUMBSDOWN), - title: 'You, Ada, and Marie', + title: `You, Ada, and Marie reacted with :${EMOJI_THUMBSDOWN}:`, }, { classes: REACTION_CONTROL_CLASSES, count: 2, html: matchingEmojiTag(EMOJI_SMILE), - title: 'Ada and Jane', + title: `Ada and Jane reacted with :${EMOJI_SMILE}:`, }, { classes: [...REACTION_CONTROL_CLASSES, 'selected'], count: 4, html: matchingEmojiTag(EMOJI_OK), - title: 'You, Ada, Jane, and Leonardo', + title: `You, Ada, Jane, and Leonardo reacted with :${EMOJI_OK}:`, }, { classes: [...REACTION_CONTROL_CLASSES, 'selected'], count: 1, html: matchingEmojiTag(EMOJI_CACTUS), - title: 'You', + title: `You reacted with :${EMOJI_CACTUS}:`, }, { classes: REACTION_CONTROL_CLASSES, count: 1, html: matchingEmojiTag(EMOJI_A), - title: 'Marie', + title: `Marie reacted with :${EMOJI_A}:`, }, { classes: [...REACTION_CONTROL_CLASSES, 'selected'], count: 1, html: matchingEmojiTag(EMOJI_B), - title: 'You', + title: `You reacted with :${EMOJI_B}:`, }, ]); }); @@ -246,13 +246,13 @@ describe('vue_shared/components/awards_list', () => { classes: REACTION_CONTROL_CLASSES, count: 1, html: matchingEmojiTag(EMOJI_100), - title: 'Marie', + title: `Marie reacted with :${EMOJI_100}:`, }, { classes: REACTION_CONTROL_CLASSES, count: 1, html: matchingEmojiTag(EMOJI_SMILE), - title: 'Marie', + title: `Marie reacted with :${EMOJI_SMILE}:`, }, ]); }); diff --git a/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js b/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js index f592db935ec..d14f3e5559f 100644 --- a/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js +++ b/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js @@ -10,9 +10,10 @@ describe('Blob Rich Viewer component', () => { const content = '<h1 id="markdown">Foo Bar</h1>'; const defaultType = 'markdown'; - function createComponent(type = defaultType) { + function createComponent(type = defaultType, richViewer) { wrapper = shallowMount(RichViewer, { propsData: { + richViewer, content, type, }, @@ -31,6 +32,12 @@ describe('Blob Rich Viewer component', () => { expect(wrapper.html()).toContain(content); }); + it('renders the richViewer if one is present', () => { + const richViewer = '<div class="js-pdf-viewer"></div>'; + createComponent('pdf', richViewer); + expect(wrapper.html()).toContain(richViewer); + }); + it('queries for advanced viewer', () => { expect(handleBlobRichViewer).toHaveBeenCalledWith(expect.anything(), defaultType); }); diff --git a/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js b/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js index 46d4edad891..c6c351a7f3f 100644 --- a/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js +++ b/spec/frontend/vue_shared/components/blob_viewers/simple_viewer_spec.js @@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils'; import waitForPromises from 'helpers/wait_for_promises'; import { HIGHLIGHT_CLASS_NAME } from '~/vue_shared/components/blob_viewers/constants'; import SimpleViewer from '~/vue_shared/components/blob_viewers/simple_viewer.vue'; -import EditorLite from '~/vue_shared/components/editor_lite.vue'; +import SourceEditor from '~/vue_shared/components/source_editor.vue'; describe('Blob Simple Viewer component', () => { let wrapper; @@ -96,7 +96,7 @@ describe('Blob Simple Viewer component', () => { }); describe('Vue refactoring to use Source Editor', () => { - const findEditorLite = () => wrapper.find(EditorLite); + const findSourceEditor = () => wrapper.find(SourceEditor); it.each` doesRender | condition | isRawContent | isRefactorFlagEnabled @@ -105,19 +105,19 @@ describe('Blob Simple Viewer component', () => { ${'Does not'} | ${'both, the FF and rawContent are not specified'} | ${false} | ${false} ${'Does'} | ${'both, the FF and rawContent are specified'} | ${true} | ${true} `( - '$doesRender render Editor Lite component in readonly mode when $condition', + '$doesRender render Source Editor component in readonly mode when $condition', async ({ isRawContent, isRefactorFlagEnabled } = {}) => { createComponent('raw content', isRawContent, isRefactorFlagEnabled); await waitForPromises(); if (isRawContent && isRefactorFlagEnabled) { - expect(findEditorLite().exists()).toBe(true); + expect(findSourceEditor().exists()).toBe(true); - expect(findEditorLite().props('value')).toBe('raw content'); - expect(findEditorLite().props('fileName')).toBe('test.js'); - expect(findEditorLite().props('editorOptions')).toEqual({ readOnly: true }); + expect(findSourceEditor().props('value')).toBe('raw content'); + expect(findSourceEditor().props('fileName')).toBe('test.js'); + expect(findSourceEditor().props('editorOptions')).toEqual({ readOnly: true }); } else { - expect(findEditorLite().exists()).toBe(false); + expect(findSourceEditor().exists()).toBe(false); } }, ); diff --git a/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js b/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js index eacc41ccdad..8deb466b33c 100644 --- a/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js +++ b/spec/frontend/vue_shared/components/diff_viewer/viewers/image_diff_viewer_spec.js @@ -109,9 +109,11 @@ describe('ImageDiffViewer', () => { components: { imageDiffViewer, }, - data: { - ...allProps, - diffMode: 'renamed', + data() { + return { + ...allProps, + diffMode: 'renamed', + }; }, ...compileToFunctions(` <image-diff-viewer @@ -121,7 +123,9 @@ describe('ImageDiffViewer', () => { :new-size="newSize" :old-size="oldSize" > - <span slot="image-overlay" class="overlay">test</span> + <template #image-overlay> + <span class="overlay">test</span> + </template> </image-diff-viewer> `), }).$mount(); diff --git a/spec/frontend/vue_shared/components/dismissible_alert_spec.js b/spec/frontend/vue_shared/components/dismissible_alert_spec.js index cfa6d1064e5..fcd004d35a7 100644 --- a/spec/frontend/vue_shared/components/dismissible_alert_spec.js +++ b/spec/frontend/vue_shared/components/dismissible_alert_spec.js @@ -5,18 +5,12 @@ import DismissibleAlert from '~/vue_shared/components/dismissible_alert.vue'; const TEST_HTML = 'Hello World! <strong>Foo</strong>'; describe('vue_shared/components/dismissible_alert', () => { - const testAlertProps = { - primaryButtonText: 'Lorem ipsum', - primaryButtonLink: '/lorem/ipsum', - }; - let wrapper; const createComponent = (props = {}) => { wrapper = shallowMount(DismissibleAlert, { propsData: { html: TEST_HTML, - ...testAlertProps, ...props, }, }); @@ -28,16 +22,13 @@ describe('vue_shared/components/dismissible_alert', () => { const findAlert = () => wrapper.find(GlAlert); - describe('with default', () => { + describe('default', () => { beforeEach(() => { createComponent(); }); it('shows alert', () => { - const alert = findAlert(); - - expect(alert.exists()).toBe(true); - expect(alert.props()).toEqual(expect.objectContaining(testAlertProps)); + expect(findAlert().exists()).toBe(true); }); it('shows given HTML', () => { @@ -54,4 +45,32 @@ describe('vue_shared/components/dismissible_alert', () => { }); }); }); + + describe('with additional props', () => { + const testAlertProps = { + dismissible: true, + title: 'Mock Title', + primaryButtonText: 'Lorem ipsum', + primaryButtonLink: '/lorem/ipsum', + variant: 'warning', + }; + + beforeEach(() => { + createComponent(testAlertProps); + }); + + it('passes other props', () => { + expect(findAlert().props()).toEqual(expect.objectContaining(testAlertProps)); + }); + }); + + describe('with unsafe HTML', () => { + beforeEach(() => { + createComponent({ html: '<a onclick="alert("XSS")">Link</a>' }); + }); + + it('removes unsafe HTML', () => { + expect(findAlert().html()).toContain('<a>Link</a>'); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/file_finder/index_spec.js b/spec/frontend/vue_shared/components/file_finder/index_spec.js index d757b7fac72..181fc4017a3 100644 --- a/spec/frontend/vue_shared/components/file_finder/index_spec.js +++ b/spec/frontend/vue_shared/components/file_finder/index_spec.js @@ -154,6 +154,16 @@ describe('File finder item spec', () => { }); }); + describe('DOM Performance', () => { + it('renders less DOM nodes if not visible by utilizing v-if', async () => { + vm.visible = false; + + await waitForPromises(); + + expect(vm.$el).toBeInstanceOf(Comment); + }); + }); + describe('watches', () => { describe('searchText', () => { it('resets focusedIndex when updated', (done) => { @@ -169,7 +179,7 @@ describe('File finder item spec', () => { }); describe('visible', () => { - it('returns searchText when false', (done) => { + it('resets searchText when changed to false', (done) => { vm.searchText = 'test'; vm.visible = true; diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_utils_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_utils_spec.js index 93cddff8421..1b97011bf7f 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_utils_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_utils_spec.js @@ -11,7 +11,7 @@ import { processFilters, filterToQueryObject, urlQueryToFilter, - getRecentlyUsedTokenValues, + getRecentlyUsedSuggestions, setTokenValueToRecentlyUsed, } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils'; @@ -328,32 +328,32 @@ describe('urlQueryToFilter', () => { ); }); -describe('getRecentlyUsedTokenValues', () => { +describe('getRecentlyUsedSuggestions', () => { useLocalStorageSpy(); beforeEach(() => { localStorage.removeItem(mockStorageKey); }); - it('returns array containing recently used token values from provided recentTokenValuesStorageKey', () => { + it('returns array containing recently used token values from provided recentSuggestionsStorageKey', () => { setLocalStorageAvailability(true); const mockExpectedArray = [{ foo: 'bar' }]; localStorage.setItem(mockStorageKey, JSON.stringify(mockExpectedArray)); - expect(getRecentlyUsedTokenValues(mockStorageKey)).toEqual(mockExpectedArray); + expect(getRecentlyUsedSuggestions(mockStorageKey)).toEqual(mockExpectedArray); }); - it('returns empty array when provided recentTokenValuesStorageKey does not have anything in localStorage', () => { + it('returns empty array when provided recentSuggestionsStorageKey does not have anything in localStorage', () => { setLocalStorageAvailability(true); - expect(getRecentlyUsedTokenValues(mockStorageKey)).toEqual([]); + expect(getRecentlyUsedSuggestions(mockStorageKey)).toEqual([]); }); it('returns empty array when when access to localStorage is not available', () => { setLocalStorageAvailability(false); - expect(getRecentlyUsedTokenValues(mockStorageKey)).toEqual([]); + expect(getRecentlyUsedSuggestions(mockStorageKey)).toEqual([]); }); }); @@ -366,7 +366,7 @@ describe('setTokenValueToRecentlyUsed', () => { localStorage.removeItem(mockStorageKey); }); - it('adds provided tokenValue to localStorage for recentTokenValuesStorageKey', () => { + it('adds provided tokenValue to localStorage for recentSuggestionsStorageKey', () => { setLocalStorageAvailability(true); setTokenValueToRecentlyUsed(mockStorageKey, mockTokenValue1); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js index 951b050495c..74f579e77ed 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js @@ -94,7 +94,7 @@ describe('AuthorToken', () => { it('calls `config.fetchAuthors` with provided searchTerm param', () => { jest.spyOn(wrapper.vm.config, 'fetchAuthors'); - getBaseToken().vm.$emit('fetch-token-values', mockAuthors[0].username); + getBaseToken().vm.$emit('fetch-suggestions', mockAuthors[0].username); expect(wrapper.vm.config.fetchAuthors).toHaveBeenCalledWith( mockAuthorToken.fetchPath, @@ -105,17 +105,17 @@ describe('AuthorToken', () => { it('sets response to `authors` when request is succesful', () => { jest.spyOn(wrapper.vm.config, 'fetchAuthors').mockResolvedValue(mockAuthors); - getBaseToken().vm.$emit('fetch-token-values', 'root'); + getBaseToken().vm.$emit('fetch-suggestions', 'root'); return waitForPromises().then(() => { - expect(getBaseToken().props('tokenValues')).toEqual(mockAuthors); + expect(getBaseToken().props('suggestions')).toEqual(mockAuthors); }); }); it('calls `createFlash` with flash error message when request fails', () => { jest.spyOn(wrapper.vm.config, 'fetchAuthors').mockRejectedValue({}); - getBaseToken().vm.$emit('fetch-token-values', 'root'); + getBaseToken().vm.$emit('fetch-suggestions', 'root'); return waitForPromises().then(() => { expect(createFlash).toHaveBeenCalledWith({ @@ -127,17 +127,17 @@ describe('AuthorToken', () => { it('sets `loading` to false when request completes', async () => { jest.spyOn(wrapper.vm.config, 'fetchAuthors').mockRejectedValue({}); - getBaseToken().vm.$emit('fetch-token-values', 'root'); + getBaseToken().vm.$emit('fetch-suggestions', 'root'); await waitForPromises(); - expect(getBaseToken().props('tokensListLoading')).toBe(false); + expect(getBaseToken().props('suggestionsLoading')).toBe(false); }); }); }); describe('template', () => { - const activateTokenValuesList = async () => { + const activateSuggestionsList = async () => { const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); @@ -154,7 +154,7 @@ describe('AuthorToken', () => { expect(baseTokenEl.exists()).toBe(true); expect(baseTokenEl.props()).toMatchObject({ - tokenValues: mockAuthors, + suggestions: mockAuthors, fnActiveTokenValue: wrapper.vm.getActiveAuthor, }); }); @@ -221,7 +221,7 @@ describe('AuthorToken', () => { stubs: { Portal: true }, }); - await activateTokenValuesList(); + await activateSuggestionsList(); const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); @@ -252,7 +252,7 @@ describe('AuthorToken', () => { stubs: { Portal: true }, }); - await activateTokenValuesList(); + await activateSuggestionsList(); const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js index 89c5cedc9b8..cd6ffd679d0 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/base_token_spec.js @@ -7,7 +7,7 @@ import { import { DEFAULT_LABELS } from '~/vue_shared/components/filtered_search_bar/constants'; import { - getRecentlyUsedTokenValues, + getRecentlyUsedSuggestions, setTokenValueToRecentlyUsed, } from '~/vue_shared/components/filtered_search_bar/filtered_search_utils'; import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue'; @@ -49,10 +49,10 @@ const mockProps = { config: mockLabelToken, value: { data: '' }, active: false, - tokenValues: [], - tokensListLoading: false, - defaultTokenValues: DEFAULT_LABELS, - recentTokenValuesStorageKey: mockStorageKey, + suggestions: [], + suggestionsLoading: false, + defaultSuggestions: DEFAULT_LABELS, + recentSuggestionsStorageKey: mockStorageKey, fnCurrentTokenValue: jest.fn(), }; @@ -83,7 +83,7 @@ describe('BaseToken', () => { props: { ...mockProps, value: { data: `"${mockRegularLabel.title}"` }, - tokenValues: mockLabels, + suggestions: mockLabels, }, }); }); @@ -93,8 +93,8 @@ describe('BaseToken', () => { }); describe('data', () => { - it('calls `getRecentlyUsedTokenValues` to populate `recentTokenValues` when `recentTokenValuesStorageKey` is defined', () => { - expect(getRecentlyUsedTokenValues).toHaveBeenCalledWith(mockStorageKey); + it('calls `getRecentlyUsedSuggestions` to populate `recentSuggestions` when `recentSuggestionsStorageKey` is defined', () => { + expect(getRecentlyUsedSuggestions).toHaveBeenCalledWith(mockStorageKey); }); }); @@ -147,15 +147,15 @@ describe('BaseToken', () => { wrapperWithTokenActive.destroy(); }); - it('emits `fetch-token-values` event on the component when value of this prop is changed to false and `tokenValues` array is empty', async () => { + it('emits `fetch-suggestions` event on the component when value of this prop is changed to false and `suggestions` array is empty', async () => { wrapperWithTokenActive.setProps({ active: false, }); await wrapperWithTokenActive.vm.$nextTick(); - expect(wrapperWithTokenActive.emitted('fetch-token-values')).toBeTruthy(); - expect(wrapperWithTokenActive.emitted('fetch-token-values')).toEqual([ + expect(wrapperWithTokenActive.emitted('fetch-suggestions')).toBeTruthy(); + expect(wrapperWithTokenActive.emitted('fetch-suggestions')).toEqual([ [`"${mockRegularLabel.title}"`], ]); }); @@ -164,7 +164,7 @@ describe('BaseToken', () => { describe('methods', () => { describe('handleTokenValueSelected', () => { - it('calls `setTokenValueToRecentlyUsed` when `recentTokenValuesStorageKey` is defined', () => { + it('calls `setTokenValueToRecentlyUsed` when `recentSuggestionsStorageKey` is defined', () => { const mockTokenValue = { id: 1, title: 'Foo', @@ -175,14 +175,14 @@ describe('BaseToken', () => { expect(setTokenValueToRecentlyUsed).toHaveBeenCalledWith(mockStorageKey, mockTokenValue); }); - it('does not add token from preloadedTokenValues', async () => { + it('does not add token from preloadedSuggestions', async () => { const mockTokenValue = { id: 1, title: 'Foo', }; wrapper.setProps({ - preloadedTokenValues: [mockTokenValue], + preloadedSuggestions: [mockTokenValue], }); await wrapper.vm.$nextTick(); @@ -228,7 +228,7 @@ describe('BaseToken', () => { wrapperWithNoStubs.destroy(); }); - it('emits `fetch-token-values` event on component after a delay when component emits `input` event', async () => { + it('emits `fetch-suggestions` event on component after a delay when component emits `input` event', async () => { jest.useFakeTimers(); wrapperWithNoStubs.find(GlFilteredSearchToken).vm.$emit('input', { data: 'foo' }); @@ -236,8 +236,8 @@ describe('BaseToken', () => { jest.runAllTimers(); - expect(wrapperWithNoStubs.emitted('fetch-token-values')).toBeTruthy(); - expect(wrapperWithNoStubs.emitted('fetch-token-values')[2]).toEqual(['foo']); + expect(wrapperWithNoStubs.emitted('fetch-suggestions')).toBeTruthy(); + expect(wrapperWithNoStubs.emitted('fetch-suggestions')[2]).toEqual(['foo']); }); }); }); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/iteration_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/iteration_token_spec.js index ca5dc984ae0..bd654c5a9cb 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/iteration_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/iteration_token_spec.js @@ -7,7 +7,7 @@ import { mockIterationToken } from '../mock_data'; jest.mock('~/flash'); describe('IterationToken', () => { - const title = 'gitlab-org: #1'; + const id = 123; let wrapper; const createComponent = ({ config = mockIterationToken, value = { data: '' } } = {}) => @@ -28,14 +28,14 @@ describe('IterationToken', () => { }); it('renders iteration value', async () => { - wrapper = createComponent({ value: { data: title } }); + wrapper = createComponent({ value: { data: id } }); await wrapper.vm.$nextTick(); const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); expect(tokenSegments).toHaveLength(3); // `Iteration` `=` `gitlab-org: #1` - expect(tokenSegments.at(2).text()).toBe(title); + expect(tokenSegments.at(2).text()).toBe(id.toString()); }); it('fetches initial values', () => { @@ -43,10 +43,10 @@ describe('IterationToken', () => { wrapper = createComponent({ config: { ...mockIterationToken, fetchIterations: fetchIterationsSpy }, - value: { data: title }, + value: { data: id }, }); - expect(fetchIterationsSpy).toHaveBeenCalledWith(title); + expect(fetchIterationsSpy).toHaveBeenCalledWith(id); }); it('fetches iterations on user input', () => { diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js index cc40ff96b65..ec9458f64d2 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js @@ -159,7 +159,7 @@ describe('LabelToken', () => { expect(baseTokenEl.exists()).toBe(true); expect(baseTokenEl.props()).toMatchObject({ - tokenValues: mockLabels, + suggestions: mockLabels, fnActiveTokenValue: wrapper.vm.getActiveLabel, }); }); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js index 9f550ac9afc..74ceb03bb96 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js @@ -9,6 +9,7 @@ import MockAdapter from 'axios-mock-adapter'; import waitForPromises from 'helpers/wait_for_promises'; import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; +import { sortMilestonesByDueDate } from '~/milestones/milestone_utils'; import { DEFAULT_MILESTONES } from '~/vue_shared/components/filtered_search_bar/constants'; import MilestoneToken from '~/vue_shared/components/filtered_search_bar/tokens/milestone_token.vue'; @@ -21,6 +22,7 @@ import { } from '../mock_data'; jest.mock('~/flash'); +jest.mock('~/milestones/milestone_utils'); const defaultStubs = { Portal: true, @@ -112,6 +114,7 @@ describe('MilestoneToken', () => { return waitForPromises().then(() => { expect(wrapper.vm.milestones).toEqual(mockMilestones); + expect(sortMilestonesByDueDate).toHaveBeenCalled(); }); }); diff --git a/spec/frontend/vue_shared/components/notes/__snapshots__/placeholder_note_spec.js.snap b/spec/frontend/vue_shared/components/notes/__snapshots__/placeholder_note_spec.js.snap index f3ce03796f9..5e956d66b6a 100644 --- a/spec/frontend/vue_shared/components/notes/__snapshots__/placeholder_note_spec.js.snap +++ b/spec/frontend/vue_shared/components/notes/__snapshots__/placeholder_note_spec.js.snap @@ -55,6 +55,8 @@ exports[`Issue placeholder note component matches snapshot 1`] = ` <p> Foo </p> + + </div> </div> </div> diff --git a/spec/frontend/vue_shared/components/paginated_list_spec.js b/spec/frontend/vue_shared/components/paginated_list_spec.js index c0ee49f194f..9f819cc4e94 100644 --- a/spec/frontend/vue_shared/components/paginated_list_spec.js +++ b/spec/frontend/vue_shared/components/paginated_list_spec.js @@ -7,9 +7,11 @@ describe('Pagination links component', () => { let glPaginatedList; const template = ` - <div class="slot" slot-scope="{ listItem }"> - <span class="item">Item Name: {{listItem.id}}</span> - </div> + <template #default="{ listItem }"> + <div class="slot"> + <span class="item">Item Name: {{ listItem.id }}</span> + </div> + </template> `; const props = { diff --git a/spec/frontend/vue_shared/components/project_avatar/default_spec.js b/spec/frontend/vue_shared/components/project_avatar/default_spec.js index 0daadeebc20..84dad2374cb 100644 --- a/spec/frontend/vue_shared/components/project_avatar/default_spec.js +++ b/spec/frontend/vue_shared/components/project_avatar/default_spec.js @@ -3,7 +3,7 @@ import mountComponent from 'helpers/vue_mount_component_helper'; import { projectData } from 'jest/ide/mock_data'; import { TEST_HOST } from 'spec/test_constants'; import { getFirstCharacterCapitalized } from '~/lib/utils/text_utility'; -import ProjectAvatarDefault from '~/vue_shared/components/project_avatar/default.vue'; +import ProjectAvatarDefault from '~/vue_shared/components/deprecated_project_avatar/default.vue'; describe('ProjectAvatarDefault component', () => { const Component = Vue.extend(ProjectAvatarDefault); diff --git a/spec/frontend/vue_shared/components/project_avatar_spec.js b/spec/frontend/vue_shared/components/project_avatar_spec.js new file mode 100644 index 00000000000..d55f3127a74 --- /dev/null +++ b/spec/frontend/vue_shared/components/project_avatar_spec.js @@ -0,0 +1,67 @@ +import { GlAvatar } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import ProjectAvatar from '~/vue_shared/components/project_avatar.vue'; + +const defaultProps = { + projectName: 'GitLab', +}; + +describe('ProjectAvatar', () => { + let wrapper; + + const findGlAvatar = () => wrapper.findComponent(GlAvatar); + + const createComponent = ({ props, attrs } = {}) => { + wrapper = shallowMount(ProjectAvatar, { propsData: { ...defaultProps, ...props }, attrs }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders GlAvatar with correct props', () => { + createComponent(); + + const avatar = findGlAvatar(); + expect(avatar.exists()).toBe(true); + expect(avatar.props()).toMatchObject({ + alt: defaultProps.projectName, + entityName: defaultProps.projectName, + size: 32, + src: '', + }); + }); + + describe('with `size` prop', () => { + it('renders GlAvatar with specified `size` prop', () => { + const mockSize = 48; + createComponent({ props: { size: mockSize } }); + + const avatar = findGlAvatar(); + expect(avatar.props('size')).toBe(mockSize); + }); + }); + + describe('with `projectAvatarUrl` prop', () => { + it('renders GlAvatar with specified `src` prop', () => { + const mockProjectAvatarUrl = 'https://gitlab.com'; + createComponent({ props: { projectAvatarUrl: mockProjectAvatarUrl } }); + + const avatar = findGlAvatar(); + expect(avatar.props('src')).toBe(mockProjectAvatarUrl); + }); + }); + + describe.each` + alt + ${''} + ${'custom-alt'} + `('when `alt` prop is "$alt"', ({ alt }) => { + it('renders GlAvatar with specified `alt` attribute', () => { + createComponent({ props: { alt } }); + + const avatar = findGlAvatar(); + expect(avatar.props('alt')).toBe(alt); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js b/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js index 649eb2643f1..ab028ea52b7 100644 --- a/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js +++ b/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js @@ -1,5 +1,6 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import { trimText } from 'helpers/text_helper'; +import ProjectAvatar from '~/vue_shared/components/deprecated_project_avatar/default.vue'; import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue'; const localVue = createLocalVue(); @@ -53,7 +54,7 @@ describe('ProjectListItem component', () => { it(`renders the project avatar`, () => { wrapper = shallowMount(Component, options); - expect(wrapper.find('.js-project-avatar').exists()).toBe(true); + expect(wrapper.findComponent(ProjectAvatar).exists()).toBe(true); }); it(`renders a simple namespace name with a trailing slash`, () => { diff --git a/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap index add0c36a120..cdfe311acd9 100644 --- a/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap +++ b/spec/frontend/vue_shared/components/resizable_chart/__snapshots__/resizable_chart_container_spec.js.snap @@ -2,20 +2,22 @@ exports[`Resizable Chart Container renders the component 1`] = ` <div> - <div - class="slot" - > - <span - class="width" + <template> + <div + class="slot" > - 0 - </span> - - <span - class="height" - > - 0 - </span> - </div> + <span + class="width" + > + 0 + </span> + + <span + class="height" + > + 0 + </span> + </div> + </template> </div> `; diff --git a/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js index 1fce3c5d0b0..40f0c0f29f2 100644 --- a/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js +++ b/spec/frontend/vue_shared/components/resizable_chart/resizable_chart_container_spec.js @@ -16,10 +16,12 @@ describe('Resizable Chart Container', () => { wrapper = mount(ResizableChartContainer, { scopedSlots: { default: ` - <div class="slot" slot-scope="{ width, height }"> - <span class="width">{{width}}</span> - <span class="height">{{height}}</span> - </div> + <template #default="{ width, height }"> + <div class="slot"> + <span class="width">{{width}}</span> + <span class="height">{{height}}</span> + </div> + </template> `, }, }); diff --git a/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js b/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js index d58c87d66cb..395c74dcba6 100644 --- a/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js +++ b/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js @@ -3,7 +3,7 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; import { - expectedDownloadDropdownProps, + expectedDownloadDropdownPropsWithTitle, securityReportMergeRequestDownloadPathsQueryResponse, } from 'jest/vue_shared/security_reports/mock_data'; import createFlash from '~/flash'; @@ -80,7 +80,7 @@ describe('Merge request artifact Download', () => { }); it('renders the download dropdown', () => { - expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps); + expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownPropsWithTitle); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/copyable_field_spec.js b/spec/frontend/vue_shared/components/sidebar/copyable_field_spec.js index b99b1a66b79..3980033862e 100644 --- a/spec/frontend/vue_shared/components/sidebar/copyable_field_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/copyable_field_spec.js @@ -1,4 +1,4 @@ -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlSprintf } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import CopyableField from '~/vue_shared/components/sidebar/copyable_field.vue'; @@ -14,6 +14,9 @@ describe('SidebarCopyableField', () => { const createComponent = (propsData = defaultProps) => { wrapper = shallowMount(CopyableField, { propsData, + stubs: { + GlSprintf, + }, }); }; diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js index 60903933505..06ea88c09a0 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js @@ -54,7 +54,6 @@ describe('DropdownContentsLabelsView', () => { afterEach(() => { wrapper.destroy(); - wrapper = null; }); const findDropdownContent = () => wrapper.find('[data-testid="dropdown-content"]'); @@ -381,6 +380,15 @@ describe('DropdownContentsLabelsView', () => { expect(findDropdownFooter().exists()).toBe(false); }); + it('does not render footer list items when `allowLabelCreate` is false and `labelsManagePath` is null', () => { + createComponent({ + ...mockConfig, + allowLabelCreate: false, + labelsManagePath: null, + }); + expect(findDropdownFooter().exists()).toBe(false); + }); + it('renders footer list items when `state.variant` is "embedded"', () => { expect(findDropdownFooter().exists()).toBe(true); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js index 3f11095cb04..46ade5d5857 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/actions_spec.js @@ -1,11 +1,14 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; +import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import * as actions from '~/vue_shared/components/sidebar/labels_select_vue/store/actions'; import * as types from '~/vue_shared/components/sidebar/labels_select_vue/store/mutation_types'; import defaultState from '~/vue_shared/components/sidebar/labels_select_vue/store/state'; +jest.mock('~/flash'); + describe('LabelsSelect Actions', () => { let state; const mockInitialState = { @@ -91,10 +94,6 @@ describe('LabelsSelect Actions', () => { }); describe('receiveLabelsFailure', () => { - beforeEach(() => { - setFixtures('<div class="flash-container"></div>'); - }); - it('sets value `state.labelsFetchInProgress` to `false`', (done) => { testAction( actions.receiveLabelsFailure, @@ -109,9 +108,7 @@ describe('LabelsSelect Actions', () => { it('shows flash error', () => { actions.receiveLabelsFailure({ commit: () => {} }); - expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe( - 'Error fetching labels.', - ); + expect(createFlash).toHaveBeenCalledWith({ message: 'Error fetching labels.' }); }); }); @@ -186,10 +183,6 @@ describe('LabelsSelect Actions', () => { }); describe('receiveCreateLabelFailure', () => { - beforeEach(() => { - setFixtures('<div class="flash-container"></div>'); - }); - it('sets value `state.labelCreateInProgress` to `false`', (done) => { testAction( actions.receiveCreateLabelFailure, @@ -204,9 +197,7 @@ describe('LabelsSelect Actions', () => { it('shows flash error', () => { actions.receiveCreateLabelFailure({ commit: () => {} }); - expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe( - 'Error creating label.', - ); + expect(createFlash).toHaveBeenCalledWith({ message: 'Error creating label.' }); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js index ab266ac8aed..1d2a9c34599 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/store/mutations_spec.js @@ -153,7 +153,16 @@ describe('LabelsSelect Mutations', () => { }); describe(`${types.UPDATE_SELECTED_LABELS}`, () => { - const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]; + let labels; + + beforeEach(() => { + labels = [ + { id: 1, title: 'scoped::test', set: true }, + { id: 2, set: false, title: 'scoped::one' }, + { id: 3, title: '' }, + { id: 4, title: '' }, + ]; + }); it('updates `state.labels` to include `touched` and `set` props based on provided `labels` param', () => { const updatedLabelIds = [2]; @@ -169,5 +178,23 @@ describe('LabelsSelect Mutations', () => { } }); }); + + describe('when label is scoped', () => { + it('unsets the currently selected scoped label and sets the current label', () => { + const state = { + labels, + }; + mutations[types.UPDATE_SELECTED_LABELS](state, { + labels: [{ id: 2, title: 'scoped::one' }], + }); + + expect(state.labels).toEqual([ + { id: 1, title: 'scoped::test', set: false }, + { id: 2, set: true, title: 'scoped::one', touched: true }, + { id: 3, title: '' }, + { id: 4, title: '' }, + ]); + }); + }); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_value_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_value_spec.js index 59f3268c000..b3ffee2d020 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_value_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/dropdown_value_spec.js @@ -1,88 +1,97 @@ import { GlLabel } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; +import { shallowMount } from '@vue/test-utils'; import DropdownValue from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_value.vue'; -import labelsSelectModule from '~/vue_shared/components/sidebar/labels_select_widget/store'; - -import { mockConfig, mockRegularLabel, mockScopedLabel } from './mock_data'; - -const localVue = createLocalVue(); -localVue.use(Vuex); +import { mockRegularLabel, mockScopedLabel } from './mock_data'; describe('DropdownValue', () => { let wrapper; - const createComponent = (initialState = {}, slots = {}) => { - const store = new Vuex.Store(labelsSelectModule()); - - store.dispatch('setInitialState', { ...mockConfig, ...initialState }); + const findAllLabels = () => wrapper.findAllComponents(GlLabel); + const findRegularLabel = () => findAllLabels().at(0); + const findScopedLabel = () => findAllLabels().at(1); + const findWrapper = () => wrapper.find('[data-testid="value-wrapper"]'); + const findEmptyPlaceholder = () => wrapper.find('[data-testid="empty-placeholder"]'); + const createComponent = (props = {}, slots = {}) => { wrapper = shallowMount(DropdownValue, { - localVue, - store, slots, + propsData: { + selectedLabels: [mockRegularLabel, mockScopedLabel], + allowLabelRemove: true, + allowScopedLabels: true, + labelsFilterBasePath: '/gitlab-org/my-project/issues', + labelsFilterParam: 'label_name', + ...props, + }, }); }; afterEach(() => { wrapper.destroy(); - wrapper = null; }); - describe('methods', () => { - describe('labelFilterUrl', () => { - it('returns a label filter URL based on provided label param', () => { - createComponent(); - - expect(wrapper.vm.labelFilterUrl(mockRegularLabel)).toBe( - '/gitlab-org/my-project/issues?label_name[]=Foo%20Label', - ); - }); + describe('when there are no labels', () => { + beforeEach(() => { + createComponent( + { + selectedLabels: [], + }, + { + default: 'None', + }, + ); }); - describe('scopedLabel', () => { - beforeEach(() => { - createComponent(); - }); + it('does not apply `has-labels` class to the wrapping container', () => { + expect(findWrapper().classes()).not.toContain('has-labels'); + }); - it('returns `true` when provided label param is a scoped label', () => { - expect(wrapper.vm.scopedLabel(mockScopedLabel)).toBe(true); - }); + it('renders an empty placeholder', () => { + expect(findEmptyPlaceholder().exists()).toBe(true); + expect(findEmptyPlaceholder().text()).toBe('None'); + }); - it('returns `false` when provided label param is a regular label', () => { - expect(wrapper.vm.scopedLabel(mockRegularLabel)).toBe(false); - }); + it('does not render any labels', () => { + expect(findAllLabels().length).toBe(0); }); }); - describe('template', () => { - it('renders class `has-labels` on component container element when `selectedLabels` is not empty', () => { + describe('when there are labels', () => { + beforeEach(() => { createComponent(); + }); - expect(wrapper.attributes('class')).toContain('has-labels'); + it('applies `has-labels` class to the wrapping container', () => { + expect(findWrapper().classes()).toContain('has-labels'); }); - it('renders element containing `None` when `selectedLabels` is empty', () => { - createComponent( - { - selectedLabels: [], - }, - { - default: 'None', - }, - ); - const noneEl = wrapper.find('span.text-secondary'); + it('does not render an empty placeholder', () => { + expect(findEmptyPlaceholder().exists()).toBe(false); + }); - expect(noneEl.exists()).toBe(true); - expect(noneEl.text()).toBe('None'); + it('renders a list of two labels', () => { + expect(findAllLabels().length).toBe(2); }); - it('renders labels when `selectedLabels` is not empty', () => { - createComponent(); + it('passes correct props to the regular label', () => { + expect(findRegularLabel().props('target')).toBe( + '/gitlab-org/my-project/issues?label_name[]=Foo%20Label', + ); + expect(findRegularLabel().props('scoped')).toBe(false); + }); + + it('passes correct props to the scoped label', () => { + expect(findScopedLabel().props('target')).toBe( + '/gitlab-org/my-project/issues?label_name[]=Foo%3A%3ABar', + ); + expect(findScopedLabel().props('scoped')).toBe(true); + }); - expect(wrapper.findAll(GlLabel).length).toBe(2); + it('emits `onLabelRemove` event with the correct ID', () => { + findRegularLabel().vm.$emit('close'); + expect(wrapper.emitted('onLabelRemove')).toEqual([[mockRegularLabel.id]]); }); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js index ee1346c362f..66971446f47 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/labels_select_root_spec.js @@ -34,6 +34,10 @@ describe('LabelsSelectRoot', () => { stubs: { 'dropdown-contents': DropdownContents, }, + provide: { + iid: '1', + projectPath: 'test', + }, }); }; diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/actions_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/actions_spec.js index 7ef4b769b6b..27de7de2411 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/actions_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/actions_spec.js @@ -1,11 +1,14 @@ import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; +import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import * as actions from '~/vue_shared/components/sidebar/labels_select_widget/store/actions'; import * as types from '~/vue_shared/components/sidebar/labels_select_widget/store/mutation_types'; import defaultState from '~/vue_shared/components/sidebar/labels_select_widget/store/state'; +jest.mock('~/flash'); + describe('LabelsSelect Actions', () => { let state; const mockInitialState = { @@ -91,10 +94,6 @@ describe('LabelsSelect Actions', () => { }); describe('receiveLabelsFailure', () => { - beforeEach(() => { - setFixtures('<div class="flash-container"></div>'); - }); - it('sets value `state.labelsFetchInProgress` to `false`', (done) => { testAction( actions.receiveLabelsFailure, @@ -109,9 +108,7 @@ describe('LabelsSelect Actions', () => { it('shows flash error', () => { actions.receiveLabelsFailure({ commit: () => {} }); - expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe( - 'Error fetching labels.', - ); + expect(createFlash).toHaveBeenCalledWith({ message: 'Error fetching labels.' }); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/mutations_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/mutations_spec.js index acb275b5d90..9e965cb33e8 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/mutations_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_widget/store/mutations_spec.js @@ -120,7 +120,16 @@ describe('LabelsSelect Mutations', () => { }); describe(`${types.UPDATE_SELECTED_LABELS}`, () => { - const labels = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }]; + let labels; + + beforeEach(() => { + labels = [ + { id: 1, title: 'scoped::test', set: true }, + { id: 2, set: false, title: 'scoped::one' }, + { id: 3, title: '' }, + { id: 4, title: '' }, + ]; + }); it('updates `state.labels` to include `touched` and `set` props based on provided `labels` param', () => { const updatedLabelIds = [2]; @@ -136,5 +145,23 @@ describe('LabelsSelect Mutations', () => { } }); }); + + describe('when label is scoped', () => { + it('unsets the currently selected scoped label and sets the current label', () => { + const state = { + labels, + }; + mutations[types.UPDATE_SELECTED_LABELS](state, { + labels: [{ id: 2, title: 'scoped::one' }], + }); + + expect(state.labels).toEqual([ + { id: 1, title: 'scoped::test', set: false }, + { id: 2, set: true, title: 'scoped::one', touched: true }, + { id: 3, title: '' }, + { id: 4, title: '' }, + ]); + }); + }); }); }); diff --git a/spec/frontend/vue_shared/components/todo_button_spec.js b/spec/frontend/vue_shared/components/sidebar/todo_button_spec.js index 8043bb7785b..de3e1ccfb03 100644 --- a/spec/frontend/vue_shared/components/todo_button_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/todo_button_spec.js @@ -1,9 +1,10 @@ import { GlButton } from '@gitlab/ui'; import { shallowMount, mount } from '@vue/test-utils'; -import TodoButton from '~/vue_shared/components/todo_button.vue'; +import TodoButton from '~/vue_shared/components/sidebar/todo_toggle/todo_button.vue'; describe('Todo Button', () => { let wrapper; + let dispatchEventSpy; const createComponent = (props = {}, mountFn = shallowMount) => { wrapper = mountFn(TodoButton, { @@ -13,8 +14,17 @@ describe('Todo Button', () => { }); }; + beforeEach(() => { + dispatchEventSpy = jest.spyOn(document, 'dispatchEvent'); + jest.spyOn(document, 'querySelector').mockReturnValue({ + innerText: 2, + }); + }); + afterEach(() => { wrapper.destroy(); + dispatchEventSpy = null; + jest.clearAllMocks(); }); it('renders GlButton', () => { @@ -30,6 +40,16 @@ describe('Todo Button', () => { expect(wrapper.emitted().click).toBeTruthy(); }); + it('calls dispatchDocumentEvent to update global To-Do counter correctly', () => { + createComponent({}, mount); + wrapper.find(GlButton).trigger('click'); + const dispatchedEvent = dispatchEventSpy.mock.calls[0][0]; + + expect(dispatchEventSpy).toHaveBeenCalledTimes(1); + expect(dispatchedEvent.detail).toEqual({ count: 1 }); + expect(dispatchedEvent.type).toBe('todo:toggle'); + }); + it.each` label | isTodo ${'Mark as done'} | ${true} diff --git a/spec/frontend/vue_shared/components/editor_lite_spec.js b/spec/frontend/vue_shared/components/source_editor_spec.js index badd5aed0e3..dca4d60e23c 100644 --- a/spec/frontend/vue_shared/components/editor_lite_spec.js +++ b/spec/frontend/vue_shared/components/source_editor_spec.js @@ -1,12 +1,12 @@ import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; import { EDITOR_READY_EVENT } from '~/editor/constants'; -import Editor from '~/editor/editor_lite'; -import EditorLite from '~/vue_shared/components/editor_lite.vue'; +import Editor from '~/editor/source_editor'; +import SourceEditor from '~/vue_shared/components/source_editor.vue'; -jest.mock('~/editor/editor_lite'); +jest.mock('~/editor/source_editor'); -describe('Editor Lite component', () => { +describe('Source Editor component', () => { let wrapper; let mockInstance; @@ -30,7 +30,7 @@ describe('Editor Lite component', () => { }; }); function createComponent(props = {}) { - wrapper = shallowMount(EditorLite, { + wrapper = shallowMount(SourceEditor, { propsData: { value, fileName, @@ -73,10 +73,10 @@ describe('Editor Lite component', () => { createComponent({ value: undefined }); expect(spy).not.toHaveBeenCalled(); - expect(wrapper.find('[id^="editor-lite-"]').exists()).toBe(true); + expect(wrapper.find('[id^="source-editor-"]').exists()).toBe(true); }); - it('initialises Editor Lite instance', () => { + it('initialises Source Editor instance', () => { const el = wrapper.find({ ref: 'editor' }).element; expect(createInstanceMock).toHaveBeenCalledWith({ el, @@ -111,7 +111,7 @@ describe('Editor Lite component', () => { expect(wrapper.emitted().input).toEqual([[value]]); }); - it('emits EDITOR_READY_EVENT event when the Editor Lite is ready', async () => { + it('emits EDITOR_READY_EVENT event when the Source Editor is ready', async () => { const el = wrapper.find({ ref: 'editor' }).element; expect(wrapper.emitted()[EDITOR_READY_EVENT]).toBeUndefined(); diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js index 87fe8619f28..538e67ef354 100644 --- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js +++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js @@ -1,5 +1,5 @@ -import { GlSkeletonLoader, GlSprintf, GlIcon } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; +import { GlSkeletonLoader, GlIcon } from '@gitlab/ui'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import { AVAILABILITY_STATUS } from '~/set_status_modal/utils'; import UserNameWithStatus from '~/sidebar/components/assignees/user_name_with_status.vue'; import UserPopover from '~/vue_shared/components/user_popover/user_popover.vue'; @@ -13,6 +13,7 @@ const DEFAULT_PROPS = { bio: null, workInformation: null, status: null, + pronouns: 'they/them', loaded: true, }, }; @@ -30,23 +31,18 @@ describe('User Popover Component', () => { wrapper.destroy(); }); - const findByTestId = (testid) => wrapper.find(`[data-testid="${testid}"]`); const findUserStatus = () => wrapper.find('.js-user-status'); const findTarget = () => document.querySelector('.js-user-link'); const findUserName = () => wrapper.find(UserNameWithStatus); - const findSecurityBotDocsLink = () => findByTestId('user-popover-bot-docs-link'); + const findSecurityBotDocsLink = () => wrapper.findByTestId('user-popover-bot-docs-link'); const createWrapper = (props = {}, options = {}) => { - wrapper = shallowMount(UserPopover, { + wrapper = mountExtended(UserPopover, { propsData: { ...DEFAULT_PROPS, target: findTarget(), ...props, }, - stubs: { - GlSprintf, - UserNameWithStatus, - }, ...options, }); }; @@ -232,6 +228,12 @@ describe('User Popover Component', () => { expect(wrapper.text()).not.toContain('(Busy)'); }); + + it('passes `pronouns` prop to `UserNameWithStatus` component', () => { + createWrapper(); + + expect(findUserName().props('pronouns')).toBe('they/them'); + }); }); describe('bot user', () => { diff --git a/spec/frontend/vue_shared/components/user_select_spec.js b/spec/frontend/vue_shared/components/user_select_spec.js index 0fabc6525ea..b777ac0a0a4 100644 --- a/spec/frontend/vue_shared/components/user_select_spec.js +++ b/spec/frontend/vue_shared/components/user_select_spec.js @@ -275,48 +275,4 @@ describe('User select dropdown', () => { expect(findEmptySearchResults().exists()).toBe(true); }); }); - - // TODO Remove this test after the following issue is resolved in the backend - // https://gitlab.com/gitlab-org/gitlab/-/issues/329750 - describe('temporary error suppression', () => { - beforeEach(() => { - jest.spyOn(console, 'error').mockImplementation(); - }); - - const nullError = { message: 'Cannot return null for non-nullable field GroupMember.user' }; - - it.each` - mockErrors - ${[nullError]} - ${[nullError, nullError]} - `('does not emit errors', async ({ mockErrors }) => { - createComponent({ - searchQueryHandler: jest.fn().mockResolvedValue({ - errors: mockErrors, - }), - }); - await waitForSearch(); - - expect(wrapper.emitted()).toEqual({}); - // eslint-disable-next-line no-console - expect(console.error).toHaveBeenCalled(); - }); - - it.each` - mockErrors - ${[{ message: 'serious error' }]} - ${[nullError, { message: 'serious error' }]} - `('emits error when non-null related errors are included', async ({ mockErrors }) => { - createComponent({ - searchQueryHandler: jest.fn().mockResolvedValue({ - errors: mockErrors, - }), - }); - await waitForSearch(); - - expect(wrapper.emitted('error')).toEqual([[]]); - // eslint-disable-next-line no-console - expect(console.error).not.toHaveBeenCalled(); - }); - }); }); diff --git a/spec/frontend/vue_shared/components/web_ide_link_spec.js b/spec/frontend/vue_shared/components/web_ide_link_spec.js index 5a6c91bda9f..0fd4d0dab87 100644 --- a/spec/frontend/vue_shared/components/web_ide_link_spec.js +++ b/spec/frontend/vue_shared/components/web_ide_link_spec.js @@ -15,8 +15,8 @@ const ACTION_EDIT = { tooltip: '', attrs: { 'data-qa-selector': 'edit_button', - 'data-track-event': 'click_edit', - 'data-track-label': 'Edit', + 'data-track-action': 'click_consolidated_edit', + 'data-track-label': 'edit', }, }; const ACTION_EDIT_CONFIRM_FORK = { @@ -32,8 +32,8 @@ const ACTION_WEB_IDE = { text: 'Web IDE', attrs: { 'data-qa-selector': 'web_ide_button', - 'data-track-event': 'click_edit_ide', - 'data-track-label': 'Web IDE', + 'data-track-action': 'click_consolidated_edit_ide', + 'data-track-label': 'web_ide', }, }; const ACTION_WEB_IDE_CONFIRM_FORK = { diff --git a/spec/frontend/vue_shared/new_namespace/components/welcome_spec.js b/spec/frontend/vue_shared/new_namespace/components/welcome_spec.js index 602213fca83..2d51f6dbeeb 100644 --- a/spec/frontend/vue_shared/new_namespace/components/welcome_spec.js +++ b/spec/frontend/vue_shared/new_namespace/components/welcome_spec.js @@ -1,12 +1,8 @@ import { shallowMount } from '@vue/test-utils'; import { nextTick } from 'vue'; import { mockTracking } from 'helpers/tracking_helper'; -import { TRACKING_CONTEXT_SCHEMA } from '~/experimentation/constants'; -import { getExperimentData } from '~/experimentation/utils'; import WelcomePage from '~/vue_shared/new_namespace/components/welcome.vue'; -jest.mock('~/experimentation/utils', () => ({ getExperimentData: jest.fn() })); - describe('Welcome page', () => { let wrapper; let trackingSpy; @@ -28,7 +24,6 @@ describe('Welcome page', () => { beforeEach(() => { trackingSpy = mockTracking('_category_', document, jest.spyOn); trackingSpy.mockImplementation(() => {}); - getExperimentData.mockReturnValue(undefined); }); afterEach(() => { @@ -38,7 +33,7 @@ describe('Welcome page', () => { }); it('tracks link clicks', async () => { - createComponent({ propsData: { experiment: 'foo', panels: [{ name: 'test', href: '#' }] } }); + createComponent({ propsData: { panels: [{ name: 'test', href: '#' }] } }); const link = wrapper.find('a'); link.trigger('click'); await nextTick(); @@ -47,25 +42,6 @@ describe('Welcome page', () => { }); }); - it('adds experiment data if in experiment', async () => { - const mockExperimentData = 'data'; - getExperimentData.mockReturnValue(mockExperimentData); - - createComponent({ propsData: { experiment: 'foo', panels: [{ name: 'test', href: '#' }] } }); - const link = wrapper.find('a'); - link.trigger('click'); - await nextTick(); - return wrapper.vm.$nextTick().then(() => { - expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_tab', { - label: 'test', - context: { - data: mockExperimentData, - schema: TRACKING_CONTEXT_SCHEMA, - }, - }); - }); - }); - it('renders footer slot if provided', () => { const DUMMY = 'Test message'; createComponent({ diff --git a/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js b/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js index 30937921900..6115dc6e61b 100644 --- a/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js +++ b/spec/frontend/vue_shared/new_namespace/new_namespace_page_spec.js @@ -37,13 +37,6 @@ describe('Experimental new project creation app', () => { window.location.hash = ''; }); - it('passes experiment to welcome component if provided', () => { - const EXPERIMENT = 'foo'; - createComponent({ propsData: { experiment: EXPERIMENT } }); - - expect(findWelcomePage().props().experiment).toBe(EXPERIMENT); - }); - describe('with empty hash', () => { beforeEach(() => { createComponent(); diff --git a/spec/frontend/vue_shared/oncall_schedules_list_spec.js b/spec/frontend/vue_shared/oncall_schedules_list_spec.js index 5c30809c09b..f83a5187b8b 100644 --- a/spec/frontend/vue_shared/oncall_schedules_list_spec.js +++ b/spec/frontend/vue_shared/oncall_schedules_list_spec.js @@ -18,7 +18,7 @@ const mockSchedules = [ }, ]; -const userName = 'User 1'; +const userName = "O'User"; describe('On-call schedules list', () => { let wrapper; diff --git a/spec/frontend/vue_shared/plugins/global_toast_spec.js b/spec/frontend/vue_shared/plugins/global_toast_spec.js index 89f43a5e556..322586a772c 100644 --- a/spec/frontend/vue_shared/plugins/global_toast_spec.js +++ b/spec/frontend/vue_shared/plugins/global_toast_spec.js @@ -1,11 +1,10 @@ -import Vue from 'vue'; -import toast from '~/vue_shared/plugins/global_toast'; +import toast, { instance } from '~/vue_shared/plugins/global_toast'; describe('Global toast', () => { let spyFunc; beforeEach(() => { - spyFunc = jest.spyOn(Vue.prototype.$toast, 'show').mockImplementation(() => {}); + spyFunc = jest.spyOn(instance.$toast, 'show').mockImplementation(() => {}); }); afterEach(() => { @@ -18,7 +17,7 @@ describe('Global toast', () => { toast(arg1, arg2); - expect(Vue.prototype.$toast.show).toHaveBeenCalledTimes(1); - expect(Vue.prototype.$toast.show).toHaveBeenCalledWith(arg1, arg2); + expect(instance.$toast.show).toHaveBeenCalledTimes(1); + expect(instance.$toast.show).toHaveBeenCalledWith(arg1, arg2); }); }); diff --git a/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js index 517eee6a729..facbd51168c 100644 --- a/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js +++ b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js @@ -9,6 +9,7 @@ import waitForPromises from 'helpers/wait_for_promises'; import { humanize } from '~/lib/utils/text_utility'; import { redirectTo } from '~/lib/utils/url_utility'; import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue'; +import { REPORT_TYPE_SAST } from '~/vue_shared/security_reports/constants'; import { buildConfigureSecurityFeatureMockFactory } from './apollo_mocks'; jest.mock('~/lib/utils/url_utility'); @@ -169,6 +170,29 @@ describe('ManageViaMr component', () => { }, ); + describe('canRender static method', () => { + it.each` + context | type | available | configured | canEnableByMergeRequest | expectedValue + ${'an unconfigured feature'} | ${REPORT_TYPE_SAST} | ${true} | ${false} | ${true} | ${true} + ${'a configured feature'} | ${REPORT_TYPE_SAST} | ${true} | ${true} | ${true} | ${false} + ${'an unavailable feature'} | ${REPORT_TYPE_SAST} | ${false} | ${false} | ${true} | ${false} + ${'a feature which cannot be enabled via MR'} | ${REPORT_TYPE_SAST} | ${true} | ${false} | ${false} | ${false} + ${'an unknown feature'} | ${'foo'} | ${true} | ${false} | ${true} | ${false} + `( + 'given $context returns $expectedValue', + ({ type, available, configured, canEnableByMergeRequest, expectedValue }) => { + expect( + ManageViaMr.canRender({ + type, + available, + configured, + canEnableByMergeRequest, + }), + ).toBe(expectedValue); + }, + ); + }); + describe('button props', () => { it('passes the variant and category props to the GlButton', () => { const variant = 'danger'; diff --git a/spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js b/spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js index 9138d2d3f4c..4b75da0b126 100644 --- a/spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js +++ b/spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js @@ -40,14 +40,13 @@ describe('SecurityReportDownloadDropdown component', () => { expect(findDropdown().props('loading')).toBe(false); }); - it('renders a dropdown items for each artifact', () => { + it('renders a dropdown item for each artifact', () => { artifacts.forEach((artifact, i) => { const item = findDropdownItems().at(i); expect(item.text()).toContain(artifact.name); - expect(item.attributes()).toMatchObject({ - href: artifact.path, - download: expect.any(String), - }); + + expect(item.element.getAttribute('href')).toBe(artifact.path); + expect(item.element.getAttribute('download')).toBeDefined(); }); }); }); @@ -61,4 +60,32 @@ describe('SecurityReportDownloadDropdown component', () => { expect(findDropdown().props('loading')).toBe(true); }); }); + + describe('given title props', () => { + beforeEach(() => { + createComponent({ artifacts: [], loading: true, title: 'test title' }); + }); + + it('should render title', () => { + expect(findDropdown().attributes('title')).toBe('test title'); + }); + + it('should not render text', () => { + expect(findDropdown().text().trim()).toBe(''); + }); + }); + + describe('given text props', () => { + beforeEach(() => { + createComponent({ artifacts: [], loading: true, text: 'test text' }); + }); + + it('should not render title', () => { + expect(findDropdown().props().title).not.toBeDefined(); + }); + + it('should render text', () => { + expect(findDropdown().props().text).toContain('test text'); + }); + }); }); diff --git a/spec/frontend/vue_shared/security_reports/mock_data.js b/spec/frontend/vue_shared/security_reports/mock_data.js index bd9ce3b7314..06631710509 100644 --- a/spec/frontend/vue_shared/security_reports/mock_data.js +++ b/spec/frontend/vue_shared/security_reports/mock_data.js @@ -581,9 +581,18 @@ export const secretDetectionArtifacts = [ }, ]; -export const expectedDownloadDropdownProps = { +export const expectedDownloadDropdownPropsWithTitle = { loading: false, artifacts: [...secretDetectionArtifacts, ...sastArtifacts], + text: '', + title: 'Download results', +}; + +export const expectedDownloadDropdownPropsWithText = { + loading: false, + artifacts: [...secretDetectionArtifacts, ...sastArtifacts], + title: '', + text: 'Download results', }; /** diff --git a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js b/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js index 038d7754776..bef538e1ff1 100644 --- a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js +++ b/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js @@ -8,7 +8,7 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import { trimText } from 'helpers/text_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { - expectedDownloadDropdownProps, + expectedDownloadDropdownPropsWithText, securityReportMergeRequestDownloadPathsQueryNoArtifactsResponse, securityReportMergeRequestDownloadPathsQueryResponse, sastDiffSuccessMock, @@ -99,7 +99,7 @@ describe('Security reports app', () => { }); it('renders the download dropdown', () => { - expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps); + expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownPropsWithText); }); it('renders the expected message', () => { @@ -203,7 +203,7 @@ describe('Security reports app', () => { }); it('renders the download dropdown', () => { - expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps); + expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownPropsWithText); }); }); @@ -225,7 +225,7 @@ describe('Security reports app', () => { }); it('renders the download dropdown', () => { - expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps); + expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownPropsWithText); }); }); @@ -247,7 +247,7 @@ describe('Security reports app', () => { }); it('renders the download dropdown', () => { - expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps); + expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownPropsWithText); }); }); |