@mdxui/terminal 2.0.0
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/README.md +571 -0
- package/dist/ansi-css-Sk5mWtdK.d.ts +119 -0
- package/dist/ansi-css-V6JIHGsM.d.ts +119 -0
- package/dist/ansi-css-_3eSEU9d.d.ts +119 -0
- package/dist/chunk-3EFDH7PK.js +5235 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-3X5IR6WE.js +884 -0
- package/dist/chunk-4FV5ZDCE.js +5236 -0
- package/dist/chunk-4OVMSF2J.js +243 -0
- package/dist/chunk-63FEETIS.js +4048 -0
- package/dist/chunk-B43KP7XJ.js +884 -0
- package/dist/chunk-BMTJXWUV.js +655 -0
- package/dist/chunk-C3SVH4N7.js +882 -0
- package/dist/chunk-EVWR7Y47.js +874 -0
- package/dist/chunk-F6A5VWUC.js +1285 -0
- package/dist/chunk-FD7KW7GE.js +882 -0
- package/dist/chunk-GBQ6UD6I.js +655 -0
- package/dist/chunk-GMDD3M6U.js +5227 -0
- package/dist/chunk-JBHRXOXM.js +1058 -0
- package/dist/chunk-JFOO3EYO.js +1182 -0
- package/dist/chunk-JQ5H3WXL.js +1291 -0
- package/dist/chunk-JQD5NASE.js +234 -0
- package/dist/chunk-KRHJP5R7.js +592 -0
- package/dist/chunk-KWF6WVJE.js +962 -0
- package/dist/chunk-LHYQVN3H.js +1038 -0
- package/dist/chunk-M3TLQLGC.js +1032 -0
- package/dist/chunk-MVW4Q5OP.js +240 -0
- package/dist/chunk-NXCZSWLU.js +1294 -0
- package/dist/chunk-O25TNRO6.js +607 -0
- package/dist/chunk-PNECDA2I.js +884 -0
- package/dist/chunk-QIHWRLJR.js +962 -0
- package/dist/chunk-QW5YMQ7K.js +882 -0
- package/dist/chunk-R5U7XKVJ.js +16 -0
- package/dist/chunk-RP2MVQLR.js +962 -0
- package/dist/chunk-TP6RXGXA.js +1087 -0
- package/dist/chunk-TQQSTITZ.js +655 -0
- package/dist/chunk-X24GWXQV.js +1281 -0
- package/dist/components/index.d.ts +802 -0
- package/dist/components/index.js +149 -0
- package/dist/data/index.d.ts +2554 -0
- package/dist/data/index.js +51 -0
- package/dist/forms/index.d.ts +1596 -0
- package/dist/forms/index.js +464 -0
- package/dist/index-CQRFZntR.d.ts +867 -0
- package/dist/index.d.ts +579 -0
- package/dist/index.js +786 -0
- package/dist/interactive-D0JkWosD.d.ts +217 -0
- package/dist/keyboard/index.d.ts +2 -0
- package/dist/keyboard/index.js +43 -0
- package/dist/renderers/index.d.ts +546 -0
- package/dist/renderers/index.js +2157 -0
- package/dist/storybook/index.d.ts +396 -0
- package/dist/storybook/index.js +641 -0
- package/dist/theme/index.d.ts +1339 -0
- package/dist/theme/index.js +123 -0
- package/dist/types-Bxu5PAgA.d.ts +710 -0
- package/dist/types-CIlop5Ji.d.ts +701 -0
- package/dist/types-Ca8p_p5X.d.ts +710 -0
- package/package.json +90 -0
- package/src/__tests__/components/data/card.test.ts +458 -0
- package/src/__tests__/components/data/list.test.ts +473 -0
- package/src/__tests__/components/data/metrics.test.ts +541 -0
- package/src/__tests__/components/data/table.test.ts +448 -0
- package/src/__tests__/components/input/field.test.ts +555 -0
- package/src/__tests__/components/input/form.test.ts +870 -0
- package/src/__tests__/components/input/search.test.ts +1238 -0
- package/src/__tests__/components/input/select.test.ts +658 -0
- package/src/__tests__/components/navigation/breadcrumb.test.ts +923 -0
- package/src/__tests__/components/navigation/command-palette.test.ts +1095 -0
- package/src/__tests__/components/navigation/sidebar.test.ts +1018 -0
- package/src/__tests__/components/navigation/tabs.test.ts +995 -0
- package/src/__tests__/components.test.tsx +1197 -0
- package/src/__tests__/core/compiler.test.ts +986 -0
- package/src/__tests__/core/parser.test.ts +785 -0
- package/src/__tests__/core/tier-switcher.test.ts +1103 -0
- package/src/__tests__/core/types.test.ts +1398 -0
- package/src/__tests__/data/collections.test.ts +1337 -0
- package/src/__tests__/data/db.test.ts +1265 -0
- package/src/__tests__/data/reactive.test.ts +1010 -0
- package/src/__tests__/data/sync.test.ts +1614 -0
- package/src/__tests__/errors.test.ts +660 -0
- package/src/__tests__/forms/integration.test.ts +444 -0
- package/src/__tests__/integration.test.ts +905 -0
- package/src/__tests__/keyboard.test.ts +1791 -0
- package/src/__tests__/renderer.test.ts +489 -0
- package/src/__tests__/renderers/ansi-css.test.ts +948 -0
- package/src/__tests__/renderers/ansi.test.ts +1366 -0
- package/src/__tests__/renderers/ascii.test.ts +1360 -0
- package/src/__tests__/renderers/interactive.test.ts +2353 -0
- package/src/__tests__/renderers/markdown.test.ts +1483 -0
- package/src/__tests__/renderers/text.test.ts +1369 -0
- package/src/__tests__/renderers/unicode.test.ts +1307 -0
- package/src/__tests__/theme.test.ts +639 -0
- package/src/__tests__/utils/assertions.ts +685 -0
- package/src/__tests__/utils/index.ts +115 -0
- package/src/__tests__/utils/test-renderer.ts +381 -0
- package/src/__tests__/utils/utils.test.ts +560 -0
- package/src/components/containers/card.ts +56 -0
- package/src/components/containers/dialog.ts +53 -0
- package/src/components/containers/index.ts +9 -0
- package/src/components/containers/panel.ts +59 -0
- package/src/components/feedback/badge.ts +40 -0
- package/src/components/feedback/index.ts +8 -0
- package/src/components/feedback/spinner.ts +23 -0
- package/src/components/helpers.ts +81 -0
- package/src/components/index.ts +153 -0
- package/src/components/layout/breadcrumb.ts +31 -0
- package/src/components/layout/index.ts +10 -0
- package/src/components/layout/list.ts +29 -0
- package/src/components/layout/sidebar.ts +79 -0
- package/src/components/layout/table.ts +62 -0
- package/src/components/primitives/box.ts +95 -0
- package/src/components/primitives/button.ts +54 -0
- package/src/components/primitives/index.ts +11 -0
- package/src/components/primitives/input.ts +88 -0
- package/src/components/primitives/select.ts +97 -0
- package/src/components/primitives/text.ts +60 -0
- package/src/components/render.ts +155 -0
- package/src/components/templates/app.ts +43 -0
- package/src/components/templates/index.ts +8 -0
- package/src/components/templates/site.ts +54 -0
- package/src/components/types.ts +777 -0
- package/src/core/compiler.ts +718 -0
- package/src/core/parser.ts +127 -0
- package/src/core/tier-switcher.ts +607 -0
- package/src/core/types.ts +672 -0
- package/src/data/collection.ts +316 -0
- package/src/data/collections.ts +50 -0
- package/src/data/context.tsx +174 -0
- package/src/data/db.ts +127 -0
- package/src/data/hooks.ts +532 -0
- package/src/data/index.ts +138 -0
- package/src/data/reactive.ts +1225 -0
- package/src/data/saas-collections.ts +375 -0
- package/src/data/sync.ts +1213 -0
- package/src/data/types.ts +660 -0
- package/src/forms/converters.ts +512 -0
- package/src/forms/index.ts +133 -0
- package/src/forms/schemas.ts +403 -0
- package/src/forms/types.ts +476 -0
- package/src/index.ts +542 -0
- package/src/keyboard/focus.ts +748 -0
- package/src/keyboard/index.ts +96 -0
- package/src/keyboard/integration.ts +371 -0
- package/src/keyboard/manager.ts +377 -0
- package/src/keyboard/presets.ts +90 -0
- package/src/renderers/ansi-css.ts +576 -0
- package/src/renderers/ansi.ts +802 -0
- package/src/renderers/ascii.ts +680 -0
- package/src/renderers/breadcrumb.ts +480 -0
- package/src/renderers/command-palette.ts +802 -0
- package/src/renderers/components/field.ts +210 -0
- package/src/renderers/components/form.ts +327 -0
- package/src/renderers/components/index.ts +21 -0
- package/src/renderers/components/search.ts +449 -0
- package/src/renderers/components/select.ts +222 -0
- package/src/renderers/index.ts +101 -0
- package/src/renderers/interactive/component-handlers.ts +622 -0
- package/src/renderers/interactive/cursor-manager.ts +147 -0
- package/src/renderers/interactive/focus-manager.ts +279 -0
- package/src/renderers/interactive/index.ts +661 -0
- package/src/renderers/interactive/input-handler.ts +164 -0
- package/src/renderers/interactive/keyboard-handler.ts +212 -0
- package/src/renderers/interactive/mouse-handler.ts +167 -0
- package/src/renderers/interactive/state-manager.ts +109 -0
- package/src/renderers/interactive/types.ts +338 -0
- package/src/renderers/interactive-string.ts +299 -0
- package/src/renderers/interactive.ts +59 -0
- package/src/renderers/markdown.ts +950 -0
- package/src/renderers/sidebar.ts +549 -0
- package/src/renderers/tabs.ts +682 -0
- package/src/renderers/text.ts +791 -0
- package/src/renderers/unicode.ts +917 -0
- package/src/renderers/utils.ts +942 -0
- package/src/router/adapters.ts +383 -0
- package/src/router/types.ts +140 -0
- package/src/router/utils.ts +452 -0
- package/src/schemas.ts +205 -0
- package/src/storybook/index.ts +91 -0
- package/src/storybook/interactive-decorator.tsx +659 -0
- package/src/storybook/keyboard-simulator.ts +501 -0
- package/src/theme/ansi-codes.ts +80 -0
- package/src/theme/box-drawing.ts +132 -0
- package/src/theme/color-convert.ts +254 -0
- package/src/theme/color-support.ts +321 -0
- package/src/theme/index.ts +134 -0
- package/src/theme/strip-ansi.ts +50 -0
- package/src/theme/tailwind-map.ts +469 -0
- package/src/theme/text-styles.ts +206 -0
- package/src/theme/theme-system.ts +568 -0
- package/src/types.ts +103 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mdxui/terminal Metrics Component Tests (RED phase)
|
|
3
|
+
*
|
|
4
|
+
* TDD RED Phase: These tests define the contract for the Metrics data component
|
|
5
|
+
* across all 6 render tiers (TEXT, MARKDOWN, ASCII, UNICODE, ANSI, INTERACTIVE).
|
|
6
|
+
*
|
|
7
|
+
* Metrics Component Features:
|
|
8
|
+
* - Value display (number, currency, percentage)
|
|
9
|
+
* - Label/title
|
|
10
|
+
* - Trend indicator (up, down, neutral)
|
|
11
|
+
* - Sparkline mini chart
|
|
12
|
+
* - Comparison values (previous, target)
|
|
13
|
+
* - Status variants (success, warning, error, info)
|
|
14
|
+
*
|
|
15
|
+
* NOTE: These tests are expected to FAIL until implementation is complete.
|
|
16
|
+
* Run: pnpm --filter @mdxui/terminal test -- --run src/__tests__/components/data/metrics.test.ts
|
|
17
|
+
*/
|
|
18
|
+
import { describe, it, expect } from 'vitest'
|
|
19
|
+
|
|
20
|
+
import type { UINode, RenderContext, ThemeTokens, RenderTier } from '../../../core/types'
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Test Helpers
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
const ALL_TIERS: RenderTier[] = ['text', 'markdown', 'ascii', 'unicode', 'ansi', 'interactive']
|
|
27
|
+
|
|
28
|
+
function createTestTheme(): ThemeTokens {
|
|
29
|
+
return {
|
|
30
|
+
primary: '\x1b[34m',
|
|
31
|
+
secondary: '\x1b[36m',
|
|
32
|
+
muted: '\x1b[90m',
|
|
33
|
+
foreground: '\x1b[37m',
|
|
34
|
+
background: '\x1b[40m',
|
|
35
|
+
border: '\x1b[90m',
|
|
36
|
+
success: '\x1b[32m',
|
|
37
|
+
warning: '\x1b[33m',
|
|
38
|
+
error: '\x1b[31m',
|
|
39
|
+
info: '\x1b[34m',
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createTestContext(tier: RenderTier, overrides?: Partial<RenderContext>): RenderContext {
|
|
44
|
+
return {
|
|
45
|
+
tier,
|
|
46
|
+
width: 80,
|
|
47
|
+
height: 24,
|
|
48
|
+
depth: 0,
|
|
49
|
+
theme: createTestTheme(),
|
|
50
|
+
interactive: tier === 'interactive',
|
|
51
|
+
...overrides,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function createMetricsNode(props: Record<string, unknown>): UINode {
|
|
56
|
+
return {
|
|
57
|
+
type: 'metrics',
|
|
58
|
+
props,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createSingleMetricNode(props: Record<string, unknown>): UINode {
|
|
63
|
+
return {
|
|
64
|
+
type: 'metric',
|
|
65
|
+
props,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// Renderer import helper - uses dynamic import for all tiers
|
|
71
|
+
// ============================================================================
|
|
72
|
+
|
|
73
|
+
async function renderForTier(node: UINode, tier: RenderTier, ctx?: RenderContext): Promise<string> {
|
|
74
|
+
const context = ctx ?? createTestContext(tier)
|
|
75
|
+
switch (tier) {
|
|
76
|
+
case 'text': {
|
|
77
|
+
const { renderText } = await import('../../../renderers/text')
|
|
78
|
+
return renderText(node)
|
|
79
|
+
}
|
|
80
|
+
case 'markdown': {
|
|
81
|
+
const { renderMarkdown } = await import('../../../renderers/markdown')
|
|
82
|
+
return renderMarkdown(node)
|
|
83
|
+
}
|
|
84
|
+
case 'ascii': {
|
|
85
|
+
const { renderASCII } = await import('../../../renderers/ascii')
|
|
86
|
+
return renderASCII(node, context)
|
|
87
|
+
}
|
|
88
|
+
case 'unicode': {
|
|
89
|
+
const { renderUnicode } = await import('../../../renderers/unicode')
|
|
90
|
+
return renderUnicode(node, context)
|
|
91
|
+
}
|
|
92
|
+
case 'ansi': {
|
|
93
|
+
const { renderANSI } = await import('../../../renderers/ansi')
|
|
94
|
+
return renderANSI(node)
|
|
95
|
+
}
|
|
96
|
+
case 'interactive': {
|
|
97
|
+
const { renderInteractive } = await import('../../../renderers/interactive')
|
|
98
|
+
return renderInteractive(node, context)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ============================================================================
|
|
104
|
+
// Basic Metrics Rendering Tests
|
|
105
|
+
// ============================================================================
|
|
106
|
+
|
|
107
|
+
describe('Metrics Component', () => {
|
|
108
|
+
describe('basic rendering across all tiers', () => {
|
|
109
|
+
ALL_TIERS.forEach((tier) => {
|
|
110
|
+
describe(`${tier.toUpperCase()} tier`, () => {
|
|
111
|
+
it('renders single metric with label and value', async () => {
|
|
112
|
+
const node = createMetricsNode({
|
|
113
|
+
metrics: [{ label: 'Total Users', value: 1234 }],
|
|
114
|
+
})
|
|
115
|
+
const result = await renderForTier(node, tier)
|
|
116
|
+
|
|
117
|
+
expect(result).toContain('Total Users')
|
|
118
|
+
expect(result).toContain('1234')
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('renders multiple metrics', async () => {
|
|
122
|
+
const node = createMetricsNode({
|
|
123
|
+
metrics: [
|
|
124
|
+
{ label: 'Revenue', value: '$50,000' },
|
|
125
|
+
{ label: 'Orders', value: 156 },
|
|
126
|
+
{ label: 'Customers', value: 89 },
|
|
127
|
+
],
|
|
128
|
+
})
|
|
129
|
+
const result = await renderForTier(node, tier)
|
|
130
|
+
|
|
131
|
+
expect(result).toContain('Revenue')
|
|
132
|
+
expect(result).toContain('$50,000')
|
|
133
|
+
expect(result).toContain('Orders')
|
|
134
|
+
expect(result).toContain('156')
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('handles empty metrics array', async () => {
|
|
138
|
+
const node = createMetricsNode({ metrics: [] })
|
|
139
|
+
const result = await renderForTier(node, tier)
|
|
140
|
+
|
|
141
|
+
expect(typeof result).toBe('string')
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('handles single metric component', async () => {
|
|
145
|
+
const node = createSingleMetricNode({
|
|
146
|
+
label: 'Active Sessions',
|
|
147
|
+
value: 42,
|
|
148
|
+
})
|
|
149
|
+
const result = await renderForTier(node, tier)
|
|
150
|
+
|
|
151
|
+
expect(result).toContain('Active Sessions')
|
|
152
|
+
expect(result).toContain('42')
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
// ============================================================================
|
|
159
|
+
// Value Formatting Tests
|
|
160
|
+
// ============================================================================
|
|
161
|
+
|
|
162
|
+
describe('value formatting', () => {
|
|
163
|
+
ALL_TIERS.forEach((tier) => {
|
|
164
|
+
describe(`${tier.toUpperCase()} tier`, () => {
|
|
165
|
+
it('renders number value', async () => {
|
|
166
|
+
const node = createMetricsNode({
|
|
167
|
+
metrics: [{ label: 'Count', value: 9876 }],
|
|
168
|
+
})
|
|
169
|
+
const result = await renderForTier(node, tier)
|
|
170
|
+
|
|
171
|
+
expect(result).toContain('9876')
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('renders percentage value', async () => {
|
|
175
|
+
const node = createMetricsNode({
|
|
176
|
+
metrics: [{ label: 'Completion', value: 75, format: 'percentage' }],
|
|
177
|
+
})
|
|
178
|
+
const result = await renderForTier(node, tier)
|
|
179
|
+
|
|
180
|
+
expect(result).toContain('Completion')
|
|
181
|
+
expect(result).toContain('75')
|
|
182
|
+
expect(result).toContain('%')
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('renders with unit suffix', async () => {
|
|
186
|
+
const node = createMetricsNode({
|
|
187
|
+
metrics: [{ label: 'Response Time', value: 250, unit: 'ms' }],
|
|
188
|
+
})
|
|
189
|
+
const result = await renderForTier(node, tier)
|
|
190
|
+
|
|
191
|
+
expect(result).toContain('250')
|
|
192
|
+
expect(result).toContain('ms')
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
it('renders string value as-is', async () => {
|
|
196
|
+
const node = createMetricsNode({
|
|
197
|
+
metrics: [{ label: 'Status', value: 'Healthy' }],
|
|
198
|
+
})
|
|
199
|
+
const result = await renderForTier(node, tier)
|
|
200
|
+
|
|
201
|
+
expect(result).toContain('Healthy')
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
// ============================================================================
|
|
208
|
+
// Trend Indicator Tests
|
|
209
|
+
// ============================================================================
|
|
210
|
+
|
|
211
|
+
describe('trend indicators', () => {
|
|
212
|
+
ALL_TIERS.forEach((tier) => {
|
|
213
|
+
describe(`${tier.toUpperCase()} tier`, () => {
|
|
214
|
+
it('renders upward trend', async () => {
|
|
215
|
+
const node = createMetricsNode({
|
|
216
|
+
metrics: [{ label: 'Sales', value: 500, trend: 'up' }],
|
|
217
|
+
})
|
|
218
|
+
const result = await renderForTier(node, tier)
|
|
219
|
+
|
|
220
|
+
expect(result).toContain('Sales')
|
|
221
|
+
expect(result).toContain('500')
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('renders downward trend', async () => {
|
|
225
|
+
const node = createMetricsNode({
|
|
226
|
+
metrics: [{ label: 'Errors', value: 12, trend: 'down' }],
|
|
227
|
+
})
|
|
228
|
+
const result = await renderForTier(node, tier)
|
|
229
|
+
|
|
230
|
+
expect(result).toContain('Errors')
|
|
231
|
+
expect(result).toContain('12')
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('renders neutral trend', async () => {
|
|
235
|
+
const node = createMetricsNode({
|
|
236
|
+
metrics: [{ label: 'Steady', value: 100, trend: 'neutral' }],
|
|
237
|
+
})
|
|
238
|
+
const result = await renderForTier(node, tier)
|
|
239
|
+
|
|
240
|
+
expect(result).toContain('Steady')
|
|
241
|
+
expect(result).toContain('100')
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('renders trend with percentage change', async () => {
|
|
245
|
+
const node = createMetricsNode({
|
|
246
|
+
metrics: [{
|
|
247
|
+
label: 'Revenue',
|
|
248
|
+
value: 1000,
|
|
249
|
+
trend: 'up',
|
|
250
|
+
trendValue: 12.5,
|
|
251
|
+
}],
|
|
252
|
+
})
|
|
253
|
+
const result = await renderForTier(node, tier)
|
|
254
|
+
|
|
255
|
+
expect(result).toContain('Revenue')
|
|
256
|
+
expect(result).toContain('1000')
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
// ============================================================================
|
|
263
|
+
// Sparkline Tests
|
|
264
|
+
// ============================================================================
|
|
265
|
+
|
|
266
|
+
describe('sparkline mini chart', () => {
|
|
267
|
+
const sparklineData = [10, 15, 12, 18, 14, 20, 16, 22, 19, 25]
|
|
268
|
+
|
|
269
|
+
ALL_TIERS.forEach((tier) => {
|
|
270
|
+
describe(`${tier.toUpperCase()} tier`, () => {
|
|
271
|
+
it('renders metric with sparkline', async () => {
|
|
272
|
+
const node = createMetricsNode({
|
|
273
|
+
metrics: [{
|
|
274
|
+
label: 'Traffic',
|
|
275
|
+
value: 25,
|
|
276
|
+
sparkline: sparklineData,
|
|
277
|
+
}],
|
|
278
|
+
})
|
|
279
|
+
const result = await renderForTier(node, tier)
|
|
280
|
+
|
|
281
|
+
expect(result).toContain('Traffic')
|
|
282
|
+
expect(result).toContain('25')
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('handles empty sparkline data', async () => {
|
|
286
|
+
const node = createMetricsNode({
|
|
287
|
+
metrics: [{
|
|
288
|
+
label: 'Empty',
|
|
289
|
+
value: 0,
|
|
290
|
+
sparkline: [],
|
|
291
|
+
}],
|
|
292
|
+
})
|
|
293
|
+
const result = await renderForTier(node, tier)
|
|
294
|
+
|
|
295
|
+
expect(result).toContain('Empty')
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
})
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
// ============================================================================
|
|
302
|
+
// Status Variants Tests
|
|
303
|
+
// ============================================================================
|
|
304
|
+
|
|
305
|
+
describe('status variants', () => {
|
|
306
|
+
ALL_TIERS.forEach((tier) => {
|
|
307
|
+
describe(`${tier.toUpperCase()} tier`, () => {
|
|
308
|
+
it('renders success variant', async () => {
|
|
309
|
+
const node = createMetricsNode({
|
|
310
|
+
metrics: [{ label: 'Uptime', value: '99.9%', variant: 'success' }],
|
|
311
|
+
})
|
|
312
|
+
const result = await renderForTier(node, tier)
|
|
313
|
+
|
|
314
|
+
expect(result).toContain('Uptime')
|
|
315
|
+
expect(result).toContain('99.9%')
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
it('renders warning variant', async () => {
|
|
319
|
+
const node = createMetricsNode({
|
|
320
|
+
metrics: [{ label: 'CPU Usage', value: '85%', variant: 'warning' }],
|
|
321
|
+
})
|
|
322
|
+
const result = await renderForTier(node, tier)
|
|
323
|
+
|
|
324
|
+
expect(result).toContain('CPU Usage')
|
|
325
|
+
expect(result).toContain('85%')
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
it('renders error variant', async () => {
|
|
329
|
+
const node = createMetricsNode({
|
|
330
|
+
metrics: [{ label: 'Failed Jobs', value: 15, variant: 'error' }],
|
|
331
|
+
})
|
|
332
|
+
const result = await renderForTier(node, tier)
|
|
333
|
+
|
|
334
|
+
expect(result).toContain('Failed Jobs')
|
|
335
|
+
expect(result).toContain('15')
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
})
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
// ============================================================================
|
|
342
|
+
// Tier-Specific Rendering Tests
|
|
343
|
+
// ============================================================================
|
|
344
|
+
|
|
345
|
+
describe('tier-specific rendering', () => {
|
|
346
|
+
describe('TEXT tier', () => {
|
|
347
|
+
it('renders as Label: value format', async () => {
|
|
348
|
+
const node = createMetricsNode({
|
|
349
|
+
metrics: [{ label: 'Users', value: 1234 }],
|
|
350
|
+
})
|
|
351
|
+
const result = await renderForTier(node, 'text')
|
|
352
|
+
|
|
353
|
+
expect(result).toContain('Users')
|
|
354
|
+
expect(result).toContain('1234')
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
describe('MARKDOWN tier', () => {
|
|
359
|
+
it('renders with markdown formatting', async () => {
|
|
360
|
+
const node = createMetricsNode({
|
|
361
|
+
metrics: [{ label: 'Revenue', value: 5000 }],
|
|
362
|
+
})
|
|
363
|
+
const result = await renderForTier(node, 'markdown')
|
|
364
|
+
|
|
365
|
+
expect(result).toContain('Revenue')
|
|
366
|
+
expect(result).toContain('5000')
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
describe('ASCII tier', () => {
|
|
371
|
+
it('renders sparkline with ASCII characters', async () => {
|
|
372
|
+
const node = createMetricsNode({
|
|
373
|
+
metrics: [{
|
|
374
|
+
label: 'Chart',
|
|
375
|
+
value: 100,
|
|
376
|
+
sparkline: [20, 40, 60, 80, 100],
|
|
377
|
+
}],
|
|
378
|
+
})
|
|
379
|
+
const result = await renderForTier(node, 'ascii')
|
|
380
|
+
|
|
381
|
+
expect(result).toContain('Chart')
|
|
382
|
+
expect(result).toContain('100')
|
|
383
|
+
})
|
|
384
|
+
})
|
|
385
|
+
|
|
386
|
+
describe('UNICODE tier', () => {
|
|
387
|
+
it('renders sparkline with unicode block characters', async () => {
|
|
388
|
+
const node = createMetricsNode({
|
|
389
|
+
metrics: [{
|
|
390
|
+
label: 'Spark',
|
|
391
|
+
value: 75,
|
|
392
|
+
sparkline: [10, 20, 30, 40, 50, 60, 70, 75],
|
|
393
|
+
}],
|
|
394
|
+
})
|
|
395
|
+
const result = await renderForTier(node, 'unicode')
|
|
396
|
+
|
|
397
|
+
expect(result).toContain('Spark')
|
|
398
|
+
expect(result).toMatch(/[▁▂▃▄▅▆▇█]/)
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
it('renders trend arrows with unicode', async () => {
|
|
402
|
+
const node = createMetricsNode({
|
|
403
|
+
metrics: [
|
|
404
|
+
{ label: 'Up', value: 10, trend: 'up' },
|
|
405
|
+
{ label: 'Down', value: 5, trend: 'down' },
|
|
406
|
+
],
|
|
407
|
+
})
|
|
408
|
+
const result = await renderForTier(node, 'unicode')
|
|
409
|
+
|
|
410
|
+
expect(result).toContain('Up')
|
|
411
|
+
expect(result).toContain('Down')
|
|
412
|
+
expect(result).toMatch(/[↑↓→←▲▼]/)
|
|
413
|
+
})
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
describe('ANSI tier', () => {
|
|
417
|
+
it('renders with ANSI color codes', async () => {
|
|
418
|
+
const node = createMetricsNode({
|
|
419
|
+
metrics: [{ label: 'Colored', value: 100 }],
|
|
420
|
+
})
|
|
421
|
+
const result = await renderForTier(node, 'ansi')
|
|
422
|
+
|
|
423
|
+
expect(result).toMatch(/\x1b\[/)
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
it('applies variant colors', async () => {
|
|
427
|
+
const node = createMetricsNode({
|
|
428
|
+
metrics: [
|
|
429
|
+
{ label: 'Success', value: 100, variant: 'success' },
|
|
430
|
+
{ label: 'Error', value: 0, variant: 'error' },
|
|
431
|
+
],
|
|
432
|
+
})
|
|
433
|
+
const result = await renderForTier(node, 'ansi')
|
|
434
|
+
|
|
435
|
+
expect(result).toContain('Success')
|
|
436
|
+
expect(result).toContain('Error')
|
|
437
|
+
expect(result).toMatch(/\x1b\[\d+m/)
|
|
438
|
+
})
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
describe('INTERACTIVE tier', () => {
|
|
442
|
+
it('renders focusable metrics', async () => {
|
|
443
|
+
const node = createMetricsNode({
|
|
444
|
+
metrics: [
|
|
445
|
+
{ label: 'Metric A', value: 100 },
|
|
446
|
+
{ label: 'Metric B', value: 200 },
|
|
447
|
+
],
|
|
448
|
+
focusedIndex: 0,
|
|
449
|
+
})
|
|
450
|
+
const result = await renderForTier(node, 'interactive')
|
|
451
|
+
|
|
452
|
+
expect(result).toContain('Metric A')
|
|
453
|
+
expect(result).toContain('Metric B')
|
|
454
|
+
})
|
|
455
|
+
})
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
// ============================================================================
|
|
459
|
+
// Edge Cases
|
|
460
|
+
// ============================================================================
|
|
461
|
+
|
|
462
|
+
describe('edge cases', () => {
|
|
463
|
+
ALL_TIERS.forEach((tier) => {
|
|
464
|
+
describe(`${tier.toUpperCase()} tier`, () => {
|
|
465
|
+
it('handles zero value', async () => {
|
|
466
|
+
const node = createMetricsNode({
|
|
467
|
+
metrics: [{ label: 'Zero', value: 0 }],
|
|
468
|
+
})
|
|
469
|
+
const result = await renderForTier(node, tier)
|
|
470
|
+
|
|
471
|
+
expect(result).toContain('Zero')
|
|
472
|
+
expect(result).toContain('0')
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
it('handles negative value', async () => {
|
|
476
|
+
const node = createMetricsNode({
|
|
477
|
+
metrics: [{ label: 'Negative', value: -500 }],
|
|
478
|
+
})
|
|
479
|
+
const result = await renderForTier(node, tier)
|
|
480
|
+
|
|
481
|
+
expect(result).toContain('Negative')
|
|
482
|
+
expect(result).toContain('-500')
|
|
483
|
+
})
|
|
484
|
+
|
|
485
|
+
it('handles very large numbers', async () => {
|
|
486
|
+
const node = createMetricsNode({
|
|
487
|
+
metrics: [{ label: 'Big', value: 999999999999 }],
|
|
488
|
+
})
|
|
489
|
+
const result = await renderForTier(node, tier)
|
|
490
|
+
|
|
491
|
+
expect(result).toContain('Big')
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
it('handles null value', async () => {
|
|
495
|
+
const node = createMetricsNode({
|
|
496
|
+
metrics: [{ label: 'Null', value: null }],
|
|
497
|
+
})
|
|
498
|
+
const result = await renderForTier(node, tier)
|
|
499
|
+
|
|
500
|
+
expect(result).toContain('Null')
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
it('handles very long label', async () => {
|
|
504
|
+
const longLabel = 'A'.repeat(100)
|
|
505
|
+
const node = createMetricsNode({
|
|
506
|
+
metrics: [{ label: longLabel, value: 42 }],
|
|
507
|
+
})
|
|
508
|
+
const ctx = createTestContext(tier, { width: 40 })
|
|
509
|
+
const result = await renderForTier(node, tier, ctx)
|
|
510
|
+
|
|
511
|
+
expect(result).toContain('A')
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
it('handles many metrics', async () => {
|
|
515
|
+
const manyMetrics = Array.from({ length: 20 }, (_, i) => ({
|
|
516
|
+
label: `Metric ${i}`,
|
|
517
|
+
value: i * 100,
|
|
518
|
+
}))
|
|
519
|
+
const node = createMetricsNode({ metrics: manyMetrics })
|
|
520
|
+
const result = await renderForTier(node, tier)
|
|
521
|
+
|
|
522
|
+
expect(result).toContain('Metric 0')
|
|
523
|
+
expect(result).toContain('Metric 19')
|
|
524
|
+
})
|
|
525
|
+
|
|
526
|
+
it('handles sparkline with all same values', async () => {
|
|
527
|
+
const node = createMetricsNode({
|
|
528
|
+
metrics: [{
|
|
529
|
+
label: 'Flat',
|
|
530
|
+
value: 50,
|
|
531
|
+
sparkline: [50, 50, 50, 50, 50],
|
|
532
|
+
}],
|
|
533
|
+
})
|
|
534
|
+
const result = await renderForTier(node, tier)
|
|
535
|
+
|
|
536
|
+
expect(result).toContain('Flat')
|
|
537
|
+
})
|
|
538
|
+
})
|
|
539
|
+
})
|
|
540
|
+
})
|
|
541
|
+
})
|