import { describe, expect, test } from "bun:test" import { FACT_ELEMENT_TAGS, renderBlockquote, renderQuote, renderValueHtml } from "./placeholder.mjs" import { applyEdits, cutSpan, POLISH_EDIT_FIELDS, project, validatePolishEdits } from "./polish-apply.mjs" import { diffPolish } from "./polish-diff.mjs" type Row = { fact_id: string; kind: string; quote: string; raw: string; value: string; unit: string | null; qualifier: string | null } const row = (fact_id: string, quote: string, raw: string, value: string, unit: string | null, qualifier: string | null = null): Row => ({ fact_id, kind: "measured", quote, raw, value, unit, qualifier, }) const REVENUE = row("F1", "Revenue was about 10 million USD in 2025.", "10 million USD", "10000000", "USD", "approximate") const SPAN = row("F2", "The bridge has a main span of 320 m.", "320 m", "320", "m") const USERS = row("F3", "Usage reached 5,000 users.", "5,000 users", "5000", "users") const BOUND = row("F4", "The bridge has a main span of up to 320 m.", "320 m", "320", "m", "upper_inclusive") const LEDGER = { schemaVersion: 1, rows: [REVENUE, SPAN, USERS, BOUND], events: [] } const f = (r: Row) => renderValueHtml(r, r.raw) const doc = (body: string) => `
\n${body}\n` const edit = (before: string, after: string, reason = "polish") => ({ before, after, reason }) const codes = (list: { code: string }[]) => list.map((item) => item.code) describe("applyEdits: hedges and qualifiers", () => { test("removing 'about' that the row quote carries is rejected and leaves the paragraph unchanged", () => { const html = doc(`Revenue was about ${f(REVENUE)}.
`) const result = applyEdits(html, [edit(`Revenue was about ${f(REVENUE)}.`, `Revenue was ${f(REVENUE)}.`)], LEDGER) expect(result.applied).toEqual([]) expect(codes(result.rejected)).toEqual(["polish_edit_rejected"]) expect(result.rejected[0].reason).toContain("about") expect(result.out).toBe(html) }) test("removing a hedge the row quote lacks is applied", () => { const html = doc(`The span was about ${f(SPAN)}.
`) const result = applyEdits(html, [edit("The span was about ", "The span was ")], LEDGER) expect(result.applied).toEqual([0]) expect(result.rejected).toEqual([]) expect(result.out).toBe(doc(`The span was ${f(SPAN)}.
`)) }) test("dropping the row's qualifier ('up to') is rejected", () => { const html = doc(`The main span is up to ${f(BOUND)}.
`) const result = applyEdits(html, [edit(`The main span is up to ${f(BOUND)}.`, `The main span is ${f(BOUND)}.`)], LEDGER) expect(codes(result.rejected)).toEqual(["polish_edit_rejected"]) expect(result.rejected[0].reason).toContain("up to") }) test("a claim-only sentence keeps the hedges of before", () => { const html = doc("Results may improve [S1].
") expect(codes(applyEdits(html, [edit("Results may improve [S1].", "Results improve [S1].")], LEDGER).rejected)).toEqual(["polish_edit_rejected"]) }) }) describe("applyEdits: fact spans and numerals", () => { test("splitting a sentence with its fact spans intact is applied", () => { const html = doc(`The span is ${f(SPAN)} and usage is ${f(USERS)}.
`) const result = applyEdits(html, [edit(" and usage is ", ". Usage is ")], LEDGER) expect(result.applied).toEqual([0]) expect(result.out).toBe(doc(`The span is ${f(SPAN)}. Usage is ${f(USERS)}.
`)) }) test("swapping two numerals is rejected", () => { const html = doc("See Figure 1 and Figure 2 [S1].
") const result = applyEdits(html, [edit("See Figure 1 and Figure 2 [S1].", "See Figure 2 and Figure 1 [S1].")], LEDGER) expect(codes(result.rejected)).toEqual(["polish_edit_rejected"]) expect(result.rejected[0].reason).toContain("numeral") }) test("moving a decimal point is rejected", () => { const html = doc("Throughput grew 3.5 times [S1].
") expect(codes(applyEdits(html, [edit("grew 3.5 times", "grew 35 times")], LEDGER).rejected)).toEqual(["polish_edit_rejected"]) }) test("changing a plain unit ('320 m' to '320 mm') is rejected with a reason", () => { const html = doc("The span is 320 m [S1].
") const result = applyEdits(html, [edit("The span is 320 m [S1].", "The span is 320 mm [S1].")], LEDGER) expect(codes(result.rejected)).toEqual(["polish_edit_rejected"]) expect(result.rejected[0].reason).toContain("numeral") }) test("swapping the order of two fact spans is rejected", () => { const html = doc(`The span is ${f(SPAN)} and usage is ${f(USERS)}.
`) const before = `The span is ${f(SPAN)} and usage is ${f(USERS)}.` const after = `Usage is ${f(USERS)} and the span is ${f(SPAN)}.` expect(codes(applyEdits(html, [edit(before, after)], LEDGER).rejected)).toEqual(["polish_edit_rejected"]) }) test("editing the rendered value inside a fact span is rejected", () => { const html = doc(`The span is ${f(SPAN)}.
`) const changed = f(SPAN).replace(">320 m<", ">321 m<") expect(codes(applyEdits(html, [edit(f(SPAN), changed)], LEDGER).rejected)).toEqual(["polish_edit_rejected"]) }) }) describe("applyEdits: links and location", () => { test("changing a link is rejected", () => { const html = doc('Reference [S1].
') const before = 'Reference' const result = applyEdits(html, [edit(before, before.replace("/a", "/b"))], LEDGER) expect(codes(result.rejected)).toEqual(["polish_edit_rejected"]) expect(result.rejected[0].reason).toContain("href") }) test("changing a data-claim id is rejected", () => { const html = doc('Results improve [S1].
') const result = applyEdits(html, [edit('data-claim="C1"', 'data-claim="C2"')], LEDGER) expect(codes(result.rejected)).toEqual(["polish_edit_rejected"]) expect(result.rejected[0].reason).toContain("markup changed") }) test("a before that is not found is unlocated", () => { const result = applyEdits(doc("Text [S1].
"), [edit("ABSENT", "replacement")], LEDGER) expect(codes(result.unlocated)).toEqual(["polish_edit_unlocated"]) expect(result.applied).toEqual([]) }) test("a before that occurs twice is unlocated", () => { const result = applyEdits(doc("The term and term appear [S1].
"), [edit("term", "word")], LEDGER) expect(codes(result.unlocated)).toEqual(["polish_edit_unlocated"]) }) test("before matches across normalized whitespace", () => { const html = doc("Results\n improve [S1].
") const result = applyEdits(html, [edit("Results improve [S1].", "Results clearly improve [S1].")], LEDGER) expect(result.applied).toEqual([0]) expect(result.out).toBe(doc("Results clearly improve [S1].
")) }) test("edits apply in order; a later edit whose before is gone is unlocated", () => { const html = doc("Results improve [S1].
") const result = applyEdits( html, [edit("Results improve [S1].", "Results improve steadily [S1]."), edit("Results improve [S1].", "Results improve rapidly [S1].")], LEDGER, ) expect(result.applied).toEqual([0]) expect(result.unlocated.map((item: { index: number }) => item.index)).toEqual([1]) expect(result.out).toBe(doc("Results improve steadily [S1].
")) }) test("a rejected edit does not block a later good edit", () => { const html = doc(`Revenue was about ${f(REVENUE)}.
Results improve [S1].
`) const result = applyEdits( html, [edit(`Revenue was about ${f(REVENUE)}.`, `Revenue was ${f(REVENUE)}.`), edit("Results improve [S1].", "Results clearly improve [S1].")], LEDGER, ) expect(result.applied).toEqual([1]) expect(result.rejected.map((item: { index: number }) => item.index)).toEqual([0]) expect(result.out).toBe(doc(`Revenue was about ${f(REVENUE)}.
Results clearly improve [S1].
`)) }) }) describe("applyEdits: rendered visibility (review #11152 round 1)", () => { const html = doc(`The span is ${f(SPAN)} [S1].
`) const hide = (after: string) => applyEdits(html, [edit(f(SPAN), after)], LEDGER) test.each([ ["an HTML comment", ``], ["a hidden attribute", `${f(SPAN)}`], ["display:none", ``], ["visibility:hidden", ``], ["font-size:0", `${f(SPAN)}`], ["a template", `${f(SPAN)}`], ["an unclosed hidden wrapper", `Results improve [S1].
') const before = 'Results improve' expect(codes(applyEdits(claim, [edit(before, `Results improve`)], LEDGER).rejected)).toEqual(["polish_edit_rejected"]) }) test("a hedge hidden with a hidden attribute is rejected", () => { const claim = doc("Results may improve [S1].
") const result = applyEdits(claim, [edit("Results may improve", "Results may improve")], LEDGER) expect(codes(result.rejected)).toEqual(["polish_edit_rejected"]) expect(result.rejected[0].reason).toContain("markup changed") }) }) describe("applyEdits: the same rules as diffPolish (review #11152 round 1)", () => { test("adding a ticker is rejected", () => { const result = applyEdits(doc("The firm reported growth [S1].
"), [edit("The firm reported", "AAPL reported")], LEDGER) expect(codes(result.rejected)).toEqual(["polish_edit_rejected"]) expect(result.rejected[0].reason).toContain("tickers") }) test("renaming an id that an internal link targets is rejected", () => { const html = doc('Method [S1].
') const result = applyEdits(html, [edit('id="s1"', 'id="s2"')], LEDGER) expect(codes(result.rejected)).toEqual(["polish_edit_rejected"]) expect(result.rejected[0].reason).toContain("#s1") }) test("dropping a table row is rejected", () => { const html = doc("| A |
| B |
Alpha beta gamma. Delta epsilon zeta. Eta theta iota. Kappa lambda mu.
") const edits = [edit("Alpha beta gamma.", "Nu xi omicron."), edit("Delta epsilon zeta.", "Rho sigma tau."), edit("Eta theta iota.", "Phi chi psi.")] const result = applyEdits(html, edits, LEDGER) expect(result.rejected.length).toBeGreaterThan(0) expect(result.rejected.at(-1)?.reason).toContain("percent of the normalized prose") expect(diffPolish(html, result.out, LEDGER)).toEqual([]) }) }) describe("applyEdits: one baseline with diffPolish (review #11152 round 2, M1)", () => { const TOWER = row("F7", "The tower is 320 m tall.", "320 m", "320", "m") const ledger = { ...LEDGER, rows: [...LEDGER.rows, TOWER] } const html = doc(`The tower is ${f(TOWER)} tall. It may be the tallest.
`) const pair = [edit(" tall. It may be", " tall and may be"), edit("and may be the tallest", "and is the tallest")] test("each edit of the hedge-stripping pair alone keeps the hedge", () => { expect(applyEdits(html, [pair[0]], ledger).applied).toEqual([0]) const merged = applyEdits(html, [pair[0]], ledger).out expect(diffPolish(merged, applyEdits(merged, [pair[1]], ledger).out, ledger)).toEqual([]) }) test("the pair together is refused at the second edit, which strips the source document's hedge", () => { const result = applyEdits(html, pair, ledger) expect(result.applied).toEqual([0]) expect(codes(result.rejected)).toEqual(["polish_edit_rejected"]) expect(result.rejected[0].reason).toContain("may") expect(diffPolish(html, result.out, ledger)).toEqual([]) }) test("no cross-edit case remains: every applyEdits output is diffPolish-clean against the input", () => { const cases: [string, { before: string; after: string; reason: string }[]][] = [ [html, pair], [doc("Alpha beta gamma. Delta epsilon zeta. Eta theta iota. Kappa lambda mu.
"), [edit("Alpha beta gamma.", "Nu xi omicron."), edit("Delta epsilon zeta.", "Rho sigma tau."), edit("Eta theta iota.", "Phi chi psi.")]], [doc(`The span is ${f(SPAN)} and usage is ${f(USERS)}.
`), [edit(" and usage is", ". Usage is"), edit("The span is", "Usage is"), edit(". Usage is", " and the span is")]], ] for (const [input, edits] of cases) expect(diffPolish(input, applyEdits(input, edits, ledger).out, ledger)).toEqual([]) }) }) describe("applyEdits: hidden content keeps its bytes and its class (review #11152 round 2, H1 and M2)", () => { const refused = (html: string, edits: { before: string; after: string; reason: string }[], word: string) => { const result = applyEdits(html, edits, LEDGER) expect(result.applied).toEqual([]) expect(result.out).toBe(html) expect(result.rejected.map((item: { reason: string }) => item.reason).join(" | ")).toContain(word) } const flagged = (before: string, after: string) => expect(codes(diffPolish(before, after, LEDGER))).toContain("polish_diff_fail") test("probe 1: a number under aria-hidden renders, so editing it is refused and flagged", () => { const html = doc('Growth was overall.
') refused(html, [edit("3.2% in 2024", "9.9% in 2031")], "numerals") flagged(html, html.replace("3.2% in 2024", "9.9% in 2031")) }) test("probe 2: a number after an unclosedis inside the hidden region, whose bytes may not change", () => { const html = doc("
note
internal note 42
Results improve [S1].
") const result = applyEdits(html, [edit("Results improve [S1].", "Results clearly improve [S1].")], LEDGER) expect(result.applied).toEqual([0]) expect(diffPolish(html, result.out, LEDGER)).toEqual([]) }) const shown = doc(`The span is ${f(SPAN)} [S1].
`) const changed = f(SPAN).replace(">320 m<", ">321 m<") test("relabelling a visible fact span as hidden while changing its number, in one edit, is refused and flagged", () => { refused(shown, [edit(`${f(SPAN)}`, `${changed}`)], "markup changed") flagged(shown, shown.replace(`${f(SPAN)}`, `${changed}`)) }) test("the two-edit variant (hide, then change) is refused at both edits and flagged", () => { const edits = [edit(`${f(SPAN)}`, `