import "jest"; import { compose } from "../../compose"; import { RefsNode } from "../../composing"; import { EncodeToPlainSingleLineConstraints } from "../encodeToPlainSingleLine"; import { buildOpSequence, OpType, Segment, segmentRanges, StartOp, StopOp } from "../segmentRanges"; const { doc, p } = compose; describe(segmentRanges.name, () => { function test(doc: RefsNode, expected: ReadonlyArray>, constraints?: EncodeToPlainSingleLineConstraints) { const { node, refMap } = doc; // Look for ranges using single-letter names. const ranges = []; for (const id of "abcdefghijklmnopqrstuvwxyz".split("")) { const from = refMap.get(id); const to = refMap.get(`/${id}`); if (from !== undefined && to !== undefined) { ranges.push({ id, from, to }); } } const labels = ranges.map(range => ({ rangeId: range.id, id: `${range.id}-label` })); expect(segmentRanges(node, ranges, labels, { constraints })).toMatchObject(expected); } it("returns an empty array when no ranges specified", () => { test(doc(p("Hello world")), []); }); it("returns an empty array when no ranges match", () => { test(doc(p("He{a}llo wor{/a}ld")), [ { text: "llo wor", textBefore: "He", textAfter: "ld", labelIds: set("a-label"), rangeId: "a" } ]); }); it("returns before/after text", () => { test(doc(p("He{a}llo wor{/a}ld")), [ { text: "llo wor", textBefore: "He", textAfter: "ld", labelIds: set("a-label"), rangeId: "a" } ]); }); it("honors constraints", () => { test( doc(p("Hel{a}lo wo{/a}rld")), [{ text: "lo wo", textBefore: "…l", textAfter: "r…", labelIds: set("a-label"), rangeId: "a" }], { text: 5, textBefore: 2, textAfter: 2 } ); }); describe("overlaps", () => { it("offset", () => { test(doc(p("He{a}llo {b}wor{/a}ld{/b}")), [ { text: "llo wor", labelIds: set("a-label"), rangeId: "a" }, { text: "wor", labelIds: set("a-label", "b-label"), rangeId: undefined }, { text: "world", labelIds: set("b-label"), rangeId: "b" } ]); }); it("identical range (×2)", () => { test(doc(p("He{a}{b}llo wor{/a}{/b}ld")), [{ text: "llo wor", labelIds: set("a-label", "b-label"), rangeId: "b" }]); }); it("identical range (×3)", () => { test(doc(p("He{a}{b}{c}llo wor{/a}{/b}{/c}ld")), [ { text: "llo wor", labelIds: set("a-label", "b-label", "c-label"), rangeId: "c" } ]); }); it("offset tails (×2)", () => { test(doc(p("{a}{b}Hello{/a} world{/b}")), [ { text: "Hello", labelIds: set("a-label", "b-label"), rangeId: "a" }, { text: "Hello world", labelIds: set("b-label"), rangeId: "b" } ]); }); it("offset tails (×3)", () => { test(doc(p("{a}{b}{c}Hello{/a} wor{/c}ld{/b}")), [ { text: "Hello", labelIds: set("a-label", "b-label", "c-label"), rangeId: "a" }, { text: "Hello wor", labelIds: set("b-label", "c-label"), rangeId: "c" }, { text: "Hello world", labelIds: set("b-label"), rangeId: "b" } ]); }); it("offset heads (×2)", () => { test(doc(p("{a}Hello {b}world{/a}{/b}")), [ { text: "Hello world", labelIds: set("a-label"), rangeId: "a" }, { text: "world", labelIds: set("a-label", "b-label"), rangeId: "b" } ]); }); it("offset heads (×3)", () => { test(doc(p("{a}Hel{c}lo {b}world{/a}{/b}{/c}")), [ { text: "Hello world", labelIds: set("a-label"), rangeId: "a" }, { text: "lo world", labelIds: set("a-label", "c-label"), rangeId: "c" }, { text: "world", labelIds: set("a-label", "b-label", "c-label"), rangeId: "b" } ]); }); }); describe("adjacent", () => { it("separated", () => { test(doc(p("{a}Hello{/a} {b}world{/b}")), [ { text: "Hello", labelIds: set("a-label"), rangeId: "a" }, { text: "world", labelIds: set("b-label"), rangeId: "b" } ]); }); it("touching", () => { test(doc(p("{a}Hello{/a}{b}world{/b}")), [ { text: "Hello", labelIds: set("a-label"), rangeId: "a" }, { text: "world", labelIds: set("b-label"), rangeId: "b" } ]); }); it("agnostic to range order", () => { test(doc(p("{b}Hello{/b}{a}world{/a}")), [ { text: "Hello", labelIds: set("b-label"), rangeId: "b" }, { text: "world", labelIds: set("a-label"), rangeId: "a" } ]); }); }); }); describe(buildOpSequence.name, () => { describe("single", () => { it("non-collapsed turned into ops", () => { expect(buildOpSequence([{ id: "0", from: 2, to: 4 }], [{ id: "a", rangeId: "0" }])).toMatchObject([ start(2, "a"), stop(4, "a") ]); }); it("collapsed ignored", () => { expect(buildOpSequence([{ id: "0", from: 2, to: 2 }], [{ id: "a", rangeId: "0" }])).toMatchObject([]); }); }); describe("multiple", () => { it("separated, same label", () => { expect( buildOpSequence( [{ id: "0", from: 2, to: 4 }, { id: "1", from: 6, to: 8 }], [{ id: "a", rangeId: "0" }, { id: "a", rangeId: "1" }] ) ).toMatchObject([start(2, "a"), stop(4, "a"), start(6, "a"), stop(8, "a")]); }); it("separated, same label, reverse order", () => { expect( buildOpSequence( [{ id: "1", from: 6, to: 8 }, { id: "0", from: 2, to: 4 }], [{ id: "a", rangeId: "0" }, { id: "a", rangeId: "1" }] ) ).toMatchObject([start(2, "a"), stop(4, "a"), start(6, "a"), stop(8, "a")]); }); it("adjacent same-label ranges merged", () => { expect( buildOpSequence( [{ id: "0", from: 2, to: 4 }, { id: "1", from: 4, to: 6 }], [{ id: "a", rangeId: "0" }, { id: "a", rangeId: "1" }] ) ).toMatchObject([start(2, "a"), stop(6, "a")]); }); it("overlapping same-label ranges merged", () => { expect( buildOpSequence( [{ id: "0", from: 2, to: 5 }, { id: "1", from: 4, to: 6 }], [{ id: "a", rangeId: "0" }, { id: "a", rangeId: "1" }] ) ).toMatchObject([start(2, "a"), stop(6, "a")]); }); it("many overlapping same-label ranges merged", () => { expect( buildOpSequence( [{ id: "0", from: 2, to: 5 }, { id: "1", from: 4, to: 6 }, { id: "2", from: 3, to: 5 }], [{ id: "a", rangeId: "0" }, { id: "a", rangeId: "1" }, { id: "a", rangeId: "2" }] ) ).toMatchObject([start(2, "a"), stop(6, "a")]); }); it("overlapping different labels", () => { expect( buildOpSequence( [{ id: "0", from: 2, to: 5 }, { id: "1", from: 4, to: 6 }], [{ id: "a", rangeId: "0" }, { id: "b", rangeId: "1" }] ) ).toMatchObject([start(2, "a"), start(4, "b"), stop(5, "a"), stop(6, "b")]); }); it("same from/to, different labels merges ops", () => { expect( buildOpSequence( [{ id: "0", from: 2, to: 5 }, { id: "1", from: 2, to: 5 }], [{ id: "a", rangeId: "0" }, { id: "b", rangeId: "1" }] ) ).toMatchObject([start(2, "a", "b"), stop(5, "a", "b")]); }); }); }); function start(pos: number, ...labels: string[]): StartOp { return { type: OpType.START, pos, labels: new Set(labels) }; } function stop(pos: number, ...labels: string[]): StopOp { return { type: OpType.STOP, pos, labels: new Set(labels) }; } function set(...values: T[]): Set { return new Set(values); }