diff --git a/internal/reconcile/disputes_test.go b/internal/reconcile/disputes_test.go new file mode 100644 index 0000000..dfc6c54 --- /dev/null +++ b/internal/reconcile/disputes_test.go @@ -0,0 +1,131 @@ +package reconcile_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "git.d-ma.be/mathias/tapir/internal/reconcile" +) + +func disputes(base, alt string) []reconcile.Dispute { + return reconcile.FindDisputes(reconcile.Tokenize(base), reconcile.Tokenize(alt)) +} + +func TestFindDisputes_NoneWhenTheTextsAgreeWordForWord(t *testing.T) { + assert.Empty(t, disputes("Vi går igenom läget.", "vi går igenom läget"), + "casing and punctuation are not disagreements") +} + +func TestFindDisputes_OneReplacedWordIsOneDisputeCarryingBothReadings(t *testing.T) { + got := disputes("Vi ska onboardade kunderna.", "Vi ska omvårdade kunderna.") + + require.Len(t, got, 1) + d := got[0] + assert.Equal(t, "onboardade", d.Base) + assert.Equal(t, "omvårdade", d.Alt) + assert.Equal(t, 2, d.BaseStart) + assert.Equal(t, 3, d.BaseEnd) + assert.Equal(t, 2, d.AltStart) + assert.Equal(t, 3, d.AltEnd) +} + +func TestFindDisputes_ReadingsKeepTheirOwnSpellingAndPunctuation(t *testing.T) { + got := disputes("Det blir T+1, alltså.", "Det blir T 1, alltså.") + + require.Len(t, got, 1) + assert.Equal(t, "T+1,", got[0].Base) + assert.Equal(t, "T 1,", got[0].Alt, "the alternative's words replace the span as the engine wrote them") +} + +func TestFindDisputes_AWordOnlyOneEngineHeardHasAnEmptyOtherSide(t *testing.T) { + got := disputes("Man får tillbaka ett förslag.", "Man får tillbaka ett coolt förslag.") + + require.Len(t, got, 1) + assert.Equal(t, "", got[0].Base) + assert.Equal(t, "coolt", got[0].Alt) + assert.Equal(t, got[0].BaseStart, got[0].BaseEnd, "an insertion has an empty base range at its position") + assert.Equal(t, 4, got[0].BaseStart) +} + +func TestFindDisputes_TwoDifferencesSeparatedByOneSharedWordAreOneDispute(t *testing.T) { + // "x ... y" around a single agreed word is one decision for the reader, not two. + got := disputes("a b c d e", "a x c y e") + + require.Len(t, got, 1) + assert.Equal(t, "b c d", got[0].Base) + assert.Equal(t, "x c y", got[0].Alt) +} + +func TestFindDisputes_TwoSharedWordsKeepTheDisputesApart(t *testing.T) { + got := disputes("a b c d e f", "a x c d y f") + + require.Len(t, got, 2) + assert.Equal(t, "b", got[0].Base) + assert.Equal(t, "e", got[1].Base) +} + +func TestFindDisputes_ChainsOfCloseDifferencesMergeIntoOne(t *testing.T) { + got := disputes("a b c d e f g", "a x c y e z g") + + require.Len(t, got, 1) + assert.Equal(t, "b c d e f", got[0].Base) +} + +func TestFindDisputes_LeavesIndicesThatAddressBothTokenSlices(t *testing.T) { + base := reconcile.Tokenize("Hej Anders och Maria") + alt := reconcile.Tokenize("Hej Sanders och Maria") + + got := reconcile.FindDisputes(base, alt) + + require.Len(t, got, 1) + assert.Equal(t, "Anders", base[got[0].BaseStart].Text) + assert.Equal(t, "Sanders", alt[got[0].AltStart].Text) +} + +func TestAutoResolve(t *testing.T) { + tests := []struct { + name string + base, alt string + resolved bool + }{ + {"a compound written as two words or one", "i crossborder delen", "i crossborderdelen", true}, + {"a compound with a hyphen", "crossborder-delen", "crossborder delen", true}, + {"a filler only the alternative heard", "vi ska ju betala", "vi ska betala", true}, + {"a filler only the base heard", "vi ska betala", "vi ska ju betala", true}, + {"two fillers in a row", "vi ska ja ju betala", "vi ska betala", true}, + {"a different word is for the model", "vi ska onboardade", "vi ska omvårdade", false}, + {"a different number is for the model", "totalt 1050 kronor", "totalt 2050 kronor", false}, + {"a content word only one side heard is for the model", "ett förslag", "ett coolt förslag", false}, + {"three filler words are too many to wave through", "vi ska ju då liksom betala", "vi ska betala", false}, + {"a filler swapped for a content word is for the model", "vi ska ju betala", "vi ska nu betala", false}, + {"nej is an answer, not a filler", "men nej det går inte", "men det går inte", false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := disputes(tt.base, tt.alt) + require.Len(t, got, 1, "the fixture must be exactly one dispute") + + assert.Equal(t, tt.resolved, reconcile.AutoResolve(got[0])) + }) + } +} + +func TestContextAround_TakesWordsFromTheBaseOnBothSidesAndStopsAtTheEdges(t *testing.T) { + base := reconcile.Tokenize("ett två tre fyra fem sex sju åtta nio tio") + d := reconcile.Dispute{BaseStart: 4, BaseEnd: 6} // "fem sex" + + before, after := reconcile.ContextAround(base, d, 3) + + assert.Equal(t, "två tre fyra", before) + assert.Equal(t, "sju åtta nio", after) + + b2, a2 := reconcile.ContextAround(base, reconcile.Dispute{BaseStart: 0, BaseEnd: 1}, 3) + assert.Equal(t, "", b2) + assert.Equal(t, "två tre fyra", a2) + + b3, a3 := reconcile.ContextAround(base, reconcile.Dispute{BaseStart: 9, BaseEnd: 10}, 3) + assert.Equal(t, "sju åtta nio", b3) + assert.Equal(t, "", a3) +}