1package app23import (4 "strconv"5 "testing"67 "charm.land/bubbles/v2/textinput"8 "github.com/FelineStateMachine/puzzletea/config"9 "github.com/FelineStateMachine/puzzletea/difficulty"10 "github.com/FelineStateMachine/puzzletea/game"11 "github.com/FelineStateMachine/puzzletea/puzzle"12 "github.com/FelineStateMachine/puzzletea/registry"13 sessionflow "github.com/FelineStateMachine/puzzletea/session"14 "github.com/FelineStateMachine/puzzletea/store"15)1617type createTestEloSpawner struct{}1819func (createTestEloSpawner) SpawnElo(string, difficulty.Elo) (game.Gamer, difficulty.Report, error) {20 return &escapeTrackingGame{}, difficulty.Report{21 TargetElo: 1200,22 ActualElo: 1200,23 Confidence: difficulty.ConfidenceHigh,24 }, nil25}2627func TestCreateGenerateBlocksWithoutSelection(t *testing.T) {28 m := model{create: createTestState(nil, 1200), store: openAppTestStore(t), cfg: config.Default()}2930 got, _ := m.handleCreateGenerate()31 if got.notice.message == "" {32 t.Fatal("expected notice for empty selection")33 }34}3536func TestCreateGenerateUsesSingleSelectedLeafAndSeed(t *testing.T) {37 s := openAppTestStore(t)38 state := createTestState([]createLeaf{createTestLeaf("a", "Alpha", "Tiny")}, 1300)39 state.seedInput.SetValue("daily create seed")40 m := model{create: state, store: s, cfg: config.Default()}4142 got, cmd := m.handleCreateGenerate()43 if cmd == nil {44 t.Fatal("expected generation command")45 }46 if got.session.spawn == nil {47 t.Fatal("missing spawn metadata")48 }49 if got.session.spawn.gameType != "Alpha" || got.session.spawn.modeTitle != "Tiny" {50 t.Fatalf("spawn = %#v, want Alpha/Tiny", got.session.spawn)51 }52 if got.session.spawn.run.Kind != store.RunKindSeeded {53 t.Fatalf("run kind = %q, want seeded", got.session.spawn.run.Kind)54 }55}5657func TestCreateGenerateMultipleSelectionIgnoresSeedAndUsesInjectedRandomChoice(t *testing.T) {58 s := openAppTestStore(t)59 state := createTestState([]createLeaf{60 createTestLeaf("a", "Alpha", "Tiny"),61 createTestLeaf("b", "Beta", "Large"),62 }, 1300)63 state.seedInput.SetValue("ignored")64 m := model{65 create: state,66 store: s,67 cfg: config.Default(),68 createRandIndex: func(n int) int {69 if n != 2 {70 t.Fatalf("random choice bound = %d, want 2", n)71 }72 return 173 },74 }7576 got, cmd := m.handleCreateGenerate()77 if cmd == nil {78 t.Fatal("expected generation command")79 }80 if got.session.spawn.gameType != "Beta" {81 t.Fatalf("spawn gameType = %q, want Beta", got.session.spawn.gameType)82 }83 if got.session.spawn.run.Kind != store.RunKindNormal {84 t.Fatalf("run kind = %q, want normal", got.session.spawn.run.Kind)85 }86 if got.session.spawn.run.SeedText != "" {87 t.Fatalf("seed text = %q, want blank", got.session.spawn.run.SeedText)88 }89}9091func TestCreateGenerateSeededSingleLeafResumesExistingRecord(t *testing.T) {92 s := openAppTestStore(t)93 leaf := createTestLeaf("lights", "Lights Out", "Easy")94 state := createTestState([]createLeaf{leaf}, 1000)95 state.seedInput.SetValue("resume create")96 name := sessionflow.SeededNameForCreateLeaf("resume create", leaf.gameType, leaf.id, 1000)97 rec := &store.GameRecord{98 Name: name,99 GameType: "Lights Out",100 Mode: "Easy",101 InitialState: `{"grid":[[true]],"mode_title":"Easy"}`,102 SaveState: `{"grid":[[true]],"mode_title":"Easy"}`,103 Status: store.StatusAbandoned,104 RunKind: store.RunKindSeeded,105 SeedText: "resume create",106 }107 if err := s.CreateGame(rec); err != nil {108 t.Fatal(err)109 }110 m := model{create: state, store: s, cfg: config.Default()}111112 got, cmd := m.handleCreateGenerate()113 if cmd != nil {114 t.Fatal("expected resume without generation command")115 }116 if got.state != gameView {117 t.Fatalf("state = %d, want gameView", got.state)118 }119 saved, err := s.GetDailyGame(name)120 if err != nil {121 t.Fatal(err)122 }123 if saved.Status != store.StatusInProgress {124 t.Fatalf("status = %q, want in_progress", saved.Status)125 }126}127128func createTestState(leaves []createLeaf, elo int) createState {129 eloInput := textinput.New()130 eloInput.SetValue("1200")131 if elo > 0 {132 eloInput.SetValue(strconv.Itoa(elo))133 }134 seedInput := textinput.New()135136 children := make([]createTreeNode, 0, len(leaves))137 checked := make(map[string]bool, len(leaves))138 for _, leaf := range leaves {139 leaf := leaf140 children = append(children, createTreeNode{id: leaf.id, title: leaf.title, leaf: &leaf})141 checked[leaf.id] = true142 }143144 return createState{145 tree: []createTreeNode{{146 id: "group:test",147 title: "Test",148 children: children,149 }},150 checked: checked,151 expanded: map[string]bool{"group:test": true},152 eloInput: eloInput,153 seedInput: seedInput,154 }155}156157func createTestLeaf(id, gameType, modeTitle string) createLeaf {158 spawner := createTestEloSpawner{}159 return createLeaf{160 id: id,161 title: modeTitle,162 gameType: gameType,163 variant: registry.VariantEntry{164 Definition: puzzle.VariantDef{165 Title: modeTitle,166 DefaultElo: 1200,167 },168 Elo: spawner,169 },170 }171}