package reflux import ( "encoding/json" "fmt" "testing" ) // Scope 必须返回快实现(*rfx), 否则它之后的所有 Get/Set 都退回逐次反射。 // 这一条很容易在重构里悄悄失守: 交回 refx 功能完全正常, 只是慢 3 倍, // 测试不看类型就发现不了。 func TestScopeReturnsFastImpl(t *testing.T) { type leaf struct{ V string } type doc struct { A leaf Tags []string M map[string]any } d := &doc{A: leaf{V: "v"}, Tags: []string{"t"}, M: map[string]any{"k": "v"}} r := New(d) for _, p := range [][]string{nil, {"A"}, {"Tags"}, {"M"}} { got := r.Scope(p...) if _, ok := got.(*rfx); !ok { t.Fatalf("Scope(%v) 返回了 %T, 应当是 *rfx —— 慢实现会让后续操作退回逐次反射", p, got) } // 链下去也必须还是快实现 if inner := got.Get("V"); p != nil && len(p) == 1 && p[0] == "A" { if _, ok := inner.(*rfx); !ok { t.Fatalf("Scope(%v).Get() 返回了 %T", p, inner) } } } } // 语义不能变: Scope 仍然是深度克隆, 改副本不影响原数据 func TestScopeStillIsolates(t *testing.T) { type inner struct { Name string Tags []string } type doc struct { In inner M map[string]string Pure struct{ A, B string } } mk := func() *doc { return &doc{ In: inner{Name: "n", Tags: []string{"t0", "t1"}}, M: map[string]string{"k": "v"}, Pure: struct{ A, B string }{"a", "b"}, } } t.Run("含引用的子树", func(t *testing.T) { d := mk() s := New(d).Scope("In") s.Set("Name", "changed") s.Set("Tags.0", "changed") if d.In.Name != "n" || d.In.Tags[0] != "t0" { t.Fatalf("原数据被改写了: %+v", d.In) } if s.Get("Name").String() != "changed" || s.Get("Tags.0").String() != "changed" { t.Fatal("副本上的写入没生效") } }) t.Run("纯值子树(走浅拷贝分支)", func(t *testing.T) { d := mk() s := New(d).Scope("Pure") s.Set("A", "changed") if d.Pure.A != "a" { t.Fatalf("原数据被改写了: %+v", d.Pure) } }) t.Run("map 子树", func(t *testing.T) { d := mk() s := New(d).Scope("M") s.Set("k", "changed") if d.M["k"] != "v" { t.Fatalf("原 map 被改写了: %v", d.M) } }) t.Run("无参数克隆整个对象", func(t *testing.T) { d := mk() s := New(d).Scope() s.Set("In.Name", "changed") if d.In.Name != "n" { t.Fatalf("原数据被改写了: %+v", d.In) } }) } // 与旧实现逐格对拍 func TestScopeMatchesLegacy(t *testing.T) { type inner struct { Name string Tags []string } type doc struct { In inner M map[string]any S []inner } mk := func() *doc { return &doc{ In: inner{Name: "n", Tags: []string{"t"}}, M: map[string]any{"k": map[string]any{"deep": "d"}}, S: []inner{{Name: "s0"}}, } } paths := [][]string{ nil, {"In"}, {"In", "Name"}, {"In", "Tags"}, {"In", "Tags", "0"}, {"M"}, {"M", "k"}, {"M", "k", "deep"}, {"S"}, {"S", "0"}, {"S", "0", "Name"}, {"nosuch"}, {"In", "nosuch"}, } ops := []struct { name string f func(R) any }{ {"String", func(r R) any { return r.String() }}, {"Exists", func(r R) any { return r.Exists() }}, {"Keys", func(r R) any { return fmt.Sprintf("%v", r.Keys()) }}, {"RawKind", func(r R) any { return r.Raw().Kind().String() }}, {"JSON", func(r R) any { b, e := json.Marshal(r); return fmt.Sprintf("%s/%v", b, e) }}, {"再取一层", func(r R) any { return r.Get("Name").String() }}, } for _, p := range paths { for _, op := range ops { t.Run(fmt.Sprintf("%v/%s", p, op.name), func(t *testing.T) { want := call(newLegacy(mk()).Scope(p...), op.f) got := call(New(mk()).Scope(p...), op.f) if want != got { t.Fatalf("行为不一致\n 旧 = %s\n 新 = %s", want, got) } }) } } }