Files
OmaPhoto/tests/TextColorModelTests.cpp
T

160 lines
6.2 KiB
C++

#include "Document/EditorSession.h"
#include "IO/ProjectStore.h"
#include <QTemporaryDir>
#include <QtTest>
// Swift's colour runs in a text style: arithmetic and validity.
namespace {
const PaletteColor red{1, 0, 0};
const PaletteColor blue{0, 0, 1};
const PaletteColor green{0, 1, 0};
LayerTextColorRun run(qint64 location, qint64 length, const PaletteColor &color)
{
return {location, length, color.red, color.green, color.blue};
}
std::unique_ptr<EditorSession> makeSession()
{
auto session = std::make_unique<EditorSession>();
session->createDocument(800, 600, true);
session->selectTool(NavigationTool::type);
return session;
}
void edit(EditorSession &session, const QString &content, TextSpan selection)
{
TextDraft draft = session.textDraft().value();
draft.style.content = content;
draft.selection = selection;
session.setTextDraft(draft);
}
}
class TextColorModelTests : public QObject {
Q_OBJECT
private slots:
void colorAppliesToSelectionAndFollowsEdits();
void invalidColorRunsAreRejected();
void everyChannelOfARunIsChecked();
void runsOfOneColourJoinOnlyWhereTheyMeet();
};
void TextColorModelTests::colorAppliesToSelectionAndFollowsEdits()
{
LayerTextStyle style;
style.content = QStringLiteral("Hello world");
// The whole text takes every channel of its colour.
style.setColor(PaletteColor{0.25, 0.5, 0.75}, {0, 0});
QVERIFY(style.red == 0.25 && style.green == 0.5 && style.blue == 0.75 && !style.colorRuns);
style.setColor(PaletteColor::black(), {0, 11});
style.setColor(red, {6, 5});
QCOMPARE(style.colorRuns.value(), (std::vector{run(6, 5, red)}));
QVERIFY(style.color(5) == PaletteColor::black() && style.color(6) == red);
// Painting beside a run in its colour joins it.
style.setColor(red, {5, 1});
QVERIFY(style.colorRuns.value().size() == 1 && style.colorRuns.value().front().location == 5);
// Typed letters take the colour before them; deleted ones go.
style.replaceCharacters({11, 0}, 1);
style.content += u'!';
QVERIFY(style.isValid() && style.color(11) == red);
style.replaceCharacters({0, 2}, 0);
style.content.remove(0, 2);
QVERIFY(style.isValid() && style.colorRuns.value().front().location == 3 && style.colorRuns.value().front().length == 7);
// Typed at the start: the first letter's colour.
style.replaceCharacters({0, 0}, 2);
style.content.prepend(QStringLiteral("ab"));
QVERIFY(style.color(0) == PaletteColor::black() && style.colorRuns.value().front().location == 5);
// No selection, or all of it, recolours the whole text.
style.setColor(red, {4, 0});
QVERIFY(!style.colorRuns && style.red == 1);
style.setColor(blue, {0, style.content.size()});
QVERIFY(!style.colorRuns && style.blue == 1 && style.red == 0);
// Past the end, a span is clamped to the text.
style.setColor(red, {3, 900});
QCOMPARE(style.colorRuns.value(), (std::vector{run(3, style.content.size() - 3, red)}));
// Runs matching the base colour vanish.
style.setColor(blue, {3, 900});
QVERIFY(!style.colorRuns);
// Without runs, an edit keeps none.
style.replaceCharacters({0, 0}, 3);
QVERIFY(!style.colorRuns);
}
void TextColorModelTests::invalidColorRunsAreRejected()
{
LayerTextStyle style;
style.content = QStringLiteral("Text");
style.colorRuns = std::vector{run(2, 3, red)};
QVERIFY(!style.isValid());
style.colorRuns = std::vector{run(0, 2, red), run(1, 2, PaletteColor{0, 1, 0})};
QVERIFY(!style.isValid());
style.colorRuns = std::vector{LayerTextColorRun{0, 1, 2, 0, 0}};
QVERIFY(!style.isValid());
style.colorRuns = std::vector{run(1, 0, red)};
QVERIFY(!style.isValid());
style.colorRuns = std::vector{run(-1, 2, red)};
QVERIFY(!style.isValid());
style.colorRuns = std::vector{run(1, std::numeric_limits<qint64>::max(), red)};
QVERIFY(!style.isValid());
style.colorRuns = std::vector<LayerTextColorRun>();
QVERIFY(!style.isValid());
style.colorRuns = std::vector{run(0, 1, red), run(3, 1, blue)};
QVERIFY(style.isValid());
}
void TextColorModelTests::everyChannelOfARunIsChecked()
{
LayerTextStyle style;
style.content = QStringLiteral("Text");
for (int channel = 0; channel < 3; ++channel) {
for (const double value : {-0.01, 1.01, std::nan(""), std::numeric_limits<double>::infinity(), 0.0, 1.0}) {
LayerTextColorRun run{0, 1, 0.5, 0.5, 0.5};
(channel == 0 ? run.red : channel == 1 ? run.green : run.blue) = value;
style.colorRuns = std::vector{run};
const bool valid = value == 0 || value == 1;
QVERIFY2(style.isValid() == valid, qPrintable(QStringLiteral("channel %1 at %2").arg(channel).arg(value)));
}
}
// The store refuses an invalid run on saving too.
const auto session = makeSession();
session->beginText(QPointF(30, 40));
edit(*session, QStringLiteral("Text"), {0, 1});
session->setPaletteColor(red, false);
QVERIFY(session->finishText());
ProjectSnapshot snapshot = session->projectSnapshot().value();
snapshot.manifest.layers.back().text->colorRuns->front().blue = 2;
QTemporaryDir root;
try {
ProjectStore::save(snapshot, root.filePath(QStringLiteral("Bad.comp")));
QFAIL("an invalid run saved");
} catch (const ProjectError &error) {
QCOMPARE(error.kind, ProjectError::Kind::invalid);
}
}
void TextColorModelTests::runsOfOneColourJoinOnlyWhereTheyMeet()
{
LayerTextStyle style;
style.content = QStringLiteral("abcde");
style.setColor(green, {0, 1});
style.setColor(green, {4, 1});
// A base-coloured gap keeps two runs apart.
QCOMPARE(style.colorRuns.value(), (std::vector{run(0, 1, green), run(4, 1, green)}));
QVERIFY(style.color(2) == PaletteColor::black());
// Split by the base colour, then repainted: one run again.
style.setColor(green, {0, 5});
style.setColor(red, {0, 5});
style.setColor(green, {1, 3});
style.setColor(red, {2, 1});
QCOMPARE(style.colorRuns.value(), (std::vector{run(1, 1, green), run(3, 1, green)}));
style.setColor(green, {2, 1});
QCOMPARE(style.colorRuns.value(), (std::vector{run(1, 3, green)}));
}
QTEST_MAIN(TextColorModelTests)
#include "TextColorModelTests.moc"