@titan-design/react-ui 0.9.3 → 0.11.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/dist/bodymap.js +78 -24
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +78 -24
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +429 -11
- package/dist/index.d.ts +429 -11
- package/dist/index.js +2386 -885
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2306 -836
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-DyEx-lBf.d.ts → pages-B1hqnGIC.d.ts} +149 -15
- package/dist/{pages-D7hOD4Vj.d.mts → pages-CvsFqNtx.d.mts} +149 -15
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1900 -1609
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1901 -1610
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +129 -34
- package/dist/theme/index.d.ts +129 -34
- package/dist/theme/index.js +95 -19
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +92 -20
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +88 -24
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +88 -24
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +8 -3
- package/src/components/custom/Fatigue/DualGhostSpark.stories.tsx +258 -0
- package/src/components/custom/Fatigue/DualGhostSpark.test.tsx +138 -0
- package/src/components/custom/Fatigue/DualGhostSpark.tsx +208 -0
- package/src/components/custom/Fatigue/FatigueLights.stories.tsx +74 -0
- package/src/components/custom/Fatigue/FatigueLights.test.tsx +33 -0
- package/src/components/custom/Fatigue/FatigueLights.tsx +83 -0
- package/src/components/custom/Fatigue/GhostBand.test.tsx +235 -0
- package/src/components/custom/Fatigue/GhostBand.tsx +246 -0
- package/src/components/custom/Fatigue/GhostBloom.tsx +132 -0
- package/src/components/custom/Fatigue/GhostSpark.stories.tsx +175 -0
- package/src/components/custom/Fatigue/GhostSpark.test.tsx +45 -0
- package/src/components/custom/Fatigue/GhostSpark.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.stories.tsx +126 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.test.tsx +28 -0
- package/src/components/custom/Fatigue/LiveFatigueCard.tsx +86 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.stories.tsx +95 -0
- package/src/components/custom/Fatigue/LiveFatiguePanel.tsx +151 -0
- package/src/components/custom/Fatigue/README.md +93 -0
- package/src/components/custom/Fatigue/RomProgressionChart.stories.tsx +94 -0
- package/src/components/custom/Fatigue/RomProgressionChart.test.tsx +31 -0
- package/src/components/custom/Fatigue/RomProgressionChart.tsx +132 -0
- package/src/components/custom/Fatigue/SilverRedPalette.stories.tsx +226 -0
- package/src/components/custom/Fatigue/VelocityHero.stories.tsx +70 -0
- package/src/components/custom/Fatigue/VelocityHero.test.tsx +26 -0
- package/src/components/custom/Fatigue/VelocityHero.tsx +46 -0
- package/src/components/custom/Fatigue/VerdictHero.stories.tsx +59 -0
- package/src/components/custom/Fatigue/VerdictHero.test.tsx +49 -0
- package/src/components/custom/Fatigue/VerdictHero.tsx +79 -0
- package/src/components/custom/Fatigue/fatigue-mock.test.ts +59 -0
- package/src/components/custom/Fatigue/fatigue-mock.ts +268 -0
- package/src/components/custom/Fatigue/fatigue-model.ts +129 -0
- package/src/components/custom/Fatigue/fatigue-tokens.test.ts +70 -0
- package/src/components/custom/Fatigue/fatigue-tokens.ts +180 -0
- package/src/components/custom/Fatigue/index.ts +41 -0
- package/src/components/custom/Fatigue/tempo-pacing.test.ts +176 -0
- package/src/components/custom/Fatigue/tempo-pacing.ts +112 -0
- package/src/components/custom/Workout/BaseBadge.tsx +5 -5
- package/src/components/custom/Workout/DualVelocityStrip.test.tsx +685 -0
- package/src/components/custom/Workout/ExerciseCard.test.tsx +4 -4
- package/src/components/custom/Workout/ExerciseCard.tsx +3 -1
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +7 -1
- package/src/components/custom/Workout/README.md +62 -16
- package/src/components/custom/Workout/RowInventory.stories.tsx +5 -2
- package/src/components/custom/Workout/S3FullRail.stories.tsx +13 -1
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +3 -3
- package/src/components/custom/Workout/SessionHeader.tsx +2 -2
- package/src/components/custom/Workout/SessionRail.stories.tsx +4 -1
- package/src/components/custom/Workout/SessionRail.test.tsx +43 -0
- package/src/components/custom/Workout/SessionRail.tsx +55 -18
- package/src/components/custom/Workout/SetRow.test.tsx +6 -6
- package/src/components/custom/Workout/SetRow.tsx +9 -2
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +16 -25
- package/src/components/custom/Workout/SupersetWrapper.tsx +1 -1
- package/src/components/custom/Workout/TempoDisplay.tsx +3 -7
- package/src/components/custom/Workout/VelocityStrip.compact.stories.tsx +191 -0
- package/src/components/custom/Workout/VelocityStrip.dual.stories.tsx +186 -0
- package/src/components/custom/Workout/VelocityStrip.expanded.stories.tsx +296 -0
- package/src/components/custom/Workout/VelocityStrip.hero.stories.tsx +259 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +217 -250
- package/src/components/custom/Workout/VelocityStrip.test.tsx +196 -79
- package/src/components/custom/Workout/VelocityStrip.tsx +953 -609
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +4 -1
- package/src/components/custom/Workout/WorkoutPill.tsx +2 -2
- package/src/components/custom/Workout/index.ts +3 -0
- package/src/components/custom/Workout/velocity-story-kit.tsx +438 -0
- package/src/components/custom/charts/SetBarChart.test.tsx +275 -0
- package/src/components/custom/charts/SetBarChart.tsx +651 -0
- package/src/components/custom/charts/live-rep-growth.ts +91 -0
- package/src/components/custom/index.ts +1 -0
- package/src/components/ui/surface/Surface.stories.tsx +195 -1
- package/src/components/ui/surface/Surface.test.tsx +148 -9
- package/src/components/ui/surface/Surface.tsx +37 -9
- package/src/components/ui/surface/SurfaceContext.ts +48 -8
- package/src/components/ui/surface/index.ts +1 -0
- package/src/components/ui/surface/surface.contract.test.ts +283 -0
- package/src/test/raw-color-patterns.test.ts +71 -0
- package/src/theme/ColorPalettes.stories.tsx +871 -0
- package/src/theme/ColorPrimitives.stories.tsx +300 -0
- package/src/theme/bar-paper.ts +59 -0
- package/src/theme/color-stories.coverage.test.ts +130 -0
- package/src/theme/color-story-kit.tsx +170 -0
- package/src/theme/config.ts +12 -0
- package/src/theme/elevation.ts +90 -67
- package/src/theme/global.css +35 -9
- package/src/theme/tokens/primitives.ts +40 -0
- package/src/theme/tokens/semantic.ts +63 -14
- package/tailwind.config.js +8 -0
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +0 -254
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +0 -145
- package/src/theme/ColorSystem.stories.tsx +0 -323
- package/src/theme/Colors.stories.tsx +0 -323
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getVelocityZoneName,
|
|
8
8
|
calculateVelocityLoss,
|
|
9
9
|
calculateMeanVelocity,
|
|
10
|
+
getVelocityLossColor,
|
|
10
11
|
} from './VelocityStrip'
|
|
11
12
|
|
|
12
13
|
const sampleVelocities = [1.1, 0.95, 0.82, 0.68, 0.45]
|
|
@@ -29,7 +30,7 @@ describe('VelocityStrip expanded (framed chart)', () => {
|
|
|
29
30
|
).toBeInTheDocument()
|
|
30
31
|
})
|
|
31
32
|
|
|
32
|
-
it('
|
|
33
|
+
it('collapses to the flat state (tap-to-expand, no info row) when expanded is false', () => {
|
|
33
34
|
render(<VelocityStrip velocities={sampleVelocities} expanded={false} />)
|
|
34
35
|
expect(
|
|
35
36
|
screen.getByLabelText('Velocity chart for set, 5 reps, tap to expand')
|
|
@@ -78,17 +79,28 @@ describe('VelocityStrip expanded (framed chart)', () => {
|
|
|
78
79
|
})
|
|
79
80
|
|
|
80
81
|
describe('scale', () => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
// Folded onto SetBarChart: bars are px value-height (not %); the bare spotlight has static
|
|
83
|
+
// heights (no expand animation), so we assert the taller-rep-reads-taller scaling relationship.
|
|
84
|
+
const barPx = (id: string) => parseFloat(getComputedStyle(screen.getByTestId(id)).height)
|
|
85
|
+
const bareScale = {
|
|
86
|
+
variant: 'expanded' as const,
|
|
87
|
+
showNumbers: false,
|
|
88
|
+
showInfo: false,
|
|
89
|
+
height: 60,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
it('peak (default) scales the tallest rep taller than a smaller one', () => {
|
|
93
|
+
render(<VelocityStrip {...bareScale} velocities={[1.0, 0.5]} />)
|
|
94
|
+
expect(barPx('velocity-bar-0')).toBeGreaterThan(barPx('velocity-bar-1'))
|
|
95
|
+
// peak: 1.0 fills nearly the whole 60px plot.
|
|
96
|
+
expect(barPx('velocity-bar-0')).toBeGreaterThan(52)
|
|
85
97
|
})
|
|
86
98
|
|
|
87
|
-
it('fixed scales against
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
expect(
|
|
91
|
-
expect(
|
|
99
|
+
it('fixed scales against a ceiling above the set max (bars shorter than peak)', () => {
|
|
100
|
+
render(<VelocityStrip {...bareScale} scale="fixed" velocities={[1.0, 0.5]} />)
|
|
101
|
+
// fixed ceiling 1.15 > 1.0, so the tallest bar no longer fills the plot; 0.5 is ~half of 1.0.
|
|
102
|
+
expect(barPx('velocity-bar-0')).toBeLessThan(56)
|
|
103
|
+
expect(barPx('velocity-bar-1')).toBeLessThan(barPx('velocity-bar-0'))
|
|
92
104
|
})
|
|
93
105
|
})
|
|
94
106
|
|
|
@@ -116,31 +128,31 @@ describe('VelocityStrip expanded (framed chart)', () => {
|
|
|
116
128
|
})
|
|
117
129
|
})
|
|
118
130
|
|
|
119
|
-
describe('VelocityStrip
|
|
120
|
-
it('renders the flat
|
|
121
|
-
render(<VelocityStrip velocities={sampleVelocities} variant="
|
|
122
|
-
expect(screen.getByTestId('velocity-strip-
|
|
131
|
+
describe('VelocityStrip compact variant (flat resting strip)', () => {
|
|
132
|
+
it('renders the flat compact strip', () => {
|
|
133
|
+
render(<VelocityStrip velocities={sampleVelocities} variant="compact" />)
|
|
134
|
+
expect(screen.getByTestId('velocity-strip-compact')).toBeInTheDocument()
|
|
123
135
|
})
|
|
124
136
|
|
|
125
137
|
it('does not respond to press', () => {
|
|
126
|
-
render(<VelocityStrip velocities={sampleVelocities} variant="
|
|
138
|
+
render(<VelocityStrip velocities={sampleVelocities} variant="compact" onToggle={() => {}} />)
|
|
127
139
|
expect(screen.queryByTestId('velocity-strip-pressable')).not.toBeInTheDocument()
|
|
128
140
|
})
|
|
129
141
|
|
|
130
142
|
it('renders one bar per rep', () => {
|
|
131
|
-
render(<VelocityStrip velocities={sampleVelocities} variant="
|
|
143
|
+
render(<VelocityStrip velocities={sampleVelocities} variant="compact" />)
|
|
132
144
|
for (let i = 0; i < sampleVelocities.length; i++) {
|
|
133
145
|
expect(screen.getByTestId(`velocity-bar-${i}`)).toBeInTheDocument()
|
|
134
146
|
}
|
|
135
147
|
})
|
|
136
148
|
|
|
137
149
|
it('has no accessibility violations', async () => {
|
|
138
|
-
const { container } = render(<VelocityStrip velocities={sampleVelocities} variant="
|
|
150
|
+
const { container } = render(<VelocityStrip velocities={sampleVelocities} variant="compact" />)
|
|
139
151
|
expect(await axe(container)).toHaveNoViolations()
|
|
140
152
|
})
|
|
141
153
|
|
|
142
|
-
it('does not apply live marking', () => {
|
|
143
|
-
render(<VelocityStrip velocities={sampleVelocities} liveRepIndex={0} variant="
|
|
154
|
+
it('does not apply live marking (compact is the resting form)', () => {
|
|
155
|
+
render(<VelocityStrip velocities={sampleVelocities} liveRepIndex={0} variant="compact" />)
|
|
144
156
|
expect(screen.queryByLabelText(/latest rep/)).not.toBeInTheDocument()
|
|
145
157
|
})
|
|
146
158
|
})
|
|
@@ -216,6 +228,94 @@ describe('calculateMeanVelocity', () => {
|
|
|
216
228
|
})
|
|
217
229
|
})
|
|
218
230
|
|
|
231
|
+
// Literal hex for the four VL-band tones (green-300 / amber-300 / orange-400 / red-600),
|
|
232
|
+
// the SAME stops FatigueMeter's VL10/VL20/VL30 default thresholds use.
|
|
233
|
+
const VL_GREEN = '#2ED573'
|
|
234
|
+
const VL_YELLOW = '#F9B415'
|
|
235
|
+
const VL_ORANGE = '#FF7900'
|
|
236
|
+
const VL_RED = '#D14343'
|
|
237
|
+
|
|
238
|
+
describe('getVelocityLossColor', () => {
|
|
239
|
+
it('reads green below the VL10 threshold', () => {
|
|
240
|
+
expect(getVelocityLossColor(0)).toBe(VL_GREEN)
|
|
241
|
+
expect(getVelocityLossColor(9.9)).toBe(VL_GREEN)
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
it('reads gold/amber from VL10 up to (not including) VL20', () => {
|
|
245
|
+
expect(getVelocityLossColor(10)).toBe(VL_YELLOW)
|
|
246
|
+
expect(getVelocityLossColor(19.9)).toBe(VL_YELLOW)
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
it('reads orange from VL20 up to (not including) VL30', () => {
|
|
250
|
+
expect(getVelocityLossColor(20)).toBe(VL_ORANGE)
|
|
251
|
+
expect(getVelocityLossColor(29.9)).toBe(VL_ORANGE)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('reads red at and past VL30', () => {
|
|
255
|
+
expect(getVelocityLossColor(30)).toBe(VL_RED)
|
|
256
|
+
expect(getVelocityLossColor(100)).toBe(VL_RED)
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
describe('VelocityStrip barColor prop', () => {
|
|
261
|
+
// Best rep is 1.0 m/s, so per-rep loss lands exactly on the VL10/20/30 boundaries:
|
|
262
|
+
// 0%, 10%, 20%, 30% loss — one rep in each band.
|
|
263
|
+
const decliningSet = [1.0, 0.9, 0.8, 0.7]
|
|
264
|
+
|
|
265
|
+
it('defaults to loss coloring (the landed default is barColor="loss")', () => {
|
|
266
|
+
render(<VelocityStrip velocities={decliningSet} variant="compact" />)
|
|
267
|
+
// Loss from the set's own best (1.0): 0% / 10% / 20% / 30% -> green / yellow / orange / red.
|
|
268
|
+
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: VL_GREEN })
|
|
269
|
+
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: VL_YELLOW })
|
|
270
|
+
expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ backgroundColor: VL_ORANGE })
|
|
271
|
+
expect(screen.getByTestId('velocity-bar-3')).toHaveStyle({ backgroundColor: VL_RED })
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
it('explicit barColor="zone" matches the default', () => {
|
|
275
|
+
render(<VelocityStrip velocities={decliningSet} variant="compact" barColor="zone" />)
|
|
276
|
+
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: VL_YELLOW })
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('barColor="loss" colors each bar green→red by loss from the set\'s own best', () => {
|
|
280
|
+
render(<VelocityStrip velocities={decliningSet} variant="compact" barColor="loss" />)
|
|
281
|
+
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: VL_GREEN })
|
|
282
|
+
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: VL_YELLOW })
|
|
283
|
+
expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ backgroundColor: VL_ORANGE })
|
|
284
|
+
expect(screen.getByTestId('velocity-bar-3')).toHaveStyle({ backgroundColor: VL_RED })
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
it('barColor="loss" reads a slow-but-still-best rep as green (loss-relative, not absolute)', () => {
|
|
288
|
+
// 0.3 m/s would be "vel-red" under the absolute zone scale, but as the set's
|
|
289
|
+
// (single) best rep its loss is 0 — loss-relative coloring reads it green.
|
|
290
|
+
render(<VelocityStrip velocities={[0.3]} variant="compact" barColor="loss" />)
|
|
291
|
+
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: VL_GREEN })
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('barColor="loss" ignores a supplied zones prop (it is a separate scale)', () => {
|
|
295
|
+
render(
|
|
296
|
+
<VelocityStrip
|
|
297
|
+
velocities={decliningSet}
|
|
298
|
+
variant="compact"
|
|
299
|
+
barColor="loss"
|
|
300
|
+
zones={compoundBands}
|
|
301
|
+
/>
|
|
302
|
+
)
|
|
303
|
+
expect(screen.getByTestId('velocity-bar-3')).toHaveStyle({ backgroundColor: VL_RED })
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it('barColor="loss" guards all-zero velocities to green (no NaN/best<=0)', () => {
|
|
307
|
+
render(<VelocityStrip velocities={[0, 0, 0]} variant="compact" barColor="loss" />)
|
|
308
|
+
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: VL_GREEN })
|
|
309
|
+
expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ backgroundColor: VL_GREEN })
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
it('renders no bars (and does not crash) for an empty velocities array', () => {
|
|
313
|
+
render(<VelocityStrip velocities={[]} variant="compact" barColor="loss" />)
|
|
314
|
+
expect(screen.getByTestId('velocity-strip-compact')).toBeInTheDocument()
|
|
315
|
+
expect(screen.queryAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(0)
|
|
316
|
+
})
|
|
317
|
+
})
|
|
318
|
+
|
|
219
319
|
// WA-shaped 5-band zone set (compound movement-class defaults, mean m/s).
|
|
220
320
|
const compoundBands = [
|
|
221
321
|
{ id: 'grinding', label: 'Grinding', min: 0, max: 0.35 },
|
|
@@ -226,14 +326,29 @@ const compoundBands = [
|
|
|
226
326
|
] as const
|
|
227
327
|
|
|
228
328
|
describe('VelocityStrip zones prop', () => {
|
|
329
|
+
// The band mapping is the `zone` scale; the landed default is `loss`, so these pass `barColor="zone"`.
|
|
229
330
|
it('colors bars from the supplied bands (mini)', () => {
|
|
230
|
-
render(
|
|
331
|
+
render(
|
|
332
|
+
<VelocityStrip
|
|
333
|
+
velocities={[1.1, 0.45]}
|
|
334
|
+
zones={compoundBands}
|
|
335
|
+
variant="compact"
|
|
336
|
+
barColor="zone"
|
|
337
|
+
/>
|
|
338
|
+
)
|
|
231
339
|
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ED573' })
|
|
232
340
|
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: '#D14343' })
|
|
233
341
|
})
|
|
234
342
|
|
|
235
343
|
it('gives grinding and maximalStrength DISTINCT reds (5-band, no collapse)', () => {
|
|
236
|
-
render(
|
|
344
|
+
render(
|
|
345
|
+
<VelocityStrip
|
|
346
|
+
velocities={[0.45, 0.2]}
|
|
347
|
+
zones={compoundBands}
|
|
348
|
+
variant="compact"
|
|
349
|
+
barColor="zone"
|
|
350
|
+
/>
|
|
351
|
+
)
|
|
237
352
|
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#D14343' })
|
|
238
353
|
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: '#A4221C' })
|
|
239
354
|
})
|
|
@@ -244,7 +359,7 @@ describe('VelocityStrip zones prop', () => {
|
|
|
244
359
|
})
|
|
245
360
|
|
|
246
361
|
it('falls back to the default scale when zones is absent', () => {
|
|
247
|
-
render(<VelocityStrip velocities={[1.1]} variant="
|
|
362
|
+
render(<VelocityStrip velocities={[1.1]} variant="compact" />)
|
|
248
363
|
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ED573' })
|
|
249
364
|
})
|
|
250
365
|
})
|
|
@@ -257,16 +372,17 @@ describe('VelocityStrip live mode', () => {
|
|
|
257
372
|
else delete (window as { matchMedia?: unknown }).matchMedia
|
|
258
373
|
})
|
|
259
374
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
375
|
+
// The bars are now SetBarChart's (a11y-hidden; live-grow is the visual marking via `liveRepIndex`).
|
|
376
|
+
const spotlight = { variant: 'expanded' as const, showNumbers: false, showInfo: false }
|
|
377
|
+
|
|
378
|
+
it('renders the newest rep in the live spotlight', () => {
|
|
379
|
+
render(<VelocityStrip {...spotlight} velocities={sampleVelocities} liveRepIndex={3} />)
|
|
380
|
+
expect(screen.getByTestId('velocity-bar-3')).toBeInTheDocument()
|
|
263
381
|
})
|
|
264
382
|
|
|
265
|
-
it('
|
|
266
|
-
render(<VelocityStrip velocities={sampleVelocities} liveRepIndex={0} />)
|
|
267
|
-
expect(
|
|
268
|
-
screen.getByLabelText(/Rep 1: 1\.10 meters per second, latest rep, new set peak$/)
|
|
269
|
-
).toBeInTheDocument()
|
|
383
|
+
it('renders a new-set-peak live rep without crashing', () => {
|
|
384
|
+
render(<VelocityStrip {...spotlight} velocities={sampleVelocities} liveRepIndex={0} />)
|
|
385
|
+
expect(screen.getByTestId('velocity-bar-0')).toBeInTheDocument()
|
|
270
386
|
})
|
|
271
387
|
|
|
272
388
|
it('renders without animation when prefers-reduced-motion is set', () => {
|
|
@@ -275,48 +391,56 @@ describe('VelocityStrip live mode', () => {
|
|
|
275
391
|
addEventListener: vi.fn(),
|
|
276
392
|
removeEventListener: vi.fn(),
|
|
277
393
|
}) as unknown as typeof window.matchMedia
|
|
278
|
-
render(<VelocityStrip velocities={sampleVelocities} liveRepIndex={0} />)
|
|
394
|
+
render(<VelocityStrip {...spotlight} velocities={sampleVelocities} liveRepIndex={0} />)
|
|
279
395
|
expect(screen.getByTestId('velocity-bar-0')).toBeInTheDocument()
|
|
280
396
|
})
|
|
281
397
|
})
|
|
282
398
|
|
|
283
399
|
describe('VelocityStrip all-zero velocities (NaN guard)', () => {
|
|
284
|
-
it('emits a valid
|
|
285
|
-
render(
|
|
286
|
-
|
|
287
|
-
|
|
400
|
+
it('emits a valid (non-NaN) bar height when every velocity is zero', () => {
|
|
401
|
+
render(
|
|
402
|
+
<VelocityStrip
|
|
403
|
+
velocities={[0, 0, 0]}
|
|
404
|
+
variant="expanded"
|
|
405
|
+
showNumbers={false}
|
|
406
|
+
showInfo={false}
|
|
407
|
+
/>
|
|
408
|
+
)
|
|
409
|
+
const h = getComputedStyle(screen.getByTestId('velocity-bar-0')).height
|
|
410
|
+
expect(h).not.toContain('NaN')
|
|
411
|
+
expect(parseFloat(h)).toBeGreaterThanOrEqual(0)
|
|
288
412
|
})
|
|
289
413
|
})
|
|
290
414
|
|
|
291
415
|
// Literal-hex slot colors (must match the component's tokens exactly).
|
|
292
|
-
|
|
416
|
+
// TODO_SOLID = the LANDED surface-relative solid to-do tone (base/dark plane blended toward the
|
|
417
|
+
// on-surface neutral) — replaced the old fixed charcoal #2C2C2C. Solid fill, not a dashed outline.
|
|
418
|
+
const TODO_SOLID = '#4C4E55'
|
|
293
419
|
const VARIABLE_CYAN = '#0B3149'
|
|
294
420
|
const CONTINUE_OUTLINE = '#22465F'
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
describe('VelocityStrip set-type modes (mini)', () => {
|
|
300
|
-
it('straight: done reps colored + grey todo remainder to the planned count', () => {
|
|
421
|
+
describe('VelocityStrip set-type modes (compact)', () => {
|
|
422
|
+
// compact composes SetBarChart, so the per-slot geometry (WIDE chunk-notch px, container gap) is
|
|
423
|
+
// covered in SetBarChart.test; here we verify VelocityStrip maps each set type to the right slots.
|
|
424
|
+
it('straight: done reps colored + solid todo remainder to the planned count', () => {
|
|
301
425
|
render(
|
|
302
426
|
<VelocityStrip
|
|
303
427
|
set={{ type: 'straight', velocities: [1.1, 0.9, 0.8], planned: 5 }}
|
|
304
|
-
variant="
|
|
428
|
+
variant="compact"
|
|
305
429
|
/>
|
|
306
430
|
)
|
|
307
431
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(3)
|
|
308
432
|
expect(screen.getAllByTestId('velocity-slot-todo')).toHaveLength(2)
|
|
309
433
|
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ED573' })
|
|
310
434
|
expect(screen.getAllByTestId('velocity-slot-todo')[0]).toHaveStyle({
|
|
311
|
-
backgroundColor:
|
|
435
|
+
backgroundColor: TODO_SOLID,
|
|
312
436
|
})
|
|
313
437
|
})
|
|
314
438
|
|
|
315
|
-
it('range: committed
|
|
439
|
+
it('range: committed reps + solid floor todo + a cyan variable window floor..max', () => {
|
|
316
440
|
render(
|
|
317
441
|
<VelocityStrip
|
|
318
442
|
set={{ type: 'range', velocities: [1.1, 1.0, 0.9, 0.85, 0.8, 0.7], floor: 8, max: 12 }}
|
|
319
|
-
variant="
|
|
443
|
+
variant="compact"
|
|
320
444
|
/>
|
|
321
445
|
)
|
|
322
446
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(6)
|
|
@@ -327,15 +451,15 @@ describe('VelocityStrip set-type modes (mini)', () => {
|
|
|
327
451
|
})
|
|
328
452
|
})
|
|
329
453
|
|
|
330
|
-
it('amrap: done reps + one trailing
|
|
331
|
-
render(<VelocityStrip set={{ type: 'amrap', velocities: [1.1, 1.0, 0.9] }} variant="
|
|
454
|
+
it('amrap: done reps + one trailing cyan-outline continue slot', () => {
|
|
455
|
+
render(<VelocityStrip set={{ type: 'amrap', velocities: [1.1, 1.0, 0.9] }} variant="compact" />)
|
|
332
456
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(3)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
457
|
+
expect(screen.getByTestId('velocity-slot-continue')).toHaveStyle({
|
|
458
|
+
borderTopColor: CONTINUE_OUTLINE,
|
|
459
|
+
})
|
|
336
460
|
})
|
|
337
461
|
|
|
338
|
-
it('drop:
|
|
462
|
+
it('drop: one bar per rep across the sub-loads', () => {
|
|
339
463
|
render(
|
|
340
464
|
<VelocityStrip
|
|
341
465
|
set={{
|
|
@@ -346,17 +470,13 @@ describe('VelocityStrip set-type modes (mini)', () => {
|
|
|
346
470
|
[0.6, 0.5],
|
|
347
471
|
],
|
|
348
472
|
}}
|
|
349
|
-
variant="
|
|
473
|
+
variant="compact"
|
|
350
474
|
/>
|
|
351
475
|
)
|
|
352
476
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(6)
|
|
353
|
-
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ marginLeft: '0px' })
|
|
354
|
-
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ marginLeft: REP_SLOT_ML })
|
|
355
|
-
expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
|
|
356
|
-
expect(screen.getByTestId('velocity-bar-4')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
|
|
357
477
|
})
|
|
358
478
|
|
|
359
|
-
it('myo: activation + clusters
|
|
479
|
+
it('myo: activation + clusters as reps, open adds a cyan continue', () => {
|
|
360
480
|
render(
|
|
361
481
|
<VelocityStrip
|
|
362
482
|
set={{
|
|
@@ -368,16 +488,14 @@ describe('VelocityStrip set-type modes (mini)', () => {
|
|
|
368
488
|
],
|
|
369
489
|
open: true,
|
|
370
490
|
}}
|
|
371
|
-
variant="
|
|
491
|
+
variant="compact"
|
|
372
492
|
/>
|
|
373
493
|
)
|
|
374
494
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(7)
|
|
375
|
-
expect(screen.getByTestId('velocity-bar-3')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
|
|
376
|
-
expect(screen.getByTestId('velocity-bar-5')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
|
|
377
495
|
expect(screen.getByTestId('velocity-slot-continue')).toBeInTheDocument()
|
|
378
496
|
})
|
|
379
497
|
|
|
380
|
-
it('cluster: fixed count
|
|
498
|
+
it('cluster: fixed count + solid planned remainder', () => {
|
|
381
499
|
render(
|
|
382
500
|
<VelocityStrip
|
|
383
501
|
set={{
|
|
@@ -386,22 +504,19 @@ describe('VelocityStrip set-type modes (mini)', () => {
|
|
|
386
504
|
groupSize: 2,
|
|
387
505
|
planned: 8,
|
|
388
506
|
}}
|
|
389
|
-
variant="
|
|
507
|
+
variant="compact"
|
|
390
508
|
/>
|
|
391
509
|
)
|
|
392
510
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(5)
|
|
393
511
|
expect(screen.getAllByTestId('velocity-slot-todo')).toHaveLength(3)
|
|
394
|
-
expect(screen.getByTestId('velocity-bar-2')).toHaveStyle({ marginLeft: WIDE_SLOT_ML })
|
|
395
|
-
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ marginLeft: REP_SLOT_ML })
|
|
396
512
|
})
|
|
397
513
|
|
|
398
|
-
it('back-compat: a velocities-only
|
|
399
|
-
render(<VelocityStrip velocities={sampleVelocities} variant="
|
|
514
|
+
it('back-compat: a velocities-only compact strip renders rep bars, no todo', () => {
|
|
515
|
+
render(<VelocityStrip velocities={sampleVelocities} variant="compact" />)
|
|
400
516
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(sampleVelocities.length)
|
|
401
517
|
expect(screen.queryByTestId('velocity-slot-todo')).not.toBeInTheDocument()
|
|
402
|
-
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ marginLeft: '0px' })
|
|
403
518
|
expect(screen.getByTestId('velocity-bar-0')).toHaveStyle({ backgroundColor: '#2ED573' })
|
|
404
|
-
expect(screen.getByTestId('velocity-strip-
|
|
519
|
+
expect(screen.getByTestId('velocity-strip-compact')).toBeInTheDocument()
|
|
405
520
|
})
|
|
406
521
|
|
|
407
522
|
it('derives the summary from a set descriptor (mean / loss info row)', () => {
|
|
@@ -410,14 +525,14 @@ describe('VelocityStrip set-type modes (mini)', () => {
|
|
|
410
525
|
})
|
|
411
526
|
|
|
412
527
|
it('labels the strip per set type', () => {
|
|
413
|
-
render(<VelocityStrip set={{ type: 'amrap', velocities: [1.0, 0.9] }} variant="
|
|
528
|
+
render(<VelocityStrip set={{ type: 'amrap', velocities: [1.0, 0.9] }} variant="compact" />)
|
|
414
529
|
expect(
|
|
415
530
|
screen.getByLabelText('Velocity strip, AMRAP set, 2 reps and counting')
|
|
416
531
|
).toBeInTheDocument()
|
|
417
532
|
})
|
|
418
533
|
|
|
419
534
|
it('renders nothing when neither velocities nor set is provided', () => {
|
|
420
|
-
const { container } = render(<VelocityStrip variant="
|
|
535
|
+
const { container } = render(<VelocityStrip variant="compact" />)
|
|
421
536
|
expect(container.firstChild).toBeNull()
|
|
422
537
|
})
|
|
423
538
|
})
|
|
@@ -428,7 +543,7 @@ describe('VelocityStrip set-type modes (expanded framed)', () => {
|
|
|
428
543
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(2)
|
|
429
544
|
expect(screen.getAllByTestId('velocity-slot-todo')).toHaveLength(2)
|
|
430
545
|
expect(screen.getAllByTestId('velocity-slot-todo')[0]).toHaveStyle({
|
|
431
|
-
backgroundColor:
|
|
546
|
+
backgroundColor: TODO_SOLID,
|
|
432
547
|
})
|
|
433
548
|
})
|
|
434
549
|
|
|
@@ -459,8 +574,8 @@ describe('VelocityStrip expanded bare strip (spotlight: numbers + info off)', ()
|
|
|
459
574
|
set={{ type: 'straight', velocities: [1.0, 0.8], planned: 2 }}
|
|
460
575
|
/>
|
|
461
576
|
)
|
|
462
|
-
expect(screen.getByTestId('velocity-strip-
|
|
463
|
-
expect(screen.queryByTestId('velocity-strip-
|
|
577
|
+
expect(screen.getByTestId('velocity-strip-spotlight')).toBeInTheDocument()
|
|
578
|
+
expect(screen.queryByTestId('velocity-strip-compact')).not.toBeInTheDocument()
|
|
464
579
|
expect(screen.queryByTestId('velocity-strip')).not.toBeInTheDocument()
|
|
465
580
|
})
|
|
466
581
|
|
|
@@ -500,7 +615,8 @@ describe('VelocityStrip expanded bare strip (spotlight: numbers + info off)', ()
|
|
|
500
615
|
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(1)
|
|
501
616
|
const stubs = screen.getAllByTestId('velocity-slot-todo')
|
|
502
617
|
expect(stubs).toHaveLength(2)
|
|
503
|
-
|
|
618
|
+
// Folded onto SetBarChart: the solid to-do stub is the shared 9px height + surface-relative tone.
|
|
619
|
+
expect(stubs[0]).toHaveStyle({ height: '9px', backgroundColor: TODO_SOLID })
|
|
504
620
|
})
|
|
505
621
|
|
|
506
622
|
it('colors performed reps from the default effort scale', () => {
|
|
@@ -514,7 +630,7 @@ describe('VelocityStrip expanded bare strip (spotlight: numbers + info off)', ()
|
|
|
514
630
|
expect(screen.getByTestId('velocity-bar-1')).toHaveStyle({ backgroundColor: '#D14343' })
|
|
515
631
|
})
|
|
516
632
|
|
|
517
|
-
it('carries the set-type
|
|
633
|
+
it('carries the set-type chunk encoding (drop sub-loads) as one bar per rep', () => {
|
|
518
634
|
render(
|
|
519
635
|
<VelocityStrip
|
|
520
636
|
{...bareProps}
|
|
@@ -527,8 +643,9 @@ describe('VelocityStrip expanded bare strip (spotlight: numbers + info off)', ()
|
|
|
527
643
|
}}
|
|
528
644
|
/>
|
|
529
645
|
)
|
|
530
|
-
|
|
531
|
-
|
|
646
|
+
// Folded onto SetBarChart: 4 rep bars; the WIDE chunk-notch (now a proportional column margin)
|
|
647
|
+
// is covered in SetBarChart.test.
|
|
648
|
+
expect(screen.getAllByTestId(/^velocity-bar-\d+$/)).toHaveLength(4)
|
|
532
649
|
})
|
|
533
650
|
|
|
534
651
|
it('has no chrome: no per-bar labels, no info row, no pressable', () => {
|
|
@@ -576,7 +693,7 @@ describe('VelocityStrip hero variant', () => {
|
|
|
576
693
|
expect(screen.getByTestId('velocity-label-0')).toHaveTextContent('0.90')
|
|
577
694
|
})
|
|
578
695
|
|
|
579
|
-
it('draws a
|
|
696
|
+
it('draws a to-do placeholder for each rep still to come (target − done)', () => {
|
|
580
697
|
render(<VelocityStrip velocities={heroSet} variant="hero" targetReps={8} />)
|
|
581
698
|
expect(screen.getAllByTestId('velocity-slot-todo')).toHaveLength(4)
|
|
582
699
|
})
|