dashboard-shell-shell 1.0.113 → 1.0.115
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/components/ActionDropdown.vue +1 -1
- package/components/ActionMenu.vue +2 -2
- package/components/ActionMenuShell.vue +0 -1
- package/components/AppModal.vue +6 -78
- package/components/AssignTo.vue +11 -25
- package/components/AsyncButton.vue +7 -24
- package/components/BannerGraphic.vue +0 -1
- package/components/ButtonDropdown.vue +4 -26
- package/components/ButtonGroup.vue +0 -4
- package/components/ButtonMultiAction.vue +0 -1
- package/components/CommunityLinks.vue +3 -3
- package/components/ConsumptionGauge.vue +5 -24
- package/components/CopyToClipboardText.vue +1 -2
- package/components/CruResource.vue +7 -12
- package/components/CruResourceFooter.vue +2 -2
- package/components/DashboardOptions.vue +15 -21
- package/components/DetailText.vue +0 -5
- package/components/DisableAuthProviderModal.vue +0 -1
- package/components/ExplorerMembers.vue +1 -1
- package/components/ExplorerProjectsNamespaces.vue +14 -56
- package/components/FixedBanner.vue +12 -19
- package/components/GlobalRoleBindings.vue +1 -5
- package/components/GrafanaDashboard.vue +4 -4
- package/components/GrowlManager.vue +1 -4
- package/components/HardwareResourceGauge.vue +3 -39
- package/components/InfoBox.vue +3 -3
- package/components/InputOrDisplay.vue +2 -28
- package/components/LabelValue.vue +1 -16
- package/components/LandingPagePreference.vue +3 -5
- package/components/LocaleSelector.vue +93 -39
- package/components/ModalWithCard.vue +0 -2
- package/components/MoveModal.vue +0 -1
- package/components/PromptChangePassword.vue +1 -1
- package/components/PromptModal.vue +2 -15
- package/components/PromptRemove.vue +8 -28
- package/components/PromptRestore.vue +0 -1
- package/components/ResourceCancelModal.vue +0 -1
- package/components/ResourceDetail/Masthead.vue +43 -188
- package/components/ResourceDetail/__tests__/Masthead.test.ts +1 -5
- package/components/ResourceDetail/index.vue +14 -49
- package/components/ResourceList/Masthead.vue +18 -80
- package/components/ResourceTable.vue +8 -3
- package/components/SideNav.vue +3 -2
- package/components/SortableTable/THead.vue +4 -10
- package/components/SortableTable/actions.js +1 -1
- package/components/SortableTable/index.vue +537 -637
- package/components/SortableTable/selection.js +11 -0
- package/components/Tabbed/Tab.vue +3 -3
- package/components/Tabbed/index.vue +26 -44
- package/components/Wizard.vue +2 -2
- package/components/__tests__/AsyncButton.test.ts +2 -2
- package/components/__tests__/FixedBanner.test.ts +3 -3
- package/components/auth/Principal.vue +3 -10
- package/components/auth/__tests__/RoleDetailEdit.test.ts +2 -3
- package/components/form/ArrayList.vue +85 -123
- package/components/form/ArrayListGrouped.vue +2 -10
- package/components/form/Command.vue +15 -6
- package/components/form/EnvVars.vue +8 -16
- package/components/form/Footer.vue +5 -8
- package/components/form/HealthCheck.vue +3 -3
- package/components/form/HookOption.vue +16 -11
- package/components/form/KeyValue.vue +7 -16
- package/components/form/LabeledSelect.vue +76 -59
- package/components/form/LifecycleHooks.vue +3 -3
- package/components/form/MatchExpressions.vue +12 -35
- package/components/form/NameNsDescription.vue +115 -147
- package/components/form/Networking.vue +12 -20
- package/components/form/NodeAffinity.vue +23 -31
- package/components/form/NodeScheduling.vue +3 -13
- package/components/form/Password.vue +5 -11
- package/components/form/PodAffinity.vue +44 -43
- package/components/form/Probe.vue +66 -68
- package/components/form/ResourceQuota/Project.vue +1 -5
- package/components/form/ResourceSelector.vue +9 -7
- package/components/form/SSHKnownHosts/KnownHostsEditDialog.vue +3 -6
- package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +1 -12
- package/components/form/SSHKnownHosts/index.vue +2 -16
- package/components/form/Security.vue +56 -54
- package/components/form/Select.vue +7 -41
- package/components/form/ShellInput.vue +1 -5
- package/components/form/Tolerations.vue +1 -5
- package/components/form/UnitInput.vue +2 -2
- package/components/form/ValueFromResource.vue +121 -134
- package/components/form/WorkloadPorts.vue +18 -18
- package/components/form/__tests__/ArrayList.test.ts +2 -5
- package/components/form/__tests__/MatchExpressions.test.ts +12 -12
- package/components/form/__tests__/NameNsDescription.test.ts +14 -115
- package/components/form/__tests__/Probe.test.ts +8 -12
- package/components/form/__tests__/SSHKnownHosts.test.ts +0 -11
- package/components/form/__tests__/Select.test.ts +0 -37
- package/components/form/__tests__/UnitInput.test.ts +5 -4
- package/components/formatter/BadgeStateFormatter.vue +5 -8
- package/components/formatter/ExtensionCache.vue +74 -0
- package/components/formatter/InternalExternalIP.vue +0 -2
- package/components/formatter/Port.vue +24 -0
- package/components/formatter/SecretData.vue +7 -20
- package/components/formatter/SecretType.vue +41 -0
- package/components/nav/Favorite.vue +1 -5
- package/components/nav/Group.vue +3 -16
- package/components/nav/Header.vue +13 -39
- package/components/nav/Jump.vue +0 -7
- package/components/nav/NamespaceFilter.vue +8 -14
- package/components/nav/Pinned.vue +1 -1
- package/components/nav/TopLevelMenu.vue +17 -5
- package/components/nav/Type.vue +1 -14
- package/components/nav/__tests__/TopLevelMenu.test.ts +40 -0
- package/components/templates/blank.vue +1 -4
- package/components/templates/default.vue +0 -8
- package/components/templates/home.vue +1 -10
- package/components/templates/plain.vue +1 -10
- package/package.json +1 -1
- package/public/index.html +3 -3
- package/components/ActionDropdownShell.vue +0 -71
- package/components/DotState.vue +0 -84
- package/components/ModalManager.vue +0 -55
- package/components/SlideInPanelManager.vue +0 -126
- package/components/StatusBadge.vue +0 -77
- package/components/__tests__/ModalManager.spec.ts +0 -176
- package/components/__tests__/SlideInPanelManager.spec.ts +0 -166
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
import { computed } from 'vue';
|
|
3
|
-
import { useStore } from 'vuex';
|
|
4
|
-
|
|
5
|
-
const HEADER_HEIGHT = 55;
|
|
6
|
-
|
|
7
|
-
const store = useStore();
|
|
8
|
-
const isOpen = computed(() => store.getters['slideInPanel/isOpen']);
|
|
9
|
-
const currentComponent = computed(() => store.getters['slideInPanel/component']);
|
|
10
|
-
const currentProps = computed(() => store.getters['slideInPanel/componentProps']);
|
|
11
|
-
|
|
12
|
-
const panelTop = computed(() => {
|
|
13
|
-
const banner = document.getElementById('banner-header');
|
|
14
|
-
let height = HEADER_HEIGHT;
|
|
15
|
-
|
|
16
|
-
if (banner) {
|
|
17
|
-
height += banner.clientHeight;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return `${ height }px`;
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
const panelHeight = computed(() => `calc(100vh - ${ panelTop?.value })`);
|
|
24
|
-
const panelWidth = computed(() => currentProps?.value?.width || '33%');
|
|
25
|
-
const panelRight = computed(() => (isOpen?.value ? '0' : `-${ panelWidth?.value }`));
|
|
26
|
-
|
|
27
|
-
const panelTitle = computed(() => currentProps?.value?.title || 'Details');
|
|
28
|
-
|
|
29
|
-
function closePanel() {
|
|
30
|
-
store.commit('slideInPanel/close');
|
|
31
|
-
}
|
|
32
|
-
</script>
|
|
33
|
-
|
|
34
|
-
<template>
|
|
35
|
-
<Teleport to="#slides">
|
|
36
|
-
<div id="slide-in-panel-manager">
|
|
37
|
-
<div
|
|
38
|
-
v-show="isOpen"
|
|
39
|
-
data-testid="slide-in-glass"
|
|
40
|
-
class="slide-in-glass"
|
|
41
|
-
:class="{ 'slide-in-glass-open': isOpen }"
|
|
42
|
-
@click="closePanel"
|
|
43
|
-
/>
|
|
44
|
-
<div
|
|
45
|
-
class="slide-in"
|
|
46
|
-
:class="{ 'slide-in-open': isOpen }"
|
|
47
|
-
:style="{ width: panelWidth, right: panelRight, top: panelTop, height: panelHeight }"
|
|
48
|
-
>
|
|
49
|
-
<div class="header">
|
|
50
|
-
<div class="title">
|
|
51
|
-
{{ panelTitle }}
|
|
52
|
-
</div>
|
|
53
|
-
<i
|
|
54
|
-
class="icon icon-close"
|
|
55
|
-
data-testid="slide-in-close"
|
|
56
|
-
:trigger-focus-trap="true"
|
|
57
|
-
tabindex="0"
|
|
58
|
-
@click="closePanel"
|
|
59
|
-
/>
|
|
60
|
-
</div>
|
|
61
|
-
<div class="main-panel">
|
|
62
|
-
<component
|
|
63
|
-
:is="currentComponent"
|
|
64
|
-
v-if="isOpen || currentComponent"
|
|
65
|
-
v-bind="currentProps"
|
|
66
|
-
data-testid="slide-in-panel-component"
|
|
67
|
-
class="dynamic-panel-content"
|
|
68
|
-
/>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
</Teleport>
|
|
73
|
-
</template>
|
|
74
|
-
|
|
75
|
-
<style lang="scss" scoped>
|
|
76
|
-
.slide-in-glass {
|
|
77
|
-
display: none;
|
|
78
|
-
position: fixed;
|
|
79
|
-
top: 0;
|
|
80
|
-
left: 0;
|
|
81
|
-
height: 100vh;
|
|
82
|
-
width: 100vw;
|
|
83
|
-
}
|
|
84
|
-
.slide-in-glass-open {
|
|
85
|
-
background-color: var(--body-bg);
|
|
86
|
-
display: block;
|
|
87
|
-
opacity: 0.5;
|
|
88
|
-
z-index: 1000;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.slide-in {
|
|
92
|
-
display: flex;
|
|
93
|
-
flex-direction: column;
|
|
94
|
-
position: fixed;
|
|
95
|
-
top: 0;
|
|
96
|
-
z-index: 2000;
|
|
97
|
-
transition: right 0.5s ease;
|
|
98
|
-
border-left: 1px solid var(--border);
|
|
99
|
-
background-color: var(--body-bg);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.slide-in-open {
|
|
103
|
-
right: 0;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.header {
|
|
107
|
-
display: flex;
|
|
108
|
-
align-items: center;
|
|
109
|
-
padding: 4px;
|
|
110
|
-
border-bottom: 1px solid var(--border);
|
|
111
|
-
|
|
112
|
-
.title {
|
|
113
|
-
flex: 1;
|
|
114
|
-
font-weight: bold;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
.icon-close {
|
|
118
|
-
cursor: pointer;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.main-panel {
|
|
123
|
-
padding: 10px;
|
|
124
|
-
overflow: auto;
|
|
125
|
-
}
|
|
126
|
-
</style>
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
const STATUS = {
|
|
4
|
-
success: {
|
|
5
|
-
color: 'text-success',
|
|
6
|
-
icon: 'icon-checkmark'
|
|
7
|
-
},
|
|
8
|
-
warning: {
|
|
9
|
-
color: 'text-warning',
|
|
10
|
-
icon: 'icon-warning'
|
|
11
|
-
},
|
|
12
|
-
info: {
|
|
13
|
-
color: 'text-info',
|
|
14
|
-
icon: 'icon-info'
|
|
15
|
-
},
|
|
16
|
-
error: {
|
|
17
|
-
color: 'text-error',
|
|
18
|
-
icon: 'icon-error'
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
withDefaults(
|
|
23
|
-
defineProps<{
|
|
24
|
-
status?: 'success' | 'warning' | 'info' | 'error',
|
|
25
|
-
label?: string
|
|
26
|
-
}>(),
|
|
27
|
-
{
|
|
28
|
-
status: 'success',
|
|
29
|
-
label: '',
|
|
30
|
-
}
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
</script>
|
|
34
|
-
<template>
|
|
35
|
-
<div
|
|
36
|
-
class="status-badge"
|
|
37
|
-
>
|
|
38
|
-
<i
|
|
39
|
-
class="status-badge__icon icon"
|
|
40
|
-
:class="{
|
|
41
|
-
[STATUS[status].icon]: true,
|
|
42
|
-
[STATUS[status].color]: true
|
|
43
|
-
}"
|
|
44
|
-
/>
|
|
45
|
-
<div
|
|
46
|
-
v-if="label"
|
|
47
|
-
class="status-badge__label"
|
|
48
|
-
>
|
|
49
|
-
{{ label }}
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
|
-
</template>
|
|
53
|
-
|
|
54
|
-
<style lang="scss" scoped>
|
|
55
|
-
.status-badge {
|
|
56
|
-
align-items: center;
|
|
57
|
-
display: inline-flex;
|
|
58
|
-
border: 1px solid;
|
|
59
|
-
border-color: var(--border);
|
|
60
|
-
margin-top: 20px;
|
|
61
|
-
|
|
62
|
-
& + & {
|
|
63
|
-
margin-left: 20px;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
&__label {
|
|
67
|
-
border-left: 1px solid var(--border);
|
|
68
|
-
padding: 5px 20px;
|
|
69
|
-
color: var(--body-text);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
&__icon {
|
|
73
|
-
text-align: center;
|
|
74
|
-
padding: 5px 10px;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
</style>
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { mount } from '@vue/test-utils';
|
|
2
|
-
import { createStore, Store } from 'vuex';
|
|
3
|
-
import { nextTick } from 'vue';
|
|
4
|
-
|
|
5
|
-
import ModalManager from '@shell/components/ModalManager.vue';
|
|
6
|
-
|
|
7
|
-
interface ModalManagerMethods {
|
|
8
|
-
registerBackgroundClosing(fn: Function): void;
|
|
9
|
-
close(): void;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const MockComponent = {
|
|
13
|
-
template: '<div data-testid="modal-manager-component">Mock Content</div>',
|
|
14
|
-
props: ['someProp', 'resources', 'registerBackgroundClosing']
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
describe('modalManager.vue with Teleport', () => {
|
|
18
|
-
let store: Store<any>;
|
|
19
|
-
let getters: Record<string, () => any>;
|
|
20
|
-
let modalsDiv: HTMLDivElement;
|
|
21
|
-
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
// Create the teleport target container
|
|
24
|
-
modalsDiv = document.createElement('div');
|
|
25
|
-
modalsDiv.setAttribute('id', 'modals');
|
|
26
|
-
document.body.appendChild(modalsDiv);
|
|
27
|
-
|
|
28
|
-
getters = {
|
|
29
|
-
'modal/isOpen': () => true,
|
|
30
|
-
'modal/component': () => MockComponent,
|
|
31
|
-
'modal/componentProps': () => ({ someProp: 'testValue' }),
|
|
32
|
-
'modal/resources': () => ({ data: 'mockData' }),
|
|
33
|
-
'modal/closeOnClickOutside': () => true,
|
|
34
|
-
'modal/modalWidth': () => '500px'
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
store = createStore({
|
|
38
|
-
getters,
|
|
39
|
-
mutations: { 'modal/closeModal': jest.fn() }
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
afterEach(() => {
|
|
44
|
-
// Clean up the teleport container after each test
|
|
45
|
-
document.body.removeChild(modalsDiv);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const factory = () => {
|
|
49
|
-
return mount(ModalManager, {
|
|
50
|
-
attachTo: document.body, // attach so Teleport can work properly
|
|
51
|
-
global: {
|
|
52
|
-
plugins: [store],
|
|
53
|
-
stubs: {
|
|
54
|
-
AppModal: {
|
|
55
|
-
name: 'AppModal',
|
|
56
|
-
template: `<div data-testid="app-modal" @close="$emit('close')" :style="{ '--prompt-modal-width': width }"><slot /></div>`,
|
|
57
|
-
props: ['clickToClose', 'width']
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
it('renders the AppModal and dynamic component when modal is open', async() => {
|
|
65
|
-
factory();
|
|
66
|
-
|
|
67
|
-
await nextTick();
|
|
68
|
-
|
|
69
|
-
// Because Teleport moves content out of the normal wrapper,
|
|
70
|
-
// we query the document for the teleported elements.
|
|
71
|
-
const appModal = document.querySelector('[data-testid="app-modal"]');
|
|
72
|
-
const dynamicComponent = document.querySelector('[data-testid="modal-manager-component"]');
|
|
73
|
-
|
|
74
|
-
expect(appModal).toBeTruthy();
|
|
75
|
-
expect(dynamicComponent).toBeTruthy();
|
|
76
|
-
expect(appModal?.getAttribute('style')).toContain('--prompt-modal-width: 500px');
|
|
77
|
-
|
|
78
|
-
// We assume the mock component is rendered correctly if its markup is found.
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('does not render the AppModal when modal is closed', async() => {
|
|
82
|
-
getters['modal/isOpen'] = () => false;
|
|
83
|
-
store = createStore({
|
|
84
|
-
getters,
|
|
85
|
-
mutations: { 'modal/closeModal': jest.fn() }
|
|
86
|
-
});
|
|
87
|
-
factory();
|
|
88
|
-
await nextTick();
|
|
89
|
-
|
|
90
|
-
const appModal = document.querySelector('[data-testid="app-modal"]');
|
|
91
|
-
|
|
92
|
-
expect(appModal).toBeNull();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('does not render the AppModal when dynamic component is null', async() => {
|
|
96
|
-
getters['modal/component'] = () => null;
|
|
97
|
-
store = createStore({
|
|
98
|
-
getters,
|
|
99
|
-
mutations: { 'modal/closeModal': jest.fn() }
|
|
100
|
-
});
|
|
101
|
-
factory();
|
|
102
|
-
await nextTick();
|
|
103
|
-
|
|
104
|
-
const appModal = document.querySelector('[data-testid="app-modal"]');
|
|
105
|
-
|
|
106
|
-
expect(appModal).toBeNull();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('calls store commit when close is triggered', async() => {
|
|
110
|
-
const closeModalMutation = jest.fn();
|
|
111
|
-
|
|
112
|
-
getters['modal/isOpen'] = () => true;
|
|
113
|
-
store = createStore({
|
|
114
|
-
getters,
|
|
115
|
-
mutations: { 'modal/closeModal': closeModalMutation }
|
|
116
|
-
});
|
|
117
|
-
const wrapper = factory();
|
|
118
|
-
|
|
119
|
-
await nextTick();
|
|
120
|
-
|
|
121
|
-
const appModalWrapper = wrapper.findComponent({ name: 'AppModal' });
|
|
122
|
-
|
|
123
|
-
appModalWrapper.vm.$emit('close');
|
|
124
|
-
await nextTick();
|
|
125
|
-
|
|
126
|
-
expect(closeModalMutation).toHaveBeenCalledWith({}, undefined);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('calls registered background closing function on close', async() => {
|
|
130
|
-
const closeModalMutation = jest.fn();
|
|
131
|
-
|
|
132
|
-
getters['modal/isOpen'] = () => true;
|
|
133
|
-
store = createStore({
|
|
134
|
-
getters,
|
|
135
|
-
mutations: { 'modal/closeModal': closeModalMutation }
|
|
136
|
-
});
|
|
137
|
-
const wrapper = factory();
|
|
138
|
-
|
|
139
|
-
await nextTick();
|
|
140
|
-
|
|
141
|
-
const backgroundFn = jest.fn();
|
|
142
|
-
|
|
143
|
-
(wrapper.vm as unknown as ModalManagerMethods).registerBackgroundClosing(backgroundFn);
|
|
144
|
-
await nextTick();
|
|
145
|
-
|
|
146
|
-
const appModalWrapper = wrapper.findComponent({ name: 'AppModal' });
|
|
147
|
-
|
|
148
|
-
appModalWrapper.vm.$emit('close');
|
|
149
|
-
await nextTick();
|
|
150
|
-
|
|
151
|
-
expect(backgroundFn).toHaveBeenCalledWith();
|
|
152
|
-
expect(closeModalMutation).toHaveBeenCalledWith({}, undefined);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('does nothing if modal is already closed when close is triggered', async() => {
|
|
156
|
-
const closeModalMutation = jest.fn();
|
|
157
|
-
|
|
158
|
-
getters['modal/isOpen'] = () => false;
|
|
159
|
-
store = createStore({
|
|
160
|
-
getters,
|
|
161
|
-
mutations: { 'modal/closeModal': closeModalMutation }
|
|
162
|
-
});
|
|
163
|
-
const wrapper = factory();
|
|
164
|
-
|
|
165
|
-
await nextTick();
|
|
166
|
-
|
|
167
|
-
const modalManager = wrapper.vm as unknown as ModalManagerMethods;
|
|
168
|
-
const spy = jest.spyOn(modalManager, 'close');
|
|
169
|
-
|
|
170
|
-
modalManager.close();
|
|
171
|
-
await nextTick();
|
|
172
|
-
|
|
173
|
-
expect(spy).toHaveBeenCalledWith();
|
|
174
|
-
expect(closeModalMutation).not.toHaveBeenCalled();
|
|
175
|
-
});
|
|
176
|
-
});
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { mount } from '@vue/test-utils';
|
|
2
|
-
import { createStore, Store } from 'vuex';
|
|
3
|
-
import { nextTick } from 'vue';
|
|
4
|
-
import SlideInPanelManager from '@shell/components/SlideInPanelManager.vue';
|
|
5
|
-
|
|
6
|
-
const MockComponent = {
|
|
7
|
-
template: '<div data-testid="slide-in-panel-component">Mock Panel Content</div>',
|
|
8
|
-
props: ['width', 'title', 'extraProp']
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
describe('slideInPanelManager.vue with Teleport', () => {
|
|
12
|
-
let store: Store<any>;
|
|
13
|
-
let getters: Record<string, () => any>;
|
|
14
|
-
let slidesDiv: HTMLDivElement;
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
// Create teleport target container
|
|
18
|
-
slidesDiv = document.createElement('div');
|
|
19
|
-
slidesDiv.setAttribute('id', 'slides');
|
|
20
|
-
document.body.appendChild(slidesDiv);
|
|
21
|
-
|
|
22
|
-
getters = {
|
|
23
|
-
'slideInPanel/isOpen': () => true,
|
|
24
|
-
'slideInPanel/component': () => MockComponent,
|
|
25
|
-
'slideInPanel/componentProps': () => ({
|
|
26
|
-
width: '40%', title: 'Test Title', extraProp: 'extra'
|
|
27
|
-
})
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
store = createStore({
|
|
31
|
-
getters,
|
|
32
|
-
mutations: { 'slideInPanel/close': jest.fn() }
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
afterEach(() => {
|
|
37
|
-
// Clean up the teleport container
|
|
38
|
-
document.body.removeChild(slidesDiv);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const factory = () => {
|
|
42
|
-
return mount(SlideInPanelManager, {
|
|
43
|
-
attachTo: document.body, // attach to document so Teleport renders
|
|
44
|
-
global: { plugins: [store] }
|
|
45
|
-
});
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
it('renders slide in panel with proper style when open', async() => {
|
|
49
|
-
factory();
|
|
50
|
-
await nextTick();
|
|
51
|
-
|
|
52
|
-
const slidePanel = document.querySelector('#slides .slide-in') as HTMLElement;
|
|
53
|
-
const slideGlass = document.querySelector('[data-testid="slide-in-glass"]') as HTMLElement;
|
|
54
|
-
const slideComponent = document.querySelector('[data-testid="slide-in-panel-component"]') as HTMLElement;
|
|
55
|
-
const headerTitle = document.querySelector('#slides .slide-in .header .title') as HTMLElement;
|
|
56
|
-
|
|
57
|
-
expect(slidePanel).toBeTruthy();
|
|
58
|
-
expect(slideGlass).toBeTruthy();
|
|
59
|
-
expect(slideComponent).toBeTruthy();
|
|
60
|
-
expect(headerTitle.textContent?.trim()).toBe('Test Title');
|
|
61
|
-
|
|
62
|
-
const styleAttr = slidePanel.getAttribute('style') || '';
|
|
63
|
-
|
|
64
|
-
expect(styleAttr).toContain('width: 40%');
|
|
65
|
-
expect(styleAttr).toContain('top: 55px');
|
|
66
|
-
expect(styleAttr).toContain('height: calc(100vh - 55px)');
|
|
67
|
-
expect(styleAttr).toContain('right: 0');
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('renders default panel title when no title is provided', async() => {
|
|
71
|
-
// Update getter so that no title is provided
|
|
72
|
-
getters['slideInPanel/componentProps'] = () => ({ width: '40%' });
|
|
73
|
-
store = createStore({
|
|
74
|
-
getters,
|
|
75
|
-
mutations: { 'slideInPanel/close': jest.fn() }
|
|
76
|
-
});
|
|
77
|
-
factory();
|
|
78
|
-
await nextTick();
|
|
79
|
-
|
|
80
|
-
const headerTitle = document.querySelector('#slides #slide-in-panel-manager .header .title') as HTMLElement;
|
|
81
|
-
|
|
82
|
-
expect(headerTitle.textContent?.trim()).toBe('Details');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('computes panelTop correctly when a banner exists', async() => {
|
|
86
|
-
// Create a banner element with a simulated clientHeight.
|
|
87
|
-
const banner = document.createElement('div');
|
|
88
|
-
|
|
89
|
-
banner.setAttribute('id', 'banner-header');
|
|
90
|
-
document.body.appendChild(banner);
|
|
91
|
-
// Simulate a banner with a clientHeight of 100.
|
|
92
|
-
Object.defineProperty(banner, 'clientHeight', { value: 100, configurable: true });
|
|
93
|
-
|
|
94
|
-
factory();
|
|
95
|
-
await nextTick();
|
|
96
|
-
|
|
97
|
-
const slidePanel = document.querySelector('#slides .slide-in') as HTMLElement;
|
|
98
|
-
const styleAttr = slidePanel.getAttribute('style') || '';
|
|
99
|
-
|
|
100
|
-
// Expected panelTop = HEADER_HEIGHT (55) + banner.clientHeight (100) = "155px"
|
|
101
|
-
expect(styleAttr).toContain('top: 155px');
|
|
102
|
-
expect(styleAttr).toContain('height: calc(100vh - 155px)');
|
|
103
|
-
|
|
104
|
-
document.body.removeChild(banner);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('renders slide in glass as hidden and panel with negative right when closed', async() => {
|
|
108
|
-
// Set isOpen to false.
|
|
109
|
-
getters['slideInPanel/isOpen'] = () => false;
|
|
110
|
-
store = createStore({
|
|
111
|
-
getters,
|
|
112
|
-
mutations: { 'slideInPanel/close': jest.fn() }
|
|
113
|
-
});
|
|
114
|
-
factory();
|
|
115
|
-
await nextTick();
|
|
116
|
-
|
|
117
|
-
const slideGlass = document.querySelector('[data-testid="slide-in-glass"]') as HTMLElement;
|
|
118
|
-
|
|
119
|
-
expect(slideGlass).toBeTruthy();
|
|
120
|
-
expect(slideGlass.style.display).toBe('none');
|
|
121
|
-
|
|
122
|
-
const slidePanel = document.querySelector('#slides .slide-in') as HTMLElement;
|
|
123
|
-
const styleAttr = slidePanel.getAttribute('style') || '';
|
|
124
|
-
|
|
125
|
-
// With currentProps width "40%", panelRight should be "-40%" when closed.
|
|
126
|
-
expect(styleAttr).toContain('right: -40%');
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('calls store commit when clicking on the slide-in glass overlay', async() => {
|
|
130
|
-
const closeMutation = jest.fn();
|
|
131
|
-
|
|
132
|
-
getters['slideInPanel/isOpen'] = () => true;
|
|
133
|
-
store = createStore({
|
|
134
|
-
getters,
|
|
135
|
-
mutations: { 'slideInPanel/close': closeMutation }
|
|
136
|
-
});
|
|
137
|
-
factory();
|
|
138
|
-
await nextTick();
|
|
139
|
-
|
|
140
|
-
const slideGlass = document.querySelector('[data-testid="slide-in-glass"]') as HTMLElement;
|
|
141
|
-
|
|
142
|
-
slideGlass.click();
|
|
143
|
-
await nextTick();
|
|
144
|
-
|
|
145
|
-
expect(closeMutation).toHaveBeenCalledWith({}, undefined);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('calls store commit when clicking on the slide-in close icon', async() => {
|
|
149
|
-
const closeMutation = jest.fn();
|
|
150
|
-
|
|
151
|
-
getters['slideInPanel/isOpen'] = () => true;
|
|
152
|
-
store = createStore({
|
|
153
|
-
getters,
|
|
154
|
-
mutations: { 'slideInPanel/close': closeMutation }
|
|
155
|
-
});
|
|
156
|
-
factory();
|
|
157
|
-
await nextTick();
|
|
158
|
-
|
|
159
|
-
const closeIcon = document.querySelector('[data-testid="slide-in-close"]') as HTMLElement;
|
|
160
|
-
|
|
161
|
-
closeIcon.click();
|
|
162
|
-
await nextTick();
|
|
163
|
-
|
|
164
|
-
expect(closeMutation).toHaveBeenCalledWith({}, undefined);
|
|
165
|
-
});
|
|
166
|
-
});
|