@rancher/shell 3.0.9 → 3.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/styles/base/_color.scss +4 -0
- package/assets/styles/themes/_light.scss +6 -6
- package/assets/styles/themes/_modern.scss +14 -6
- package/assets/translations/en-us.yaml +2 -5
- package/components/CopyToClipboard.vue +28 -0
- package/components/CopyToClipboardText.vue +4 -0
- package/components/CruResource.vue +1 -0
- package/components/GlobalRoleBindings.vue +1 -5
- package/components/ResourceDetail/index.vue +0 -21
- package/components/__tests__/CruResource.test.ts +35 -1
- package/composables/useIsNewDetailPageEnabled.test.ts +98 -0
- package/composables/useIsNewDetailPageEnabled.ts +12 -0
- package/config/product/explorer.js +11 -1
- package/config/table-headers.js +0 -9
- package/config/types.js +0 -1
- package/edit/auth/github-app-steps.vue +2 -0
- package/edit/auth/github-steps.vue +2 -0
- package/edit/management.cattle.io.user.vue +60 -35
- package/edit/token.vue +29 -68
- package/models/token.js +0 -4
- package/package.json +8 -8
- package/pages/account/index.vue +67 -96
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +66 -9
- package/pages/c/_cluster/explorer/index.vue +2 -19
- package/pkg/auto-import.js +41 -0
- package/plugins/dashboard-store/resource-class.js +2 -2
- package/plugins/steve/__tests__/steve-class.test.ts +1 -1
- package/plugins/steve/steve-class.js +3 -3
- package/plugins/steve/steve-pagination-utils.ts +2 -4
- package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +7 -7
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +5 -2
- package/rancher-components/RcIcon/types.ts +2 -2
- package/rancher-components/RcSection/RcSection.test.ts +323 -0
- package/rancher-components/RcSection/RcSection.vue +252 -0
- package/rancher-components/RcSection/RcSectionActions.test.ts +212 -0
- package/rancher-components/RcSection/RcSectionActions.vue +85 -0
- package/rancher-components/RcSection/RcSectionBadges.test.ts +149 -0
- package/rancher-components/RcSection/RcSectionBadges.vue +29 -0
- package/rancher-components/RcSection/index.ts +12 -0
- package/rancher-components/RcSection/types.ts +86 -0
- package/scripts/test-plugins-build.sh +5 -4
- package/types/shell/index.d.ts +92 -108
- package/utils/style.ts +17 -0
- package/utils/units.js +14 -5
- package/models/ext.cattle.io.token.js +0 -48
package/pkg/auto-import.js
CHANGED
|
@@ -11,6 +11,43 @@ function replaceAll(str, find, replace) {
|
|
|
11
11
|
return str.split(find).join(replace);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
// Injected at the top of every generated importTypes() function.
|
|
15
|
+
// Ensures both $extension (newer Rancher) and $plugin (older Rancher) are available on
|
|
16
|
+
// Vue globalProperties, regardless of which one the host injected. This makes all
|
|
17
|
+
// extensions compatible across Rancher versions without any per-extension code changes.
|
|
18
|
+
const COMPAT_SHIM = ` if (typeof document !== 'undefined') {
|
|
19
|
+
var patchGlobalProps = function() {
|
|
20
|
+
var __vueApp = document.getElementById('app').__vue_app__;
|
|
21
|
+
|
|
22
|
+
if (!__vueApp) {
|
|
23
|
+
// no __vue_app__, vueApp.mount('#app') has not been called yet
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (__vueApp.config && __vueApp.config.globalProperties) {
|
|
28
|
+
var __gp = __vueApp.config.globalProperties;
|
|
29
|
+
if (!__gp.$extension && __gp.$plugin) { __gp.$extension = __gp.$plugin; }
|
|
30
|
+
else if (!__gp.$plugin && __gp.$extension) { __gp.$plugin = __gp.$extension; }
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Fallback to failure case
|
|
35
|
+
return false;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (!patchGlobalProps()) {
|
|
39
|
+
// Could not patch, keep retrying until it works
|
|
40
|
+
var __retry = setInterval(function() {
|
|
41
|
+
if (patchGlobalProps()) {
|
|
42
|
+
clearInterval(__retry);
|
|
43
|
+
}
|
|
44
|
+
}, 100);
|
|
45
|
+
|
|
46
|
+
// Fallback: clear interval after 10 seconds just in case
|
|
47
|
+
setTimeout(function() { clearInterval(__retry); }, 10000);
|
|
48
|
+
}
|
|
49
|
+
}\n`;
|
|
50
|
+
|
|
14
51
|
function registerFile(file, type, pkg, f) {
|
|
15
52
|
const importType = (f === 'models') ? 'require' : 'import';
|
|
16
53
|
const chunkName = (f === 'l10n') ? '' : `/* webpackChunkName: "${ f }" */`;
|
|
@@ -31,6 +68,8 @@ function register(file, pkg, f) {
|
|
|
31
68
|
function generateTypeImport(pkg, dir) {
|
|
32
69
|
let content = 'export function importTypes($extension) { \n';
|
|
33
70
|
|
|
71
|
+
content += COMPAT_SHIM;
|
|
72
|
+
|
|
34
73
|
// Auto-import if the folder exists
|
|
35
74
|
contextFolders.forEach((f) => {
|
|
36
75
|
const filePath = path.join(dir, f);
|
|
@@ -79,6 +118,8 @@ function generateDynamicTypeImport(pkg, dir) {
|
|
|
79
118
|
const template = fs.readFileSync(path.join(__dirname, 'import.js'), { encoding: 'utf8' });
|
|
80
119
|
let content = 'export function importTypes($extension) { \n';
|
|
81
120
|
|
|
121
|
+
content += COMPAT_SHIM;
|
|
122
|
+
|
|
82
123
|
// Auto-import if the folder exists
|
|
83
124
|
contextFolders.forEach((f) => {
|
|
84
125
|
if (fs.existsSync(path.join(dir, f))) {
|
|
@@ -1193,7 +1193,7 @@ export default class Resource {
|
|
|
1193
1193
|
* Allow to handle the response of the save request
|
|
1194
1194
|
* @param {*} res Full request response
|
|
1195
1195
|
*/
|
|
1196
|
-
processSaveResponse(res) { }
|
|
1196
|
+
processSaveResponse(res, opt = {}) { }
|
|
1197
1197
|
|
|
1198
1198
|
async _save(opt = { }) {
|
|
1199
1199
|
const forNew = !this.id;
|
|
@@ -1280,7 +1280,7 @@ export default class Resource {
|
|
|
1280
1280
|
const res = await this.$dispatch('request', { opt, type: this.type } );
|
|
1281
1281
|
|
|
1282
1282
|
// Allow to process response independently from the related models
|
|
1283
|
-
this.processSaveResponse(res);
|
|
1283
|
+
this.processSaveResponse(res, opt);
|
|
1284
1284
|
|
|
1285
1285
|
// Steve sometimes returns Table responses instead of the resource you just saved.. ignore
|
|
1286
1286
|
if ( res && res.kind !== 'Table') {
|
|
@@ -74,7 +74,7 @@ describe('class: Steve', () => {
|
|
|
74
74
|
|
|
75
75
|
steve.processSaveResponse(response);
|
|
76
76
|
|
|
77
|
-
expect(parentProcessSaveResponse).toHaveBeenCalledWith(response);
|
|
77
|
+
expect(parentProcessSaveResponse).toHaveBeenCalledWith(response, {});
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
describe('growl notifications', () => {
|
|
@@ -70,11 +70,11 @@ export default class SteveModel extends HybridModel {
|
|
|
70
70
|
*
|
|
71
71
|
* @param {*} res
|
|
72
72
|
*/
|
|
73
|
-
processSaveResponse(res) {
|
|
74
|
-
super.processSaveResponse(res);
|
|
73
|
+
processSaveResponse(res, opt = {}) {
|
|
74
|
+
super.processSaveResponse(res, opt);
|
|
75
75
|
|
|
76
76
|
// Conditionally show the growl for autogenerated names
|
|
77
|
-
if (res && res._status === 201 && res.metadata?.generateName && res.id) {
|
|
77
|
+
if (res && res._status === 201 && res.metadata?.generateName && res.id && !opt.suppressSuccessToast) {
|
|
78
78
|
// Split to remove the namespace if present (default/generated-xxx)
|
|
79
79
|
const nameOnly = res.id.split('/').pop();
|
|
80
80
|
|
|
@@ -15,8 +15,7 @@ import {
|
|
|
15
15
|
INGRESS,
|
|
16
16
|
WORKLOAD_TYPES,
|
|
17
17
|
HPA,
|
|
18
|
-
SECRET
|
|
19
|
-
EXT
|
|
18
|
+
SECRET
|
|
20
19
|
} from '@shell/config/types';
|
|
21
20
|
import { CAPI as CAPI_LAB_AND_ANO, CATTLE_PUBLIC_ENDPOINTS, STORAGE, UI_PROJECT_SECRET_COPY } from '@shell/config/labels-annotations';
|
|
22
21
|
import { Schema } from '@shell/plugins/steve/schema';
|
|
@@ -768,8 +767,7 @@ export const PAGINATION_SETTINGS_STORE_DEFAULTS: PaginationSettingsStores = {
|
|
|
768
767
|
{ resource: MANAGEMENT.CLUSTER, context: ['side-bar'] },
|
|
769
768
|
{ resource: CATALOG.APP, context: ['branding'] },
|
|
770
769
|
SECRET,
|
|
771
|
-
CAPI.MACHINE_SET
|
|
772
|
-
EXT.TOKEN
|
|
770
|
+
CAPI.MACHINE_SET
|
|
773
771
|
],
|
|
774
772
|
generic: false,
|
|
775
773
|
}
|
|
@@ -11,27 +11,27 @@ const displayCount = computed(() => props.count < 1000 ? props.count : '999+');
|
|
|
11
11
|
:class="{[props.type]: true, disabled: props.disabled}"
|
|
12
12
|
data-testid="rc-counter-badge"
|
|
13
13
|
>
|
|
14
|
-
{{ displayCount }}
|
|
14
|
+
<span class="count">{{ displayCount }}</span>
|
|
15
15
|
</div>
|
|
16
16
|
</template>
|
|
17
17
|
|
|
18
18
|
<style lang="scss" scoped>
|
|
19
19
|
.rc-counter-badge {
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
height: 21px;
|
|
22
|
+
|
|
20
23
|
display: inline-flex;
|
|
21
|
-
padding:
|
|
24
|
+
padding: 2px 8px;
|
|
22
25
|
align-items: center;
|
|
23
|
-
gap: 8px;
|
|
24
26
|
|
|
25
27
|
border-radius: 30px;
|
|
26
28
|
border: 1px solid var(--rc-active-border);
|
|
27
29
|
|
|
28
|
-
overflow: hidden;
|
|
29
|
-
text-overflow: ellipsis;
|
|
30
30
|
font-family: Lato;
|
|
31
|
-
font-size:
|
|
31
|
+
font-size: 12px;
|
|
32
32
|
font-style: normal;
|
|
33
33
|
font-weight: 400;
|
|
34
|
-
line-height:
|
|
34
|
+
line-height: 17px;
|
|
35
35
|
color: var(--body-text);
|
|
36
36
|
|
|
37
37
|
&.active {
|
|
@@ -20,17 +20,20 @@ const { backgroundColor, borderColor, textColor } = useStatusColors(status, 'out
|
|
|
20
20
|
|
|
21
21
|
<style lang="scss" scoped>
|
|
22
22
|
.rc-status-badge {
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
height: 21px;
|
|
25
|
+
|
|
23
26
|
display: inline-flex;
|
|
24
27
|
align-items: center;
|
|
25
28
|
justify-content: center;
|
|
26
|
-
padding:
|
|
29
|
+
padding: 2px 7px;
|
|
27
30
|
|
|
28
31
|
border: 1px solid transparent;
|
|
29
32
|
border-radius: 30px;
|
|
30
33
|
|
|
31
34
|
font-family: Lato;
|
|
32
35
|
font-size: 12px;
|
|
33
|
-
line-height:
|
|
36
|
+
line-height: 17px;
|
|
34
37
|
|
|
35
38
|
background-color: v-bind(backgroundColor);
|
|
36
39
|
border-color: v-bind(borderColor);
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import RcSection from './RcSection.vue';
|
|
3
|
+
|
|
4
|
+
describe('component: RcSection', () => {
|
|
5
|
+
const defaultProps = {
|
|
6
|
+
type: 'primary' as const,
|
|
7
|
+
mode: 'with-header' as const,
|
|
8
|
+
background: 'primary' as const,
|
|
9
|
+
expandable: false,
|
|
10
|
+
title: 'Test title',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe('type prop', () => {
|
|
14
|
+
it('should apply type-primary class when type is "primary"', () => {
|
|
15
|
+
const wrapper = mount(RcSection, { props: { ...defaultProps, type: 'primary' } });
|
|
16
|
+
|
|
17
|
+
expect(wrapper.find('.rc-section').classes()).toContain('type-primary');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should apply type-secondary class when type is "secondary"', () => {
|
|
21
|
+
const wrapper = mount(RcSection, { props: { ...defaultProps, type: 'secondary' } });
|
|
22
|
+
|
|
23
|
+
expect(wrapper.find('.rc-section').classes()).toContain('type-secondary');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('background prop', () => {
|
|
28
|
+
it('should apply bg-primary class when background is "primary"', () => {
|
|
29
|
+
const wrapper = mount(RcSection, { props: { ...defaultProps, background: 'primary' } });
|
|
30
|
+
|
|
31
|
+
expect(wrapper.find('.rc-section').classes()).toContain('bg-primary');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should apply bg-secondary class when background is "secondary"', () => {
|
|
35
|
+
const wrapper = mount(RcSection, { props: { ...defaultProps, background: 'secondary' } });
|
|
36
|
+
|
|
37
|
+
expect(wrapper.find('.rc-section').classes()).toContain('bg-secondary');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should default to "primary" background when no background prop and no parent', () => {
|
|
41
|
+
const { background: _, ...propsWithoutBg } = defaultProps;
|
|
42
|
+
const wrapper = mount(RcSection, { props: propsWithoutBg });
|
|
43
|
+
|
|
44
|
+
expect(wrapper.find('.rc-section').classes()).toContain('bg-primary');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should alternate background from parent via provide/inject', () => {
|
|
48
|
+
const wrapper = mount(RcSection, {
|
|
49
|
+
props: {
|
|
50
|
+
...defaultProps, background: 'primary', expanded: true
|
|
51
|
+
},
|
|
52
|
+
slots: {
|
|
53
|
+
default: {
|
|
54
|
+
components: { RcSection },
|
|
55
|
+
template: '<RcSection type="secondary" mode="with-header" :expandable="false" title="Child" />',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const childSection = wrapper.findAll('.rc-section')[1];
|
|
61
|
+
|
|
62
|
+
expect(childSection.classes()).toContain('bg-secondary');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should allow explicit background to override the injected alternation', () => {
|
|
66
|
+
const wrapper = mount(RcSection, {
|
|
67
|
+
props: {
|
|
68
|
+
...defaultProps, background: 'primary', expanded: true
|
|
69
|
+
},
|
|
70
|
+
slots: {
|
|
71
|
+
default: {
|
|
72
|
+
components: { RcSection },
|
|
73
|
+
template: '<RcSection type="secondary" mode="with-header" :expandable="false" background="primary" title="Child" />',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const childSection = wrapper.findAll('.rc-section')[1];
|
|
79
|
+
|
|
80
|
+
expect(childSection.classes()).toContain('bg-primary');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('mode prop', () => {
|
|
85
|
+
it('should render section-header when mode is "with-header"', () => {
|
|
86
|
+
const wrapper = mount(RcSection, { props: { ...defaultProps, mode: 'with-header' } });
|
|
87
|
+
|
|
88
|
+
expect(wrapper.find('.section-header').exists()).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should not render section-header when mode is "no-header"', () => {
|
|
92
|
+
const wrapper = mount(RcSection, { props: { ...defaultProps, mode: 'no-header' } });
|
|
93
|
+
|
|
94
|
+
expect(wrapper.find('.section-header').exists()).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should apply no-header class to content when mode is "no-header"', () => {
|
|
98
|
+
const wrapper = mount(RcSection, { props: { ...defaultProps, mode: 'no-header' } });
|
|
99
|
+
|
|
100
|
+
expect(wrapper.find('.section-content').classes()).toContain('no-header');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('title prop', () => {
|
|
105
|
+
it('should render the title text', () => {
|
|
106
|
+
const wrapper = mount(RcSection, { props: { ...defaultProps, title: 'My Section' } });
|
|
107
|
+
|
|
108
|
+
expect(wrapper.find('.title').text()).toBe('My Section');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should render the title slot when provided', () => {
|
|
112
|
+
const wrapper = mount(RcSection, {
|
|
113
|
+
props: { ...defaultProps },
|
|
114
|
+
slots: { title: '<span class="custom-title">Custom</span>' },
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(wrapper.find('.custom-title').exists()).toBe(true);
|
|
118
|
+
expect(wrapper.find('.custom-title').text()).toBe('Custom');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('expandable behavior', () => {
|
|
123
|
+
it('should render toggle button when expandable is true', () => {
|
|
124
|
+
const wrapper = mount(RcSection, { props: { ...defaultProps, expandable: true } });
|
|
125
|
+
|
|
126
|
+
expect(wrapper.find('.toggle-button').exists()).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should not render toggle button when expandable is false', () => {
|
|
130
|
+
const wrapper = mount(RcSection, { props: { ...defaultProps, expandable: false } });
|
|
131
|
+
|
|
132
|
+
expect(wrapper.find('.toggle-button').exists()).toBe(false);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should set aria-expanded on toggle button when expandable', () => {
|
|
136
|
+
const wrapper = mount(RcSection, {
|
|
137
|
+
props: {
|
|
138
|
+
...defaultProps, expandable: true, expanded: true
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(wrapper.find('.toggle-button').attributes('aria-expanded')).toBe('true');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should set aria-expanded="false" on toggle button when collapsed', () => {
|
|
146
|
+
const wrapper = mount(RcSection, {
|
|
147
|
+
props: {
|
|
148
|
+
...defaultProps, expandable: true, expanded: false
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(wrapper.find('.toggle-button').attributes('aria-expanded')).toBe('false');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should set aria-label to "Collapse section" on toggle button when expanded', () => {
|
|
156
|
+
const wrapper = mount(RcSection, {
|
|
157
|
+
props: {
|
|
158
|
+
...defaultProps, expandable: true, expanded: true
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(wrapper.find('.toggle-button').attributes('aria-label')).toBe('Collapse section');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should set aria-label to "Expand section" on toggle button when collapsed', () => {
|
|
166
|
+
const wrapper = mount(RcSection, {
|
|
167
|
+
props: {
|
|
168
|
+
...defaultProps, expandable: true, expanded: false
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
expect(wrapper.find('.toggle-button').attributes('aria-label')).toBe('Expand section');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should emit update:expanded with false when clicking an expanded header', async() => {
|
|
176
|
+
const wrapper = mount(RcSection, {
|
|
177
|
+
props: {
|
|
178
|
+
...defaultProps, expandable: true, expanded: true
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await wrapper.find('.section-header').trigger('click');
|
|
183
|
+
|
|
184
|
+
expect(wrapper.emitted('update:expanded')).toHaveLength(1);
|
|
185
|
+
expect(wrapper.emitted('update:expanded')![0]).toStrictEqual([false]);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should emit update:expanded with true when clicking a collapsed header', async() => {
|
|
189
|
+
const wrapper = mount(RcSection, {
|
|
190
|
+
props: {
|
|
191
|
+
...defaultProps, expandable: true, expanded: false
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await wrapper.find('.section-header').trigger('click');
|
|
196
|
+
|
|
197
|
+
expect(wrapper.emitted('update:expanded')).toHaveLength(1);
|
|
198
|
+
expect(wrapper.emitted('update:expanded')![0]).toStrictEqual([true]);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should not emit update:expanded when clicking a non-expandable header', async() => {
|
|
202
|
+
const wrapper = mount(RcSection, { props: { ...defaultProps, expandable: false } });
|
|
203
|
+
|
|
204
|
+
await wrapper.find('.section-header').trigger('click');
|
|
205
|
+
|
|
206
|
+
expect(wrapper.emitted('update:expanded')).toBeUndefined();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should emit update:expanded when toggle button is clicked', async() => {
|
|
210
|
+
const wrapper = mount(RcSection, {
|
|
211
|
+
props: {
|
|
212
|
+
...defaultProps, expandable: true, expanded: true
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
await wrapper.find('.toggle-button').trigger('click');
|
|
217
|
+
|
|
218
|
+
expect(wrapper.emitted('update:expanded')).toHaveLength(1);
|
|
219
|
+
expect(wrapper.emitted('update:expanded')![0]).toStrictEqual([false]);
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('expanded prop', () => {
|
|
224
|
+
it('should default expanded to true', () => {
|
|
225
|
+
const wrapper = mount(RcSection, { props: { ...defaultProps, expandable: true } });
|
|
226
|
+
|
|
227
|
+
expect(wrapper.find('.section-content').exists()).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should render content when expanded is true', () => {
|
|
231
|
+
const wrapper = mount(RcSection, {
|
|
232
|
+
props: { ...defaultProps, expanded: true },
|
|
233
|
+
slots: { default: '<p>Content</p>' },
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
expect(wrapper.find('.section-content').exists()).toBe(true);
|
|
237
|
+
expect(wrapper.find('p').text()).toBe('Content');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should hide content when expanded is false', () => {
|
|
241
|
+
const wrapper = mount(RcSection, {
|
|
242
|
+
props: { ...defaultProps, expanded: false },
|
|
243
|
+
slots: { default: '<p>Content</p>' },
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
expect(wrapper.find('.section-content').exists()).toBe(false);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should apply expandable-content class when expandable is true', () => {
|
|
250
|
+
const wrapper = mount(RcSection, {
|
|
251
|
+
props: {
|
|
252
|
+
...defaultProps, expandable: true, expanded: true
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
expect(wrapper.find('.section-content').classes()).toContain('expandable-content');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should not apply expandable-content class when expandable is false', () => {
|
|
260
|
+
const wrapper = mount(RcSection, {
|
|
261
|
+
props: {
|
|
262
|
+
...defaultProps, expandable: false, expanded: true
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
expect(wrapper.find('.section-content').classes()).not.toContain('expandable-content');
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should add collapsed class to header when not expanded', () => {
|
|
270
|
+
const wrapper = mount(RcSection, {
|
|
271
|
+
props: {
|
|
272
|
+
...defaultProps, expandable: true, expanded: false
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
expect(wrapper.find('.section-header').classes()).toContain('collapsed');
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('slots', () => {
|
|
281
|
+
it('should render badges slot inside right-wrapper', () => {
|
|
282
|
+
const wrapper = mount(RcSection, {
|
|
283
|
+
props: { ...defaultProps },
|
|
284
|
+
slots: { badges: '<span class="test-badge">Badge</span>' },
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
expect(wrapper.find('.right-wrapper .status-badges .test-badge').exists()).toBe(true);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should render actions slot inside right-wrapper', () => {
|
|
291
|
+
const wrapper = mount(RcSection, {
|
|
292
|
+
props: { ...defaultProps },
|
|
293
|
+
slots: { actions: '<button class="test-action">Act</button>' },
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(wrapper.find('.right-wrapper .actions .test-action').exists()).toBe(true);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('should not render right-wrapper when no badges or actions slots', () => {
|
|
300
|
+
const wrapper = mount(RcSection, { props: { ...defaultProps } });
|
|
301
|
+
|
|
302
|
+
expect(wrapper.find('.right-wrapper').exists()).toBe(false);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should render counter slot', () => {
|
|
306
|
+
const wrapper = mount(RcSection, {
|
|
307
|
+
props: { ...defaultProps },
|
|
308
|
+
slots: { counter: '<span class="test-counter">5</span>' },
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
expect(wrapper.find('.test-counter').exists()).toBe(true);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should render errors slot', () => {
|
|
315
|
+
const wrapper = mount(RcSection, {
|
|
316
|
+
props: { ...defaultProps },
|
|
317
|
+
slots: { errors: '<span class="test-error">!</span>' },
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(wrapper.find('.test-error').exists()).toBe(true);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
});
|