mirror of
https://github.com/Chatterino/chatterino2.git
synced 2026-08-24 10:04:53 -05:00
Show participants in a shared chat session in the split header (#6948)
Reviewed-by: Nerixyz <nerixdev@outlook.de> Reviewed-by: pajlada <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -534,6 +534,13 @@ public:
|
||||
(FailureCallback<HelixUnpinMessageError, QString>)failureCallback),
|
||||
(override));
|
||||
|
||||
MOCK_METHOD(void, getSharedChatSession,
|
||||
(QString broadcasterID,
|
||||
ResultCallback<HelixSharedChatSession> successCallback,
|
||||
(FailureCallback<HelixGetSharedChatSessionError, QString>
|
||||
failureCallback)),
|
||||
(override));
|
||||
|
||||
MOCK_METHOD(void, update, (QString clientId, QString oauthToken),
|
||||
(override));
|
||||
|
||||
|
||||
@@ -1224,6 +1224,11 @@ void IrcMessageHandler::addMessage(Communi::IrcMessage *message,
|
||||
MessageContext::Original);
|
||||
}
|
||||
|
||||
if (msg->flags.has(MessageFlag::SharedMessage))
|
||||
{
|
||||
chan->probeSharedChatSession();
|
||||
}
|
||||
|
||||
sink.addMessage(msg, MessageContext::Original);
|
||||
chan->addRecentChatter(msg->displayName);
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ TwitchChannel::TwitchChannel(const QString &name)
|
||||
, bttvEmotes_(std::make_shared<EmoteMap>())
|
||||
, ffzEmotes_(std::make_shared<EmoteMap>())
|
||||
, seventvEmotes_(std::make_shared<EmoteMap>())
|
||||
, nextSharedChatSessionProbe_(QDateTime::currentDateTime())
|
||||
{
|
||||
qCDebug(chatterinoTwitch) << "[TwitchChannel" << name << "] Opened";
|
||||
|
||||
@@ -179,6 +180,11 @@ TwitchChannel::TwitchChannel(const QString &name)
|
||||
});
|
||||
this->threadClearTimer_.start(5 * 60 * 1000);
|
||||
|
||||
QObject::connect(&this->nextSharedChatSessionUpdateTimer_, &QTimer::timeout,
|
||||
&this->lifetimeGuard_, [this] {
|
||||
this->refreshSharedChatSessionState();
|
||||
});
|
||||
|
||||
this->signalHolder_.managedConnect(
|
||||
getApp()->getAccounts()->twitch.emotesReloaded,
|
||||
[this](auto *caller, const auto &result) {
|
||||
@@ -2528,6 +2534,152 @@ bool TwitchChannel::isLoadingRecentMessages() const
|
||||
return this->loadingRecentMessages_.test();
|
||||
}
|
||||
|
||||
const QStringList &TwitchChannel::getSharedChatSessionParticipants() const
|
||||
{
|
||||
return this->sharedChatSessionParticipants_;
|
||||
}
|
||||
|
||||
void TwitchChannel::probeSharedChatSession()
|
||||
{
|
||||
auto now = QDateTime::currentDateTime();
|
||||
|
||||
if (!this->nextSharedChatSessionUpdateTimer_.isActive() &&
|
||||
now >= this->nextSharedChatSessionProbe_)
|
||||
{
|
||||
this->nextSharedChatSessionProbe_ = now.addSecs(30);
|
||||
this->refreshSharedChatSessionState();
|
||||
}
|
||||
}
|
||||
|
||||
void TwitchChannel::refreshSharedChatSessionState()
|
||||
{
|
||||
getHelix()->getSharedChatSession(
|
||||
this->roomId(),
|
||||
[this,
|
||||
weak = this->weakFromThis()](const HelixSharedChatSession &session) {
|
||||
const auto self = weak.lock();
|
||||
if (!self)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto intervalSecs = std::clamp(
|
||||
getSettings()->sharedChatSessionRefreshInterval.getValue(), 5,
|
||||
999);
|
||||
this->nextSharedChatSessionUpdateTimer_.setInterval(intervalSecs *
|
||||
1000);
|
||||
|
||||
if (session.participantIds.empty())
|
||||
{
|
||||
// Allow immediate re-probe
|
||||
this->nextSharedChatSessionProbe_ =
|
||||
QDateTime::currentDateTime();
|
||||
this->nextSharedChatSessionUpdateTimer_.stop();
|
||||
|
||||
this->sharedChatSessionParticipants_.clear();
|
||||
this->sharedChatSessionParticipantIds_.clear();
|
||||
|
||||
this->sharedChatStatusChanged.invoke({});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool participantsDiffer =
|
||||
session.participantIds.size() - 1 !=
|
||||
this->sharedChatSessionParticipantIds_.size();
|
||||
if (!participantsDiffer)
|
||||
{
|
||||
for (const auto &broadcasterID : session.participantIds)
|
||||
{
|
||||
if (this->roomId() == broadcasterID)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this->sharedChatSessionParticipantIds_.contains(
|
||||
broadcasterID))
|
||||
{
|
||||
participantsDiffer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!participantsDiffer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
getHelix()->fetchUsers(
|
||||
session.participantIds, {},
|
||||
[this, weak = this->weakFromThis()](const auto &users) {
|
||||
const auto self = weak.lock();
|
||||
if (!self)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->sharedChatSessionParticipants_.clear();
|
||||
this->sharedChatSessionParticipantIds_.clear();
|
||||
|
||||
for (const auto &user : users)
|
||||
{
|
||||
if (user.id != this->roomId())
|
||||
{
|
||||
this->sharedChatSessionParticipantIds_.insert(
|
||||
user.id);
|
||||
this->sharedChatSessionParticipants_.push_back(
|
||||
user.displayName);
|
||||
}
|
||||
}
|
||||
|
||||
this->nextSharedChatSessionUpdateTimer_.start();
|
||||
|
||||
this->sharedChatStatusChanged.invoke(
|
||||
this->sharedChatSessionParticipants_);
|
||||
},
|
||||
[] {
|
||||
qCWarning(chatterinoTwitch) << "Failed to get user info";
|
||||
});
|
||||
},
|
||||
[](HelixGetSharedChatSessionError error, const QString &message) {
|
||||
QString errorMessage = "Failed to get shared chat session state: ";
|
||||
|
||||
switch (error)
|
||||
{
|
||||
case HelixGetSharedChatSessionError::InvalidBroadcasterId: {
|
||||
errorMessage += "Invalid broadcaster ID";
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixGetSharedChatSessionError::UserMissingScope: {
|
||||
errorMessage +=
|
||||
"Missing required scope. Re-login with your "
|
||||
"account and try again.";
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixGetSharedChatSessionError::UserNotAuthorized: {
|
||||
errorMessage +=
|
||||
"you don't have permission to perform that action.";
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixGetSharedChatSessionError::Unknown: {
|
||||
errorMessage += "Unknown error";
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixGetSharedChatSessionError::Forwarded: {
|
||||
errorMessage += message;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
qCWarning(chatterinoTwitch) << errorMessage;
|
||||
});
|
||||
}
|
||||
|
||||
void TwitchChannel::refreshPinnedMessage()
|
||||
{
|
||||
auto currentAccount = getApp()->getAccounts()->twitch.getCurrent();
|
||||
|
||||
@@ -356,6 +356,8 @@ public:
|
||||
|
||||
pajlada::Signals::Signal<const QString &> sendWaitUpdate;
|
||||
|
||||
pajlada::Signals::Signal<const QStringList &> sharedChatStatusChanged;
|
||||
|
||||
// Channel point rewards
|
||||
void addQueuedRedemption(const QString &rewardId,
|
||||
const QString &originalContent,
|
||||
@@ -401,6 +403,7 @@ public:
|
||||
|
||||
bool isLoadingRecentMessages() const;
|
||||
|
||||
const QStringList &getSharedChatSessionParticipants() const;
|
||||
// Pinned message
|
||||
/**
|
||||
* Fetches the currently pinned message for this channel via the Helix API.
|
||||
@@ -459,6 +462,9 @@ private:
|
||||
/// This should only happen once per channel, whenever the ID goes from unset to set
|
||||
void roomIdChanged();
|
||||
|
||||
void probeSharedChatSession();
|
||||
void refreshSharedChatSessionState();
|
||||
|
||||
/** Joins (subscribes to) a Twitch channel for updates on BTTV. */
|
||||
void joinBttvChannel() const;
|
||||
|
||||
@@ -616,6 +622,35 @@ private:
|
||||
/** A list of the emotes listed in the lat live emote update message. */
|
||||
std::vector<QString> lastLiveUpdateEmoteNames_;
|
||||
|
||||
/**
|
||||
* List of display names of broadcasters participating in a
|
||||
* shared chat session on this channel. The list does not include
|
||||
* the broadcaster who owns the channel.
|
||||
* This list is passed to the UI for display.
|
||||
*/
|
||||
QStringList sharedChatSessionParticipants_;
|
||||
|
||||
/**
|
||||
* Set of broadcasterIDs of broadcasters participating in a
|
||||
* shared chat session on this channel. The set does not include
|
||||
* the broadcaster who owns the channel.
|
||||
* This set is used to quickly determine if the participants have
|
||||
* changed since the last query of the shared chat session state.
|
||||
*/
|
||||
QSet<QString> sharedChatSessionParticipantIds_;
|
||||
|
||||
/**
|
||||
* Timer scheduling the next check of the shared chat session state.
|
||||
*/
|
||||
QTimer nextSharedChatSessionUpdateTimer_;
|
||||
|
||||
/**
|
||||
* Time when the next probe of shared chat session state triggered
|
||||
* by reception of a shared chat message is allowed.
|
||||
* Used to rate-limit Twitch API queries.
|
||||
*/
|
||||
QDateTime nextSharedChatSessionProbe_;
|
||||
|
||||
pajlada::Signals::SignalHolder signalHolder_;
|
||||
|
||||
eventsub::SubscriptionHandle eventSubChannelModerateHandle;
|
||||
|
||||
@@ -3753,6 +3753,82 @@ void Helix::createEventSubSubscription(
|
||||
.execute();
|
||||
}
|
||||
|
||||
void Helix::getSharedChatSession(
|
||||
QString broadcasterID,
|
||||
ResultCallback<HelixSharedChatSession> successCallback,
|
||||
FailureCallback<HelixGetSharedChatSessionError, QString> failureCallback)
|
||||
{
|
||||
using Error = HelixGetSharedChatSessionError;
|
||||
|
||||
this->makeGet("shared_chat/session", {{u"broadcaster_id"_s, broadcasterID}})
|
||||
.onSuccess([successCallback](const NetworkResult &result) {
|
||||
if (result.status() != 200)
|
||||
{
|
||||
qCWarning(chatterinoTwitch)
|
||||
<< "Success result for getting shared chat session was "
|
||||
<< result.formatError() << " but we expected it to be 200";
|
||||
}
|
||||
|
||||
const auto response = result.parseJson();
|
||||
const auto session = response["data"_L1].toArray().at(0);
|
||||
|
||||
successCallback(HelixSharedChatSession(session.toObject()));
|
||||
})
|
||||
.onError([failureCallback](const NetworkResult &result) -> void {
|
||||
if (!result.status())
|
||||
{
|
||||
failureCallback(Error::Unknown, result.formatError());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto obj = result.parseJson();
|
||||
auto message = obj["message"].toString();
|
||||
|
||||
switch (*result.status())
|
||||
{
|
||||
case 400: {
|
||||
failureCallback(Error::InvalidBroadcasterId, message);
|
||||
}
|
||||
break;
|
||||
|
||||
case 401: {
|
||||
if (message.startsWith("Missing scope",
|
||||
Qt::CaseInsensitive))
|
||||
{
|
||||
failureCallback(Error::UserMissingScope, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
failureCallback(Error::UserNotAuthorized, message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 500: {
|
||||
if (message.isEmpty())
|
||||
{
|
||||
failureCallback(Error::Unknown,
|
||||
"Twitch internal server error");
|
||||
}
|
||||
else
|
||||
{
|
||||
failureCallback(Error::Unknown, message);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default: {
|
||||
qCWarning(chatterinoTwitch)
|
||||
<< "Helix get shared chat session, unhandled error "
|
||||
"data:"
|
||||
<< result.formatError() << result.getData() << obj;
|
||||
failureCallback(Error::Forwarded, message);
|
||||
}
|
||||
}
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
QDebug &operator<<(QDebug &dbg,
|
||||
const HelixCreateEventSubSubscriptionResponse &data)
|
||||
{
|
||||
|
||||
@@ -608,6 +608,21 @@ struct HelixPredictions {
|
||||
}
|
||||
};
|
||||
|
||||
struct HelixSharedChatSession {
|
||||
QStringList participantIds;
|
||||
|
||||
explicit HelixSharedChatSession(const QJsonObject &jsonObject)
|
||||
{
|
||||
const auto &participants = jsonObject.value("participants").toArray();
|
||||
for (const auto p : participants)
|
||||
{
|
||||
const auto broadcasterId =
|
||||
p.toObject().value("broadcaster_id").toString();
|
||||
this->participantIds.push_back(broadcasterId);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct HelixStartCommercialResponse {
|
||||
// Length of the triggered commercial
|
||||
int length;
|
||||
@@ -1182,6 +1197,13 @@ public:
|
||||
const QString &messageID, ResultCallback<> successCallback,
|
||||
FailureCallback<HelixUnpinMessageError, QString> failureCallback) = 0;
|
||||
|
||||
// https://dev.twitch.tv/docs/api/reference/#get-shared-chat-session
|
||||
virtual void getSharedChatSession(
|
||||
QString broadcasterID,
|
||||
ResultCallback<HelixSharedChatSession> successCallback,
|
||||
FailureCallback<HelixGetSharedChatSessionError, QString>
|
||||
failureCallback) = 0;
|
||||
|
||||
virtual void update(QString clientId, QString oauthToken) = 0;
|
||||
|
||||
protected:
|
||||
@@ -1611,6 +1633,13 @@ public:
|
||||
const QString &messageID, ResultCallback<> successCallback,
|
||||
FailureCallback<HelixUnpinMessageError, QString> failureCallback) final;
|
||||
|
||||
// https://dev.twitch.tv/docs/api/reference/#get-shared-chat-session
|
||||
void getSharedChatSession(
|
||||
QString broadcasterID,
|
||||
ResultCallback<HelixSharedChatSession> successCallback,
|
||||
FailureCallback<HelixGetSharedChatSessionError, QString>
|
||||
failureCallback) final;
|
||||
|
||||
void update(QString clientId, QString oauthToken) final;
|
||||
|
||||
static void initialize();
|
||||
|
||||
@@ -327,4 +327,14 @@ enum class HelixUnpinMessageError : std::uint8_t {
|
||||
Forwarded,
|
||||
};
|
||||
|
||||
enum class HelixGetSharedChatSessionError : std::uint8_t {
|
||||
Unknown,
|
||||
InvalidBroadcasterId,
|
||||
UserMissingScope,
|
||||
UserNotAuthorized,
|
||||
|
||||
// The error message is forwarded directly from the Twitch API
|
||||
Forwarded,
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -411,6 +411,9 @@ public:
|
||||
false,
|
||||
};
|
||||
|
||||
IntSetting sharedChatSessionRefreshInterval = {
|
||||
"/behaviour/sharedChatSessionRefreshInterval", 60};
|
||||
|
||||
/// Emotes
|
||||
BoolSetting scaleEmotesByLineHeight = {"/emotes/scaleEmotesByLineHeight",
|
||||
false};
|
||||
|
||||
@@ -1632,6 +1632,14 @@ void GeneralPage::initLayout(GeneralPageView &layout)
|
||||
"double-clicked")
|
||||
->addTo(layout);
|
||||
|
||||
SettingWidget::intInput(
|
||||
"Shared chat session status refresh interval",
|
||||
s.sharedChatSessionRefreshInterval,
|
||||
{.min = 5, .max = 999, .singleStep = 1, .suffix = "s"})
|
||||
->setTooltip("How often Chatterino polls the Twitch API for the "
|
||||
"shared chat session status.")
|
||||
->addTo(layout);
|
||||
|
||||
layout.addStretch();
|
||||
|
||||
// invisible element for width
|
||||
|
||||
@@ -864,6 +864,10 @@ void Split::setChannel(IndirectChannel newChannel)
|
||||
this->getInput().setSendWaitStatus(text);
|
||||
});
|
||||
|
||||
this->channelSignalHolder_.managedConnect(
|
||||
tc->sharedChatStatusChanged, [this](const QStringList &) {
|
||||
this->header_->updateChannelText();
|
||||
});
|
||||
this->pinnedBanner_->setChannel(tc);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -185,7 +185,8 @@ auto formatOfflineTooltip(const TwitchChannel::StreamStatus &s)
|
||||
.arg(s.title.toHtmlEscaped());
|
||||
}
|
||||
|
||||
auto formatTitle(const TwitchChannel::StreamStatus &s, Settings &settings)
|
||||
auto formatTitle(const TwitchChannel::StreamStatus &s, Settings &settings,
|
||||
const QStringList &sharedChatParticipants)
|
||||
{
|
||||
auto title = QString();
|
||||
|
||||
@@ -200,7 +201,14 @@ auto formatTitle(const TwitchChannel::StreamStatus &s, Settings &settings)
|
||||
}
|
||||
else
|
||||
{
|
||||
title += " (live)";
|
||||
if (sharedChatParticipants.isEmpty())
|
||||
{
|
||||
title += " (live)";
|
||||
}
|
||||
else
|
||||
{
|
||||
title += " (live with " + sharedChatParticipants.join(", ") + ")";
|
||||
}
|
||||
}
|
||||
|
||||
// description
|
||||
@@ -978,7 +986,10 @@ void SplitHeader::updateChannelText()
|
||||
this->lastThumbnail_.restart();
|
||||
}
|
||||
this->tooltipText_ = formatTooltip(*streamStatus, this->thumbnail_);
|
||||
title += formatTitle(*streamStatus, *getSettings());
|
||||
|
||||
title +=
|
||||
formatTitle(*streamStatus, *getSettings(),
|
||||
twitchChannel->getSharedChatSessionParticipants());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -274,5 +274,10 @@
|
||||
"userID": "",
|
||||
"usernameColor": "#ff000000"
|
||||
}
|
||||
]
|
||||
],
|
||||
"params": {
|
||||
"helixExpectations": {
|
||||
"getSharedChatSession": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,5 +307,10 @@
|
||||
"userID": "129546453",
|
||||
"usernameColor": "#ffff0000"
|
||||
}
|
||||
]
|
||||
],
|
||||
"params": {
|
||||
"helixExpectations": {
|
||||
"getSharedChatSession": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,5 +200,10 @@
|
||||
"userID": "106940612",
|
||||
"usernameColor": "#ff00ff00"
|
||||
}
|
||||
]
|
||||
],
|
||||
"params": {
|
||||
"helixExpectations": {
|
||||
"getSharedChatSession": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,5 +178,10 @@
|
||||
"userID": "100229878",
|
||||
"usernameColor": "#ff00ff7f"
|
||||
}
|
||||
]
|
||||
],
|
||||
"params": {
|
||||
"helixExpectations": {
|
||||
"getSharedChatSession": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,5 +186,10 @@
|
||||
"userID": "100229878",
|
||||
"usernameColor": "#ff00ff7f"
|
||||
}
|
||||
]
|
||||
],
|
||||
"params": {
|
||||
"helixExpectations": {
|
||||
"getSharedChatSession": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,5 +200,10 @@
|
||||
"userID": "106940612",
|
||||
"usernameColor": "#ff00ff00"
|
||||
}
|
||||
]
|
||||
],
|
||||
"params": {
|
||||
"helixExpectations": {
|
||||
"getSharedChatSession": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,5 +189,10 @@
|
||||
"userID": "100229878",
|
||||
"usernameColor": "#ff00ff7f"
|
||||
}
|
||||
]
|
||||
],
|
||||
"params": {
|
||||
"helixExpectations": {
|
||||
"getSharedChatSession": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,5 +187,10 @@
|
||||
"userID": "100229878",
|
||||
"usernameColor": "#ff00ff7f"
|
||||
}
|
||||
]
|
||||
],
|
||||
"params": {
|
||||
"helixExpectations": {
|
||||
"getSharedChatSession": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "mocks/ChatterinoBadges.hpp"
|
||||
#include "mocks/DisabledStreamerMode.hpp"
|
||||
#include "mocks/EmoteController.hpp"
|
||||
#include "mocks/Helix.hpp"
|
||||
#include "mocks/LinkResolver.hpp"
|
||||
#include "mocks/Logging.hpp"
|
||||
#include "mocks/TwitchIrcServer.hpp"
|
||||
@@ -160,6 +161,7 @@ public:
|
||||
mock::EmptyLogging logging;
|
||||
AccountController accounts;
|
||||
mock::EmoteController emotes;
|
||||
mock::Helix helix;
|
||||
mock::UserDataController userData;
|
||||
mock::MockTwitchIrcServer twitch;
|
||||
mock::ChatterinoBadges chatterinoBadges;
|
||||
@@ -561,6 +563,21 @@ public:
|
||||
|
||||
this->mockApplication->twitch.mockChannels.emplace(
|
||||
"twitchdev", this->twitchdevChannel);
|
||||
|
||||
const auto helixExpectations =
|
||||
this->snapshot->param("helixExpectations").toObject();
|
||||
if (!helixExpectations.isEmpty())
|
||||
{
|
||||
initializeHelix(&this->mockHelix);
|
||||
|
||||
int nCalls =
|
||||
helixExpectations.value("getSharedChatSession").toInt();
|
||||
if (nCalls > 0)
|
||||
{
|
||||
EXPECT_CALL(this->mockHelix, getSharedChatSession)
|
||||
.Times(nCalls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
@@ -573,6 +590,7 @@ public:
|
||||
std::shared_ptr<TwitchChannel> twitchdevChannel;
|
||||
std::unique_ptr<MockApplication> mockApplication;
|
||||
std::unique_ptr<testlib::Snapshot> snapshot;
|
||||
testing::StrictMock<mock::Helix> mockHelix;
|
||||
};
|
||||
|
||||
/// This tests the process of parsing IRC messages and emitting `MessagePtr`s.
|
||||
@@ -593,6 +611,9 @@ public:
|
||||
/// - `findAllUsernames`: A boolean controlling the equally named setting
|
||||
/// (default: false)
|
||||
/// - `nAdditional`: Include n additional built messages (from `prevMessages`)
|
||||
/// - `helixExpectations`: An object with names of Helix API methods that will
|
||||
/// be called during the test and the expected call count. Name of the method
|
||||
/// is the key and the number of calls its value.
|
||||
TEST_P(TestIrcMessageHandlerP, Run)
|
||||
{
|
||||
auto channel = makeMockTwitchChannel(u"pajlada"_s, *snapshot);
|
||||
|
||||
Reference in New Issue
Block a user