@rancher/shell 0.3.21 → 0.3.23
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/translations/en-us.yaml +4 -0
- package/assets/translations/zh-hans.yaml +8 -1
- package/babel.config.js +3 -0
- package/cloud-credential/__tests__/azure.test.ts +53 -0
- package/cloud-credential/azure.vue +6 -0
- package/components/GrowlManager.vue +33 -30
- package/components/SortableTable/paging.js +10 -0
- package/components/form/GitPicker.vue +16 -0
- package/components/form/ResourceQuota/ProjectRow.vue +38 -15
- package/components/form/SelectOrCreateAuthSecret.vue +9 -3
- package/components/formatter/ClusterProvider.vue +9 -3
- package/components/formatter/__tests__/ClusterProvider.test.ts +5 -1
- package/components/nav/Header.vue +1 -0
- package/config/settings.ts +59 -2
- package/config/types.js +2 -0
- package/creators/pkg/files/.github/workflows/build-extension-catalog.yml +28 -0
- package/creators/pkg/files/.github/workflows/build-extension-charts.yml +26 -0
- package/creators/pkg/init +63 -4
- package/detail/provisioning.cattle.io.cluster.vue +4 -2
- package/edit/fleet.cattle.io.gitrepo.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +4 -4
- package/edit/resources.cattle.io.backup.vue +3 -1
- package/edit/resources.cattle.io.restore.vue +3 -1
- package/mixins/__tests__/chart.test.ts +40 -0
- package/mixins/chart.js +5 -0
- package/models/catalog.cattle.io.clusterrepo.js +6 -2
- package/models/fleet.cattle.io.cluster.js +10 -2
- package/package.json +1 -1
- package/pages/c/_cluster/gatekeeper/index.vue +10 -1
- package/plugins/steve/__tests__/header-warnings.spec.ts +238 -0
- package/plugins/steve/actions.js +4 -23
- package/plugins/steve/header-warnings.ts +91 -0
- package/promptRemove/management.cattle.io.project.vue +9 -6
- package/rancher-components/BadgeState/BadgeState.vue +1 -5
- package/rancher-components/Banner/Banner.test.ts +1 -51
- package/rancher-components/Banner/Banner.vue +53 -134
- package/rancher-components/Card/Card.vue +7 -24
- package/rancher-components/Form/Checkbox/Checkbox.test.ts +29 -20
- package/rancher-components/Form/Checkbox/Checkbox.vue +20 -45
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +8 -2
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +10 -22
- package/rancher-components/Form/Radio/RadioButton.vue +13 -30
- package/rancher-components/Form/Radio/RadioGroup.vue +7 -26
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +6 -7
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +38 -25
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +11 -23
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +5 -19
- package/rancher-components/StringList/StringList.test.ts +49 -453
- package/rancher-components/StringList/StringList.vue +58 -92
- package/scripts/extension/parse-tag-name +30 -0
- package/types/shell/index.d.ts +16 -9
- package/utils/__tests__/formatter.test.ts +77 -0
- package/utils/formatter.js +11 -0
- package/utils/settings.ts +2 -17
- package/vue-config-helper.js +135 -0
- package/vue.config.js +29 -141
- package/creators/pkg/files/.github/workflows/build-container.yml +0 -64
- package/creators/pkg/files/.github/workflows/build-extension.yml +0 -110
- package/rancher-components/Card/Card.test.ts +0 -37
- package/rancher-components/Form/Radio/RadioButton.test.ts +0 -31
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { PerfSettingsWarningHeaders } from '@shell/config/settings';
|
|
2
|
+
import { getPerformanceSetting } from '@shell/utils/settings';
|
|
3
|
+
|
|
4
|
+
interface HttpResponse {
|
|
5
|
+
headers?: { [key: string]: string},
|
|
6
|
+
data?: any,
|
|
7
|
+
config: {
|
|
8
|
+
url: string,
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Cache the kube api warning header settings that will determine if they are growled or not
|
|
14
|
+
*/
|
|
15
|
+
let warningHeaderSettings: PerfSettingsWarningHeaders;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Extract sanitised warnings from the warnings header string
|
|
19
|
+
*/
|
|
20
|
+
function kubeApiHeaderWarnings(allWarnings: string): string[] {
|
|
21
|
+
// Find each warning.
|
|
22
|
+
// Each warning is separated by `,`... however... this can appear within the warning itself so can't `split` on it
|
|
23
|
+
// Instead provide a configurable way to split (default 299 - )
|
|
24
|
+
const warnings = allWarnings.split(warningHeaderSettings.separator) || [];
|
|
25
|
+
|
|
26
|
+
// Trim and remove effects of split
|
|
27
|
+
return warnings.reduce((res, warning) => {
|
|
28
|
+
const trimmedWarning = warning.trim();
|
|
29
|
+
|
|
30
|
+
if (!trimmedWarning) {
|
|
31
|
+
return res;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const fixedWarning = trimmedWarning.endsWith(',') ? trimmedWarning.slice(0, -1) : trimmedWarning;
|
|
35
|
+
|
|
36
|
+
// Why add the separator again? It's almost certainly `299 - ` which is important info to include
|
|
37
|
+
res.push(warningHeaderSettings.separator + fixedWarning);
|
|
38
|
+
|
|
39
|
+
return res;
|
|
40
|
+
}, [] as string[]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Take action given the `warnings` in the response header of a kube api request
|
|
45
|
+
*/
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
47
|
+
export function handleKubeApiHeaderWarnings(res: HttpResponse, dispatch: any, rootGetters: any, method: string, refreshCache = false): void {
|
|
48
|
+
const safeMethod = method?.toLowerCase(); // Some requests have this as uppercase
|
|
49
|
+
|
|
50
|
+
// Exit early if there's no warnings
|
|
51
|
+
if ((safeMethod !== 'post' && safeMethod !== 'put') || !res.headers?.warning) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Grab the required settings
|
|
56
|
+
if (!warningHeaderSettings || refreshCache) {
|
|
57
|
+
const settings = getPerformanceSetting(rootGetters);
|
|
58
|
+
|
|
59
|
+
// Cache this, we don't need to react to changes within the same session
|
|
60
|
+
warningHeaderSettings = settings?.kubeAPI.warningHeader;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Determine each warning
|
|
64
|
+
const sanitisedWarnings = kubeApiHeaderWarnings(res.headers?.warning);
|
|
65
|
+
|
|
66
|
+
if (!sanitisedWarnings.length) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Shows warnings as growls
|
|
71
|
+
const growlWarnings = sanitisedWarnings.filter((w) => !warningHeaderSettings.notificationBlockList.find((blocked) => w.startsWith(blocked)));
|
|
72
|
+
|
|
73
|
+
if (growlWarnings.length) {
|
|
74
|
+
const resourceType = res.data?.type || res.data?.kind || rootGetters['i18n/t']('generic.resource', { count: 1 });
|
|
75
|
+
|
|
76
|
+
dispatch('growl/warning', {
|
|
77
|
+
title: method === 'put' ? rootGetters['i18n/t']('growl.kubeApiHeaderWarning.titleUpdate', { resourceType }) : rootGetters['i18n/t']('growl.kubeApiHeaderWarning.titleCreate', { resourceType }),
|
|
78
|
+
message: growlWarnings.join(', '),
|
|
79
|
+
timeout: 0,
|
|
80
|
+
}, { root: true });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Print warnings to console
|
|
84
|
+
const message = `Validation Warnings for ${ res.config.url }\n\n${ sanitisedWarnings.join('\n') }`;
|
|
85
|
+
|
|
86
|
+
if (process.env.dev) {
|
|
87
|
+
console.warn(`${ message }\n\n`, res.data); // eslint-disable-line no-console
|
|
88
|
+
} else {
|
|
89
|
+
console.debug(message); // eslint-disable-line no-console
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -71,9 +71,12 @@ export default {
|
|
|
71
71
|
names() {
|
|
72
72
|
return this.filteredNamespaces.map((obj) => obj.nameDisplay).slice(0, 5);
|
|
73
73
|
},
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
|
|
75
|
+
canManageNamespaces() {
|
|
76
|
+
// Only admins and cluster owners can see namespaces outside of projects
|
|
77
|
+
// BUT cluster members can also manage projects and namespaces and may want to not delete the namespaces associated with the project
|
|
78
|
+
// as per https://github.com/rancher/dashboard/issues/9517 despite the namespaces cannot be seen afterwards (projectless)
|
|
79
|
+
return this.currentCluster.canUpdate || (this.currentProject.canDelete && this.filteredNamespaces.length && this.filteredNamespaces[0]?.canDelete);
|
|
77
80
|
}
|
|
78
81
|
},
|
|
79
82
|
methods: {
|
|
@@ -81,7 +84,7 @@ export default {
|
|
|
81
84
|
remove() {
|
|
82
85
|
// Delete all of thre namespaces and return false - this tells the prompt remove dialog to continue and delete the project
|
|
83
86
|
// Delete all namespaces if the user wouldn't be able to see them after deleting the project
|
|
84
|
-
if (this.deleteProjectNamespaces || !this.
|
|
87
|
+
if (this.deleteProjectNamespaces || !this.canManageNamespaces) {
|
|
85
88
|
return Promise.all(this.filteredNamespaces.map((n) => n.remove())).then(() => false);
|
|
86
89
|
}
|
|
87
90
|
|
|
@@ -97,7 +100,7 @@ export default {
|
|
|
97
100
|
<div>
|
|
98
101
|
<div class="mb-10">
|
|
99
102
|
{{ t('promptRemove.attemptingToRemove', { type }) }} <span class="display-name">{{ `${displayName}.` }}</span>
|
|
100
|
-
<template v-if="!
|
|
103
|
+
<template v-if="!canManageNamespaces">
|
|
101
104
|
<span class="delete-warning"> {{ t('promptRemove.willDeleteAssociatedNamespaces') }}</span> <br>
|
|
102
105
|
<div
|
|
103
106
|
v-clean-html="resourceNames(names, plusMore, t)"
|
|
@@ -106,7 +109,7 @@ export default {
|
|
|
106
109
|
</template>
|
|
107
110
|
</div>
|
|
108
111
|
<div
|
|
109
|
-
v-if="filteredNamespaces.length > 0 &&
|
|
112
|
+
v-if="filteredNamespaces.length > 0 && canManageNamespaces"
|
|
110
113
|
class="mt-20 remove-project-dialog"
|
|
111
114
|
>
|
|
112
115
|
<Checkbox
|
|
@@ -60,11 +60,7 @@ export default Vue.extend({
|
|
|
60
60
|
|
|
61
61
|
<template>
|
|
62
62
|
<span :class="{'badge-state': true, [bg]: true}">
|
|
63
|
-
<i
|
|
64
|
-
v-if="icon"
|
|
65
|
-
class="icon"
|
|
66
|
-
:class="{[icon]: true, 'mr-5': !!msg}"
|
|
67
|
-
/>{{ msg }}
|
|
63
|
+
<i v-if="icon" class="icon" :class="{[icon]: true, 'mr-5': !!msg}" />{{ msg }}
|
|
68
64
|
</span>
|
|
69
65
|
</template>
|
|
70
66
|
|
|
@@ -1,63 +1,13 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import { Banner } from './index';
|
|
3
|
-
import { cleanHtmlDirective } from '@shell/plugins/clean-html-directive';
|
|
4
3
|
|
|
5
4
|
describe('component: Banner', () => {
|
|
6
5
|
it('should display text based on label', () => {
|
|
7
6
|
const label = 'test';
|
|
8
|
-
const wrapper = mount(
|
|
9
|
-
Banner,
|
|
10
|
-
{
|
|
11
|
-
directives: { cleanHtmlDirective },
|
|
12
|
-
propsData: { label }
|
|
13
|
-
});
|
|
7
|
+
const wrapper = mount(Banner, { propsData: { label } });
|
|
14
8
|
|
|
15
9
|
const element = wrapper.find('span').element;
|
|
16
10
|
|
|
17
11
|
expect(element.textContent).toBe(label);
|
|
18
12
|
});
|
|
19
|
-
|
|
20
|
-
it('should display an icon', () => {
|
|
21
|
-
const icon = 'my-icon';
|
|
22
|
-
const wrapper = mount(Banner, { propsData: { icon } });
|
|
23
|
-
|
|
24
|
-
const element = wrapper.find(`.${ icon }`).element;
|
|
25
|
-
|
|
26
|
-
expect(element.classList).toContain(icon);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should not display an icon', () => {
|
|
30
|
-
const wrapper = mount(Banner);
|
|
31
|
-
|
|
32
|
-
const element = wrapper.find(`[data-testid="banner-icon"]`).element;
|
|
33
|
-
|
|
34
|
-
expect(element).not.toBeDefined();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('should emit close event', () => {
|
|
38
|
-
const wrapper = mount(Banner, { propsData: { closable: true } });
|
|
39
|
-
const element = wrapper.find(`[data-testid="banner-close"]`).element;
|
|
40
|
-
|
|
41
|
-
element.click();
|
|
42
|
-
|
|
43
|
-
expect(wrapper.emitted('close')).toHaveLength(1);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should add the right color', () => {
|
|
47
|
-
const color = 'red';
|
|
48
|
-
const wrapper = mount(Banner, { propsData: { color } });
|
|
49
|
-
|
|
50
|
-
const element = wrapper.element;
|
|
51
|
-
|
|
52
|
-
expect(element.classList).toContain(color);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should stack the banner messages', () => {
|
|
56
|
-
const stacked = true;
|
|
57
|
-
const wrapper = mount(Banner, { propsData: { stacked } });
|
|
58
|
-
|
|
59
|
-
const element = wrapper.find(`[data-testid="banner-content"]`).element;
|
|
60
|
-
|
|
61
|
-
expect(element.classList).toContain('stacked');
|
|
62
|
-
});
|
|
63
13
|
});
|
|
@@ -27,13 +27,6 @@ export default Vue.extend({
|
|
|
27
27
|
type: String,
|
|
28
28
|
default: null
|
|
29
29
|
},
|
|
30
|
-
/**
|
|
31
|
-
* Add icon for the banner
|
|
32
|
-
*/
|
|
33
|
-
icon: {
|
|
34
|
-
type: String,
|
|
35
|
-
default: null
|
|
36
|
-
},
|
|
37
30
|
/**
|
|
38
31
|
* Toggles the banner's close button.
|
|
39
32
|
*/
|
|
@@ -65,137 +58,31 @@ export default Vue.extend({
|
|
|
65
58
|
class="banner"
|
|
66
59
|
:class="{
|
|
67
60
|
[color]: true,
|
|
61
|
+
closable,
|
|
62
|
+
stacked
|
|
68
63
|
}"
|
|
69
64
|
>
|
|
70
|
-
<
|
|
71
|
-
v-if="
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
>
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
:class="icon"
|
|
78
|
-
/>
|
|
79
|
-
</div>
|
|
80
|
-
<div
|
|
81
|
-
class="banner__content"
|
|
82
|
-
data-testid="banner-content"
|
|
83
|
-
:class="{
|
|
84
|
-
closable,
|
|
85
|
-
stacked,
|
|
86
|
-
icon
|
|
87
|
-
}"
|
|
88
|
-
>
|
|
89
|
-
<slot>
|
|
90
|
-
<t
|
|
91
|
-
v-if="labelKey"
|
|
92
|
-
:k="labelKey"
|
|
93
|
-
:raw="true"
|
|
94
|
-
/>
|
|
95
|
-
<span v-else-if="messageLabel">{{ messageLabel }}</span>
|
|
96
|
-
<span
|
|
97
|
-
v-else
|
|
98
|
-
v-clean-html="nlToBr(label)"
|
|
99
|
-
/>
|
|
100
|
-
</slot>
|
|
101
|
-
<div
|
|
102
|
-
v-if="closable"
|
|
103
|
-
class="banner__content__closer"
|
|
104
|
-
@click="$emit('close')"
|
|
105
|
-
>
|
|
106
|
-
<i
|
|
107
|
-
data-testid="banner-close"
|
|
108
|
-
class="icon icon-close closer-icon"
|
|
109
|
-
/>
|
|
110
|
-
</div>
|
|
65
|
+
<slot>
|
|
66
|
+
<t v-if="labelKey" :k="labelKey" :raw="true" />
|
|
67
|
+
<span v-else-if="messageLabel">{{ messageLabel }}</span>
|
|
68
|
+
<span v-else v-html="nlToBr(label)" />
|
|
69
|
+
</slot>
|
|
70
|
+
<div v-if="closable" class="closer" @click="$emit('close')">
|
|
71
|
+
<i class="icon icon-2x icon-close closer-icon" />
|
|
111
72
|
</div>
|
|
112
73
|
</div>
|
|
113
74
|
</template>
|
|
114
75
|
|
|
115
76
|
<style lang="scss" scoped>
|
|
116
|
-
$left-border-size: 4px;
|
|
117
|
-
$icon-size: 24px;
|
|
118
|
-
|
|
119
|
-
.banner {
|
|
120
|
-
display: flex;
|
|
121
|
-
margin: 15px 0;
|
|
122
|
-
position: relative;
|
|
123
|
-
width: 100%;
|
|
124
|
-
color: var(--body-text);
|
|
125
|
-
|
|
126
|
-
&__icon {
|
|
127
|
-
width: $icon-size * 2;
|
|
128
|
-
flex-grow: 1;
|
|
129
|
-
display: flex;
|
|
130
|
-
justify-content: center;
|
|
131
|
-
align-items: center;
|
|
132
|
-
box-sizing: content-box;
|
|
133
|
-
|
|
134
|
-
.primary & {
|
|
135
|
-
background: var(--primary);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
.secondary & {
|
|
139
|
-
background: var(--default);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.success & {
|
|
143
|
-
background: var(--success);
|
|
144
|
-
}
|
|
77
|
+
$left-border-size: 4px;
|
|
145
78
|
|
|
146
|
-
|
|
147
|
-
background: var(--info);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.warning & {
|
|
151
|
-
background: var(--warning);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
.error & {
|
|
155
|
-
background: var(--error);
|
|
156
|
-
color: var(--primary-text);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
&__content {
|
|
79
|
+
.banner {
|
|
161
80
|
padding: 10px;
|
|
81
|
+
margin: 15px 0;
|
|
82
|
+
width: 100%;
|
|
162
83
|
transition: all 0.2s ease;
|
|
84
|
+
position: relative;
|
|
163
85
|
line-height: 20px;
|
|
164
|
-
width: 100%;
|
|
165
|
-
border-left: solid $left-border-size transparent;
|
|
166
|
-
display: flex;
|
|
167
|
-
gap: 3px;
|
|
168
|
-
|
|
169
|
-
.primary & {
|
|
170
|
-
background: var(--primary);
|
|
171
|
-
border-color: var(--primary);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
.secondary & {
|
|
175
|
-
background: var(--default-banner-bg);
|
|
176
|
-
border-color: var(--default);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.success & {
|
|
180
|
-
background: var(--success-banner-bg);
|
|
181
|
-
border-color: var(--success);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
.info & {
|
|
185
|
-
background: var(--info-banner-bg);
|
|
186
|
-
border-color: var(--info);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
.warning & {
|
|
190
|
-
background: var(--warning-banner-bg);
|
|
191
|
-
border-color: var(--warning);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
.error & {
|
|
195
|
-
background: var(--error-banner-bg);
|
|
196
|
-
border-color: var(--error);
|
|
197
|
-
color: var(--error);
|
|
198
|
-
}
|
|
199
86
|
|
|
200
87
|
&.stacked {
|
|
201
88
|
padding: 0 10px;
|
|
@@ -210,10 +97,10 @@ $icon-size: 24px;
|
|
|
210
97
|
}
|
|
211
98
|
|
|
212
99
|
&.closable {
|
|
213
|
-
padding-right:
|
|
100
|
+
padding-right: 40px;
|
|
214
101
|
}
|
|
215
102
|
|
|
216
|
-
|
|
103
|
+
.closer {
|
|
217
104
|
display: flex;
|
|
218
105
|
align-items: center;
|
|
219
106
|
|
|
@@ -222,11 +109,12 @@ $icon-size: 24px;
|
|
|
222
109
|
top: 0;
|
|
223
110
|
right: 0;
|
|
224
111
|
bottom: 0;
|
|
225
|
-
width:
|
|
226
|
-
line-height:
|
|
112
|
+
width: 40px;
|
|
113
|
+
line-height: 42px;
|
|
227
114
|
text-align: center;
|
|
228
115
|
|
|
229
116
|
.closer-icon {
|
|
117
|
+
font-size: 22px;
|
|
230
118
|
opacity: 0.7;
|
|
231
119
|
|
|
232
120
|
&:hover {
|
|
@@ -236,9 +124,40 @@ $icon-size: 24px;
|
|
|
236
124
|
}
|
|
237
125
|
}
|
|
238
126
|
|
|
239
|
-
&.
|
|
240
|
-
|
|
127
|
+
&.primary {
|
|
128
|
+
background: var(--primary);
|
|
129
|
+
border-left: solid $left-border-size var(--primary);
|
|
130
|
+
color: var(--body-text);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
&.secondary {
|
|
134
|
+
background: var(--default-banner-bg);
|
|
135
|
+
border-left: solid $left-border-size var(--default);
|
|
136
|
+
color: var(--body-text);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
&.success {
|
|
140
|
+
background: var(--success-banner-bg);
|
|
141
|
+
border-left: solid $left-border-size var(--success);
|
|
142
|
+
color: var(--body-text);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
&.info {
|
|
146
|
+
background: var(--info-banner-bg);
|
|
147
|
+
border-left: solid $left-border-size var(--info);
|
|
148
|
+
color: var(--body-text);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
&.warning {
|
|
152
|
+
background: var(--warning-banner-bg);
|
|
153
|
+
border-left: solid $left-border-size var(--warning);
|
|
154
|
+
color: var(--body-text);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
&.error {
|
|
158
|
+
background: var(--error-banner-bg);
|
|
159
|
+
border-left: solid $left-border-size var(--error);
|
|
160
|
+
color: var(--error);
|
|
241
161
|
}
|
|
242
162
|
}
|
|
243
|
-
}
|
|
244
163
|
</style>
|
|
@@ -49,45 +49,28 @@ export default Vue.extend({
|
|
|
49
49
|
sticky: {
|
|
50
50
|
type: Boolean,
|
|
51
51
|
default: false,
|
|
52
|
-
},
|
|
52
|
+
},
|
|
53
53
|
}
|
|
54
54
|
});
|
|
55
55
|
</script>
|
|
56
56
|
|
|
57
57
|
<template>
|
|
58
|
-
<div
|
|
59
|
-
class="card-container"
|
|
60
|
-
:class="{'highlight-border': showHighlightBorder, 'card-sticky': sticky}"
|
|
61
|
-
data-testid="card"
|
|
62
|
-
>
|
|
58
|
+
<div class="card-container" :class="{'highlight-border': showHighlightBorder, 'card-sticky': sticky}">
|
|
63
59
|
<div class="card-wrap">
|
|
64
|
-
<div
|
|
65
|
-
class="card-title"
|
|
66
|
-
data-testid="card-title-slot"
|
|
67
|
-
>
|
|
60
|
+
<div class="card-title">
|
|
68
61
|
<slot name="title">
|
|
69
62
|
{{ title }}
|
|
70
63
|
</slot>
|
|
71
64
|
</div>
|
|
72
|
-
<hr
|
|
73
|
-
<div
|
|
74
|
-
class="card-body"
|
|
75
|
-
data-testid="card-body-slot"
|
|
76
|
-
>
|
|
65
|
+
<hr />
|
|
66
|
+
<div class="card-body">
|
|
77
67
|
<slot name="body">
|
|
78
68
|
{{ content }}
|
|
79
69
|
</slot>
|
|
80
70
|
</div>
|
|
81
|
-
<div
|
|
82
|
-
v-if="showActions"
|
|
83
|
-
class="card-actions"
|
|
84
|
-
data-testid="card-actions-slot"
|
|
85
|
-
>
|
|
71
|
+
<div v-if="showActions" class="card-actions">
|
|
86
72
|
<slot name="actions">
|
|
87
|
-
<button
|
|
88
|
-
class="btn role-primary"
|
|
89
|
-
@click="buttonAction"
|
|
90
|
-
>
|
|
73
|
+
<button class="btn role-primary" @click="buttonAction">
|
|
91
74
|
{{ buttonText }}
|
|
92
75
|
</button>
|
|
93
76
|
</slot>
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import { shallowMount
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
2
|
import { Checkbox } from './index';
|
|
3
3
|
|
|
4
|
-
describe('
|
|
5
|
-
const event = {
|
|
6
|
-
target: { tagName: 'input', href: null },
|
|
7
|
-
stopPropagation: () => { },
|
|
8
|
-
preventDefault: () => { }
|
|
9
|
-
} as unknown as MouseEvent;
|
|
10
|
-
|
|
4
|
+
describe('Checkbox.vue', () => {
|
|
11
5
|
it('is unchecked by default', () => {
|
|
12
6
|
const wrapper = shallowMount(Checkbox);
|
|
13
7
|
const cbInput = wrapper.find('input[type="checkbox"]').element as HTMLInputElement;
|
|
@@ -22,7 +16,7 @@ describe('checkbox.vue', () => {
|
|
|
22
16
|
expect(cbInput.checked).toBe(true);
|
|
23
17
|
});
|
|
24
18
|
|
|
25
|
-
it('updates from false to true when props change', async() => {
|
|
19
|
+
it('updates from false to true when props change', async () => {
|
|
26
20
|
const wrapper = shallowMount(Checkbox);
|
|
27
21
|
const cbInput = wrapper.find('input[type="checkbox"]').element as HTMLInputElement;
|
|
28
22
|
|
|
@@ -33,36 +27,51 @@ describe('checkbox.vue', () => {
|
|
|
33
27
|
expect(cbInput.checked).toBe(true);
|
|
34
28
|
});
|
|
35
29
|
|
|
36
|
-
it('emits an input event with a true value', async() => {
|
|
37
|
-
const wrapper
|
|
30
|
+
it('emits an input event with a true value', async () => {
|
|
31
|
+
const wrapper = shallowMount(Checkbox);
|
|
32
|
+
const event = {
|
|
33
|
+
target: { tagName: 'input', href: null },
|
|
34
|
+
stopPropagation: () => { },
|
|
35
|
+
preventDefault: () => { }
|
|
36
|
+
};
|
|
38
37
|
|
|
39
|
-
wrapper.vm.clicked(event);
|
|
38
|
+
(wrapper.vm as any).clicked(event);
|
|
40
39
|
await wrapper.vm.$nextTick();
|
|
41
40
|
|
|
42
41
|
expect(wrapper.emitted().input?.length).toBe(1);
|
|
43
42
|
expect(wrapper.emitted().input?.[0][0]).toBe(true);
|
|
44
43
|
});
|
|
45
44
|
|
|
46
|
-
it('emits an input event with a custom valueWhenTrue', async() => {
|
|
45
|
+
it('emits an input event with a custom valueWhenTrue', async () => {
|
|
47
46
|
const valueWhenTrue = 'BIG IF TRUE';
|
|
47
|
+
const event = {
|
|
48
|
+
target: { tagName: 'input', href: null },
|
|
49
|
+
stopPropagation: () => { },
|
|
50
|
+
preventDefault: () => { }
|
|
51
|
+
};
|
|
48
52
|
|
|
49
|
-
const wrapper
|
|
53
|
+
const wrapper = shallowMount(Checkbox, { propsData: { value: false, valueWhenTrue } });
|
|
50
54
|
|
|
51
|
-
wrapper.vm.clicked(event);
|
|
55
|
+
(wrapper.vm as any).clicked(event);
|
|
52
56
|
await wrapper.vm.$nextTick();
|
|
53
57
|
|
|
54
58
|
expect(wrapper.emitted().input?.length).toBe(1);
|
|
55
59
|
expect(wrapper.emitted().input?.[0][0]).toBe(valueWhenTrue);
|
|
56
60
|
});
|
|
57
61
|
|
|
58
|
-
it('updates from valueWhenTrue to falsy', async() => {
|
|
62
|
+
it('updates from valueWhenTrue to falsy', async () => {
|
|
59
63
|
const valueWhenTrue = 'REAL HUGE IF FALSE';
|
|
64
|
+
const event = {
|
|
65
|
+
target: { tagName: 'input', href: null },
|
|
66
|
+
stopPropagation: () => { },
|
|
67
|
+
preventDefault: () => { }
|
|
68
|
+
};
|
|
60
69
|
|
|
61
|
-
const wrapper
|
|
70
|
+
const wrapper = shallowMount(Checkbox, { propsData: { value: valueWhenTrue, valueWhenTrue } });
|
|
62
71
|
|
|
63
|
-
wrapper.vm.clicked(event);
|
|
72
|
+
(wrapper.vm as any).clicked(event);
|
|
64
73
|
await wrapper.vm.$nextTick();
|
|
65
74
|
|
|
66
|
-
expect(wrapper.emitted().input?.[0][0]).
|
|
67
|
-
})
|
|
75
|
+
expect(wrapper.emitted().input?.[0][0]).toBe(null);
|
|
76
|
+
})
|
|
68
77
|
});
|