@titan-design/react-ui 0.2.6 → 0.3.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.
Files changed (191) hide show
  1. package/dist/bodymap-BhG55xHM.d.mts +318 -0
  2. package/dist/bodymap-BhG55xHM.d.ts +318 -0
  3. package/dist/bodymap.d.mts +3 -0
  4. package/dist/bodymap.d.ts +3 -0
  5. package/dist/bodymap.js +2670 -0
  6. package/dist/bodymap.js.map +1 -0
  7. package/dist/bodymap.mjs +1101 -0
  8. package/dist/bodymap.mjs.map +1 -0
  9. package/dist/{chunk-UXVRPOME.mjs → chunk-7XPB4NAO.mjs} +125 -785
  10. package/dist/chunk-7XPB4NAO.mjs.map +1 -0
  11. package/dist/chunk-P7TSQUN6.mjs +846 -0
  12. package/dist/chunk-P7TSQUN6.mjs.map +1 -0
  13. package/dist/chunk-TOD2WQBG.mjs +1048 -0
  14. package/dist/chunk-TOD2WQBG.mjs.map +1 -0
  15. package/dist/index.d.mts +1131 -4
  16. package/dist/index.d.ts +1131 -4
  17. package/dist/index.js +6396 -225
  18. package/dist/index.js.map +1 -1
  19. package/dist/index.mjs +5572 -641
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/theme/index.d.mts +317 -1
  22. package/dist/theme/index.d.ts +317 -1
  23. package/dist/theme/index.js +185 -2
  24. package/dist/theme/index.js.map +1 -1
  25. package/dist/theme/index.mjs +2 -1
  26. package/dist/theme/tokens-css.d.mts +20 -0
  27. package/dist/theme/tokens-css.d.ts +20 -0
  28. package/dist/theme/tokens-css.js +598 -0
  29. package/dist/theme/tokens-css.js.map +1 -0
  30. package/dist/theme/tokens-css.mjs +24 -0
  31. package/dist/theme/tokens-css.mjs.map +1 -0
  32. package/package.json +25 -5
  33. package/src/bodymap.ts +37 -0
  34. package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
  35. package/src/components/custom/Gauge/Gauge.stories.tsx +45 -0
  36. package/src/components/custom/Gauge/Gauge.test.tsx +71 -0
  37. package/src/components/custom/Gauge/Gauge.tsx +167 -0
  38. package/src/components/custom/Gauge/index.ts +2 -0
  39. package/src/components/custom/Scatter/Scatter.stories.tsx +85 -0
  40. package/src/components/custom/Scatter/Scatter.test.tsx +96 -0
  41. package/src/components/custom/Scatter/Scatter.tsx +260 -0
  42. package/src/components/custom/Scatter/index.ts +2 -0
  43. package/src/components/custom/Sidebar/Sidebar.tsx +1 -1
  44. package/src/components/custom/Table/Table.test.tsx +27 -0
  45. package/src/components/custom/Table/Table.tsx +33 -17
  46. package/src/components/custom/Treemap/Treemap.stories.tsx +60 -0
  47. package/src/components/custom/Treemap/Treemap.test.tsx +67 -0
  48. package/src/components/custom/Treemap/Treemap.tsx +192 -0
  49. package/src/components/custom/Treemap/index.ts +2 -0
  50. package/src/components/custom/Workout/ActiveWorkoutPage.stories.tsx +118 -0
  51. package/src/components/custom/Workout/ActiveWorkoutPage.test.tsx +171 -0
  52. package/src/components/custom/Workout/ActiveWorkoutPage.tsx +447 -0
  53. package/src/components/custom/Workout/BaseBadge.stories.tsx +75 -0
  54. package/src/components/custom/Workout/BaseBadge.test.tsx +102 -0
  55. package/src/components/custom/Workout/BaseBadge.tsx +113 -0
  56. package/src/components/custom/Workout/BodyMap.stories.tsx +121 -0
  57. package/src/components/custom/Workout/BodyMap.test.tsx +124 -0
  58. package/src/components/custom/Workout/BodyMap.tsx +347 -0
  59. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +171 -0
  60. package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +225 -0
  61. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +477 -0
  62. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +125 -0
  63. package/src/components/custom/Workout/CapacityBandChart.test.tsx +193 -0
  64. package/src/components/custom/Workout/CapacityBandChart.tsx +493 -0
  65. package/src/components/custom/Workout/DeviationBar.stories.tsx +58 -0
  66. package/src/components/custom/Workout/DeviationBar.test.tsx +86 -0
  67. package/src/components/custom/Workout/DeviationBar.tsx +78 -0
  68. package/src/components/custom/Workout/ExerciseCard.stories.tsx +186 -0
  69. package/src/components/custom/Workout/ExerciseCard.test.tsx +343 -0
  70. package/src/components/custom/Workout/ExerciseCard.tsx +442 -0
  71. package/src/components/custom/Workout/ExerciseDetailPage.stories.tsx +225 -0
  72. package/src/components/custom/Workout/ExerciseDetailPage.test.tsx +128 -0
  73. package/src/components/custom/Workout/ExerciseDetailPage.tsx +630 -0
  74. package/src/components/custom/Workout/InputBar.stories.tsx +75 -0
  75. package/src/components/custom/Workout/InputBar.test.tsx +116 -0
  76. package/src/components/custom/Workout/InputBar.tsx +162 -0
  77. package/src/components/custom/Workout/IntensityBar.stories.tsx +60 -0
  78. package/src/components/custom/Workout/IntensityBar.test.tsx +113 -0
  79. package/src/components/custom/Workout/IntensityBar.tsx +166 -0
  80. package/src/components/custom/Workout/MesoCard.stories.tsx +152 -0
  81. package/src/components/custom/Workout/MesoCard.test.tsx +170 -0
  82. package/src/components/custom/Workout/MesoCard.tsx +237 -0
  83. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +85 -0
  84. package/src/components/custom/Workout/MesoProgressBar.test.tsx +153 -0
  85. package/src/components/custom/Workout/MesoProgressBar.tsx +165 -0
  86. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +126 -0
  87. package/src/components/custom/Workout/MesoStatusCard.test.tsx +209 -0
  88. package/src/components/custom/Workout/MesoStatusCard.tsx +456 -0
  89. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +118 -0
  90. package/src/components/custom/Workout/MuscleGroupChip.test.tsx +91 -0
  91. package/src/components/custom/Workout/MuscleGroupChip.tsx +92 -0
  92. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +94 -0
  93. package/src/components/custom/Workout/PlaceholderStrip.test.tsx +83 -0
  94. package/src/components/custom/Workout/PlaceholderStrip.tsx +90 -0
  95. package/src/components/custom/Workout/PrBadge.stories.tsx +105 -0
  96. package/src/components/custom/Workout/PrBadge.test.tsx +111 -0
  97. package/src/components/custom/Workout/PrBadge.tsx +103 -0
  98. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +116 -0
  99. package/src/components/custom/Workout/PrHistoryModal.test.tsx +161 -0
  100. package/src/components/custom/Workout/PrHistoryModal.tsx +244 -0
  101. package/src/components/custom/Workout/ProgramPlanningPage.stories.tsx +257 -0
  102. package/src/components/custom/Workout/ProgramPlanningPage.test.tsx +142 -0
  103. package/src/components/custom/Workout/ProgramPlanningPage.tsx +422 -0
  104. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +126 -0
  105. package/src/components/custom/Workout/ReadinessCheck.test.tsx +181 -0
  106. package/src/components/custom/Workout/ReadinessCheck.tsx +265 -0
  107. package/src/components/custom/Workout/RestTimer.test.tsx +10 -0
  108. package/src/components/custom/Workout/RestTimer.tsx +15 -11
  109. package/src/components/custom/Workout/SetRow.stories.tsx +165 -0
  110. package/src/components/custom/Workout/SetRow.test.tsx +222 -0
  111. package/src/components/custom/Workout/SetRow.tsx +247 -0
  112. package/src/components/custom/Workout/Sparkline.stories.tsx +125 -0
  113. package/src/components/custom/Workout/Sparkline.test.tsx +113 -0
  114. package/src/components/custom/Workout/Sparkline.tsx +188 -0
  115. package/src/components/custom/Workout/StatusDot.stories.tsx +150 -0
  116. package/src/components/custom/Workout/StatusDot.test.tsx +165 -0
  117. package/src/components/custom/Workout/StatusDot.tsx +160 -0
  118. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +130 -0
  119. package/src/components/custom/Workout/StrengthTrendChart.test.tsx +217 -0
  120. package/src/components/custom/Workout/StrengthTrendChart.tsx +628 -0
  121. package/src/components/custom/Workout/SupersetWrapper.tsx +1 -2
  122. package/src/components/custom/Workout/TempoDisplay.stories.tsx +66 -0
  123. package/src/components/custom/Workout/TempoDisplay.test.tsx +90 -0
  124. package/src/components/custom/Workout/TempoDisplay.tsx +209 -0
  125. package/src/components/custom/Workout/TrainingStatusPage.stories.tsx +212 -0
  126. package/src/components/custom/Workout/TrainingStatusPage.test.tsx +131 -0
  127. package/src/components/custom/Workout/TrainingStatusPage.tsx +321 -0
  128. package/src/components/custom/Workout/VelocityStrip.stories.tsx +125 -0
  129. package/src/components/custom/Workout/VelocityStrip.test.tsx +244 -0
  130. package/src/components/custom/Workout/VelocityStrip.tsx +288 -0
  131. package/src/components/custom/Workout/WeekRow.stories.tsx +121 -0
  132. package/src/components/custom/Workout/WeekRow.test.tsx +117 -0
  133. package/src/components/custom/Workout/WeekRow.tsx +140 -0
  134. package/src/components/custom/Workout/WeightBadge.stories.tsx +87 -0
  135. package/src/components/custom/Workout/WeightBadge.test.tsx +164 -0
  136. package/src/components/custom/Workout/WeightBadge.tsx +113 -0
  137. package/src/components/custom/Workout/WorkoutCard.stories.tsx +135 -0
  138. package/src/components/custom/Workout/WorkoutCard.test.tsx +172 -0
  139. package/src/components/custom/Workout/WorkoutCard.tsx +228 -0
  140. package/src/components/custom/Workout/WorkoutPill.stories.tsx +63 -0
  141. package/src/components/custom/Workout/WorkoutPill.test.tsx +153 -0
  142. package/src/components/custom/Workout/WorkoutPill.tsx +177 -0
  143. package/src/components/custom/Workout/index.ts +152 -0
  144. package/src/components/custom/Workout/muscleTaxonomy.ts +252 -0
  145. package/src/components/custom/index.ts +3 -0
  146. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  147. package/src/components/custom/stepper/Stepper.tsx +2 -2
  148. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  149. package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
  150. package/src/components/ui/card/Card.tsx +1 -1
  151. package/src/components/ui/checkbox/Checkbox.tsx +1 -1
  152. package/src/components/ui/collapse/Collapse.tsx +1 -1
  153. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  154. package/src/components/ui/drawer/Drawer.tsx +3 -3
  155. package/src/components/ui/form-field/FormField.tsx +1 -1
  156. package/src/components/ui/help-tip/HelpTip.tsx +1 -1
  157. package/src/components/ui/menu/Menu.tsx +2 -2
  158. package/src/components/ui/pill/Pill.tsx +1 -1
  159. package/src/components/ui/popover/Popover.stories.tsx +2 -2
  160. package/src/components/ui/popover/Popover.tsx +1 -1
  161. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  162. package/src/components/ui/section/Section.stories.tsx +4 -4
  163. package/src/components/ui/select/Select.tsx +1 -1
  164. package/src/components/ui/stack/Stack.stories.tsx +4 -4
  165. package/src/components/ui/tabs/Tabs.tsx +2 -2
  166. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
  167. package/src/components/ui/toolbar-button/ToolbarButton.tsx +1 -1
  168. package/src/examples/react-hook-form/ControlledFields.tsx +171 -0
  169. package/src/examples/react-hook-form/ReactHookForm.stories.tsx +51 -0
  170. package/src/examples/react-hook-form/ReactHookForm.test.tsx +61 -0
  171. package/src/examples/react-hook-form/RhfContactForm.tsx +99 -0
  172. package/src/stories/PresetShowcase.stories.tsx +15 -15
  173. package/src/theme/config.completeness.test.ts +77 -0
  174. package/src/theme/config.ts +151 -0
  175. package/src/theme/index.ts +2 -1
  176. package/src/theme/manifest/css-property-manifest.example.json +79 -0
  177. package/src/theme/manifest/css-property-manifest.schema.json +93 -0
  178. package/src/theme/manifest/css-property-manifest.test.ts +81 -0
  179. package/src/theme/manifest/css-property-manifest.types.ts +49 -0
  180. package/src/theme/manifest/css-property-manifest.validate.ts +84 -0
  181. package/src/theme/manifest/design-freeze.test.ts +155 -0
  182. package/src/theme/manifest/extract-css-properties.fixture.html +29 -0
  183. package/src/theme/manifest/extract-css-properties.test.ts +112 -0
  184. package/src/theme/manifest/index.ts +11 -0
  185. package/src/theme/tokens-css.test.ts +28 -0
  186. package/src/theme/tokens-css.ts +36 -0
  187. package/src/theme/workout-tokens.ts +59 -0
  188. package/src/utils/index.ts +10 -0
  189. package/src/utils/workout-format.test.ts +81 -0
  190. package/src/utils/workout-format.ts +83 -0
  191. package/dist/chunk-UXVRPOME.mjs.map +0 -1
@@ -0,0 +1,343 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { render, screen, fireEvent } from '@testing-library/react'
3
+ import { axe } from 'jest-axe'
4
+ import { ExerciseCard } from './ExerciseCard'
5
+ import type { SetRowProps } from './SetRow'
6
+
7
+ const baseCollapsedProps = {
8
+ name: 'Bench Press',
9
+ state: 'collapsed' as const,
10
+ onToggle: vi.fn(),
11
+ summary: { sets: 3, reps: 6, weight: 175, unit: 'lbs' as const },
12
+ }
13
+
14
+ const baseSets: SetRowProps[] = [
15
+ { mode: 'completed', setNumber: 1, reps: 8, weight: 135, unit: 'lbs' },
16
+ { mode: 'completed', setNumber: 2, reps: 8, weight: 135, unit: 'lbs' },
17
+ { mode: 'active', setNumber: 3, reps: null, weight: null, unit: 'lbs', isNextSet: true },
18
+ ]
19
+
20
+ describe('ExerciseCard', () => {
21
+ describe('collapsed state', () => {
22
+ it('renders exercise name', () => {
23
+ render(<ExerciseCard {...baseCollapsedProps} />)
24
+ expect(screen.getByTestId('exercise-card-name')).toHaveTextContent('Bench Press')
25
+ })
26
+
27
+ it('renders summary text', () => {
28
+ 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()
42
+ })
43
+
44
+ it('renders PrBadge when isPR is true', () => {
45
+ render(<ExerciseCard {...baseCollapsedProps} isPR />)
46
+ expect(screen.getByTestId('pr-badge-star')).toBeInTheDocument()
47
+ })
48
+
49
+ it('renders mini velocity strips for completed sets', () => {
50
+ render(
51
+ <ExerciseCard
52
+ {...baseCollapsedProps}
53
+ setVelocities={[[1.1, 0.95], [0.9, 0.8]]}
54
+ totalPlannedSets={4}
55
+ />,
56
+ )
57
+ expect(screen.getByTestId('exercise-card-velocity-strip-0')).toBeInTheDocument()
58
+ expect(screen.getByTestId('exercise-card-velocity-strip-1')).toBeInTheDocument()
59
+ })
60
+
61
+ it('renders placeholder strips for remaining planned sets', () => {
62
+ render(
63
+ <ExerciseCard
64
+ {...baseCollapsedProps}
65
+ setVelocities={[[1.1, 0.95]]}
66
+ totalPlannedSets={3}
67
+ />,
68
+ )
69
+ expect(screen.getByTestId('exercise-card-placeholder-0')).toBeInTheDocument()
70
+ expect(screen.getByTestId('exercise-card-placeholder-1')).toBeInTheDocument()
71
+ })
72
+
73
+ it('does not render strips container when no velocities and no planned sets', () => {
74
+ render(<ExerciseCard {...baseCollapsedProps} />)
75
+ expect(screen.queryByTestId('exercise-card-strips')).not.toBeInTheDocument()
76
+ })
77
+ })
78
+
79
+ describe('expanded state', () => {
80
+ const expandedProps = {
81
+ name: 'Squat',
82
+ state: 'expanded' as const,
83
+ onToggle: vi.fn(),
84
+ summary: { sets: 3, reps: 8, weight: 185, unit: 'lbs' as const },
85
+ sets: baseSets,
86
+ }
87
+
88
+ it('renders exercise name in header', () => {
89
+ render(<ExerciseCard {...expandedProps} />)
90
+ expect(screen.getByTestId('exercise-card-name')).toHaveTextContent('Squat')
91
+ })
92
+
93
+ it('renders column headers', () => {
94
+ render(<ExerciseCard {...expandedProps} />)
95
+ const headers = screen.getByTestId('exercise-card-column-headers')
96
+ expect(headers).toHaveTextContent('SET')
97
+ expect(headers).toHaveTextContent('PREV')
98
+ expect(headers).toHaveTextContent('REPS')
99
+ expect(headers).toHaveTextContent('LBS')
100
+ expect(headers).toHaveTextContent('RPE')
101
+ })
102
+
103
+ it('shows the KG weight header when the unit is kg', () => {
104
+ render(
105
+ <ExerciseCard
106
+ {...expandedProps}
107
+ summary={{ sets: 3, reps: 8, weight: 84, unit: 'kg' }}
108
+ />,
109
+ )
110
+ const headers = screen.getByTestId('exercise-card-column-headers')
111
+ expect(headers).toHaveTextContent('KG')
112
+ expect(headers).not.toHaveTextContent('LBS')
113
+ })
114
+
115
+ it('derives the weight header from the sets unit when summary is absent', () => {
116
+ render(
117
+ <ExerciseCard
118
+ name="Squat"
119
+ state="expanded"
120
+ onToggle={vi.fn()}
121
+ sets={[{ mode: 'completed', setNumber: 1, reps: 8, weight: 60, unit: 'kg' }]}
122
+ />,
123
+ )
124
+ expect(screen.getByTestId('exercise-card-column-headers')).toHaveTextContent('KG')
125
+ })
126
+
127
+ it('renders SetRow components', () => {
128
+ render(<ExerciseCard {...expandedProps} />)
129
+ const setRows = screen.getAllByTestId('set-row')
130
+ expect(setRows).toHaveLength(3)
131
+ })
132
+
133
+ it('renders TempoDisplay when tempo provided', () => {
134
+ render(
135
+ <ExerciseCard
136
+ {...expandedProps}
137
+ tempo={[2, 1, 3, 0]}
138
+ />,
139
+ )
140
+ expect(screen.getByTestId('exercise-card-tempo')).toBeInTheDocument()
141
+ expect(screen.getByTestId('tempo-display')).toBeInTheDocument()
142
+ })
143
+
144
+ it('does not render TempoDisplay when no tempo', () => {
145
+ render(<ExerciseCard {...expandedProps} />)
146
+ expect(screen.queryByTestId('exercise-card-tempo')).not.toBeInTheDocument()
147
+ })
148
+
149
+ it('sets aria-expanded on header button', () => {
150
+ render(<ExerciseCard {...expandedProps} />)
151
+ const header = screen.getByTestId('exercise-card-header')
152
+ expect(header).toHaveAttribute('aria-expanded', 'true')
153
+ })
154
+ })
155
+
156
+ describe('upcoming state', () => {
157
+ const upcomingProps = {
158
+ name: 'Deadlift',
159
+ state: 'upcoming' as const,
160
+ onToggle: vi.fn(),
161
+ prescription: '3\u00D78-12 @ RPE 8',
162
+ previousBest: '185 lbs \u00D7 10',
163
+ }
164
+
165
+ it('renders with reduced opacity', () => {
166
+ render(<ExerciseCard {...upcomingProps} />)
167
+ const card = screen.getByTestId('exercise-card')
168
+ expect(card).toHaveStyle({ opacity: 0.6 })
169
+ })
170
+
171
+ it('renders exercise name', () => {
172
+ render(<ExerciseCard {...upcomingProps} />)
173
+ expect(screen.getByTestId('exercise-card-name')).toHaveTextContent('Deadlift')
174
+ })
175
+
176
+ it('renders prescription text', () => {
177
+ render(<ExerciseCard {...upcomingProps} />)
178
+ expect(screen.getByTestId('exercise-card-prescription')).toHaveTextContent(
179
+ '3\u00D78-12 @ RPE 8',
180
+ )
181
+ })
182
+
183
+ it('renders previous best text', () => {
184
+ render(<ExerciseCard {...upcomingProps} />)
185
+ expect(screen.getByTestId('exercise-card-previous-best')).toHaveTextContent(
186
+ '185 lbs \u00D7 10',
187
+ )
188
+ })
189
+
190
+ it('does not render interactive elements', () => {
191
+ render(<ExerciseCard {...upcomingProps} />)
192
+ expect(screen.queryByTestId('exercise-card-strips')).not.toBeInTheDocument()
193
+ expect(screen.queryByTestId('exercise-card-column-headers')).not.toBeInTheDocument()
194
+ })
195
+ })
196
+
197
+ describe('onToggle', () => {
198
+ it('fires onToggle when collapsed card is pressed', () => {
199
+ const onToggle = vi.fn()
200
+ render(
201
+ <ExerciseCard
202
+ {...baseCollapsedProps}
203
+ onToggle={onToggle}
204
+ />,
205
+ )
206
+ fireEvent.click(screen.getByTestId('exercise-card-header'))
207
+ expect(onToggle).toHaveBeenCalledOnce()
208
+ })
209
+
210
+ it('fires onToggle when expanded header is pressed', () => {
211
+ const onToggle = vi.fn()
212
+ render(
213
+ <ExerciseCard
214
+ name="Squat"
215
+ state="expanded"
216
+ onToggle={onToggle}
217
+ sets={baseSets}
218
+ />,
219
+ )
220
+ fireEvent.click(screen.getByTestId('exercise-card-header'))
221
+ expect(onToggle).toHaveBeenCalledOnce()
222
+ })
223
+ })
224
+
225
+ describe('superset position border radius', () => {
226
+ it('applies first position border radius', () => {
227
+ render(
228
+ <ExerciseCard
229
+ {...baseCollapsedProps}
230
+ supersetPosition="first"
231
+ />,
232
+ )
233
+ const card = screen.getByTestId('exercise-card')
234
+ const container = card.firstChild as HTMLElement
235
+ expect(container).toHaveStyle({
236
+ borderTopLeftRadius: '12px',
237
+ borderTopRightRadius: '12px',
238
+ borderBottomLeftRadius: '8px',
239
+ borderBottomRightRadius: '8px',
240
+ })
241
+ })
242
+
243
+ it('applies last position border radius', () => {
244
+ render(
245
+ <ExerciseCard
246
+ {...baseCollapsedProps}
247
+ supersetPosition="last"
248
+ />,
249
+ )
250
+ const card = screen.getByTestId('exercise-card')
251
+ const container = card.firstChild as HTMLElement
252
+ expect(container).toHaveStyle({
253
+ borderTopLeftRadius: '8px',
254
+ borderTopRightRadius: '8px',
255
+ borderBottomLeftRadius: '12px',
256
+ borderBottomRightRadius: '12px',
257
+ })
258
+ })
259
+
260
+ it('applies middle position border radius', () => {
261
+ render(
262
+ <ExerciseCard
263
+ {...baseCollapsedProps}
264
+ supersetPosition="middle"
265
+ />,
266
+ )
267
+ const card = screen.getByTestId('exercise-card')
268
+ const container = card.firstChild as HTMLElement
269
+ expect(container).toHaveStyle({
270
+ borderTopLeftRadius: '8px',
271
+ borderTopRightRadius: '8px',
272
+ borderBottomLeftRadius: '8px',
273
+ borderBottomRightRadius: '8px',
274
+ })
275
+ })
276
+ })
277
+
278
+ describe('accessibility', () => {
279
+ it('has correct accessibility label for collapsed state', () => {
280
+ render(<ExerciseCard {...baseCollapsedProps} />)
281
+ expect(
282
+ screen.getByLabelText('Bench Press, 3\u00D76 @ 175 lbs'),
283
+ ).toBeInTheDocument()
284
+ })
285
+
286
+ it('has correct accessibility label for upcoming state with prescription', () => {
287
+ render(
288
+ <ExerciseCard
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()
298
+ })
299
+
300
+ it('has no accessibility violations in collapsed state', async () => {
301
+ const { container } = render(
302
+ <ExerciseCard
303
+ {...baseCollapsedProps}
304
+ e1rm={{ value: 225, unit: 'lbs' }}
305
+ isPR
306
+ setVelocities={[[1.1, 0.95]]}
307
+ totalPlannedSets={3}
308
+ />,
309
+ )
310
+ const results = await axe(container)
311
+ expect(results).toHaveNoViolations()
312
+ })
313
+
314
+ it('has no accessibility violations in expanded state', async () => {
315
+ const { container } = render(
316
+ <ExerciseCard
317
+ name="Squat"
318
+ state="expanded"
319
+ onToggle={vi.fn()}
320
+ summary={{ sets: 3, reps: 8, weight: 185, unit: 'lbs' }}
321
+ sets={baseSets}
322
+ tempo={[2, 1, 3, 0]}
323
+ />,
324
+ )
325
+ const results = await axe(container)
326
+ expect(results).toHaveNoViolations()
327
+ })
328
+
329
+ it('has no accessibility violations in upcoming state', async () => {
330
+ const { container } = render(
331
+ <ExerciseCard
332
+ name="Deadlift"
333
+ state="upcoming"
334
+ onToggle={vi.fn()}
335
+ prescription="3x8 @ RPE 8"
336
+ previousBest="185 lbs x 10"
337
+ />,
338
+ )
339
+ const results = await axe(container)
340
+ expect(results).toHaveNoViolations()
341
+ })
342
+ })
343
+ })