/* Gloss blocks as the app renders them: lib parses the rows, parseMessage decides where each block ends. */ import { describe, it, expect } from "vitest"; import { parseMessage } from "@app/domain/gloss.js"; describe("parseMessage", () => { it("splits a gloss block into one entry per = line", () => { const p = parseMessage( "::gloss\n나 | S | I\n가 | V | go\n= I go.\n밥 | O | rice\n먹어 | V | eat\n= I eat rice.\n::", ); expect(p.gloss).toHaveLength(2); expect(p.gloss![0]!.en).toBe("I go."); expect(p.gloss![0]!.parts.map((x) => x.ko)).toEqual(["나", "가"]); expect(p.gloss![1]!.en).toBe("I eat rice."); expect(p.gloss![1]!.parts.map((x) => x.ko)).toEqual(["밥", "먹어"]); }); it("leaves a single-sentence block exactly as parse() produced it", () => { const src = "::gloss\n저는 | T | I | 는\n갔어 | V | went | 었\n= I went.\n::"; const p = parseMessage(src); expect(p.gloss).toHaveLength(1); expect(p.gloss![0]!.en).toBe("I went."); expect(p.gloss![0]!.parts[0]!.highlight).toBe("는"); }); it("keeps a trailing sentence that has no = line", () => { const p = parseMessage("::gloss\n나 | S | I\n= I.\n밥 | O | rice\n::"); expect(p.gloss).toHaveLength(2); expect(p.gloss![1]!.en).toBe(""); }); it("does not disturb the other blocks", () => { const p = parseMessage( [ "Have a look.", "::gloss", "나 | S | I", "= I.", "밥 | O | rice", "= Rice.", "::", "::task translate", "밥 먹어", "::", "::words", "밥 | rice", "::", "::progress 55 | coming along", ].join("\n"), ); expect(p.body).toBe("Have a look."); expect(p.gloss).toHaveLength(2); expect(p.task!.type).toBe("translate"); expect(p.words).toHaveLength(1); expect(p.progress!.score).toBe(55); }); it("passes a message with no gloss block straight through", () => { const p = parseMessage("Just prose.\n::progress 10 | early days"); expect(p.gloss).toBeNull(); expect(p.body).toBe("Just prose."); }); });