215 lines
5.7 KiB
Go
215 lines
5.7 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package interpres
|
|
|
|
import (
|
|
"context"
|
|
"slices"
|
|
"testing"
|
|
)
|
|
|
|
func TestOrderedMapBasics(t *testing.T) {
|
|
m := NewOrderedMap()
|
|
if m.Len() != 0 {
|
|
t.Fatalf("fresh map holds %d keys", m.Len())
|
|
}
|
|
m.Set("b", 1)
|
|
m.Set("a", 2)
|
|
m.Set("c", 3)
|
|
if got := m.Keys(); !slices.Equal(got, []string{"b", "a", "c"}) {
|
|
t.Errorf("keys = %v, want [b a c]", got)
|
|
}
|
|
if v, ok := m.Get("a"); !ok || v != 2 {
|
|
t.Errorf("a = %v, %v", v, ok)
|
|
}
|
|
m.Set("a", 9)
|
|
if got := m.Keys(); !slices.Equal(got, []string{"b", "a", "c"}) {
|
|
t.Errorf("keys after replace = %v, want the position kept", got)
|
|
}
|
|
if v, _ := m.Get("a"); v != 9 {
|
|
t.Errorf("a = %v, want 9", v)
|
|
}
|
|
seen := ""
|
|
m.Range(func(key string, value any) bool {
|
|
seen += key
|
|
return key != "a"
|
|
})
|
|
if seen != "ba" {
|
|
t.Errorf("range visited %q, want \"ba\"", seen)
|
|
}
|
|
m.Delete("b")
|
|
m.Delete("missing")
|
|
if got := m.Keys(); !slices.Equal(got, []string{"a", "c"}) {
|
|
t.Errorf("keys after delete = %v, want [a c]", got)
|
|
}
|
|
m.Delete("c")
|
|
m.Set("c", 3)
|
|
if got := m.Keys(); !slices.Equal(got, []string{"a", "c"}) {
|
|
t.Errorf("re-set key = %v, want it appended as [a c]", got)
|
|
}
|
|
}
|
|
|
|
func TestMarshalOrderedMap(t *testing.T) {
|
|
t.Run("top level keeps the order", func(t *testing.T) {
|
|
m := NewOrderedMap()
|
|
m.Set("zebra", int64(1))
|
|
m.Set("alpha", "x")
|
|
out, err := Marshal(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := "zebra = 1\nalpha = \"x\"\n"
|
|
if string(out) != want {
|
|
t.Errorf("output:\n%q\nwant:\n%q", out, want)
|
|
}
|
|
})
|
|
t.Run("a pointer top level does the same", func(t *testing.T) {
|
|
m := &OrderedMap{}
|
|
m.Set("second", true)
|
|
m.Set("first", int64(2))
|
|
out, err := Marshal(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(out) != "second = true\nfirst = 2\n" {
|
|
t.Errorf("output %q", out)
|
|
}
|
|
})
|
|
t.Run("a struct field keeps the order as a table", func(t *testing.T) {
|
|
type Cfg struct {
|
|
Title string `toml:"title"`
|
|
Extra *OrderedMap `toml:"extra"`
|
|
}
|
|
m := &OrderedMap{}
|
|
m.Set("late", int64(1))
|
|
m.Set("early", int64(2))
|
|
out, err := Marshal(Cfg{Title: "t", Extra: m})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := "title = \"t\"\n\n[extra]\nlate = 1\nearly = 2\n"
|
|
if string(out) != want {
|
|
t.Errorf("output:\n%q\nwant:\n%q", out, want)
|
|
}
|
|
})
|
|
t.Run("inline form keeps the order too", func(t *testing.T) {
|
|
m := NewOrderedMap()
|
|
m.Set("zebra", int64(1))
|
|
m.Set("alpha", int64(2))
|
|
out, err := NewEncoder().InlineTables(60).Marshal(map[string]any{"t": m})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(out) != "t = {zebra = 1, alpha = 2}\n" {
|
|
t.Errorf("output %q", out)
|
|
}
|
|
})
|
|
t.Run("an array of tables keeps each element's order", func(t *testing.T) {
|
|
type Cfg struct {
|
|
Items []*OrderedMap `toml:"items"`
|
|
}
|
|
a, b := NewOrderedMap(), NewOrderedMap()
|
|
a.Set("y", int64(1))
|
|
a.Set("x", int64(2))
|
|
b.Set("n", int64(3))
|
|
out, err := Marshal(Cfg{Items: []*OrderedMap{a, b}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := "[[items]]\ny = 1\nx = 2\n\n[[items]]\nn = 3\n"
|
|
if string(out) != want {
|
|
t.Errorf("output:\n%q\nwant:\n%q", out, want)
|
|
}
|
|
})
|
|
t.Run("a nil value is skipped", func(t *testing.T) {
|
|
m := NewOrderedMap()
|
|
m.Set("gone", nil)
|
|
m.Set("here", int64(1))
|
|
out, err := Marshal(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(out) != "here = 1\n" {
|
|
t.Errorf("output %q", out)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestDecodeOrderedMap(t *testing.T) {
|
|
t.Run("keys come back in written order", func(t *testing.T) {
|
|
doc := []byte("zebra = 1\nmiddle = \"m\"\nalpha = true\n")
|
|
var m OrderedMap
|
|
if err := Unmarshal(doc, &m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := m.Keys(); !slices.Equal(got, []string{"zebra", "middle", "alpha"}) {
|
|
t.Fatalf("keys = %v", got)
|
|
}
|
|
if v, _ := m.Get("middle"); v != "m" {
|
|
t.Errorf("middle = %#v", v)
|
|
}
|
|
})
|
|
t.Run("a nested table keeps the table order", func(t *testing.T) {
|
|
type Cfg struct {
|
|
Ports []int `toml:"ports"`
|
|
DB *OrderedMap `toml:"db"`
|
|
}
|
|
doc := []byte("ports = [1, 2]\n\n[db]\nslow = 1\nfast = 2\n")
|
|
var cfg Cfg
|
|
if err := Unmarshal(doc, &cfg); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := cfg.DB.Keys(); !slices.Equal(got, []string{"slow", "fast"}) {
|
|
t.Errorf("db keys = %v", got)
|
|
}
|
|
})
|
|
t.Run("an array of tables fills in order", func(t *testing.T) {
|
|
var m OrderedMap
|
|
doc := []byte("b = 1\n[[items]]\nname = \"x\"\n[[items]]\nname = \"y\"\na = 2\n")
|
|
if err := Unmarshal(doc, &m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := m.Keys(); !slices.Equal(got, []string{"b", "items"}) {
|
|
t.Errorf("keys = %v, want [b items]", got)
|
|
}
|
|
elems, ok := m.values["items"].([]map[string]any)
|
|
if !ok || len(elems) != 2 {
|
|
t.Fatalf("items = %#v", m.values["items"])
|
|
}
|
|
if elems[1]["name"] != "y" {
|
|
t.Errorf("second element = %#v", elems[1])
|
|
}
|
|
})
|
|
t.Run("the sorted fallback needs a tree without nodes", func(t *testing.T) {
|
|
// Unmarshal and Decode build the node tree whenever the destination can
|
|
// reach an OrderedMap, so the sorted fallback is only reachable from a
|
|
// tree that never had one.
|
|
tree, _, err := parseWithOptions(context.Background(), []byte("b = 1\na = 2\n"), parseOptions{}, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var m OrderedMap
|
|
if err := newDecoder().decode(tree, &m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := m.Keys(); !slices.Equal(got, []string{"a", "b"}) {
|
|
t.Errorf("keys = %v, want the sorted [a b]", got)
|
|
}
|
|
})
|
|
t.Run("the order survives a round trip", func(t *testing.T) {
|
|
doc := []byte("z = 1\na = 2\nm = 3\n")
|
|
var m OrderedMap
|
|
if err := Unmarshal(doc, &m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out, err := Marshal(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if string(out) != "z = 1\na = 2\nm = 3\n" {
|
|
t.Errorf("output:\n%q", out)
|
|
}
|
|
})
|
|
}
|