@swimedge/metrics 1.1.2 → 1.1.3
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/index.js +6 -6
- package/dist/index.spec.js +239 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -92,7 +92,7 @@ exports.LAP_METRICS_REGISTRY = {
|
|
|
92
92
|
label: 'Pace',
|
|
93
93
|
unit: '/100m',
|
|
94
94
|
category: MetricCategory.PERFORMANCE,
|
|
95
|
-
description: 'Time per 100m
|
|
95
|
+
description: 'Time per 100m per lap',
|
|
96
96
|
decimals: 1,
|
|
97
97
|
availableFrom: { watch: true, manual: false },
|
|
98
98
|
swimTypes: [SwimType.INDOOR],
|
|
@@ -103,7 +103,7 @@ exports.LAP_METRICS_REGISTRY = {
|
|
|
103
103
|
label: 'SR',
|
|
104
104
|
unit: 'SPM',
|
|
105
105
|
category: MetricCategory.TECHNIQUE,
|
|
106
|
-
description: 'Strokes per minute
|
|
106
|
+
description: 'Strokes per minute per lap',
|
|
107
107
|
icon: 'repeat-outline',
|
|
108
108
|
decimals: 1,
|
|
109
109
|
formula: 'Lap Strokes ÷ (Lap Time in Minutes)',
|
|
@@ -115,7 +115,7 @@ exports.LAP_METRICS_REGISTRY = {
|
|
|
115
115
|
label: 'DPS',
|
|
116
116
|
unit: 'm',
|
|
117
117
|
category: MetricCategory.TECHNIQUE,
|
|
118
|
-
description: 'Distance per stroke
|
|
118
|
+
description: 'Distance per stroke per lap',
|
|
119
119
|
icon: 'arrow-forward-outline',
|
|
120
120
|
decimals: 2,
|
|
121
121
|
formula: 'Lap Distance ÷ Lap Strokes',
|
|
@@ -126,7 +126,7 @@ exports.LAP_METRICS_REGISTRY = {
|
|
|
126
126
|
key: 'strokeStyle',
|
|
127
127
|
label: 'Stroke',
|
|
128
128
|
category: MetricCategory.TECHNIQUE,
|
|
129
|
-
description: 'Swimming stroke used
|
|
129
|
+
description: 'Swimming stroke used per lap',
|
|
130
130
|
icon: 'list-outline',
|
|
131
131
|
availableFrom: { watch: true, manual: false },
|
|
132
132
|
swimTypes: [SwimType.INDOOR],
|
|
@@ -136,7 +136,7 @@ exports.LAP_METRICS_REGISTRY = {
|
|
|
136
136
|
label: 'Time',
|
|
137
137
|
unit: 's',
|
|
138
138
|
category: MetricCategory.PERFORMANCE,
|
|
139
|
-
description: 'Time taken
|
|
139
|
+
description: 'Time taken per lap',
|
|
140
140
|
icon: 'stopwatch-outline',
|
|
141
141
|
source: 'Tracked per lap completion',
|
|
142
142
|
availableFrom: { watch: true, manual: false },
|
|
@@ -147,7 +147,7 @@ exports.LAP_METRICS_REGISTRY = {
|
|
|
147
147
|
key: 'swolfScore',
|
|
148
148
|
label: 'SWOLF',
|
|
149
149
|
category: MetricCategory.TECHNIQUE,
|
|
150
|
-
description: 'SWOLF score
|
|
150
|
+
description: 'SWOLF score per lap',
|
|
151
151
|
decimals: 0,
|
|
152
152
|
availableFrom: { watch: true, manual: false },
|
|
153
153
|
swimTypes: [SwimType.INDOOR],
|
package/dist/index.spec.js
CHANGED
|
@@ -14,6 +14,18 @@ describe('Metrics Formatting Functions', () => {
|
|
|
14
14
|
it('should handle integers', () => {
|
|
15
15
|
expect((0, index_1.formatNumber)(100, 2)).toBe('100.00');
|
|
16
16
|
});
|
|
17
|
+
it('should handle negative numbers', () => {
|
|
18
|
+
expect((0, index_1.formatNumber)(-123.456, 2)).toBe('-123.46');
|
|
19
|
+
});
|
|
20
|
+
it('should handle zero', () => {
|
|
21
|
+
expect((0, index_1.formatNumber)(0, 2)).toBe('0.00');
|
|
22
|
+
});
|
|
23
|
+
it('should handle very small numbers', () => {
|
|
24
|
+
expect((0, index_1.formatNumber)(0.001, 3)).toBe('0.001');
|
|
25
|
+
});
|
|
26
|
+
it('should handle very large numbers', () => {
|
|
27
|
+
expect((0, index_1.formatNumber)(999999.99, 2)).toBe('999999.99');
|
|
28
|
+
});
|
|
17
29
|
});
|
|
18
30
|
describe('formatPace', () => {
|
|
19
31
|
it('should return string pace as-is', () => {
|
|
@@ -30,12 +42,21 @@ describe('Metrics Formatting Functions', () => {
|
|
|
30
42
|
it('should pad seconds with leading zero', () => {
|
|
31
43
|
expect((0, index_1.formatPace)(125)).toBe('2:05');
|
|
32
44
|
});
|
|
45
|
+
it('should handle large values', () => {
|
|
46
|
+
expect((0, index_1.formatPace)(3665)).toBe('61:05');
|
|
47
|
+
});
|
|
48
|
+
it('should handle single digit seconds', () => {
|
|
49
|
+
expect((0, index_1.formatPace)(61)).toBe('1:01');
|
|
50
|
+
});
|
|
33
51
|
});
|
|
34
52
|
describe('formatTime', () => {
|
|
35
53
|
it('should format time in seconds to mm:ss', () => {
|
|
36
54
|
expect((0, index_1.formatTime)(125)).toBe('2:05');
|
|
37
55
|
expect((0, index_1.formatTime)(90)).toBe('1:30');
|
|
56
|
+
});
|
|
57
|
+
it('should format time with hours', () => {
|
|
38
58
|
expect((0, index_1.formatTime)(3665)).toBe('1:01:05');
|
|
59
|
+
expect((0, index_1.formatTime)(7200)).toBe('2:00:00');
|
|
39
60
|
});
|
|
40
61
|
it('should handle zero', () => {
|
|
41
62
|
expect((0, index_1.formatTime)(0)).toBe('0:00');
|
|
@@ -44,9 +65,18 @@ describe('Metrics Formatting Functions', () => {
|
|
|
44
65
|
expect((0, index_1.formatTime)(65)).toBe('1:05');
|
|
45
66
|
expect((0, index_1.formatTime)(5)).toBe('0:05');
|
|
46
67
|
});
|
|
68
|
+
it('should pad minutes with leading zero in hour format', () => {
|
|
69
|
+
expect((0, index_1.formatTime)(3605)).toBe('1:00:05');
|
|
70
|
+
});
|
|
47
71
|
it('should handle fractional seconds by flooring', () => {
|
|
48
72
|
expect((0, index_1.formatTime)(65.7)).toBe('1:05');
|
|
49
73
|
});
|
|
74
|
+
it('should handle exactly 60 seconds', () => {
|
|
75
|
+
expect((0, index_1.formatTime)(60)).toBe('1:00');
|
|
76
|
+
});
|
|
77
|
+
it('should handle exactly 1 hour', () => {
|
|
78
|
+
expect((0, index_1.formatTime)(3600)).toBe('1:00:00');
|
|
79
|
+
});
|
|
50
80
|
});
|
|
51
81
|
describe('formatHeartRate', () => {
|
|
52
82
|
it('should format heart rate with bpm unit', () => {
|
|
@@ -57,6 +87,12 @@ describe('Metrics Formatting Functions', () => {
|
|
|
57
87
|
expect((0, index_1.formatHeartRate)(145.4)).toBe('145 bpm');
|
|
58
88
|
expect((0, index_1.formatHeartRate)(145.6)).toBe('146 bpm');
|
|
59
89
|
});
|
|
90
|
+
it('should handle zero', () => {
|
|
91
|
+
expect((0, index_1.formatHeartRate)(0)).toBe('0 bpm');
|
|
92
|
+
});
|
|
93
|
+
it('should handle high heart rates', () => {
|
|
94
|
+
expect((0, index_1.formatHeartRate)(200)).toBe('200 bpm');
|
|
95
|
+
});
|
|
60
96
|
});
|
|
61
97
|
describe('formatDistance', () => {
|
|
62
98
|
it('should format meters for distances under 1000m', () => {
|
|
@@ -71,5 +107,208 @@ describe('Metrics Formatting Functions', () => {
|
|
|
71
107
|
it('should round meters to nearest integer', () => {
|
|
72
108
|
expect((0, index_1.formatDistance)(500.7)).toBe('501m');
|
|
73
109
|
});
|
|
110
|
+
it('should handle zero', () => {
|
|
111
|
+
expect((0, index_1.formatDistance)(0)).toBe('0m');
|
|
112
|
+
});
|
|
113
|
+
it('should handle very large distances', () => {
|
|
114
|
+
expect((0, index_1.formatDistance)(10000)).toBe('10.0km');
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
describe('formatSwimType', () => {
|
|
118
|
+
it('should format indoor pool type', () => {
|
|
119
|
+
expect((0, index_1.formatSwimType)(index_1.SwimType.INDOOR)).toBe('Indoor Pool');
|
|
120
|
+
expect((0, index_1.formatSwimType)('indoorPool')).toBe('Indoor Pool');
|
|
121
|
+
});
|
|
122
|
+
it('should format open water type', () => {
|
|
123
|
+
expect((0, index_1.formatSwimType)(index_1.SwimType.OPEN_WATER)).toBe('Open Water');
|
|
124
|
+
expect((0, index_1.formatSwimType)('openWater')).toBe('Open Water');
|
|
125
|
+
});
|
|
126
|
+
it('should handle undefined', () => {
|
|
127
|
+
expect((0, index_1.formatSwimType)(undefined)).toBe('Unknown');
|
|
128
|
+
});
|
|
129
|
+
it('should return unknown type as-is', () => {
|
|
130
|
+
expect((0, index_1.formatSwimType)('unknown')).toBe('unknown');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
describe('MetricsHelper', () => {
|
|
135
|
+
describe('getMetric', () => {
|
|
136
|
+
it('should return metric definition by key', () => {
|
|
137
|
+
const metric = index_1.MetricsHelper.getMetric('distance');
|
|
138
|
+
expect(metric).toBeDefined();
|
|
139
|
+
expect(metric?.key).toBe('distance');
|
|
140
|
+
expect(metric?.label).toBe('Distance');
|
|
141
|
+
});
|
|
142
|
+
it('should return undefined for non-existent key', () => {
|
|
143
|
+
const metric = index_1.MetricsHelper.getMetric('nonexistent');
|
|
144
|
+
expect(metric).toBeUndefined();
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
describe('getSessionMetrics', () => {
|
|
148
|
+
it('should return all session metrics', () => {
|
|
149
|
+
const metrics = index_1.MetricsHelper.getSessionMetrics();
|
|
150
|
+
expect(metrics.length).toBeGreaterThan(0);
|
|
151
|
+
expect(metrics.every(m => m.isPerSession)).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
describe('getChartMetrics', () => {
|
|
155
|
+
it('should return only chartable metrics', () => {
|
|
156
|
+
const metrics = index_1.MetricsHelper.getChartMetrics();
|
|
157
|
+
expect(metrics.length).toBeGreaterThan(0);
|
|
158
|
+
expect(metrics.every(m => m.hasChart && m.isPerSession)).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
describe('getLapMetrics', () => {
|
|
162
|
+
it('should return all lap metrics', () => {
|
|
163
|
+
const metrics = index_1.MetricsHelper.getLapMetrics();
|
|
164
|
+
expect(metrics.length).toBeGreaterThan(0);
|
|
165
|
+
expect(metrics).toContainEqual(expect.objectContaining({ key: 'pace' }));
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
describe('getLapMetric', () => {
|
|
169
|
+
it('should return lap metric by key', () => {
|
|
170
|
+
const metric = index_1.MetricsHelper.getLapMetric('pace');
|
|
171
|
+
expect(metric).toBeDefined();
|
|
172
|
+
expect(metric.key).toBe('pace');
|
|
173
|
+
expect(metric.label).toBe('Pace');
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
describe('getMetricsByCategory', () => {
|
|
177
|
+
it('should return metrics by performance category', () => {
|
|
178
|
+
const metrics = index_1.MetricsHelper.getMetricsByCategory(index_1.MetricCategory.PERFORMANCE);
|
|
179
|
+
expect(metrics.length).toBeGreaterThan(0);
|
|
180
|
+
expect(metrics.every(m => m.category === index_1.MetricCategory.PERFORMANCE)).toBe(true);
|
|
181
|
+
});
|
|
182
|
+
it('should return metrics by health category', () => {
|
|
183
|
+
const metrics = index_1.MetricsHelper.getMetricsByCategory(index_1.MetricCategory.HEALTH);
|
|
184
|
+
expect(metrics.length).toBeGreaterThan(0);
|
|
185
|
+
expect(metrics.every(m => m.category === index_1.MetricCategory.HEALTH)).toBe(true);
|
|
186
|
+
});
|
|
187
|
+
it('should return metrics by technique category', () => {
|
|
188
|
+
const metrics = index_1.MetricsHelper.getMetricsByCategory(index_1.MetricCategory.TECHNIQUE);
|
|
189
|
+
expect(metrics.length).toBeGreaterThan(0);
|
|
190
|
+
expect(metrics.every(m => m.category === index_1.MetricCategory.TECHNIQUE)).toBe(true);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
describe('getMetricsBySwimType', () => {
|
|
194
|
+
it('should return indoor pool metrics', () => {
|
|
195
|
+
const metrics = index_1.MetricsHelper.getMetricsBySwimType(index_1.SwimType.INDOOR);
|
|
196
|
+
expect(metrics.length).toBeGreaterThan(0);
|
|
197
|
+
expect(metrics.every(m => m.swimTypes.includes(index_1.SwimType.INDOOR))).toBe(true);
|
|
198
|
+
});
|
|
199
|
+
it('should return open water metrics', () => {
|
|
200
|
+
const metrics = index_1.MetricsHelper.getMetricsBySwimType(index_1.SwimType.OPEN_WATER);
|
|
201
|
+
expect(metrics.length).toBeGreaterThan(0);
|
|
202
|
+
expect(metrics.every(m => m.swimTypes.includes(index_1.SwimType.OPEN_WATER))).toBe(true);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
describe('getIndoorMetrics', () => {
|
|
206
|
+
it('should return only indoor metrics', () => {
|
|
207
|
+
const metrics = index_1.MetricsHelper.getIndoorMetrics();
|
|
208
|
+
expect(metrics.length).toBeGreaterThan(0);
|
|
209
|
+
expect(metrics.every(m => m?.swimTypes?.includes(index_1.SwimType.INDOOR))).toBe(true);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
describe('getOpenWaterMetrics', () => {
|
|
213
|
+
it('should return only open water metrics', () => {
|
|
214
|
+
const metrics = index_1.MetricsHelper.getOpenWaterMetrics();
|
|
215
|
+
expect(metrics.length).toBeGreaterThan(0);
|
|
216
|
+
expect(metrics.every(m => m?.swimTypes?.includes(index_1.SwimType.OPEN_WATER))).toBe(true);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
describe('getAllMetricKeys', () => {
|
|
220
|
+
it('should return all metric keys', () => {
|
|
221
|
+
const keys = index_1.MetricsHelper.getAllMetricKeys();
|
|
222
|
+
expect(keys.length).toBeGreaterThan(0);
|
|
223
|
+
expect(keys).toContain('distance');
|
|
224
|
+
expect(keys).toContain('time');
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
describe('getAllLapMetricKeys', () => {
|
|
228
|
+
it('should return all lap metric keys', () => {
|
|
229
|
+
const keys = index_1.MetricsHelper.getAllLapMetricKeys();
|
|
230
|
+
expect(keys.length).toBeGreaterThan(0);
|
|
231
|
+
expect(keys).toContain('pace');
|
|
232
|
+
expect(keys).toContain('strokeCount');
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
describe('LAP_METRICS_REGISTRY', () => {
|
|
237
|
+
it('should have all required lap metrics', () => {
|
|
238
|
+
expect(index_1.LAP_METRICS_REGISTRY.lapNumber).toBeDefined();
|
|
239
|
+
expect(index_1.LAP_METRICS_REGISTRY.strokeCount).toBeDefined();
|
|
240
|
+
expect(index_1.LAP_METRICS_REGISTRY.pace).toBeDefined();
|
|
241
|
+
expect(index_1.LAP_METRICS_REGISTRY.strokeRate).toBeDefined();
|
|
242
|
+
expect(index_1.LAP_METRICS_REGISTRY.distancePerStroke).toBeDefined();
|
|
243
|
+
expect(index_1.LAP_METRICS_REGISTRY.strokeStyle).toBeDefined();
|
|
244
|
+
expect(index_1.LAP_METRICS_REGISTRY.splitTime).toBeDefined();
|
|
245
|
+
expect(index_1.LAP_METRICS_REGISTRY.swolfScore).toBeDefined();
|
|
246
|
+
});
|
|
247
|
+
it('should have correct descriptions using "per lap"', () => {
|
|
248
|
+
expect(index_1.LAP_METRICS_REGISTRY.pace.description).toBe('Time per 100m per lap');
|
|
249
|
+
expect(index_1.LAP_METRICS_REGISTRY.strokeRate.description).toBe('Strokes per minute per lap');
|
|
250
|
+
expect(index_1.LAP_METRICS_REGISTRY.distancePerStroke.description).toBe('Distance per stroke per lap');
|
|
251
|
+
expect(index_1.LAP_METRICS_REGISTRY.splitTime.description).toBe('Time taken per lap');
|
|
252
|
+
expect(index_1.LAP_METRICS_REGISTRY.swolfScore.description).toBe('SWOLF score per lap');
|
|
253
|
+
});
|
|
254
|
+
it('should have correct categories', () => {
|
|
255
|
+
expect(index_1.LAP_METRICS_REGISTRY.pace.category).toBe(index_1.MetricCategory.PERFORMANCE);
|
|
256
|
+
expect(index_1.LAP_METRICS_REGISTRY.strokeCount.category).toBe(index_1.MetricCategory.TECHNIQUE);
|
|
257
|
+
expect(index_1.LAP_METRICS_REGISTRY.strokeRate.category).toBe(index_1.MetricCategory.TECHNIQUE);
|
|
258
|
+
});
|
|
259
|
+
it('should have correct swim types', () => {
|
|
260
|
+
expect(index_1.LAP_METRICS_REGISTRY.pace.swimTypes).toEqual([index_1.SwimType.INDOOR]);
|
|
261
|
+
expect(index_1.LAP_METRICS_REGISTRY.strokeCount.swimTypes).toEqual([index_1.SwimType.INDOOR]);
|
|
262
|
+
});
|
|
263
|
+
it('should have lap formatters where needed', () => {
|
|
264
|
+
expect(index_1.LAP_METRICS_REGISTRY.pace.lapFormatter).toBeDefined();
|
|
265
|
+
expect(index_1.LAP_METRICS_REGISTRY.splitTime.lapFormatter).toBeDefined();
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
describe('METRICS_REGISTRY', () => {
|
|
269
|
+
it('should have all core session metrics', () => {
|
|
270
|
+
expect(index_1.METRICS_REGISTRY.distance).toBeDefined();
|
|
271
|
+
expect(index_1.METRICS_REGISTRY.time).toBeDefined();
|
|
272
|
+
expect(index_1.METRICS_REGISTRY.activeTime).toBeDefined();
|
|
273
|
+
expect(index_1.METRICS_REGISTRY.restTime).toBeDefined();
|
|
274
|
+
expect(index_1.METRICS_REGISTRY.laps).toBeDefined();
|
|
275
|
+
expect(index_1.METRICS_REGISTRY.avgPace).toBeDefined();
|
|
276
|
+
expect(index_1.METRICS_REGISTRY.calories).toBeDefined();
|
|
277
|
+
});
|
|
278
|
+
it('should have all heart rate metrics', () => {
|
|
279
|
+
expect(index_1.METRICS_REGISTRY.avgHeartRate).toBeDefined();
|
|
280
|
+
expect(index_1.METRICS_REGISTRY.maxHeartRate).toBeDefined();
|
|
281
|
+
expect(index_1.METRICS_REGISTRY.minHeartRate).toBeDefined();
|
|
282
|
+
});
|
|
283
|
+
it('should have all stroke metrics', () => {
|
|
284
|
+
expect(index_1.METRICS_REGISTRY.totalStrokeCount).toBeDefined();
|
|
285
|
+
expect(index_1.METRICS_REGISTRY.avgStrokeRate).toBeDefined();
|
|
286
|
+
expect(index_1.METRICS_REGISTRY.avgDistancePerStroke).toBeDefined();
|
|
287
|
+
expect(index_1.METRICS_REGISTRY.avgSwolfScore).toBeDefined();
|
|
288
|
+
});
|
|
289
|
+
it('should have correct HealthKit sources', () => {
|
|
290
|
+
expect(index_1.METRICS_REGISTRY.distance?.isHealthKit).toBe(true);
|
|
291
|
+
expect(index_1.METRICS_REGISTRY.avgHeartRate?.isHealthKit).toBe(true);
|
|
292
|
+
expect(index_1.METRICS_REGISTRY.totalStrokeCount?.isHealthKit).toBe(true);
|
|
293
|
+
});
|
|
294
|
+
it('should have correct swim type availability', () => {
|
|
295
|
+
expect(index_1.METRICS_REGISTRY.laps?.swimTypes).toEqual([index_1.SwimType.INDOOR]);
|
|
296
|
+
expect(index_1.METRICS_REGISTRY.avgSwolfScore?.swimTypes).toEqual([index_1.SwimType.INDOOR]);
|
|
297
|
+
expect(index_1.METRICS_REGISTRY.distance?.swimTypes).toEqual([index_1.SwimType.INDOOR, index_1.SwimType.OPEN_WATER]);
|
|
298
|
+
});
|
|
299
|
+
it('should have formatters for display metrics', () => {
|
|
300
|
+
expect(index_1.METRICS_REGISTRY.distance?.formatter).toBeDefined();
|
|
301
|
+
expect(index_1.METRICS_REGISTRY.time?.formatter).toBeDefined();
|
|
302
|
+
expect(index_1.METRICS_REGISTRY.avgHeartRate?.formatter).toBeDefined();
|
|
303
|
+
});
|
|
304
|
+
it('should have conditions for optional metrics', () => {
|
|
305
|
+
expect(index_1.METRICS_REGISTRY.restTime?.condition).toBeDefined();
|
|
306
|
+
expect(index_1.METRICS_REGISTRY.avgSwolfScore?.condition).toBeDefined();
|
|
307
|
+
expect(index_1.METRICS_REGISTRY.waterTemp?.condition).toBeDefined();
|
|
308
|
+
});
|
|
309
|
+
it('should mark chartable metrics correctly', () => {
|
|
310
|
+
expect(index_1.METRICS_REGISTRY.distance?.hasChart).toBe(true);
|
|
311
|
+
expect(index_1.METRICS_REGISTRY.time?.hasChart).toBe(true);
|
|
312
|
+
expect(index_1.METRICS_REGISTRY.avgPace?.hasChart).toBe(true);
|
|
74
313
|
});
|
|
75
314
|
});
|