@titan-design/react-ui 0.6.0 → 0.8.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 +12 -7
- package/dist/bodymap.js.map +1 -1
- package/dist/bodymap.mjs +12 -7
- package/dist/bodymap.mjs.map +1 -1
- package/dist/index.d.mts +956 -128
- package/dist/index.d.ts +956 -128
- package/dist/index.js +2873 -981
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2826 -982
- package/dist/index.mjs.map +1 -1
- package/dist/{pages-MO0JCySL.d.mts → pages-DCFBqp79.d.mts} +289 -57
- package/dist/{pages-D7Jlssvp.d.ts → pages-_fI3-x7Z.d.ts} +289 -57
- package/dist/pages.d.mts +1 -1
- package/dist/pages.d.ts +1 -1
- package/dist/pages.js +1659 -799
- package/dist/pages.js.map +1 -1
- package/dist/pages.mjs +1660 -800
- package/dist/pages.mjs.map +1 -1
- package/dist/theme/index.d.mts +16 -82
- package/dist/theme/index.d.ts +16 -82
- package/dist/theme/index.js +13 -115
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +13 -115
- package/dist/theme/index.mjs.map +1 -1
- package/dist/theme/tokens-css.js +14 -6
- package/dist/theme/tokens-css.js.map +1 -1
- package/dist/theme/tokens-css.mjs +14 -6
- package/dist/theme/tokens-css.mjs.map +1 -1
- package/package.json +1 -1
- package/src/arch/Architecture.stories.tsx +870 -0
- package/src/arch/annotations.json +48 -0
- package/src/arch/arch-graph.json +4301 -0
- package/src/components/custom/CircularTimer/CircularTimer.stories.tsx +90 -0
- package/src/components/custom/CircularTimer/CircularTimer.test.tsx +62 -0
- package/src/components/custom/CircularTimer/CircularTimer.tsx +112 -0
- package/src/components/custom/CircularTimer/index.ts +1 -0
- package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
- package/src/components/custom/EmptyState/EmptyState.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.stories.tsx +1 -1
- package/src/components/custom/Metric/Metric.stories.tsx +1 -1
- package/src/components/custom/Scatter/Scatter.stories.tsx +1 -1
- package/src/components/custom/Sidebar/Sidebar.stories.tsx +1 -1
- package/src/components/custom/Table/Table.stories.tsx +1 -1
- package/src/components/custom/TimerReadout/TimerReadout.stories.tsx +62 -0
- package/src/components/custom/TimerReadout/TimerReadout.test.tsx +72 -0
- package/src/components/custom/TimerReadout/TimerReadout.tsx +75 -0
- package/src/components/custom/TimerReadout/index.ts +1 -0
- package/src/components/custom/Treemap/Treemap.stories.tsx +1 -1
- package/src/components/custom/Typography/Typography.stories.tsx +1 -1
- package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +21 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +16 -13
- package/src/components/custom/Workout/ActiveWorkoutPage.tsx +9 -15
- package/src/components/custom/Workout/BaseBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMap.stories.tsx +1 -1
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +1 -1
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +1 -1
- package/src/components/custom/Workout/DeviationBar.stories.tsx +1 -1
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +51 -66
- package/src/components/custom/Workout/ExerciseCard.test.tsx +146 -143
- package/src/components/custom/Workout/ExerciseCard.tsx +176 -198
- package/src/components/custom/Workout/ExerciseCardHeading.stories.tsx +99 -0
- package/src/components/custom/Workout/ExerciseCardHeading.test.tsx +72 -0
- package/src/components/custom/Workout/ExerciseCardHeading.tsx +43 -0
- package/src/components/custom/Workout/ExerciseCardUnified.stories.tsx +151 -0
- package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +5 -3
- package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +4 -4
- package/src/components/custom/Workout/ExerciseDetailPage.tsx +4 -5
- package/src/components/custom/Workout/ExerciseHeading.stories.tsx +81 -0
- package/src/components/custom/Workout/ExerciseHeading.test.tsx +77 -0
- package/src/components/custom/Workout/ExerciseHeading.tsx +85 -0
- package/src/components/custom/Workout/ExerciseIndicator.stories.tsx +110 -0
- package/src/components/custom/Workout/ExerciseIndicator.test.tsx +93 -0
- package/src/components/custom/Workout/ExerciseIndicator.tsx +120 -0
- package/src/components/custom/Workout/FatigueMeter.stories.tsx +51 -0
- package/src/components/custom/Workout/FatigueMeter.test.tsx +85 -0
- package/src/components/custom/Workout/FatigueMeter.tsx +86 -0
- package/src/components/custom/Workout/InputBar.stories.tsx +1 -1
- package/src/components/custom/Workout/IntensityBar.stories.tsx +1 -1
- package/src/components/custom/Workout/LiveAuraFrame.stories.tsx +153 -0
- package/src/components/custom/Workout/LiveAuraFrame.test.tsx +74 -0
- package/src/components/custom/Workout/LiveAuraFrame.tsx +105 -0
- package/src/components/custom/Workout/MesoCard.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoProgressBar.stories.tsx +1 -1
- package/src/components/custom/Workout/MesoStatusCard.stories.tsx +1 -1
- package/src/components/custom/Workout/MetricTiles.stories.tsx +60 -0
- package/src/components/custom/Workout/MetricTiles.test.tsx +58 -0
- package/src/components/custom/Workout/MetricTiles.tsx +48 -0
- package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +1 -1
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +1 -1
- package/src/components/custom/Workout/PrBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +1 -1
- package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +4 -16
- package/src/components/custom/Workout/ProgramPlanningPage.tsx +8 -6
- package/src/components/custom/Workout/README.md +193 -4
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +1 -1
- package/src/components/custom/Workout/RestTimer.stories.tsx +133 -3
- package/src/components/custom/Workout/RestTimer.test.tsx +73 -0
- package/src/components/custom/Workout/RestTimer.tsx +155 -59
- package/src/components/custom/Workout/RowInventory.stories.tsx +405 -0
- package/src/components/custom/Workout/S3FullRail.stories.tsx +34 -0
- package/src/components/custom/Workout/S3SessionPace.stories.tsx +95 -0
- package/src/components/custom/Workout/S3SessionRailHeading.stories.tsx +61 -0
- package/src/components/custom/Workout/S3SetTypes.stories.tsx +176 -0
- package/src/components/custom/Workout/S3WorkoutExpansion.stories.tsx +240 -0
- package/src/components/custom/Workout/ScheduleTiles.stories.tsx +40 -0
- package/src/components/custom/Workout/ScheduleTiles.test.tsx +34 -0
- package/src/components/custom/Workout/ScheduleTiles.tsx +70 -0
- package/src/components/custom/Workout/SegmentedBar.stories.tsx +99 -0
- package/src/components/custom/Workout/SegmentedBar.test.tsx +77 -0
- package/src/components/custom/Workout/SegmentedBar.tsx +172 -0
- package/src/components/custom/Workout/SegmentedProgressBar.stories.tsx +50 -0
- package/src/components/custom/Workout/SegmentedProgressBar.test.tsx +80 -0
- package/src/components/custom/Workout/SegmentedProgressBar.tsx +67 -0
- package/src/components/custom/Workout/SessionHeader.stories.tsx +121 -0
- package/src/components/custom/Workout/SessionHeader.test.tsx +122 -0
- package/src/components/custom/Workout/SessionHeader.tsx +152 -0
- package/src/components/custom/Workout/SessionRail.stories.tsx +153 -0
- package/src/components/custom/Workout/SessionRail.test.tsx +86 -0
- package/src/components/custom/Workout/SessionRail.tsx +126 -0
- package/src/components/custom/Workout/SetBar.stories.tsx +124 -0
- package/src/components/custom/Workout/SetBar.test.tsx +123 -0
- package/src/components/custom/Workout/SetBar.tsx +171 -0
- package/src/components/custom/Workout/SetRow.stories.tsx +50 -100
- package/src/components/custom/Workout/SetRow.test.tsx +94 -166
- package/src/components/custom/Workout/SetRow.tsx +169 -232
- package/src/components/custom/Workout/SetStrip.stories.tsx +154 -0
- package/src/components/custom/Workout/SetStrip.test.tsx +109 -0
- package/src/components/custom/Workout/SetStrip.tsx +71 -0
- package/src/components/custom/Workout/SetTableHeader.stories.tsx +66 -0
- package/src/components/custom/Workout/SetTableHeader.test.tsx +47 -0
- package/src/components/custom/Workout/SetTableHeader.tsx +88 -0
- package/src/components/custom/Workout/SetsRepsLoad.stories.tsx +47 -0
- package/src/components/custom/Workout/SetsRepsLoad.test.tsx +44 -0
- package/src/components/custom/Workout/SetsRepsLoad.tsx +52 -0
- package/src/components/custom/Workout/Sparkline.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusDot.stories.tsx +1 -1
- package/src/components/custom/Workout/StatusPill.stories.tsx +59 -0
- package/src/components/custom/Workout/StatusPill.test.tsx +63 -0
- package/src/components/custom/Workout/StatusPill.tsx +88 -0
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +1 -1
- package/src/components/custom/Workout/SupersetWrapper.stories.tsx +1 -1
- package/src/components/custom/Workout/TempoBar.stories.tsx +70 -1
- package/src/components/custom/Workout/TempoBar.test.tsx +119 -4
- package/src/components/custom/Workout/TempoBar.tsx +107 -22
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +12 -1
- package/src/components/custom/Workout/TempoDisplay.tsx +23 -33
- package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +1 -1
- package/src/components/custom/Workout/VelocityStrip.modalities.stories.tsx +254 -0
- package/src/components/custom/Workout/VelocityStrip.responsive.stories.tsx +145 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +262 -56
- package/src/components/custom/Workout/VelocityStrip.test.tsx +440 -141
- package/src/components/custom/Workout/VelocityStrip.tsx +776 -141
- package/src/components/custom/Workout/VolumeLandmarkBar.stories.tsx +73 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.test.tsx +119 -0
- package/src/components/custom/Workout/VolumeLandmarkBar.tsx +154 -0
- package/src/components/custom/Workout/WeekRow.stories.tsx +1 -1
- package/src/components/custom/Workout/WeightBadge.stories.tsx +1 -1
- package/src/components/custom/Workout/WorkoutCard.stories.tsx +2 -6
- package/src/components/custom/Workout/WorkoutCard.test.tsx +13 -46
- package/src/components/custom/Workout/WorkoutPill.stories.tsx +1 -1
- package/src/components/custom/Workout/ZoneTrack.stories.tsx +139 -0
- package/src/components/custom/Workout/ZoneTrack.test.tsx +234 -0
- package/src/components/custom/Workout/ZoneTrack.tsx +373 -0
- package/src/components/custom/Workout/index.ts +55 -2
- package/src/components/custom/Workout/metricText.stories.tsx +69 -0
- package/src/components/custom/Workout/metricText.tsx +34 -0
- package/src/components/custom/Workout/paceTone.test.ts +29 -0
- package/src/components/custom/Workout/paceTone.ts +27 -0
- package/src/components/custom/Workout/setHeadingKit.tsx +177 -0
- package/src/components/custom/index.ts +2 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/icons/icons.stories.tsx +67 -2
- package/src/components/icons/icons.test.tsx +32 -2
- package/src/components/icons/icons.tsx +108 -0
- package/src/components/shell/BrandLockup.stories.tsx +3 -3
- package/src/components/shell/DashboardShell.stories.tsx +65 -0
- package/src/components/shell/DashboardShell.test.tsx +42 -0
- package/src/components/shell/DashboardShell.tsx +74 -0
- package/src/components/shell/DeviceIndicator.stories.tsx +2 -2
- package/src/components/shell/DeviceMenu.stories.tsx +5 -5
- package/src/components/shell/DeviceRow.stories.tsx +3 -3
- package/src/components/shell/NavItem.stories.tsx +52 -0
- package/src/components/shell/NavItem.test.tsx +35 -0
- package/src/components/shell/NavItem.tsx +75 -0
- package/src/components/shell/README.md +30 -0
- package/src/components/shell/SessionRailSpecimen.stories.tsx +753 -0
- package/src/components/shell/SessionStatePill.stories.tsx +3 -3
- package/src/components/shell/SessionStatePill.tsx +1 -1
- package/src/components/shell/SideNav.stories.tsx +81 -0
- package/src/components/shell/SideNav.test.tsx +51 -0
- package/src/components/shell/SideNav.tsx +74 -0
- package/src/components/shell/TopBar.stories.tsx +5 -5
- package/src/components/shell/index.ts +5 -0
- package/src/components/ui/alert/Alert.stories.tsx +86 -3
- package/src/components/ui/alert/Alert.test.tsx +52 -0
- package/src/components/ui/alert/Alert.tsx +54 -20
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/components/ui/avatar/Avatar.stories.tsx +1 -1
- package/src/components/ui/badge/Badge.stories.tsx +1 -1
- package/src/components/ui/breadcrumbs/Breadcrumbs.stories.tsx +1 -1
- package/src/components/ui/button/Button.stories.tsx +1 -1
- package/src/components/ui/card/Card.stories.tsx +1 -1
- package/src/components/ui/checkbox/Checkbox.stories.tsx +1 -1
- package/src/components/ui/chip/Chip.stories.tsx +1 -1
- package/src/components/ui/collapse/Collapse.stories.tsx +1 -1
- package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
- package/src/components/ui/divider/Divider.stories.tsx +1 -1
- package/src/components/ui/drawer/Drawer.stories.tsx +1 -1
- package/src/components/ui/form-field/FormField.stories.tsx +1 -1
- package/src/components/ui/help-tip/HelpTip.stories.tsx +1 -1
- package/src/components/ui/icon-box/IconBox.stories.tsx +1 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/indicator/Indicator.stories.tsx +2 -2
- package/src/components/ui/indicator/Indicator.test.tsx +1 -0
- package/src/components/ui/indicator/Indicator.tsx +6 -1
- package/src/components/ui/input/Input.stories.tsx +1 -1
- package/src/components/ui/link/Link.stories.tsx +1 -1
- package/src/components/ui/list-item/ListItem.stories.tsx +1 -1
- package/src/components/ui/menu/Menu.stories.tsx +1 -1
- package/src/components/ui/modal/Modal.stories.tsx +1 -1
- package/src/components/ui/pill/Pill.stories.tsx +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +1 -1
- package/src/components/ui/progress/Progress.stories.tsx +1 -1
- package/src/components/ui/progress/Progress.tsx +47 -22
- package/src/components/ui/radio/Radio.stories.tsx +1 -1
- package/src/components/ui/section/Section.stories.tsx +1 -1
- package/src/components/ui/select/Select.stories.tsx +1 -1
- package/src/components/ui/skeleton/Skeleton.stories.tsx +1 -1
- package/src/components/ui/spinner/Spinner.stories.tsx +1 -1
- package/src/components/ui/stack/Stack.stories.tsx +1 -1
- package/src/components/ui/surface/Surface.stories.tsx +1 -1
- package/src/components/ui/switch/Switch.stories.tsx +1 -1
- package/src/components/ui/tabs/Tabs.stories.tsx +1 -1
- package/src/components/ui/tile/Tile.stories.tsx +79 -0
- package/src/components/ui/tile/Tile.test.tsx +48 -0
- package/src/components/ui/tile/Tile.tsx +64 -0
- package/src/components/ui/tile/index.ts +2 -0
- package/src/components/ui/toast/Toast.stories.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
- package/src/components/ui/tooltip/Tooltip.stories.tsx +1 -1
- package/src/examples/react-hook-form/ReactHookForm.stories.tsx +1 -1
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useTimer.test.ts +150 -0
- package/src/hooks/useTimer.ts +77 -0
- package/src/index.ts +3 -0
- package/src/pages.ts +1 -1
- package/src/stories/PresetShowcase.stories.tsx +1 -1
- package/src/stories/ThemePresets.stories.tsx +1 -1
- package/src/theme/ColorSystem.stories.tsx +1 -1
- package/src/theme/Colors.stories.tsx +5 -8
- package/src/theme/config.ts +4 -0
- package/src/theme/elevation.stories.tsx +1 -1
- package/src/theme/global.css +5 -1
- package/src/theme/shadows.stories.tsx +1 -1
- package/src/theme/tokens/primitives.ts +11 -107
- package/src/theme/tokens/semantic.ts +8 -0
- package/src/theme/workout-tokens.ts +11 -7
- package/tailwind.config.js +5 -0
|
@@ -1,24 +1,39 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
-
import { render, screen, fireEvent } from '@testing-library/react'
|
|
2
|
+
import { render, screen, fireEvent, within } from '@testing-library/react'
|
|
3
3
|
import { axe } from 'jest-axe'
|
|
4
4
|
import { ExerciseCard } from './ExerciseCard'
|
|
5
5
|
import type { SetRowProps } from './SetRow'
|
|
6
6
|
|
|
7
7
|
const baseCollapsedProps = {
|
|
8
8
|
name: 'Bench Press',
|
|
9
|
-
state: 'collapsed' as const,
|
|
10
|
-
onToggle: vi.fn(),
|
|
11
9
|
summary: { sets: 3, reps: 6, weight: 175, unit: 'lbs' as const },
|
|
12
10
|
}
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{
|
|
17
|
-
|
|
12
|
+
// Done · LIVE · upcoming — the locked "one object" narrative.
|
|
13
|
+
const unifiedSets: SetRowProps[] = [
|
|
14
|
+
{
|
|
15
|
+
state: 'done',
|
|
16
|
+
setNumber: 1,
|
|
17
|
+
unit: 'lbs',
|
|
18
|
+
reps: 10,
|
|
19
|
+
weight: 90,
|
|
20
|
+
rpe: 7.5,
|
|
21
|
+
velocities: [1.05, 0.9, 0.82],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
state: 'live',
|
|
25
|
+
setNumber: 2,
|
|
26
|
+
unit: 'lbs',
|
|
27
|
+
target: { reps: 10, weight: 90 },
|
|
28
|
+
reps: 2,
|
|
29
|
+
weight: 90,
|
|
30
|
+
velocities: [0.95, 0.9],
|
|
31
|
+
},
|
|
32
|
+
{ state: 'todo', setNumber: 3, unit: 'lbs', target: { reps: 10, weight: 90 } },
|
|
18
33
|
]
|
|
19
34
|
|
|
20
35
|
describe('ExerciseCard', () => {
|
|
21
|
-
describe('collapsed
|
|
36
|
+
describe('collapsed', () => {
|
|
22
37
|
it('renders exercise name', () => {
|
|
23
38
|
render(<ExerciseCard {...baseCollapsedProps} />)
|
|
24
39
|
expect(screen.getByTestId('exercise-card-name')).toHaveTextContent('Bench Press')
|
|
@@ -26,19 +41,7 @@ describe('ExerciseCard', () => {
|
|
|
26
41
|
|
|
27
42
|
it('renders summary text', () => {
|
|
28
43
|
render(<ExerciseCard {...baseCollapsedProps} />)
|
|
29
|
-
expect(screen.getByTestId('exercise-card-summary')).toHaveTextContent(
|
|
30
|
-
'3\u00D76 @ 175 lbs',
|
|
31
|
-
)
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
it('renders WeightBadge when e1rm provided', () => {
|
|
35
|
-
render(
|
|
36
|
-
<ExerciseCard
|
|
37
|
-
{...baseCollapsedProps}
|
|
38
|
-
e1rm={{ value: 225, unit: 'lbs' }}
|
|
39
|
-
/>,
|
|
40
|
-
)
|
|
41
|
-
expect(screen.getByTestId('exercise-card-e1rm')).toBeInTheDocument()
|
|
44
|
+
expect(screen.getByTestId('exercise-card-summary')).toHaveTextContent('3×6 @ 175 lbs')
|
|
42
45
|
})
|
|
43
46
|
|
|
44
47
|
it('renders PrBadge when isPR is true', () => {
|
|
@@ -46,13 +49,16 @@ describe('ExerciseCard', () => {
|
|
|
46
49
|
expect(screen.getByTestId('pr-badge-star')).toBeInTheDocument()
|
|
47
50
|
})
|
|
48
51
|
|
|
49
|
-
it('renders mini velocity strips for
|
|
52
|
+
it('renders mini velocity strips for logged sets', () => {
|
|
50
53
|
render(
|
|
51
54
|
<ExerciseCard
|
|
52
55
|
{...baseCollapsedProps}
|
|
53
|
-
setVelocities={[
|
|
56
|
+
setVelocities={[
|
|
57
|
+
[1.1, 0.95],
|
|
58
|
+
[0.9, 0.8],
|
|
59
|
+
]}
|
|
54
60
|
totalPlannedSets={4}
|
|
55
|
-
|
|
61
|
+
/>
|
|
56
62
|
)
|
|
57
63
|
expect(screen.getByTestId('exercise-card-velocity-strip-0')).toBeInTheDocument()
|
|
58
64
|
expect(screen.getByTestId('exercise-card-velocity-strip-1')).toBeInTheDocument()
|
|
@@ -60,11 +66,7 @@ describe('ExerciseCard', () => {
|
|
|
60
66
|
|
|
61
67
|
it('renders placeholder strips for remaining planned sets', () => {
|
|
62
68
|
render(
|
|
63
|
-
<ExerciseCard
|
|
64
|
-
{...baseCollapsedProps}
|
|
65
|
-
setVelocities={[[1.1, 0.95]]}
|
|
66
|
-
totalPlannedSets={3}
|
|
67
|
-
/>,
|
|
69
|
+
<ExerciseCard {...baseCollapsedProps} setVelocities={[[1.1, 0.95]]} totalPlannedSets={3} />
|
|
68
70
|
)
|
|
69
71
|
expect(screen.getByTestId('exercise-card-placeholder-0')).toBeInTheDocument()
|
|
70
72
|
expect(screen.getByTestId('exercise-card-placeholder-1')).toBeInTheDocument()
|
|
@@ -76,36 +78,49 @@ describe('ExerciseCard', () => {
|
|
|
76
78
|
})
|
|
77
79
|
})
|
|
78
80
|
|
|
79
|
-
describe('expanded
|
|
81
|
+
describe('expanded (unified: rail heading header + faded body)', () => {
|
|
80
82
|
const expandedProps = {
|
|
81
83
|
name: 'Squat',
|
|
82
|
-
|
|
83
|
-
onToggle: vi.fn(),
|
|
84
|
+
defaultExpanded: true,
|
|
84
85
|
summary: { sets: 3, reps: 8, weight: 185, unit: 'lbs' as const },
|
|
85
|
-
sets:
|
|
86
|
+
sets: unifiedSets,
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
// Literal-hex text treatments (dark theme neutral 100 / 400).
|
|
90
|
+
const T_ACTIVE = '#F3F4F6'
|
|
91
|
+
const T_MUTED = '#9CA3AF'
|
|
92
|
+
|
|
93
|
+
it('renders exercise name via the real ExerciseCardHeading header', () => {
|
|
89
94
|
render(<ExerciseCard {...expandedProps} />)
|
|
90
95
|
expect(screen.getByTestId('exercise-card-name')).toHaveTextContent('Squat')
|
|
91
96
|
})
|
|
92
97
|
|
|
93
|
-
it('
|
|
98
|
+
it('is a single object: the heading is nested in the card, not a duplicate exercise-card', () => {
|
|
99
|
+
render(<ExerciseCard {...expandedProps} />)
|
|
100
|
+
expect(screen.getByTestId('exercise-card')).toBeInTheDocument()
|
|
101
|
+
expect(screen.getByTestId('exercise-card-heading')).toBeInTheDocument()
|
|
102
|
+
expect(screen.getByTestId('exercise-card-body')).toBeInTheDocument()
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('renders the header per-set strip derived from the set rows', () => {
|
|
106
|
+
render(<ExerciseCard {...expandedProps} />)
|
|
107
|
+
expect(screen.getByTestId('exercise-card-strip')).toBeInTheDocument()
|
|
108
|
+
expect(screen.getByTestId('set-strip')).toBeInTheDocument()
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('drops the PREV column (showPrevious=false) but keeps SET/REPS/LBS/RPE', () => {
|
|
94
112
|
render(<ExerciseCard {...expandedProps} />)
|
|
95
113
|
const headers = screen.getByTestId('exercise-card-column-headers')
|
|
96
114
|
expect(headers).toHaveTextContent('SET')
|
|
97
|
-
expect(headers).toHaveTextContent('PREV')
|
|
98
115
|
expect(headers).toHaveTextContent('REPS')
|
|
99
116
|
expect(headers).toHaveTextContent('LBS')
|
|
100
117
|
expect(headers).toHaveTextContent('RPE')
|
|
118
|
+
expect(headers).not.toHaveTextContent('PREV')
|
|
101
119
|
})
|
|
102
120
|
|
|
103
121
|
it('shows the KG weight header when the unit is kg', () => {
|
|
104
122
|
render(
|
|
105
|
-
<ExerciseCard
|
|
106
|
-
{...expandedProps}
|
|
107
|
-
summary={{ sets: 3, reps: 8, weight: 84, unit: 'kg' }}
|
|
108
|
-
/>,
|
|
123
|
+
<ExerciseCard {...expandedProps} summary={{ sets: 3, reps: 8, weight: 84, unit: 'kg' }} />
|
|
109
124
|
)
|
|
110
125
|
const headers = screen.getByTestId('exercise-card-column-headers')
|
|
111
126
|
expect(headers).toHaveTextContent('KG')
|
|
@@ -116,56 +131,61 @@ describe('ExerciseCard', () => {
|
|
|
116
131
|
render(
|
|
117
132
|
<ExerciseCard
|
|
118
133
|
name="Squat"
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
/>,
|
|
134
|
+
defaultExpanded
|
|
135
|
+
sets={[{ state: 'done', setNumber: 1, unit: 'kg', reps: 8, weight: 60, velocities: [] }]}
|
|
136
|
+
/>
|
|
123
137
|
)
|
|
124
138
|
expect(screen.getByTestId('exercise-card-column-headers')).toHaveTextContent('KG')
|
|
125
139
|
})
|
|
126
140
|
|
|
127
|
-
it('renders SetRow
|
|
141
|
+
it('renders one SetRow per set', () => {
|
|
128
142
|
render(<ExerciseCard {...expandedProps} />)
|
|
129
|
-
|
|
130
|
-
expect(setRows).toHaveLength(3)
|
|
143
|
+
expect(screen.getAllByTestId('set-row')).toHaveLength(3)
|
|
131
144
|
})
|
|
132
145
|
|
|
133
|
-
it('
|
|
134
|
-
render(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
/>,
|
|
139
|
-
)
|
|
140
|
-
expect(screen.getByTestId('exercise-card-tempo')).toBeInTheDocument()
|
|
141
|
-
expect(screen.getByTestId('tempo-display')).toBeInTheDocument()
|
|
146
|
+
it('shows the TARGET reps for the live set (no "reps-done/target")', () => {
|
|
147
|
+
render(<ExerciseCard {...expandedProps} />)
|
|
148
|
+
const rows = screen.getAllByTestId('set-row')
|
|
149
|
+
expect(rows[1]).toHaveTextContent('10')
|
|
150
|
+
expect(rows[1]).not.toHaveTextContent('2/10')
|
|
142
151
|
})
|
|
143
152
|
|
|
144
|
-
it('
|
|
153
|
+
it('spotlights the active set by brightness and mutes done + upcoming', () => {
|
|
145
154
|
render(<ExerciseCard {...expandedProps} />)
|
|
146
|
-
|
|
155
|
+
const rows = screen.getAllByTestId('set-row')
|
|
156
|
+
expect(within(rows[0]).getByText('1')).toHaveStyle({ color: T_MUTED })
|
|
157
|
+
expect(within(rows[1]).getByText('2')).toHaveStyle({ color: T_ACTIVE })
|
|
158
|
+
expect(within(rows[2]).getByText('3')).toHaveStyle({ color: T_MUTED })
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('uses the compact velocity-height spotlight for the live set and mini elsewhere', () => {
|
|
162
|
+
render(<ExerciseCard {...expandedProps} />)
|
|
163
|
+
expect(screen.getAllByTestId('velocity-strip-compact')).toHaveLength(1)
|
|
164
|
+
expect(screen.getAllByTestId('velocity-strip-mini')).toHaveLength(2)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('renders the TempoDisplay in the header when tempo provided', () => {
|
|
168
|
+
render(<ExerciseCard {...expandedProps} tempo={[2, 1, 3, 0]} />)
|
|
169
|
+
expect(screen.getByTestId('tempo-display')).toBeInTheDocument()
|
|
147
170
|
})
|
|
148
171
|
|
|
149
|
-
it('
|
|
172
|
+
it('does not render TempoDisplay when no tempo', () => {
|
|
150
173
|
render(<ExerciseCard {...expandedProps} />)
|
|
151
|
-
|
|
152
|
-
expect(header).toHaveAttribute('aria-expanded', 'true')
|
|
174
|
+
expect(screen.queryByTestId('tempo-display')).not.toBeInTheDocument()
|
|
153
175
|
})
|
|
154
176
|
})
|
|
155
177
|
|
|
156
|
-
describe('upcoming
|
|
178
|
+
describe('upcoming', () => {
|
|
157
179
|
const upcomingProps = {
|
|
158
180
|
name: 'Deadlift',
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
previousBest: '185 lbs \u00D7 10',
|
|
181
|
+
upcoming: true,
|
|
182
|
+
prescription: '3×8-12 @ RPE 8',
|
|
183
|
+
previousBest: '185 lbs × 10',
|
|
163
184
|
}
|
|
164
185
|
|
|
165
186
|
it('renders with reduced opacity', () => {
|
|
166
187
|
render(<ExerciseCard {...upcomingProps} />)
|
|
167
|
-
|
|
168
|
-
expect(card).toHaveStyle({ opacity: 0.6 })
|
|
188
|
+
expect(screen.getByTestId('exercise-card')).toHaveStyle({ opacity: 0.6 })
|
|
169
189
|
})
|
|
170
190
|
|
|
171
191
|
it('renders exercise name', () => {
|
|
@@ -175,63 +195,72 @@ describe('ExerciseCard', () => {
|
|
|
175
195
|
|
|
176
196
|
it('renders prescription text', () => {
|
|
177
197
|
render(<ExerciseCard {...upcomingProps} />)
|
|
178
|
-
expect(screen.getByTestId('exercise-card-prescription')).toHaveTextContent(
|
|
179
|
-
'3\u00D78-12 @ RPE 8',
|
|
180
|
-
)
|
|
198
|
+
expect(screen.getByTestId('exercise-card-prescription')).toHaveTextContent('3×8-12 @ RPE 8')
|
|
181
199
|
})
|
|
182
200
|
|
|
183
201
|
it('renders previous best text', () => {
|
|
184
202
|
render(<ExerciseCard {...upcomingProps} />)
|
|
185
|
-
expect(screen.getByTestId('exercise-card-previous-best')).toHaveTextContent(
|
|
186
|
-
'185 lbs \u00D7 10',
|
|
187
|
-
)
|
|
203
|
+
expect(screen.getByTestId('exercise-card-previous-best')).toHaveTextContent('185 lbs × 10')
|
|
188
204
|
})
|
|
189
205
|
|
|
190
|
-
it('
|
|
191
|
-
render(<ExerciseCard {...upcomingProps} />)
|
|
192
|
-
expect(screen.queryByTestId('exercise-card-
|
|
206
|
+
it('ignores expand and never reveals the body', () => {
|
|
207
|
+
render(<ExerciseCard {...upcomingProps} defaultExpanded sets={unifiedSets} />)
|
|
208
|
+
expect(screen.queryByTestId('exercise-card-body')).not.toBeInTheDocument()
|
|
193
209
|
expect(screen.queryByTestId('exercise-card-column-headers')).not.toBeInTheDocument()
|
|
194
210
|
})
|
|
195
211
|
})
|
|
196
212
|
|
|
197
|
-
describe('
|
|
198
|
-
it('
|
|
199
|
-
|
|
213
|
+
describe('expand control', () => {
|
|
214
|
+
it('uncontrolled: defaultExpanded opens, and pressing the header collapses it', () => {
|
|
215
|
+
render(<ExerciseCard {...baseCollapsedProps} defaultExpanded sets={unifiedSets} />)
|
|
216
|
+
expect(screen.getByTestId('exercise-card-body')).toBeInTheDocument()
|
|
217
|
+
fireEvent.click(screen.getByTestId('exercise-card-header'))
|
|
218
|
+
expect(screen.queryByTestId('exercise-card-body')).not.toBeInTheDocument()
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it('uncontrolled: collapsed by default, pressing the header expands it', () => {
|
|
222
|
+
render(<ExerciseCard {...baseCollapsedProps} sets={unifiedSets} />)
|
|
223
|
+
expect(screen.queryByTestId('exercise-card-body')).not.toBeInTheDocument()
|
|
224
|
+
fireEvent.click(screen.getByTestId('exercise-card-header'))
|
|
225
|
+
expect(screen.getByTestId('exercise-card-body')).toBeInTheDocument()
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('controlled: reports the requested state but does not self-toggle', () => {
|
|
229
|
+
const onExpandedChange = vi.fn()
|
|
200
230
|
render(
|
|
201
231
|
<ExerciseCard
|
|
202
232
|
{...baseCollapsedProps}
|
|
203
|
-
|
|
204
|
-
|
|
233
|
+
expanded={false}
|
|
234
|
+
onExpandedChange={onExpandedChange}
|
|
235
|
+
sets={unifiedSets}
|
|
236
|
+
/>
|
|
205
237
|
)
|
|
238
|
+
expect(screen.queryByTestId('exercise-card-body')).not.toBeInTheDocument()
|
|
206
239
|
fireEvent.click(screen.getByTestId('exercise-card-header'))
|
|
207
|
-
expect(
|
|
240
|
+
expect(onExpandedChange).toHaveBeenCalledWith(true)
|
|
241
|
+
// Controlled: still collapsed until the parent flips `expanded`.
|
|
242
|
+
expect(screen.queryByTestId('exercise-card-body')).not.toBeInTheDocument()
|
|
208
243
|
})
|
|
209
244
|
|
|
210
|
-
it('fires
|
|
211
|
-
const
|
|
245
|
+
it('fires onExpandedChange(false) when an expanded header is pressed', () => {
|
|
246
|
+
const onExpandedChange = vi.fn()
|
|
212
247
|
render(
|
|
213
248
|
<ExerciseCard
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
sets={
|
|
218
|
-
|
|
249
|
+
{...baseCollapsedProps}
|
|
250
|
+
defaultExpanded
|
|
251
|
+
onExpandedChange={onExpandedChange}
|
|
252
|
+
sets={unifiedSets}
|
|
253
|
+
/>
|
|
219
254
|
)
|
|
220
255
|
fireEvent.click(screen.getByTestId('exercise-card-header'))
|
|
221
|
-
expect(
|
|
256
|
+
expect(onExpandedChange).toHaveBeenCalledWith(false)
|
|
222
257
|
})
|
|
223
258
|
})
|
|
224
259
|
|
|
225
260
|
describe('superset position border radius', () => {
|
|
226
261
|
it('applies first position border radius', () => {
|
|
227
|
-
render(
|
|
228
|
-
|
|
229
|
-
{...baseCollapsedProps}
|
|
230
|
-
supersetPosition="first"
|
|
231
|
-
/>,
|
|
232
|
-
)
|
|
233
|
-
const card = screen.getByTestId('exercise-card')
|
|
234
|
-
const container = card.firstChild as HTMLElement
|
|
262
|
+
render(<ExerciseCard {...baseCollapsedProps} supersetPosition="first" />)
|
|
263
|
+
const container = screen.getByTestId('exercise-card').firstChild as HTMLElement
|
|
235
264
|
expect(container).toHaveStyle({
|
|
236
265
|
borderTopLeftRadius: '12px',
|
|
237
266
|
borderTopRightRadius: '12px',
|
|
@@ -241,14 +270,8 @@ describe('ExerciseCard', () => {
|
|
|
241
270
|
})
|
|
242
271
|
|
|
243
272
|
it('applies last position border radius', () => {
|
|
244
|
-
render(
|
|
245
|
-
|
|
246
|
-
{...baseCollapsedProps}
|
|
247
|
-
supersetPosition="last"
|
|
248
|
-
/>,
|
|
249
|
-
)
|
|
250
|
-
const card = screen.getByTestId('exercise-card')
|
|
251
|
-
const container = card.firstChild as HTMLElement
|
|
273
|
+
render(<ExerciseCard {...baseCollapsedProps} supersetPosition="last" />)
|
|
274
|
+
const container = screen.getByTestId('exercise-card').firstChild as HTMLElement
|
|
252
275
|
expect(container).toHaveStyle({
|
|
253
276
|
borderTopLeftRadius: '8px',
|
|
254
277
|
borderTopRightRadius: '8px',
|
|
@@ -258,14 +281,8 @@ describe('ExerciseCard', () => {
|
|
|
258
281
|
})
|
|
259
282
|
|
|
260
283
|
it('applies middle position border radius', () => {
|
|
261
|
-
render(
|
|
262
|
-
|
|
263
|
-
{...baseCollapsedProps}
|
|
264
|
-
supersetPosition="middle"
|
|
265
|
-
/>,
|
|
266
|
-
)
|
|
267
|
-
const card = screen.getByTestId('exercise-card')
|
|
268
|
-
const container = card.firstChild as HTMLElement
|
|
284
|
+
render(<ExerciseCard {...baseCollapsedProps} supersetPosition="middle" />)
|
|
285
|
+
const container = screen.getByTestId('exercise-card').firstChild as HTMLElement
|
|
269
286
|
expect(container).toHaveStyle({
|
|
270
287
|
borderTopLeftRadius: '8px',
|
|
271
288
|
borderTopRightRadius: '8px',
|
|
@@ -276,65 +293,51 @@ describe('ExerciseCard', () => {
|
|
|
276
293
|
})
|
|
277
294
|
|
|
278
295
|
describe('accessibility', () => {
|
|
279
|
-
it('has correct accessibility label for collapsed
|
|
296
|
+
it('has correct accessibility label for collapsed', () => {
|
|
280
297
|
render(<ExerciseCard {...baseCollapsedProps} />)
|
|
281
|
-
expect(
|
|
282
|
-
screen.getByLabelText('Bench Press, 3\u00D76 @ 175 lbs'),
|
|
283
|
-
).toBeInTheDocument()
|
|
298
|
+
expect(screen.getByLabelText('Bench Press, 3×6 @ 175 lbs')).toBeInTheDocument()
|
|
284
299
|
})
|
|
285
300
|
|
|
286
|
-
it('has correct accessibility label for upcoming
|
|
287
|
-
render(
|
|
288
|
-
|
|
289
|
-
name="Deadlift"
|
|
290
|
-
state="upcoming"
|
|
291
|
-
onToggle={vi.fn()}
|
|
292
|
-
prescription="3x8 @ RPE 8"
|
|
293
|
-
/>,
|
|
294
|
-
)
|
|
295
|
-
expect(
|
|
296
|
-
screen.getByLabelText('Deadlift, 3x8 @ RPE 8'),
|
|
297
|
-
).toBeInTheDocument()
|
|
301
|
+
it('has correct accessibility label for upcoming with prescription', () => {
|
|
302
|
+
render(<ExerciseCard name="Deadlift" upcoming prescription="3x8 @ RPE 8" />)
|
|
303
|
+
expect(screen.getByLabelText('Deadlift, 3x8 @ RPE 8')).toBeInTheDocument()
|
|
298
304
|
})
|
|
299
305
|
|
|
300
|
-
it('has no accessibility violations
|
|
306
|
+
it('has no accessibility violations when collapsed', async () => {
|
|
301
307
|
const { container } = render(
|
|
302
308
|
<ExerciseCard
|
|
303
309
|
{...baseCollapsedProps}
|
|
304
|
-
e1rm={{ value: 225, unit: 'lbs' }}
|
|
305
310
|
isPR
|
|
306
311
|
setVelocities={[[1.1, 0.95]]}
|
|
307
312
|
totalPlannedSets={3}
|
|
308
|
-
|
|
313
|
+
/>
|
|
309
314
|
)
|
|
310
315
|
const results = await axe(container)
|
|
311
316
|
expect(results).toHaveNoViolations()
|
|
312
317
|
})
|
|
313
318
|
|
|
314
|
-
it('has no accessibility violations
|
|
319
|
+
it('has no accessibility violations when expanded', async () => {
|
|
315
320
|
const { container } = render(
|
|
316
321
|
<ExerciseCard
|
|
317
322
|
name="Squat"
|
|
318
|
-
|
|
319
|
-
onToggle={vi.fn()}
|
|
323
|
+
defaultExpanded
|
|
320
324
|
summary={{ sets: 3, reps: 8, weight: 185, unit: 'lbs' }}
|
|
321
|
-
sets={
|
|
325
|
+
sets={unifiedSets}
|
|
322
326
|
tempo={[2, 1, 3, 0]}
|
|
323
|
-
|
|
327
|
+
/>
|
|
324
328
|
)
|
|
325
329
|
const results = await axe(container)
|
|
326
330
|
expect(results).toHaveNoViolations()
|
|
327
331
|
})
|
|
328
332
|
|
|
329
|
-
it('has no accessibility violations
|
|
333
|
+
it('has no accessibility violations when upcoming', async () => {
|
|
330
334
|
const { container } = render(
|
|
331
335
|
<ExerciseCard
|
|
332
336
|
name="Deadlift"
|
|
333
|
-
|
|
334
|
-
onToggle={vi.fn()}
|
|
337
|
+
upcoming
|
|
335
338
|
prescription="3x8 @ RPE 8"
|
|
336
339
|
previousBest="185 lbs x 10"
|
|
337
|
-
|
|
340
|
+
/>
|
|
338
341
|
)
|
|
339
342
|
const results = await axe(container)
|
|
340
343
|
expect(results).toHaveNoViolations()
|