Skip to content

Commit 68d40f7

Browse files
[16714] Changes to allow the cards to be clickable
1 parent 81a17c9 commit 68d40f7

4 files changed

Lines changed: 269 additions & 0 deletions

File tree

shell/components/CountBox.vue

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
export default {
44
name: 'CountBox',
55
6+
emits: ['click'],
7+
68
props: {
79
name: {
810
type: String,
@@ -19,6 +21,10 @@ export default {
1921
compact: {
2022
type: Boolean,
2123
default: false
24+
},
25+
clickable: {
26+
type: Boolean,
27+
default: false
2228
}
2329
},
2430
computed: {
@@ -34,6 +40,12 @@ export default {
3440
methods: {
3541
customizePrimaryColorOpacity(opacity) {
3642
return `rgba(var(${ this.primaryColorVar }), ${ opacity })`;
43+
},
44+
45+
handleClick() {
46+
if (this.clickable) {
47+
this.$emit('click');
48+
}
3749
}
3850
}
3951
};
@@ -42,7 +54,9 @@ export default {
4254
<template>
4355
<div
4456
class="count-container"
57+
:class="{ 'clickable': clickable }"
4558
:style="sideStyle"
59+
@click="handleClick"
4660
>
4761
<div
4862
class="count"
@@ -61,6 +75,12 @@ export default {
6175
</template>
6276

6377
<style lang="scss" scoped>
78+
.count-container {
79+
&.clickable {
80+
cursor: pointer;
81+
}
82+
}
83+
6484
.count {
6585
$padding: 10px;
6686
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { shallowMount } from '@vue/test-utils';
2+
import CountBox from '@shell/components/CountBox.vue';
3+
4+
describe('component: CountBox', () => {
5+
const defaultProps = {
6+
name: 'Test',
7+
count: 5,
8+
primaryColorVar: '--sizzle-1',
9+
};
10+
11+
describe('when clickable is false', () => {
12+
it('should render as a div', () => {
13+
const wrapper = shallowMount(CountBox, { props: defaultProps });
14+
15+
expect(wrapper.element.tagName).toBe('DIV');
16+
});
17+
18+
it('should not have the clickable class', () => {
19+
const wrapper = shallowMount(CountBox, { props: defaultProps });
20+
21+
expect(wrapper.classes()).not.toContain('clickable');
22+
});
23+
24+
it('should not emit click event when clicked', async() => {
25+
const wrapper = shallowMount(CountBox, { props: defaultProps });
26+
27+
await wrapper.trigger('click');
28+
29+
expect(wrapper.emitted('click')).toBeUndefined();
30+
});
31+
});
32+
33+
describe('when clickable is true', () => {
34+
it('should have the clickable class', () => {
35+
const wrapper = shallowMount(CountBox, {
36+
props: {
37+
...defaultProps,
38+
clickable: true,
39+
},
40+
});
41+
42+
expect(wrapper.classes()).toContain('clickable');
43+
});
44+
45+
it('should emit click event when clicked', async() => {
46+
const wrapper = shallowMount(CountBox, {
47+
props: {
48+
...defaultProps,
49+
clickable: true,
50+
},
51+
});
52+
53+
await wrapper.trigger('click');
54+
55+
expect(wrapper.emitted('click')).toHaveLength(1);
56+
});
57+
});
58+
59+
describe('display', () => {
60+
it('should display the count', () => {
61+
const wrapper = shallowMount(CountBox, { props: defaultProps });
62+
63+
expect(wrapper.find('h1').text()).toBe('5');
64+
});
65+
66+
it('should display the name', () => {
67+
const wrapper = shallowMount(CountBox, { props: defaultProps });
68+
69+
expect(wrapper.find('label').text()).toBe('Test');
70+
});
71+
});
72+
});
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { shallowMount } from '@vue/test-utils';
2+
import DetailWorkspace from '@shell/detail/management.cattle.io.fleetworkspace.vue';
3+
import { FLEET } from '@shell/config/types';
4+
import { NAME as FLEET_NAME } from '@shell/config/product/fleet';
5+
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
6+
7+
describe('component: DetailWorkspace', () => {
8+
const mockValue = {
9+
id: 'fleet-default',
10+
counts: {
11+
gitRepos: 3,
12+
helmOps: 2,
13+
clusters: 5,
14+
cluster: 5,
15+
clusterGroup: 1,
16+
clusterGroups: 1,
17+
},
18+
};
19+
20+
const mockRouter = { push: jest.fn() };
21+
22+
const defaultStore = {
23+
commit: jest.fn(),
24+
dispatch: jest.fn(),
25+
getters: {
26+
'i18n/t': (key: string) => key,
27+
'i18n/exists': () => true,
28+
currentProduct: { name: FLEET_NAME },
29+
},
30+
};
31+
32+
const createWrapper = (props = {}) => {
33+
return shallowMount(DetailWorkspace, {
34+
props: { value: mockValue, ...props },
35+
global: {
36+
mocks: {
37+
$store: defaultStore,
38+
$route: { params: {} },
39+
$router: mockRouter,
40+
},
41+
stubs: {
42+
CountBox: { template: '<div />', props: ['clickable', 'count', 'name', 'primaryColorVar'] },
43+
ResourceTabs: { template: '<div />' },
44+
},
45+
},
46+
});
47+
};
48+
49+
beforeEach(() => {
50+
jest.clearAllMocks();
51+
});
52+
53+
describe('applicationRoute', () => {
54+
it('should return the fleet application route', () => {
55+
const wrapper = createWrapper();
56+
57+
expect(wrapper.vm.applicationRoute).toStrictEqual({
58+
name: 'c-cluster-fleet-application',
59+
params: { cluster: BLANK_CLUSTER },
60+
});
61+
});
62+
});
63+
64+
describe('clustersRoute', () => {
65+
it('should return the fleet clusters list route', () => {
66+
const wrapper = createWrapper();
67+
68+
expect(wrapper.vm.clustersRoute).toStrictEqual({
69+
name: 'c-cluster-product-resource',
70+
params: {
71+
cluster: BLANK_CLUSTER,
72+
product: FLEET_NAME,
73+
resource: FLEET.CLUSTER,
74+
},
75+
});
76+
});
77+
});
78+
79+
describe('clusterGroupsRoute', () => {
80+
it('should return the fleet cluster groups list route', () => {
81+
const wrapper = createWrapper();
82+
83+
expect(wrapper.vm.clusterGroupsRoute).toStrictEqual({
84+
name: 'c-cluster-product-resource',
85+
params: {
86+
cluster: BLANK_CLUSTER,
87+
product: FLEET_NAME,
88+
resource: FLEET.CLUSTER_GROUP,
89+
},
90+
});
91+
});
92+
});
93+
94+
describe('setWorkspaceAndNavigate', () => {
95+
it('should commit updateWorkspace with the workspace id', () => {
96+
const wrapper = createWrapper();
97+
const route = wrapper.vm.applicationRoute;
98+
99+
wrapper.vm.setWorkspaceAndNavigate(route);
100+
101+
expect(defaultStore.commit).toHaveBeenCalledWith('updateWorkspace', {
102+
value: 'fleet-default',
103+
getters: defaultStore.getters,
104+
});
105+
});
106+
107+
it('should dispatch prefs/set with the workspace id', () => {
108+
const wrapper = createWrapper();
109+
const route = wrapper.vm.applicationRoute;
110+
111+
wrapper.vm.setWorkspaceAndNavigate(route);
112+
113+
expect(defaultStore.dispatch).toHaveBeenCalledWith('prefs/set', {
114+
key: expect.any(String),
115+
value: 'fleet-default',
116+
});
117+
});
118+
119+
it('should navigate to the given route', () => {
120+
const wrapper = createWrapper();
121+
const route = wrapper.vm.clustersRoute;
122+
123+
wrapper.vm.setWorkspaceAndNavigate(route);
124+
125+
expect(mockRouter.push).toHaveBeenCalledWith(route);
126+
});
127+
});
128+
});

shell/detail/management.cattle.io.fleetworkspace.vue

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import ResourceTabs from '@shell/components/form/ResourceTabs';
44
import { SCOPE_NAMESPACE, SCOPE_CLUSTER } from '@shell/components/RoleBindings.vue';
55
import { NAME as FLEET_NAME } from '@shell/config/product/fleet';
66
import { FLEET } from '@shell/config/types';
7+
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
8+
import { WORKSPACE } from '@shell/store/prefs';
79
810
export default {
911
name: 'DetailWorkspace',
@@ -34,6 +36,35 @@ export default {
3436
return this.t(`typeLabel."${ FLEET.HELM_OP }"`, { count: this.value.counts.helmOps });
3537
},
3638
39+
applicationRoute() {
40+
return {
41+
name: 'c-cluster-fleet-application',
42+
params: { cluster: BLANK_CLUSTER }
43+
};
44+
},
45+
46+
clustersRoute() {
47+
return {
48+
name: 'c-cluster-product-resource',
49+
params: {
50+
cluster: BLANK_CLUSTER,
51+
product: FLEET_NAME,
52+
resource: FLEET.CLUSTER,
53+
}
54+
};
55+
},
56+
57+
clusterGroupsRoute() {
58+
return {
59+
name: 'c-cluster-product-resource',
60+
params: {
61+
cluster: BLANK_CLUSTER,
62+
product: FLEET_NAME,
63+
resource: FLEET.CLUSTER_GROUP,
64+
}
65+
};
66+
},
67+
3768
SCOPE_NAMESPACE() {
3869
return SCOPE_NAMESPACE;
3970
},
@@ -46,6 +77,16 @@ export default {
4677
return FLEET_NAME;
4778
}
4879
},
80+
81+
methods: {
82+
setWorkspaceAndNavigate(route) {
83+
const workspaceId = this.value.id;
84+
85+
this.$store.commit('updateWorkspace', { value: workspaceId, getters: this.$store.getters });
86+
this.$store.dispatch('prefs/set', { key: WORKSPACE, value: workspaceId });
87+
this.$router.push(route);
88+
}
89+
}
4990
};
5091
</script>
5192

@@ -58,27 +99,35 @@ export default {
5899
:count="value.counts.gitRepos"
59100
:name="gitRepoLabel"
60101
:primary-color-var="'--sizzle-3'"
102+
:clickable="true"
103+
@click="setWorkspaceAndNavigate(applicationRoute)"
61104
/>
62105
</div>
63106
<div class="col span-3">
64107
<CountBox
65108
:count="value.counts.helmOps"
66109
:name="helmOpsLabel"
67110
:primary-color-var="'--sizzle-3'"
111+
:clickable="true"
112+
@click="setWorkspaceAndNavigate(applicationRoute)"
68113
/>
69114
</div>
70115
<div class="col span-3">
71116
<CountBox
72117
:count="value.counts.clusters"
73118
:name="clustersLabel"
74119
:primary-color-var="'--sizzle-1'"
120+
:clickable="true"
121+
@click="setWorkspaceAndNavigate(clustersRoute)"
75122
/>
76123
</div>
77124
<div class="col span-3">
78125
<CountBox
79126
:count="value.counts.clusterGroups"
80127
:name="clusterGroupsLabel"
81128
:primary-color-var="'--sizzle-2'"
129+
:clickable="true"
130+
@click="setWorkspaceAndNavigate(clusterGroupsRoute)"
82131
/>
83132
</div>
84133
</div>

0 commit comments

Comments
 (0)