Files
OmaPhoto/tests/SessionGateTests.cpp
T
雷电芽衣andClaude c864304695
Release / deb (push) Canceled after 0s
Release / rpm (push) Canceled after 0s
Release / arch (push) Canceled after 0s
Release / publish (push) Canceled after 0s
OmaPhoto 1.0.0: the Linux port of Compositor
A layered image editor for Linux and Omarchy, ported from Compositor
(github.com/robbietilton/Compositor), whose Swift source is its
specification. It reads and writes Compositor's .comp projects.

Packages for Ubuntu, Fedora and Arch come from the release workflow;
scripts/install/ holds one install script per distribution family,
and flake.nix serves NixOS.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-26 00:44:52 -04:00

178 lines
6.3 KiB
C++

#include "Document/EditorSession.h"
#include <QSignalSpy>
#include <QtTest>
// Who may start a project operation, and when waiters run.
class SessionGateTests : public QObject {
Q_OBJECT
private slots:
void fileRequestsWaitForEveryGate_data();
void fileRequestsWaitForEveryGate();
void aWaiterLooksAgainWhenItsTurnComes();
void aNewCanvasFreesWaitingFileRequests();
void waitersEndWithTheirSession();
void theBusyIndicatorWaitsAQuarterSecond();
};
void SessionGateTests::fileRequestsWaitForEveryGate_data()
{
QTest::addColumn<int>("gate");
QTest::newRow("a busy project") << 0;
QTest::newRow("an import under way") << 1;
QTest::newRow("the new canvas sheet") << 2;
QTest::newRow("the importer sheet") << 3;
QTest::newRow("a layer being renamed") << 4;
QTest::newRow("an import error on show") << 5;
QTest::newRow("an adjustment being edited") << 6;
}
void SessionGateTests::fileRequestsWaitForEveryGate()
{
QFETCH(int, gate);
EditorSession session;
const auto set = [&](bool closed) {
switch (gate) {
case 0: session.setIsProjectBusy(closed); break;
case 1: session.setIsImporting(closed); break;
case 2: session.setShowsNewDocument(closed); break;
case 3: session.setShowsImporter(closed); break;
case 4: session.setRenamingLayerID(closed ? std::optional(QUuid::createUuid()) : std::nullopt); break;
case 5: session.setImportError(closed ? std::optional(QString("failed")) : std::nullopt); break;
default: session.setAdjustmentEditingID(closed ? std::optional(QUuid::createUuid()) : std::nullopt); break;
}
};
int ran = 0;
session.waitForFileRequest([&] { ++ran; });
QCOMPARE(ran, 1);
QVERIFY(session.canStartProjectOperation());
set(true);
QVERIFY(!session.canStartProjectOperation());
session.waitForFileRequest([&] { ++ran; });
QTest::qWait(20);
QCOMPARE(ran, 1);
// Another gate opening and closing frees nobody.
session.setShowsImporter(gate != 3);
session.setShowsImporter(gate == 3);
QTest::qWait(20);
QCOMPARE(ran, 1);
set(false);
QVERIFY(QTest::qWaitFor([&] { return ran == 2; }, 5'000));
}
void SessionGateTests::aWaiterLooksAgainWhenItsTurnComes()
{
EditorSession session;
QSignalSpy changes(&session, &EditorSession::changed);
session.setIsProjectBusy(true);
QStringList ran;
qsizetype heard = 0;
// The first to run takes the project for itself.
session.waitForProjectAccess([&] {
ran << "first";
heard = changes.count();
session.setIsProjectBusy(true);
});
session.waitForProjectAccess([&] { ran << "second"; });
session.waitForFileRequest([&] { ran << "file"; });
const qsizetype before = changes.count();
session.setIsProjectBusy(false);
// Waiters run once the change is made and announced.
QVERIFY(ran.isEmpty());
QTest::qWait(50);
QCOMPARE(ran, QStringList{"first"});
QCOMPARE(heard, before + 1);
session.setIsProjectBusy(false);
QVERIFY(QTest::qWaitFor([&] { return ran.size() == 3; }, 5'000));
QCOMPARE(ran, (QStringList{"first", "second", "file"}));
// Busy said again frees nobody and keeps the line.
ran.clear();
session.setIsProjectBusy(true);
session.waitForProjectAccess([&] { ran << "a"; });
session.setIsProjectBusy(true);
session.waitForProjectAccess([&] { ran << "b"; });
QTest::qWait(50);
QVERIFY(ran.isEmpty());
session.setIsProjectBusy(false);
QVERIFY(QTest::qWaitFor([&] { return ran.size() == 2; }, 5'000));
QCOMPARE(ran, (QStringList{"a", "b"}));
}
void SessionGateTests::aNewCanvasFreesWaitingFileRequests()
{
// The sheet closes, or a rename ends, inside createDocument.
for (const bool renaming : {false, true}) {
EditorSession session;
if (renaming)
session.setRenamingLayerID(QUuid::createUuid());
else
session.setShowsNewDocument(true);
bool ran = false;
session.waitForFileRequest([&] { ran = true; });
QTest::qWait(20);
QVERIFY(!ran);
session.createDocument(16, 16);
QVERIFY(QTest::qWaitFor([&] { return ran; }, 5'000));
}
}
void SessionGateTests::waitersEndWithTheirSession()
{
QStringList ran;
{
EditorSession session;
session.setIsProjectBusy(true);
session.waitForProjectAccess([&] { ran << "project"; });
session.waitForFileRequest([&] { ran << "file"; });
// Both are posted, and the session ends before they run.
session.setIsProjectBusy(false);
}
QTest::qWait(50);
QCOMPARE(ran, QStringList());
}
void SessionGateTests::theBusyIndicatorWaitsAQuarterSecond()
{
QCOMPARE(EditorSession::busyIndicatorDelay, 250);
EditorSession session;
QSignalSpy changes(&session, &EditorSession::changed);
QElapsedTimer clock;
clock.start();
session.setIsProjectBusy(true);
QVERIFY(!session.showsBusy());
// A quick operation never dims the controls.
QTest::qWait(60);
QVERIFY(!session.showsBusy() || clock.elapsed() >= 250);
session.setIsProjectBusy(false);
QTest::qWait(400);
QVERIFY(!session.showsBusy());
// A long one does, by itself and never early.
qint64 shownAfter = -1;
connect(&session, &EditorSession::changed, this, [&] {
if (session.showsBusy() && shownAfter < 0)
shownAfter = clock.nsecsElapsed();
});
clock.restart();
session.setIsProjectBusy(true);
const qsizetype before = changes.count();
// Busy said again on the way starts no new wait.
QTest::qSleep(100);
const QTimer *timer = session.findChild<QTimer *>();
QVERIFY(timer != nullptr && timer->isActive());
QCOMPARE(timer->timerType(), Qt::PreciseTimer);
session.setIsProjectBusy(true);
QVERIFY2(timer->remainingTime() <= 150, qPrintable(QString::number(timer->remainingTime())));
QVERIFY(QTest::qWaitFor([&] { return session.showsBusy(); }, 10'000));
QVERIFY2(shownAfter >= 250'000'000, qPrintable(QString::number(shownAfter)));
QCOMPARE(changes.count(), before + 2);
// Busy again keeps it on and starts no second wait.
session.setIsProjectBusy(true);
QVERIFY(session.showsBusy());
QTest::qWait(400);
QCOMPARE(changes.count(), before + 3);
session.setIsProjectBusy(false);
QVERIFY(!session.showsBusy());
}
QTEST_GUILESS_MAIN(SessionGateTests)
#include "SessionGateTests.moc"