// Copyright (c) Microsoft Corporation. // Licensed under the MIT license. #include "pch.h" #include "../TerminalSettingsModel/CascadiaSettings.h" #include "JsonTestClass.h" #include "TestUtils.h" using namespace Microsoft::Console; using namespace winrt::Microsoft::Terminal::Settings::Model; using namespace winrt::Microsoft::Terminal::TerminalControl; using namespace winrt::Windows::Foundation::Collections; using namespace WEX::Logging; using namespace WEX::TestExecution; using namespace WEX::Common; namespace SettingsModelLocalTests { // 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 CommandTests : public JsonTestClass { // Use a custom AppxManifest to ensure that we can activate winrt types // from our test. This property will tell taef to manually use this as // the AppxManifest for this test class. // This does not yet work for anything XAML-y. See TabTests.cpp for more // details on that. BEGIN_TEST_CLASS(CommandTests) TEST_CLASS_PROPERTY(L"RunAs", L"UAP") TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml") END_TEST_CLASS() TEST_METHOD(ManyCommandsSameAction); TEST_METHOD(LayerCommand); TEST_METHOD(TestSplitPaneArgs); TEST_METHOD(TestResourceKeyName); TEST_METHOD(TestAutogeneratedName); TEST_METHOD(TestLayerOnAutogeneratedName); TEST_CLASS_SETUP(ClassSetup) { InitializeJsonReader(); return true; } }; void CommandTests::ManyCommandsSameAction() { const std::string commands0String{ R"([ { "name":"action0", "command": "copy" } ])" }; const std::string commands1String{ R"([ { "name":"action1", "command": { "action": "copy", "singleLine": false } } ])" }; const std::string commands2String{ R"([ { "name":"action2", "command": "paste" }, { "name":"action3", "command": "paste" } ])" }; const auto commands0Json = VerifyParseSucceeded(commands0String); const auto commands1Json = VerifyParseSucceeded(commands1String); const auto commands2Json = VerifyParseSucceeded(commands2String); IMap commands = winrt::single_threaded_map(); VERIFY_ARE_EQUAL(0u, commands.Size()); { auto warnings = implementation::Command::LayerJson(commands, commands0Json); VERIFY_ARE_EQUAL(0u, warnings.size()); } VERIFY_ARE_EQUAL(1u, commands.Size()); { auto warnings = implementation::Command::LayerJson(commands, commands1Json); VERIFY_ARE_EQUAL(0u, warnings.size()); } VERIFY_ARE_EQUAL(2u, commands.Size()); { auto warnings = implementation::Command::LayerJson(commands, commands2Json); VERIFY_ARE_EQUAL(0u, warnings.size()); } VERIFY_ARE_EQUAL(4u, commands.Size()); } void CommandTests::LayerCommand() { // Each one of the commands in this test should layer upon the previous, overriding the action. const std::string commands0String{ R"([ { "name":"action0", "command": "copy" } ])" }; const std::string commands1String{ R"([ { "name":"action0", "command": "paste" } ])" }; const std::string commands2String{ R"([ { "name":"action0", "command": "newTab" } ])" }; const std::string commands3String{ R"([ { "name":"action0", "command": null } ])" }; const auto commands0Json = VerifyParseSucceeded(commands0String); const auto commands1Json = VerifyParseSucceeded(commands1String); const auto commands2Json = VerifyParseSucceeded(commands2String); const auto commands3Json = VerifyParseSucceeded(commands3String); IMap commands = winrt::single_threaded_map(); VERIFY_ARE_EQUAL(0u, commands.Size()); { auto warnings = implementation::Command::LayerJson(commands, commands0Json); VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(1u, commands.Size()); auto command = commands.Lookup(L"action0"); VERIFY_IS_NOT_NULL(command); VERIFY_IS_NOT_NULL(command.Action()); VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action()); const auto& realArgs = command.Action().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); } { auto warnings = implementation::Command::LayerJson(commands, commands1Json); VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(1u, commands.Size()); auto command = commands.Lookup(L"action0"); VERIFY_IS_NOT_NULL(command); VERIFY_IS_NOT_NULL(command.Action()); VERIFY_ARE_EQUAL(ShortcutAction::PasteText, command.Action().Action()); VERIFY_IS_NULL(command.Action().Args()); } { auto warnings = implementation::Command::LayerJson(commands, commands2Json); VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(1u, commands.Size()); auto command = commands.Lookup(L"action0"); VERIFY_IS_NOT_NULL(command); VERIFY_IS_NOT_NULL(command.Action()); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.Action().Action()); const auto& realArgs = command.Action().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); } { // This last command should "unbind" the action. auto warnings = implementation::Command::LayerJson(commands, commands3Json); VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(0u, commands.Size()); } } void CommandTests::TestSplitPaneArgs() { // This is the same as KeyBindingsTests::TestSplitPaneArgs, but with // looking up the action and its args from a map of commands, instead // of from keybindings. const std::string commands0String{ R"([ { "name": "command0", "command": { "action": "splitPane", "split": null } }, { "name": "command1", "command": { "action": "splitPane", "split": "vertical" } }, { "name": "command2", "command": { "action": "splitPane", "split": "horizontal" } }, { "name": "command4", "command": { "action": "splitPane" } }, { "name": "command5", "command": { "action": "splitPane", "split": "auto" } } ])" }; const auto commands0Json = VerifyParseSucceeded(commands0String); IMap commands = winrt::single_threaded_map(); VERIFY_ARE_EQUAL(0u, commands.Size()); auto warnings = implementation::Command::LayerJson(commands, commands0Json); VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(5u, commands.Size()); { auto command = commands.Lookup(L"command0"); VERIFY_IS_NOT_NULL(command); VERIFY_IS_NOT_NULL(command.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); const auto& realArgs = command.Action().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle()); } { auto command = commands.Lookup(L"command1"); VERIFY_IS_NOT_NULL(command); VERIFY_IS_NOT_NULL(command.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); const auto& realArgs = command.Action().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); } { auto command = commands.Lookup(L"command2"); VERIFY_IS_NOT_NULL(command); VERIFY_IS_NOT_NULL(command.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); const auto& realArgs = command.Action().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle()); } { auto command = commands.Lookup(L"command4"); VERIFY_IS_NOT_NULL(command); VERIFY_IS_NOT_NULL(command.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); const auto& realArgs = command.Action().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle()); } { auto command = commands.Lookup(L"command5"); VERIFY_IS_NOT_NULL(command); VERIFY_IS_NOT_NULL(command.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); const auto& realArgs = command.Action().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle()); } } void CommandTests::TestResourceKeyName() { // This test checks looking up a name from a resource key. const std::string commands0String{ R"([ { "name": { "key": "DuplicateTabCommandKey"}, "command": "copy" } ])" }; const auto commands0Json = VerifyParseSucceeded(commands0String); IMap commands = winrt::single_threaded_map(); VERIFY_ARE_EQUAL(0u, commands.Size()); { auto warnings = implementation::Command::LayerJson(commands, commands0Json); VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(1u, commands.Size()); // NOTE: We're relying on DuplicateTabCommandKey being defined as // "Duplicate Tab" here. If that string changes in our resources, // this test will break. auto command = commands.Lookup(L"Duplicate tab"); VERIFY_IS_NOT_NULL(command); VERIFY_IS_NOT_NULL(command.Action()); VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action()); const auto& realArgs = command.Action().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); } } void CommandTests::TestAutogeneratedName() { // Tests run in Helix can't report Skipped until GH#7286 is resolved. // Set ignore flag to make Helix run completely overlook it. BEGIN_TEST_METHOD_PROPERTIES() TEST_METHOD_PROPERTY(L"Ignore", L"True") END_TEST_METHOD_PROPERTIES() // This test to be corrected as a part of GH#7281 // This test ensures that we'll correctly create commands for actions // that don't have given names, pursuant to the spec in GH#6532. // NOTE: The keys used to look up these commands are partially generated // from strings in our Resources.resw. If those string values should // change, it's likely that this test will break. const std::string commands0String{ R"([ { "command": { "action": "splitPane", "split": null } }, { "command": { "action": "splitPane", "split": "vertical" } }, { "command": { "action": "splitPane", "split": "horizontal" } }, { "command": { "action": "splitPane", "split": "none" } }, { "command": { "action": "splitPane" } }, { "command": { "action": "splitPane", "split": "auto" } }, { "command": { "action": "splitPane", "split": "foo" } } ])" }; const auto commands0Json = VerifyParseSucceeded(commands0String); IMap commands = winrt::single_threaded_map(); VERIFY_ARE_EQUAL(0u, commands.Size()); auto warnings = implementation::Command::LayerJson(commands, commands0Json); VERIFY_ARE_EQUAL(0u, warnings.size()); // There are only 3 commands here: all of the `"none"`, `"auto"`, // `"foo"`, `null`, and bindings all generate the same action, // which will generate just a single name for all of them. VERIFY_ARE_EQUAL(3u, commands.Size()); { auto command = commands.Lookup(L"Split pane"); VERIFY_IS_NOT_NULL(command); VERIFY_IS_NOT_NULL(command.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); const auto& realArgs = command.Action().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle()); } { auto command = commands.Lookup(L"Split pane, split: vertical"); VERIFY_IS_NOT_NULL(command); VERIFY_IS_NOT_NULL(command.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); const auto& realArgs = command.Action().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); } { auto command = commands.Lookup(L"Split pane, split: horizontal"); VERIFY_IS_NOT_NULL(command); VERIFY_IS_NOT_NULL(command.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); const auto& realArgs = command.Action().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle()); } } void CommandTests::TestLayerOnAutogeneratedName() { const std::string commands0String{ R"([ { "command": { "action": "splitPane" } }, { "name":"Split pane", "command": { "action": "splitPane", "split": "vertical" } }, ])" }; const auto commands0Json = VerifyParseSucceeded(commands0String); IMap commands = winrt::single_threaded_map(); VERIFY_ARE_EQUAL(0u, commands.Size()); auto warnings = implementation::Command::LayerJson(commands, commands0Json); VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(1u, commands.Size()); { auto command = commands.Lookup(L"Split pane"); VERIFY_IS_NOT_NULL(command); VERIFY_IS_NOT_NULL(command.Action()); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); const auto& realArgs = command.Action().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); } } }