Files
OmaPhoto/tests/AcceptanceTests.cpp
T

176 lines
8.4 KiB
C++

#include "AcceptanceFixtures.h"
#include "Document/ColorPalette.h"
#include "Document/Guides.h"
#include "Document/ShapeTool.h"
// The acceptance run, part one: layers, selections, painting, arranging.
class AcceptanceTests : public QObject {
Q_OBJECT
private slots:
void initTestCase() { QStandardPaths::setTestModeEnabled(true); }
void layersFoldersMasksAndClipping();
void selections();
void paintingTools();
void transformGuidesAndGrid();
};
void AcceptanceTests::layersFoldersMasksAndClipping()
{
App app;
QVERIFY(scene(1200, 800).save(app.path("photo.png")));
app.session().createDocument(1200, 800);
app.importFile(app.path("photo.png"));
const QUuid photo = app.session().activeLayerID().value();
// A painted layer, clipped to the photo, multiplied at 60%.
app.action("newBlankLayer").trigger();
const QUuid paint = app.session().activeLayerID().value();
QVERIFY(paint != photo);
app.session().selectTool(NavigationTool::brush);
app.session().setBrushSettings({.diameter = 120});
app.session().setForegroundColor({1, 0.8, 0});
app.drag(QPointF(100, 400), QPointF(1100, 400));
app.settle();
QCOMPARE(pixel(app.session(), paint, 600, 400), QColor(255, 204, 0));
QCOMPARE(pixel(app.session(), paint, 600, 100).alpha(), 0);
app.action("clippingMask").trigger();
app.session().setLayerBlendMode(LayerBlendMode::multiply);
app.session().setLayerOpacity(0.6);
// A mask on the photo; both wrapped in a folder.
app.session().selectLayer(photo);
app.session().addLayerMask();
app.session().selectLayers({photo, paint}, paint);
app.action("groupLayers").trigger();
const std::vector<ImageLayer> &layers = app.session().document().value().layers;
QCOMPARE(int(layers.size()), 3);
const auto find = [&layers](const std::function<bool(const ImageLayer &)> &match) {
const auto found = std::find_if(layers.begin(), layers.end(), match);
if (found == layers.end())
throw std::runtime_error("no such layer");
return *found;
};
const ImageLayer folder = find([](const ImageLayer &layer) { return layer.isGroup; });
const ImageLayer photoLayer = find([photo](const ImageLayer &layer) { return layer.id == photo; });
const ImageLayer paintLayer = find([paint](const ImageLayer &layer) { return layer.id == paint; });
QVERIFY(photoLayer.mask);
QCOMPARE(photoLayer.parentID, std::optional<QUuid>(folder.id));
QCOMPARE(paintLayer.parentID, std::optional<QUuid>(folder.id));
QCOMPARE(paintLayer.maskSourceID, std::optional<QUuid>(photo));
QCOMPARE(paintLayer.blendMode, LayerBlendMode::multiply);
QCOMPARE(paintLayer.opacity, 0.6);
app.shot(QStringLiteral("01-layers"));
// Undo walks the steps back one at a time.
app.action("undo").trigger();
QCOMPARE(int(app.session().document().value().layers.size()), 2);
}
void AcceptanceTests::selections()
{
App app;
QVERIFY(scene(1200, 800).save(app.path("photo.png")));
app.session().createDocument(1200, 800);
app.importFile(app.path("photo.png"));
// A marquee dragged over the canvas.
app.session().selectTool(NavigationTool::marquee);
app.drag(QPointF(100, 100), QPointF(500, 300));
const QRectF marquee = app.session().selection().value().path.boundingRect();
QVERIFY2(qAbs(marquee.left() - 100) <= 2 && qAbs(marquee.right() - 500) <= 2, qPrintable(QString::number(marquee.width())));
// The wand takes the ground's flat green.
app.session().selectTool(NavigationTool::wand);
app.press(QPointF(600, 750));
app.release(QPointF(600, 750));
app.settle();
const QRectF ground = app.session().selection().value().path.boundingRect();
QVERIFY2(ground.top() >= 598 && ground.top() <= 602 && ground.width() >= 1198, qPrintable(QString::number(ground.top())));
// Select Subject finds the red disc.
app.action("subject").trigger();
app.settle();
const QRectF subject = app.session().selection().value().path.boundingRect();
QVERIFY2(qAbs(subject.center().x() - 600) < 12 && qAbs(subject.width() - 400) < 40, qPrintable(QString::number(subject.width())));
app.shot(QStringLiteral("02-selections"));
app.action("deselect").trigger();
QVERIFY(!app.session().selection());
}
void AcceptanceTests::paintingTools()
{
App app;
app.session().createDocument(1200, 800, true);
const QUuid base = app.session().activeLayerID().value();
// A gradient dragged across the empty layer.
app.session().selectTool(NavigationTool::gradient);
app.session().setForegroundColor({0.1, 0.2, 0.6});
app.drag(QPointF(0, 400), QPointF(1200, 400));
QTest::keyClick(&app.canvas(), Qt::Key_Return);
app.settle();
QCOMPARE(pixel(app.session(), base, 2, 400).blue(), 153);
// The default style fades the foreground to clear.
QVERIFY(qAbs(pixel(app.session(), base, 600, 400).alpha() - 128) <= 2);
// A rectangle, then a brush stroke on the base.
app.session().selectTool(NavigationTool::shape);
app.session().setShapeKind(ShapeKind::rectangle);
app.session().setForegroundColor({0.9, 0.3, 0.1});
app.drag(QPointF(200, 200), QPointF(500, 500));
const QUuid shape = app.session().activeLayerID().value();
QVERIFY(shape != base);
QVERIFY(app.session().activeLayer().value().liveShape());
app.session().selectLayer(base);
app.session().selectTool(NavigationTool::brush);
app.session().setBrushSettings({.diameter = 60, .hardness = 0.5});
app.session().setForegroundColor(PaletteColor::white());
app.drag(QPointF(700, 200), QPointF(1000, 600));
app.settle();
QCOMPARE(pixel(app.session(), base, 850, 400), QColor(Qt::white));
// Text typed on the canvas, its first word coloured.
app.session().selectTool(NavigationTool::type);
app.session().setForegroundColor(PaletteColor::black());
app.press(QPointF(600, 650));
app.release(QPointF(600, 650));
QTRY_VERIFY(app.session().textDraft());
QTest::keyClicks(&app.canvas(), QStringLiteral("Hello OmaPhoto"));
QCOMPARE(app.session().textDraft().value().style.content, QStringLiteral("Hello OmaPhoto"));
QTest::keyClick(&app.canvas(), Qt::Key_Home);
for (int letter = 0; letter < 5; ++letter)
QTest::keyClick(&app.canvas(), Qt::Key_Right, Qt::ShiftModifier);
app.session().setPaletteColor({0.9, 0.1, 0.1}, false);
app.shot(QStringLiteral("03-painting"));
QTest::keyClick(&app.canvas(), Qt::Key_Return, Qt::ControlModifier);
QTRY_VERIFY(!app.session().textDraft());
const LayerText text = app.session().activeLayer().value().liveText().value();
QCOMPARE(text.style.content, QStringLiteral("Hello OmaPhoto"));
// One run: the first word red over black text.
QCOMPARE(text.style.colorRuns.value(), (std::vector<LayerTextColorRun>{{.location = 0, .length = 5, .red = 0.9, .green = 0.1, .blue = 0.1}}));
QCOMPARE(int(app.session().document().value().layers.size()), 3);
}
void AcceptanceTests::transformGuidesAndGrid()
{
App app;
QVERIFY(scene(1200, 800).save(app.path("photo.png")));
app.session().createDocument(1600, 1000);
app.importFile(app.path("photo.png"));
// The Move tool drags the layer; Transform scales it.
app.session().selectTool(NavigationTool::move);
const QPointF origin = app.session().activeLayer().value().transform.origin;
app.drag(QPointF(800, 500), QPointF(900, 560));
// View points round to whole pixels on a reduced canvas.
const QPointF moved = app.session().activeLayer().value().transform.origin - origin;
QVERIFY2(qAbs(moved.x() - 100) <= 3 && qAbs(moved.y() - 60) <= 3, qPrintable(QString::number(moved.x())));
app.action("transformLayer").trigger();
QVERIFY(app.session().transformEdit());
LayerTransform half = app.session().activeLayer().value().transform;
half.size = half.size / 2;
app.session().previewTransform(half);
QTest::keyClick(&app.canvas(), Qt::Key_Return);
QCOMPARE(app.session().activeLayer().value().transform.size, QSizeF(600, 400));
// Rulers, a guide and the grid.
app.session().setShowsRulers(true);
app.session().setShowsGrid(true);
app.session().addGuide({.id = QUuid::createUuid(), .axis = CanvasGuide::Axis::horizontal, .position = 500});
QCOMPARE(int(app.session().document().value().guides.size()), 1);
app.session().selectTool(NavigationTool::move);
app.shot(QStringLiteral("04-transform-guides-grid"));
}
QTEST_MAIN(AcceptanceTests)
#include "AcceptanceTests.moc"