diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-19 01:45:44 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-19 01:45:44 +0000 |
commit | 85dc423f7090da0a52c73eb66faf22ddb20efff9 (patch) | |
tree | 9160f299afd8c80c038f08e1545be119f5e3f1e1 /spec/frontend/releases/components | |
parent | 15c2c8c66dbe422588e5411eee7e68f1fa440bb8 (diff) | |
download | gitlab-ce-85dc423f7090da0a52c73eb66faf22ddb20efff9.tar.gz |
Add latest changes from gitlab-org/gitlab@13-4-stable-ee
Diffstat (limited to 'spec/frontend/releases/components')
10 files changed, 408 insertions, 90 deletions
diff --git a/spec/frontend/releases/components/app_index_spec.js b/spec/frontend/releases/components/app_index_spec.js index 8eafe07cb2f..bcb87509cc3 100644 --- a/spec/frontend/releases/components/app_index_spec.js +++ b/spec/frontend/releases/components/app_index_spec.js @@ -1,12 +1,11 @@ import { range as rge } from 'lodash'; -import Vue from 'vue'; -import { mountComponentWithStore } from 'helpers/vue_mount_component_helper'; +import Vuex from 'vuex'; +import { shallowMount, createLocalVue } from '@vue/test-utils'; import waitForPromises from 'helpers/wait_for_promises'; -import app from '~/releases/components/app_index.vue'; +import ReleasesApp from '~/releases/components/app_index.vue'; import createStore from '~/releases/stores'; -import listModule from '~/releases/stores/modules/list'; +import createListModule from '~/releases/stores/modules/list'; import api from '~/api'; -import { resetStore } from '../stores/modules/list/helpers'; import { pageInfoHeadersWithoutPagination, pageInfoHeadersWithPagination, @@ -14,30 +13,67 @@ import { releases, } from '../mock_data'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; + +const localVue = createLocalVue(); +localVue.use(Vuex); describe('Releases App ', () => { - const Component = Vue.extend(app); - let store; - let vm; - let releasesPagination; + let wrapper; + let fetchReleaseSpy; + + const releasesPagination = rge(21).map(index => ({ + ...convertObjectPropsToCamelCase(release, { deep: true }), + tagName: `${index}.00`, + })); - const props = { + const defaultInitialState = { projectId: 'gitlab-ce', + projectPath: 'gitlab-org/gitlab-ce', documentationPath: 'help/releases', illustrationPath: 'illustration/path', }; - beforeEach(() => { - store = createStore({ modules: { list: listModule } }); - releasesPagination = rge(21).map(index => ({ - ...convertObjectPropsToCamelCase(release, { deep: true }), - tagName: `${index}.00`, - })); - }); + const createComponent = (stateUpdates = {}) => { + const listModule = createListModule({ + ...defaultInitialState, + ...stateUpdates, + }); + + fetchReleaseSpy = jest.spyOn(listModule.actions, 'fetchReleases'); + + const store = createStore({ + modules: { list: listModule }, + featureFlags: { + graphqlReleaseData: true, + graphqlReleasesPage: false, + graphqlMilestoneStats: true, + }, + }); + + wrapper = shallowMount(ReleasesApp, { + store, + localVue, + }); + }; afterEach(() => { - resetStore(store); - vm.$destroy(); + wrapper.destroy(); + }); + + describe('on startup', () => { + beforeEach(() => { + jest + .spyOn(api, 'releases') + .mockResolvedValue({ data: releases, headers: pageInfoHeadersWithoutPagination }); + + createComponent(); + }); + + it('calls fetchRelease with the page parameter', () => { + expect(fetchReleaseSpy).toHaveBeenCalledTimes(1); + expect(fetchReleaseSpy).toHaveBeenCalledWith(expect.anything(), { page: null }); + }); }); describe('while loading', () => { @@ -47,16 +83,15 @@ describe('Releases App ', () => { // Need to defer the return value here to the next stack, // otherwise the loading state disappears before our test even starts. .mockImplementation(() => waitForPromises().then(() => ({ data: [], headers: {} }))); - vm = mountComponentWithStore(Component, { props, store }); + + createComponent(); }); it('renders loading icon', () => { - expect(vm.$el.querySelector('.js-loading')).not.toBeNull(); - expect(vm.$el.querySelector('.js-empty-state')).toBeNull(); - expect(vm.$el.querySelector('.js-success-state')).toBeNull(); - expect(vm.$el.querySelector('.gl-pagination')).toBeNull(); - - return waitForPromises(); + expect(wrapper.contains('.js-loading')).toBe(true); + expect(wrapper.contains('.js-empty-state')).toBe(false); + expect(wrapper.contains('.js-success-state')).toBe(false); + expect(wrapper.contains(TablePagination)).toBe(false); }); }); @@ -65,14 +100,15 @@ describe('Releases App ', () => { jest .spyOn(api, 'releases') .mockResolvedValue({ data: releases, headers: pageInfoHeadersWithoutPagination }); - vm = mountComponentWithStore(Component, { props, store }); + + createComponent(); }); it('renders success state', () => { - expect(vm.$el.querySelector('.js-loading')).toBeNull(); - expect(vm.$el.querySelector('.js-empty-state')).toBeNull(); - expect(vm.$el.querySelector('.js-success-state')).not.toBeNull(); - expect(vm.$el.querySelector('.gl-pagination')).toBeNull(); + expect(wrapper.contains('.js-loading')).toBe(false); + expect(wrapper.contains('.js-empty-state')).toBe(false); + expect(wrapper.contains('.js-success-state')).toBe(true); + expect(wrapper.contains(TablePagination)).toBe(true); }); }); @@ -81,69 +117,60 @@ describe('Releases App ', () => { jest .spyOn(api, 'releases') .mockResolvedValue({ data: releasesPagination, headers: pageInfoHeadersWithPagination }); - vm = mountComponentWithStore(Component, { props, store }); + + createComponent(); }); it('renders success state', () => { - expect(vm.$el.querySelector('.js-loading')).toBeNull(); - expect(vm.$el.querySelector('.js-empty-state')).toBeNull(); - expect(vm.$el.querySelector('.js-success-state')).not.toBeNull(); - expect(vm.$el.querySelector('.gl-pagination')).not.toBeNull(); + expect(wrapper.contains('.js-loading')).toBe(false); + expect(wrapper.contains('.js-empty-state')).toBe(false); + expect(wrapper.contains('.js-success-state')).toBe(true); + expect(wrapper.contains(TablePagination)).toBe(true); }); }); describe('with empty request', () => { beforeEach(() => { jest.spyOn(api, 'releases').mockResolvedValue({ data: [], headers: {} }); - vm = mountComponentWithStore(Component, { props, store }); + + createComponent(); }); it('renders empty state', () => { - expect(vm.$el.querySelector('.js-loading')).toBeNull(); - expect(vm.$el.querySelector('.js-empty-state')).not.toBeNull(); - expect(vm.$el.querySelector('.js-success-state')).toBeNull(); - expect(vm.$el.querySelector('.gl-pagination')).toBeNull(); + expect(wrapper.contains('.js-loading')).toBe(false); + expect(wrapper.contains('.js-empty-state')).toBe(true); + expect(wrapper.contains('.js-success-state')).toBe(false); }); }); describe('"New release" button', () => { - const findNewReleaseButton = () => vm.$el.querySelector('.js-new-release-btn'); + const findNewReleaseButton = () => wrapper.find('.js-new-release-btn'); beforeEach(() => { jest.spyOn(api, 'releases').mockResolvedValue({ data: [], headers: {} }); }); - const factory = additionalProps => { - vm = mountComponentWithStore(Component, { - props: { - ...props, - ...additionalProps, - }, - store, - }); - }; - describe('when the user is allowed to create a new Release', () => { const newReleasePath = 'path/to/new/release'; beforeEach(() => { - factory({ newReleasePath }); + createComponent({ ...defaultInitialState, newReleasePath }); }); it('renders the "New release" button', () => { - expect(findNewReleaseButton()).not.toBeNull(); + expect(findNewReleaseButton().exists()).toBe(true); }); it('renders the "New release" button with the correct href', () => { - expect(findNewReleaseButton().getAttribute('href')).toBe(newReleasePath); + expect(findNewReleaseButton().attributes('href')).toBe(newReleasePath); }); }); describe('when the user is not allowed to create a new Release', () => { - beforeEach(() => factory()); + beforeEach(() => createComponent()); it('does not render the "New release" button', () => { - expect(findNewReleaseButton()).toBeNull(); + expect(findNewReleaseButton().exists()).toBe(false); }); }); }); diff --git a/spec/frontend/releases/components/app_show_spec.js b/spec/frontend/releases/components/app_show_spec.js index e757fe98661..502a1053663 100644 --- a/spec/frontend/releases/components/app_show_spec.js +++ b/spec/frontend/releases/components/app_show_spec.js @@ -1,6 +1,6 @@ import Vuex from 'vuex'; import { shallowMount } from '@vue/test-utils'; -import { GlSkeletonLoading } from '@gitlab/ui'; +import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui'; import ReleaseShowApp from '~/releases/components/app_show.vue'; import { release as originalRelease } from '../mock_data'; import ReleaseBlock from '~/releases/components/release_block.vue'; diff --git a/spec/frontend/releases/components/asset_links_form_spec.js b/spec/frontend/releases/components/asset_links_form_spec.js index 727d593d851..582c0b32716 100644 --- a/spec/frontend/releases/components/asset_links_form_spec.js +++ b/spec/frontend/releases/components/asset_links_form_spec.js @@ -115,14 +115,10 @@ describe('Release edit component', () => { const expectStoreMethodToBeCalled = () => { expect(actions.updateAssetLinkUrl).toHaveBeenCalledTimes(1); - expect(actions.updateAssetLinkUrl).toHaveBeenCalledWith( - expect.anything(), - { - linkIdToUpdate, - newUrl, - }, - undefined, - ); + expect(actions.updateAssetLinkUrl).toHaveBeenCalledWith(expect.anything(), { + linkIdToUpdate, + newUrl, + }); }; it('calls the "updateAssetLinkUrl" store method when text is entered into the "URL" input field', () => { @@ -177,14 +173,10 @@ describe('Release edit component', () => { const expectStoreMethodToBeCalled = () => { expect(actions.updateAssetLinkName).toHaveBeenCalledTimes(1); - expect(actions.updateAssetLinkName).toHaveBeenCalledWith( - expect.anything(), - { - linkIdToUpdate, - newName, - }, - undefined, - ); + expect(actions.updateAssetLinkName).toHaveBeenCalledWith(expect.anything(), { + linkIdToUpdate, + newName, + }); }; it('calls the "updateAssetLinkName" store method when text is entered into the "Link title" input field', () => { @@ -225,14 +217,10 @@ describe('Release edit component', () => { wrapper.find({ ref: 'typeSelect' }).vm.$emit('change', newType); expect(actions.updateAssetLinkType).toHaveBeenCalledTimes(1); - expect(actions.updateAssetLinkType).toHaveBeenCalledWith( - expect.anything(), - { - linkIdToUpdate, - newType, - }, - undefined, - ); + expect(actions.updateAssetLinkType).toHaveBeenCalledWith(expect.anything(), { + linkIdToUpdate, + newType, + }); }); it('selects the default asset type if no type was provided by the backend', () => { diff --git a/spec/frontend/releases/components/release_block_assets_spec.js b/spec/frontend/releases/components/release_block_assets_spec.js index 5e84290716c..3453ecbf8ab 100644 --- a/spec/frontend/releases/components/release_block_assets_spec.js +++ b/spec/frontend/releases/components/release_block_assets_spec.js @@ -128,7 +128,7 @@ describe('Release block assets', () => { describe('external vs internal links', () => { const containsExternalSourceIndicator = () => - wrapper.contains('[data-testid="external-link-indicator"]'); + wrapper.find('[data-testid="external-link-indicator"]').exists(); describe('when a link is external', () => { beforeEach(() => { diff --git a/spec/frontend/releases/components/release_block_footer_spec.js b/spec/frontend/releases/components/release_block_footer_spec.js index c066bfbf020..bde01cc0e00 100644 --- a/spec/frontend/releases/components/release_block_footer_spec.js +++ b/spec/frontend/releases/components/release_block_footer_spec.js @@ -1,9 +1,8 @@ import { mount } from '@vue/test-utils'; -import { GlLink } from '@gitlab/ui'; +import { GlLink, GlIcon } from '@gitlab/ui'; import { trimText } from 'helpers/text_helper'; import { cloneDeep } from 'lodash'; import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue'; -import Icon from '~/vue_shared/components/icon.vue'; import { release as originalRelease } from '../mock_data'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; @@ -56,7 +55,7 @@ describe('Release block footer', () => { beforeEach(() => factory()); it('renders the commit icon', () => { - const commitIcon = commitInfoSection().find(Icon); + const commitIcon = commitInfoSection().find(GlIcon); expect(commitIcon.exists()).toBe(true); expect(commitIcon.props('name')).toBe('commit'); @@ -71,7 +70,7 @@ describe('Release block footer', () => { }); it('renders the tag icon', () => { - const commitIcon = tagInfoSection().find(Icon); + const commitIcon = tagInfoSection().find(GlIcon); expect(commitIcon.exists()).toBe(true); expect(commitIcon.props('name')).toBe('tag'); diff --git a/spec/frontend/releases/components/release_block_spec.js b/spec/frontend/releases/components/release_block_spec.js index 19119d99f3c..a7f1388664b 100644 --- a/spec/frontend/releases/components/release_block_spec.js +++ b/spec/frontend/releases/components/release_block_spec.js @@ -1,11 +1,11 @@ import $ from 'jquery'; import { mount } from '@vue/test-utils'; +import { GlIcon } from '@gitlab/ui'; import EvidenceBlock from '~/releases/components/evidence_block.vue'; import ReleaseBlock from '~/releases/components/release_block.vue'; import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; import { release as originalRelease } from '../mock_data'; -import Icon from '~/vue_shared/components/icon.vue'; import * as commonUtils from '~/lib/utils/common_utils'; import { BACK_URL_PARAM } from '~/releases/constants'; import * as urlUtility from '~/lib/utils/url_utility'; @@ -247,7 +247,7 @@ describe('Release block', () => { it('renders the milestone icon', () => { expect( milestoneListLabel() - .find(Icon) + .find(GlIcon) .exists(), ).toBe(true); }); diff --git a/spec/frontend/releases/components/releases_pagination_graphql_spec.js b/spec/frontend/releases/components/releases_pagination_graphql_spec.js new file mode 100644 index 00000000000..b01a28eb6c3 --- /dev/null +++ b/spec/frontend/releases/components/releases_pagination_graphql_spec.js @@ -0,0 +1,175 @@ +import Vuex from 'vuex'; +import { mount, createLocalVue } from '@vue/test-utils'; +import createStore from '~/releases/stores'; +import createListModule from '~/releases/stores/modules/list'; +import ReleasesPaginationGraphql from '~/releases/components/releases_pagination_graphql.vue'; +import { historyPushState } from '~/lib/utils/common_utils'; + +jest.mock('~/lib/utils/common_utils', () => ({ + ...jest.requireActual('~/lib/utils/common_utils'), + historyPushState: jest.fn(), +})); + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('~/releases/components/releases_pagination_graphql.vue', () => { + let wrapper; + let listModule; + + const cursors = { + startCursor: 'startCursor', + endCursor: 'endCursor', + }; + + const projectPath = 'my/project'; + + const createComponent = pageInfo => { + listModule = createListModule({ projectPath }); + + listModule.state.graphQlPageInfo = pageInfo; + + listModule.actions.fetchReleasesGraphQl = jest.fn(); + + wrapper = mount(ReleasesPaginationGraphql, { + store: createStore({ + modules: { + list: listModule, + }, + featureFlags: {}, + }), + localVue, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findPrevButton = () => wrapper.find('[data-testid="prevButton"]'); + const findNextButton = () => wrapper.find('[data-testid="nextButton"]'); + + const expectDisabledPrev = () => { + expect(findPrevButton().attributes().disabled).toBe('disabled'); + }; + const expectEnabledPrev = () => { + expect(findPrevButton().attributes().disabled).toBe(undefined); + }; + const expectDisabledNext = () => { + expect(findNextButton().attributes().disabled).toBe('disabled'); + }; + const expectEnabledNext = () => { + expect(findNextButton().attributes().disabled).toBe(undefined); + }; + + describe('when there is only one page of results', () => { + beforeEach(() => { + createComponent({ + hasPreviousPage: false, + hasNextPage: false, + }); + }); + + it('does not render anything', () => { + expect(wrapper.isEmpty()).toBe(true); + }); + }); + + describe('when there is a next page, but not a previous page', () => { + beforeEach(() => { + createComponent({ + hasPreviousPage: false, + hasNextPage: true, + }); + }); + + it('renders a disabled "Prev" button', () => { + expectDisabledPrev(); + }); + + it('renders an enabled "Next" button', () => { + expectEnabledNext(); + }); + }); + + describe('when there is a previous page, but not a next page', () => { + beforeEach(() => { + createComponent({ + hasPreviousPage: true, + hasNextPage: false, + }); + }); + + it('renders a enabled "Prev" button', () => { + expectEnabledPrev(); + }); + + it('renders an disabled "Next" button', () => { + expectDisabledNext(); + }); + }); + + describe('when there is both a previous page and a next page', () => { + beforeEach(() => { + createComponent({ + hasPreviousPage: true, + hasNextPage: true, + }); + }); + + it('renders a enabled "Prev" button', () => { + expectEnabledPrev(); + }); + + it('renders an enabled "Next" button', () => { + expectEnabledNext(); + }); + }); + + describe('button behavior', () => { + beforeEach(() => { + createComponent({ + hasPreviousPage: true, + hasNextPage: true, + ...cursors, + }); + }); + + describe('next button behavior', () => { + beforeEach(() => { + findNextButton().trigger('click'); + }); + + it('calls fetchReleasesGraphQl with the correct after cursor', () => { + expect(listModule.actions.fetchReleasesGraphQl.mock.calls).toEqual([ + [expect.anything(), { after: cursors.endCursor }], + ]); + }); + + it('calls historyPushState with the new URL', () => { + expect(historyPushState.mock.calls).toEqual([ + [expect.stringContaining(`?after=${cursors.endCursor}`)], + ]); + }); + }); + + describe('previous button behavior', () => { + beforeEach(() => { + findPrevButton().trigger('click'); + }); + + it('calls fetchReleasesGraphQl with the correct before cursor', () => { + expect(listModule.actions.fetchReleasesGraphQl.mock.calls).toEqual([ + [expect.anything(), { before: cursors.startCursor }], + ]); + }); + + it('calls historyPushState with the new URL', () => { + expect(historyPushState.mock.calls).toEqual([ + [expect.stringContaining(`?before=${cursors.startCursor}`)], + ]); + }); + }); + }); +}); diff --git a/spec/frontend/releases/components/releases_pagination_rest_spec.js b/spec/frontend/releases/components/releases_pagination_rest_spec.js new file mode 100644 index 00000000000..4fd3e085fc9 --- /dev/null +++ b/spec/frontend/releases/components/releases_pagination_rest_spec.js @@ -0,0 +1,72 @@ +import Vuex from 'vuex'; +import { mount, createLocalVue } from '@vue/test-utils'; +import { GlPagination } from '@gitlab/ui'; +import ReleasesPaginationRest from '~/releases/components/releases_pagination_rest.vue'; +import createStore from '~/releases/stores'; +import createListModule from '~/releases/stores/modules/list'; +import * as commonUtils from '~/lib/utils/common_utils'; + +commonUtils.historyPushState = jest.fn(); + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('~/releases/components/releases_pagination_rest.vue', () => { + let wrapper; + let listModule; + + const projectId = 19; + + const createComponent = pageInfo => { + listModule = createListModule({ projectId }); + + listModule.state.pageInfo = pageInfo; + + listModule.actions.fetchReleasesRest = jest.fn(); + + wrapper = mount(ReleasesPaginationRest, { + store: createStore({ + modules: { + list: listModule, + }, + featureFlags: {}, + }), + localVue, + }); + }; + + const findGlPagination = () => wrapper.find(GlPagination); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + describe('when a page number is clicked', () => { + const newPage = 2; + + beforeEach(() => { + createComponent({ + perPage: 20, + page: 1, + total: 40, + totalPages: 2, + nextPage: 2, + }); + + findGlPagination().vm.$emit('input', newPage); + }); + + it('calls fetchReleasesRest with the correct page', () => { + expect(listModule.actions.fetchReleasesRest.mock.calls).toEqual([ + [expect.anything(), { page: newPage }], + ]); + }); + + it('calls historyPushState with the new URL', () => { + expect(commonUtils.historyPushState.mock.calls).toEqual([ + [expect.stringContaining(`?page=${newPage}`)], + ]); + }); + }); +}); diff --git a/spec/frontend/releases/components/releases_pagination_spec.js b/spec/frontend/releases/components/releases_pagination_spec.js new file mode 100644 index 00000000000..2466fb53a68 --- /dev/null +++ b/spec/frontend/releases/components/releases_pagination_spec.js @@ -0,0 +1,52 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import Vuex from 'vuex'; +import ReleasesPagination from '~/releases/components/releases_pagination.vue'; +import ReleasesPaginationGraphql from '~/releases/components/releases_pagination_graphql.vue'; +import ReleasesPaginationRest from '~/releases/components/releases_pagination_rest.vue'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('~/releases/components/releases_pagination.vue', () => { + let wrapper; + + const createComponent = useGraphQLEndpoint => { + const store = new Vuex.Store({ + getters: { + useGraphQLEndpoint: () => useGraphQLEndpoint, + }, + }); + + wrapper = shallowMount(ReleasesPagination, { store, localVue }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findRestPagination = () => wrapper.find(ReleasesPaginationRest); + const findGraphQlPagination = () => wrapper.find(ReleasesPaginationGraphql); + + describe('when one of necessary feature flags is disabled', () => { + beforeEach(() => { + createComponent(false); + }); + + it('renders the REST pagination component', () => { + expect(findRestPagination().exists()).toBe(true); + expect(findGraphQlPagination().exists()).toBe(false); + }); + }); + + describe('when all the necessary feature flags are enabled', () => { + beforeEach(() => { + createComponent(true); + }); + + it('renders the GraphQL pagination component', () => { + expect(findGraphQlPagination().exists()).toBe(true); + expect(findRestPagination().exists()).toBe(false); + }); + }); +}); diff --git a/spec/frontend/releases/components/tag_field_exsting_spec.js b/spec/frontend/releases/components/tag_field_exsting_spec.js index 0a04f68bd67..70a195556df 100644 --- a/spec/frontend/releases/components/tag_field_exsting_spec.js +++ b/spec/frontend/releases/components/tag_field_exsting_spec.js @@ -1,5 +1,6 @@ +import Vuex from 'vuex'; import { GlFormInput } from '@gitlab/ui'; -import { shallowMount, mount } from '@vue/test-utils'; +import { shallowMount, mount, createLocalVue } from '@vue/test-utils'; import TagFieldExisting from '~/releases/components/tag_field_existing.vue'; import createStore from '~/releases/stores'; import createDetailModule from '~/releases/stores/modules/detail'; @@ -7,6 +8,9 @@ import createDetailModule from '~/releases/stores/modules/detail'; const TEST_TAG_NAME = 'test-tag-name'; const TEST_DOCS_PATH = '/help/test/docs/path'; +const localVue = createLocalVue(); +localVue.use(Vuex); + describe('releases/components/tag_field_existing', () => { let store; let wrapper; @@ -14,6 +18,7 @@ describe('releases/components/tag_field_existing', () => { const createComponent = (mountFn = shallowMount) => { wrapper = mountFn(TagFieldExisting, { store, + localVue, }); }; |