Files
interpres/orderedmap_test.go
T

215 lines
5.7 KiB
Go
Raw Normal View History

// 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 := Marshal(map[string]any{"t": m}, InlineTables(60))
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)
}
})
}