1package app23import (4 "testing"56 "github.com/FelineStateMachine/puzzletea/config"7 "github.com/FelineStateMachine/puzzletea/difficulty"8)910func TestCreateStateStartsCollapsedAndRestoresCheckedLeaves(t *testing.T) {11 const leafID = "nonogram/nonogram"12 state := newCreateState(config.CreateConfig{13 SelectedLeafIDs: []string{leafID},14 Elo: 1500,15 }, 80)1617 if !state.checked[leafID] {18 t.Fatalf("expected %q to be checked", leafID)19 }20 if got := state.eloInput.Value(); got != "1500" {21 t.Fatalf("elo input = %q, want 1500", got)22 }23 for _, item := range state.visibleNodes() {24 if item.node.leaf != nil && item.node.leaf.id == leafID {25 return26 }27 }28 t.Fatalf("visible nodes = %#v, want restored top-level leaf %q", state.visibleNodes(), leafID)29}3031func TestCreateStateIgnoresUnknownRestoredLeafIDs(t *testing.T) {32 const validLeafID = "nonogram/nonogram"33 state := newCreateState(config.CreateConfig{34 SelectedLeafIDs: []string{35 "nonogram/5x5",36 validLeafID,37 "stale/game/mode",38 },39 }, 80)4041 if got := state.selectedCount(); got != 1 {42 t.Fatalf("selected count = %d, want 1", got)43 }44 if got := state.selectedLeafIDs(); len(got) != 1 || got[0] != validLeafID {45 t.Fatalf("selected leaf IDs = %#v, want [%q]", got, validLeafID)46 }47 if len(state.checked) != 1 || !state.checked[validLeafID] {48 t.Fatalf("checked map = %#v, want only %q", state.checked, validLeafID)49 }50}5152func TestCreateStateDefaultStartsCollapsed(t *testing.T) {53 state := newCreateState(config.CreateConfig{}, 80)5455 if len(state.visibleNodes()) != len(state.tree) {56 t.Fatalf("visible node count = %d, want %d", len(state.visibleNodes()), len(state.tree))57 }58}5960func TestCreateTreeGroupsTakuzuAndSudokuVariants(t *testing.T) {61 state := newCreateState(config.CreateConfig{}, 80)6263 takuzu := requireCreateNode(t, state.tree, "Takuzu")64 if !hasCreateChild(takuzu, "Takuzu") || !hasCreateChild(takuzu, "Takuzu+") {65 t.Fatalf("Takuzu group children = %#v, want Takuzu and Takuzu+", childTitles(takuzu))66 }67 for _, child := range takuzu.children {68 if child.leaf == nil {69 t.Fatalf("Takuzu child %q = %#v, want leaf", child.title, child)70 }71 }7273 sudoku := requireCreateNode(t, state.tree, "Sudoku")74 if !hasCreateChild(sudoku, "Sudoku") || !hasCreateChild(sudoku, "Sudoku RGB") {75 t.Fatalf("Sudoku group children = %#v, want Sudoku and Sudoku RGB", childTitles(sudoku))76 }77 for _, child := range sudoku.children {78 if child.leaf == nil {79 t.Fatalf("Sudoku child %q = %#v, want leaf", child.title, child)80 }81 }82}8384func TestCreateTreePromotesSingleVariantGameToTopLevelLeaf(t *testing.T) {85 state := newCreateState(config.CreateConfig{}, 80)86 nonogram := requireCreateNode(t, state.tree, "Nonogram")8788 if nonogram.leaf == nil {89 t.Fatalf("Nonogram node = %#v, want top-level leaf", nonogram)90 }91 if len(nonogram.children) != 0 {92 t.Fatalf("Nonogram children = %#v, want none", childTitles(nonogram))93 }94}9596func TestCreateResolveLeafVariantUsesSelectedVariant(t *testing.T) {97 state := newCreateState(config.CreateConfig{}, 80)98 nonogram := requireCreateNode(t, state.tree, "Nonogram")99 leaf := nonogram.leaf100 if leaf == nil {101 t.Fatal("Nonogram node is not a leaf")102 }103104 _, modeTitle, err := state.resolveLeafVariant(*leaf, difficulty.Elo(700))105 if err != nil {106 t.Fatal(err)107 }108 if modeTitle != "Nonogram" {109 t.Fatalf("modeTitle = %q, want Nonogram", modeTitle)110 }111}112113func TestCreateStateDescendantLeafCountsIncludeCollapsedDescendants(t *testing.T) {114 state := newCreateState(config.CreateConfig{}, 80)115 nonogram := requireCreateNode(t, state.tree, "Nonogram")116 state.checked[nonogram.leaf.id] = true117118 selected, total := state.descendantLeafCounts(nonogram)119 if selected != 1 || total != 1 {120 t.Fatalf("descendant counts = %d/%d, want 1/1", selected, total)121 }122}123124func TestCreateStateToggleDescendantLeavesSelectsAllWhenAnyUnchecked(t *testing.T) {125 state := newCreateState(config.CreateConfig{}, 80)126 sudoku := requireCreateNode(t, state.tree, "Sudoku")127 state.checked[firstDescendantLeafID(t, sudoku.children[0])] = true128129 state.toggleDescendantLeaves(sudoku)130131 selected, total := state.descendantLeafCounts(sudoku)132 if selected != total || total == 0 {133 t.Fatalf("descendant counts after select-all = %d/%d, want all selected", selected, total)134 }135}136137func TestCreateStateToggleDescendantLeavesClearsFullySelectedParent(t *testing.T) {138 state := newCreateState(config.CreateConfig{}, 80)139 nonogram := requireCreateNode(t, state.tree, "Nonogram")140 state.toggleDescendantLeaves(nonogram)141142 state.toggleDescendantLeaves(nonogram)143144 selected, total := state.descendantLeafCounts(nonogram)145 if selected != 0 || total == 0 {146 t.Fatalf("descendant counts after clear = %d/%d, want 0 selected", selected, total)147 }148}149150func TestCreateStateToggleDescendantLeavesWorksForGroupedLeaf(t *testing.T) {151 state := newCreateState(config.CreateConfig{}, 80)152 takuzu := requireCreateNode(t, state.tree, "Takuzu")153 takuzuPlus := requireCreateNode(t, takuzu.children, "Takuzu+")154155 state.toggleDescendantLeaves(takuzuPlus)156157 selected, total := state.descendantLeafCounts(takuzuPlus)158 if selected != total || total == 0 {159 t.Fatalf("Takuzu+ descendant counts after toggle = %d/%d, want all selected", selected, total)160 }161}162163func requireCreateNode(t *testing.T, nodes []createTreeNode, title string) createTreeNode {164 t.Helper()165 for _, node := range nodes {166 if node.title == title {167 return node168 }169 }170 t.Fatalf("missing create node %q in %#v", title, childTitles(createTreeNode{children: nodes}))171 return createTreeNode{}172}173174func hasCreateChild(node createTreeNode, title string) bool {175 for _, child := range node.children {176 if child.title == title {177 return true178 }179 }180 return false181}182183func childTitles(node createTreeNode) []string {184 titles := make([]string, 0, len(node.children))185 for _, child := range node.children {186 titles = append(titles, child.title)187 }188 return titles189}190191func firstDescendantLeafID(t *testing.T, node createTreeNode) string {192 t.Helper()193 if node.leaf != nil {194 return node.leaf.id195 }196 for _, child := range node.children {197 if id := firstDescendantLeafID(t, child); id != "" {198 return id199 }200 }201 t.Fatalf("node %q has no descendant leaf", node.title)202 return ""203}