Files
OmaPhoto/tests/CommandPaletteWindowTests.cpp
T

232 lines
11 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 "MenuFixtures.h"
#include "UI/CommandPaletteView.h"
#include <QFontDatabase>
#include <QLabel>
#include <QScrollBar>
// The palette in the app's window, on Ctrl+F.
namespace {
struct Shown : Bar {
Shown()
{
window.resize(1000, 700);
window.show();
if (!QTest::qWaitForWindowActive(&window))
throw std::runtime_error("window never active");
}
QWidget *panel() const { return window.findChild<QWidget *>("commandPalette"); }
CommandPaletteView &view() const
{
CommandPaletteView *found = nullptr;
for (CommandPaletteView *each : window.findChildren<CommandPaletteView *>())
if (each->isVisible())
found = each;
if (!found)
throw std::runtime_error("no palette shown");
return *found;
}
void open()
{
QTest::keyClick(&window, Qt::Key_F, Qt::ControlModifier);
QTRY_VERIFY(panel() && panel()->isVisible());
}
};
}
class CommandPaletteWindowTests : public QObject {
Q_OBJECT
private slots:
void ctrlFOpensItOverTheWindowAndAgainCloses();
void typingRanksAndReturnRunsTheChosenRow();
void aDisabledRowStaysOpenAndEscapeCloses();
void aClickRunsARowAndAClickOutsideCloses();
void rowsDrawChecksKeysAndTheEmptyWords();
void aRowDrawsItsKeyAtItsEnd();
};
void CommandPaletteWindowTests::ctrlFOpensItOverTheWindowAndAgainCloses()
{
Shown shown;
QAction &search = shown.action("searchCommands");
QVERIFY(search.text() == "Search Commands…" && search.isEnabled());
// First in View; Toggle Fullscreen, then a separator.
const QList<QAction *> view = qobject_cast<QMenu *>(search.parent())->actions();
QVERIFY(view[0] == &search && view[1] == &shown.action("toggleFullscreen") && view[2]->isSeparator());
const QPoint centred = shown.window.frameGeometry().center() - QPoint(230, 145);
shown.open();
QCOMPARE(shown.panel()->size(), QSize(460, 290));
QCOMPARE(shown.panel()->pos(), centred);
QTRY_VERIFY(shown.view().field().hasFocus());
QCOMPARE(shown.view().field().placeholderText(), QString("Search commands and tools"));
// Every menu entry, the mask commands and the tools.
const std::vector<CommandPaletteEntry> &entries = shown.view().model().entries();
QCOMPARE(entries.back().id, QString("Tool › Zoom"));
QVERIFY(std::ranges::any_of(entries, [](const CommandPaletteEntry &e) { return e.id == "Layer › Add Layer Mask"; }));
QVERIFY(std::ranges::none_of(entries, [](const CommandPaletteEntry &e) { return e.title.contains("Search Commands"); }));
// Ctrl+F inside the palette puts it away.
QTest::keyClick(&shown.view().field(), Qt::Key_F, Qt::ControlModifier);
QTRY_VERIFY(!shown.panel()->isVisible());
shown.open();
QVERIFY(shown.view().field().text().isEmpty());
// The last palette goes once the event loop runs.
QTRY_COMPARE(shown.window.findChildren<CommandPaletteView *>().size(), qsizetype(1));
}
void CommandPaletteWindowTests::typingRanksAndReturnRunsTheChosenRow()
{
Shown shown;
shown.session().createDocument(400, 300);
shown.session().zoom(1);
shown.open();
// Up from the top wraps to the last row, shown.
auto *scroller = shown.view().findChild<QScrollArea *>();
QTest::keyClick(&shown.view().field(), Qt::Key_Up);
QVERIFY(shown.view().model().results().size() > 100);
QCOMPARE(shown.view().model().selection, int(shown.view().model().results().size()) - 1);
QCOMPARE(scroller->verticalScrollBar()->value(), scroller->verticalScrollBar()->maximum());
QTest::keyClick(&shown.view().field(), Qt::Key_Up);
QCOMPARE(shown.view().model().selection, int(shown.view().model().results().size()) - 2);
QTest::keyClick(&shown.view().field(), Qt::Key_Down);
QTest::keyClick(&shown.view().field(), Qt::Key_Down);
QCOMPARE(shown.view().model().selection, 0);
QCOMPARE(scroller->verticalScrollBar()->value(), 0);
QTest::keyClicks(&shown.view().field(), "zoom in");
QCOMPARE(shown.view().model().selected().value().id, QString("View › Zoom In"));
QTest::keyClick(&shown.view().field(), Qt::Key_Return);
QVERIFY(!shown.panel()->isVisible());
// The hidden palette keeps no focus: Undo is no field’s.
QVERIFY(!QApplication::focusWidget());
// It runs once the event loop is next free.
QCOMPARE(shown.session().viewport.zoom(), 1.0);
QTRY_COMPARE(shown.session().viewport.zoom(), 1.25);
// A mode by its name; Enter runs the chosen row.
shown.open();
QTest::keyClicks(&shown.view().field(), "lasso");
QCOMPARE(shown.view().model().results()[1].id, QString("Tool › Polygonal Lasso"));
QTest::keyClick(&shown.view().field(), Qt::Key_Down);
QTest::keyClick(&shown.view().field(), Qt::Key_Enter);
QTRY_VERIFY(shown.session().tool() == NavigationTool::lasso && shown.session().lassoKind() == LassoKind::polygonal);
}
void CommandPaletteWindowTests::aDisabledRowStaysOpenAndEscapeCloses()
{
Shown shown;
shown.open();
QTest::keyClicks(&shown.view().field(), "zoom in");
QVERIFY(!shown.view().model().selected().value().isEnabled);
QTest::ignoreMessage(QtWarningMsg, "palette: disabled: \"View › Zoom In\"");
QTest::keyClick(&shown.view().field(), Qt::Key_Return);
QVERIFY(shown.panel()->isVisible());
QTest::keyClick(&shown.view().field(), Qt::Key_Escape);
QVERIFY(!shown.panel()->isVisible());
QVERIFY(!shown.session().document());
}
void CommandPaletteWindowTests::aClickRunsARowAndAClickOutsideCloses()
{
Shown shown;
shown.session().createDocument(40, 30);
shown.open();
QTest::keyClicks(&shown.view().field(), "tool eraser");
auto *rows = shown.view().findChild<CommandPaletteRows *>();
QCOMPARE(shown.view().model().results()[0].id, QString("Tool › Eraser"));
QTest::mouseClick(rows, Qt::RightButton, {}, rows->rowRect(0).center());
QVERIFY(shown.panel()->isVisible());
QTest::mouseClick(rows, Qt::LeftButton, {}, rows->rowRect(0).center());
QTRY_VERIFY(shown.session().tool() == NavigationTool::brush && shown.session().brushMode() == BrushToolMode::erase);
shown.open();
QTest::mouseClick(shown.panel(), Qt::LeftButton, {}, QPoint(-20, -20));
QTRY_VERIFY(!shown.panel()->isVisible());
}
void CommandPaletteWindowTests::rowsDrawChecksKeysAndTheEmptyWords()
{
Shown shown;
shown.open();
CommandPaletteView &view = shown.view();
auto *rows = view.findChild<CommandPaletteRows *>();
QTest::keyClicks(&view.field(), "pixel grid");
QCOMPARE(view.model().results()[0].id, QString("View › Pixel Grid (800% and above)"));
QVERIFY(view.model().results()[0].isOn);
const QImage drawn = rows->grab().toImage();
const QRect row = rows->rowRect(0);
// The chosen row is tinted; its checkmark draws first.
const QColor tint = drawn.pixelColor(row.right() - 3, row.center().y());
QVERIFY(tint != rows->palette().color(QPalette::Window));
int inked = 0;
for (int x = row.left() + 10; x < row.left() + 22; ++x)
for (int y = row.top(); y < row.bottom(); ++y)
inked += drawn.pixelColor(x, y) != drawn.pixelColor(row.right() - 3, y);
QVERIFY(inked > 6);
// Text starts past the check; off, the title is there.
const auto gap = [&](const QImage &image) {
int ink = 0;
for (int x = row.left() + 23; x < row.left() + 27; ++x)
for (int y = row.top() + 2; y < row.bottom() - 2; ++y)
ink += image.pixelColor(x, y) != image.pixelColor(row.right() - 3, y);
return ink;
};
QCOMPARE(gap(drawn), 0);
QTest::keyClick(&view.field(), Qt::Key_Escape);
shown.session().setShowsPixelGrid(false);
shown.open();
QTest::keyClicks(&shown.view().field(), "pixel grid");
QVERIFY(!shown.view().model().results()[0].isOn);
QVERIFY(gap(shown.view().findChild<CommandPaletteRows *>()->grab().toImage()) > 0);
QVERIFY(!shown.view().findChild<QLabel *>("noCommands")->isVisible());
QTest::keyClicks(&shown.view().field(), "qqqq");
QVERIFY(shown.view().model().results().empty());
QVERIFY(shown.view().findChild<QLabel *>("noCommands")->isVisible());
QTest::keyClick(&shown.view().field(), Qt::Key_Return);
QVERIFY(shown.panel()->isVisible());
}
void CommandPaletteWindowTests::aRowDrawsItsKeyAtItsEnd()
{
Shown shown;
shown.open();
const auto keyInk = [&](const QString &query) {
shown.view().field().setText(query);
auto *rows = shown.view().findChild<CommandPaletteRows *>();
const QImage drawn = rows->grab().toImage();
const QRect row = rows->rowRect(0);
int ink = 0;
for (int x = row.right() - 40; x < row.right() - 10; ++x)
for (int y = row.top() + 2; y < row.bottom() - 2; ++y)
ink += drawn.pixelColor(x, y) != drawn.pixelColor(row.right() - 3, y);
return ink;
};
// A long title ends, elided, six points before the key.
QVERIFY(keyInk("fit canvas") > 20);
QCOMPARE(shown.view().model().results()[0].shortcut.value(), QKeySequence(Qt::CTRL | Qt::Key_0).toString(QKeySequence::NativeText));
QVERIFY(keyInk("tool lasso") > 5);
QCOMPARE(shown.view().model().results()[0].shortcut.value(), QString("L"));
QCOMPARE(keyInk("pixel grid"), 0);
QVERIFY(!shown.view().model().results()[0].shortcut);
// A long title ends, elided, six points before its key.
const QString key = QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_P).toString(QKeySequence::NativeText);
CommandPaletteView view({{.id = "Long", .title = QString("Layer › ").repeated(30), .shortcut = key, .isEnabled = true, .isOn = false, .perform = [] {}}},
[](const CommandPaletteEntry &) {}, nullptr);
view.resize(460, 290);
view.show();
auto *rows = view.findChild<CommandPaletteRows *>();
QFont fixed = QFontDatabase::systemFont(QFontDatabase::FixedFont);
fixed.setPixelSize(12);
const QRect row = rows->rowRect(0);
const int keyLeft = row.right() - 10 - QFontMetrics(fixed).horizontalAdvance(key);
const QImage drawn = rows->grab().toImage();
int between = 0;
for (int x = keyLeft - 5; x < keyLeft - 1; ++x)
for (int y = row.top() + 2; y < row.bottom() - 2; ++y)
between += drawn.pixelColor(x, y) != drawn.pixelColor(row.right() - 3, y);
QCOMPARE(between, 0);
int title = 0;
for (int x = keyLeft - 40; x < keyLeft - 8; ++x)
for (int y = row.top() + 2; y < row.bottom() - 2; ++y)
title += drawn.pixelColor(x, y) != drawn.pixelColor(row.right() - 3, y);
QVERIFY(title > 10);
}
QTEST_MAIN(CommandPaletteWindowTests)
#include "CommandPaletteWindowTests.moc"