terminal/src/cascadia/LocalTests_TerminalApp/TabTests.cpp

1607 lines
64 KiB
C++

// 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(SwapPanes);
TEST_METHOD(NextMRUTab);
TEST_METHOD(VerifyCommandPaletteTabSwitcherOrder);
TEST_METHOD(TestWindowRenameSuccessful);
TEST_METHOD(TestWindowRenameFailure);
TEST_METHOD(TestControlSettingsHasParent);
TEST_METHOD(TestPreviewCommitScheme);
TEST_METHOD(TestPreviewDismissScheme);
TEST_METHOD(TestPreviewSchemeWhilePreviewing);
TEST_METHOD(TestClampSwitchToTab);
TEST_CLASS_SETUP(ClassSetup)
{
return true;
}
TEST_METHOD_CLEANUP(MethodCleanup)
{
return true;
}
private:
void _initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
CascadiaSettings initialSettings);
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> _commonSetup();
};
template<typename TFunction>
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<winrt::TerminalApp::implementation::MinMaxCloseControl> mmcc{ nullptr };
auto result = RunOnUIThread([&mmcc]() {
mmcc = winrt::make_self<winrt::TerminalApp::implementation::MinMaxCloseControl>();
VERIFY_IS_NOT_NULL(mmcc);
});
VERIFY_SUCCEEDED(result);
}
void TabTests::CreateTerminalMuxXamlType()
{
winrt::com_ptr<winrt::TerminalApp::implementation::TabRowControl> tabRowControl{ nullptr };
auto result = RunOnUIThread([&tabRowControl]() {
tabRowControl = winrt::make_self<winrt::TerminalApp::implementation::TabRowControl>();
VERIFY_IS_NOT_NULL(tabRowControl);
});
VERIFY_SUCCEEDED(result);
}
void TabTests::CreateTerminalPage()
{
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> page{ nullptr };
auto result = RunOnUIThread([&page]() {
page = winrt::make_self<winrt::TerminalApp::implementation::TerminalPage>();
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:
// - <none>
void TabTests::_initializeTerminalPage(winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage>& page,
CascadiaSettings initialSettings)
{
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> 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<winrt::TerminalApp::implementation::TerminalPage>(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(tab);
});
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.
static constexpr std::wstring_view settingsJson0{ LR"(
{
"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{ settingsJson0, {} };
VERIFY_IS_NOT_NULL(settings0);
// This is super wacky, but we can't just initialize the
// com_ptr<impl::TerminalPage> 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<winrt::TerminalApp::implementation::TerminalPage> 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
static constexpr std::wstring_view settingsJson0{ LR"(
{
"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
}
]
})" };
static constexpr std::wstring_view settingsJson1{ LR"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
CascadiaSettings settings0{ settingsJson0, {} };
VERIFY_IS_NOT_NULL(settings0);
CascadiaSettings settings1{ 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<impl::TerminalPage> 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<winrt::TerminalApp::implementation::TerminalPage> 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(3u, page->_tabs.Size(), L"We should successfully duplicate a tab hosting a deleted profile.");
});
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
static constexpr std::wstring_view settingsJson0{ LR"(
{
"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
}
]
})" };
static constexpr std::wstring_view settingsJson1{ LR"(
{
"defaultProfile": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
"profiles": [
{
"name" : "profile1",
"guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}",
"historySize": 2
}
]
})" };
CascadiaSettings settings0{ settingsJson0, {} };
VERIFY_IS_NOT_NULL(settings0);
CascadiaSettings settings1{ 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<impl::TerminalPage> 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<winrt::TerminalApp::implementation::TerminalPage> 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(SplitDirection::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(SplitDirection::Automatic, SplitType::Duplicate, 0.5f, nullptr);
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(3,
tab->GetLeafPaneCount(),
L"We should successfully duplicate a pane hosting a deleted profile.");
});
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:
// - <none>
// Return Value:
// - The initialized TerminalPage, ready to use.
winrt::com_ptr<winrt::TerminalApp::implementation::TerminalPage> TabTests::_commonSetup()
{
static constexpr std::wstring_view settingsJson0{ LR"(
{
"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
}
],
"schemes":
[
{
"name": "Campbell",
"foreground": "#CCCCCC",
"background": "#0C0C0C",
"cursorColor": "#FFFFFF",
"black": "#0C0C0C",
"red": "#C50F1F",
"green": "#13A10E",
"yellow": "#C19C00",
"blue": "#0037DA",
"purple": "#881798",
"cyan": "#3A96DD",
"white": "#CCCCCC",
"brightBlack": "#767676",
"brightRed": "#E74856",
"brightGreen": "#16C60C",
"brightYellow": "#F9F1A5",
"brightBlue": "#3B78FF",
"brightPurple": "#B4009E",
"brightCyan": "#61D6D6",
"brightWhite": "#F2F2F2"
},
{
"name": "Vintage",
"foreground": "#C0C0C0",
"background": "#000000",
"cursorColor": "#FFFFFF",
"black": "#000000",
"red": "#800000",
"green": "#008000",
"yellow": "#808000",
"blue": "#000080",
"purple": "#800080",
"cyan": "#008080",
"white": "#C0C0C0",
"brightBlack": "#808080",
"brightRed": "#FF0000",
"brightGreen": "#00FF00",
"brightYellow": "#FFFF00",
"brightBlue": "#0000FF",
"brightPurple": "#FF00FF",
"brightCyan": "#00FFFF",
"brightWhite": "#FFFFFF"
},
{
"name": "One Half Light",
"foreground": "#383A42",
"background": "#FAFAFA",
"cursorColor": "#4F525D",
"black": "#383A42",
"red": "#E45649",
"green": "#50A14F",
"yellow": "#C18301",
"blue": "#0184BC",
"purple": "#A626A4",
"cyan": "#0997B3",
"white": "#FAFAFA",
"brightBlack": "#4F525D",
"brightRed": "#DF6C75",
"brightGreen": "#98C379",
"brightYellow": "#E4C07A",
"brightBlue": "#61AFEF",
"brightPurple": "#C577DD",
"brightCyan": "#56B5C1",
"brightWhite": "#FFFFFF"
}
]
})" };
CascadiaSettings settings0{ 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<impl::TerminalPage> 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<winrt::TerminalApp::implementation::TerminalPage> 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::SwapPanes()
{
auto page = _commonSetup();
Log::Comment(L"Setup 4 panes.");
// Create the following layout
// -------------------
// | 1 | 2 |
// | | |
// -------------------
// | 3 | 4 |
// | | |
// -------------------
uint32_t firstId = 0, secondId = 0, thirdId = 0, fourthId = 0;
TestOnUIThread([&]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
firstId = tab->_activePane->Id().value();
// We start with 1 tab, split vertically to get
// -------------------
// | 1 | 2 |
// | | |
// -------------------
page->_SplitPane(SplitDirection::Right, SplitType::Duplicate, 0.5f, nullptr);
secondId = tab->_activePane->Id().value();
});
Sleep(250);
TestOnUIThread([&]() {
// After this the `2` pane is focused, go back to `1` being focused
page->_MoveFocus(FocusDirection::Left);
});
Sleep(250);
TestOnUIThread([&]() {
// Split again to make the 3rd tab
// -------------------
// | 1 | |
// | | |
// ---------| 2 |
// | 3 | |
// | | |
// -------------------
page->_SplitPane(SplitDirection::Down, SplitType::Duplicate, 0.5f, nullptr);
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
// Split again to make the 3rd tab
thirdId = tab->_activePane->Id().value();
});
Sleep(250);
TestOnUIThread([&]() {
// After this the `3` pane is focused, go back to `2` being focused
page->_MoveFocus(FocusDirection::Right);
});
Sleep(250);
TestOnUIThread([&]() {
// Split to create the final pane
// -------------------
// | 1 | 2 |
// | | |
// -------------------
// | 3 | 4 |
// | | |
// -------------------
page->_SplitPane(SplitDirection::Down, SplitType::Duplicate, 0.5f, nullptr);
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
fourthId = tab->_activePane->Id().value();
});
Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// just to be complete, make sure we actually have 4 different ids
VERIFY_ARE_NOT_EQUAL(firstId, fourthId);
VERIFY_ARE_NOT_EQUAL(secondId, fourthId);
VERIFY_ARE_NOT_EQUAL(thirdId, fourthId);
VERIFY_ARE_NOT_EQUAL(firstId, thirdId);
VERIFY_ARE_NOT_EQUAL(secondId, thirdId);
VERIFY_ARE_NOT_EQUAL(firstId, secondId);
});
// Gratuitous use of sleep to make sure that the UI has updated properly
// after each operation.
Sleep(250);
// Now try to move the pane through the tree
Log::Comment(L"Move pane to the left. This should swap panes 3 and 4");
// -------------------
// | 1 | 2 |
// | | |
// -------------------
// | 4 | 3 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
SwapPaneArgs args{ FocusDirection::Left };
ActionEventArgs eventArgs{ args };
page->_HandleSwapPane(nullptr, eventArgs);
});
Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_firstChild->_secondChild->Id().value());
VERIFY_ARE_EQUAL(thirdId, tab->_rootPane->_secondChild->_secondChild->Id().value());
});
Sleep(250);
Log::Comment(L"Move pane to up. This should swap panes 1 and 4");
// -------------------
// | 4 | 2 |
// | | |
// -------------------
// | 1 | 3 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
SwapPaneArgs args{ FocusDirection::Up };
ActionEventArgs eventArgs{ args };
page->_HandleSwapPane(nullptr, eventArgs);
});
Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_firstChild->_firstChild->Id().value());
VERIFY_ARE_EQUAL(firstId, tab->_rootPane->_firstChild->_secondChild->Id().value());
});
Sleep(250);
Log::Comment(L"Move pane to the right. This should swap panes 2 and 4");
// -------------------
// | 2 | 4 |
// | | |
// -------------------
// | 1 | 3 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
SwapPaneArgs args{ FocusDirection::Right };
ActionEventArgs eventArgs{ args };
page->_HandleSwapPane(nullptr, eventArgs);
});
Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_secondChild->_firstChild->Id().value());
VERIFY_ARE_EQUAL(secondId, tab->_rootPane->_firstChild->_firstChild->Id().value());
});
Sleep(250);
Log::Comment(L"Move pane down. This should swap panes 3 and 4");
// -------------------
// | 2 | 3 |
// | | |
// -------------------
// | 1 | 4 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
SwapPaneArgs args{ FocusDirection::Down };
ActionEventArgs eventArgs{ args };
page->_HandleSwapPane(nullptr, eventArgs);
});
Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());
// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_secondChild->_secondChild->Id().value());
VERIFY_ARE_EQUAL(thirdId, tab->_rootPane->_secondChild->_firstChild->Id().value());
});
}
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(page->_tabs.GetAt(0));
page->_UpdatedSelectedTab(page->_tabs.GetAt(1));
page->_UpdatedSelectedTab(page->_tabs.GetAt(2));
page->_UpdatedSelectedTab(page->_tabs.GetAt(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<winrt::TerminalApp::implementation::CommandPalette>(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()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
END_TEST_METHOD_PROPERTIES()
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()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"IsolationLevel", L"Method")
END_TEST_METHOD_PROPERTIES()
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.");
});
}
void TabTests::TestControlSettingsHasParent()
{
Log::Comment(L"Ensure that when we create a control, it always has a parent TerminalSettings");
auto page = _commonSetup();
VERIFY_IS_NOT_NULL(page);
TestOnUIThread([&page]() {
const auto& activeControl{ page->_GetActiveControl() };
VERIFY_IS_NOT_NULL(activeControl);
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
VERIFY_IS_NOT_NULL(controlSettings);
const auto& originalSettings = controlSettings.GetParent();
VERIFY_IS_NOT_NULL(originalSettings);
});
}
void TabTests::TestPreviewCommitScheme()
{
Log::Comment(L"Preview a color scheme. Make sure it's applied, then committed accordingly");
auto page = _commonSetup();
VERIFY_IS_NOT_NULL(page);
TestOnUIThread([&page]() {
const auto& activeControl{ page->_GetActiveControl() };
VERIFY_IS_NOT_NULL(activeControl);
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
VERIFY_IS_NOT_NULL(controlSettings);
const auto& originalSettings = controlSettings.GetParent();
VERIFY_IS_NOT_NULL(originalSettings);
VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, controlSettings.DefaultBackground());
});
TestOnUIThread([&page]() {
Log::Comment(L"Emulate previewing the SetColorScheme action");
SetColorSchemeArgs args{ L"Vintage" };
page->_PreviewColorScheme(args);
});
TestOnUIThread([&page]() {
const auto& activeControl{ page->_GetActiveControl() };
VERIFY_IS_NOT_NULL(activeControl);
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
VERIFY_IS_NOT_NULL(controlSettings);
const auto& previewSettings = controlSettings.GetParent();
VERIFY_IS_NOT_NULL(previewSettings);
const auto& originalSettings = previewSettings.GetParent();
VERIFY_IS_NOT_NULL(originalSettings);
Log::Comment(L"Color should be changed to the preview");
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
});
TestOnUIThread([&page]() {
Log::Comment(L"Emulate committing the SetColorScheme action");
SetColorSchemeArgs args{ L"Vintage" };
page->_EndPreviewColorScheme();
page->_HandleSetColorScheme(nullptr, ActionEventArgs{ args });
});
TestOnUIThread([&page]() {
const auto& activeControl{ page->_GetActiveControl() };
VERIFY_IS_NOT_NULL(activeControl);
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
VERIFY_IS_NOT_NULL(controlSettings);
const auto& originalSettings = controlSettings.GetParent();
VERIFY_IS_NOT_NULL(originalSettings);
const auto& grandparentSettings = originalSettings.GetParent();
VERIFY_IS_NULL(grandparentSettings);
Log::Comment(L"Color should be changed");
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(nullptr, page->_originalSettings);
});
}
void TabTests::TestPreviewDismissScheme()
{
Log::Comment(L"Preview a color scheme. Make sure it's applied, then dismissed accordingly");
auto page = _commonSetup();
VERIFY_IS_NOT_NULL(page);
TestOnUIThread([&page]() {
const auto& activeControl{ page->_GetActiveControl() };
VERIFY_IS_NOT_NULL(activeControl);
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
VERIFY_IS_NOT_NULL(controlSettings);
const auto& originalSettings = controlSettings.GetParent();
VERIFY_IS_NOT_NULL(originalSettings);
VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, controlSettings.DefaultBackground());
});
TestOnUIThread([&page]() {
Log::Comment(L"Emulate previewing the SetColorScheme action");
SetColorSchemeArgs args{ L"Vintage" };
page->_PreviewColorScheme(args);
});
TestOnUIThread([&page]() {
const auto& activeControl{ page->_GetActiveControl() };
VERIFY_IS_NOT_NULL(activeControl);
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
VERIFY_IS_NOT_NULL(controlSettings);
const auto& previewSettings = controlSettings.GetParent();
VERIFY_IS_NOT_NULL(previewSettings);
const auto& originalSettings = previewSettings.GetParent();
VERIFY_IS_NOT_NULL(originalSettings);
Log::Comment(L"Color should be changed to the preview");
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
});
TestOnUIThread([&page]() {
Log::Comment(L"Emulate dismissing the SetColorScheme action");
page->_EndPreviewColorScheme();
});
TestOnUIThread([&page]() {
const auto& activeControl{ page->_GetActiveControl() };
VERIFY_IS_NOT_NULL(activeControl);
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
VERIFY_IS_NOT_NULL(controlSettings);
const auto& originalSettings = controlSettings.GetParent();
VERIFY_IS_NOT_NULL(originalSettings);
const auto& grandparentSettings = originalSettings.GetParent();
VERIFY_IS_NULL(grandparentSettings);
Log::Comment(L"Color should be the same as it originally was");
VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(nullptr, page->_originalSettings);
});
}
void TabTests::TestPreviewSchemeWhilePreviewing()
{
Log::Comment(L"Preview a color scheme, then preview another scheme. ");
Log::Comment(L"Preview a color scheme. Make sure it's applied, then committed accordingly");
auto page = _commonSetup();
VERIFY_IS_NOT_NULL(page);
TestOnUIThread([&page]() {
const auto& activeControl{ page->_GetActiveControl() };
VERIFY_IS_NOT_NULL(activeControl);
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
VERIFY_IS_NOT_NULL(controlSettings);
const auto& originalSettings = controlSettings.GetParent();
VERIFY_IS_NOT_NULL(originalSettings);
VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, controlSettings.DefaultBackground());
});
TestOnUIThread([&page]() {
Log::Comment(L"Emulate previewing the SetColorScheme action");
SetColorSchemeArgs args{ L"Vintage" };
page->_PreviewColorScheme(args);
});
TestOnUIThread([&page]() {
const auto& activeControl{ page->_GetActiveControl() };
VERIFY_IS_NOT_NULL(activeControl);
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
VERIFY_IS_NOT_NULL(controlSettings);
const auto& previewSettings = controlSettings.GetParent();
VERIFY_IS_NOT_NULL(previewSettings);
const auto& originalSettings = previewSettings.GetParent();
VERIFY_IS_NOT_NULL(originalSettings);
Log::Comment(L"Color should be changed to the preview");
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
});
TestOnUIThread([&page]() {
Log::Comment(L"Now, preview another scheme");
SetColorSchemeArgs args{ L"One Half Light" };
page->_PreviewColorScheme(args);
});
TestOnUIThread([&page]() {
const auto& activeControl{ page->_GetActiveControl() };
VERIFY_IS_NOT_NULL(activeControl);
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
VERIFY_IS_NOT_NULL(controlSettings);
const auto& previewSettings = controlSettings.GetParent();
VERIFY_IS_NOT_NULL(previewSettings);
const auto& originalSettings = previewSettings.GetParent();
VERIFY_IS_NOT_NULL(originalSettings);
Log::Comment(L"Color should be changed to the preview");
VERIFY_ARE_EQUAL(til::color{ 0xffFAFAFA }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
});
TestOnUIThread([&page]() {
Log::Comment(L"Emulate committing the SetColorScheme action");
SetColorSchemeArgs args{ L"One Half Light" };
page->_EndPreviewColorScheme();
page->_HandleSetColorScheme(nullptr, ActionEventArgs{ args });
});
TestOnUIThread([&page]() {
const auto& activeControl{ page->_GetActiveControl() };
VERIFY_IS_NOT_NULL(activeControl);
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
VERIFY_IS_NOT_NULL(controlSettings);
const auto& originalSettings = controlSettings.GetParent();
VERIFY_IS_NOT_NULL(originalSettings);
const auto& grandparentSettings = originalSettings.GetParent();
VERIFY_IS_NULL(grandparentSettings);
Log::Comment(L"Color should be changed");
VERIFY_ARE_EQUAL(til::color{ 0xffFAFAFA }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(nullptr, page->_originalSettings);
});
}
void TabTests::TestClampSwitchToTab()
{
Log::Comment(L"Test that switching to a tab index higher than the number of tabs just clamps to the last tab.");
auto page = _commonSetup();
VERIFY_IS_NOT_NULL(page);
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());
TestOnUIThread([&page]() {
auto focusedTabIndexOpt{ page->_GetFocusedTabIndex() };
VERIFY_IS_TRUE(focusedTabIndexOpt.has_value());
VERIFY_ARE_EQUAL(2u, focusedTabIndexOpt.value());
});
TestOnUIThread([&page]() {
Log::Comment(L"Switch to the first tab");
page->_SelectTab(0);
});
TestOnUIThread([&page]() {
auto focusedTabIndexOpt{ page->_GetFocusedTabIndex() };
VERIFY_IS_TRUE(focusedTabIndexOpt.has_value());
VERIFY_ARE_EQUAL(0u, focusedTabIndexOpt.value());
});
TestOnUIThread([&page]() {
Log::Comment(L"Switch to the tab 6, which is greater than number of tabs. This should switch to the third tab");
page->_SelectTab(6);
});
TestOnUIThread([&page]() {
auto focusedTabIndexOpt{ page->_GetFocusedTabIndex() };
VERIFY_IS_TRUE(focusedTabIndexOpt.has_value());
VERIFY_ARE_EQUAL(2u, focusedTabIndexOpt.value());
});
}
}