@skyhook-io/k8s-ui 1.8.9 → 1.8.11
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/package.json +5 -4
- package/src/assets/gitops/argocd.png +0 -0
- package/src/assets/gitops/flux.svg +7 -0
- package/src/components/applications/AppChips.tsx +10 -0
- package/src/components/applications/ApplicationDetail.test.tsx +569 -0
- package/src/components/applications/ApplicationDetail.tsx +2517 -450
- package/src/components/applications/ApplicationsList.tsx +4 -1
- package/src/components/applications/ApplicationsView.tsx +101 -18
- package/src/components/applications/applications-view.test.tsx +47 -0
- package/src/components/applications/index.ts +1 -1
- package/src/components/audit/AuditAlerts.tsx +14 -10
- package/src/components/checks/ChecksView.tsx +81 -51
- package/src/components/checks/severity.ts +39 -45
- package/src/components/cluster-switcher/ClusterSwitcher.tsx +2 -2
- package/src/components/compare/CompareResourcePicker.tsx +2 -2
- package/src/components/gitops/GitOpsGraphFilterRail.tsx +2 -1
- package/src/components/gitops/GitOpsTableView.tsx +14 -59
- package/src/components/gitops/ManagedResourcesList.tsx +8 -11
- package/src/components/gitops/SyncCountdown.tsx +26 -17
- package/src/components/gitops/SyncOptionsDialog.tsx +2 -2
- package/src/components/gitops/insights/GitOpsInsightViews.tsx +277 -104
- package/src/components/gitops/insights/insights-helpers.test.ts +86 -1
- package/src/components/gitops/insights/insights-helpers.ts +69 -1
- package/src/components/gitops/tree/GitOpsTreeGraph.tsx +5 -2
- package/src/components/issues/IssuesView.tsx +219 -143
- package/src/components/issues/ResourceIssuesSection.test.tsx +40 -0
- package/src/components/issues/ResourceIssuesSection.tsx +28 -128
- package/src/components/issues/index.ts +3 -2
- package/src/components/issues/issue-timing.ts +53 -0
- package/src/components/issues/issues.test.ts +146 -4
- package/src/components/issues/severity.ts +29 -32
- package/src/components/logs/LogCore.tsx +14 -4
- package/src/components/logs/WorkloadLogsViewer.tsx +31 -6
- package/src/components/logs/useLogStream.ts +8 -1
- package/src/components/namespace-switcher/NamespacePicker.tsx +86 -107
- package/src/components/resources/ResourcesSidebar.test.tsx +107 -0
- package/src/components/resources/ResourcesSidebar.tsx +70 -52
- package/src/components/resources/ResourcesView.selected-kind.test.ts +143 -0
- package/src/components/resources/ResourcesView.tsx +243 -53
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +99 -0
- package/src/components/resources/renderers/JobRenderer.tsx +3 -1
- package/src/components/resources/renderers/MetricsUnavailableNotice.tsx +47 -0
- package/src/components/resources/renderers/NodeRenderer.test.tsx +103 -0
- package/src/components/resources/renderers/NodeRenderer.tsx +20 -12
- package/src/components/resources/renderers/PVCRenderer.test.tsx +23 -0
- package/src/components/resources/renderers/PVCRenderer.tsx +2 -2
- package/src/components/resources/renderers/PodRenderer.test.tsx +101 -0
- package/src/components/resources/renderers/PodRenderer.tsx +81 -70
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +2 -2
- package/src/components/resources/renderers/SbomReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/SecretRenderer.tsx +2 -0
- package/src/components/resources/renderers/ServiceRenderer.tsx +58 -40
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +2 -2
- package/src/components/resources/renderers/WorkflowRenderer.test.tsx +43 -0
- package/src/components/resources/renderers/WorkflowRenderer.tsx +60 -78
- package/src/components/resources/renderers/index.ts +1 -0
- package/src/components/resources/resource-utils-capi.ts +1 -1
- package/src/components/resources/resource-utils.ts +31 -0
- package/src/components/shared/DetailShell.tsx +2 -2
- package/src/components/shared/ResourceActionsBar.tsx +140 -108
- package/src/components/shared/ResourceRendererDispatch.tsx +33 -4
- package/src/components/timeline/TimelineList.tsx +371 -354
- package/src/components/timeline/TimelineStrip.test.tsx +194 -0
- package/src/components/timeline/TimelineStrip.tsx +836 -0
- package/src/components/timeline/TimelineSwimlanes.test.tsx +1028 -0
- package/src/components/timeline/TimelineSwimlanes.tsx +2498 -593
- package/src/components/timeline/TimelineToolbar.test.tsx +320 -0
- package/src/components/timeline/TimelineToolbar.tsx +759 -0
- package/src/components/timeline/TimelineToolbarState.test.tsx +64 -0
- package/src/components/timeline/index.ts +23 -0
- package/src/components/timeline/scrubber-math.test.ts +235 -0
- package/src/components/timeline/scrubber-math.ts +249 -0
- package/src/components/timeline/shared.test.tsx +219 -0
- package/src/components/timeline/shared.tsx +232 -447
- package/src/components/timeline/timeline-filters.test.ts +196 -0
- package/src/components/timeline/timeline-filters.ts +180 -0
- package/src/components/timeline/timeline-lane-sort.test.ts +97 -0
- package/src/components/timeline/timeline-lane-sort.ts +77 -0
- package/src/components/timeline/timeline-live.test.ts +131 -0
- package/src/components/timeline/timeline-live.ts +77 -0
- package/src/components/topology/GroupNode.tsx +9 -2
- package/src/components/topology/K8sResourceNode.test.ts +11 -0
- package/src/components/topology/K8sResourceNode.tsx +387 -243
- package/src/components/topology/TopologyFilterSidebar.tsx +9 -0
- package/src/components/topology/TopologyGraph.tsx +89 -16
- package/src/components/topology/TopologySearch.tsx +2 -2
- package/src/components/topology/layout.ts +10 -2
- package/src/components/topology/topology.css +0 -92
- package/src/components/ui/Badge.tsx +5 -0
- package/src/components/ui/BoardSkeleton.tsx +47 -0
- package/src/components/ui/CardSection.tsx +117 -0
- package/src/components/ui/CodeViewer.tsx +2 -2
- package/src/components/ui/Collapse.tsx +59 -0
- package/src/components/ui/ConfirmDialog.tsx +7 -3
- package/src/components/ui/DialogPortal.tsx +31 -9
- package/src/components/ui/Input.tsx +24 -0
- package/src/components/ui/MultiSelectPicker.test.tsx +101 -0
- package/src/components/ui/MultiSelectPicker.tsx +167 -0
- package/src/components/ui/PaneLoader.tsx +10 -2
- package/src/components/ui/ResourceBar.tsx +68 -25
- package/src/components/ui/SearchBox.tsx +18 -4
- package/src/components/ui/SearchPillInput.tsx +2 -2
- package/src/components/ui/Toast.tsx +1 -1
- package/src/components/ui/drawer-components.tsx +39 -26
- package/src/components/ui/index.ts +6 -0
- package/src/components/ui/severity-tone.ts +63 -0
- package/src/components/workload/ResourceDetailDrawer.tsx +6 -2
- package/src/components/workload/WorkloadView.tsx +2682 -472
- package/src/components/workload/index.ts +1 -1
- package/src/theme/variables.css +15 -1
- package/src/types/core.ts +57 -4
- package/src/utils/api-resources.test.ts +54 -0
- package/src/utils/api-resources.ts +68 -1
- package/src/utils/application-topology.test.ts +198 -0
- package/src/utils/application-topology.ts +261 -0
- package/src/utils/applications.test.ts +167 -1
- package/src/utils/applications.ts +1157 -393
- package/src/utils/index.ts +2 -0
- package/src/utils/navigation.test.ts +32 -1
- package/src/utils/navigation.ts +70 -0
- package/src/utils/resource-hierarchy.test.ts +990 -1
- package/src/utils/resource-hierarchy.ts +905 -267
- package/src/utils/resource-icons.ts +2 -0
- package/src/utils/topology-neighborhood.test.ts +202 -1
- package/src/utils/topology-neighborhood.ts +194 -13
- package/src/utils/workflow-execution.test.ts +556 -0
- package/src/utils/workflow-execution.ts +814 -0
- package/src/utils/workload-colors.ts +2 -3
- package/tsconfig.json +2 -2
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { renderToString } from 'react-dom/server'
|
|
3
|
+
import type { TimelineEvent } from '../../types'
|
|
4
|
+
import type { ActivityFilterKey, ActivitySelection } from './timeline-filters'
|
|
5
|
+
import { activityKeysToSelection, selectionToActivityKeys } from './timeline-filters'
|
|
6
|
+
import { TimelineToolbar, ViewMenu, DeletedEventsToggle, PinnedOnlyToggle } from './TimelineToolbar'
|
|
7
|
+
|
|
8
|
+
function ev(partial: Partial<TimelineEvent>): TimelineEvent {
|
|
9
|
+
return {
|
|
10
|
+
id: Math.random().toString(36),
|
|
11
|
+
timestamp: '2026-01-01T00:00:00Z',
|
|
12
|
+
source: 'informer',
|
|
13
|
+
kind: 'Pod',
|
|
14
|
+
namespace: 'default',
|
|
15
|
+
name: 'thing',
|
|
16
|
+
eventType: 'update',
|
|
17
|
+
...partial,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const EVENTS: TimelineEvent[] = [
|
|
22
|
+
ev({ source: 'informer', eventType: 'add' }),
|
|
23
|
+
ev({ source: 'informer', eventType: 'update', healthState: 'unhealthy' }),
|
|
24
|
+
ev({ source: 'informer', eventType: 'delete' }),
|
|
25
|
+
ev({ source: 'k8s_event', eventType: 'Warning', reason: 'BackOff' }),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
const baseProps = {
|
|
29
|
+
search: '',
|
|
30
|
+
onSearchChange: () => {},
|
|
31
|
+
searchShortcutId: 'test-search',
|
|
32
|
+
activityFilter: [] as ActivityFilterKey[],
|
|
33
|
+
onActivityFilterChange: () => {},
|
|
34
|
+
showDeleted: true,
|
|
35
|
+
onShowDeletedChange: () => {},
|
|
36
|
+
kindFilter: [] as string[],
|
|
37
|
+
onKindFilterChange: () => {},
|
|
38
|
+
kindOptions: ['Deployment', 'Pod'],
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('TimelineToolbar SSR', () => {
|
|
42
|
+
it('renders the two-axis activity control: source radiogroup + Problems toggle', () => {
|
|
43
|
+
const html = renderToString(<TimelineToolbar {...baseProps} events={EVENTS} />)
|
|
44
|
+
expect(html).toContain('aria-label="Activity source"')
|
|
45
|
+
expect(html).toContain('>All<')
|
|
46
|
+
expect(html).toContain('>Changes<')
|
|
47
|
+
expect(html).toContain('>K8s Events<')
|
|
48
|
+
expect(html).toContain('>Problems<')
|
|
49
|
+
// The old severity pills are gone — they were subsets posing as peers.
|
|
50
|
+
expect(html).not.toContain('>Warnings<')
|
|
51
|
+
expect(html).not.toContain('>Unhealthy<')
|
|
52
|
+
expect(html).toContain('>3<') // changes count
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('shows per-source counts and a source-scoped Problems count', () => {
|
|
56
|
+
const html = renderToString(
|
|
57
|
+
<TimelineToolbar
|
|
58
|
+
{...baseProps}
|
|
59
|
+
stats={{ total: 99, changes: 42, k8sEvents: 57, warnings: 7, unhealthy: 5, deleted: 3 }}
|
|
60
|
+
/>,
|
|
61
|
+
)
|
|
62
|
+
expect(html).toContain('>99<')
|
|
63
|
+
expect(html).toContain('>42<')
|
|
64
|
+
expect(html).toContain('>57<')
|
|
65
|
+
// Source = All → Problems previews warnings + unhealthy.
|
|
66
|
+
expect(html).toContain('>12<')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('scopes the Problems count to the picked source', () => {
|
|
70
|
+
const stats = { total: 99, changes: 42, k8sEvents: 57, warnings: 7, unhealthy: 5, deleted: 3 }
|
|
71
|
+
const changesOnly = renderToString(
|
|
72
|
+
<TimelineToolbar {...baseProps} stats={stats} activityFilter={['changes']} />,
|
|
73
|
+
)
|
|
74
|
+
expect(changesOnly).toContain('>5<') // unhealthy slice
|
|
75
|
+
const eventsOnly = renderToString(
|
|
76
|
+
<TimelineToolbar {...baseProps} stats={stats} activityFilter={['k8s_events']} />,
|
|
77
|
+
)
|
|
78
|
+
expect(eventsOnly).toContain('>7<') // warnings slice
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('reads legacy key sets back into the two axes (deep-link compat)', () => {
|
|
82
|
+
// ['warnings'] = problems within K8s Events: that segment is checked and
|
|
83
|
+
// the Problems toggle is pressed.
|
|
84
|
+
const html = renderToString(
|
|
85
|
+
<TimelineToolbar {...baseProps} events={EVENTS} activityFilter={['warnings']} />,
|
|
86
|
+
)
|
|
87
|
+
expect(html).toContain('aria-checked="true"')
|
|
88
|
+
expect(html).toContain('aria-pressed="true"')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('renders the amber dot on the Problems toggle only (no per-pill colors)', () => {
|
|
92
|
+
const html = renderToString(<TimelineToolbar {...baseProps} events={EVENTS} />)
|
|
93
|
+
expect(html).toContain('bg-amber-500')
|
|
94
|
+
expect(html).not.toContain('bg-blue-500')
|
|
95
|
+
expect(html).not.toContain('bg-rose-500')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('renders ONE View trigger (Sort + Group live in its popover), swimlane only', () => {
|
|
99
|
+
const withOpts = renderToString(
|
|
100
|
+
<TimelineToolbar
|
|
101
|
+
{...baseProps}
|
|
102
|
+
events={EVENTS}
|
|
103
|
+
view="swimlane"
|
|
104
|
+
onViewChange={() => {}}
|
|
105
|
+
viewOptions={{
|
|
106
|
+
sort: { value: 'importance', onChange: () => {} },
|
|
107
|
+
grouping: { value: 'app', onChange: () => {} },
|
|
108
|
+
}}
|
|
109
|
+
/>,
|
|
110
|
+
)
|
|
111
|
+
// The trigger renders; the popover is closed on the server, so the Sort /
|
|
112
|
+
// Group rows are not in the initial markup.
|
|
113
|
+
expect(withOpts).toContain('>View<')
|
|
114
|
+
expect(withOpts).not.toContain('role="radiogroup" aria-label="Lane sort"')
|
|
115
|
+
// The view toggle is labeled — the words themselves are the affordance.
|
|
116
|
+
expect(withOpts).toContain('>List<')
|
|
117
|
+
expect(withOpts).toContain('>Timeline<')
|
|
118
|
+
|
|
119
|
+
// List view (TimelineList) passes no viewOptions — no View trigger.
|
|
120
|
+
const noOpts = renderToString(
|
|
121
|
+
<TimelineToolbar {...baseProps} events={EVENTS} view="list" onViewChange={() => {}} />,
|
|
122
|
+
)
|
|
123
|
+
expect(noOpts).not.toContain('>View<')
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('dims a zero-count chip in place instead of hiding it (position memory)', () => {
|
|
127
|
+
// EVENTS has 3 changes and 1 warning K8s event; craft stats with zeros.
|
|
128
|
+
const html = renderToString(
|
|
129
|
+
<TimelineToolbar
|
|
130
|
+
{...baseProps}
|
|
131
|
+
stats={{ total: 5, changes: 0, k8sEvents: 5, warnings: 0, unhealthy: 0, deleted: 0 }}
|
|
132
|
+
/>,
|
|
133
|
+
)
|
|
134
|
+
// Changes 0 and Problems 0 dim + disable; K8s Events stays interactive.
|
|
135
|
+
expect(html).toContain('opacity-45')
|
|
136
|
+
expect(html.match(/opacity-45/g)?.length).toBe(2)
|
|
137
|
+
expect(html.match(/disabled=""/g)?.length).toBe(2)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('renders the Kinds chip with its own badge = selected kinds, hidden when none', () => {
|
|
141
|
+
const none = renderToString(<TimelineToolbar {...baseProps} events={EVENTS} />)
|
|
142
|
+
// Chip always renders its label; badge hidden with no selection.
|
|
143
|
+
expect(none).toContain('>Kinds<')
|
|
144
|
+
expect(none).not.toContain('bg-accent px-1.5')
|
|
145
|
+
|
|
146
|
+
const two = renderToString(
|
|
147
|
+
<TimelineToolbar {...baseProps} events={EVENTS} kindFilter={['Pod', 'Service']} />,
|
|
148
|
+
)
|
|
149
|
+
expect(two).toContain('bg-accent px-1.5')
|
|
150
|
+
expect(two).toContain('>2<')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
it('keeps Show-deleted (View menu) and the kinds picker (Kinds chip) out of the bar on SSR', () => {
|
|
154
|
+
const html = renderToString(
|
|
155
|
+
<TimelineToolbar {...baseProps} events={EVENTS} showDeleted={false} kindFilter={['Pod']} />,
|
|
156
|
+
)
|
|
157
|
+
// Both popovers are closed on the server: the Kinds chip is present, but its
|
|
158
|
+
// picker body and the View menu's Show-deleted switch are not yet in the DOM.
|
|
159
|
+
expect(html).toContain('>Kinds<')
|
|
160
|
+
expect(html).not.toContain('Filter kinds')
|
|
161
|
+
expect(html).not.toContain('Show deleted')
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('renders the search always open with a static width (no collapse)', () => {
|
|
165
|
+
const html = renderToString(<TimelineToolbar {...baseProps} events={EVENTS} />)
|
|
166
|
+
// Always a real text input, even when empty — the collapsed magnifier button
|
|
167
|
+
// is retired for this toolbar.
|
|
168
|
+
expect(html).toContain('placeholder="Search..."')
|
|
169
|
+
expect(html).not.toContain('(press /)')
|
|
170
|
+
expect(html).toContain('value=""')
|
|
171
|
+
expect(html).not.toContain('aria-label="Search"')
|
|
172
|
+
// Compact static width — focus/typing can never resize it.
|
|
173
|
+
expect(html).toContain('w-44')
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('reflects the controlled search value', () => {
|
|
177
|
+
const html = renderToString(
|
|
178
|
+
<TimelineToolbar {...baseProps} events={EVENTS} search="checkout-api" />,
|
|
179
|
+
)
|
|
180
|
+
expect(html).toContain('value="checkout-api"')
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('places the search first in the filters row, before the activity segments', () => {
|
|
184
|
+
const html = renderToString(<TimelineToolbar {...baseProps} events={EVENTS} />)
|
|
185
|
+
// Founder-locked order: search leads the filters row.
|
|
186
|
+
const searchIdx = html.indexOf('placeholder="Search..."')
|
|
187
|
+
expect(searchIdx).toBeGreaterThan(-1)
|
|
188
|
+
expect(searchIdx).toBeLessThan(html.indexOf('>All<'))
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
it('renders the range dropdown only when range props are supplied', () => {
|
|
192
|
+
const withRange = renderToString(
|
|
193
|
+
<TimelineToolbar
|
|
194
|
+
{...baseProps}
|
|
195
|
+
events={EVENTS}
|
|
196
|
+
rangeOptions={[{ value: '1h', label: '1 hour' }, { value: '24h', label: '24 hours' }]}
|
|
197
|
+
timeRange="1h"
|
|
198
|
+
onTimeRangeChange={() => {}}
|
|
199
|
+
/>,
|
|
200
|
+
)
|
|
201
|
+
expect(withRange).toContain('1 hour')
|
|
202
|
+
expect(withRange).toContain('24 hours')
|
|
203
|
+
|
|
204
|
+
const withoutRange = renderToString(<TimelineToolbar {...baseProps} events={EVENTS} />)
|
|
205
|
+
expect(withoutRange).not.toContain('24 hours')
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
it('renders swimlane-style counts (resources · events) and list-style (events only)', () => {
|
|
209
|
+
const swim = renderToString(
|
|
210
|
+
<TimelineToolbar {...baseProps} events={EVENTS} counts={{ resources: 4, events: 12 }} />,
|
|
211
|
+
)
|
|
212
|
+
expect(swim).toContain('4 resources · ')
|
|
213
|
+
expect(swim).toContain('12 events')
|
|
214
|
+
|
|
215
|
+
const list = renderToString(
|
|
216
|
+
<TimelineToolbar {...baseProps} events={EVENTS} counts={{ events: 12 }} />,
|
|
217
|
+
)
|
|
218
|
+
expect(list).toContain('12 events')
|
|
219
|
+
expect(list).not.toContain('resources · ')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('renders refresh only when onRefresh is given', () => {
|
|
223
|
+
const withRefresh = renderToString(
|
|
224
|
+
<TimelineToolbar {...baseProps} events={EVENTS} onRefresh={() => {}} />,
|
|
225
|
+
)
|
|
226
|
+
expect(withRefresh).toContain('aria-label="Refresh"')
|
|
227
|
+
const noRefresh = renderToString(<TimelineToolbar {...baseProps} events={EVENTS} />)
|
|
228
|
+
expect(noRefresh).not.toContain('aria-label="Refresh"')
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('normalizes an unmappable legacy multi-select to the widest reading (All, problems off)', () => {
|
|
232
|
+
const html = renderToString(
|
|
233
|
+
<TimelineToolbar {...baseProps} events={EVENTS} activityFilter={['changes', 'warnings']} />,
|
|
234
|
+
)
|
|
235
|
+
// Showing more than a stale link intended beats silently hiding activity —
|
|
236
|
+
// and the Problems toggle stays unpressed.
|
|
237
|
+
expect(html).toContain('aria-checked="true"')
|
|
238
|
+
expect(html).not.toContain('aria-pressed="true"')
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
// The live/paused chip moved out of the toolbar into the scrubber header — the
|
|
242
|
+
// toolbar must never render it, whatever props flow through.
|
|
243
|
+
it('never renders the live/paused chip', () => {
|
|
244
|
+
const html = renderToString(<TimelineToolbar {...baseProps} events={EVENTS} />)
|
|
245
|
+
expect(html).not.toContain('timeline-live-chip')
|
|
246
|
+
expect(html).not.toContain('timeline-paused-chip')
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
describe('ViewMenu SSR', () => {
|
|
251
|
+
it('renders the View trigger with the popover closed', () => {
|
|
252
|
+
const html = renderToString(
|
|
253
|
+
<ViewMenu
|
|
254
|
+
viewOptions={{
|
|
255
|
+
sort: { value: 'recent', onChange: () => {} },
|
|
256
|
+
grouping: { value: 'app', onChange: () => {} },
|
|
257
|
+
}}
|
|
258
|
+
/>,
|
|
259
|
+
)
|
|
260
|
+
expect(html).toContain('>View<')
|
|
261
|
+
expect(html).toContain('aria-haspopup="menu"')
|
|
262
|
+
expect(html).toContain('aria-expanded="false"')
|
|
263
|
+
// Closed on the server: the Sort/Group radio rows are not in the markup.
|
|
264
|
+
expect(html).not.toContain('role="radio"')
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('DeletedEventsToggle: labeled "Deleted", marked "Deleted hidden" when hiding', () => {
|
|
268
|
+
const shown = renderToString(<DeletedEventsToggle showDeleted={true} onChange={() => {}} />)
|
|
269
|
+
expect(shown).toContain('aria-pressed="false"')
|
|
270
|
+
expect(shown).toContain('Hide delete events')
|
|
271
|
+
expect(shown).toContain('>Deleted<')
|
|
272
|
+
expect(shown).not.toContain('Deleted hidden')
|
|
273
|
+
const hiding = renderToString(<DeletedEventsToggle showDeleted={false} onChange={() => {}} />)
|
|
274
|
+
expect(hiding).toContain('aria-pressed="true"')
|
|
275
|
+
expect(hiding).toContain('Show delete events')
|
|
276
|
+
expect(hiding).toContain('Deleted hidden')
|
|
277
|
+
})
|
|
278
|
+
})
|
|
279
|
+
|
|
280
|
+
describe('activity two-axis mapping (source × problems ↔ ActivityFilterKey[])', () => {
|
|
281
|
+
const CANONICAL: [ActivitySelection, ActivityFilterKey[]][] = [
|
|
282
|
+
[{ source: 'all', problemsOnly: false }, []],
|
|
283
|
+
[{ source: 'changes', problemsOnly: false }, ['changes']],
|
|
284
|
+
[{ source: 'k8s_events', problemsOnly: false }, ['k8s_events']],
|
|
285
|
+
[{ source: 'changes', problemsOnly: true }, ['unhealthy']],
|
|
286
|
+
[{ source: 'k8s_events', problemsOnly: true }, ['warnings']],
|
|
287
|
+
[{ source: 'all', problemsOnly: true }, ['warnings', 'unhealthy']],
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
it('round-trips all six reachable states through the shared key vocabulary', () => {
|
|
291
|
+
for (const [sel, keys] of CANONICAL) {
|
|
292
|
+
expect(selectionToActivityKeys(sel)).toEqual(keys)
|
|
293
|
+
expect(activityKeysToSelection(keys)).toEqual(sel)
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('falls back to All/off for legacy multi-select key sets', () => {
|
|
298
|
+
expect(activityKeysToSelection(['changes', 'warnings'])).toEqual({ source: 'all', problemsOnly: false })
|
|
299
|
+
expect(activityKeysToSelection(['changes', 'k8s_events'])).toEqual({ source: 'all', problemsOnly: false })
|
|
300
|
+
})
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
describe('PinnedOnlyToggle', () => {
|
|
304
|
+
it('is hidden from the toolbar when nothing is pinned', () => {
|
|
305
|
+
const html = renderToString(<TimelineToolbar {...baseProps} events={EVENTS} pinnedCount={0} pinnedOnly={false} onPinnedOnlyChange={() => {}} />)
|
|
306
|
+
expect(html).not.toContain('Show pinned rows only')
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it('renders in the toolbar once rows are pinned, quiet by default', () => {
|
|
310
|
+
const html = renderToString(<TimelineToolbar {...baseProps} events={EVENTS} pinnedCount={2} pinnedOnly={false} onPinnedOnlyChange={() => {}} />)
|
|
311
|
+
expect(html).toContain('Show pinned rows only')
|
|
312
|
+
expect(html).toContain('aria-pressed="false"')
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
it('shows the engaged treatment when active', () => {
|
|
316
|
+
const html = renderToString(<PinnedOnlyToggle pinnedOnly={true} pinnedCount={2} onChange={() => {}} />)
|
|
317
|
+
expect(html).toContain('aria-pressed="true"')
|
|
318
|
+
expect(html).toContain('only')
|
|
319
|
+
})
|
|
320
|
+
})
|