|
1 | 1 | import { describe, it, expect } from "vitest"; |
2 | | -import { parse } from "../src/index.js"; |
| 2 | +import { parse, normalizeMode, MODES } from "../src/index.js"; |
3 | 3 |
|
4 | 4 | const PUSHUP = [ |
5 | 5 | 'posecode exercise "Push-up"', |
@@ -38,7 +38,7 @@ describe("parse", () => { |
38 | 38 | const lower = ir!.phases[0]!; |
39 | 39 | expect(lower.name).toBe("Lower"); |
40 | 40 | expect(lower.durationSec).toBe(2); |
41 | | - expect(lower.easing).toBe("ease-in"); |
| 41 | + expect(lower.easing).toBe("drive"); // legacy `ease-in` normalizes to canonical mode |
42 | 42 | expect(lower.cue).toBe("Elbows ~45 from torso"); |
43 | 43 | expect(lower.groundLock.sort()).toEqual(["feet", "hands"]); |
44 | 44 |
|
@@ -114,15 +114,15 @@ describe("parse", () => { |
114 | 114 | expect(errors[0]!.message).toMatch(/header|must start/i); |
115 | 115 | }); |
116 | 116 |
|
117 | | - it("rejects an unknown easing", () => { |
| 117 | + it("rejects an unknown timing mode", () => { |
118 | 118 | const src = [ |
119 | | - 'posecode exercise "Bad easing"', |
| 119 | + 'posecode exercise "Bad mode"', |
120 | 120 | " rig humanoid", |
121 | 121 | ' step "Move" 1s wobble:', |
122 | 122 | " elbows: flex 90", |
123 | 123 | ].join("\n"); |
124 | 124 | const { errors } = parse(src); |
125 | | - expect(errors.some((e) => /easing/i.test(e.message))).toBe(true); |
| 125 | + expect(errors.some((e) => /mode/i.test(e.message))).toBe(true); |
126 | 126 | }); |
127 | 127 |
|
128 | 128 | it("parses turn and travel into the phase IR", () => { |
@@ -267,3 +267,37 @@ describe("clip directive", () => { |
267 | 267 | expect(errors[0]!.message).toContain("clip"); |
268 | 268 | }); |
269 | 269 | }); |
| 270 | + |
| 271 | +describe("timing modes", () => { |
| 272 | + it("accepts the canonical modes", () => { |
| 273 | + for (const m of MODES) { |
| 274 | + const src = `posecode exercise "x"\n rig humanoid\n step "s" 1s ${m}:\n knees: flex 10\n`; |
| 275 | + const { errors } = parse(src); |
| 276 | + expect(errors).toEqual([]); |
| 277 | + } |
| 278 | + }); |
| 279 | + |
| 280 | + it("normalizes legacy easing names to canonical modes", () => { |
| 281 | + expect(normalizeMode("ease-in")).toEqual({ mode: "drive", legacy: true }); |
| 282 | + expect(normalizeMode("ease-out")).toEqual({ mode: "settle", legacy: true }); |
| 283 | + expect(normalizeMode("ease-in-out")).toEqual({ mode: "settle", legacy: true }); |
| 284 | + expect(normalizeMode("linear")).toEqual({ mode: "linear", legacy: false }); |
| 285 | + expect(normalizeMode("flow")).toEqual({ mode: "flow", legacy: false }); |
| 286 | + expect(normalizeMode("bogus")).toEqual({ mode: null, legacy: false }); |
| 287 | + }); |
| 288 | + |
| 289 | + it("legacy documents still parse and carry a canonical mode", () => { |
| 290 | + const src = |
| 291 | + `posecode exercise "sq"\n rig humanoid\n step "Descend" 1s ease-in-out:\n knees: flex 90\n`; |
| 292 | + const { ir, errors } = parse(src); |
| 293 | + expect(errors).toEqual([]); |
| 294 | + expect(ir?.phases[0]?.easing).toBe("settle"); |
| 295 | + }); |
| 296 | + |
| 297 | + it("rejects an unknown mode with a clear error", () => { |
| 298 | + const src = `posecode exercise "x"\n rig humanoid\n step "s" 1s wobble:\n knees: flex 10\n`; |
| 299 | + const { errors } = parse(src); |
| 300 | + expect(errors.length).toBeGreaterThan(0); |
| 301 | + expect(errors[0]!.message.toLowerCase()).toContain("mode"); |
| 302 | + }); |
| 303 | +}); |
0 commit comments