clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

feat/elo-difficulty
puzzletea / games / spellpuzzle / print_adapter_test.go
print_adapter_test.go 5.3K
1package spellpuzzle23import (4 "encoding/json"5 "os"6 "path/filepath"7 "testing"8 "time"910 "github.com/FelineStateMachine/puzzletea/export/pdf"11)1213func TestPrintAdapterReturnsNilForBlankSave(t *testing.T) {14 payload, err := printAdapter{}.BuildPDFPayload(nil)15 if err != nil {16 t.Fatal(err)17 }18 if payload != nil {19 t.Fatalf("payload = %#v, want nil", payload)20 }21}2223func TestPrintAdapterBuildsPayloadAndIgnoresFoundState(t *testing.T) {24 save := Save{25 ModeTitle: "print-test",26 Bank: "PATACN",27 Placements: []WordPlacement{28 {Text: "CATNAP", Start: Position{X: 3, Y: 0}, Orientation: Vertical, Found: true},29 {Text: "CAPTAN", Start: Position{X: 0, Y: 2}, Orientation: Vertical, Found: false},30 {Text: "CANT", Start: Position{X: 0, Y: 2}, Orientation: Horizontal, Found: true},31 },32 BonusWords: []string{"TAN", "ACT"},33 Trace: []int{0, 1},34 BankCursor: 1,35 Solved: true,36 Feedback: feedback{Kind: feedbackSuccess, Message: "bonus word counted"},37 }3839 data, err := json.Marshal(save)40 if err != nil {41 t.Fatal(err)42 }4344 payload, err := printAdapter{}.BuildPDFPayload(data)45 if err != nil {46 t.Fatal(err)47 }4849 got, ok := payload.(*printPayload)50 if !ok || got == nil {51 t.Fatal("expected print payload")52 }53 if got.Board.Width <= 0 || got.Board.Height <= 0 {54 t.Fatalf("board dims = %dx%d, want positive", got.Board.Width, got.Board.Height)55 }56 if string(got.Bank) != "PATACN" {57 t.Fatalf("bank = %q, want PATACN", string(got.Bank))58 }59 for y := range got.Board.Height {60 for x := range got.Board.Width {61 if got.Board.Cells[y][x].Revealed {62 t.Fatalf("cell (%d,%d) unexpectedly revealed", x, y)63 }64 }65 }66}6768func TestPrintAdapterDeduplicatesDuplicatePlacements(t *testing.T) {69 save := Save{70 ModeTitle: "opal-crane",71 Bank: "PATACN",72 Placements: []WordPlacement{73 {Text: "CATNAP", Start: Position{X: 3, Y: 0}, Orientation: Vertical, Found: true},74 {Text: "CAPTAN", Start: Position{X: 0, Y: 2}, Orientation: Vertical, Found: true},75 {Text: "CANT", Start: Position{X: 0, Y: 2}, Orientation: Horizontal, Found: false},76 {Text: "CANT", Start: Position{X: 0, Y: 2}, Orientation: Horizontal, Found: true},77 },78 }7980 data, err := json.Marshal(save)81 if err != nil {82 t.Fatal(err)83 }8485 payload, err := printAdapter{}.BuildPDFPayload(data)86 if err != nil {87 t.Fatal(err)88 }8990 got := payload.(*printPayload)91 if len(got.Placements) != 3 {92 t.Fatalf("placement count = %d, want 3", len(got.Placements))93 }94}9596func TestSpellPuzzlePrintAdapterRegistration(t *testing.T) {97 pdfexport.RegisterPrintAdapter(PDFPrintAdapter)9899 for _, gameType := range []string{"Spell Puzzle", "spell", "spellpuzzle"} {100 if !pdfexport.HasPrintAdapter(gameType) {101 t.Fatalf("expected print adapter for %q", gameType)102 }103 }104}105106func TestSpellPuzzleJSONLHydratesPrintPayload(t *testing.T) {107 pdfexport.RegisterPrintAdapter(PDFPrintAdapter)108109 record := pdfexport.JSONLRecord{110 Schema: pdfexport.ExportSchemaV1,111 Pack: pdfexport.JSONLPackMeta{112 Generated: "2026-03-10T10:00:00Z",113 Version: "v-test",114 Category: "Spell Puzzle",115 ModeSelection: "Beginner",116 Count: 1,117 },118 Puzzle: pdfexport.JSONLPuzzle{119 Index: 1,120 Name: "print-bank",121 Game: "Spell Puzzle",122 Mode: "Beginner",123 Save: json.RawMessage(`{124 "mode_title":"print-bank",125 "bank":"PATACN",126 "placements":[127 {"text":"CATNAP","start":{"x":3,"y":0},"orientation":1,"found":true},128 {"text":"CAPTAN","start":{"x":0,"y":2},"orientation":1,"found":false},129 {"text":"CANT","start":{"x":0,"y":2},"orientation":0,"found":true}130 ]131 }`),132 },133 }134135 path := filepath.Join(t.TempDir(), "spellpuzzle.jsonl")136 data, err := json.Marshal(record)137 if err != nil {138 t.Fatal(err)139 }140 if err := os.WriteFile(path, append(data, '\n'), 0o644); err != nil {141 t.Fatal(err)142 }143144 doc, err := pdfexport.ParseJSONLFile(path)145 if err != nil {146 t.Fatal(err)147 }148 if got, want := len(doc.Puzzles), 1; got != want {149 t.Fatalf("puzzles = %d, want %d", got, want)150 }151 if _, ok := doc.Puzzles[0].PrintPayload.(*printPayload); !ok {152 t.Fatalf("payload type = %T, want *printPayload", doc.Puzzles[0].PrintPayload)153 }154}155156func TestSpellPuzzlePDFRenderSmokeForAllModes(t *testing.T) {157 pdfexport.RegisterPrintAdapter(PDFPrintAdapter)158159 puzzles := make([]pdfexport.Puzzle, 0, len(Modes))160 for i, item := range Modes {161 mode := item.(SpellPuzzleMode)162 generated, err := GeneratePuzzleSeeded(mode, newTestRNG(uint64(100+i)))163 if err != nil {164 t.Fatalf("%s: %v", mode.Title(), err)165 }166167 saveData, err := json.Marshal(Save{168 ModeTitle: mode.Title(),169 Bank: generated.Bank,170 Placements: generated.Placements,171 })172 if err != nil {173 t.Fatal(err)174 }175176 payload, err := printAdapter{}.BuildPDFPayload(saveData)177 if err != nil {178 t.Fatalf("%s payload: %v", mode.Title(), err)179 }180 puzzles = append(puzzles, pdfexport.Puzzle{181 Category: "Spell Puzzle",182 Name: mode.Title(),183 Index: i + 1,184 ModeSelection: mode.Title(),185 PrintPayload: payload,186 DifficultyScore: 0.5,187 })188 }189190 output := filepath.Join(t.TempDir(), "spellpuzzle.pdf")191 err := pdfexport.WritePDF(output, nil, puzzles, pdfexport.RenderConfig{192 VolumeNumber: 1,193 GeneratedAt: time.Date(2026, 3, 10, 12, 0, 0, 0, time.UTC),194 })195 if err != nil {196 t.Fatal(err)197 }198199 info, err := os.Stat(output)200 if err != nil {201 t.Fatal(err)202 }203 if info.Size() == 0 {204 t.Fatal("expected non-empty pdf output")205 }206}