@rancher/shell 3.0.12-rc.5 → 3.0.12-rc.7
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/apis/intf/resources-api/resource-base.ts +42 -2
- package/apis/intf/resources-api/resource-instance.ts +101 -0
- package/apis/intf/resources-api/resources-api.ts +177 -23
- package/apis/intf/resources.ts +8 -6
- package/apis/intf/shell-api/slide-in.ts +46 -17
- package/apis/resources/__tests__/resources-api-class.test.ts +309 -28
- package/apis/resources/resources-api-class.ts +232 -43
- package/apis/shell/__tests__/slide-in.test.ts +83 -2
- package/apis/shell/slide-in.ts +20 -0
- package/assets/images/providers/traefik.png +0 -0
- package/assets/translations/en-us.yaml +10 -4
- package/chart/__tests__/rancher-monitoring-dashboards-index.test.ts +269 -0
- package/chart/rancher-monitoring-dashboards/index.vue +155 -0
- package/cloud-credential/__tests__/generic.test.ts +132 -0
- package/cloud-credential/generic.vue +33 -3
- package/components/EtcdInfoBanner.vue +2 -12
- package/components/ExplorerProjectsNamespaces.vue +2 -2
- package/components/GrafanaDashboard.vue +2 -8
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +68 -14
- package/components/Resource/Detail/Masthead/index.vue +8 -3
- package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +9 -0
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +5 -1
- package/components/SlideInPanelManager.vue +103 -25
- package/components/__tests__/SlideInPanelManager.spec.ts +153 -10
- package/components/auth/AuthBanner.vue +1 -1
- package/components/form/Security.vue +1 -1
- package/components/formatter/Translate.vue +22 -1
- package/components/nav/Group.vue +5 -10
- package/components/nav/TopLevelMenu.vue +1 -30
- package/components/nav/__tests__/Group.test.ts +61 -0
- package/config/labels-annotations.js +1 -0
- package/config/table-headers.js +5 -2
- package/core/__tests__/plugin-products-apply.test.ts +787 -0
- package/core/__tests__/plugin-products-extend.test.ts +189 -0
- package/core/__tests__/plugin-products-helpers.test.ts +81 -78
- package/core/__tests__/plugin-products-new.test.ts +389 -0
- package/core/__tests__/plugin-products-scenarios.test.ts +980 -0
- package/core/__tests__/plugin-products-side-menu.test.ts +1935 -0
- package/core/plugin-products-base.ts +201 -93
- package/core/plugin-products-extending.ts +1 -1
- package/core/plugin-products-external.ts +380 -0
- package/core/plugin-products-helpers.ts +28 -24
- package/core/plugin-products-internal.ts +207 -0
- package/core/plugin-products-top-level.ts +10 -5
- package/core/plugin-products-type-guards.ts +17 -6
- package/core/plugin-products.ts +5 -7
- package/core/plugin-types.ts +1 -389
- package/core/plugin.ts +10 -11
- package/core/plugins-loader.js +2 -0
- package/core/types.ts +6 -202
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +52 -0
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +0 -2
- package/edit/auth/AuthProviderWarningBanners.vue +12 -0
- package/edit/auth/__tests__/AuthProviderWarningBanners.test.ts +10 -1
- package/edit/auth/__tests__/azuread.test.ts +1 -0
- package/edit/auth/__tests__/github.test.ts +1 -0
- package/edit/auth/__tests__/oidc.test.ts +1 -0
- package/edit/auth/__tests__/saml.test.ts +1 -0
- package/edit/auth/ldap/__tests__/index.test.ts +1 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +2 -2
- package/list/management.cattle.io.user.vue +24 -5
- package/list/utils/management.cattle.io.cluster.utils.ts +1 -1
- package/machine-config/__tests__/generic.test.ts +163 -0
- package/machine-config/components/EC2Networking.vue +31 -7
- package/machine-config/components/__tests__/EC2Networking.test.ts +38 -15
- package/machine-config/generic.vue +32 -5
- package/mixins/resource-fetch.js +1 -1
- package/models/__tests__/namespace.test.ts +133 -8
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +57 -1
- package/models/__tests__/rke-machine.cattle.io.vmwarevspheremachinetemplate.test.ts +22 -0
- package/models/namespace.js +34 -2
- package/models/pod.js +12 -10
- package/models/provisioning.cattle.io.cluster.js +20 -13
- package/models/rke-machine.cattle.io.vmwarevspheremachinetemplate.js +9 -0
- package/package.json +1 -1
- package/pages/auth/login.vue +1 -1
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +110 -0
- package/pages/c/_cluster/apps/charts/install.vue +29 -0
- package/pages/c/_cluster/auth/config/index.vue +11 -4
- package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +136 -1
- package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +69 -1
- package/pages/c/_cluster/fleet/index.vue +5 -9
- package/pages/c/_cluster/monitoring/index.vue +6 -1
- package/pkg/vue.config.js +4 -3
- package/plugins/codemirror.js +2 -0
- package/plugins/dashboard-store/resource-class.js +13 -9
- package/plugins/steve/__tests__/steve-class-resource-api.test.ts +147 -0
- package/plugins/steve/steve-class.js +43 -0
- package/plugins/steve/subscribe.js +11 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +115 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +83 -3
- package/store/__tests__/action-menu.test.ts +622 -0
- package/store/__tests__/auth.test.ts +18 -0
- package/store/__tests__/notifications.test.ts +466 -1
- package/store/__tests__/slideInPanel.test.ts +143 -43
- package/store/__tests__/ui-context.test.ts +255 -0
- package/store/__tests__/wm.test.ts +503 -0
- package/store/auth.js +11 -1
- package/store/aws.js +19 -4
- package/store/features.js +1 -0
- package/store/plugins.js +6 -0
- package/store/slideInPanel.ts +15 -3
- package/types/rancher/steve.api.ts +2 -2
- package/types/shell/index.d.ts +84 -2
- package/types/store/dashboard-store.types.ts +2 -2
- package/types/store/type-map.ts +262 -1
- package/utils/__tests__/fleet-appco.test.ts +23 -0
- package/utils/async.ts +2 -0
- package/utils/fleet-appco.ts +6 -1
- package/utils/grafana.js +2 -17
- package/utils/monitoring.js +38 -1
- package/utils/sort.js +1 -1
- package/core/__tests__/plugin-products.test.ts +0 -4694
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import
|
|
1
|
+
import slideInPanel, { SlideInPanelState } from '@shell/store/slideInPanel';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
const MockComponentA = { template: '<div>A</div>' };
|
|
4
|
+
const MockComponentB = { template: '<div>B</div>' };
|
|
5
|
+
|
|
6
|
+
const { state: stateFactory, mutations, getters } = slideInPanel;
|
|
7
|
+
|
|
8
|
+
function createState(overrides: Partial<SlideInPanelState> = {}): SlideInPanelState {
|
|
9
|
+
return {
|
|
10
|
+
...stateFactory(),
|
|
11
|
+
...overrides
|
|
12
|
+
};
|
|
13
|
+
}
|
|
6
14
|
|
|
15
|
+
describe('slideInPanel store', () => {
|
|
7
16
|
beforeEach(() => {
|
|
8
|
-
s = slideInPanelStore.state();
|
|
9
17
|
jest.useFakeTimers();
|
|
10
18
|
});
|
|
11
19
|
|
|
@@ -14,74 +22,166 @@ describe('slideInPanel store', () => {
|
|
|
14
22
|
});
|
|
15
23
|
|
|
16
24
|
describe('state', () => {
|
|
17
|
-
it('returns initial
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
expect(
|
|
21
|
-
|
|
25
|
+
it('returns correct initial state', () => {
|
|
26
|
+
const result = stateFactory();
|
|
27
|
+
|
|
28
|
+
expect(result).toStrictEqual({
|
|
29
|
+
isOpen: false,
|
|
30
|
+
isClosing: false,
|
|
31
|
+
component: null,
|
|
32
|
+
componentProps: {}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('getters', () => {
|
|
38
|
+
it('returns isOpen from state', () => {
|
|
39
|
+
const state = createState({ isOpen: true });
|
|
40
|
+
|
|
41
|
+
expect(getters.isOpen(state, {}, {}, {})).toStrictEqual(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('returns isClosing from state', () => {
|
|
45
|
+
const state = createState({ isClosing: true });
|
|
46
|
+
|
|
47
|
+
expect(getters.isClosing(state, {}, {}, {})).toStrictEqual(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('returns component from state', () => {
|
|
51
|
+
const state = createState({ component: MockComponentA as any });
|
|
52
|
+
|
|
53
|
+
expect(getters.component(state, {}, {}, {})).toStrictEqual(MockComponentA);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('returns componentProps from state', () => {
|
|
57
|
+
const props = { title: 'Test' };
|
|
58
|
+
const state = createState({ componentProps: props });
|
|
59
|
+
|
|
60
|
+
expect(getters.componentProps(state, {}, {}, {})).toStrictEqual(props);
|
|
22
61
|
});
|
|
23
62
|
});
|
|
24
63
|
|
|
25
64
|
describe('mutations', () => {
|
|
26
65
|
describe('open', () => {
|
|
27
|
-
it('sets isOpen to true', () => {
|
|
28
|
-
|
|
66
|
+
it('sets isOpen to true and stores component and props', () => {
|
|
67
|
+
const state = createState();
|
|
68
|
+
const props = { title: 'Test Panel' };
|
|
29
69
|
|
|
30
|
-
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('stores the component reference', () => {
|
|
34
|
-
slideInPanelStore.mutations.open(s, { component: fakeComponent });
|
|
70
|
+
mutations.open(state, { component: MockComponentA, componentProps: props });
|
|
35
71
|
|
|
36
|
-
expect(
|
|
72
|
+
expect(state.isOpen).toStrictEqual(true);
|
|
73
|
+
expect(state.component).toStrictEqual(MockComponentA);
|
|
74
|
+
expect(state.componentProps).toStrictEqual(props);
|
|
37
75
|
});
|
|
38
76
|
|
|
39
|
-
it('
|
|
40
|
-
|
|
77
|
+
it('defaults componentProps to empty object when not provided', () => {
|
|
78
|
+
const state = createState();
|
|
41
79
|
|
|
42
|
-
|
|
80
|
+
mutations.open(state, { component: MockComponentA });
|
|
81
|
+
|
|
82
|
+
expect(state.componentProps).toStrictEqual({});
|
|
43
83
|
});
|
|
44
84
|
|
|
45
|
-
it('
|
|
46
|
-
|
|
85
|
+
it('resets isClosing to false', () => {
|
|
86
|
+
const state = createState({ isClosing: true });
|
|
47
87
|
|
|
48
|
-
|
|
88
|
+
mutations.open(state, { component: MockComponentA });
|
|
89
|
+
|
|
90
|
+
expect(state.isClosing).toStrictEqual(false);
|
|
49
91
|
});
|
|
50
92
|
});
|
|
51
93
|
|
|
52
94
|
describe('close', () => {
|
|
53
|
-
|
|
54
|
-
|
|
95
|
+
it('sets isClosing to true and isOpen to false immediately', () => {
|
|
96
|
+
const state = createState({ isOpen: true, component: MockComponentA as any });
|
|
97
|
+
|
|
98
|
+
mutations.close(state);
|
|
99
|
+
|
|
100
|
+
expect(state.isClosing).toStrictEqual(true);
|
|
101
|
+
expect(state.isOpen).toStrictEqual(false);
|
|
102
|
+
expect(state.component).toStrictEqual(MockComponentA);
|
|
55
103
|
});
|
|
56
104
|
|
|
57
|
-
it('
|
|
58
|
-
|
|
105
|
+
it('retains component and componentProps before the 500ms delay elapses', () => {
|
|
106
|
+
const state = createState({
|
|
107
|
+
isOpen: true,
|
|
108
|
+
component: MockComponentA as any,
|
|
109
|
+
componentProps: { title: 'Test' }
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
mutations.close(state);
|
|
113
|
+
jest.advanceTimersByTime(499);
|
|
59
114
|
|
|
60
|
-
expect(
|
|
115
|
+
expect(state.component).toStrictEqual(MockComponentA);
|
|
116
|
+
expect(state.componentProps).toStrictEqual({ title: 'Test' });
|
|
117
|
+
expect(state.isClosing).toStrictEqual(true);
|
|
61
118
|
});
|
|
62
119
|
|
|
63
|
-
it('
|
|
64
|
-
|
|
120
|
+
it('clears component and props after 500ms transition', () => {
|
|
121
|
+
const state = createState({
|
|
122
|
+
isOpen: true,
|
|
123
|
+
component: MockComponentA as any,
|
|
124
|
+
componentProps: { title: 'Test' }
|
|
125
|
+
});
|
|
65
126
|
|
|
66
|
-
|
|
127
|
+
mutations.close(state);
|
|
128
|
+
|
|
129
|
+
expect(state.component).toStrictEqual(MockComponentA);
|
|
130
|
+
expect(state.componentProps).toStrictEqual({ title: 'Test' });
|
|
131
|
+
|
|
132
|
+
jest.advanceTimersByTime(500);
|
|
133
|
+
|
|
134
|
+
expect(state.component).toStrictEqual(null);
|
|
135
|
+
expect(state.componentProps).toStrictEqual({});
|
|
136
|
+
expect(state.isClosing).toStrictEqual(false);
|
|
67
137
|
});
|
|
138
|
+
});
|
|
68
139
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
140
|
+
describe('close-then-open race condition', () => {
|
|
141
|
+
it('does not leave an untracked timer when close is called twice before open', () => {
|
|
142
|
+
const state = createState({
|
|
143
|
+
isOpen: true,
|
|
144
|
+
component: MockComponentA as any
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
mutations.close(state);
|
|
148
|
+
mutations.close(state);
|
|
149
|
+
mutations.open(state, { component: MockComponentB, componentProps: { title: 'Panel B' } });
|
|
150
|
+
|
|
151
|
+
expect(state.isOpen).toStrictEqual(true);
|
|
152
|
+
expect(state.isClosing).toStrictEqual(false);
|
|
153
|
+
expect(state.component).toStrictEqual(MockComponentB);
|
|
154
|
+
|
|
155
|
+
jest.advanceTimersByTime(500);
|
|
72
156
|
|
|
73
|
-
|
|
74
|
-
expect(
|
|
75
|
-
expect(
|
|
157
|
+
// Neither of the two stale close timers should fire and clear the newly opened panel
|
|
158
|
+
expect(state.component).toStrictEqual(MockComponentB);
|
|
159
|
+
expect(state.isClosing).toStrictEqual(false);
|
|
76
160
|
});
|
|
77
161
|
|
|
78
|
-
it('
|
|
79
|
-
|
|
162
|
+
it('cancels pending close timer when open is called', () => {
|
|
163
|
+
const state = createState({
|
|
164
|
+
isOpen: true,
|
|
165
|
+
component: MockComponentA as any
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
mutations.close(state);
|
|
169
|
+
|
|
170
|
+
expect(state.isOpen).toStrictEqual(false);
|
|
171
|
+
expect(state.isClosing).toStrictEqual(true);
|
|
172
|
+
|
|
173
|
+
mutations.open(state, { component: MockComponentB, componentProps: { title: 'Panel B' } });
|
|
174
|
+
|
|
175
|
+
expect(state.isOpen).toStrictEqual(true);
|
|
176
|
+
expect(state.isClosing).toStrictEqual(false);
|
|
177
|
+
expect(state.component).toStrictEqual(MockComponentB);
|
|
178
|
+
expect(state.componentProps).toStrictEqual({ title: 'Panel B' });
|
|
179
|
+
|
|
80
180
|
jest.advanceTimersByTime(500);
|
|
81
181
|
|
|
82
|
-
expect(
|
|
83
|
-
expect(
|
|
84
|
-
expect(
|
|
182
|
+
expect(state.component).toStrictEqual(MockComponentB);
|
|
183
|
+
expect(state.componentProps).toStrictEqual({ title: 'Panel B' });
|
|
184
|
+
expect(state.isClosing).toStrictEqual(false);
|
|
85
185
|
});
|
|
86
186
|
});
|
|
87
187
|
});
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { state, getters, mutations, actions } from '../ui-context';
|
|
2
|
+
|
|
3
|
+
describe('ui-context store', () => {
|
|
4
|
+
describe('state', () => {
|
|
5
|
+
it('returns initial state with empty elements and zero idCounter', () => {
|
|
6
|
+
expect(state()).toStrictEqual({ idCounter: 0, elements: {} });
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('returns a fresh object on each call', () => {
|
|
10
|
+
const s1 = state();
|
|
11
|
+
const s2 = state();
|
|
12
|
+
|
|
13
|
+
expect(s1).not.toBe(s2);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('getters', () => {
|
|
18
|
+
describe('all', () => {
|
|
19
|
+
it('returns empty array when no elements exist', () => {
|
|
20
|
+
expect(getters.all(state())).toStrictEqual([]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('returns contexts sorted alphabetically by tag', () => {
|
|
24
|
+
const s = state();
|
|
25
|
+
|
|
26
|
+
s.elements = {
|
|
27
|
+
1: { id: 1, context: { tag: 'z-tag', value: 'a' } },
|
|
28
|
+
2: { id: 2, context: { tag: 'a-tag', value: 'b' } },
|
|
29
|
+
3: { id: 3, context: { tag: 'm-tag', value: 'c' } },
|
|
30
|
+
} as any;
|
|
31
|
+
|
|
32
|
+
const result = getters.all(s);
|
|
33
|
+
|
|
34
|
+
expect(result.map((c: any) => c.tag)).toStrictEqual(['a-tag', 'm-tag', 'z-tag']);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('sorts contexts without tag before tagged contexts', () => {
|
|
38
|
+
const s = state();
|
|
39
|
+
|
|
40
|
+
s.elements = {
|
|
41
|
+
1: { id: 1, context: { tag: 'b-tag', value: 'a' } },
|
|
42
|
+
2: { id: 2, context: { value: 'b' } },
|
|
43
|
+
} as any;
|
|
44
|
+
|
|
45
|
+
const result = getters.all(s);
|
|
46
|
+
|
|
47
|
+
expect(result[0]).toStrictEqual({ value: 'b' });
|
|
48
|
+
expect(result[1].tag).toBe('b-tag');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('returns context objects, not full element wrappers', () => {
|
|
52
|
+
const context = { tag: 'my-tag', value: 'test-val' };
|
|
53
|
+
const s = state();
|
|
54
|
+
|
|
55
|
+
s.elements = { 1: { id: 1, context } } as any;
|
|
56
|
+
|
|
57
|
+
expect(getters.all(s)).toStrictEqual([context]);
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('mutations', () => {
|
|
63
|
+
describe('add', () => {
|
|
64
|
+
it('adds element to state', () => {
|
|
65
|
+
const s = state();
|
|
66
|
+
const element = { id: 1, context: { tag: 'test', value: 'val' } };
|
|
67
|
+
|
|
68
|
+
mutations.add(s, element);
|
|
69
|
+
|
|
70
|
+
expect(s.elements[1]).toStrictEqual(element);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('adds multiple elements without overwriting', () => {
|
|
74
|
+
const s = state();
|
|
75
|
+
const e1 = { id: 1, context: { tag: 'a', value: 1 } };
|
|
76
|
+
const e2 = { id: 2, context: { tag: 'b', value: 2 } };
|
|
77
|
+
|
|
78
|
+
mutations.add(s, e1);
|
|
79
|
+
mutations.add(s, e2);
|
|
80
|
+
|
|
81
|
+
expect(Object.keys(s.elements)).toStrictEqual(['1', '2']);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('update', () => {
|
|
86
|
+
it('updates context of existing element', () => {
|
|
87
|
+
const s = state();
|
|
88
|
+
|
|
89
|
+
s.elements[1] = { id: 1, context: { tag: 'test', value: 'original' } };
|
|
90
|
+
mutations.update(s, { id: 1, context: { tag: 'test', value: 'updated' } });
|
|
91
|
+
|
|
92
|
+
expect(s.elements[1].context).toStrictEqual({ tag: 'test', value: 'updated' });
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('does nothing when element id is not found', () => {
|
|
96
|
+
const s = state();
|
|
97
|
+
const element = { id: 1, context: { tag: 'test', value: 'val' } };
|
|
98
|
+
|
|
99
|
+
s.elements[1] = element;
|
|
100
|
+
mutations.update(s, { id: 999, context: { tag: 'other', value: 'new' } });
|
|
101
|
+
|
|
102
|
+
expect(s.elements[1]).toStrictEqual(element);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('remove', () => {
|
|
107
|
+
it('removes element from state by id', () => {
|
|
108
|
+
const s = state();
|
|
109
|
+
const element = { id: 1, context: { tag: 'test', value: 'val' } };
|
|
110
|
+
|
|
111
|
+
s.elements[1] = element;
|
|
112
|
+
mutations.remove(s, element);
|
|
113
|
+
|
|
114
|
+
expect(s.elements[1]).toBeUndefined();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('leaves other elements intact when removing one', () => {
|
|
118
|
+
const s = state();
|
|
119
|
+
const e1 = { id: 1, context: { tag: 'a', value: 1 } };
|
|
120
|
+
const e2 = { id: 2, context: { tag: 'b', value: 2 } };
|
|
121
|
+
|
|
122
|
+
s.elements[1] = e1;
|
|
123
|
+
s.elements[2] = e2;
|
|
124
|
+
mutations.remove(s, e1);
|
|
125
|
+
|
|
126
|
+
expect(s.elements[2]).toStrictEqual(e2);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('actions', () => {
|
|
132
|
+
const makeStore = () => {
|
|
133
|
+
const s = state() as any;
|
|
134
|
+
const commit = jest.fn((mutation: string, payload: any) => {
|
|
135
|
+
(mutations as any)[mutation](s, payload);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return { s, commit };
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
describe('add', () => {
|
|
142
|
+
it.each([
|
|
143
|
+
{
|
|
144
|
+
desc: 'undefined context',
|
|
145
|
+
ctx: undefined as any,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
desc: 'null context',
|
|
149
|
+
ctx: null as any,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
desc: 'missing tag property',
|
|
153
|
+
ctx: { value: 'something' } as any,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
desc: 'empty string tag',
|
|
157
|
+
ctx: { value: 'something', tag: '' } as any,
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
desc: 'missing value property',
|
|
161
|
+
ctx: { tag: 'test' } as any,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
desc: 'explicit undefined value',
|
|
165
|
+
ctx: { tag: 'test', value: undefined } as any,
|
|
166
|
+
},
|
|
167
|
+
])('throws for invalid context: $desc', ({ ctx }) => {
|
|
168
|
+
const { s, commit } = makeStore();
|
|
169
|
+
|
|
170
|
+
expect(() => actions.add({ commit, state: s }, ctx)).toThrow('[ui-context]');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('commits add and returns generated id for valid context', () => {
|
|
174
|
+
const { s, commit } = makeStore();
|
|
175
|
+
const ctx = { tag: 'my-tag', value: 'my-value' };
|
|
176
|
+
|
|
177
|
+
const id = actions.add({ commit, state: s }, ctx);
|
|
178
|
+
|
|
179
|
+
expect(id).toBe('ctx-0');
|
|
180
|
+
expect(commit).toHaveBeenCalledWith('add', { id: 'ctx-0', context: ctx });
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('increments idCounter on successive calls', () => {
|
|
184
|
+
const { s, commit } = makeStore();
|
|
185
|
+
const ctx = { tag: 't', value: 'v' };
|
|
186
|
+
|
|
187
|
+
const id1 = actions.add({ commit, state: s }, ctx);
|
|
188
|
+
const id2 = actions.add({ commit, state: s }, ctx);
|
|
189
|
+
|
|
190
|
+
expect(id1).toBe('ctx-0');
|
|
191
|
+
expect(id2).toBe('ctx-1');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it.each([
|
|
195
|
+
{
|
|
196
|
+
desc: 'null value',
|
|
197
|
+
ctx: { tag: 'test', value: null },
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
desc: 'false value',
|
|
201
|
+
ctx: { tag: 'test', value: false },
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
desc: 'zero value',
|
|
205
|
+
ctx: { tag: 'test', value: 0 },
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
desc: 'empty string value',
|
|
209
|
+
ctx: { tag: 'test', value: '' },
|
|
210
|
+
},
|
|
211
|
+
])('does not throw for valid context with $desc', ({ ctx }) => {
|
|
212
|
+
const { s, commit } = makeStore();
|
|
213
|
+
|
|
214
|
+
expect(() => actions.add({ commit, state: s }, ctx as any)).not.toThrow();
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('update', () => {
|
|
219
|
+
it('throws when element id is not found in state', () => {
|
|
220
|
+
const { s, commit } = makeStore();
|
|
221
|
+
|
|
222
|
+
expect(() => actions.update({ commit, state: s }, { id: 42, context: { tag: 't', value: 'v' } } as any)).toThrow('[ui-context]');
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('commits update for existing element', () => {
|
|
226
|
+
const { s, commit } = makeStore();
|
|
227
|
+
|
|
228
|
+
s.elements[1] = { id: 1, context: { tag: 'test', value: 'original' } };
|
|
229
|
+
const updated = { id: 1, context: { tag: 'test', value: 'updated' } };
|
|
230
|
+
|
|
231
|
+
actions.update({ commit, state: s }, updated as any);
|
|
232
|
+
|
|
233
|
+
expect(commit).toHaveBeenCalledWith('update', updated);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
describe('remove', () => {
|
|
238
|
+
it('throws when element id is not found in state', () => {
|
|
239
|
+
const { s, commit } = makeStore();
|
|
240
|
+
|
|
241
|
+
expect(() => actions.remove({ commit, state: s }, 999 as any)).toThrow('[ui-context]');
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('commits remove with the found element', () => {
|
|
245
|
+
const { s, commit } = makeStore();
|
|
246
|
+
const element = { id: 1, context: { tag: 'test', value: 'val' } };
|
|
247
|
+
|
|
248
|
+
s.elements[1] = element;
|
|
249
|
+
actions.remove({ commit, state: s }, 1 as any);
|
|
250
|
+
|
|
251
|
+
expect(commit).toHaveBeenCalledWith('remove', element);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
});
|