Files
radiantwave/tests/test_scroller_streams.cpp
T

294 lines
8.9 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "scroller_streams.h"
#include "db/database_manager.h"
#include <gtest/gtest.h>
#include <string>
#include <unordered_map>
#include <vector>
namespace {
using radiantwave::db::AudioFileRecord;
using radiantwave::db::ProclamationRecord;
using radiantwave::scroller::ProclamationStream;
using radiantwave::scroller::StreamColor;
using radiantwave::scroller::buildStreams;
using radiantwave::scroller::kDefaultRepeatCount;
using radiantwave::scroller::kSelectedRepeatCount;
using radiantwave::scroller::minuteInScheduleBlock;
const std::string kDefaultId = "default";
std::vector<StreamColor> palette()
{
StreamColor c;
c.r = 10;
c.g = 20;
c.b = 30;
return {c};
}
} // namespace
TEST(ScrollerStreamsTest, InterleavesSelectedWithDefault)
{
ProclamationRecord p1;
p1.id = "one";
p1.title = "One";
p1.content = "hello world";
p1.selected = true;
p1.solfeggio_frequency_id = 2;
ProclamationRecord p2;
p2.id = "two";
p2.title = "Two";
p2.content = "foo bar baz";
p2.selected = true;
p2.solfeggio_frequency_id = 5;
ProclamationRecord def;
def.id = kDefaultId;
def.title = "Default";
def.content = "default text";
def.selected = true;
def.solfeggio_frequency_id = 5;
std::unordered_map<int, std::vector<StreamColor>> palettes;
palettes[2] = palette();
std::unordered_map<int, AudioFileRecord> audio;
AudioFileRecord af;
af.id = 1;
af.filename = "396Hz.wav";
af.solfeggio_frequency_id = 2;
audio[2] = af;
auto streams = buildStreams({p1, p2, def}, palettes, palette(), audio, kDefaultId);
// The first user-selected proclamation plays first, followed by the
// default, then the next selected, then the default again.
ASSERT_EQ(streams.size(), 4u);
EXPECT_EQ(streams[0].name, "One");
ASSERT_EQ(streams[0].words.size(), 2u);
EXPECT_EQ(streams[0].words[0], "hello");
EXPECT_EQ(streams[0].words[1], "world");
EXPECT_EQ(streams[0].repeat, kSelectedRepeatCount);
EXPECT_EQ(streams[0].solfeggio_frequency_id, 2);
EXPECT_EQ(streams[0].audioFilename, "396Hz.wav");
EXPECT_EQ(streams[0].palette.size(), 1u);
EXPECT_EQ(streams[1].name, "Default");
EXPECT_EQ(streams[1].repeat, kDefaultRepeatCount);
EXPECT_EQ(streams[1].solfeggio_frequency_id, 5);
EXPECT_EQ(streams[1].audioFilename, "OriginalAudio.wav"); // no audio for freq 5
EXPECT_EQ(streams[1].palette.size(), 1u); // falls back to 528 palette
EXPECT_EQ(streams[2].name, "Two");
EXPECT_EQ(streams[2].repeat, kSelectedRepeatCount);
EXPECT_EQ(streams[2].palette.size(), 1u); // falls back to 528 palette
EXPECT_EQ(streams[3].name, "Default");
EXPECT_EQ(streams[3].repeat, kDefaultRepeatCount);
EXPECT_EQ(streams[3].audioFilename, "OriginalAudio.wav");
}
TEST(ScrollerStreamsTest, InterleavedListCyclesBackToFirstSelected)
{
// Three selected proclamations: the built list must be
// [S1, Default, S2, Default, S3, Default]. ScrollerPage advances through
// this list with a modulo, so once the trailing default finishes the cycle
// restarts at the FIRST selected proclamation — the "always on" default
// must never accumulate or take over the queue.
ProclamationRecord b;
b.id = "B";
b.title = "B";
b.content = "bbbb";
b.selected = true;
b.solfeggio_frequency_id = 2;
ProclamationRecord c;
c.id = "C";
c.title = "C";
c.content = "cccc";
c.selected = true;
c.solfeggio_frequency_id = 3;
ProclamationRecord d;
d.id = "D";
d.title = "D";
d.content = "dddd";
d.selected = true;
d.solfeggio_frequency_id = 4;
ProclamationRecord def;
def.id = kDefaultId;
def.title = "Default";
def.content = "dddddddddd";
def.selected = true;
def.solfeggio_frequency_id = 5;
auto streams = buildStreams({b, def, c, d}, {}, palette(), {}, kDefaultId);
ASSERT_EQ(streams.size(), 6u);
EXPECT_EQ(streams[0].name, "B");
EXPECT_EQ(streams[1].name, "Default");
EXPECT_EQ(streams[2].name, "C");
EXPECT_EQ(streams[3].name, "Default");
EXPECT_EQ(streams[4].name, "D");
EXPECT_EQ(streams[5].name, "Default");
// Every default copy is independent playback state (fresh counters), so a
// copy finishing its passes advances to the next stream rather than
// restarting "the default" forever.
for (size_t i = 1; i < streams.size(); i += 2) {
EXPECT_EQ(streams[i].name, "Default");
EXPECT_EQ(streams[i].completed, 0) << "default copy " << i;
EXPECT_EQ(streams[i].wordsPlaced, 0) << "default copy " << i;
EXPECT_EQ(streams[i].repeat, kDefaultRepeatCount) << "default copy " << i;
}
}
TEST(ScrollerStreamsTest, DefaultOnlyWhenNothingSelected)
{
ProclamationRecord p1;
p1.id = "one";
p1.title = "One";
p1.content = "x";
p1.selected = false;
ProclamationRecord def;
def.id = kDefaultId;
def.title = "Default";
def.content = "y";
def.selected = true;
auto streams = buildStreams({p1, def}, {}, palette(), {}, kDefaultId);
ASSERT_EQ(streams.size(), 1u);
EXPECT_EQ(streams[0].name, "Default");
EXPECT_EQ(streams[0].repeat, kDefaultRepeatCount);
}
TEST(ScrollerStreamsTest, MissingDefaultStillStreamsSelected)
{
ProclamationRecord p1;
p1.id = "one";
p1.title = "One";
p1.content = "x";
p1.selected = true;
auto streams = buildStreams({p1}, {}, palette(), {}, kDefaultId);
ASSERT_EQ(streams.size(), 1u);
EXPECT_EQ(streams[0].name, "One");
EXPECT_EQ(streams[0].repeat, kSelectedRepeatCount);
}
TEST(ScrollerStreamsTest, MinuteInScheduleBlockDaytime)
{
// 06:00 (360) to 08:00 (480), half-open.
EXPECT_FALSE(minuteInScheduleBlock(359, 360, 480, false));
EXPECT_TRUE(minuteInScheduleBlock(360, 360, 480, false));
EXPECT_TRUE(minuteInScheduleBlock(479, 360, 480, false));
EXPECT_FALSE(minuteInScheduleBlock(480, 360, 480, false));
}
TEST(ScrollerStreamsTest, MinuteInScheduleBlockOvernight)
{
// 21:00 (1260) to 09:00 (540) wraps past midnight.
EXPECT_TRUE(minuteInScheduleBlock(1260, 1260, 540, true));
EXPECT_TRUE(minuteInScheduleBlock(1439, 1260, 540, true));
EXPECT_TRUE(minuteInScheduleBlock(0, 1260, 540, true));
EXPECT_TRUE(minuteInScheduleBlock(539, 1260, 540, true));
EXPECT_FALSE(minuteInScheduleBlock(540, 1260, 540, true));
EXPECT_FALSE(minuteInScheduleBlock(1259, 1260, 540, true));
}
TEST(ScrollerStreamsTest, ScheduledIdsDriveOrderRegardlessOfSelection)
{
ProclamationRecord p1;
p1.id = "one";
p1.title = "One";
p1.content = "x";
p1.selected = false;
ProclamationRecord p2;
p2.id = "two";
p2.title = "Two";
p2.content = "y";
p2.selected = false;
ProclamationRecord def;
def.id = kDefaultId;
def.title = "Default";
def.content = "z";
// Explicit order two -> one, interleaved with the default. Unknown ids are
// skipped; the `selected` flag is ignored.
std::vector<std::string> scheduled = {"two", "missing", "one"};
auto streams = buildStreams({p1, p2, def}, {}, palette(), {}, kDefaultId, &scheduled);
ASSERT_EQ(streams.size(), 4u);
EXPECT_EQ(streams[0].name, "Two");
EXPECT_EQ(streams[1].name, "Default");
EXPECT_EQ(streams[2].name, "One");
EXPECT_EQ(streams[3].name, "Default");
}
TEST(ScrollerStreamsTest, EmptyScheduleFallsBackToDefault)
{
ProclamationRecord p1;
p1.id = "one";
p1.title = "One";
p1.content = "x";
p1.selected = true;
ProclamationRecord def;
def.id = kDefaultId;
def.title = "Default";
def.content = "y";
std::vector<std::string> scheduled; // empty block
auto streams = buildStreams({p1, def}, {}, palette(), {}, kDefaultId, &scheduled);
ASSERT_EQ(streams.size(), 1u);
EXPECT_EQ(streams[0].name, "Default");
}
TEST(ScrollerStreamsTest, SplitsGluedSentencePunctuation)
{
ProclamationRecord p1;
p1.id = "one";
p1.title = "One";
p1.content = "state?Create NOW?KING field’s fine (Acts 17:27)I example.com";
p1.selected = true;
auto streams = buildStreams({p1}, {}, palette(), {}, kDefaultId);
ASSERT_EQ(streams.size(), 1u);
const auto& words = streams[0].words;
// "?" glued to an uppercase word splits after the punctuation, keeping the
// sentence separator attached to the preceding word.
EXPECT_EQ(words[0], "state?");
EXPECT_EQ(words[1], "Create");
EXPECT_EQ(words[2], "NOW?");
EXPECT_EQ(words[3], "KING");
// Curly apostrophes are not sentence punctuation and stay inside the word.
EXPECT_EQ(words[4], "field’s");
EXPECT_EQ(words[5], "fine");
// Closing paren followed by an uppercase letter splits so the verse
// reference and the next word are separate tokens.
EXPECT_EQ(words[6], "(Acts");
EXPECT_EQ(words[7], "17:27)");
EXPECT_EQ(words[8], "I");
// A lowercase continuation after a dot is not a new sentence, so this
// stays intact.
EXPECT_EQ(words[9], "example.com");
}