diff --git a/shell/components/SortableTable/sorting.js b/shell/components/SortableTable/sorting.js index 0791d1c50bd..392a9fab738 100644 --- a/shell/components/SortableTable/sorting.js +++ b/shell/components/SortableTable/sorting.js @@ -79,7 +79,7 @@ export default { if ( markedColumn ) { this._defaultSortBy = markedColumn.name; - descending = markedColumn.defaultSortDescending; + descending = markedColumn.defaultSortDescending || false; } else if ( nameColumn ) { // Use the name column if there is one this._defaultSortBy = nameColumn.name; diff --git a/shell/components/nav/TopLevelMenu.helper.ts b/shell/components/nav/TopLevelMenu.helper.ts index ccab261e475..47bfd63bb2b 100644 --- a/shell/components/nav/TopLevelMenu.helper.ts +++ b/shell/components/nav/TopLevelMenu.helper.ts @@ -28,7 +28,9 @@ interface UpdateArgs { searchTerm: string, pinnedIds: string[], unPinnedMax?: number, - forceWatch?: boolean + forceWatch?: boolean, + mgmtClusterRevision?: string, + provClusterRevision?: string, } type MgmtCluster = { @@ -179,7 +181,7 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme // No need to monitor for changes, the UNPINNED request will handle it this.clustersPinnedWrapper = new PaginationWrapper({ $store, - id: 'tlm-pinned-clusters', + id: 'top-level-menu-pinned-clusters', enabledFor: { store: STORE.MANAGEMENT, resource: { @@ -192,13 +194,19 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme // Fetch all UNPINNED clusters capped at 10 (see `clustersOthers` description for details) this.clustersOthersWrapper = new PaginationWrapper({ $store, - id: 'tlm-unpinned-clusters', - onChange: async({ forceWatch }) => { - if (this.args) { + id: 'top-level-menu-unpinned-clusters', + onChange: async({ forceWatch, revision }) => { + if (!this.args) { + return; + } + try { await this.update({ ...this.args, - forceWatch + forceWatch, + mgmtClusterRevision: revision, }); + } catch { + // Failures should be logged lower down, not much we can do here except catch to prevent whole ui page warnings in dev mode } }, enabledFor: { @@ -213,13 +221,19 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme // Fetch all prov clusters for the mgmt clusters we have this.provClusterWrapper = new PaginationWrapper({ $store, - id: 'tlm-prov-clusters', - onChange: async({ forceWatch }) => { - if (this.args) { + id: 'top-level-menu-prov-clusters', + onChange: async({ forceWatch, revision }) => { + if (!this.args) { + return; + } + try { await this.update({ ...this.args, - forceWatch + forceWatch, + provClusterRevision: revision, }); + } catch { + // Failures should be logged lower down, not much we can do here except catch to prevent whole ui page warnings in dev mode } }, enabledFor: { @@ -251,7 +265,8 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme pinned: MgmtCluster[], notPinned: MgmtCluster[] } = await allHash(promises) as any; - const provClusters = await this.updateProvCluster(res.notPinned, res.pinned, args.forceWatch || false); + + const provClusters = await this.updateProvCluster(res.notPinned, res.pinned, args); const provClustersByMgmtId = provClusters.reduce((res: { [mgmtId: string]: ProvCluster}, provCluster: ProvCluster) => { if (provCluster.mgmtClusterId) { res[provCluster.mgmtClusterId] = provCluster; @@ -357,7 +372,9 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme sort: DEFAULT_SORT, projectsOrNamespaces: [] }, - }).then((r) => r.data); + revision: args.mgmtClusterRevision + }) + .then((r) => r.data); } /** @@ -378,15 +395,17 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme sort: DEFAULT_SORT, projectsOrNamespaces: [] }, - }).then((r) => r.data); + revision: args.mgmtClusterRevision + }) + .then((r) => r.data); } /** * Find all provisioning clusters associated with the displayed mgmt clusters */ - private async updateProvCluster(notPinned: MgmtCluster[], pinned: MgmtCluster[], forceWatch: boolean): Promise { + private async updateProvCluster(notPinned: MgmtCluster[], pinned: MgmtCluster[], args: UpdateArgs): Promise { return this.provClusterWrapper.request({ - forceWatch, + forceWatch: args.forceWatch, pagination: { filters: [ PaginationParamFilter.createMultipleFields( @@ -400,7 +419,9 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme sort: [], projectsOrNamespaces: [] }, - }).then((r) => r.data); + revision: args.provClusterRevision + }) + .then((r) => r.data); } } @@ -585,6 +606,8 @@ export class TopLevelMenuHelperLegacy extends BaseTopLevelMenuHelper implements */ class TopLevelMenuHelperService { private _helper?: TopLevelMenuHelper; + public initialized = false; + public init($store: VuexStore) { if (this._helper) { return; @@ -599,6 +622,8 @@ class TopLevelMenuHelperService { }); this._helper = canPagination ? new TopLevelMenuHelperPagination({ $store }) : new TopLevelMenuHelperLegacy({ $store }); + + this.initialized = true; } public async reset() { diff --git a/shell/components/nav/TopLevelMenu.vue b/shell/components/nav/TopLevelMenu.vue index 00587e0e838..c1ce2523aa3 100644 --- a/shell/components/nav/TopLevelMenu.vue +++ b/shell/components/nav/TopLevelMenu.vue @@ -27,6 +27,9 @@ export default { }, data() { + const sideNavServiceInitialized = sideNavService.initialized; + const maxClustersToShow = MENU_MAX_CLUSTERS; + sideNavService.init(this.$store); const { displayVersion, fullVersion } = getVersionInfo(this.$store); @@ -43,26 +46,27 @@ export default { const provClusters = !canPagination && hasProvCluster ? this.$store.getters[`management/all`](CAPI.RANCHER_CLUSTER) : []; const mgmtClusters = !canPagination ? this.$store.getters[`management/all`](MANAGEMENT.CLUSTER) : []; - if (!canPagination) { - // Reduce the impact of the initial load, but only if we're not making a request + if (!canPagination || !sideNavServiceInitialized) { + // Reduce the impact of the initial load, or properly initialised + // Doing this here means we don't need an 'immediate' on the watches below const args = { - pinnedIds: this.pinnedIds, - searchTerm: this.search, - unPinnedMax: this.maxClustersToShow + pinnedIds: this.$store.getters['prefs/get'](PINNED_CLUSTERS), + searchTerm: '', + unPinnedMax: maxClustersToShow }; helper.update(args); } return { - shown: false, + shown: false, displayVersion, fullVersion, - clusterFilter: '', + clusterFilter: '', hasProvCluster, - maxClustersToShow: MENU_MAX_CLUSTERS, - emptyCluster: BLANK_CLUSTER, - routeCombo: false, + maxClustersToShow, + emptyCluster: BLANK_CLUSTER, + routeCombo: false, canPagination, helper, @@ -284,7 +288,6 @@ export default { // 2. When SSP is disabled (legacy) reduce fn churn (this was a known performance customer issue) pinnedIds: { - immediate: true, handler(neu, old) { if (sameContents(neu, old)) { return; @@ -302,20 +305,28 @@ export default { provClusters: { handler(neu, old) { + if (this.canPagination) { + // Shouldn't be doing this at all if pagination is on (updates handled by TopLevelMenu pagination wrapper) + return; + } + // Potentially incredibly high throughput. Changes should be at least limited (slow if state change, quick if added/removed). Shouldn't get here if SSP this.updateClusters(this.pinnedIds, neu?.length === old?.length ? 'slow' : 'quick'); }, - deep: true, - immediate: true, + deep: true, }, mgmtClusters: { handler(neu, old) { + if (this.canPagination) { + // Shouldn't be doing this at all if pagination is on (updates handled by TopLevelMenu pagination wrapper) + return; + } + // Potentially incredibly high throughput. Changes should be at least limited (slow if state change, quick if added/removed). Shouldn't get here if SSP this.updateClusters(this.pinnedIds, neu?.length === old?.length ? 'slow' : 'quick'); }, - deep: true, - immediate: true, + deep: true, }, hideLocalCluster() { @@ -454,16 +465,25 @@ export default { unPinnedMax: this.maxClustersToShow }; - switch (speed) { - case 'slow': - this.debouncedHelperUpdateSlow(args); - break; - case 'medium': - this.debouncedHelperUpdateMedium(args); - break; - case 'quick': - this.debouncedHelperUpdateQuick(args); - break; + try { + switch (speed) { + case 'slow': + this.debouncedHelperUpdateSlow(args); + break; + case 'medium': + this.debouncedHelperUpdateMedium(args); + break; + case 'quick': + this.debouncedHelperUpdateQuick(args); + break; + } + } catch (err) { + if (this.canPagination) { + // Double bubble up errors here, errors are tracked further down + // Note that this won't pick up async errors, further tweaks are required for that + } else { + throw err; + } } } } diff --git a/shell/components/nav/__tests__/TopLevelMenu.helper.test.ts b/shell/components/nav/__tests__/TopLevelMenu.helper.test.ts new file mode 100644 index 00000000000..766e9833579 --- /dev/null +++ b/shell/components/nav/__tests__/TopLevelMenu.helper.test.ts @@ -0,0 +1,277 @@ +import TopLevelMenuHelperService, { TopLevelMenuHelperLegacy, TopLevelMenuHelperPagination } from '../TopLevelMenu.helper'; +import { CAPI, MANAGEMENT } from '@shell/config/types'; +import PaginationWrapper from '@shell/utils/pagination-wrapper'; + +// Mock dependencies +jest.mock('@shell/utils/pagination-wrapper'); +jest.mock('@shell/utils/cluster', () => ({ + filterHiddenLocalCluster: jest.fn((clusters) => clusters), + filterOnlyKubernetesClusters: jest.fn((clusters) => clusters), + paginationFilterClusters: jest.fn(() => []), +})); + +describe('topLevelMenu.helper', () => { + let mockStore: any; + + beforeEach(() => { + mockStore = { + getters: { + 'management/schemaFor': jest.fn(), + 'management/all': jest.fn(), + 'management/paginationEnabled': jest.fn(), + }, + dispatch: jest.fn(), + }; + + jest.clearAllMocks(); + (PaginationWrapper as unknown as jest.Mock).mockImplementation(() => ({ + request: jest.fn().mockResolvedValue({ data: [] }), + onDestroy: jest.fn(), + })); + }); + + describe('class: TopLevelMenuHelperLegacy', () => { + it('should dispatch findAll for CAPI.RANCHER_CLUSTER on init if schema exists', () => { + mockStore.getters['management/schemaFor'].mockReturnValue(true); + new TopLevelMenuHelperLegacy({ $store: mockStore }); + expect(mockStore.dispatch).toHaveBeenCalledWith('management/findAll', { type: CAPI.RANCHER_CLUSTER }); + }); + + it('should not dispatch findAll if schema does not exist', () => { + mockStore.getters['management/schemaFor'].mockReturnValue(false); + new TopLevelMenuHelperLegacy({ $store: mockStore }); + expect(mockStore.dispatch).not.toHaveBeenCalled(); + }); + + it('should filter and sort clusters correctly in update', async() => { + mockStore.getters['management/schemaFor'].mockReturnValue(true); + const mgmtClusters = [ + { + id: 'c1', nameDisplay: 'Cluster 1', isReady: true, pinned: false, pin: jest.fn(), unpin: jest.fn() + }, + { + id: 'c2', nameDisplay: 'Cluster 2', isReady: true, pinned: true, pin: jest.fn(), unpin: jest.fn() + }, + { + id: 'local', nameDisplay: 'Local', isReady: true, pinned: true, isLocal: true, pin: jest.fn(), unpin: jest.fn() + }, + ]; + const provClusters = [ + { mgmt: { id: 'c1' } }, + { mgmt: { id: 'c2' } }, + { mgmt: { id: 'local' } }, + ]; + + mockStore.getters['management/all'].mockImplementation((type: string) => { + if (type === MANAGEMENT.CLUSTER) { + return mgmtClusters; + } + if (type === CAPI.RANCHER_CLUSTER) { + return provClusters; + } + + return []; + }); + + const helper = new TopLevelMenuHelperLegacy({ $store: mockStore }); + + // Test with no search, expecting pinned clusters (local + c2) + await helper.update({ + searchTerm: '', + pinnedIds: [], + }); + + expect(helper.clustersPinned).toHaveLength(2); + expect(helper.clustersPinned[0].id).toBe('local'); + expect(helper.clustersPinned[1].id).toBe('c2'); + expect(helper.clustersOthers).toHaveLength(1); + expect(helper.clustersOthers[0].id).toBe('c1'); + + // Test with search + await helper.update({ + searchTerm: 'Cluster 1', + pinnedIds: [], + }); + + expect(helper.clustersOthers).toHaveLength(1); + expect(helper.clustersOthers[0].id).toBe('c1'); + }); + }); + + describe('class: TopLevelMenuHelperPagination', () => { + it('should initialize PaginationWrappers', () => { + mockStore.getters['management/schemaFor'].mockReturnValue(true); + new TopLevelMenuHelperPagination({ $store: mockStore }); + expect(PaginationWrapper).toHaveBeenCalledTimes(3); + }); + + it('should update clusters correctly', async() => { + mockStore.getters['management/schemaFor'].mockReturnValue(true); + const mgmtPinned = [{ + id: 'c1', nameDisplay: 'Pinned', isReady: true, pinned: true, pin: jest.fn(), unpin: jest.fn() + }]; + const mgmtOthers = [{ + id: 'c2', nameDisplay: 'Other', isReady: true, pinned: false, pin: jest.fn(), unpin: jest.fn() + }]; + const provClusters = [ + { mgmtClusterId: 'c1' }, + { mgmtClusterId: 'c2' } + ]; + + const mockRequestPinned = jest.fn().mockResolvedValue({ data: mgmtPinned }); + const mockRequestOthers = jest.fn().mockResolvedValue({ data: mgmtOthers }); + const mockRequestProv = jest.fn().mockResolvedValue({ data: provClusters }); + + (PaginationWrapper as unknown as jest.Mock) + .mockImplementationOnce(() => ({ request: mockRequestPinned, onDestroy: jest.fn() })) + .mockImplementationOnce(() => ({ request: mockRequestOthers, onDestroy: jest.fn() })) + .mockImplementationOnce(() => ({ request: mockRequestProv, onDestroy: jest.fn() })); + + const helper = new TopLevelMenuHelperPagination({ $store: mockStore }); + + const input = { + searchTerm: '', + pinnedIds: ['c1'], + }; + + await helper.update(input); + + expect(mockRequestPinned).toHaveBeenCalledWith({ + forceWatch: undefined, + pagination: { + filters: [{ + equals: true, + fields: [{ + equals: true, exact: true, field: 'id', value: input.pinnedIds[0] + }], + param: 'filter' + }], + page: 1, + projectsOrNamespaces: [], + sort: [{ asc: false, field: 'spec.internal' }, { asc: false, field: 'status.connected' }, { asc: true, field: 'spec.displayName' }] + }, + revision: undefined + }); + expect(mockRequestOthers).toHaveBeenCalledWith({ + forceWatch: undefined, + pagination: { + filters: [{ + equals: true, + fields: [{ + equality: '!=', equals: false, exact: true, exists: false, field: 'id', value: input.pinnedIds[0] + }], + param: 'filter' + }], + page: 1, + pageSize: undefined, + projectsOrNamespaces: [], + sort: [{ asc: false, field: 'spec.internal' }, { asc: false, field: 'status.connected' }, { asc: true, field: 'spec.displayName' }] + }, + revision: undefined + }); + expect(mockRequestProv).toHaveBeenCalledWith({ + forceWatch: undefined, + pagination: { + filters: [{ + equals: true, + fields: [{ + equals: true, exact: true, field: 'status.clusterName', value: mgmtOthers[0].id + }, { + equals: true, exact: true, field: 'status.clusterName', value: mgmtPinned[0].id + }], + param: 'filter' + }], + page: 1, + projectsOrNamespaces: [], + sort: [] + }, + revision: undefined + }); + + expect(helper.clustersPinned).toHaveLength(1); + expect(helper.clustersPinned[0].id).toBe('c1'); + expect(helper.clustersOthers).toHaveLength(1); + expect(helper.clustersOthers[0].id).toBe('c2'); + }); + + it('should filter out mgmt clusters without matching prov clusters', async() => { + mockStore.getters['management/schemaFor'].mockReturnValue(true); + const mgmtOthers = [{ + id: 'c2', nameDisplay: 'Other', isReady: true, pinned: false, pin: jest.fn(), unpin: jest.fn() + }]; + // No prov cluster for c2 + const provClusters: any[] = []; + + const mockRequestPinned = jest.fn().mockResolvedValue({ data: [] }); + const mockRequestOthers = jest.fn().mockResolvedValue({ data: mgmtOthers }); + const mockRequestProv = jest.fn().mockResolvedValue({ data: provClusters }); + + (PaginationWrapper as unknown as jest.Mock) + .mockImplementationOnce(() => ({ request: mockRequestPinned, onDestroy: jest.fn() })) + .mockImplementationOnce(() => ({ request: mockRequestOthers, onDestroy: jest.fn() })) + .mockImplementationOnce(() => ({ request: mockRequestProv, onDestroy: jest.fn() })); + + const helper = new TopLevelMenuHelperPagination({ $store: mockStore }); + + await helper.update({ + searchTerm: '', + pinnedIds: [], + }); + + expect(helper.clustersOthers).toHaveLength(0); + }); + }); + + describe('class: TopLevelMenuHelperService', () => { + beforeEach(async() => { + await TopLevelMenuHelperService.reset(); + }); + + it('should throw error if helper is accessed before init', () => { + expect(() => TopLevelMenuHelperService.helper).toThrow('Unable to use the side nav cluster helper (not initialised)'); + }); + + it('should initialize with Legacy helper when pagination is disabled', () => { + mockStore.getters['management/paginationEnabled'].mockReturnValue(false); + + TopLevelMenuHelperService.init(mockStore); + + expect(TopLevelMenuHelperService.helper).toBeInstanceOf(TopLevelMenuHelperLegacy); + }); + + it('should initialize with Pagination helper when pagination is enabled', () => { + mockStore.getters['management/paginationEnabled'].mockReturnValue(true); + + TopLevelMenuHelperService.init(mockStore); + + expect(TopLevelMenuHelperService.helper).toBeInstanceOf(TopLevelMenuHelperPagination); + }); + + it('should not re-initialize if already initialized', () => { + mockStore.getters['management/paginationEnabled'].mockReturnValue(false); + TopLevelMenuHelperService.init(mockStore); + const helper1 = TopLevelMenuHelperService.helper; + + // Change condition + mockStore.getters['management/paginationEnabled'].mockReturnValue(true); + TopLevelMenuHelperService.init(mockStore); + const helper2 = TopLevelMenuHelperService.helper; + + expect(helper1).toBe(helper2); + expect(helper2).toBeInstanceOf(TopLevelMenuHelperLegacy); + }); + + it('should reset correctly', async() => { + mockStore.getters['management/paginationEnabled'].mockReturnValue(false); + TopLevelMenuHelperService.init(mockStore); + + const helper = TopLevelMenuHelperService.helper; + const destroySpy = jest.spyOn(helper, 'destroy'); + + await TopLevelMenuHelperService.reset(); + + expect(destroySpy).toHaveBeenCalledWith(); + expect(() => TopLevelMenuHelperService.helper).toThrow('Unable to use the side nav cluster helper (not initialised)'); + }); + }); +}); diff --git a/shell/components/nav/__tests__/TopLevelMenu.test.ts b/shell/components/nav/__tests__/TopLevelMenu.test.ts index d78ef746fae..d414ab71159 100644 --- a/shell/components/nav/__tests__/TopLevelMenu.test.ts +++ b/shell/components/nav/__tests__/TopLevelMenu.test.ts @@ -5,6 +5,15 @@ import { PINNED_CLUSTERS } from '@shell/store/prefs'; import { nextTick } from 'vue'; import sideNavService from '@shell/components/nav/TopLevelMenu.helper'; +jest.mock('@shell/utils/pagination-wrapper', () => { + return jest.fn().mockImplementation(() => { + return { + request: jest.fn().mockResolvedValue({ data: [] }), + onDestroy: jest.fn(), + }; + }); +}); + /** * `clusters` doubles up as both mgmt and prov clusters (don't shoot the messenger) */ @@ -55,11 +64,14 @@ describe('topLevelMenu', () => { beforeEach(() => { jest.useFakeTimers(); sideNavService.reset(); + sideNavService.initialized = false; + jest.restoreAllMocks(); }); afterEach(() => { jest.runOnlyPendingTimers(); jest.useRealTimers(); + jest.restoreAllMocks(); }); it('should display clusters', async() => { @@ -434,8 +446,6 @@ describe('topLevelMenu', () => { describe('should displays a no results message if have clusters but', () => { it('given no matching clusters', async() => { const wrapper: Wrapper> = mount(TopLevelMenu, { - data: () => ({ clusterFilter: 'whatever' }), - global: { mocks: { $route: {}, @@ -454,6 +464,8 @@ describe('topLevelMenu', () => { }, }); + await wrapper.setData({ clusterFilter: 'whatever' }); + await waitForIt(); const noResults = wrapper.find('[data-testid="top-level-menu-no-results"]'); @@ -527,8 +539,6 @@ describe('topLevelMenu', () => { it('given clusters with status pinned', async() => { const search = 'you found me'; const wrapper: Wrapper> = mount(TopLevelMenu, { - data: () => ({ clusterFilter: search }), - global: { mocks: { $route: {}, @@ -548,6 +558,8 @@ describe('topLevelMenu', () => { }, }); + await wrapper.setData({ clusterFilter: search }); + await waitForIt(); const noResults = wrapper.find('[data-testid="top-level-menu-no-results"]'); @@ -557,4 +569,148 @@ describe('topLevelMenu', () => { }); }); }); + + describe('initialization', () => { + it('should initialize sideNavService', () => { + const spyInit = jest.spyOn(sideNavService, 'init'); + + mount(TopLevelMenu, { + global: { + mocks: { + $route: {}, + $store: { ...generateStore([]) }, + }, + stubs: ['BrandImage', 'router-link'], + }, + }); + + expect(spyInit).toHaveBeenCalled(); // eslint-disable-line jest/prefer-called-with + }); + + it('should call helper.update if pagination is disabled', () => { + const store = generateStore([]); + + store.getters['management/paginationEnabled'] = () => false; + + jest.spyOn(sideNavService, 'init').mockImplementation(() => {}); + const updateSpy = jest.fn(); + const mockHelper = { + update: updateSpy, clustersPinned: [], clustersOthers: [] + }; + + jest.spyOn(sideNavService, 'helper', 'get').mockReturnValue(mockHelper as any); + + mount(TopLevelMenu, { + global: { + mocks: { + $route: {}, + $store: store, + }, + stubs: ['BrandImage', 'router-link'], + }, + }); + + expect(updateSpy).toHaveBeenCalledWith({ + pinnedIds: [], searchTerm: '', unPinnedMax: 10 + }); + }); + + it('should call helper.update if pagination is enabled but service not initialized', () => { + const store = generateStore([]); + + store.getters['management/paginationEnabled'] = () => true; + sideNavService.initialized = false; + + jest.spyOn(sideNavService, 'init').mockImplementation(() => {}); + const updateSpy = jest.fn(); + const mockHelper = { + update: updateSpy, clustersPinned: [], clustersOthers: [] + }; + + jest.spyOn(sideNavService, 'helper', 'get').mockReturnValue(mockHelper as any); + + mount(TopLevelMenu, { + global: { + mocks: { + $route: {}, + $store: store, + }, + stubs: ['BrandImage', 'router-link'], + }, + }); + + expect(updateSpy).toHaveBeenCalledWith({ + pinnedIds: [], searchTerm: '', unPinnedMax: 10 + }); + }); + + it('should NOT call helper.update if pagination is enabled and service initialized', () => { + const store = generateStore([]); + + store.getters['management/paginationEnabled'] = () => true; + sideNavService.initialized = true; + + jest.spyOn(sideNavService, 'init').mockImplementation(() => {}); + const updateSpy = jest.fn(); + const mockHelper = { + update: updateSpy, clustersPinned: [], clustersOthers: [] + }; + + jest.spyOn(sideNavService, 'helper', 'get').mockReturnValue(mockHelper as any); + + mount(TopLevelMenu, { + global: { + mocks: { + $route: {}, + $store: store, + }, + stubs: ['BrandImage', 'router-link'], + }, + }); + + expect(updateSpy).not.toHaveBeenCalled(); + }); + + it('should populate clusters from store if pagination is disabled', () => { + const clusters = [{ id: 'c1' }]; + const store = generateStore(clusters); + + store.getters['management/paginationEnabled'] = () => false; + store.getters['management/schemaFor'] = () => true; + + const wrapper = mount(TopLevelMenu, { + global: { + mocks: { + $route: {}, + $store: store, + }, + stubs: ['BrandImage', 'router-link'], + }, + }); + + expect(wrapper.vm.provClusters).toStrictEqual(clusters); + expect(wrapper.vm.mgmtClusters).toStrictEqual(clusters); + }); + + it('should NOT populate clusters from store if pagination is enabled', () => { + const clusters = [{ id: 'c1' }]; + const store = generateStore(clusters); + + store.getters['management/paginationEnabled'] = () => true; + store.getters['management/schemaFor'] = () => true; + + const wrapper = mount(TopLevelMenu, { + global: { + mocks: { + $route: {}, + $store: store, + }, + stubs: ['BrandImage', 'router-link'], + }, + }); + + expect(wrapper.vm.provClusters).toStrictEqual([]); + expect(wrapper.vm.mgmtClusters).toStrictEqual([]); + }); + }); }); diff --git a/shell/list/catalog.cattle.io.clusterrepo.vue b/shell/list/catalog.cattle.io.clusterrepo.vue index 7dcd86aeb8c..9ae3b297c7d 100644 --- a/shell/list/catalog.cattle.io.clusterrepo.vue +++ b/shell/list/catalog.cattle.io.clusterrepo.vue @@ -1,7 +1,7 @@