222 lines
6.6 KiB
C++
222 lines
6.6 KiB
C++
#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;
|
||
|
||
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, 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");
|
||
}
|