80 lines
3.7 KiB
Go
80 lines
3.7 KiB
Go
package membank
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
|
||
"textmachine/backend/internal/lang"
|
||
)
|
||
|
||
// render_test.go: the injection RENDERER fixtures — the exact bytes the bank puts on the wire for
|
||
// each role. ruTX is the ru target wire-text (pair-14 §2: the strings live in lang/data/injection.txt).
|
||
|
||
func ruTX() lang.InjectionTexts { return lang.InjectionTextsFor("ru") }
|
||
|
||
func TestRenderGlossaryBlock(t *testing.T) {
|
||
confirmed := PickedEntry{entry: &entry{src: "阿Q", dst: "А-кью", status: "approved"}, Disp: Confirmed}
|
||
ambiguous := PickedEntry{entry: &entry{src: "小D", dst: "Малыш Дэ", status: "auto"}, Disp: Ambiguous}
|
||
emptyDst := PickedEntry{entry: &entry{src: "鈴木", dst: "", status: "auto"}, Disp: Ambiguous}
|
||
|
||
block := RenderGlossaryBlock([]PickedEntry{confirmed, ambiguous, emptyDst}, ruTX())
|
||
if !strings.Contains(block, "阿Q → А-кью") {
|
||
t.Errorf("confirmed line missing: %q", block)
|
||
}
|
||
if !strings.Contains(block, "小D → Малыш Дэ ⟨проверить⟩") {
|
||
t.Errorf("ambiguous line missing its unverified tag: %q", block)
|
||
}
|
||
if strings.Contains(block, "鈴木") {
|
||
t.Errorf("empty-dst candidate should be skipped (nothing to inject): %q", block)
|
||
}
|
||
// Nothing to inject → empty block (better nothing).
|
||
if RenderGlossaryBlock(nil, ruTX()) != "" {
|
||
t.Error("empty selection must render an empty block")
|
||
}
|
||
if RenderGlossaryBlock([]PickedEntry{emptyDst}, ruTX()) != "" {
|
||
t.Error("only-empty-dst selection must render an empty block")
|
||
}
|
||
}
|
||
|
||
// TestDC3GenderInjection pins the DC3 gender constraint annotation on the editor block (WS5 §5(б)): a
|
||
// gendered CONFIRMED term carries a gender directive; a genderless term does not (byte-identical line).
|
||
func TestDC3GenderInjection(t *testing.T) {
|
||
mk := func(gender string) []PickedEntry {
|
||
return []PickedEntry{{entry: &entry{src: "方源", dst: "Фан Юань", status: "approved", gender: gender}, Via: "方源", Disp: Confirmed}}
|
||
}
|
||
male := RenderEditorConstraintBlock(mk("male"), ruTX())
|
||
if !strings.Contains(male, "方源 → «Фан Юань» (муж.") {
|
||
t.Fatalf("male term must carry a masculine directive, got:\n%s", male)
|
||
}
|
||
hidden := RenderEditorConstraintBlock(mk("hidden"), ruTX())
|
||
if !strings.Contains(hidden, "пол СКРЫТ") {
|
||
t.Fatalf("hidden term must carry the gender-avoidance mandate, got:\n%s", hidden)
|
||
}
|
||
none := RenderEditorConstraintBlock(mk(""), ruTX())
|
||
if !strings.HasSuffix(none, "«Фан Юань»") {
|
||
t.Fatalf("a genderless term must have NO gender note (line ends at the dst), got:\n%s", none)
|
||
}
|
||
}
|
||
|
||
// TestZeroValuePickedEntryAndBankDoNotPanic pins the pack-16 tail: both types are EXPORTED with exported
|
||
// fields, so a caller outside this package can construct a zero value whose private row/matcher is nil.
|
||
// Every consumer used to nil-deref on it — a panic in a paid run. The contract is now "a row-less record is
|
||
// skipped; a matcher-less bank selects nothing", which cannot change a real bank's behaviour because
|
||
// Materialize and Select always populate both.
|
||
func TestZeroValuePickedEntryAndBankDoNotPanic(t *testing.T) {
|
||
zero := []PickedEntry{{Via: "x", Disp: Confirmed}}
|
||
tx := lang.InjectionTextsFor("ru")
|
||
if got := RenderGlossaryBlock(zero, tx); got != "" {
|
||
t.Fatalf("a row-less record must render nothing, got %q", got)
|
||
}
|
||
if got := RenderEditorConstraintBlock(zero, tx); got != "" {
|
||
t.Fatalf("a row-less record must render no constraint, got %q", got)
|
||
}
|
||
var b Bank
|
||
if misses := b.Postcheck(zero, "любой текст"); len(misses) != 0 {
|
||
t.Fatalf("a row-less record must produce no post-check miss, got %+v", misses)
|
||
}
|
||
if sel := b.Select("непустой текст для матчера", 1, nil, 100); len(sel.Injected) != 0 {
|
||
t.Fatalf("a zero-value bank must select nothing, got %+v", sel.Injected)
|
||
}
|
||
}
|