clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

feat/atlas-next-horizon
atlas / internal / generate / doc / doc_test.go
doc_test.go 5.7K
1package doc23import (4 "encoding/json"5 "strings"6 "testing"7)89// minimal is the smallest document that passes Validate. Every test below10// starts here and breaks one thing, so a failing case names exactly the rule it11// broke.12func minimal() Document {13 return Document{14 Doc: Doc,15 Version: Version,16 Volume: Volume{Slug: "tunic", Title: "TUNIC"},17 Source: Provenance{Name: "somewhere", Label: "Somewhere", IDSpace: IDSpaceNative},18 Worlds: []World{{19 ID: 427,20 Slug: "world",21 Title: "World",22 Capture: Capture{Kind: "map", ContentHash: "abc", CapturedAt: "2026-07-30T03:57:41.529Z"},23 Lenses: []Lens{{Name: "Default", TileSet: "tunic/world/default-v2"}},24 Collections: []Collection{{25 ID: 1, Title: "Shrines", Kind: KindPoint, Visible: true,26 Features: []Feature{{ID: 10, Title: "One", At: &Position{Lat: 1, Lng: 2}}},27 }},28 }},29 }30}3132func TestValidate(t *testing.T) {33 tests := []struct {34 name string35 bend func(*Document)36 want string // substring of the expected message; "" means valid37 }{38 {"the minimal document", func(*Document) {}, ""},39 {"a foreign schema", func(d *Document) { d.Doc = "something-else" }, "not \"atlas-generate-doc\""},40 {"a version from the future", func(d *Document) { d.Version = 99 }, "version 99"},41 {"no volume", func(d *Document) { d.Volume.Slug = "" }, "names no volume"},42 {"no source", func(d *Document) { d.Source.Name = "" }, "names no source"},43 {"an unspoken id space", func(d *Document) { d.Source.IDSpace = "" }, "declares id space"},44 {"no world", func(d *Document) { d.Worlds = nil }, "carries no world"},45 {"a world with no slug", func(d *Document) { d.Worlds[0].Slug = "" }, "carries no slug"},46 {"two worlds of one slug", func(d *Document) {47 d.Worlds = append(d.Worlds, d.Worlds[0])48 }, "appears twice"},49 {"a world naming no capture", func(d *Document) {50 d.Worlds[0].Capture.ContentHash = ""51 }, "names no capture"},52 {"a capture with no time", func(d *Document) {53 d.Worlds[0].Capture.CapturedAt = ""54 }, "carries no time"},55 {"a world with no lens", func(d *Document) { d.Worlds[0].Lenses = nil }, "has no lens"},56 {"a lens naming no tile set", func(d *Document) {57 d.Worlds[0].Lenses[0].TileSet = ""58 }, "names no tile set"},59 {"a collection of no kind", func(d *Document) {60 d.Worlds[0].Collections[0].Kind = "blob"61 }, "declares kind \"blob\""},62 {"a feature with no id", func(d *Document) {63 d.Worlds[0].Collections[0].Features[0].ID = 064 }, "feature with no id"},65 {"two features of one id", func(d *Document) {66 world := &d.Worlds[0]67 world.Collections = append(world.Collections, Collection{68 ID: 2, Title: "Chests", Kind: KindPoint,69 Features: []Feature{{ID: 10, Title: "Other", At: &Position{}}},70 })71 }, "share id 10"},72 {"a point standing nowhere", func(d *Document) {73 d.Worlds[0].Collections[0].Features[0].At = nil74 }, "stands nowhere"},75 {"an area drawing nothing", func(d *Document) {76 d.Worlds[0].Collections[0].Kind = KindArea77 d.Worlds[0].Collections[0].Features[0].At = nil78 }, "draws nothing"},79 {"an icon with no key", func(d *Document) {80 d.Icons = []Icon{{File: "x.svg"}}81 }, "carries no key"},82 {"one icon twice", func(d *Document) {83 d.Icons = []Icon{{Key: "a", File: "a.svg"}, {Key: "a", File: "a.png"}}84 }, "carried twice"},85 }8687 for _, tt := range tests {88 t.Run(tt.name, func(t *testing.T) {89 d := minimal()90 tt.bend(&d)91 err := d.Validate()92 switch {93 case tt.want == "" && err != nil:94 t.Fatalf("valid document refused: %v", err)95 case tt.want == "":96 return97 case err == nil:98 t.Fatalf("accepted, want a refusal naming %q", tt.want)99 case !strings.Contains(err.Error(), tt.want):100 t.Fatalf("refused with %q, want it to name %q", err, tt.want)101 }102 })103 }104}105106func TestNewestCapture(t *testing.T) {107 d := minimal()108 d.Worlds = append(d.Worlds, World{109 Slug: "second",110 Capture: Capture{ContentHash: "def", CapturedAt: "2026-08-01T00:00:00Z"},111 Lenses: []Lens{{TileSet: "x"}},112 })113 if got := d.NewestCapture(); got != "2026-08-01T00:00:00Z" {114 t.Errorf("newest capture %q", got)115 }116}117118// TestRoundTrip holds the document to the one property a debugging dump needs:119// what is printed reads back as the same thing.120func TestRoundTrip(t *testing.T) {121 d := minimal()122 d.Icons = []Icon{{Key: "shrine", File: "shrine.svg", Data: []byte("<svg/>")}}123 d.Worlds[0].Collections[0].Features[0].Links = []Link{{Title: "See also", Feature: 11}}124 data, err := d.Marshal()125 if err != nil {126 t.Fatal(err)127 }128 back, err := Unmarshal(data)129 if err != nil {130 t.Fatal(err)131 }132 again, err := back.Marshal()133 if err != nil {134 t.Fatal(err)135 }136 if string(data) != string(again) {137 t.Errorf("a document does not survive a round trip\nfirst:\n%s\nagain:\n%s", data, again)138 }139}140141// TestGeometryIsCarriedOpaquely is the property that lets a source's numbers142// reach a payload unrounded: coordinates are never decoded on the way through.143func TestGeometryIsCarriedOpaquely(t *testing.T) {144 raw := json.RawMessage(`[[[0.30000001192092896,1e3]]]`)145 d := minimal()146 d.Worlds[0].Collections[0].Kind = KindArea147 d.Worlds[0].Collections[0].Features[0].At = nil148 d.Worlds[0].Collections[0].Features[0].Geometry = []Geometry{{Type: "Polygon", Coordinates: raw}}149 if err := d.Validate(); err != nil {150 t.Fatal(err)151 }152 data, err := d.Marshal()153 if err != nil {154 t.Fatal(err)155 }156 if !strings.Contains(string(data), "0.30000001192092896") || !strings.Contains(string(data), "1e3") {157 t.Errorf("coordinates were reformatted on the way through:\n%s", data)158 }159}160161func TestAttrKeys(t *testing.T) {162 got := AttrKeys(map[string]string{"b": "", "a": "", "c": ""})163 if len(got) != 3 || got[0] != "a" || got[1] != "b" || got[2] != "c" {164 t.Errorf("AttrKeys = %v, want sorted", got)165 }166}