832 lines
21 KiB
TypeScript
832 lines
21 KiB
TypeScript
import { parse } from ".";
|
|
|
|
describe("Parser", () => {
|
|
it("creates Untitled workout from empty file", () => {
|
|
expect(parse("")).toMatchInlineSnapshot(`
|
|
Object {
|
|
"author": "",
|
|
"description": "",
|
|
"intervals": Array [],
|
|
"name": "Untitled",
|
|
"tags": Array [],
|
|
}
|
|
`);
|
|
|
|
expect(parse(" \n \n \t")).toMatchInlineSnapshot(`
|
|
Object {
|
|
"author": "",
|
|
"description": "",
|
|
"intervals": Array [],
|
|
"name": "Untitled",
|
|
"tags": Array [],
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("parses workout with just Name field", () => {
|
|
expect(parse(`Name: My Workout`)).toMatchInlineSnapshot(`
|
|
Object {
|
|
"author": "",
|
|
"description": "",
|
|
"intervals": Array [],
|
|
"name": "My Workout",
|
|
"tags": Array [],
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("parses workout header with name, author, description", () => {
|
|
expect(
|
|
parse(`
|
|
Name: My Workout
|
|
Author: John Doe
|
|
Description:
|
|
It's a great workout.
|
|
|
|
Do it when you dare,
|
|
it'll cause lots of pain.
|
|
`),
|
|
).toMatchInlineSnapshot(`
|
|
Object {
|
|
"author": "John Doe",
|
|
"description": "It's a great workout.
|
|
|
|
Do it when you dare,
|
|
it'll cause lots of pain.",
|
|
"intervals": Array [],
|
|
"name": "My Workout",
|
|
"tags": Array [],
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("parses workout header with comma-separated tags", () => {
|
|
expect(
|
|
parse(`
|
|
Name: My Workout
|
|
Tags: Recovery, Intervals , FTP
|
|
`),
|
|
).toMatchInlineSnapshot(`
|
|
Object {
|
|
"author": "",
|
|
"description": "",
|
|
"intervals": Array [],
|
|
"name": "My Workout",
|
|
"tags": Array [
|
|
"Recovery",
|
|
"Intervals",
|
|
"FTP",
|
|
],
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("treats with space-separated tags as single tag", () => {
|
|
expect(
|
|
parse(`
|
|
Name: My Workout
|
|
Tags: Recovery Intervals FTP
|
|
`),
|
|
).toMatchInlineSnapshot(`
|
|
Object {
|
|
"author": "",
|
|
"description": "",
|
|
"intervals": Array [],
|
|
"name": "My Workout",
|
|
"tags": Array [
|
|
"Recovery Intervals FTP",
|
|
],
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("throws error for unknown labels", () => {
|
|
expect(() =>
|
|
parse(`
|
|
Name: Strange workout
|
|
Level: Advanced
|
|
`),
|
|
).toThrowErrorMatchingInlineSnapshot(`"Unknown label \\"Level:\\" at line 3 char 1"`);
|
|
});
|
|
|
|
it("parses basic intervals", () => {
|
|
expect(
|
|
parse(`
|
|
Name: My Workout
|
|
|
|
Rest: 5:00 50%
|
|
|
|
Interval: 10:00 80% 90rpm
|
|
|
|
Rest: 5:00 45%
|
|
`).intervals,
|
|
).toMatchInlineSnapshot(`
|
|
Array [
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 300,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.5,
|
|
},
|
|
"type": "Rest",
|
|
},
|
|
Object {
|
|
"cadence": 90,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 600,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.8,
|
|
},
|
|
"type": "Interval",
|
|
},
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 300,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.45,
|
|
},
|
|
"type": "Rest",
|
|
},
|
|
]
|
|
`);
|
|
});
|
|
|
|
it("parses intervals after multi-line description", () => {
|
|
expect(
|
|
parse(`
|
|
Name: My Workout
|
|
Author: John Doe
|
|
Description:
|
|
It's a great workout.
|
|
|
|
Do it when you dare,
|
|
it'll cause lots of pain.
|
|
|
|
Interval: 5:00 50%
|
|
|
|
Interval: 10:00 100%
|
|
|
|
Interval: 5:00 50%
|
|
`),
|
|
).toMatchInlineSnapshot(`
|
|
Object {
|
|
"author": "John Doe",
|
|
"description": "It's a great workout.
|
|
|
|
Do it when you dare,
|
|
it'll cause lots of pain.",
|
|
"intervals": Array [
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 300,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.5,
|
|
},
|
|
"type": "Interval",
|
|
},
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 600,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 1,
|
|
},
|
|
"type": "Interval",
|
|
},
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 300,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.5,
|
|
},
|
|
"type": "Interval",
|
|
},
|
|
],
|
|
"name": "My Workout",
|
|
"tags": Array [],
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("parses power-range intervals", () => {
|
|
expect(
|
|
parse(`
|
|
Name: My Workout
|
|
|
|
Warmup: 5:30 50%..80% 100rpm
|
|
Cooldown: 5:30 70%..45%
|
|
`).intervals,
|
|
).toMatchInlineSnapshot(`
|
|
Array [
|
|
Object {
|
|
"cadence": 100,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 330,
|
|
},
|
|
"intensity": RangeIntensity {
|
|
"_end": 0.8,
|
|
"_start": 0.5,
|
|
},
|
|
"type": "Warmup",
|
|
},
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 330,
|
|
},
|
|
"intensity": RangeIntensity {
|
|
"_end": 0.45,
|
|
"_start": 0.7,
|
|
},
|
|
"type": "Cooldown",
|
|
},
|
|
]
|
|
`);
|
|
});
|
|
|
|
it("parses free-ride intervals", () => {
|
|
expect(
|
|
parse(`
|
|
Name: My Workout
|
|
|
|
FreeRide: 5:00
|
|
`).intervals,
|
|
).toMatchInlineSnapshot(`
|
|
Array [
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 300,
|
|
},
|
|
"intensity": FreeIntensity {},
|
|
"type": "FreeRide",
|
|
},
|
|
]
|
|
`);
|
|
});
|
|
|
|
it("Treats any interval without intensity as a free-ride interval", () => {
|
|
expect(
|
|
parse(`
|
|
Name: My Workout
|
|
|
|
Interval: 5:00
|
|
`).intervals,
|
|
).toMatchInlineSnapshot(`
|
|
Array [
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 300,
|
|
},
|
|
"intensity": FreeIntensity {},
|
|
"type": "Interval",
|
|
},
|
|
]
|
|
`);
|
|
});
|
|
|
|
const parseInterval = (interval: string) => parse(`Name: My Workout\n${interval}`).intervals[0];
|
|
|
|
it("requires duration parameter to be specified", () => {
|
|
expect(() => parseInterval("Interval: 50%")).toThrowErrorMatchingInlineSnapshot(
|
|
`"Duration not specified at line 2 char 1"`,
|
|
);
|
|
expect(() => parseInterval("Interval: 10rpm")).toThrowErrorMatchingInlineSnapshot(
|
|
`"Duration not specified at line 2 char 1"`,
|
|
);
|
|
});
|
|
|
|
it("allows any order for interval parameters", () => {
|
|
expect(parseInterval("Interval: 50% 00:10")).toMatchInlineSnapshot(`
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 10,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.5,
|
|
},
|
|
"type": "Interval",
|
|
}
|
|
`);
|
|
expect(parseInterval("Interval: 50% 100rpm 00:10")).toMatchInlineSnapshot(`
|
|
Object {
|
|
"cadence": 100,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 10,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.5,
|
|
},
|
|
"type": "Interval",
|
|
}
|
|
`);
|
|
expect(parseInterval("Interval: 100rpm 00:10 50%")).toMatchInlineSnapshot(`
|
|
Object {
|
|
"cadence": 100,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 10,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.5,
|
|
},
|
|
"type": "Interval",
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("allows whitespace between interval parameters", () => {
|
|
expect(parseInterval("Interval: 50% 00:10 100rpm")).toMatchInlineSnapshot(`
|
|
Object {
|
|
"cadence": 100,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 10,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.5,
|
|
},
|
|
"type": "Interval",
|
|
}
|
|
`);
|
|
expect(parseInterval("Interval: \t 50% \t 00:10 \t\t 100rpm \t")).toMatchInlineSnapshot(`
|
|
Object {
|
|
"cadence": 100,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 10,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.5,
|
|
},
|
|
"type": "Interval",
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("parses correct duration formats", () => {
|
|
expect(parseInterval("Interval: 0:10 50%").duration.seconds).toEqual(10);
|
|
expect(parseInterval("Interval: 00:10 50%").duration.seconds).toEqual(10);
|
|
expect(parseInterval("Interval: 0:00:10 50%").duration.seconds).toEqual(10);
|
|
expect(parseInterval("Interval: 0:02:05 50%").duration.seconds).toEqual(125);
|
|
expect(parseInterval("Interval: 1:00:00 50%").duration.seconds).toEqual(3600);
|
|
expect(parseInterval("Interval: 1:00:0 50%").duration.seconds).toEqual(3600);
|
|
expect(parseInterval("Interval: 1:0:0 50%").duration.seconds).toEqual(3600);
|
|
expect(parseInterval("Interval: 10:00:00 50%").duration.seconds).toEqual(36000);
|
|
});
|
|
|
|
it("throws error for incorrect duration formats", () => {
|
|
expect(() => parseInterval("Interval: 10 50%")).toThrowErrorMatchingInlineSnapshot(
|
|
`"Unrecognized interval parameter \\"10\\" at line 2 char 11"`,
|
|
);
|
|
expect(() => parseInterval("Interval: :10 50%")).toThrowErrorMatchingInlineSnapshot(
|
|
`"Unrecognized interval parameter \\":10\\" at line 2 char 11"`,
|
|
);
|
|
expect(() => parseInterval("Interval: 0:100 50%")).toThrowErrorMatchingInlineSnapshot(
|
|
`"Unrecognized interval parameter \\"0:100\\" at line 2 char 11"`,
|
|
);
|
|
expect(() => parseInterval("Interval: 00:00:00:10 50%")).toThrowErrorMatchingInlineSnapshot(
|
|
`"Unrecognized interval parameter \\"00:00:00:10\\" at line 2 char 11"`,
|
|
);
|
|
});
|
|
|
|
it("throws error for unexpected interval parameter", () => {
|
|
expect(() => parseInterval("Interval: 10:00 50% foobar")).toThrowErrorMatchingInlineSnapshot(
|
|
`"Unrecognized interval parameter \\"foobar\\" at line 2 char 21"`,
|
|
);
|
|
expect(() => parseInterval("Interval: 10:00 50% 123blah")).toThrowErrorMatchingInlineSnapshot(
|
|
`"Unrecognized interval parameter \\"123blah\\" at line 2 char 21"`,
|
|
);
|
|
expect(() => parseInterval("Interval: 10:00 50% ^*&")).toThrowErrorMatchingInlineSnapshot(
|
|
`"Unrecognized interval parameter \\"^*&\\" at line 2 char 21"`,
|
|
);
|
|
});
|
|
|
|
it("throws error for unexpected type of interval", () => {
|
|
expect(() => parseInterval("Interval: 30:00 5% \n CustomInterval: 15:00 10%")).toThrowErrorMatchingInlineSnapshot(
|
|
`"Unexpected token [text CustomInterval: 15:00 10%] at line 3 char 1"`,
|
|
);
|
|
});
|
|
|
|
it("parses intervals with comments", () => {
|
|
expect(
|
|
parse(`
|
|
Name: My Workout
|
|
Interval: 10:00 90%
|
|
@ 0:00 Find your rythm.
|
|
@ 1:00 Try to settle in for the effort
|
|
|
|
@ 5:00 Half way through
|
|
|
|
@ 9:00 Almost there
|
|
@ 9:30 Final push. YOU GOT IT!
|
|
|
|
Rest: 5:00 50%
|
|
@ 0:00 Great effort!
|
|
@ 0:30 Cool down well after all of this.
|
|
`),
|
|
).toMatchInlineSnapshot(`
|
|
Object {
|
|
"author": "",
|
|
"description": "",
|
|
"intervals": Array [
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [
|
|
Object {
|
|
"loc": Object {
|
|
"col": 4,
|
|
"row": 3,
|
|
},
|
|
"offset": Duration {
|
|
"seconds": 0,
|
|
},
|
|
"text": "Find your rythm.",
|
|
},
|
|
Object {
|
|
"loc": Object {
|
|
"col": 4,
|
|
"row": 4,
|
|
},
|
|
"offset": Duration {
|
|
"seconds": 60,
|
|
},
|
|
"text": "Try to settle in for the effort",
|
|
},
|
|
Object {
|
|
"loc": Object {
|
|
"col": 4,
|
|
"row": 6,
|
|
},
|
|
"offset": Duration {
|
|
"seconds": 300,
|
|
},
|
|
"text": "Half way through",
|
|
},
|
|
Object {
|
|
"loc": Object {
|
|
"col": 4,
|
|
"row": 8,
|
|
},
|
|
"offset": Duration {
|
|
"seconds": 540,
|
|
},
|
|
"text": "Almost there",
|
|
},
|
|
Object {
|
|
"loc": Object {
|
|
"col": 4,
|
|
"row": 9,
|
|
},
|
|
"offset": Duration {
|
|
"seconds": 570,
|
|
},
|
|
"text": "Final push. YOU GOT IT!",
|
|
},
|
|
],
|
|
"duration": Duration {
|
|
"seconds": 600,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.9,
|
|
},
|
|
"type": "Interval",
|
|
},
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [
|
|
Object {
|
|
"loc": Object {
|
|
"col": 4,
|
|
"row": 12,
|
|
},
|
|
"offset": Duration {
|
|
"seconds": 0,
|
|
},
|
|
"text": "Great effort!",
|
|
},
|
|
Object {
|
|
"loc": Object {
|
|
"col": 4,
|
|
"row": 13,
|
|
},
|
|
"offset": Duration {
|
|
"seconds": 30,
|
|
},
|
|
"text": "Cool down well after all of this.",
|
|
},
|
|
],
|
|
"duration": Duration {
|
|
"seconds": 300,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.5,
|
|
},
|
|
"type": "Rest",
|
|
},
|
|
],
|
|
"name": "My Workout",
|
|
"tags": Array [],
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("parses intervals with negative comment offsets", () => {
|
|
expect(
|
|
parse(`
|
|
Name: My Workout
|
|
Interval: 10:00 90%
|
|
@ 0:10 Find your rythm.
|
|
@ -0:10 Final push. YOU GOT IT!
|
|
|
|
Rest: 5:00 50%
|
|
@ -4:30 Great effort!
|
|
@ -2:00 Cool down well after all of this.
|
|
`),
|
|
).toMatchInlineSnapshot(`
|
|
Object {
|
|
"author": "",
|
|
"description": "",
|
|
"intervals": Array [
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [
|
|
Object {
|
|
"loc": Object {
|
|
"col": 4,
|
|
"row": 3,
|
|
},
|
|
"offset": Duration {
|
|
"seconds": 10,
|
|
},
|
|
"text": "Find your rythm.",
|
|
},
|
|
Object {
|
|
"loc": Object {
|
|
"col": 4,
|
|
"row": 4,
|
|
},
|
|
"offset": Duration {
|
|
"seconds": 590,
|
|
},
|
|
"text": "Final push. YOU GOT IT!",
|
|
},
|
|
],
|
|
"duration": Duration {
|
|
"seconds": 600,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.9,
|
|
},
|
|
"type": "Interval",
|
|
},
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [
|
|
Object {
|
|
"loc": Object {
|
|
"col": 4,
|
|
"row": 7,
|
|
},
|
|
"offset": Duration {
|
|
"seconds": 30,
|
|
},
|
|
"text": "Great effort!",
|
|
},
|
|
Object {
|
|
"loc": Object {
|
|
"col": 4,
|
|
"row": 8,
|
|
},
|
|
"offset": Duration {
|
|
"seconds": 180,
|
|
},
|
|
"text": "Cool down well after all of this.",
|
|
},
|
|
],
|
|
"duration": Duration {
|
|
"seconds": 300,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.5,
|
|
},
|
|
"type": "Rest",
|
|
},
|
|
],
|
|
"name": "My Workout",
|
|
"tags": Array [],
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("throws error when comment offset is outside of interval length", () => {
|
|
expect(() =>
|
|
parse(`
|
|
Name: My Workout
|
|
Interval: 2:00 90%
|
|
@ 0:00 Find your rythm.
|
|
@ 3:10 Try to settle in for the effort
|
|
`),
|
|
).toThrowErrorMatchingInlineSnapshot(`"Comment offset is larger than interval length at line 5 char 5"`);
|
|
});
|
|
|
|
it("throws error when negative comment offset is outside of interval", () => {
|
|
expect(() =>
|
|
parse(`
|
|
Name: My Workout
|
|
Interval: 2:00 90%
|
|
@ 0:00 Find your rythm.
|
|
@ -3:10 Try to settle in for the effort
|
|
`),
|
|
).toThrowErrorMatchingInlineSnapshot(`"Negative comment offset is larger than interval length at line 5 char 5"`);
|
|
});
|
|
|
|
it("throws error when comment offset is the same as another comment offset", () => {
|
|
expect(() =>
|
|
parse(`
|
|
Name: My Workout
|
|
Interval: 2:00 90%
|
|
@ 0:00 First comment
|
|
@ 1:00 Comment
|
|
@ 1:00 Overlapping comment
|
|
@ 1:50 Last comment
|
|
`),
|
|
).toThrowErrorMatchingInlineSnapshot(`"Comment overlaps previous comment at line 6 char 5"`);
|
|
});
|
|
|
|
it("throws error when comment offset is greater than next comment offset", () => {
|
|
expect(() =>
|
|
parse(`
|
|
Name: My Workout
|
|
Interval: 2:00 90%
|
|
@ 0:00 First comment
|
|
@ 1:20 Comment
|
|
@ 1:00 Misplaced comment
|
|
@ 1:50 Last comment
|
|
`),
|
|
).toThrowErrorMatchingInlineSnapshot(`"Comment overlaps previous comment at line 6 char 5"`);
|
|
});
|
|
|
|
it("throws error when comments too close together", () => {
|
|
expect(() =>
|
|
parse(`
|
|
Name: My Workout
|
|
Interval: 2:00 90%
|
|
@ 0:00 First comment
|
|
@ 0:01 Second Comment
|
|
`),
|
|
).toThrowErrorMatchingInlineSnapshot(`"Less than 10 seconds between comments at line 5 char 5"`);
|
|
|
|
expect(() =>
|
|
parse(`
|
|
Name: My Workout
|
|
Interval: 2:00 90%
|
|
@ 0:00 First comment
|
|
@ 0:09 Second Comment
|
|
`),
|
|
).toThrowErrorMatchingInlineSnapshot(`"Less than 10 seconds between comments at line 5 char 5"`);
|
|
});
|
|
|
|
it("triggers no error when comments at least 10 seconds apart", () => {
|
|
expect(
|
|
parse(`
|
|
Name: My Workout
|
|
Interval: 2:00 90%
|
|
@ 0:00 First comment
|
|
@ 0:10 Second Comment
|
|
`),
|
|
).toMatchInlineSnapshot(`
|
|
Object {
|
|
"author": "",
|
|
"description": "",
|
|
"intervals": Array [
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [
|
|
Object {
|
|
"loc": Object {
|
|
"col": 4,
|
|
"row": 3,
|
|
},
|
|
"offset": Duration {
|
|
"seconds": 0,
|
|
},
|
|
"text": "First comment",
|
|
},
|
|
Object {
|
|
"loc": Object {
|
|
"col": 4,
|
|
"row": 4,
|
|
},
|
|
"offset": Duration {
|
|
"seconds": 10,
|
|
},
|
|
"text": "Second Comment",
|
|
},
|
|
],
|
|
"duration": Duration {
|
|
"seconds": 120,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.9,
|
|
},
|
|
"type": "Interval",
|
|
},
|
|
],
|
|
"name": "My Workout",
|
|
"tags": Array [],
|
|
}
|
|
`);
|
|
});
|
|
|
|
it("throws error when comment does not finish before end of interval", () => {
|
|
expect(() =>
|
|
parse(`
|
|
Name: My Workout
|
|
Interval: 1:00 80%
|
|
@ 0:51 First comment
|
|
Interval: 1:00 90%
|
|
`),
|
|
).toThrowErrorMatchingInlineSnapshot(
|
|
`"Less than 10 seconds between comment start and interval end at line 4 char 5"`,
|
|
);
|
|
});
|
|
|
|
it("triggers no error when comment finishes right at interval end", () => {
|
|
expect(
|
|
parse(`
|
|
Name: My Workout
|
|
Interval: 1:00 80%
|
|
@ 0:50 First comment
|
|
Interval: 1:00 90%
|
|
`),
|
|
).toMatchInlineSnapshot(`
|
|
Object {
|
|
"author": "",
|
|
"description": "",
|
|
"intervals": Array [
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [
|
|
Object {
|
|
"loc": Object {
|
|
"col": 4,
|
|
"row": 3,
|
|
},
|
|
"offset": Duration {
|
|
"seconds": 50,
|
|
},
|
|
"text": "First comment",
|
|
},
|
|
],
|
|
"duration": Duration {
|
|
"seconds": 60,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.8,
|
|
},
|
|
"type": "Interval",
|
|
},
|
|
Object {
|
|
"cadence": undefined,
|
|
"comments": Array [],
|
|
"duration": Duration {
|
|
"seconds": 60,
|
|
},
|
|
"intensity": ConstantIntensity {
|
|
"_value": 0.9,
|
|
},
|
|
"type": "Interval",
|
|
},
|
|
],
|
|
"name": "My Workout",
|
|
"tags": Array [],
|
|
}
|
|
`);
|
|
});
|
|
});
|