mgv-backoffice 1.36.0 → 1.36.2

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.
Files changed (60) hide show
  1. package/README.md +1794 -1719
  2. package/dist/components/BaseBadge.vue.d.ts +1 -11
  3. package/dist/components/BaseDropdown.vue.d.ts +1 -1
  4. package/dist/ui-lib.js +942 -925
  5. package/dist/ui-lib.umd.cjs +1 -1
  6. package/dist/utils/specForm.d.ts +1 -1
  7. package/package.json +54 -54
  8. package/src/components/BaseAlert.vue +104 -104
  9. package/src/components/BaseBadge.vue +12 -2
  10. package/src/components/BaseBarDistribution.test.ts +51 -51
  11. package/src/components/BaseBarDistribution.vue +73 -73
  12. package/src/components/BaseChipButton.vue +42 -42
  13. package/src/components/BaseCodeBlock.test.ts +70 -70
  14. package/src/components/BaseCodeBlock.vue +74 -74
  15. package/src/components/BaseCollapsibleSection.vue +113 -113
  16. package/src/components/BaseConfirmModal.vue +114 -114
  17. package/src/components/BaseCredentialsForm.test.ts +108 -108
  18. package/src/components/BaseCredentialsForm.vue +241 -241
  19. package/src/components/BaseDropdown.test.ts +87 -87
  20. package/src/components/BaseDropdown.vue +172 -172
  21. package/src/components/BaseEntityPickerModal.vue +330 -330
  22. package/src/components/BaseFileDropzone.vue +94 -94
  23. package/src/components/BaseFilterChip.test.ts +44 -44
  24. package/src/components/BaseFilterChip.vue +67 -67
  25. package/src/components/BaseModalShell.vue +209 -209
  26. package/src/components/BasePageHeader.vue +120 -120
  27. package/src/components/BasePillPickerModal.test.ts +95 -95
  28. package/src/components/BaseRemoveButton.vue +22 -22
  29. package/src/components/BaseSpecFields.test.ts +39 -0
  30. package/src/components/BaseSpecFields.vue +27 -3
  31. package/src/components/BaseStatBreakdown.test.ts +56 -56
  32. package/src/components/BaseStatBreakdown.vue +49 -49
  33. package/src/components/BaseStatusPill.vue +54 -54
  34. package/src/components/BaseTextInputModal.vue +140 -140
  35. package/src/components/BaseToolbarButton.test.ts +48 -48
  36. package/src/components/BaseToolbarButton.vue +94 -94
  37. package/src/composables/useFieldClasses.test.ts +55 -55
  38. package/src/composables/useFieldClasses.ts +56 -56
  39. package/src/composables/usePolling.test.ts +112 -112
  40. package/src/composables/usePolling.ts +102 -102
  41. package/src/composables/useQueryParamSync.test.ts +56 -56
  42. package/src/composables/useQueryParamSync.ts +42 -42
  43. package/src/composables/useThemeClasses.test.ts +34 -34
  44. package/src/composables/useThemeClasses.ts +137 -137
  45. package/src/index.ts +138 -138
  46. package/src/style.css +32 -32
  47. package/src/types/distribution.ts +7 -7
  48. package/src/types/pillPicker.ts +14 -14
  49. package/src/types/specField.ts +30 -30
  50. package/src/types/statBreakdown.ts +6 -6
  51. package/src/utils/format.test.ts +264 -264
  52. package/src/utils/format.ts +216 -216
  53. package/src/utils/httpColors.test.ts +100 -100
  54. package/src/utils/httpColors.ts +99 -99
  55. package/src/utils/kvRows.test.ts +47 -47
  56. package/src/utils/kvRows.ts +34 -34
  57. package/src/utils/specForm.test.ts +64 -53
  58. package/src/utils/specForm.ts +49 -48
  59. package/src/utils/validate.test.ts +82 -82
  60. package/src/utils/validate.ts +76 -76
@@ -1,216 +1,216 @@
1
- /**
2
- * Locale-aware display formatters shared across back-office tables, logs and
3
- * charts. All are pure and dependency-free; they take the "missing value"
4
- * branch seriously so callers can hand them raw API values (which may be
5
- * null/strings) without pre-sanitising.
6
- */
7
-
8
- const EM_DASH = '—'
9
-
10
- /**
11
- * Format a number with a fixed number of fraction digits, rendering an
12
- * em-dash for null/undefined/non-finite input. Accepts numeric strings so
13
- * BigDecimal-as-string API payloads pass through unchanged.
14
- */
15
- export function fmtNumber(n: number | string | null | undefined, digits = 4): string {
16
- if (n === null || n === undefined) return EM_DASH
17
- if (typeof n === 'string' && n.trim() === '') return EM_DASH
18
- const v = typeof n === 'string' ? Number(n) : n
19
- if (!isFinite(v)) return EM_DASH
20
- return v.toLocaleString(undefined, {
21
- minimumFractionDigits: digits,
22
- maximumFractionDigits: digits,
23
- })
24
- }
25
-
26
- /**
27
- * Format an ISO date string or epoch value as a locale date-time, falling
28
- * back to the raw value on parse failure and an em-dash on empty input.
29
- * Accepts numbers so epoch-millis timestamps pass through unchanged.
30
- */
31
- export function fmtDate(s: string | number | null | undefined): string {
32
- if (s === null || s === undefined || s === '') return EM_DASH
33
- const d = new Date(s)
34
- if (isNaN(d.getTime())) return String(s)
35
- return d.toLocaleString()
36
- }
37
-
38
- /**
39
- * Format an epoch-millis timestamp as a compact "Mon D, HH:MM" label —
40
- * the form used for chart axes and tooltips.
41
- */
42
- export function fmtDateTime(ms: number): string {
43
- return new Date(ms).toLocaleString(undefined, {
44
- month: 'short',
45
- day: 'numeric',
46
- hour: '2-digit',
47
- minute: '2-digit',
48
- })
49
- }
50
-
51
- /**
52
- * Format an epoch-millis timestamp as a short "Mon D" calendar label.
53
- */
54
- export function fmtDateShort(ms: number): string {
55
- return new Date(ms).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })
56
- }
57
-
58
- /**
59
- * Format an ISO string / epoch-millis as a full 24-hour locale date-time WITH
60
- * the millisecond fraction — for dense feeds where several rows can land in
61
- * the same second and must stay distinguishable. Falls back to the raw value
62
- * when the input can't be parsed.
63
- */
64
- export function fmtDateTimeMs(s: string | number): string {
65
- try {
66
- return new Date(s).toLocaleString(undefined, {
67
- year: 'numeric',
68
- month: '2-digit',
69
- day: '2-digit',
70
- hour: '2-digit',
71
- minute: '2-digit',
72
- second: '2-digit',
73
- fractionalSecondDigits: 3,
74
- hour12: false,
75
- })
76
- } catch {
77
- return String(s)
78
- }
79
- }
80
-
81
- /**
82
- * Format a price with precision that scales to magnitude: penny stocks and
83
- * sub-cent crypto get more decimals so the value never collapses to "0.00".
84
- */
85
- export function fmtPrice(n: number): string {
86
- if (!isFinite(n) || n === 0) return n.toFixed(2)
87
- const abs = Math.abs(n)
88
- let digits = 2
89
- if (abs < 0.0001) digits = 8
90
- else if (abs < 0.01) digits = 6
91
- else if (abs < 1) digits = 4
92
- else if (abs < 100) digits = 3
93
- return n.toLocaleString(undefined, {
94
- minimumFractionDigits: digits,
95
- maximumFractionDigits: digits,
96
- })
97
- }
98
-
99
- /**
100
- * Format a percentage with an explicit sign (e.g. "+2.50%", "-1.00%").
101
- */
102
- export function fmtPct(n: number, digits = 2): string {
103
- return `${n >= 0 ? '+' : ''}${n.toFixed(digits)}%`
104
- }
105
-
106
- /**
107
- * Format a signed USD amount with an explicit leading sign (e.g. "+$5.00").
108
- */
109
- export function fmtUsd(v: number): string {
110
- const sign = v < 0 ? '-' : '+'
111
- return `${sign}$${Math.abs(v).toFixed(2)}`
112
- }
113
-
114
- /**
115
- * Format an ISO date string / epoch as a "Mon D, YYYY" calendar label
116
- * (en-US, e.g. "Jan 5, 2026"). Falls back to the raw value on parse
117
- * failure and an em-dash on empty input.
118
- */
119
- export function fmtCalendarDate(s: string | number | null | undefined): string {
120
- if (s === null || s === undefined || s === '') return EM_DASH
121
- const d = new Date(s)
122
- if (isNaN(d.getTime())) return String(s)
123
- return d.toLocaleDateString('en-US', {
124
- year: 'numeric',
125
- month: 'short',
126
- day: 'numeric',
127
- })
128
- }
129
-
130
- /**
131
- * Format an ISO date string / epoch as a "Mon D, YYYY, HH:MM" calendar
132
- * label with the time of day (en-US). Same fallbacks as fmtCalendarDate.
133
- */
134
- export function fmtCalendarDateTime(s: string | number | null | undefined): string {
135
- if (s === null || s === undefined || s === '') return EM_DASH
136
- const d = new Date(s)
137
- if (isNaN(d.getTime())) return String(s)
138
- return d.toLocaleDateString('en-US', {
139
- year: 'numeric',
140
- month: 'short',
141
- day: 'numeric',
142
- hour: '2-digit',
143
- minute: '2-digit',
144
- })
145
- }
146
-
147
- /**
148
- * Render a millisecond count as a "= 1.50 s" hint so users can
149
- * sanity-check magnitudes without mental math. Sub-second values get 3
150
- * decimals ("= 0.250 s"), everything else 2. Returns '' for
151
- * non-positive / non-finite input so callers can `v-if` it away.
152
- */
153
- export function fmtMsAsSeconds(ms: number | null | undefined): string {
154
- if (typeof ms !== 'number' || !isFinite(ms) || ms <= 0) return ''
155
- const seconds = ms / 1000
156
- const decimals = seconds < 1 ? 3 : 2
157
- return `= ${seconds.toFixed(decimals)} s`
158
- }
159
-
160
- /**
161
- * Pretty-print a byte count with KB/MB granularity ("512 B", "1.5 KB",
162
- * "2.0 MB"). Returns '' for zero/falsy input.
163
- */
164
- export function fmtBytes(bytes: number | null | undefined): string {
165
- if (!bytes) return ''
166
- if (bytes < 1024) return `${bytes} B`
167
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
168
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
169
- }
170
-
171
- /**
172
- * Pretty-print a string that may contain JSON: parseable input is
173
- * re-serialized with 2-space indentation, anything else is returned
174
- * verbatim. Handy for request/response bodies of unknown content type.
175
- */
176
- export function formatJson(content: string): string {
177
- try {
178
- return JSON.stringify(JSON.parse(content), null, 2)
179
- } catch {
180
- return content
181
- }
182
- }
183
-
184
- /**
185
- * Render an unknown value as a display string: strings pass through,
186
- * null/undefined become '', everything else is JSON-serialized (falling
187
- * back to String() for values JSON can't handle, e.g. cycles or BigInt).
188
- */
189
- export function stringifyValue(value: unknown): string {
190
- if (value === null || value === undefined) return ''
191
- if (typeof value === 'string') return value
192
- try {
193
- return JSON.stringify(value)
194
- } catch {
195
- return String(value)
196
- }
197
- }
198
-
199
- /**
200
- * Format a millisecond duration as compact day/hour/minute units, e.g.
201
- * "3d 5h", "5h 12m" or "12m". Zero-value leading units are dropped;
202
- * `maxUnits` caps how many units render (2 → "3d 5h", 3 → "3d 5h 12m").
203
- * Renders an em-dash for null/undefined/non-finite input.
204
- */
205
- export function fmtDuration(ms: number | null | undefined, maxUnits = 2): string {
206
- if (ms === null || ms === undefined || !isFinite(ms)) return EM_DASH
207
- const totalMins = Math.floor(Math.max(0, ms) / 60_000)
208
- const days = Math.floor(totalMins / 1440)
209
- const hours = Math.floor((totalMins % 1440) / 60)
210
- const mins = totalMins % 60
211
- const parts: string[] = []
212
- if (days > 0) parts.push(`${days}d`)
213
- if (days > 0 || hours > 0) parts.push(`${hours}h`)
214
- parts.push(`${mins}m`)
215
- return parts.slice(0, maxUnits).join(' ')
216
- }
1
+ /**
2
+ * Locale-aware display formatters shared across back-office tables, logs and
3
+ * charts. All are pure and dependency-free; they take the "missing value"
4
+ * branch seriously so callers can hand them raw API values (which may be
5
+ * null/strings) without pre-sanitising.
6
+ */
7
+
8
+ const EM_DASH = '—'
9
+
10
+ /**
11
+ * Format a number with a fixed number of fraction digits, rendering an
12
+ * em-dash for null/undefined/non-finite input. Accepts numeric strings so
13
+ * BigDecimal-as-string API payloads pass through unchanged.
14
+ */
15
+ export function fmtNumber(n: number | string | null | undefined, digits = 4): string {
16
+ if (n === null || n === undefined) return EM_DASH
17
+ if (typeof n === 'string' && n.trim() === '') return EM_DASH
18
+ const v = typeof n === 'string' ? Number(n) : n
19
+ if (!isFinite(v)) return EM_DASH
20
+ return v.toLocaleString(undefined, {
21
+ minimumFractionDigits: digits,
22
+ maximumFractionDigits: digits,
23
+ })
24
+ }
25
+
26
+ /**
27
+ * Format an ISO date string or epoch value as a locale date-time, falling
28
+ * back to the raw value on parse failure and an em-dash on empty input.
29
+ * Accepts numbers so epoch-millis timestamps pass through unchanged.
30
+ */
31
+ export function fmtDate(s: string | number | null | undefined): string {
32
+ if (s === null || s === undefined || s === '') return EM_DASH
33
+ const d = new Date(s)
34
+ if (isNaN(d.getTime())) return String(s)
35
+ return d.toLocaleString()
36
+ }
37
+
38
+ /**
39
+ * Format an epoch-millis timestamp as a compact "Mon D, HH:MM" label —
40
+ * the form used for chart axes and tooltips.
41
+ */
42
+ export function fmtDateTime(ms: number): string {
43
+ return new Date(ms).toLocaleString(undefined, {
44
+ month: 'short',
45
+ day: 'numeric',
46
+ hour: '2-digit',
47
+ minute: '2-digit',
48
+ })
49
+ }
50
+
51
+ /**
52
+ * Format an epoch-millis timestamp as a short "Mon D" calendar label.
53
+ */
54
+ export function fmtDateShort(ms: number): string {
55
+ return new Date(ms).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })
56
+ }
57
+
58
+ /**
59
+ * Format an ISO string / epoch-millis as a full 24-hour locale date-time WITH
60
+ * the millisecond fraction — for dense feeds where several rows can land in
61
+ * the same second and must stay distinguishable. Falls back to the raw value
62
+ * when the input can't be parsed.
63
+ */
64
+ export function fmtDateTimeMs(s: string | number): string {
65
+ try {
66
+ return new Date(s).toLocaleString(undefined, {
67
+ year: 'numeric',
68
+ month: '2-digit',
69
+ day: '2-digit',
70
+ hour: '2-digit',
71
+ minute: '2-digit',
72
+ second: '2-digit',
73
+ fractionalSecondDigits: 3,
74
+ hour12: false,
75
+ })
76
+ } catch {
77
+ return String(s)
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Format a price with precision that scales to magnitude: penny stocks and
83
+ * sub-cent crypto get more decimals so the value never collapses to "0.00".
84
+ */
85
+ export function fmtPrice(n: number): string {
86
+ if (!isFinite(n) || n === 0) return n.toFixed(2)
87
+ const abs = Math.abs(n)
88
+ let digits = 2
89
+ if (abs < 0.0001) digits = 8
90
+ else if (abs < 0.01) digits = 6
91
+ else if (abs < 1) digits = 4
92
+ else if (abs < 100) digits = 3
93
+ return n.toLocaleString(undefined, {
94
+ minimumFractionDigits: digits,
95
+ maximumFractionDigits: digits,
96
+ })
97
+ }
98
+
99
+ /**
100
+ * Format a percentage with an explicit sign (e.g. "+2.50%", "-1.00%").
101
+ */
102
+ export function fmtPct(n: number, digits = 2): string {
103
+ return `${n >= 0 ? '+' : ''}${n.toFixed(digits)}%`
104
+ }
105
+
106
+ /**
107
+ * Format a signed USD amount with an explicit leading sign (e.g. "+$5.00").
108
+ */
109
+ export function fmtUsd(v: number): string {
110
+ const sign = v < 0 ? '-' : '+'
111
+ return `${sign}$${Math.abs(v).toFixed(2)}`
112
+ }
113
+
114
+ /**
115
+ * Format an ISO date string / epoch as a "Mon D, YYYY" calendar label
116
+ * (en-US, e.g. "Jan 5, 2026"). Falls back to the raw value on parse
117
+ * failure and an em-dash on empty input.
118
+ */
119
+ export function fmtCalendarDate(s: string | number | null | undefined): string {
120
+ if (s === null || s === undefined || s === '') return EM_DASH
121
+ const d = new Date(s)
122
+ if (isNaN(d.getTime())) return String(s)
123
+ return d.toLocaleDateString('en-US', {
124
+ year: 'numeric',
125
+ month: 'short',
126
+ day: 'numeric',
127
+ })
128
+ }
129
+
130
+ /**
131
+ * Format an ISO date string / epoch as a "Mon D, YYYY, HH:MM" calendar
132
+ * label with the time of day (en-US). Same fallbacks as fmtCalendarDate.
133
+ */
134
+ export function fmtCalendarDateTime(s: string | number | null | undefined): string {
135
+ if (s === null || s === undefined || s === '') return EM_DASH
136
+ const d = new Date(s)
137
+ if (isNaN(d.getTime())) return String(s)
138
+ return d.toLocaleDateString('en-US', {
139
+ year: 'numeric',
140
+ month: 'short',
141
+ day: 'numeric',
142
+ hour: '2-digit',
143
+ minute: '2-digit',
144
+ })
145
+ }
146
+
147
+ /**
148
+ * Render a millisecond count as a "= 1.50 s" hint so users can
149
+ * sanity-check magnitudes without mental math. Sub-second values get 3
150
+ * decimals ("= 0.250 s"), everything else 2. Returns '' for
151
+ * non-positive / non-finite input so callers can `v-if` it away.
152
+ */
153
+ export function fmtMsAsSeconds(ms: number | null | undefined): string {
154
+ if (typeof ms !== 'number' || !isFinite(ms) || ms <= 0) return ''
155
+ const seconds = ms / 1000
156
+ const decimals = seconds < 1 ? 3 : 2
157
+ return `= ${seconds.toFixed(decimals)} s`
158
+ }
159
+
160
+ /**
161
+ * Pretty-print a byte count with KB/MB granularity ("512 B", "1.5 KB",
162
+ * "2.0 MB"). Returns '' for zero/falsy input.
163
+ */
164
+ export function fmtBytes(bytes: number | null | undefined): string {
165
+ if (!bytes) return ''
166
+ if (bytes < 1024) return `${bytes} B`
167
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
168
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
169
+ }
170
+
171
+ /**
172
+ * Pretty-print a string that may contain JSON: parseable input is
173
+ * re-serialized with 2-space indentation, anything else is returned
174
+ * verbatim. Handy for request/response bodies of unknown content type.
175
+ */
176
+ export function formatJson(content: string): string {
177
+ try {
178
+ return JSON.stringify(JSON.parse(content), null, 2)
179
+ } catch {
180
+ return content
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Render an unknown value as a display string: strings pass through,
186
+ * null/undefined become '', everything else is JSON-serialized (falling
187
+ * back to String() for values JSON can't handle, e.g. cycles or BigInt).
188
+ */
189
+ export function stringifyValue(value: unknown): string {
190
+ if (value === null || value === undefined) return ''
191
+ if (typeof value === 'string') return value
192
+ try {
193
+ return JSON.stringify(value)
194
+ } catch {
195
+ return String(value)
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Format a millisecond duration as compact day/hour/minute units, e.g.
201
+ * "3d 5h", "5h 12m" or "12m". Zero-value leading units are dropped;
202
+ * `maxUnits` caps how many units render (2 → "3d 5h", 3 → "3d 5h 12m").
203
+ * Renders an em-dash for null/undefined/non-finite input.
204
+ */
205
+ export function fmtDuration(ms: number | null | undefined, maxUnits = 2): string {
206
+ if (ms === null || ms === undefined || !isFinite(ms)) return EM_DASH
207
+ const totalMins = Math.floor(Math.max(0, ms) / 60_000)
208
+ const days = Math.floor(totalMins / 1440)
209
+ const hours = Math.floor((totalMins % 1440) / 60)
210
+ const mins = totalMins % 60
211
+ const parts: string[] = []
212
+ if (days > 0) parts.push(`${days}d`)
213
+ if (days > 0 || hours > 0) parts.push(`${hours}h`)
214
+ parts.push(`${mins}m`)
215
+ return parts.slice(0, maxUnits).join(' ')
216
+ }
@@ -1,100 +1,100 @@
1
- import { describe, it, expect } from 'vitest'
2
- import {
3
- methodBadgeSolid,
4
- methodBadgeBright,
5
- statusBadgeSolid,
6
- statusBadgeTinted,
7
- } from './httpColors'
8
-
9
- describe('methodBadgeSolid', () => {
10
- it('maps known methods (case-insensitive)', () => {
11
- expect(methodBadgeSolid('GET')).toBe('bg-blue-600')
12
- expect(methodBadgeSolid('get')).toBe('bg-blue-600')
13
- expect(methodBadgeSolid('DELETE')).toBe('bg-red-600')
14
- })
15
-
16
- it('falls back to gray for missing/unknown methods', () => {
17
- expect(methodBadgeSolid()).toBe('bg-gray-600')
18
- expect(methodBadgeSolid('TRACE')).toBe('bg-gray-600')
19
- })
20
- })
21
-
22
- describe('methodBadgeBright', () => {
23
- it('maps known methods', () => {
24
- expect(methodBadgeBright('POST')).toBe('bg-green-500')
25
- })
26
-
27
- it('falls back to gray', () => {
28
- expect(methodBadgeBright()).toBe('bg-gray-500')
29
- expect(methodBadgeBright('WAT')).toBe('bg-gray-500')
30
- })
31
- })
32
-
33
- describe('statusBadgeSolid', () => {
34
- it('buckets by status class', () => {
35
- expect(statusBadgeSolid(204)).toBe('bg-emerald-600')
36
- expect(statusBadgeSolid(301)).toBe('bg-blue-600')
37
- expect(statusBadgeSolid(404)).toBe('bg-amber-600')
38
- expect(statusBadgeSolid(500)).toBe('bg-red-600')
39
- })
40
-
41
- it('falls back to gray for missing/unknown codes', () => {
42
- expect(statusBadgeSolid()).toBe('bg-gray-600')
43
- expect(statusBadgeSolid(100)).toBe('bg-gray-600')
44
- })
45
- })
46
-
47
- describe('statusBadgeTinted', () => {
48
- it('returns dark-theme classes', () => {
49
- expect(statusBadgeTinted(200, true)).toBe('bg-green-500/15 text-green-400')
50
- expect(statusBadgeTinted(404, true)).toBe('bg-yellow-500/15 text-yellow-400')
51
- expect(statusBadgeTinted(500, true)).toBe('bg-red-500/15 text-red-400')
52
- })
53
-
54
- it('returns light-theme classes', () => {
55
- expect(statusBadgeTinted(200, false)).toBe('bg-green-100 text-green-800')
56
- expect(statusBadgeTinted(404, false)).toBe('bg-yellow-100 text-yellow-800')
57
- })
58
-
59
- it('defaults a missing status to 2xx', () => {
60
- expect(statusBadgeTinted(undefined, false)).toBe('bg-green-100 text-green-800')
61
- })
62
- })
63
-
64
- import { methodBadgeTinted, statusBadgeSoft } from './httpColors'
65
-
66
- describe('methodBadgeTinted', () => {
67
- it('gives each method its own hue (dark)', () => {
68
- expect(methodBadgeTinted('GET', true)).toBe('bg-blue-500/15 text-blue-400')
69
- expect(methodBadgeTinted('POST', true)).toBe('bg-emerald-500/15 text-emerald-400')
70
- expect(methodBadgeTinted('PUT', true)).toBe('bg-amber-500/15 text-amber-400')
71
- expect(methodBadgeTinted('DELETE', true)).toBe('bg-red-500/15 text-red-400')
72
- expect(methodBadgeTinted('PATCH', true)).toBe('bg-purple-500/15 text-purple-400')
73
- expect(methodBadgeTinted('HEAD', true)).toBe('bg-sky-500/15 text-sky-400')
74
- })
75
- it('uses the light palette when isDark is false', () => {
76
- expect(methodBadgeTinted('GET', false)).toBe('bg-blue-100 text-blue-700')
77
- })
78
- it('is case-insensitive and falls back to gray for unknown/ANY/missing', () => {
79
- expect(methodBadgeTinted('get', true)).toBe('bg-blue-500/15 text-blue-400')
80
- expect(methodBadgeTinted(undefined, false)).toBe('bg-gray-100 text-gray-600')
81
- expect(methodBadgeTinted('OPTIONS', false)).toBe('bg-gray-100 text-gray-600')
82
- })
83
- })
84
-
85
- describe('statusBadgeSoft', () => {
86
- it('buckets by status class (light)', () => {
87
- expect(statusBadgeSoft(201, false)).toBe('bg-emerald-100 text-emerald-700')
88
- expect(statusBadgeSoft(302, false)).toBe('bg-sky-100 text-sky-700')
89
- expect(statusBadgeSoft(404, false)).toBe('bg-amber-100 text-amber-700')
90
- expect(statusBadgeSoft(503, false)).toBe('bg-red-100 text-red-700')
91
- })
92
- it('uses tinted surfaces in dark mode', () => {
93
- expect(statusBadgeSoft(200, true)).toBe('bg-emerald-500/15 text-emerald-300')
94
- })
95
- it('defaults undefined to 200 and gray for out-of-range codes', () => {
96
- expect(statusBadgeSoft(undefined, false)).toBe('bg-emerald-100 text-emerald-700')
97
- expect(statusBadgeSoft(999, false)).toBe('bg-gray-100 text-gray-600')
98
- expect(statusBadgeSoft(100, true)).toBe('bg-gray-800 text-gray-400')
99
- })
100
- })
1
+ import { describe, it, expect } from 'vitest'
2
+ import {
3
+ methodBadgeSolid,
4
+ methodBadgeBright,
5
+ statusBadgeSolid,
6
+ statusBadgeTinted,
7
+ } from './httpColors'
8
+
9
+ describe('methodBadgeSolid', () => {
10
+ it('maps known methods (case-insensitive)', () => {
11
+ expect(methodBadgeSolid('GET')).toBe('bg-blue-600')
12
+ expect(methodBadgeSolid('get')).toBe('bg-blue-600')
13
+ expect(methodBadgeSolid('DELETE')).toBe('bg-red-600')
14
+ })
15
+
16
+ it('falls back to gray for missing/unknown methods', () => {
17
+ expect(methodBadgeSolid()).toBe('bg-gray-600')
18
+ expect(methodBadgeSolid('TRACE')).toBe('bg-gray-600')
19
+ })
20
+ })
21
+
22
+ describe('methodBadgeBright', () => {
23
+ it('maps known methods', () => {
24
+ expect(methodBadgeBright('POST')).toBe('bg-green-500')
25
+ })
26
+
27
+ it('falls back to gray', () => {
28
+ expect(methodBadgeBright()).toBe('bg-gray-500')
29
+ expect(methodBadgeBright('WAT')).toBe('bg-gray-500')
30
+ })
31
+ })
32
+
33
+ describe('statusBadgeSolid', () => {
34
+ it('buckets by status class', () => {
35
+ expect(statusBadgeSolid(204)).toBe('bg-emerald-600')
36
+ expect(statusBadgeSolid(301)).toBe('bg-blue-600')
37
+ expect(statusBadgeSolid(404)).toBe('bg-amber-600')
38
+ expect(statusBadgeSolid(500)).toBe('bg-red-600')
39
+ })
40
+
41
+ it('falls back to gray for missing/unknown codes', () => {
42
+ expect(statusBadgeSolid()).toBe('bg-gray-600')
43
+ expect(statusBadgeSolid(100)).toBe('bg-gray-600')
44
+ })
45
+ })
46
+
47
+ describe('statusBadgeTinted', () => {
48
+ it('returns dark-theme classes', () => {
49
+ expect(statusBadgeTinted(200, true)).toBe('bg-green-500/15 text-green-400')
50
+ expect(statusBadgeTinted(404, true)).toBe('bg-yellow-500/15 text-yellow-400')
51
+ expect(statusBadgeTinted(500, true)).toBe('bg-red-500/15 text-red-400')
52
+ })
53
+
54
+ it('returns light-theme classes', () => {
55
+ expect(statusBadgeTinted(200, false)).toBe('bg-green-100 text-green-800')
56
+ expect(statusBadgeTinted(404, false)).toBe('bg-yellow-100 text-yellow-800')
57
+ })
58
+
59
+ it('defaults a missing status to 2xx', () => {
60
+ expect(statusBadgeTinted(undefined, false)).toBe('bg-green-100 text-green-800')
61
+ })
62
+ })
63
+
64
+ import { methodBadgeTinted, statusBadgeSoft } from './httpColors'
65
+
66
+ describe('methodBadgeTinted', () => {
67
+ it('gives each method its own hue (dark)', () => {
68
+ expect(methodBadgeTinted('GET', true)).toBe('bg-blue-500/15 text-blue-400')
69
+ expect(methodBadgeTinted('POST', true)).toBe('bg-emerald-500/15 text-emerald-400')
70
+ expect(methodBadgeTinted('PUT', true)).toBe('bg-amber-500/15 text-amber-400')
71
+ expect(methodBadgeTinted('DELETE', true)).toBe('bg-red-500/15 text-red-400')
72
+ expect(methodBadgeTinted('PATCH', true)).toBe('bg-purple-500/15 text-purple-400')
73
+ expect(methodBadgeTinted('HEAD', true)).toBe('bg-sky-500/15 text-sky-400')
74
+ })
75
+ it('uses the light palette when isDark is false', () => {
76
+ expect(methodBadgeTinted('GET', false)).toBe('bg-blue-100 text-blue-700')
77
+ })
78
+ it('is case-insensitive and falls back to gray for unknown/ANY/missing', () => {
79
+ expect(methodBadgeTinted('get', true)).toBe('bg-blue-500/15 text-blue-400')
80
+ expect(methodBadgeTinted(undefined, false)).toBe('bg-gray-100 text-gray-600')
81
+ expect(methodBadgeTinted('OPTIONS', false)).toBe('bg-gray-100 text-gray-600')
82
+ })
83
+ })
84
+
85
+ describe('statusBadgeSoft', () => {
86
+ it('buckets by status class (light)', () => {
87
+ expect(statusBadgeSoft(201, false)).toBe('bg-emerald-100 text-emerald-700')
88
+ expect(statusBadgeSoft(302, false)).toBe('bg-sky-100 text-sky-700')
89
+ expect(statusBadgeSoft(404, false)).toBe('bg-amber-100 text-amber-700')
90
+ expect(statusBadgeSoft(503, false)).toBe('bg-red-100 text-red-700')
91
+ })
92
+ it('uses tinted surfaces in dark mode', () => {
93
+ expect(statusBadgeSoft(200, true)).toBe('bg-emerald-500/15 text-emerald-300')
94
+ })
95
+ it('defaults undefined to 200 and gray for out-of-range codes', () => {
96
+ expect(statusBadgeSoft(undefined, false)).toBe('bg-emerald-100 text-emerald-700')
97
+ expect(statusBadgeSoft(999, false)).toBe('bg-gray-100 text-gray-600')
98
+ expect(statusBadgeSoft(100, true)).toBe('bg-gray-800 text-gray-400')
99
+ })
100
+ })