package text import "testing" func TestTrimEnclosureFoldsPairedMarksInAnyScript(t *testing.T) { cases := []struct{ in, want string }{ {"《咏梅》", "咏梅"}, // CJK book-title marks: the case measured in a live draft wave {"「名前」", "名前"}, // CJK corner brackets {"«Название»", "Название"}, // guillemets {"“Title”", "Title"}, // curly quotes {`"Title"`, "Title"}, // ASCII quotes (category Po — not covered by Ps/Pe) {"(термин)", "термин"}, {"《《двойное》》", "двойное"}, // repeats until the ends stop pairing {"咏梅", "咏梅"}, // nothing to trim {"O'Brien", "O'Brien"}, // an apostrophe inside a word is not an enclosure {"咏梅》", "咏梅》"}, // one-sided: left visibly wrong rather than silently repaired {"「」", "「」"}, // an empty pair is never trimmed to nothing {"", ""}, } for _, c := range cases { if got := TrimEnclosure(c.in); got != c.want { t.Errorf("TrimEnclosure(%q) = %q, want %q", c.in, got, c.want) } } } // TestNormVersionUnmovedByEnclosureTrim is the axis pin: the trim lives outside the normalization // artifact, so shipping it moves no snapshot and re-bills no book. If someone folds it into // NormalizeSourceKey, this literal must be updated deliberately — which is a --resnapshot decision. func TestNormVersionUnmovedByEnclosureTrim(t *testing.T) { if NormalizeSourceKey("《咏梅》") == NormalizeSourceKey("咏梅") { t.Fatal("the normalizer must NOT trim enclosures: that would re-verdict every match the bank ever made") } const want = "memnorm-v3-nfkc+apos+stripignorable+trad+kana+lower/nfc+dash+apos+stripignorable+lower+yofold+u15.0.0+xtextv0.40.0" if got := NormVersion(); len(got) < len(want) || got[:len(want)] != want { t.Fatalf("the normalization artifact version moved: %q", got) } }