Explain descendants removed by sequence replacement
This commit is contained in:
@@ -92,7 +92,12 @@ items retain the same guarantees.
|
||||
|
||||
## 3. Record removals caused by registered-sequence replacement — P2
|
||||
|
||||
- [ ] Addressed and regression-tested.
|
||||
- [x] Addressed and regression-tested.
|
||||
|
||||
Completion: replacement queries include missing descendants and use the replacing
|
||||
container's origin. Public regressions verify shorter lists, empty lists, and
|
||||
removed fields; existing append-relevance tests remain green. Focused facade
|
||||
tests and the everyday check passed.
|
||||
|
||||
**Owner:** `applicationOperationPath` in
|
||||
[history.go](../../internal/engine/history.go), lines 134–149.
|
||||
|
||||
@@ -71,6 +71,37 @@ func TestFacadeItemHelperHistoryPreservesTraversalOrder(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFacadeRegisteredReplacementExplainsRemovedDescendants(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
upper []any
|
||||
path configkit.Path
|
||||
}{
|
||||
{"shorter", []any{map[string]any{"id": "main"}}, configkit.Keys("records").Index(1).Key("id")},
|
||||
{"empty", []any{}, configkit.Keys("records").Index(0).Key("id")},
|
||||
{"removed field", []any{map[string]any{"id": "main"}}, configkit.Keys("records").Index(0).Key("text")},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result, err := configkit.LoadLayered[map[string]any](configkit.Input{Source: configkit.Mapping("main", map[string]any{"records": test.upper})}, configkit.LayeredOptions[map[string]any]{
|
||||
Definition: configkit.Definition[map[string]any]{SequenceCompositions: []configkit.SequenceComposition{{Path: configkit.Keys("records"), IdentityField: "id"}}},
|
||||
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.Mapping("lower", map[string]any{"records": []any{map[string]any{"id": "one", "text": "old"}, map[string]any{"id": "two"}}})}}}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
explanation, ok := result.Explain(test.path)
|
||||
if !ok || explanation.Effective.Present || len(explanation.History) < 2 {
|
||||
t.Fatalf("removal not explained: %#v", explanation)
|
||||
}
|
||||
last := explanation.History[len(explanation.History)-1]
|
||||
if !last.Before.Present || last.After.Present || last.Origin.Layer != configkit.LayerBase || len(last.Origin.Locations) != 1 || last.Origin.Locations[0].SourceID != result.Sources()[0].ID {
|
||||
t.Fatalf("wrong removal transition/origin: %#v", last)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFacadeSequencePlacementControlsAcrossLoadEntryPoints(t *testing.T) {
|
||||
definition := configkit.Definition[map[string]any]{
|
||||
SequenceCompositions: []configkit.SequenceComposition{{Path: configkit.Keys("entries"), IdentityField: "id"}},
|
||||
|
||||
@@ -139,7 +139,15 @@ func applicationOperationPath(application historyApplication, query Path, lookup
|
||||
}
|
||||
sequencePathMatched = true
|
||||
if pathContains(container, query) && len(query.segments) > len(container.segments) {
|
||||
if _, ok := historyApplicationLocalPath(application, query); !ok {
|
||||
local, ok := historyApplicationLocalPath(application, query)
|
||||
if contribution.mode == sequenceHistoryReplace {
|
||||
if _, present := documentAtPathWithLookup(application.value, local, lookup); !ok || !present {
|
||||
// A replacement also changes descendants missing from the
|
||||
// incoming list. Attribute their removal to its container.
|
||||
return container, true
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user