Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/test/backend/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#include "test_pseudo_terminal.hpp"
#include "test_ui_options.hpp"
#include "test_legacy_migration.hpp"
#include "test_terminal_renderer_migration.hpp"
#include "test_session_layouts.hpp"
#include "test_ssh_user_selection.hpp"
#include "test_environment.hpp"
#include "test_termios.hpp"
Expand Down
111 changes: 111 additions & 0 deletions backend/test/backend/test_session_layouts.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#pragma once

#include <persistence/state/state.hpp>

#include <nlohmann/json.hpp>
#include <gtest/gtest.h>

#include <map>
#include <string>

namespace Test
{
namespace
{
Persistence::SessionOptions
serverWithLayouts(Persistence::TerminalEngineType type, std::map<std::string, nlohmann::json> layouts)
{
auto session = Persistence::SessionOptions{};
session.type = type;
session.layouts = std::move(layouts);
return session;
}

nlohmann::json layoutWith(std::string const& widget)
{
return nlohmann::json{{"main", {{"widgets", {widget}}}}};
}
}

TEST(SessionLayoutsTests, LayoutsOfServersAreGatheredByType)
{
Persistence::State state{};
state.sessions["server"] =
serverWithLayouts(Persistence::TerminalEngineType::ssh, {{"files", layoutWith("file-explorer")}});
state.sessions["shell"] =
serverWithLayouts(Persistence::TerminalEngineType::shell, {{"wide", layoutWith("terminal")}});

EXPECT_TRUE(state.gatherSessionLayouts());

ASSERT_TRUE(state.sessionLayouts.has_value());
EXPECT_EQ(state.sessionLayouts->ssh, (std::map<std::string, nlohmann::json>{{"files", layoutWith("file-explorer")}}));
EXPECT_EQ(state.sessionLayouts->shell, (std::map<std::string, nlohmann::json>{{"wide", layoutWith("terminal")}}));
}

TEST(SessionLayoutsTests, SameNameAndLayoutIsMergedIntoOne)
{
Persistence::State state{};
state.sessions["alpha"] =
serverWithLayouts(Persistence::TerminalEngineType::ssh, {{"files", layoutWith("file-explorer")}});
state.sessions["beta"] =
serverWithLayouts(Persistence::TerminalEngineType::ssh, {{"files", layoutWith("file-explorer")}});

state.gatherSessionLayouts();

EXPECT_EQ(state.sessionLayouts->ssh.size(), 1u);
EXPECT_TRUE(state.sessionLayouts->ssh.contains("files"));
}

TEST(SessionLayoutsTests, SameNameWithAnotherLayoutGetsTheServerName)
{
Persistence::State state{};
state.sessions["alpha"] =
serverWithLayouts(Persistence::TerminalEngineType::ssh, {{"files", layoutWith("file-explorer")}});
state.sessions["beta"] = serverWithLayouts(Persistence::TerminalEngineType::ssh, {{"files", layoutWith("terminal")}});

state.gatherSessionLayouts();

auto const& layouts = state.sessionLayouts->ssh;
ASSERT_EQ(layouts.size(), 2u);
EXPECT_EQ(layouts.at("files"), layoutWith("file-explorer"));
EXPECT_EQ(layouts.at("files (beta)"), layoutWith("terminal"));
}

TEST(SessionLayoutsTests, LayoutsOfServersStayForEarlierVersions)
{
Persistence::State state{};
state.sessions["server"] =
serverWithLayouts(Persistence::TerminalEngineType::ssh, {{"files", layoutWith("file-explorer")}});

state.gatherSessionLayouts();

ASSERT_TRUE(state.sessions["server"].layouts.has_value());
EXPECT_EQ(state.sessions["server"].layouts->size(), 1u);
}

TEST(SessionLayoutsTests, GatheringRunsOnlyOnce)
{
Persistence::State state{};
state.sessions["server"] =
serverWithLayouts(Persistence::TerminalEngineType::ssh, {{"files", layoutWith("file-explorer")}});
state.gatherSessionLayouts();
state.sessionLayouts->ssh.erase("files");

EXPECT_FALSE(state.gatherSessionLayouts());
EXPECT_TRUE(state.sessionLayouts->ssh.empty());
}

TEST(SessionLayoutsTests, GatheredLayoutsSurviveSaving)
{
Persistence::State state{};
state.sessions["server"] =
serverWithLayouts(Persistence::TerminalEngineType::ssh, {{"files", layoutWith("file-explorer")}});
state.gatherSessionLayouts();

auto reloaded = nlohmann::json(state).get<Persistence::State>();

ASSERT_TRUE(reloaded.sessionLayouts.has_value());
EXPECT_EQ(reloaded.sessionLayouts->ssh, state.sessionLayouts->ssh);
EXPECT_FALSE(reloaded.gatherSessionLayouts());
}
}
99 changes: 99 additions & 0 deletions backend/test/backend/test_terminal_renderer_migration.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#pragma once

#include <persistence/state/state.hpp>

#include <nlohmann/json.hpp>
#include <gtest/gtest.h>

#include <algorithm>
#include <string>
#include <vector>

namespace Test
{
namespace
{
Persistence::State stateWithRenderers(std::string const& profileRenderer, std::string const& sessionRenderer)
{
Persistence::State state{};
state.terminalOptions["default"].renderer = profileRenderer;
state.terminalOptions["unset"] = Persistence::TerminalOptions{};
state.sessions["server"].terminalOptions.value().renderer = sessionRenderer;
return state;
}
}

TEST(TerminalRendererMigrationTests, MarksTheMigrationAsApplied)
{
auto state = stateWithRenderers("dom", "dom");

EXPECT_TRUE(state.migrateTerminalRendererFromDom());

ASSERT_TRUE(state.appliedMigrations.has_value());
EXPECT_EQ(*state.appliedMigrations, std::vector<std::string>{"terminalRendererFromDom"});
}

#ifdef __linux__
TEST(TerminalRendererMigrationTests, MovesProfilesAndSessionsFromDomToWebgl)
{
auto state = stateWithRenderers("dom", "dom");

state.migrateTerminalRendererFromDom();

EXPECT_EQ(state.terminalOptions["default"].renderer, "webgl");
EXPECT_FALSE(state.terminalOptions["unset"].renderer.has_value());
EXPECT_EQ(state.sessions["server"].terminalOptions.value().renderer, "webgl");
}
#else
TEST(TerminalRendererMigrationTests, KeepsDomWhereItWasNeverTheDefault)
{
auto state = stateWithRenderers("dom", "dom");

state.migrateTerminalRendererFromDom();

EXPECT_EQ(state.terminalOptions["default"].renderer, "dom");
EXPECT_EQ(state.sessions["server"].terminalOptions.value().renderer, "dom");
}
#endif

TEST(TerminalRendererMigrationTests, DomChosenAfterTheMigrationIsKept)
{
auto state = stateWithRenderers("webgl", "webgl");
state.migrateTerminalRendererFromDom();
state.terminalOptions["default"].renderer = "dom";
state.sessions["server"].terminalOptions.value().renderer = "dom";

EXPECT_FALSE(state.migrateTerminalRendererFromDom());

EXPECT_EQ(state.terminalOptions["default"].renderer, "dom");
EXPECT_EQ(state.sessions["server"].terminalOptions.value().renderer, "dom");
EXPECT_EQ(state.appliedMigrations->size(), 1u);
}

TEST(TerminalRendererMigrationTests, AppliedMigrationsSurviveSaving)
{
auto state = stateWithRenderers("dom", "dom");
state.migrateTerminalRendererFromDom();

auto reloaded = nlohmann::json(state).get<Persistence::State>();
reloaded.terminalOptions["default"].renderer = "dom";

EXPECT_FALSE(reloaded.migrateTerminalRendererFromDom());
EXPECT_EQ(reloaded.terminalOptions["default"].renderer, "dom");
}

TEST(TerminalRendererMigrationTests, MissingAppliedMigrationsIsNotReportedAsMissing)
{
auto state = nlohmann::json::object().get<Persistence::State>();

const auto missing = state.collectMissingMembers(state);

EXPECT_TRUE(std::ranges::none_of(
missing,
[](std::string const& name)
{
return name.contains("appliedMigrations");
}
));
}
}
6 changes: 5 additions & 1 deletion frontend/include/frontend/session_area.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,15 @@ class SessionArea
void removeActiveSession();

Session* getActiveSession();
std::optional<nlohmann::json> getActiveSessionLayout();
/**
* @brief Whether a session is selected, whose layout can be saved.
*/
Nui::Observed<bool> const& hasActiveSession() const;
Session* getSessionByLayoutId(std::string const& layoutId);

private:
std::optional<std::size_t> findSessionIndexByTabId(int tabId) const;
void updateHasActiveSession();

/**
* @brief Reconnect entry point invoked from a Session's requestReconnect
Expand Down
2 changes: 1 addition & 1 deletion frontend/include/frontend/settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Settings
Settings(
Persistence::StateHolder* stateHolder,
FrontendEvents* events,
std::function<std::optional<nlohmann::json>()> const& obtainCurrentLayout,
std::function<std::optional<nlohmann::json>(Persistence::TerminalEngineType)> const& obtainCurrentLayout,
InputDialog& inputDialog,
ConfirmDialog& confirmDialog,
MultiInputDialog& multiInputDialog,
Expand Down
16 changes: 16 additions & 0 deletions frontend/include/frontend/settings/general_settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#include <frontend/events/frontend_events.hpp>
#include <frontend/dialog/multi_input_dialog.hpp>
#include <frontend/dialog/confirm_dialog.hpp>
#include <frontend/settings/layout_setting.hpp>
#include <frontend/settings/log_options.hpp>
#include <frontend/settings/search/setting_factory.hpp>
#include <shared_data/theme.hpp>
Expand Down Expand Up @@ -40,6 +42,11 @@ struct GeneralSettings
*/
SettingFactory fileTrackingFactory_;

/**
* @brief Creates the identities of the layout settings.
*/
SettingFactory layoutsFactory_;

public:
struct Collapsibles
{
Expand All @@ -48,6 +55,7 @@ struct GeneralSettings
Nui::Observed<bool> userInterface{true};
Nui::Observed<bool> localFilesystemOptions{true};
Nui::Observed<bool> fileTrackingOptions{true};
Nui::Observed<bool> layouts{true};
} collapsibleStates;

struct Localization
Expand Down Expand Up @@ -91,6 +99,12 @@ struct GeneralSettings
BoolSetting<> checkRemoteChanges;
} fileTrackingOptions;

struct Layouts
{
LayoutSetting ssh;
LayoutSetting shell;
} layouts;

LogOptions logOptions;
Nui::ListenRemover<decltype(AppWideEvents::availableThemes)> availableThemesListener;
bool darkLightEventOriginatesHere{false};
Expand All @@ -100,6 +114,8 @@ struct GeneralSettings
SettingFactory const& factory,
std::function<void()> const& onChange,
FrontendEvents* events,
std::function<std::optional<nlohmann::json>(Persistence::TerminalEngineType)> const& obtainCurrentLayout,
ConfirmDialog& confirmDialog,
InputDialog& inputDialog,
MultiInputDialog& multiInputDialog
);
Expand Down
19 changes: 12 additions & 7 deletions frontend/include/frontend/settings/layout_setting.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include <map>
#include <string>
#include <vector>

class LayoutSetting : public Setting<false, std::map<std::string, nlohmann::json>>
{
Expand All @@ -32,13 +33,7 @@ class LayoutSetting : public Setting<false, std::map<std::string, nlohmann::json
{
state_ = value;
engaged_ = true;
if (!state_.value().empty())
{
if (!state_.value().contains(*selected_))
selected_ = state_.value().begin()->first;
}
else
selected_ = "";
refreshNames();
}
OutfacingValueType value() const override
{
Expand All @@ -50,12 +45,22 @@ class LayoutSetting : public Setting<false, std::map<std::string, nlohmann::json
if (value)
this->value(*value);
else
{
state_ = ValueType{};
refreshNames();
}
}

private:
/**
* @brief Lists the layout names for the dropdown and keeps the selection on an existing layout.
*/
void refreshNames();

private:
ConfirmDialog* confirmDialog_;
InputDialog* newItemDialog_;
Nui::Observed<std::string> selected_;
Nui::Observed<std::vector<std::string>> names_;
std::function<std::optional<nlohmann::json>()> obtainCurrentLayout_;
};
4 changes: 0 additions & 4 deletions frontend/include/frontend/settings/session_options.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
#include <frontend/settings/terminal_options.hpp>
#include <frontend/settings/queue_options.hpp>
#include <frontend/settings/history_options.hpp>
#include <frontend/settings/layout_setting.hpp>
#include <frontend/settings/executing_session_options.hpp>
#include <frontend/settings/ssh_session_options.hpp>
#include <frontend/settings/layout_setting.hpp>
#include <frontend/settings/atomic_setting/bool_setting.hpp>
#include <frontend/settings/atomic_setting/combo_setting.hpp>

Expand All @@ -28,7 +26,6 @@ struct SessionOptions
ComboSetting<std::string> icon;
TextSetting<true> orderBy;
BoolSetting<> isStartupSession;
LayoutSetting layout;

TerminalOptions terminalOptions;
TermiosSettings termios;
Expand All @@ -46,7 +43,6 @@ struct SessionOptions
SessionOptions(
SettingFactory const& factory,
std::function<void()> const& onChange,
std::function<std::optional<nlohmann::json>()> const& obtainCurrentLayout,
ConfirmDialog& confirmDialog,
InputDialog& newItemDialog,
MultiInputDialog& multiInputDialog
Expand Down
Loading
Loading