diff --git a/mocks/include/mocks/EmptyApplication.hpp b/mocks/include/mocks/EmptyApplication.hpp index b1957898d..327304d77 100644 --- a/mocks/include/mocks/EmptyApplication.hpp +++ b/mocks/include/mocks/EmptyApplication.hpp @@ -17,61 +17,90 @@ public: Theme *getThemes() override { + assert( + false && + "EmptyApplication::getThemes was called without being initialized"); return nullptr; } Fonts *getFonts() override { + assert( + false && + "EmptyApplication::getFonts was called without being initialized"); return nullptr; } IEmotes *getEmotes() override { + assert( + false && + "EmptyApplication::getEmotes was called without being initialized"); return nullptr; } AccountController *getAccounts() override { + assert(false && "EmptyApplication::getAccounts was called without " + "being initialized"); return nullptr; } HotkeyController *getHotkeys() override { + assert(false && "EmptyApplication::getHotkeys was called without being " + "initialized"); return nullptr; } WindowManager *getWindows() override { + assert(false && "EmptyApplication::getWindows was called without being " + "initialized"); return nullptr; } Toasts *getToasts() override { + assert( + false && + "EmptyApplication::getToasts was called without being initialized"); return nullptr; } CrashHandler *getCrashHandler() override { + assert(false && "EmptyApplication::getCrashHandler was called without " + "being initialized"); return nullptr; } CommandController *getCommands() override { + assert(false && "EmptyApplication::getCommands was called without " + "being initialized"); return nullptr; } NotificationController *getNotifications() override { + assert(false && "EmptyApplication::getNotifications was called without " + "being initialized"); return nullptr; } HighlightController *getHighlights() override { + assert(false && "EmptyApplication::getHighlights was called without " + "being initialized"); return nullptr; } ITwitchIrcServer *getTwitch() override { + assert( + false && + "EmptyApplication::getTwitch was called without being initialized"); return nullptr; } @@ -83,11 +112,15 @@ public: ChatterinoBadges *getChatterinoBadges() override { + assert(false && "EmptyApplication::getChatterinoBadges was called " + "without being initialized"); return nullptr; } FfzBadges *getFfzBadges() override { + assert(false && "EmptyApplication::getFfzBadges was called without " + "being initialized"); return nullptr; } @@ -99,6 +132,8 @@ public: IUserDataController *getUserData() override { + assert(false && "EmptyApplication::getUserData was called without " + "being initialized"); return nullptr; } @@ -110,11 +145,15 @@ public: ITwitchLiveController *getTwitchLiveController() override { + assert(false && "EmptyApplication::getTwitchLiveController was called " + "without being initialized"); return nullptr; } ImageUploader *getImageUploader() override { + assert(false && "EmptyApplication::getImageUploader was called without " + "being initialized"); return nullptr; } diff --git a/src/singletons/Theme.cpp b/src/singletons/Theme.cpp index e8a110d53..45c4ff7f0 100644 --- a/src/singletons/Theme.cpp +++ b/src/singletons/Theme.cpp @@ -476,7 +476,7 @@ void Theme::normalizeColor(QColor &color) const Theme *getTheme() { - return getApp()->themes; + return getIApp()->getThemes(); } } // namespace chatterino diff --git a/src/widgets/BaseWidget.cpp b/src/widgets/BaseWidget.cpp index 9b818aa2f..18a64cefa 100644 --- a/src/widgets/BaseWidget.cpp +++ b/src/widgets/BaseWidget.cpp @@ -1,5 +1,6 @@ #include "widgets/BaseWidget.hpp" +#include "Application.hpp" #include "common/QLogging.hpp" #include "controllers/hotkeys/HotkeyController.hpp" #include "singletons/Theme.hpp" @@ -17,10 +18,8 @@ namespace chatterino { BaseWidget::BaseWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) + , theme(getIApp()->getThemes()) { - // REMOVED - this->theme = getTheme(); - this->signalHolder_.managedConnect(this->theme->updated, [this]() { this->themeChangedEvent(); diff --git a/src/widgets/Notebook.cpp b/src/widgets/Notebook.cpp index b85b50b45..2aa02e722 100644 --- a/src/widgets/Notebook.cpp +++ b/src/widgets/Notebook.cpp @@ -588,11 +588,13 @@ void Notebook::updateTabVisibility() void Notebook::updateTabVisibilityMenuAction() { - auto toggleSeq = getApp()->hotkeys->getDisplaySequence( + const auto *hotkeys = getIApp()->getHotkeys(); + + auto toggleSeq = hotkeys->getDisplaySequence( HotkeyCategory::Window, "setTabVisibility", {std::vector()}); if (toggleSeq.isEmpty()) { - toggleSeq = getApp()->hotkeys->getDisplaySequence( + toggleSeq = hotkeys->getDisplaySequence( HotkeyCategory::Window, "setTabVisibility", {{"toggle"}}); } @@ -601,12 +603,12 @@ void Notebook::updateTabVisibilityMenuAction() // show contextual shortcuts if (this->getShowTabs()) { - toggleSeq = getApp()->hotkeys->getDisplaySequence( + toggleSeq = hotkeys->getDisplaySequence( HotkeyCategory::Window, "setTabVisibility", {{"off"}}); } else if (!this->getShowTabs()) { - toggleSeq = getApp()->hotkeys->getDisplaySequence( + toggleSeq = hotkeys->getDisplaySequence( HotkeyCategory::Window, "setTabVisibility", {{"on"}}); } } diff --git a/src/widgets/helper/NotebookTab.cpp b/src/widgets/helper/NotebookTab.cpp index 1d7ac523d..7abd0f205 100644 --- a/src/widgets/helper/NotebookTab.cpp +++ b/src/widgets/helper/NotebookTab.cpp @@ -93,8 +93,8 @@ NotebookTab::NotebookTab(Notebook *notebook) [this]() { this->notebook_->removePage(this->page); }, - getApp()->hotkeys->getDisplaySequence(HotkeyCategory::Window, - "removeTab")); + getIApp()->getHotkeys()->getDisplaySequence(HotkeyCategory::Window, + "removeTab")); this->menu_.addAction( "Popup Tab", @@ -104,8 +104,8 @@ NotebookTab::NotebookTab(Notebook *notebook) container->popup(); } }, - getApp()->hotkeys->getDisplaySequence(HotkeyCategory::Window, "popup", - {{"window"}})); + getIApp()->getHotkeys()->getDisplaySequence(HotkeyCategory::Window, + "popup", {{"window"}})); highlightNewMessagesAction_ = new QAction("Mark Tab as Unread on New Messages", &this->menu_); @@ -196,7 +196,7 @@ int NotebookTab::normalTabWidth() float scale = this->scale(); int width; - QFontMetrics metrics = getApp()->fonts->getFontMetrics( + auto metrics = getIApp()->getFonts()->getFontMetrics( FontStyle::UiTabs, float(qreal(this->scale()) * deviceDpi(this))); if (this->hasXButton()) @@ -359,6 +359,11 @@ void NotebookTab::setHighlightState(HighlightState newHighlightStyle) } } +HighlightState NotebookTab::highlightState() const +{ + return this->highlightState_; +} + void NotebookTab::setHighlightsEnabled(const bool &newVal) { this->highlightNewMessagesAction_->setChecked(newVal); @@ -783,6 +788,11 @@ void NotebookTab::wheelEvent(QWheelEvent *event) } } +void NotebookTab::update() +{ + Button::update(); +} + QRect NotebookTab::getXRect() { QRect rect = this->rect(); diff --git a/src/widgets/helper/NotebookTab.hpp b/src/widgets/helper/NotebookTab.hpp index 6b8bf8f7b..a7c631f40 100644 --- a/src/widgets/helper/NotebookTab.hpp +++ b/src/widgets/helper/NotebookTab.hpp @@ -53,6 +53,8 @@ public: bool isLive() const; void setHighlightState(HighlightState style); + HighlightState highlightState() const; + void setHighlightsEnabled(const bool &newVal); bool hasHighlightsEnabled() const; @@ -84,6 +86,10 @@ protected: void mouseMoveEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; + /// This exists as an alias to its base classes update, and is virtual + /// to allow for mocking + virtual void update(); + private: void showRenameDialog(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 08cb44acf..3fc7f1308 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -36,6 +36,7 @@ set(test_SOURCES ${CMAKE_CURRENT_LIST_DIR}/src/XDGDesktopFile.cpp ${CMAKE_CURRENT_LIST_DIR}/src/XDGHelper.cpp ${CMAKE_CURRENT_LIST_DIR}/src/Selection.cpp + ${CMAKE_CURRENT_LIST_DIR}/src/NotebookTab.cpp # Add your new file above this line! ) diff --git a/tests/src/NotebookTab.cpp b/tests/src/NotebookTab.cpp new file mode 100644 index 000000000..3731d4022 --- /dev/null +++ b/tests/src/NotebookTab.cpp @@ -0,0 +1,123 @@ +#include "widgets/helper/NotebookTab.hpp" + +#include "common/Literals.hpp" +#include "controllers/hotkeys/HotkeyController.hpp" +#include "gmock/gmock.h" +#include "mocks/EmptyApplication.hpp" +#include "singletons/Fonts.hpp" +#include "singletons/Theme.hpp" +#include "widgets/Notebook.hpp" + +#include +#include +#include +#include + +using namespace chatterino; +using ::testing::Exactly; + +namespace { + +class MockApplication : mock::EmptyApplication +{ +public: + Theme *getThemes() override + { + return &this->theme; + } + + HotkeyController *getHotkeys() override + { + return &this->hotkeys; + } + + Fonts *getFonts() override + { + return &this->fonts; + } + + Theme theme; + HotkeyController hotkeys; + Fonts fonts; +}; + +class MockNotebookTab : public NotebookTab +{ +public: + explicit MockNotebookTab(Notebook *notebook) + : NotebookTab(notebook) + { + } + + MOCK_METHOD(void, update, (), (override)); +}; + +class NotebookTabFixture : public ::testing::Test +{ +protected: + NotebookTabFixture() + : notebook(nullptr) + , tab(&this->notebook) + { + } + + MockApplication mockApplication; + Notebook notebook; + MockNotebookTab tab; +}; + +} // namespace + +/// The highlight state must settable +TEST_F(NotebookTabFixture, SetHighlightState) +{ + EXPECT_CALL(this->tab, update).Times(Exactly(1)); + EXPECT_EQ(this->tab.highlightState(), HighlightState::None); + this->tab.setHighlightState(HighlightState::NewMessage); + EXPECT_EQ(this->tab.highlightState(), HighlightState::NewMessage); +} + +/// The highlight state must be able to "upgrade" from NewMessage to Highlighted +TEST_F(NotebookTabFixture, UpgradeHighlightState) +{ + EXPECT_CALL(this->tab, update).Times(Exactly(2)); + EXPECT_EQ(this->tab.highlightState(), HighlightState::None); + this->tab.setHighlightState(HighlightState::NewMessage); + EXPECT_EQ(this->tab.highlightState(), HighlightState::NewMessage); + this->tab.setHighlightState(HighlightState::Highlighted); + EXPECT_EQ(this->tab.highlightState(), HighlightState::Highlighted); +} + +/// The highlight state must stay as NewMessage when called twice +TEST_F(NotebookTabFixture, SameHighlightStateNewMessage) +{ + // XXX: This only updates the state once, so it should only update once + EXPECT_CALL(this->tab, update).Times(Exactly(2)); + EXPECT_EQ(this->tab.highlightState(), HighlightState::None); + this->tab.setHighlightState(HighlightState::NewMessage); + EXPECT_EQ(this->tab.highlightState(), HighlightState::NewMessage); + this->tab.setHighlightState(HighlightState::NewMessage); + EXPECT_EQ(this->tab.highlightState(), HighlightState::NewMessage); +} + +/// The highlight state must stay as Highlighted when called twice, and must not call update more than once +TEST_F(NotebookTabFixture, SameHighlightStateHighlighted) +{ + EXPECT_CALL(this->tab, update).Times(Exactly(1)); + EXPECT_EQ(this->tab.highlightState(), HighlightState::None); + this->tab.setHighlightState(HighlightState::Highlighted); + EXPECT_EQ(this->tab.highlightState(), HighlightState::Highlighted); + this->tab.setHighlightState(HighlightState::Highlighted); + EXPECT_EQ(this->tab.highlightState(), HighlightState::Highlighted); +} + +/// The highlight state must not downgrade from Highlighted to NewMessage +TEST_F(NotebookTabFixture, DontDowngradeHighlightState) +{ + EXPECT_CALL(this->tab, update).Times(Exactly(1)); + EXPECT_EQ(this->tab.highlightState(), HighlightState::None); + this->tab.setHighlightState(HighlightState::Highlighted); + EXPECT_EQ(this->tab.highlightState(), HighlightState::Highlighted); + this->tab.setHighlightState(HighlightState::NewMessage); + EXPECT_EQ(this->tab.highlightState(), HighlightState::Highlighted); +}