1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
package ridetest
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRideSingleSection(t *testing.T) {
r, err := Ride("1km at 10%")
require.NoError(t, err)
assert.Len(t, r.Distances, 11)
assert.Equal(t, 0.0, r.Distances[0])
assert.Equal(t, 1000.0, r.Distances[10])
assert.Equal(t, 0.0, r.Elevations[0])
assert.Equal(t, 100.0, r.Elevations[10])
}
func TestRideSectionsWithDescentAndFlat(t *testing.T) {
r, err := Ride("0.5km at 10% | 0.5km at -10%")
require.NoError(t, err)
assert.Len(t, r.Distances, 11)
assert.Equal(t, 1000.0, r.Distances[10])
assert.Equal(t, 50.0, r.Elevations[5]) // top of the first section
assert.Equal(t, 0.0, r.Elevations[10]) // back to start elevation
}
func TestRideSamplingOption(t *testing.T) {
r, err := Ride("1km at 10%", Sampling(50))
require.NoError(t, err)
assert.Len(t, r.Distances, 21)
assert.Equal(t, 1000.0, r.Distances[20])
assert.Equal(t, 5.0, r.Elevations[1])
}
func TestRideStartCoordHeading(t *testing.T) {
r, err := Ride("2km at 0%", StartCoord(43.10, 5.10), Sampling(1000))
require.NoError(t, err)
// Coordinates advance northeast by 0.01° per km; float64 literals like
// 43.11 drift in the last bits, so compare with a small tolerance.
require.Len(t, r.Coords, 3)
assert.InDelta(t, 43.10, r.Coords[0].Lat, 1e-9)
assert.InDelta(t, 5.10, r.Coords[0].Lon, 1e-9)
assert.InDelta(t, 43.11, r.Coords[1].Lat, 1e-9)
assert.InDelta(t, 5.11, r.Coords[1].Lon, 1e-9)
assert.InDelta(t, 43.12, r.Coords[2].Lat, 1e-9)
assert.InDelta(t, 5.12, r.Coords[2].Lon, 1e-9)
}
func TestRideHeadingOption(t *testing.T) {
r, err := Ride("1km at 0%", StartCoord(43.0, 5.0), Heading(0.1, 0.1), Sampling(1000))
require.NoError(t, err)
// A single kilometer at 0.1°/km ends at (43.1, 5.1), matching rides whose
// points are spaced 0.1° apart over a kilometer.
require.Len(t, r.Coords, 2)
assert.InDelta(t, 43.1, r.Coords[1].Lat, 1e-9)
assert.InDelta(t, 5.1, r.Coords[1].Lon, 1e-9)
}
func TestRideStartTime(t *testing.T) {
start := time.Date(2026, 8, 1, 10, 0, 0, 0, time.UTC)
r, err := Ride("1km at 0%", StartTime(start), Sampling(500))
require.NoError(t, err)
assert.Len(t, r.Timestamps(), 3)
assert.Equal(t, start, r.Timestamps()[0])
assert.Equal(t, start.Add(time.Second), r.Timestamps()[1])
assert.Equal(t, start.Add(2*time.Second), r.Timestamps()[2])
}
func TestRideStartElevation(t *testing.T) {
r, err := Ride("1km at 0%", StartElevation(100))
require.NoError(t, err)
assert.Equal(t, 100.0, r.Elevations[0])
assert.Equal(t, 100.0, r.Elevations[10])
}
func TestRideMetricsCompleteColumns(t *testing.T) {
r, err := Ride("0.5km at 0% hr=150 cad=80 w=200 | 0.5km at 0% hr=140 cad=70 w=150")
require.NoError(t, err)
heartRates, ok := r.HeartRates()
require.True(t, ok)
assert.Equal(t, 150.0, heartRates[0])
assert.Equal(t, 150.0, heartRates[5])
assert.Equal(t, 140.0, heartRates[6])
assert.Equal(t, 140.0, heartRates[10])
cadences, ok := r.Cadences()
require.True(t, ok)
assert.Equal(t, 80.0, cadences[0])
assert.Equal(t, 70.0, cadences[10])
powers, ok := r.Powers()
require.True(t, ok)
assert.Equal(t, 200.0, powers[0])
assert.Equal(t, 150.0, powers[10])
}
func TestRideMetricsWithoutOptions(t *testing.T) {
r, err := Ride("1km at 0%")
require.NoError(t, err)
_, ok := r.HeartRates()
assert.False(t, ok)
_, ok = r.Cadences()
assert.False(t, ok)
_, ok = r.Powers()
assert.False(t, ok)
}
func TestRideMetricsPartialColumnFails(t *testing.T) {
_, err := Ride("0.5km at 0% hr=150 | 0.5km at 0%")
require.Error(t, err)
assert.Contains(t, err.Error(), "hr=")
}
func TestRideParseErrors(t *testing.T) {
cases := []string{
"",
" ",
"|",
"1km at 0% |",
"at 7%",
"1km 7%",
"0km at 7%",
"-1km at 7%",
"1km at abc%",
"xyz at 7%",
"1km away 7%",
"1km at 7% foo=1",
"1km at 7% hr=abc",
}
for _, dsl := range cases {
_, err := Ride(dsl)
require.Error(t, err, "dsl %q", dsl)
}
}
func TestRideRejectsNonPositiveSampling(t *testing.T) {
_, err := Ride("1km at 0%", Sampling(0))
require.Error(t, err)
_, err = Ride("1km at 0%", Sampling(-10))
require.Error(t, err)
}
func TestMustRidePanicsOnMalformedDSL(t *testing.T) {
assert.Panics(t, func() { MustRide("nope") })
}
|