// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" #include "../TerminalApp/TerminalPage.h" #include "../TerminalApp/MinMaxCloseControl.h" #include "../TerminalApp/TabRowControl.h" #include "../TerminalApp/ShortcutActionDispatch.h" #include "../TerminalApp/TerminalTab.h" #include "../TerminalApp/CommandPalette.h" #include "../CppWinrtTailored.h" using namespace Microsoft::Console; using namespace TerminalApp; using namespace winrt::TerminalApp; using namespace winrt::Microsoft::Terminal::Settings::Model; using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace WEX::Common; using namespace winrt::Windows::ApplicationModel::DataTransfer; using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::System; using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Xaml::Controls; using namespace winrt::Windows::UI::Core; using namespace winrt::Windows::UI::Text; namespace winrt { namespace MUX = Microsoft::UI::Xaml; namespace WUX = Windows::UI::Xaml; using IInspectable = Windows::Foundation::IInspectable; } namespace TerminalAppLocalTests { // TODO:microsoft/terminal#3838: // Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for // an updated TAEF that will let us install framework packages when the test // package is deployed. Until then, these tests won't deploy in CI. class TabTests { // For this set of tests, we need to activate some XAML content. For // release builds, the application runs as a centennial application, // which lets us run full trust, and means that we need to use XAML // Islands to host our UI. However, in these tests, we don't really need // to run full trust - we just need to get some UI elements created. So // we can just rely on the normal UWP activation to create us. // // IMPORTANTLY! When tests need to make XAML objects, or do XAML things, // make sure to use RunOnUIThread. This helper will dispatch a lambda to // be run on the UI thread. BEGIN_TEST_CLASS(TabTests) TEST_CLASS_PROPERTY(L"RunAs", L"UAP") TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") END_TEST_CLASS() // These four tests act as canary tests. If one of them fails, then they // can help you identify if something much lower in the stack has // failed. TEST_METHOD(EnsureTestsActivate); TEST_METHOD(TryCreateSettingsType); TEST_METHOD(TryCreateConnectionType); TEST_METHOD(TryCreateXamlObjects); TEST_METHOD(TryInitializePage); TEST_METHOD(CreateSimpleTerminalXamlType); TEST_METHOD(CreateTerminalMuxXamlType); TEST_METHOD(CreateTerminalPage); TEST_METHOD(TryDuplicateBadTab); TEST_METHOD(TryDuplicateBadPane); TEST_METHOD(TryZoomPane); TEST_METHOD(MoveFocusFromZoomedPane); TEST_METHOD(CloseZoomedPane); TEST_METHOD(NextMRUTab); TEST_METHOD(VerifyCommandPaletteTabSwitcherOrder); TEST_METHOD(TestWindowRenameSuccessful); TEST_METHOD(TestWindowRenameFailure); TEST_CLASS_SETUP(ClassSetup) { return true; } TEST_METHOD_CLEANUP(MethodCleanup) { return true; } private: void _initializeTerminalPage(winrt::com_ptr& page, CascadiaSettings initialSettings); winrt::com_ptr _commonSetup(); }; template void TestOnUIThread(const TFunction& function) { const auto result = RunOnUIThread(function); VERIFY_SUCCEEDED(result); } void TabTests::EnsureTestsActivate() { // This test was originally used to ensure that XAML Islands was // initialized correctly. Now, it's used to ensure that the tests // actually deployed and activated. This test _should_ always pass. VERIFY_IS_TRUE(true); } void TabTests::TryCreateSettingsType() { // Verify we can create a WinRT type we authored // Just creating it is enough to know that everything is working. TerminalSettings settings; VERIFY_IS_NOT_NULL(settings); auto oldFontSize = settings.FontSize(); settings.FontSize(oldFontSize + 5); auto newFontSize = settings.FontSize(); VERIFY_ARE_NOT_EQUAL(oldFontSize, newFontSize); } void TabTests::TryCreateConnectionType() { // Verify we can create a WinRT type we authored // Just creating it is enough to know that everything is working. winrt::Microsoft::Terminal::TerminalConnection::EchoConnection conn{}; VERIFY_IS_NOT_NULL(conn); // We're doing this test separately from the TryCreateSettingsType test, // to ensure both dependent binaries (TerminalSettings and // TerminalConnection) both work individually. } void TabTests::TryCreateXamlObjects() { auto result = RunOnUIThread([]() { VERIFY_IS_TRUE(true, L"Congrats! We're running on the UI thread!"); auto v = winrt::Windows::ApplicationModel::Core::CoreApplication::GetCurrentView(); VERIFY_IS_NOT_NULL(v, L"Ensure we have a current view"); // Verify we can create a some XAML objects // Just creating all of them is enough to know that everything is working. winrt::Windows::UI::Xaml::Controls::UserControl controlRoot; VERIFY_IS_NOT_NULL(controlRoot, L"Try making a UserControl"); winrt::Windows::UI::Xaml::Controls::Grid root; VERIFY_IS_NOT_NULL(root, L"Try making a Grid"); winrt::Windows::UI::Xaml::Controls::SwapChainPanel swapChainPanel; VERIFY_IS_NOT_NULL(swapChainPanel, L"Try making a SwapChainPanel"); winrt::Windows::UI::Xaml::Controls::Primitives::ScrollBar scrollBar; VERIFY_IS_NOT_NULL(scrollBar, L"Try making a ScrollBar"); }); VERIFY_SUCCEEDED(result); } void TabTests::CreateSimpleTerminalXamlType() { winrt::com_ptr mmcc{ nullptr }; auto result = RunOnUIThread([&mmcc]() { mmcc = winrt::make_self(); VERIFY_IS_NOT_NULL(mmcc); }); VERIFY_SUCCEEDED(result); } void TabTests::CreateTerminalMuxXamlType() { winrt::com_ptr tabRowControl{ nullptr }; auto result = RunOnUIThread([&tabRowControl]() { tabRowControl = winrt::make_self(); VERIFY_IS_NOT_NULL(tabRowControl); }); VERIFY_SUCCEEDED(result); } void TabTests::CreateTerminalPage() { winrt::com_ptr page{ nullptr }; auto result = RunOnUIThread([&page]() { page = winrt::make_self(); VERIFY_IS_NOT_NULL(page); }); VERIFY_SUCCEEDED(result); } // Method Description: // - This is a helper to set up a TerminalPage for a unittest. This method // does a couple things: // * Create()'s a TerminalPage with the given settings. Constructing a // TerminalPage so that we can get at its implementation is wacky, so // this helper will do it correctly for you, even if this doesn't make a // ton of sense on the surface. This is also why you need to pass both a // projection and a com_ptr to this method. // * It will use the provided settings object to initialize the TerminalPage // * It will add the TerminalPage to the test Application, so that we can // get actual layout events. Much of the Terminal assumes there's a // non-zero ActualSize to the Terminal window, and adding the Page to // the Application will make it behave as expected. // * It will wait for the TerminalPage to finish initialization before // returning control to the caller. It does this by creating an event and // only setting the event when the TerminalPage raises its Initialized // event, to signal that startup is complete. At this point, there will // be one tab with the default profile in the page. // * It will also ensure that the first tab is focused, since that happens // asynchronously in the application typically. // Arguments: // - page: a TerminalPage implementation ptr that will receive the new TerminalPage instance // - initialSettings: a CascadiaSettings to initialize the TerminalPage with. // Return Value: // - void TabTests::_initializeTerminalPage(winrt::com_ptr& page, CascadiaSettings initialSettings) { // This is super wacky, but we can't just initialize the // com_ptr in the lambda and assign it back out of // the lambda. We'll crash trying to get a weak_ref to the TerminalPage // during TerminalPage::Create() below. // // Instead, create the winrt object, then get a com_ptr to the // implementation _from_ the winrt object. This seems to work, even if // it's weird. winrt::TerminalApp::TerminalPage projectedPage{ nullptr }; Log::Comment(NoThrowString().Format(L"Construct the TerminalPage")); auto result = RunOnUIThread([&projectedPage, &page, initialSettings]() { projectedPage = winrt::TerminalApp::TerminalPage(); page.copy_from(winrt::get_self(projectedPage)); page->_settings = initialSettings; }); VERIFY_SUCCEEDED(result); VERIFY_IS_NOT_NULL(page); VERIFY_IS_NOT_NULL(page->_settings); ::details::Event waitForInitEvent; if (!waitForInitEvent.IsValid()) { VERIFY_SUCCEEDED(HRESULT_FROM_WIN32(::GetLastError())); } page->Initialized([&waitForInitEvent](auto&&, auto&&) { waitForInitEvent.Set(); }); Log::Comment(L"Create() the TerminalPage"); result = RunOnUIThread([&page]() { VERIFY_IS_NOT_NULL(page); VERIFY_IS_NOT_NULL(page->_settings); page->Create(); Log::Comment(L"Create()'d the page successfully"); // Build a NewTab action, to make sure we start with one. The real // Terminal will always get one from AppCommandlineArgs. NewTerminalArgs newTerminalArgs{}; NewTabArgs args{ newTerminalArgs }; ActionAndArgs newTabAction{ ShortcutAction::NewTab, args }; // push the arg onto the front page->_startupActions.Append(newTabAction); Log::Comment(L"Added a single newTab action"); auto app = ::winrt::Windows::UI::Xaml::Application::Current(); winrt::TerminalApp::TerminalPage pp = *page; winrt::Windows::UI::Xaml::Window::Current().Content(pp); winrt::Windows::UI::Xaml::Window::Current().Activate(); }); VERIFY_SUCCEEDED(result); Log::Comment(L"Wait for the page to finish initializing..."); VERIFY_SUCCEEDED(waitForInitEvent.Wait()); Log::Comment(L"...Done"); result = RunOnUIThread([&page]() { // In the real app, this isn't a problem, but doesn't happen // reliably in the unit tests. Log::Comment(L"Ensure we set the first tab as the selected one."); auto tab = page->_tabs.GetAt(0); auto tabImpl = page->_GetTerminalTabImpl(tab); page->_tabView.SelectedItem(tabImpl->TabViewItem()); page->_UpdatedSelectedTab(0); }); VERIFY_SUCCEEDED(result); } void TabTests::TryInitializePage() { // This is a very simple test to prove we can create settings and a // TerminalPage and not only create them successfully, but also create a // tab using those settings successfully. const std::string settingsJson0{ R"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ { "name" : "profile0", "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "historySize": 1 }, { "name" : "profile1", "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", "historySize": 2 } ] })" }; CascadiaSettings settings0{ til::u8u16(settingsJson0) }; VERIFY_IS_NOT_NULL(settings0); // This is super wacky, but we can't just initialize the // com_ptr in the lambda and assign it back out of // the lambda. We'll crash trying to get a weak_ref to the TerminalPage // during TerminalPage::Create() below. // // Instead, create the winrt object, then get a com_ptr to the // implementation _from_ the winrt object. This seems to work, even if // it's weird. winrt::com_ptr page{ nullptr }; _initializeTerminalPage(page, settings0); auto result = RunOnUIThread([&page]() { VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); }); VERIFY_SUCCEEDED(result); } void TabTests::TryDuplicateBadTab() { // * Create a tab with a profile with GUID 1 // * Reload the settings so that GUID 1 is no longer in the list of profiles // * Try calling _DuplicateFocusedTab on tab 1 // * No new tab should be created (and more importantly, the app should not crash) // // Created to test GH#2455 const std::string settingsJson0{ R"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ { "name" : "profile0", "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "historySize": 1 }, { "name" : "profile1", "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", "historySize": 2 } ] })" }; const std::string settingsJson1{ R"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ { "name" : "profile1", "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", "historySize": 2 } ] })" }; CascadiaSettings settings0{ til::u8u16(settingsJson0) }; VERIFY_IS_NOT_NULL(settings0); CascadiaSettings settings1{ til::u8u16(settingsJson1) }; VERIFY_IS_NOT_NULL(settings1); const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}"); const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}"); // This is super wacky, but we can't just initialize the // com_ptr in the lambda and assign it back out of // the lambda. We'll crash trying to get a weak_ref to the TerminalPage // during TerminalPage::Create() below. // // Instead, create the winrt object, then get a com_ptr to the // implementation _from_ the winrt object. This seems to work, even if // it's weird. winrt::com_ptr page{ nullptr }; _initializeTerminalPage(page, settings0); auto result = RunOnUIThread([&page]() { VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); }); VERIFY_SUCCEEDED(result); Log::Comment(L"Duplicate the first tab"); result = RunOnUIThread([&page]() { page->_DuplicateFocusedTab(); VERIFY_ARE_EQUAL(2u, page->_tabs.Size()); }); VERIFY_SUCCEEDED(result); Log::Comment(NoThrowString().Format( L"Change the settings of the TerminalPage so the first profile is " L"no longer in the list of profiles")); result = RunOnUIThread([&page, settings1]() { page->_settings = settings1; }); VERIFY_SUCCEEDED(result); Log::Comment(L"Duplicate the tab, and don't crash"); result = RunOnUIThread([&page]() { page->_DuplicateFocusedTab(); VERIFY_ARE_EQUAL(2u, page->_tabs.Size(), L"We should gracefully do nothing here - the profile no longer exists."); }); VERIFY_SUCCEEDED(result); } void TabTests::TryDuplicateBadPane() { // * Create a tab with a profile with GUID 1 // * Reload the settings so that GUID 1 is no longer in the list of profiles // * Try calling _SplitPane(Duplicate) on tab 1 // * No new pane should be created (and more importantly, the app should not crash) // // Created to test GH#2455 const std::string settingsJson0{ R"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ { "name" : "profile0", "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "historySize": 1 }, { "name" : "profile1", "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", "historySize": 2 } ] })" }; const std::string settingsJson1{ R"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "profiles": [ { "name" : "profile1", "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", "historySize": 2 } ] })" }; CascadiaSettings settings0{ til::u8u16(settingsJson0) }; VERIFY_IS_NOT_NULL(settings0); CascadiaSettings settings1{ til::u8u16(settingsJson1) }; VERIFY_IS_NOT_NULL(settings1); const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}"); const auto guid3 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-3333-49a3-80bd-e8fdd045185c}"); // This is super wacky, but we can't just initialize the // com_ptr in the lambda and assign it back out of // the lambda. We'll crash trying to get a weak_ref to the TerminalPage // during TerminalPage::Create() below. // // Instead, create the winrt object, then get a com_ptr to the // implementation _from_ the winrt object. This seems to work, even if // it's weird. winrt::com_ptr page{ nullptr }; _initializeTerminalPage(page, settings0); auto result = RunOnUIThread([&page]() { VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); }); VERIFY_SUCCEEDED(result); result = RunOnUIThread([&page]() { VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(1, tab->GetLeafPaneCount()); }); VERIFY_SUCCEEDED(result); Log::Comment(NoThrowString().Format(L"Duplicate the first pane")); result = RunOnUIThread([&page]() { page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, 0.5f, nullptr); VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, tab->GetLeafPaneCount()); }); VERIFY_SUCCEEDED(result); Log::Comment(NoThrowString().Format( L"Change the settings of the TerminalPage so the first profile is " L"no longer in the list of profiles")); result = RunOnUIThread([&page, settings1]() { page->_settings = settings1; }); VERIFY_SUCCEEDED(result); Log::Comment(NoThrowString().Format(L"Duplicate the pane, and don't crash")); result = RunOnUIThread([&page]() { page->_SplitPane(SplitState::Automatic, SplitType::Duplicate, 0.5f, nullptr); VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, tab->GetLeafPaneCount(), L"We should gracefully do nothing here - the profile no longer exists."); }); VERIFY_SUCCEEDED(result); auto cleanup = wil::scope_exit([] { auto result = RunOnUIThread([]() { // There's something causing us to crash north of // TSFInputControl::NotifyEnter, or LayoutRequested. It's very // unclear what that issue is. Since these tests don't run in // CI, simply log a message so that the dev running these tests // knows it's expected. Log::Comment(L"This test often crashes on cleanup, even when it succeeds. If it succeeded, then crashes, that's okay."); }); VERIFY_SUCCEEDED(result); }); } // Method Description: // - This is a helper method for setting up a TerminalPage with some common // settings, and creating the first tab. // Arguments: // - // Return Value: // - The initialized TerminalPage, ready to use. winrt::com_ptr TabTests::_commonSetup() { const std::string settingsJson0{ R"( { "defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "showTabsInTitlebar": false, "profiles": [ { "name" : "profile0", "guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}", "tabTitle" : "Profile 0", "historySize": 1 }, { "name" : "profile1", "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", "tabTitle" : "Profile 1", "historySize": 2 }, { "name" : "profile2", "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}", "tabTitle" : "Profile 2", "historySize": 3 }, { "name" : "profile3", "guid": "{6239a42c-4444-49a3-80bd-e8fdd045185c}", "tabTitle" : "Profile 3", "historySize": 4 } ] })" }; CascadiaSettings settings0{ til::u8u16(settingsJson0) }; VERIFY_IS_NOT_NULL(settings0); const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}"); const auto guid2 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-2222-49a3-80bd-e8fdd045185c}"); // This is super wacky, but we can't just initialize the // com_ptr in the lambda and assign it back out of // the lambda. We'll crash trying to get a weak_ref to the TerminalPage // during TerminalPage::Create() below. // // Instead, create the winrt object, then get a com_ptr to the // implementation _from_ the winrt object. This seems to work, even if // it's weird. winrt::com_ptr page{ nullptr }; _initializeTerminalPage(page, settings0); auto result = RunOnUIThread([&page]() { VERIFY_ARE_EQUAL(1u, page->_tabs.Size()); }); VERIFY_SUCCEEDED(result); return page; } void TabTests::TryZoomPane() { auto page = _commonSetup(); Log::Comment(L"Create a second pane"); auto result = RunOnUIThread([&page]() { SplitPaneArgs args{ SplitType::Duplicate }; ActionEventArgs eventArgs{ args }; page->_HandleSplitPane(nullptr, eventArgs); auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); VERIFY_SUCCEEDED(result); Log::Comment(L"Zoom in on the pane"); result = RunOnUIThread([&page]() { ActionEventArgs eventArgs{}; page->_HandleTogglePaneZoom(nullptr, eventArgs); auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_TRUE(firstTab->IsZoomed()); }); VERIFY_SUCCEEDED(result); Log::Comment(L"Zoom out of the pane"); result = RunOnUIThread([&page]() { ActionEventArgs eventArgs{}; page->_HandleTogglePaneZoom(nullptr, eventArgs); auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); VERIFY_SUCCEEDED(result); } void TabTests::MoveFocusFromZoomedPane() { auto page = _commonSetup(); Log::Comment(L"Create a second pane"); auto result = RunOnUIThread([&page]() { // Set up action SplitPaneArgs args{ SplitType::Duplicate }; ActionEventArgs eventArgs{ args }; page->_HandleSplitPane(nullptr, eventArgs); auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); VERIFY_SUCCEEDED(result); Log::Comment(L"Zoom in on the pane"); result = RunOnUIThread([&page]() { // Set up action ActionEventArgs eventArgs{}; page->_HandleTogglePaneZoom(nullptr, eventArgs); auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_TRUE(firstTab->IsZoomed()); }); VERIFY_SUCCEEDED(result); Log::Comment(L"Move focus. This will cause us to un-zoom."); result = RunOnUIThread([&page]() { // Set up action MoveFocusArgs args{ FocusDirection::Left }; ActionEventArgs eventArgs{ args }; page->_HandleMoveFocus(nullptr, eventArgs); auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); VERIFY_SUCCEEDED(result); } void TabTests::CloseZoomedPane() { auto page = _commonSetup(); Log::Comment(L"Create a second pane"); auto result = RunOnUIThread([&page]() { // Set up action SplitPaneArgs args{ SplitType::Duplicate }; ActionEventArgs eventArgs{ args }; page->_HandleSplitPane(nullptr, eventArgs); auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); VERIFY_SUCCEEDED(result); Log::Comment(L"Zoom in on the pane"); result = RunOnUIThread([&page]() { // Set up action ActionEventArgs eventArgs{}; page->_HandleTogglePaneZoom(nullptr, eventArgs); auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount()); VERIFY_IS_TRUE(firstTab->IsZoomed()); }); VERIFY_SUCCEEDED(result); Log::Comment(L"Close Pane. This should cause us to un-zoom, and remove the second pane from the tree"); result = RunOnUIThread([&page]() { // Set up action ActionEventArgs eventArgs{}; page->_HandleClosePane(nullptr, eventArgs); auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); VERIFY_SUCCEEDED(result); // Introduce a slight delay to let the events finish propagating Sleep(250); Log::Comment(L"Check to ensure there's only one pane left."); result = RunOnUIThread([&page]() { auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0)); VERIFY_ARE_EQUAL(1, firstTab->GetLeafPaneCount()); VERIFY_IS_FALSE(firstTab->IsZoomed()); }); VERIFY_SUCCEEDED(result); } void TabTests::NextMRUTab() { // This is a test for GH#8025 - we want to make sure that we can do both // in-order and MRU tab traversal, using the tab switcher and with the // tab switcher disabled. auto page = _commonSetup(); Log::Comment(L"Create a second tab"); TestOnUIThread([&page]() { NewTerminalArgs newTerminalArgs{ 1 }; page->_OpenNewTab(newTerminalArgs); }); VERIFY_ARE_EQUAL(2u, page->_tabs.Size()); Log::Comment(L"Create a third tab"); TestOnUIThread([&page]() { NewTerminalArgs newTerminalArgs{ 2 }; page->_OpenNewTab(newTerminalArgs); }); VERIFY_ARE_EQUAL(3u, page->_tabs.Size()); Log::Comment(L"Create a fourth tab"); TestOnUIThread([&page]() { NewTerminalArgs newTerminalArgs{ 3 }; page->_OpenNewTab(newTerminalArgs); }); VERIFY_ARE_EQUAL(4u, page->_tabs.Size()); TestOnUIThread([&page]() { uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1); VERIFY_ARE_EQUAL(3u, focusedIndex, L"Verify the fourth tab is the focused one"); }); Log::Comment(L"Select the second tab"); TestOnUIThread([&page]() { page->_SelectTab(1); }); TestOnUIThread([&page]() { uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1); VERIFY_ARE_EQUAL(1u, focusedIndex, L"Verify the second tab is the focused one"); }); Log::Comment(L"Change the tab switch order to MRU switching"); TestOnUIThread([&page]() { page->_settings.GlobalSettings().TabSwitcherMode(TabSwitcherMode::MostRecentlyUsed); }); Log::Comment(L"Switch to the next MRU tab, which is the fourth tab"); TestOnUIThread([&page]() { page->_SelectNextTab(true, nullptr); }); Log::Comment(L"Sleep to let events propagate"); Sleep(250); TestOnUIThread([&page]() { Log::Comment(L"Hide the command palette, to confirm the selection"); // If you don't do this, the palette will just stay open, and the // next time we call _HandleNextTab, we'll continue traversing the // MRU list, instead of just hoping one entry. page->CommandPalette().Visibility(Visibility::Collapsed); }); TestOnUIThread([&page]() { uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1); VERIFY_ARE_EQUAL(3u, focusedIndex, L"Verify the fourth tab is the focused one"); }); Log::Comment(L"Switch to the next MRU tab, which is the second tab"); TestOnUIThread([&page]() { page->_SelectNextTab(true, nullptr); }); Log::Comment(L"Sleep to let events propagate"); Sleep(250); TestOnUIThread([&page]() { Log::Comment(L"Hide the command palette, to confirm the selection"); // If you don't do this, the palette will just stay open, and the // next time we call _HandleNextTab, we'll continue traversing the // MRU list, instead of just hoping one entry. page->CommandPalette().Visibility(Visibility::Collapsed); }); TestOnUIThread([&page]() { uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1); VERIFY_ARE_EQUAL(1u, focusedIndex, L"Verify the second tab is the focused one"); }); Log::Comment(L"Change the tab switch order to in-order switching"); page->_settings.GlobalSettings().TabSwitcherMode(TabSwitcherMode::InOrder); Log::Comment(L"Switch to the next in-order tab, which is the third tab"); TestOnUIThread([&page]() { page->_SelectNextTab(true, nullptr); }); TestOnUIThread([&page]() { uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1); VERIFY_ARE_EQUAL(2u, focusedIndex, L"Verify the third tab is the focused one"); }); Log::Comment(L"Change the tab switch order to not use the tab switcher (which is in-order always)"); page->_settings.GlobalSettings().TabSwitcherMode(TabSwitcherMode::Disabled); Log::Comment(L"Switch to the next in-order tab, which is the fourth tab"); TestOnUIThread([&page]() { page->_SelectNextTab(true, nullptr); }); TestOnUIThread([&page]() { uint32_t focusedIndex = page->_GetFocusedTabIndex().value_or(-1); VERIFY_ARE_EQUAL(3u, focusedIndex, L"Verify the fourth tab is the focused one"); }); } void TabTests::VerifyCommandPaletteTabSwitcherOrder() { // This is a test for GH#8188 - we want to make sure that the order of tabs // is preserved in the CommandPalette's TabSwitcher auto page = _commonSetup(); Log::Comment(L"Create 3 additional tabs"); RunOnUIThread([&page]() { NewTerminalArgs newTerminalArgs{ 1 }; page->_OpenNewTab(newTerminalArgs); page->_OpenNewTab(newTerminalArgs); page->_OpenNewTab(newTerminalArgs); }); VERIFY_ARE_EQUAL(4u, page->_mruTabs.Size()); Log::Comment(L"give alphabetical names to all switch tab actions"); TestOnUIThread([&page]() { page->_GetTerminalTabImpl(page->_tabs.GetAt(0))->Title(L"a"); }); TestOnUIThread([&page]() { page->_GetTerminalTabImpl(page->_tabs.GetAt(1))->Title(L"b"); }); TestOnUIThread([&page]() { page->_GetTerminalTabImpl(page->_tabs.GetAt(2))->Title(L"c"); }); TestOnUIThread([&page]() { page->_GetTerminalTabImpl(page->_tabs.GetAt(3))->Title(L"d"); }); TestOnUIThread([&page]() { Log::Comment(L"Sanity check the titles of our tabs are what we set them to."); VERIFY_ARE_EQUAL(L"a", page->_tabs.GetAt(0).Title()); VERIFY_ARE_EQUAL(L"b", page->_tabs.GetAt(1).Title()); VERIFY_ARE_EQUAL(L"c", page->_tabs.GetAt(2).Title()); VERIFY_ARE_EQUAL(L"d", page->_tabs.GetAt(3).Title()); VERIFY_ARE_EQUAL(L"d", page->_mruTabs.GetAt(0).Title()); VERIFY_ARE_EQUAL(L"c", page->_mruTabs.GetAt(1).Title()); VERIFY_ARE_EQUAL(L"b", page->_mruTabs.GetAt(2).Title()); VERIFY_ARE_EQUAL(L"a", page->_mruTabs.GetAt(3).Title()); }); Log::Comment(L"Change the tab switch order to MRU switching"); TestOnUIThread([&page]() { page->_settings.GlobalSettings().TabSwitcherMode(TabSwitcherMode::MostRecentlyUsed); }); Log::Comment(L"Select the tabs from 0 to 3"); RunOnUIThread([&page]() { page->_UpdatedSelectedTab(0); page->_UpdatedSelectedTab(1); page->_UpdatedSelectedTab(2); page->_UpdatedSelectedTab(3); }); VERIFY_ARE_EQUAL(4u, page->_mruTabs.Size()); VERIFY_ARE_EQUAL(L"d", page->_mruTabs.GetAt(0).Title()); VERIFY_ARE_EQUAL(L"c", page->_mruTabs.GetAt(1).Title()); VERIFY_ARE_EQUAL(L"b", page->_mruTabs.GetAt(2).Title()); VERIFY_ARE_EQUAL(L"a", page->_mruTabs.GetAt(3).Title()); Log::Comment(L"Switch to the next MRU tab, which is the third tab"); RunOnUIThread([&page]() { page->_SelectNextTab(true, nullptr); // In the course of a single tick, the Command Palette will: // * open // * select the proper tab from the mru's list // * raise an event for _filteredActionsView().SelectionChanged to // immediately preview the new tab // * raise a _SwitchToTabRequestedHandlers event // * then dismiss itself, because we can't fake holing down an // anchor key in the tests }); TestOnUIThread([&page]() { VERIFY_ARE_EQUAL(L"c", page->_mruTabs.GetAt(0).Title()); VERIFY_ARE_EQUAL(L"d", page->_mruTabs.GetAt(1).Title()); VERIFY_ARE_EQUAL(L"b", page->_mruTabs.GetAt(2).Title()); VERIFY_ARE_EQUAL(L"a", page->_mruTabs.GetAt(3).Title()); }); const auto palette = winrt::get_self(page->CommandPalette()); VERIFY_ARE_EQUAL(winrt::TerminalApp::implementation::CommandPaletteMode::TabSwitchMode, palette->_currentMode, L"Verify we are in the tab switcher mode"); // At this point, the contents of the command palette's _mruTabs list is // still the _old_ ordering (d, c, b, a). The ordering is only updated // in TerminalPage::_SelectNextTab, but as we saw before, the palette // will also dismiss itself immediately when that's called. So we can't // really inspect the contents of the list in this test, unfortunately. } void TabTests::TestWindowRenameSuccessful() { auto page = _commonSetup(); page->RenameWindowRequested([&page](auto&&, const winrt::TerminalApp::RenameWindowRequestedArgs args) { // In the real terminal, this would bounce up to the monarch and // come back down. Instead, immediately call back and set the name. page->WindowName(args.ProposedName()); }); bool windowNameChanged = false; page->PropertyChanged([&page, &windowNameChanged](auto&&, const winrt::WUX::Data::PropertyChangedEventArgs& args) mutable { if (args.PropertyName() == L"WindowNameForDisplay") { windowNameChanged = true; } }); TestOnUIThread([&page]() { page->_RequestWindowRename(winrt::hstring{ L"Foo" }); }); TestOnUIThread([&]() { VERIFY_ARE_EQUAL(L"Foo", page->_WindowName); VERIFY_IS_TRUE(windowNameChanged, L"The window name should have changed, and we should have raised a notification that WindowNameForDisplay changed"); }); } void TabTests::TestWindowRenameFailure() { auto page = _commonSetup(); page->RenameWindowRequested([&page](auto&&, auto&&) { // In the real terminal, this would bounce up to the monarch and // come back down. Instead, immediately call back to tell the terminal it failed. page->RenameFailed(); }); bool windowNameChanged = false; page->PropertyChanged([&page, &windowNameChanged](auto&&, const winrt::WUX::Data::PropertyChangedEventArgs& args) mutable { if (args.PropertyName() == L"WindowNameForDisplay") { windowNameChanged = true; } }); TestOnUIThread([&page]() { page->_RequestWindowRename(winrt::hstring{ L"Foo" }); }); TestOnUIThread([&]() { VERIFY_IS_FALSE(windowNameChanged, L"The window name should not have changed, we should have rejected the change."); }); } }