diff --git a/src/data/context/GameContext.hh b/src/data/context/GameContext.hh index 766e16e2..374501b2 100644 --- a/src/data/context/GameContext.hh +++ b/src/data/context/GameContext.hh @@ -72,7 +72,13 @@ public: /// /// Sets the game play mode. /// - void SetMode(Mode nMode) noexcept { m_nMode = nMode; } + void SetMode(Mode nMode) + { + m_nMode = nMode; + + if (m_nGameId != 0) + OnActiveGameChanged(); + } /// /// Gets the assets for the current game. diff --git a/src/ui/viewmodels/IntegrationMenuViewModel.cpp b/src/ui/viewmodels/IntegrationMenuViewModel.cpp index eac8dba5..023f3512 100644 --- a/src/ui/viewmodels/IntegrationMenuViewModel.cpp +++ b/src/ui/viewmodels/IntegrationMenuViewModel.cpp @@ -3,6 +3,8 @@ #include "RA_Log.h" #include "RA_Resource.h" +#include "data/context/ConsoleContext.hh" +#include "data/context/GameContext.hh" #include "data/context/UserContext.hh" #include "services/IConfiguration.hh" @@ -15,6 +17,7 @@ #include "ui/viewmodels/LoginViewModel.hh" #include "ui/viewmodels/MessageBoxViewModel.hh" #include "ui/viewmodels/OverlaySettingsViewModel.hh" +#include "ui/viewmodels/UnknownGameViewModel.hh" #include "ui/viewmodels/WindowManager.hh" namespace ra { @@ -289,8 +292,30 @@ void IntegrationMenuViewModel::ReportBrokenAchievements() void IntegrationMenuViewModel::ShowGameHash() { - ra::ui::viewmodels::GameChecksumViewModel vmGameChecksum; - vmGameChecksum.ShowModal(); + auto& pGameContext = ra::services::ServiceLocator::GetMutable(); + if (pGameContext.GetMode() == ra::data::context::GameContext::Mode::CompatibilityTest) + { + ra::ui::viewmodels::UnknownGameViewModel vmUnknownGame; + vmUnknownGame.InitializeTestCompatibilityMode(); + + auto sEstimatedGameTitle = ra::services::ServiceLocator::Get().GetGameTitle(); + vmUnknownGame.SetEstimatedGameName(ra::Widen(sEstimatedGameTitle)); + vmUnknownGame.SetSystemName(ra::services::ServiceLocator::Get().Name()); + + if (vmUnknownGame.ShowModal() == ra::ui::DialogResult::OK) + { + // Test button is disabled, can only get here by clicking Link + Expects(!vmUnknownGame.GetTestMode()); + + pGameContext.SetMode(ra::data::context::GameContext::Mode::Normal); + pGameContext.RefreshUnlocks(); + } + } + else + { + ra::ui::viewmodels::GameChecksumViewModel vmGameChecksum; + vmGameChecksum.ShowModal(); + } } } // namespace viewmodels diff --git a/src/ui/viewmodels/UnknownGameViewModel.cpp b/src/ui/viewmodels/UnknownGameViewModel.cpp index e7de830b..432bb308 100644 --- a/src/ui/viewmodels/UnknownGameViewModel.cpp +++ b/src/ui/viewmodels/UnknownGameViewModel.cpp @@ -6,6 +6,7 @@ #include "api\SubmitNewTitle.hh" #include "data\context\ConsoleContext.hh" +#include "data\context\GameContext.hh" #include "services\IClipboard.hh" @@ -16,6 +17,8 @@ namespace ui { namespace viewmodels { const IntModelProperty UnknownGameViewModel::SelectedGameIdProperty("UnknownGameViewModel", "SelectedGameId", 0); +const BoolModelProperty UnknownGameViewModel::IsSelectedGameEnabledProperty("UnknownGameViewModel", "IsSelectedGameEnabled", true); +const BoolModelProperty UnknownGameViewModel::IsAssociateEnabledProperty("UnknownGameViewModel", "IsAssociateEnabled", true); const StringModelProperty UnknownGameViewModel::NewGameNameProperty("UnknownGameViewModel", "NewGameName", L""); const StringModelProperty UnknownGameViewModel::ChecksumProperty("UnknownGameViewModel", "Checksum", L""); const StringModelProperty UnknownGameViewModel::EstimatedGameNameProperty("UnknownGameViewModel", "EstimatedGameName", L""); @@ -33,6 +36,9 @@ void UnknownGameViewModel::InitializeGameTitles() m_vGameTitles.Add(0U, L""); + SetValue(IsSelectedGameEnabledProperty, false); + SetValue(IsAssociateEnabledProperty, false); + ra::api::FetchGamesList::Request request; request.ConsoleId = pConsoleContext.Id(); @@ -49,14 +55,30 @@ void UnknownGameViewModel::InitializeGameTitles() } else { + m_vGameTitles.BeginUpdate(); for (const auto& pGame : response.Games) m_vGameTitles.Add(pGame.Id, pGame.Name); + m_vGameTitles.EndUpdate(); } m_vGameTitles.Freeze(); + + SetValue(IsAssociateEnabledProperty, true); + SetValue(IsSelectedGameEnabledProperty, true); }); } +void UnknownGameViewModel::InitializeTestCompatibilityMode() +{ + const auto& pGameContext = ra::services::ServiceLocator::Get(); + m_vGameTitles.Add(pGameContext.GameId(), pGameContext.GameTitle()); + m_vGameTitles.Freeze(); + SetSelectedGameId(pGameContext.GameId()); + SetChecksum(ra::Widen(pGameContext.GameHash())); + + SetValue(IsSelectedGameEnabledProperty, false); +} + bool UnknownGameViewModel::Associate() { ra::api::SubmitNewTitle::Request request; diff --git a/src/ui/viewmodels/UnknownGameViewModel.hh b/src/ui/viewmodels/UnknownGameViewModel.hh index 29a32eef..94e8f54f 100644 --- a/src/ui/viewmodels/UnknownGameViewModel.hh +++ b/src/ui/viewmodels/UnknownGameViewModel.hh @@ -54,6 +54,21 @@ public: /// void InitializeGameTitles(); + /// + /// Initializes the collection (asynchronously). + /// + void InitializeTestCompatibilityMode(); + + /// + /// The for whether or not a game can be selected. + /// + static const BoolModelProperty IsSelectedGameEnabledProperty; + + /// + /// Gets whether or not a game can be selected. + /// + bool IsSelectedGameEnabled() const { return GetValue(IsSelectedGameEnabledProperty); } + /// /// The for the new game name. /// @@ -134,6 +149,16 @@ public: /// void SetTestMode(bool bValue) { SetValue(TestModeProperty, bValue); } + /// + /// The for whether or not the link button is enabled. + /// + static const BoolModelProperty IsAssociateEnabledProperty; + + /// + /// Gets whether or not the link button is enabled. + /// + bool IsAssociateEnabled() const { return GetValue(IsAssociateEnabledProperty); } + /// /// Command handler for Associate button. /// diff --git a/src/ui/win32/UnknownGameDialog.cpp b/src/ui/win32/UnknownGameDialog.cpp index edb0db5b..4bdbf914 100644 --- a/src/ui/win32/UnknownGameDialog.cpp +++ b/src/ui/win32/UnknownGameDialog.cpp @@ -34,13 +34,18 @@ UnknownGameDialog::UnknownGameDialog(ra::ui::viewmodels::UnknownGameViewModel& v m_bindExistingTitle.BindItems(vmUnknownGame.GameTitles()); m_bindExistingTitle.BindSelectedItem(ra::ui::viewmodels::UnknownGameViewModel::SelectedGameIdProperty); + m_bindWindow.BindEnabled(IDC_RA_KNOWNGAMES, ra::ui::viewmodels::UnknownGameViewModel::IsSelectedGameEnabledProperty); m_bindNewTitle.BindText(ra::ui::viewmodels::UnknownGameViewModel::NewGameNameProperty, ra::ui::win32::bindings::TextBoxBinding::UpdateMode::KeyPress); + m_bindWindow.BindEnabled(IDC_RA_GAMETITLE, ra::ui::viewmodels::UnknownGameViewModel::IsSelectedGameEnabledProperty); m_bindWindow.BindLabel(IDC_RA_GAMENAME, ra::ui::viewmodels::UnknownGameViewModel::EstimatedGameNameProperty); m_bindWindow.BindLabel(IDC_RA_SYSTEMNAME, ra::ui::viewmodels::UnknownGameViewModel::SystemNameProperty); m_bindWindow.BindLabel(IDC_RA_CHECKSUM, ra::ui::viewmodels::UnknownGameViewModel::ChecksumProperty); + + m_bindWindow.BindEnabled(IDC_RA_LINK, ra::ui::viewmodels::UnknownGameViewModel::IsAssociateEnabledProperty); + m_bindWindow.BindEnabled(IDOK, ra::ui::viewmodels::UnknownGameViewModel::IsSelectedGameEnabledProperty); } BOOL UnknownGameDialog::OnInitDialog() diff --git a/tests/data/context/GameContext_Tests.cpp b/tests/data/context/GameContext_Tests.cpp index 7d7910d2..67acc734 100644 --- a/tests/data/context/GameContext_Tests.cpp +++ b/tests/data/context/GameContext_Tests.cpp @@ -2667,6 +2667,49 @@ TEST_CLASS(GameContext_Tests) const auto* pNote1 = game.FindCodeNote(1234U); Assert::IsNull(pNote1); } + + TEST_METHOD(TestSetModeNotify) + { + class NotifyHarness : public GameContext::NotifyTarget + { + public: + bool m_bNotified = false; + + protected: + void OnActiveGameChanged() noexcept override { m_bNotified = true; } + }; + NotifyHarness notifyHarness; + + GameContextHarness game; + game.mockServer.HandleRequest([](const ra::api::FetchGameData::Request&, ra::api::FetchGameData::Response& response) + { + response.Title = L"GameTitle"; + response.ImageIcon = "9743"; + return true; + }); + + Assert::AreEqual(GameContext::Mode::Normal, game.GetMode()); + + game.AddNotifyTarget(notifyHarness); + game.LoadGame(0U); + + Assert::AreEqual(0U, game.GameId()); + Assert::IsFalse(notifyHarness.m_bNotified); + + game.SetMode(GameContext::Mode::CompatibilityTest); + Assert::AreEqual(GameContext::Mode::CompatibilityTest, game.GetMode()); + Assert::IsFalse(notifyHarness.m_bNotified); + + game.LoadGame(1U, GameContext::Mode::CompatibilityTest); + Assert::AreEqual(1U, game.GameId()); + Assert::AreEqual(GameContext::Mode::CompatibilityTest, game.GetMode()); + Assert::IsTrue(notifyHarness.m_bNotified); + + notifyHarness.m_bNotified = false; + game.SetMode(GameContext::Mode::Normal); + Assert::AreEqual(GameContext::Mode::Normal, game.GetMode()); + Assert::IsTrue(notifyHarness.m_bNotified); + } }; } // namespace tests diff --git a/tests/ui/viewmodels/IntegrationMenuViewModel_Tests.cpp b/tests/ui/viewmodels/IntegrationMenuViewModel_Tests.cpp index 3dd42a70..8dd397d8 100644 --- a/tests/ui/viewmodels/IntegrationMenuViewModel_Tests.cpp +++ b/tests/ui/viewmodels/IntegrationMenuViewModel_Tests.cpp @@ -13,10 +13,13 @@ #include "ui\viewmodels\MemoryInspectorViewModel.hh" #include "ui\viewmodels\OverlaySettingsViewModel.hh" #include "ui\viewmodels\RichPresenceMonitorViewModel.hh" +#include "ui\viewmodels\UnknownGameViewModel.hh" +#include "tests\data\DataAsserts.hh" #include "tests\ui\UIAsserts.hh" #include "tests\mocks\MockAchievementRuntime.hh" #include "tests\mocks\MockConfiguration.hh" +#include "tests\mocks\MockConsoleContext.hh" #include "tests\mocks\MockDesktop.hh" #include "tests\mocks\MockEmulatorContext.hh" #include "tests\mocks\MockGameContext.hh" @@ -40,6 +43,7 @@ TEST_CLASS(IntegrationMenuViewModel_Tests) { public: ra::api::mocks::MockServer mockServer; + ra::data::context::mocks::MockConsoleContext mockConsoleContext; ra::data::context::mocks::MockEmulatorContext mockEmulatorContext; ra::data::context::mocks::MockGameContext mockGameContext; ra::data::context::mocks::MockUserContext mockUserContext; @@ -530,6 +534,47 @@ TEST_CLASS(IntegrationMenuViewModel_Tests) menu.AssertShowWindow(IDM_RA_GETROMCHECKSUM, true, "", DialogResult::None); } + TEST_METHOD(TestShowGameHashTestCompatibilityModeCancel) + { + IntegrationMenuViewModelHarness menu; + + bool bDialogShown = false; + menu.mockDesktop.ExpectWindow([&bDialogShown](ra::ui::viewmodels::UnknownGameViewModel& vmUnknown) + { + bDialogShown = true; + + Assert::IsFalse(vmUnknown.IsSelectedGameEnabled()); + + return DialogResult::Cancel; + }); + + menu.mockGameContext.SetMode(ra::data::context::GameContext::Mode::CompatibilityTest); + menu.ActivateMenuItem(IDM_RA_GETROMCHECKSUM); + + Assert::IsTrue(bDialogShown); + Assert::AreEqual(ra::data::context::GameContext::Mode::CompatibilityTest, menu.mockGameContext.GetMode()); + } + + TEST_METHOD(TestShowGameHashTestCompatibilityModeAssociate) + { + IntegrationMenuViewModelHarness menu; + + bool bDialogShown = false; + menu.mockDesktop.ExpectWindow([&bDialogShown](ra::ui::viewmodels::UnknownGameViewModel& vmUnknown) + { + bDialogShown = true; + + Assert::IsFalse(vmUnknown.IsSelectedGameEnabled()); + + return DialogResult::OK; + }); + + menu.mockGameContext.SetMode(ra::data::context::GameContext::Mode::CompatibilityTest); + menu.ActivateMenuItem(IDM_RA_GETROMCHECKSUM); + + Assert::IsTrue(bDialogShown); + Assert::AreEqual(ra::data::context::GameContext::Mode::Normal, menu.mockGameContext.GetMode()); + } }; } // namespace tests diff --git a/tests/ui/viewmodels/UnknownGameViewModel_Tests.cpp b/tests/ui/viewmodels/UnknownGameViewModel_Tests.cpp index d5be01c6..03bc6dec 100644 --- a/tests/ui/viewmodels/UnknownGameViewModel_Tests.cpp +++ b/tests/ui/viewmodels/UnknownGameViewModel_Tests.cpp @@ -7,6 +7,7 @@ #include "tests\mocks\MockClipboard.hh" #include "tests\mocks\MockConsoleContext.hh" #include "tests\mocks\MockDesktop.hh" +#include "tests\mocks\MockGameContext.hh" #include "tests\mocks\MockServer.hh" #include "tests\mocks\MockThreadPool.hh" @@ -27,6 +28,7 @@ TEST_CLASS(UnknownGameViewModel_Tests) public: ra::api::mocks::MockServer mockServer; ra::data::context::mocks::MockConsoleContext mockConsoleContext; + ra::data::context::mocks::MockGameContext mockGameContext; ra::services::mocks::MockThreadPool mockThreadPool; ra::ui::mocks::MockDesktop mockDesktop; @@ -58,6 +60,7 @@ TEST_CLASS(UnknownGameViewModel_Tests) Assert::AreEqual(std::wstring(L""), vmUnknownGame.GetNewGameName()); Assert::AreEqual(std::wstring(L""), vmUnknownGame.GetChecksum()); Assert::AreEqual({ 0U }, vmUnknownGame.GameTitles().Count()); + Assert::IsTrue(vmUnknownGame.IsSelectedGameEnabled()); } TEST_METHOD(TestInitializeGameTitles) @@ -79,18 +82,22 @@ TEST_CLASS(UnknownGameViewModel_Tests) vmUnknownGame.InitializeGameTitles(); - // item should be loaded immediately + // item should be loaded immediately, but linking should be disabled Assert::AreEqual({ 1U }, vmUnknownGame.GameTitles().Count()); Assert::AreEqual(std::wstring(L""), vmUnknownGame.GameTitles().GetLabelForId(0)); Assert::IsFalse(vmUnknownGame.GameTitles().IsFrozen()); + Assert::IsFalse(vmUnknownGame.IsAssociateEnabled()); + Assert::IsFalse(vmUnknownGame.IsSelectedGameEnabled()); - // after server response, collection should have three items + // after server response, collection should have three items and be frozen. linking should be enabled vmUnknownGame.mockThreadPool.ExecuteNextTask(); Assert::AreEqual({ 3U }, vmUnknownGame.GameTitles().Count()); Assert::AreEqual(std::wstring(L""), vmUnknownGame.GameTitles().GetLabelForId(0)); Assert::AreEqual(std::wstring(L"Game 33"), vmUnknownGame.GameTitles().GetLabelForId(33)); Assert::AreEqual(std::wstring(L"Game 37"), vmUnknownGame.GameTitles().GetLabelForId(37)); Assert::IsTrue(vmUnknownGame.GameTitles().IsFrozen()); + Assert::IsTrue(vmUnknownGame.IsAssociateEnabled()); + Assert::IsTrue(vmUnknownGame.IsSelectedGameEnabled()); } TEST_METHOD(TestNameAndSelectionInteraction) @@ -388,7 +395,62 @@ TEST_CLASS(UnknownGameViewModel_Tests) Assert::IsFalse(vmUnknownGame.BeginTest()); } -}; + + TEST_METHOD(TestInitializeTestCompatibilityMode) + { + UnknownGameViewModelHarness vmUnknownGame; + vmUnknownGame.mockConsoleContext.SetId(ConsoleID::C64); + vmUnknownGame.mockGameContext.SetGameId(33U); + vmUnknownGame.mockGameContext.SetGameTitle(L"Game 33"); + vmUnknownGame.mockGameContext.SetGameHash("CHECKSUM"); + + vmUnknownGame.InitializeTestCompatibilityMode(); + + Assert::AreEqual({ 1U }, vmUnknownGame.GameTitles().Count()); + Assert::AreEqual(33, vmUnknownGame.GameTitles().GetItemAt(0)->GetId()); + Assert::AreEqual(std::wstring(L"Game 33"), vmUnknownGame.GameTitles().GetItemAt(0)->GetLabel()); + Assert::AreEqual(33, vmUnknownGame.GetSelectedGameId()); + Assert::AreEqual(std::wstring(L"CHECKSUM"), vmUnknownGame.GetChecksum()); + Assert::IsTrue(vmUnknownGame.GameTitles().IsFrozen()); + + Assert::IsFalse(vmUnknownGame.IsSelectedGameEnabled()); + } + + TEST_METHOD(TestAssociateExistingTestCompatibilityMode) + { + UnknownGameViewModelHarness vmUnknownGame; + vmUnknownGame.mockConsoleContext.SetId(ConsoleID::PSP); + vmUnknownGame.mockGameContext.SetGameId(40U); + vmUnknownGame.mockGameContext.SetGameTitle(L"Game 40"); + vmUnknownGame.mockGameContext.SetGameHash("CHECKSUM"); + vmUnknownGame.SetEstimatedGameName(L"GAME40"); + vmUnknownGame.InitializeTestCompatibilityMode(); + + vmUnknownGame.mockDesktop.ExpectWindow([](ra::ui::viewmodels::MessageBoxViewModel& vmMessageBox) + { + Assert::AreEqual(std::wstring(L"Are you sure you want to add a new hash to 'Game 40'?"), vmMessageBox.GetHeader()); + return ra::ui::DialogResult::Yes; + }); + + vmUnknownGame.mockServer.HandleRequest([] + (const ra::api::SubmitNewTitle::Request& request, ra::api::SubmitNewTitle::Response& response) + { + Assert::AreEqual(41U, request.ConsoleId); + Assert::AreEqual(std::string("CHECKSUM"), request.Hash); + Assert::AreEqual(std::wstring(L"Game 40"), request.GameName); + Assert::AreEqual(std::wstring(L"GAME40"), request.Description); + Assert::AreEqual(40U, request.GameId); + + response.Result = ra::api::ApiResult::Success; + response.GameId = 40U; + + return true; + }); + + Assert::IsTrue(vmUnknownGame.Associate()); + Assert::AreEqual(40, vmUnknownGame.GetSelectedGameId()); + Assert::IsFalse(vmUnknownGame.GetTestMode()); + }}; } // namespace tests } // namespace viewmodels