- commit
- 3c05f0f6ac8122f4ee8df4196ba00c48d1aa66ca
- parent
- c28818af137a70d5fe351950e953a02358edda4f
- author
- Dami <dami.etoile@gmail.com>
| M | app/ | 8 | +- |
| M | app/ | 12 | +- |
| M | app/ | 164 | ++++++++++++++++++++++++++++++ |
@@ -21,18 +21,18 @@ func (m model) activateGame(g game.Gamer, activeGameID int64, completionSaved bo
2121 return m2222 }2323 24-func (m model) importAndActivateRecord(rec store.GameRecord) model {24+func (m model) importAndActivateRecord(rec store.GameRecord) (model, bool) {2525 importFn, ok := game.Registry[rec.GameType]2626 if !ok {2727 log.Printf("unknown game type in save data: %s", rec.GameType)28- return m28+ return m, false2929 }3030 3131 g, err := importFn([]byte(rec.SaveState))3232 if err != nil {3333 log.Printf("failed to import game: %v", err)34- return m34+ return m, false3535 }3636 37- return m.activateGame(g.SetTitle(rec.Name), rec.ID, rec.Status == store.StatusCompleted)37+ return m.activateGame(g.SetTitle(rec.Name), rec.ID, rec.Status == store.StatusCompleted), true3838 }@@ -349,10 +349,11 @@ func (m model) handleSeedConfirm() (tea.Model, tea.Cmd) {
349349 350350 if rec != nil {351351 // Seed game exists - resume it.352- m = m.importAndActivateRecord(*rec)352+ var resumed bool353+ m, resumed = m.importAndActivateRecord(*rec)353354 354355 // Resume abandoned seeded games by resetting their status.355- if rec.Status == store.StatusAbandoned {356+ if resumed && rec.Status == store.StatusAbandoned {356357 if err := m.store.UpdateStatus(rec.ID, store.StatusInProgress); err != nil {357358 log.Printf("failed to mark seeded game in progress: %v", err)358359 }@@ -390,10 +391,11 @@ func (m model) handleDailyPuzzle() (tea.Model, tea.Cmd) {
390391 391392 if rec != nil {392393 // Daily exists - resume or review it.393- m = m.importAndActivateRecord(*rec)394+ var resumed bool395+ m, resumed = m.importAndActivateRecord(*rec)394396 395397 // Resume abandoned dailies by resetting their status.396- if rec.Status == store.StatusAbandoned {398+ if resumed && rec.Status == store.StatusAbandoned {397399 if err := m.store.UpdateStatus(rec.ID, store.StatusInProgress); err != nil {398400 log.Printf("failed to mark daily game in progress: %v", err)399401 }@@ -453,7 +455,7 @@ func (m model) handleContinueEnter() (tea.Model, tea.Cmd) {
453455 return m, nil454456 }455457 rec := m.continueGames[idx]456- m = m.importAndActivateRecord(rec)458+ m, _ = m.importAndActivateRecord(rec)457459 return m, nil458460 }459461 @@ -1,11 +1,18 @@
11 package app22 33 import (4+ "path/filepath"45 "testing"6+ "time"57 68 "charm.land/bubbles/v2/key"9+ "charm.land/bubbles/v2/textinput"710 tea "charm.land/bubbletea/v2"11+ "github.com/FelineStateMachine/puzzletea/daily"812 "github.com/FelineStateMachine/puzzletea/game"13+ "github.com/FelineStateMachine/puzzletea/namegen"14+ "github.com/FelineStateMachine/puzzletea/resolve"15+ "github.com/FelineStateMachine/puzzletea/store"916 )1017 1118 type escapeTrackingGame struct {@@ -74,3 +81,160 @@ func TestGameViewEscapePassesThroughToGame(t *testing.T) {
7481 t.Fatal("expected game to receive escape key")7582 }7683 }84+85+func TestImportAndActivateRecordSuccessFlag(t *testing.T) {86+ unknown := model{state: playMenuView}87+ if _, ok := unknown.importAndActivateRecord(store.GameRecord{GameType: "NoSuchGameType"}); ok {88+ t.Fatal("expected unknown game type import to fail")89+ }90+91+ const gameType = "TestImportAndActivateRecordGame"92+ prev, hadPrev := game.Registry[gameType]93+ game.Register(gameType, func(_ []byte) (game.Gamer, error) {94+ return &escapeTrackingGame{}, nil95+ })96+ t.Cleanup(func() {97+ if hadPrev {98+ game.Registry[gameType] = prev99+ return100+ }101+ delete(game.Registry, gameType)102+ })103+104+ rec := store.GameRecord{105+ ID: 42,106+ Name: "resume-me",107+ GameType: gameType,108+ SaveState: "{}",109+ Status: store.StatusCompleted,110+ }111+112+ next, ok := (model{113+ state: playMenuView,114+ width: 80,115+ height: 24,116+ showFullHelp: true,117+ }).importAndActivateRecord(rec)118+ if !ok {119+ t.Fatal("expected known game type import to succeed")120+ }121+ if next.state != gameView {122+ t.Fatalf("state = %d, want %d (gameView)", next.state, gameView)123+ }124+ if next.activeGameID != rec.ID {125+ t.Fatalf("activeGameID = %d, want %d", next.activeGameID, rec.ID)126+ }127+ if !next.completionSaved {128+ t.Fatal("expected completionSaved to be true for completed record")129+ }130+ if next.game == nil {131+ t.Fatal("expected game to be activated")132+ }133+}134+135+func TestHandleSeedConfirmDoesNotResumeStatusWhenImportFails(t *testing.T) {136+ s := openAppTestStore(t)137+ seed := "import-failure-seed"138+ nameRNG := resolve.RNGFromString("name:" + seed)139+ name := seed + " - " + namegen.GenerateSeeded(nameRNG)140+ rec := &store.GameRecord{141+ Name: name,142+ GameType: "NoSuchGameType",143+ Mode: "Any",144+ InitialState: "{}",145+ SaveState: "{}",146+ Status: store.StatusAbandoned,147+ }148+ if err := s.CreateGame(rec); err != nil {149+ t.Fatal(err)150+ }151+152+ ti := textinput.New()153+ ti.SetValue(seed)154+ m := model{155+ state: seedInputView,156+ store: s,157+ seedInput: ti,158+ }159+160+ next, _ := m.handleSeedConfirm()161+ got := next.(model)162+ if got.state != seedInputView {163+ t.Fatalf("state = %d, want %d (seedInputView)", got.state, seedInputView)164+ }165+166+ saved, err := s.GetDailyGame(name)167+ if err != nil {168+ t.Fatal(err)169+ }170+ if saved == nil {171+ t.Fatal("expected seeded record to exist")172+ }173+ if saved.Status != store.StatusAbandoned {174+ t.Fatalf("status = %q, want %q", saved.Status, store.StatusAbandoned)175+ }176+}177+178+func TestHandleDailyPuzzleDoesNotResumeStatusWhenImportFails(t *testing.T) {179+ s := openAppTestStore(t)180+ now := time.Now()181+ dates := []time.Time{182+ now.AddDate(0, 0, -1),183+ now,184+ now.AddDate(0, 0, 1),185+ }186+187+ seen := map[string]bool{}188+ for _, d := range dates {189+ name := daily.Name(d)190+ if seen[name] {191+ continue192+ }193+ seen[name] = true194+ rec := &store.GameRecord{195+ Name: name,196+ GameType: "NoSuchGameType",197+ Mode: "Any",198+ InitialState: "{}",199+ SaveState: "{}",200+ Status: store.StatusAbandoned,201+ }202+ if err := s.CreateGame(rec); err != nil {203+ t.Fatal(err)204+ }205+ }206+207+ m := model{208+ state: playMenuView,209+ store: s,210+ }211+212+ next, _ := m.handleDailyPuzzle()213+ got := next.(model)214+ if got.state != playMenuView {215+ t.Fatalf("state = %d, want %d (playMenuView)", got.state, playMenuView)216+ }217+218+ for name := range seen {219+ saved, err := s.GetDailyGame(name)220+ if err != nil {221+ t.Fatal(err)222+ }223+ if saved == nil {224+ t.Fatalf("expected daily record %q to exist", name)225+ }226+ if saved.Status != store.StatusAbandoned {227+ t.Fatalf("record %q status = %q, want %q", name, saved.Status, store.StatusAbandoned)228+ }229+ }230+}231+232+func openAppTestStore(t *testing.T) *store.Store {233+ t.Helper()234+ s, err := store.Open(filepath.Join(t.TempDir(), "test.db"))235+ if err != nil {236+ t.Fatal(err)237+ }238+ t.Cleanup(func() { _ = s.Close() })239+ return s240+}