@titan-design/react-ui 0.2.5 → 0.2.7

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 (158) hide show
  1. package/dist/chunk-3VLOBS6O.mjs +846 -0
  2. package/dist/chunk-3VLOBS6O.mjs.map +1 -0
  3. package/dist/{chunk-UXVRPOME.mjs → chunk-5SSKGS6E.mjs} +3 -783
  4. package/dist/chunk-5SSKGS6E.mjs.map +1 -0
  5. package/dist/index.d.mts +1005 -4
  6. package/dist/index.d.ts +1005 -4
  7. package/dist/index.js +6043 -213
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.mjs +5901 -177
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/theme/index.d.mts +57 -1
  12. package/dist/theme/index.d.ts +57 -1
  13. package/dist/theme/index.js +63 -0
  14. package/dist/theme/index.js.map +1 -1
  15. package/dist/theme/index.mjs +2 -1
  16. package/dist/theme/tokens-css.d.mts +18 -0
  17. package/dist/theme/tokens-css.d.ts +18 -0
  18. package/dist/theme/tokens-css.js +289 -0
  19. package/dist/theme/tokens-css.js.map +1 -0
  20. package/dist/theme/tokens-css.mjs +24 -0
  21. package/dist/theme/tokens-css.mjs.map +1 -0
  22. package/docs/WEB_SETUP.md +217 -0
  23. package/package.json +17 -5
  24. package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
  25. package/src/components/custom/Gauge/Gauge.stories.tsx +45 -0
  26. package/src/components/custom/Gauge/Gauge.test.tsx +71 -0
  27. package/src/components/custom/Gauge/Gauge.tsx +167 -0
  28. package/src/components/custom/Gauge/index.ts +2 -0
  29. package/src/components/custom/Scatter/Scatter.stories.tsx +85 -0
  30. package/src/components/custom/Scatter/Scatter.test.tsx +96 -0
  31. package/src/components/custom/Scatter/Scatter.tsx +260 -0
  32. package/src/components/custom/Scatter/index.ts +2 -0
  33. package/src/components/custom/Sidebar/Sidebar.tsx +1 -1
  34. package/src/components/custom/Treemap/Treemap.stories.tsx +60 -0
  35. package/src/components/custom/Treemap/Treemap.test.tsx +67 -0
  36. package/src/components/custom/Treemap/Treemap.tsx +192 -0
  37. package/src/components/custom/Treemap/index.ts +2 -0
  38. package/src/components/custom/Workout/BaseBadge.stories.tsx +75 -0
  39. package/src/components/custom/Workout/BaseBadge.test.tsx +102 -0
  40. package/src/components/custom/Workout/BaseBadge.tsx +113 -0
  41. package/src/components/custom/Workout/BodyMap.stories.tsx +121 -0
  42. package/src/components/custom/Workout/BodyMap.test.tsx +124 -0
  43. package/src/components/custom/Workout/BodyMap.tsx +348 -0
  44. package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +171 -0
  45. package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +225 -0
  46. package/src/components/custom/Workout/BodyMapDetailPanel.tsx +478 -0
  47. package/src/components/custom/Workout/CapacityBandChart.stories.tsx +125 -0
  48. package/src/components/custom/Workout/CapacityBandChart.test.tsx +193 -0
  49. package/src/components/custom/Workout/CapacityBandChart.tsx +493 -0
  50. package/src/components/custom/Workout/DeviationBar.stories.tsx +58 -0
  51. package/src/components/custom/Workout/DeviationBar.test.tsx +86 -0
  52. package/src/components/custom/Workout/DeviationBar.tsx +78 -0
  53. package/src/components/custom/Workout/ExerciseCard.stories.tsx +186 -0
  54. package/src/components/custom/Workout/ExerciseCard.test.tsx +319 -0
  55. package/src/components/custom/Workout/ExerciseCard.tsx +429 -0
  56. package/src/components/custom/Workout/InputBar.stories.tsx +75 -0
  57. package/src/components/custom/Workout/InputBar.test.tsx +116 -0
  58. package/src/components/custom/Workout/InputBar.tsx +163 -0
  59. package/src/components/custom/Workout/IntensityBar.stories.tsx +60 -0
  60. package/src/components/custom/Workout/IntensityBar.test.tsx +113 -0
  61. package/src/components/custom/Workout/IntensityBar.tsx +166 -0
  62. package/src/components/custom/Workout/MesoCard.stories.tsx +152 -0
  63. package/src/components/custom/Workout/MesoCard.test.tsx +170 -0
  64. package/src/components/custom/Workout/MesoCard.tsx +235 -0
  65. package/src/components/custom/Workout/MesoProgressBar.stories.tsx +85 -0
  66. package/src/components/custom/Workout/MesoProgressBar.test.tsx +141 -0
  67. package/src/components/custom/Workout/MesoProgressBar.tsx +161 -0
  68. package/src/components/custom/Workout/MesoStatusCard.stories.tsx +126 -0
  69. package/src/components/custom/Workout/MesoStatusCard.test.tsx +209 -0
  70. package/src/components/custom/Workout/MesoStatusCard.tsx +456 -0
  71. package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +118 -0
  72. package/src/components/custom/Workout/MuscleGroupChip.test.tsx +91 -0
  73. package/src/components/custom/Workout/MuscleGroupChip.tsx +92 -0
  74. package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +94 -0
  75. package/src/components/custom/Workout/PlaceholderStrip.test.tsx +83 -0
  76. package/src/components/custom/Workout/PlaceholderStrip.tsx +91 -0
  77. package/src/components/custom/Workout/PrBadge.stories.tsx +105 -0
  78. package/src/components/custom/Workout/PrBadge.test.tsx +111 -0
  79. package/src/components/custom/Workout/PrBadge.tsx +103 -0
  80. package/src/components/custom/Workout/PrHistoryModal.stories.tsx +116 -0
  81. package/src/components/custom/Workout/PrHistoryModal.test.tsx +161 -0
  82. package/src/components/custom/Workout/PrHistoryModal.tsx +244 -0
  83. package/src/components/custom/Workout/ReadinessCheck.stories.tsx +126 -0
  84. package/src/components/custom/Workout/ReadinessCheck.test.tsx +181 -0
  85. package/src/components/custom/Workout/ReadinessCheck.tsx +266 -0
  86. package/src/components/custom/Workout/RestTimer.tsx +8 -6
  87. package/src/components/custom/Workout/SetRow.stories.tsx +165 -0
  88. package/src/components/custom/Workout/SetRow.test.tsx +222 -0
  89. package/src/components/custom/Workout/SetRow.tsx +253 -0
  90. package/src/components/custom/Workout/Sparkline.stories.tsx +125 -0
  91. package/src/components/custom/Workout/Sparkline.test.tsx +113 -0
  92. package/src/components/custom/Workout/Sparkline.tsx +188 -0
  93. package/src/components/custom/Workout/StatusDot.stories.tsx +150 -0
  94. package/src/components/custom/Workout/StatusDot.test.tsx +165 -0
  95. package/src/components/custom/Workout/StatusDot.tsx +159 -0
  96. package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +130 -0
  97. package/src/components/custom/Workout/StrengthTrendChart.test.tsx +217 -0
  98. package/src/components/custom/Workout/StrengthTrendChart.tsx +628 -0
  99. package/src/components/custom/Workout/SupersetWrapper.tsx +1 -2
  100. package/src/components/custom/Workout/TempoDisplay.stories.tsx +66 -0
  101. package/src/components/custom/Workout/TempoDisplay.test.tsx +90 -0
  102. package/src/components/custom/Workout/TempoDisplay.tsx +207 -0
  103. package/src/components/custom/Workout/VelocityStrip.stories.tsx +125 -0
  104. package/src/components/custom/Workout/VelocityStrip.test.tsx +234 -0
  105. package/src/components/custom/Workout/VelocityStrip.tsx +287 -0
  106. package/src/components/custom/Workout/WeekRow.stories.tsx +121 -0
  107. package/src/components/custom/Workout/WeekRow.test.tsx +117 -0
  108. package/src/components/custom/Workout/WeekRow.tsx +140 -0
  109. package/src/components/custom/Workout/WeightBadge.stories.tsx +87 -0
  110. package/src/components/custom/Workout/WeightBadge.test.tsx +164 -0
  111. package/src/components/custom/Workout/WeightBadge.tsx +113 -0
  112. package/src/components/custom/Workout/WorkoutCard.stories.tsx +135 -0
  113. package/src/components/custom/Workout/WorkoutCard.test.tsx +172 -0
  114. package/src/components/custom/Workout/WorkoutCard.tsx +226 -0
  115. package/src/components/custom/Workout/WorkoutPill.stories.tsx +63 -0
  116. package/src/components/custom/Workout/WorkoutPill.test.tsx +153 -0
  117. package/src/components/custom/Workout/WorkoutPill.tsx +177 -0
  118. package/src/components/custom/Workout/index.ts +113 -0
  119. package/src/components/custom/Workout/muscleTaxonomy.ts +252 -0
  120. package/src/components/custom/index.ts +3 -0
  121. package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
  122. package/src/components/custom/stepper/Stepper.tsx +2 -2
  123. package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
  124. package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
  125. package/src/components/ui/card/Card.tsx +1 -1
  126. package/src/components/ui/checkbox/Checkbox.tsx +1 -1
  127. package/src/components/ui/collapse/Collapse.tsx +1 -1
  128. package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
  129. package/src/components/ui/drawer/Drawer.tsx +3 -3
  130. package/src/components/ui/form-field/FormField.tsx +1 -1
  131. package/src/components/ui/help-tip/HelpTip.tsx +1 -1
  132. package/src/components/ui/menu/Menu.tsx +2 -2
  133. package/src/components/ui/pill/Pill.tsx +1 -1
  134. package/src/components/ui/popover/Popover.stories.tsx +2 -2
  135. package/src/components/ui/popover/Popover.tsx +1 -1
  136. package/src/components/ui/radio/Radio.stories.tsx +1 -1
  137. package/src/components/ui/section/Section.stories.tsx +4 -4
  138. package/src/components/ui/select/Select.tsx +1 -1
  139. package/src/components/ui/stack/Stack.stories.tsx +4 -4
  140. package/src/components/ui/tabs/Tabs.tsx +2 -2
  141. package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
  142. package/src/components/ui/toolbar-button/ToolbarButton.tsx +1 -1
  143. package/src/examples/react-hook-form/ControlledFields.tsx +171 -0
  144. package/src/examples/react-hook-form/ReactHookForm.stories.tsx +51 -0
  145. package/src/examples/react-hook-form/ReactHookForm.test.tsx +61 -0
  146. package/src/examples/react-hook-form/RhfContactForm.tsx +99 -0
  147. package/src/stories/PresetShowcase.stories.tsx +15 -15
  148. package/src/theme/index.ts +2 -1
  149. package/src/theme/manifest/css-property-manifest.example.json +79 -0
  150. package/src/theme/manifest/css-property-manifest.schema.json +93 -0
  151. package/src/theme/manifest/css-property-manifest.test.ts +81 -0
  152. package/src/theme/manifest/css-property-manifest.types.ts +49 -0
  153. package/src/theme/manifest/css-property-manifest.validate.ts +84 -0
  154. package/src/theme/manifest/index.ts +11 -0
  155. package/src/theme/tokens-css.test.ts +28 -0
  156. package/src/theme/tokens-css.ts +34 -0
  157. package/src/theme/workout-tokens.ts +61 -0
  158. package/dist/chunk-UXVRPOME.mjs.map +0 -1
@@ -0,0 +1,125 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite'
2
+ import { useState } from 'react'
3
+ import { View, Text } from 'react-native'
4
+ import { CapacityBandChart } from './CapacityBandChart'
5
+ import type {
6
+ CapacityBandDataPoint,
7
+ CapacityBandProjection,
8
+ WorkoutDot,
9
+ } from './CapacityBandChart'
10
+
11
+ const band: CapacityBandDataPoint[] = [
12
+ { date: '2026-06-01', bandLow: 40, bandHigh: 70 },
13
+ { date: '2026-06-05', bandLow: 44, bandHigh: 74 },
14
+ { date: '2026-06-09', bandLow: 50, bandHigh: 80 },
15
+ { date: '2026-06-13', bandLow: 54, bandHigh: 86 },
16
+ { date: '2026-06-17', bandLow: 58, bandHigh: 90 },
17
+ { date: '2026-06-21', bandLow: 60, bandHigh: 94 },
18
+ { date: '2026-06-25', bandLow: 62, bandHigh: 96 },
19
+ ]
20
+
21
+ const workouts: WorkoutDot[] = [
22
+ { date: '2026-06-02', load: 58, status: 'within' },
23
+ { date: '2026-06-06', load: 80, status: 'above' },
24
+ { date: '2026-06-10', load: 66, status: 'within' },
25
+ { date: '2026-06-14', load: 48, status: 'below' },
26
+ { date: '2026-06-18', load: 76, status: 'within' },
27
+ { date: '2026-06-23', load: 100, status: 'above' },
28
+ ]
29
+
30
+ const projection: CapacityBandProjection = {
31
+ withTraining: [
32
+ { date: '2026-06-27', bandLow: 64, bandHigh: 99 },
33
+ { date: '2026-06-29', bandLow: 67, bandHigh: 104 },
34
+ ],
35
+ withRest: [
36
+ { date: '2026-06-27', bandLow: 56, bandHigh: 88 },
37
+ { date: '2026-06-29', bandLow: 48, bandHigh: 78 },
38
+ ],
39
+ }
40
+
41
+ const meta: Meta<typeof CapacityBandChart> = {
42
+ title: 'Custom/Workout/CapacityBandChart',
43
+ component: CapacityBandChart,
44
+ tags: ['autodocs'],
45
+ argTypes: {
46
+ width: { control: { type: 'range', min: 240, max: 480, step: 10 }, description: 'Chart width in px' },
47
+ height: { control: { type: 'range', min: 120, max: 280, step: 10 }, description: 'Chart height in px' },
48
+ band: { description: 'Capacity band shape over time' },
49
+ workouts: { description: 'Workout sessions plotted as load dots' },
50
+ projection: { description: 'Optional dashed forward projection (training vs rest)' },
51
+ onWorkoutPress: { description: 'Called when a workout dot is tapped' },
52
+ },
53
+ }
54
+
55
+ export default meta
56
+ type Story = StoryObj<typeof CapacityBandChart>
57
+
58
+ export const Default: Story = {
59
+ args: {
60
+ band,
61
+ workouts,
62
+ projection,
63
+ width: 340,
64
+ height: 200,
65
+ },
66
+ }
67
+
68
+ export const WithoutProjection: Story = {
69
+ args: {
70
+ ...Default.args,
71
+ projection: undefined,
72
+ },
73
+ }
74
+
75
+ export const Compact: Story = {
76
+ args: {
77
+ ...Default.args,
78
+ width: 260,
79
+ height: 140,
80
+ },
81
+ }
82
+
83
+ export const SparseData: Story = {
84
+ args: {
85
+ band: band.slice(0, 3),
86
+ workouts: workouts.slice(0, 2),
87
+ projection: undefined,
88
+ width: 300,
89
+ height: 180,
90
+ },
91
+ }
92
+
93
+ export const Empty: Story = {
94
+ args: {
95
+ band: [],
96
+ workouts: [],
97
+ width: 300,
98
+ height: 180,
99
+ },
100
+ }
101
+
102
+ function InteractiveCapacityBandChart() {
103
+ const [selected, setSelected] = useState<WorkoutDot | null>(null)
104
+ return (
105
+ <View style={{ maxWidth: 380, padding: 16, gap: 10 }}>
106
+ <CapacityBandChart
107
+ band={band}
108
+ workouts={workouts}
109
+ projection={projection}
110
+ width={340}
111
+ height={200}
112
+ onWorkoutPress={setSelected}
113
+ />
114
+ <Text style={{ fontSize: 12, fontFamily: 'Inter, sans-serif', color: '#9CA3AF' }}>
115
+ {selected
116
+ ? `Selected ${selected.date}: load ${selected.load} (${selected.status})`
117
+ : 'Tap a workout dot to inspect the session.'}
118
+ </Text>
119
+ </View>
120
+ )
121
+ }
122
+
123
+ export const Interactive: Story = {
124
+ render: () => <InteractiveCapacityBandChart />,
125
+ }
@@ -0,0 +1,193 @@
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 { CapacityBandChart } from './CapacityBandChart'
5
+ import type {
6
+ CapacityBandDataPoint,
7
+ CapacityBandProjection,
8
+ WorkoutDot,
9
+ } from './CapacityBandChart'
10
+
11
+ const band: CapacityBandDataPoint[] = [
12
+ { date: '2026-06-01', bandLow: 40, bandHigh: 70 },
13
+ { date: '2026-06-05', bandLow: 44, bandHigh: 74 },
14
+ { date: '2026-06-09', bandLow: 50, bandHigh: 80 },
15
+ { date: '2026-06-13', bandLow: 54, bandHigh: 86 },
16
+ ]
17
+
18
+ const workouts: WorkoutDot[] = [
19
+ { date: '2026-06-02', load: 58, status: 'within' },
20
+ { date: '2026-06-06', load: 80, status: 'above' },
21
+ { date: '2026-06-10', load: 48, status: 'below' },
22
+ ]
23
+
24
+ const projection: CapacityBandProjection = {
25
+ withTraining: [
26
+ { date: '2026-06-15', bandLow: 56, bandHigh: 90 },
27
+ { date: '2026-06-17', bandLow: 60, bandHigh: 96 },
28
+ ],
29
+ withRest: [
30
+ { date: '2026-06-15', bandLow: 50, bandHigh: 80 },
31
+ { date: '2026-06-17', bandLow: 44, bandHigh: 70 },
32
+ ],
33
+ }
34
+
35
+ const baseProps = { band, workouts, width: 320, height: 200 }
36
+
37
+ describe('CapacityBandChart', () => {
38
+ describe('rendering', () => {
39
+ it('renders the chart container', () => {
40
+ render(<CapacityBandChart {...baseProps} />)
41
+ expect(screen.getByTestId('capacity-band-chart')).toBeInTheDocument()
42
+ })
43
+
44
+ it('renders the shaded band fill as column cells', () => {
45
+ render(<CapacityBandChart {...baseProps} />)
46
+ expect(screen.getByTestId('capacity-band-chart-band')).toBeInTheDocument()
47
+ expect(
48
+ screen.getAllByTestId('capacity-band-chart-band-cell').length,
49
+ ).toBeGreaterThan(0)
50
+ })
51
+
52
+ it('renders the top and bottom band edge lines', () => {
53
+ render(<CapacityBandChart {...baseProps} />)
54
+ expect(
55
+ screen.getAllByTestId('capacity-band-chart-edge-top').length,
56
+ ).toBe(band.length - 1)
57
+ expect(
58
+ screen.getAllByTestId('capacity-band-chart-edge-bottom').length,
59
+ ).toBe(band.length - 1)
60
+ })
61
+
62
+ it('renders a conceptual Load axis label with no numbers', () => {
63
+ render(<CapacityBandChart {...baseProps} />)
64
+ expect(
65
+ screen.getByTestId('capacity-band-chart-y-axis-label'),
66
+ ).toHaveTextContent('Load')
67
+ })
68
+
69
+ it('renders x-axis date labels', () => {
70
+ render(<CapacityBandChart {...baseProps} />)
71
+ expect(
72
+ screen.getAllByTestId('capacity-band-chart-x-label').length,
73
+ ).toBeGreaterThan(0)
74
+ })
75
+
76
+ it('renders the empty state when band has no data', () => {
77
+ render(<CapacityBandChart band={[]} workouts={[]} width={320} height={200} />)
78
+ expect(screen.getByTestId('capacity-band-chart-empty')).toBeInTheDocument()
79
+ expect(
80
+ screen.queryByTestId('capacity-band-chart-band'),
81
+ ).not.toBeInTheDocument()
82
+ })
83
+ })
84
+
85
+ describe('workout dots', () => {
86
+ it('renders one dot per workout', () => {
87
+ render(<CapacityBandChart {...baseProps} />)
88
+ expect(screen.getAllByTestId('capacity-band-chart-dot')).toHaveLength(
89
+ workouts.length,
90
+ )
91
+ })
92
+
93
+ it('colors dots by status relative to the band', () => {
94
+ render(<CapacityBandChart {...baseProps} />)
95
+ const dots = screen.getAllByTestId('capacity-band-chart-dot')
96
+ expect(dots[0]).toHaveStyle({ backgroundColor: '#14B8A6' }) // within
97
+ expect(dots[1]).toHaveStyle({ backgroundColor: '#FFB020' }) // above
98
+ expect(dots[2]).toHaveStyle({ backgroundColor: '#2196F3' }) // below
99
+ })
100
+
101
+ it('renders dots at the spec 8px size with a visible outline', () => {
102
+ render(<CapacityBandChart {...baseProps} />)
103
+ const dots = screen.getAllByTestId('capacity-band-chart-dot')
104
+ expect(dots[0]).toHaveStyle({
105
+ width: '8px',
106
+ height: '8px',
107
+ borderTopColor: '#F3F4F6',
108
+ })
109
+ })
110
+
111
+ it('does not render dots as buttons without onWorkoutPress', () => {
112
+ render(<CapacityBandChart {...baseProps} />)
113
+ expect(screen.queryByRole('button')).not.toBeInTheDocument()
114
+ })
115
+ })
116
+
117
+ describe('interaction', () => {
118
+ it('fires onWorkoutPress with the tapped workout', () => {
119
+ const onWorkoutPress = vi.fn()
120
+ render(<CapacityBandChart {...baseProps} onWorkoutPress={onWorkoutPress} />)
121
+ const dots = screen.getAllByTestId('capacity-band-chart-dot')
122
+ fireEvent.click(dots[1])
123
+ expect(onWorkoutPress).toHaveBeenCalledWith(workouts[1])
124
+ })
125
+
126
+ it('exposes dots as labeled buttons when interactive', () => {
127
+ render(<CapacityBandChart {...baseProps} onWorkoutPress={vi.fn()} />)
128
+ expect(
129
+ screen.getByLabelText('Workout on 6/6, load 80, above range'),
130
+ ).toHaveAttribute('role', 'button')
131
+ })
132
+ })
133
+
134
+ describe('projection', () => {
135
+ it('renders dashed diverging training and rest edges when provided', () => {
136
+ render(<CapacityBandChart {...baseProps} projection={projection} />)
137
+ expect(
138
+ screen.getByTestId('capacity-band-chart-projection'),
139
+ ).toBeInTheDocument()
140
+ expect(
141
+ screen.getAllByTestId('capacity-band-chart-projection-training').length,
142
+ ).toBeGreaterThan(0)
143
+ expect(
144
+ screen.getAllByTestId('capacity-band-chart-projection-rest').length,
145
+ ).toBeGreaterThan(0)
146
+ })
147
+
148
+ it('labels the training and rest projections', () => {
149
+ render(<CapacityBandChart {...baseProps} projection={projection} />)
150
+ expect(
151
+ screen.getByTestId('capacity-band-chart-projection-training-label'),
152
+ ).toHaveTextContent('Training')
153
+ expect(
154
+ screen.getByTestId('capacity-band-chart-projection-rest-label'),
155
+ ).toHaveTextContent('Rest')
156
+ })
157
+
158
+ it('omits the projection layer when not provided', () => {
159
+ render(<CapacityBandChart {...baseProps} />)
160
+ expect(
161
+ screen.queryByTestId('capacity-band-chart-projection'),
162
+ ).not.toBeInTheDocument()
163
+ })
164
+ })
165
+
166
+ describe('accessibility', () => {
167
+ it('exposes an image role with a descriptive label', () => {
168
+ render(<CapacityBandChart {...baseProps} />)
169
+ const chart = screen.getByTestId('capacity-band-chart')
170
+ expect(chart).toHaveAttribute('role', 'img')
171
+ expect(chart).toHaveAttribute(
172
+ 'aria-label',
173
+ 'Training capacity band chart. Current capacity: below range. 3 workouts shown.',
174
+ )
175
+ })
176
+
177
+ it('has no accessibility violations', async () => {
178
+ const { container } = render(<CapacityBandChart {...baseProps} />)
179
+ expect(await axe(container)).toHaveNoViolations()
180
+ })
181
+
182
+ it('has no accessibility violations with all features enabled', async () => {
183
+ const { container } = render(
184
+ <CapacityBandChart
185
+ {...baseProps}
186
+ projection={projection}
187
+ onWorkoutPress={vi.fn()}
188
+ />,
189
+ )
190
+ expect(await axe(container)).toHaveNoViolations()
191
+ })
192
+ })
193
+ })