@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.
- package/dist/chunk-3VLOBS6O.mjs +846 -0
- package/dist/chunk-3VLOBS6O.mjs.map +1 -0
- package/dist/{chunk-UXVRPOME.mjs → chunk-5SSKGS6E.mjs} +3 -783
- package/dist/chunk-5SSKGS6E.mjs.map +1 -0
- package/dist/index.d.mts +1005 -4
- package/dist/index.d.ts +1005 -4
- package/dist/index.js +6043 -213
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5901 -177
- package/dist/index.mjs.map +1 -1
- package/dist/theme/index.d.mts +57 -1
- package/dist/theme/index.d.ts +57 -1
- package/dist/theme/index.js +63 -0
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +2 -1
- package/dist/theme/tokens-css.d.mts +18 -0
- package/dist/theme/tokens-css.d.ts +18 -0
- package/dist/theme/tokens-css.js +289 -0
- package/dist/theme/tokens-css.js.map +1 -0
- package/dist/theme/tokens-css.mjs +24 -0
- package/dist/theme/tokens-css.mjs.map +1 -0
- package/docs/WEB_SETUP.md +217 -0
- package/package.json +17 -5
- package/src/components/custom/DateTime/DateTime.stories.tsx +1 -1
- package/src/components/custom/Gauge/Gauge.stories.tsx +45 -0
- package/src/components/custom/Gauge/Gauge.test.tsx +71 -0
- package/src/components/custom/Gauge/Gauge.tsx +167 -0
- package/src/components/custom/Gauge/index.ts +2 -0
- package/src/components/custom/Scatter/Scatter.stories.tsx +85 -0
- package/src/components/custom/Scatter/Scatter.test.tsx +96 -0
- package/src/components/custom/Scatter/Scatter.tsx +260 -0
- package/src/components/custom/Scatter/index.ts +2 -0
- package/src/components/custom/Sidebar/Sidebar.tsx +1 -1
- package/src/components/custom/Treemap/Treemap.stories.tsx +60 -0
- package/src/components/custom/Treemap/Treemap.test.tsx +67 -0
- package/src/components/custom/Treemap/Treemap.tsx +192 -0
- package/src/components/custom/Treemap/index.ts +2 -0
- package/src/components/custom/Workout/BaseBadge.stories.tsx +75 -0
- package/src/components/custom/Workout/BaseBadge.test.tsx +102 -0
- package/src/components/custom/Workout/BaseBadge.tsx +113 -0
- package/src/components/custom/Workout/BodyMap.stories.tsx +121 -0
- package/src/components/custom/Workout/BodyMap.test.tsx +124 -0
- package/src/components/custom/Workout/BodyMap.tsx +348 -0
- package/src/components/custom/Workout/BodyMapDetailPanel.stories.tsx +171 -0
- package/src/components/custom/Workout/BodyMapDetailPanel.test.tsx +225 -0
- package/src/components/custom/Workout/BodyMapDetailPanel.tsx +478 -0
- package/src/components/custom/Workout/CapacityBandChart.stories.tsx +125 -0
- package/src/components/custom/Workout/CapacityBandChart.test.tsx +193 -0
- package/src/components/custom/Workout/CapacityBandChart.tsx +493 -0
- package/src/components/custom/Workout/DeviationBar.stories.tsx +58 -0
- package/src/components/custom/Workout/DeviationBar.test.tsx +86 -0
- package/src/components/custom/Workout/DeviationBar.tsx +78 -0
- package/src/components/custom/Workout/ExerciseCard.stories.tsx +186 -0
- package/src/components/custom/Workout/ExerciseCard.test.tsx +319 -0
- package/src/components/custom/Workout/ExerciseCard.tsx +429 -0
- package/src/components/custom/Workout/InputBar.stories.tsx +75 -0
- package/src/components/custom/Workout/InputBar.test.tsx +116 -0
- package/src/components/custom/Workout/InputBar.tsx +163 -0
- package/src/components/custom/Workout/IntensityBar.stories.tsx +60 -0
- package/src/components/custom/Workout/IntensityBar.test.tsx +113 -0
- package/src/components/custom/Workout/IntensityBar.tsx +166 -0
- package/src/components/custom/Workout/MesoCard.stories.tsx +152 -0
- package/src/components/custom/Workout/MesoCard.test.tsx +170 -0
- package/src/components/custom/Workout/MesoCard.tsx +235 -0
- package/src/components/custom/Workout/MesoProgressBar.stories.tsx +85 -0
- package/src/components/custom/Workout/MesoProgressBar.test.tsx +141 -0
- package/src/components/custom/Workout/MesoProgressBar.tsx +161 -0
- package/src/components/custom/Workout/MesoStatusCard.stories.tsx +126 -0
- package/src/components/custom/Workout/MesoStatusCard.test.tsx +209 -0
- package/src/components/custom/Workout/MesoStatusCard.tsx +456 -0
- package/src/components/custom/Workout/MuscleGroupChip.stories.tsx +118 -0
- package/src/components/custom/Workout/MuscleGroupChip.test.tsx +91 -0
- package/src/components/custom/Workout/MuscleGroupChip.tsx +92 -0
- package/src/components/custom/Workout/PlaceholderStrip.stories.tsx +94 -0
- package/src/components/custom/Workout/PlaceholderStrip.test.tsx +83 -0
- package/src/components/custom/Workout/PlaceholderStrip.tsx +91 -0
- package/src/components/custom/Workout/PrBadge.stories.tsx +105 -0
- package/src/components/custom/Workout/PrBadge.test.tsx +111 -0
- package/src/components/custom/Workout/PrBadge.tsx +103 -0
- package/src/components/custom/Workout/PrHistoryModal.stories.tsx +116 -0
- package/src/components/custom/Workout/PrHistoryModal.test.tsx +161 -0
- package/src/components/custom/Workout/PrHistoryModal.tsx +244 -0
- package/src/components/custom/Workout/ReadinessCheck.stories.tsx +126 -0
- package/src/components/custom/Workout/ReadinessCheck.test.tsx +181 -0
- package/src/components/custom/Workout/ReadinessCheck.tsx +266 -0
- package/src/components/custom/Workout/RestTimer.tsx +8 -6
- package/src/components/custom/Workout/SetRow.stories.tsx +165 -0
- package/src/components/custom/Workout/SetRow.test.tsx +222 -0
- package/src/components/custom/Workout/SetRow.tsx +253 -0
- package/src/components/custom/Workout/Sparkline.stories.tsx +125 -0
- package/src/components/custom/Workout/Sparkline.test.tsx +113 -0
- package/src/components/custom/Workout/Sparkline.tsx +188 -0
- package/src/components/custom/Workout/StatusDot.stories.tsx +150 -0
- package/src/components/custom/Workout/StatusDot.test.tsx +165 -0
- package/src/components/custom/Workout/StatusDot.tsx +159 -0
- package/src/components/custom/Workout/StrengthTrendChart.stories.tsx +130 -0
- package/src/components/custom/Workout/StrengthTrendChart.test.tsx +217 -0
- package/src/components/custom/Workout/StrengthTrendChart.tsx +628 -0
- package/src/components/custom/Workout/SupersetWrapper.tsx +1 -2
- package/src/components/custom/Workout/TempoDisplay.stories.tsx +66 -0
- package/src/components/custom/Workout/TempoDisplay.test.tsx +90 -0
- package/src/components/custom/Workout/TempoDisplay.tsx +207 -0
- package/src/components/custom/Workout/VelocityStrip.stories.tsx +125 -0
- package/src/components/custom/Workout/VelocityStrip.test.tsx +234 -0
- package/src/components/custom/Workout/VelocityStrip.tsx +287 -0
- package/src/components/custom/Workout/WeekRow.stories.tsx +121 -0
- package/src/components/custom/Workout/WeekRow.test.tsx +117 -0
- package/src/components/custom/Workout/WeekRow.tsx +140 -0
- package/src/components/custom/Workout/WeightBadge.stories.tsx +87 -0
- package/src/components/custom/Workout/WeightBadge.test.tsx +164 -0
- package/src/components/custom/Workout/WeightBadge.tsx +113 -0
- package/src/components/custom/Workout/WorkoutCard.stories.tsx +135 -0
- package/src/components/custom/Workout/WorkoutCard.test.tsx +172 -0
- package/src/components/custom/Workout/WorkoutCard.tsx +226 -0
- package/src/components/custom/Workout/WorkoutPill.stories.tsx +63 -0
- package/src/components/custom/Workout/WorkoutPill.test.tsx +153 -0
- package/src/components/custom/Workout/WorkoutPill.tsx +177 -0
- package/src/components/custom/Workout/index.ts +113 -0
- package/src/components/custom/Workout/muscleTaxonomy.ts +252 -0
- package/src/components/custom/index.ts +3 -0
- package/src/components/custom/stepper/Stepper.stories.tsx +1 -1
- package/src/components/custom/stepper/Stepper.tsx +2 -2
- package/src/components/ui/autocomplete/Autocomplete.stories.tsx +1 -1
- package/src/components/ui/autocomplete/Autocomplete.tsx +1 -1
- package/src/components/ui/card/Card.tsx +1 -1
- package/src/components/ui/checkbox/Checkbox.tsx +1 -1
- package/src/components/ui/collapse/Collapse.tsx +1 -1
- package/src/components/ui/data-row/DataRow.stories.tsx +1 -1
- package/src/components/ui/drawer/Drawer.tsx +3 -3
- package/src/components/ui/form-field/FormField.tsx +1 -1
- package/src/components/ui/help-tip/HelpTip.tsx +1 -1
- package/src/components/ui/menu/Menu.tsx +2 -2
- package/src/components/ui/pill/Pill.tsx +1 -1
- package/src/components/ui/popover/Popover.stories.tsx +2 -2
- package/src/components/ui/popover/Popover.tsx +1 -1
- package/src/components/ui/radio/Radio.stories.tsx +1 -1
- package/src/components/ui/section/Section.stories.tsx +4 -4
- package/src/components/ui/select/Select.tsx +1 -1
- package/src/components/ui/stack/Stack.stories.tsx +4 -4
- package/src/components/ui/tabs/Tabs.tsx +2 -2
- package/src/components/ui/toolbar-button/ToolbarButton.stories.tsx +1 -1
- package/src/components/ui/toolbar-button/ToolbarButton.tsx +1 -1
- package/src/examples/react-hook-form/ControlledFields.tsx +171 -0
- package/src/examples/react-hook-form/ReactHookForm.stories.tsx +51 -0
- package/src/examples/react-hook-form/ReactHookForm.test.tsx +61 -0
- package/src/examples/react-hook-form/RhfContactForm.tsx +99 -0
- package/src/stories/PresetShowcase.stories.tsx +15 -15
- package/src/theme/index.ts +2 -1
- package/src/theme/manifest/css-property-manifest.example.json +79 -0
- package/src/theme/manifest/css-property-manifest.schema.json +93 -0
- package/src/theme/manifest/css-property-manifest.test.ts +81 -0
- package/src/theme/manifest/css-property-manifest.types.ts +49 -0
- package/src/theme/manifest/css-property-manifest.validate.ts +84 -0
- package/src/theme/manifest/index.ts +11 -0
- package/src/theme/tokens-css.test.ts +28 -0
- package/src/theme/tokens-css.ts +34 -0
- package/src/theme/workout-tokens.ts +61 -0
- 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
|
+
})
|