Modules/Widgets/tests/GalleryAppTests.cpp
Шурупов Илья Викторович 81949323d9 Rename the code name to Sketch2D
Stroke2D becomes Sketch2D across folders, files, namespaces, classes,
targets, scripts, the Android package (online.ilushabasic.sketch2d)
and the update URL. A stroke2d-sketches folder is adopted like the
older leo-sketches one.
2026-10-17 19:47:01 +03:00

643 lines
21 KiB
C++

#include "Gallery.hpp"
#include "Theme.hpp"
#include "Ui.hpp"
#include "UiDriver.hpp"
#include "UnitTest++/UnitTest++.h"
#include <cstdint>
using namespace tp;
using namespace tp::ui;
using namespace tp::ui::gallery;
using namespace tp::ui::testing;
namespace {
MenuItem& itemNamed(Menu& menu, const std::string& text) {
for (const auto& item : menu.items()) {
if (item->entry().text == text) return *item;
}
throw std::runtime_error("menu item not found: " + text);
}
bool isInside(const Widget& inner, const Widget& outer) {
const RectF a = inner.windowRect();
const RectF b = outer.windowRect();
return a.pos.x >= b.pos.x - 0.5f && a.pos.y >= b.pos.y - 0.5f && a.pos.x + a.size.x <= b.pos.x + b.size.x + 0.5f &&
a.pos.y + a.size.y <= b.pos.y + b.size.y + 0.5f;
}
}
SUITE(GalleryApplication) {
TEST(InitialLayoutFillsWindowAndShowsControlsPage) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
CHECK_CLOSE(1000, gallery.size().x, 0.01f);
CHECK_CLOSE(700, gallery.size().y, 0.01f);
CHECK(gallery.controls.isVisible());
CHECK(!gallery.listPage.isVisible());
CHECK(isInside(gallery.controls.blendMode, gallery.tabs));
CHECK(gallery.statusBar.windowRect().pos.y > gallery.tabs.windowRect().pos.y);
RecordingPainter& painter = driver.paint();
CHECK(painter.isBalanced());
CHECK(painter.hasText("File"));
CHECK(painter.hasText("Click me"));
CHECK(painter.hasText("Volume"));
}
TEST(UserSessionOnControlsPage) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
ControlsPage& page = gallery.controls;
driver.click(page.clickMe);
driver.click(page.clickMe);
driver.click(page.disabled);
CHECK_EQUAL(2, gallery.state.buttonClicks);
CHECK_EQUAL("clicked 2 times", page.clickCount.text());
driver.click(page.notifications);
CHECK(!gallery.state.notificationsEnabled);
driver.click(page.high);
CHECK_EQUAL(2, gallery.state.qualityIndex);
CHECK(!page.medium.isChecked());
const RectF slider = page.volume.slider().windowRect();
driver.drag(slider.center(), { slider.pos.x + slider.size.x, slider.center().y });
CHECK_CLOSE(1.0f, gallery.state.volume, 0.01f);
driver.settle();
CHECK_CLOSE(1.0f, page.progress.fraction(), 0.01f);
driver.click(page.blendMode);
driver.click(itemNamed(page.blendMode.menu(), "Overlay"));
CHECK_EQUAL("Overlay", page.blendMode.selectedText());
CHECK_EQUAL(0u, driver.ui().openPopupCount());
driver.settle();
CHECK(!driver.ui().needsFrame());
}
TEST(MenusDriveTheWholeApp) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
PopupButton& file = *gallery.menuBar.titles()[0];
PopupButton& view = *gallery.menuBar.titles()[1];
driver.click(file);
driver.moveTo(itemNamed(gallery.fileMenu, "Open Recent"));
driver.click(itemNamed(gallery.recentMenu, "portrait.sketch2d"));
CHECK_EQUAL("open portrait.sketch2d", gallery.state.lastAction);
CHECK_EQUAL("open portrait.sketch2d", gallery.statusBar.text());
CHECK_EQUAL(0u, driver.ui().openPopupCount());
driver.click(file);
driver.moveTo(view);
CHECK(gallery.viewMenu.isOpen());
CHECK(!gallery.fileMenu.isOpen());
driver.click(itemNamed(gallery.viewMenu, "Dark Mode"));
CHECK(!gallery.state.darkMode);
CHECK(!gallery.controls.darkMode.isChecked());
driver.click(view);
driver.click(itemNamed(gallery.viewMenu, "Canvas"));
CHECK_EQUAL(2, gallery.tabs.currentIndex());
CHECK(gallery.canvasPage.isVisible());
}
TEST(ScrollingTheListPage) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
driver.click(gallery.tabs.tabButton(1));
ListPage& page = gallery.listPage;
CHECK(!page.allRows.isExpanded());
driver.click(page.allRows.header());
CHECK(page.allRows.isExpanded());
CHECK(page.groups.front()->windowRect().pos.y < page.allRows.windowRect().pos.y);
CHECK(page.scroll.isScrollable());
Button& lastRow = *page.rows.back();
CHECK(lastRow.windowRect().pos.y > 700);
driver.moveTo(page.scroll);
for (int step = 0; step < 100; step++)
driver.wheel(-1);
driver.settle();
CHECK(isInside(lastRow, page.scroll));
int clicked = 0;
lastRow.setOnActivate([&clicked]() { clicked++; });
driver.click(lastRow);
CHECK_EQUAL(1, clicked);
}
TEST(CanvasPageFloatingPanelAndSplitter) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
driver.click(gallery.tabs.tabButton(2));
CanvasPage& page = gallery.canvasPage;
const Vec2F titleGrab = page.colorPanel.windowRect().pos + Vec2F(60.0f, 10.0f);
driver.drag(titleGrab, titleGrab + Vec2F(80.0f, 40.0f));
CHECK_CLOSE(104, page.colorPanel.rect().pos.x, 0.5f);
CHECK_CLOSE(64, page.colorPanel.rect().pos.y, 0.5f);
const RectF wheel = page.wheel.windowRect();
const halnf ringRadius = (wheel.size.x < wheel.size.y ? wheel.size.x : wheel.size.y) / 2 - 4;
driver.click(wheel.center() + Vec2F(0.0f, ringRadius));
CHECK(!page.colorReadout.text().empty());
CHECK(gallery.state.brushColor.g > gallery.state.brushColor.r || gallery.state.brushColor.b > 0.1f);
const halnf inspectorBefore = page.inspector.size().x;
driver.drag(page.split.handleRect().center() + page.split.windowPosition(), Vec2F(500.0f, 300.0f));
CHECK(page.inspector.size().x > inspectorBefore);
driver.settle();
CHECK(!driver.ui().needsFrame());
}
TEST(LongListDoesNotSquashMenuAndTabBars) {
Gallery gallery;
UiDriver driver(gallery, { 1508, 415 });
const halnf menuHeight = gallery.menuBar.size().y;
const halnf tabHeight = gallery.tabs.tabButton(0).size().y;
driver.click(gallery.tabs.tabButton(1));
driver.click(gallery.listPage.allRows.header());
CHECK_CLOSE(menuHeight, gallery.menuBar.size().y, 0.01f);
CHECK_CLOSE(tabHeight, gallery.tabs.tabButton(0).size().y, 0.01f);
CHECK_CLOSE(driver.ui().theme().metrics.lineHeight, gallery.statusBar.size().y, 0.01f);
CHECK(gallery.listPage.scroll.isScrollable());
}
TEST(DockingTabSupportsTheFullPanelWorkflow) {
Gallery gallery;
UiDriver driver(gallery, { 1200, 800 });
driver.click(gallery.tabs.tabButton(3));
DockPage& page = gallery.dockPage;
REQUIRE CHECK(page.project.isOpen());
CHECK(page.terminal.isOpen());
CHECK_CLOSE(
page.project.windowRect().pos.x + page.project.size().x + Theme::standard().metrics.surfaceGap,
page.editor.windowRect().pos.x,
0.01f
);
const Vec2F header = page.project.windowPosition() + Vec2F(20.0f, page.project.headerRect().center().y);
driver.moveTo(header);
driver.press();
driver.moveTo(page.dock.windowPosition() + page.dock.dropTarget(DockSide::Right).center());
CHECK(page.dock.isPreviewingDrop());
CHECK(driver.paint().isBalanced());
driver.release();
CHECK(page.project.side() == DockSide::Right);
CHECK(!page.properties.isOpen());
driver.click(page.project.optionsButton());
driver.hoverFor(*page.project.optionsMenu().items()[0], 50);
Menu& modes = *page.project.optionsMenu().items()[0]->entry().submenu;
driver.click(*modes.items()[1]);
driver.click(page.editor);
CHECK(!page.project.isOpen());
driver.click(*page.dock.stripeButton(page.project));
CHECK(page.project.isOpen());
driver.click(page.project.optionsButton());
driver.hoverFor(*page.project.optionsMenu().items()[0], 50);
driver.click(*modes.items()[2]);
CHECK(page.project.viewMode() == DockPanel::ViewMode::Float);
driver.settle();
CHECK(!driver.ui().needsFrame());
}
TEST(EveryVisibleBoxIsRoundedOnEveryTab) {
Gallery gallery;
UiDriver driver(gallery, { 1200, 800 });
const RGBA window = Theme::standard().palette.window;
for (size_t tab = 0; tab < gallery.tabs.tabCount(); tab++) {
driver.click(gallery.tabs.tabButton(tab));
driver.moveTo(Vec2F(600.0f, 400.0f));
driver.settle();
for (const PaintCommand& command : driver.paint().commands()) {
const bool box = command.kind == PaintCommand::Kind::FillRect || command.kind == PaintCommand::Kind::StrokeRect;
const bool visible = command.color.a > 0.01f && command.rect.size.x > 2 && command.rect.size.y > 2;
const bool isWindowBackground =
std::abs(command.color.r - window.r) < 0.002f && std::abs(command.color.g - window.g) < 0.002f;
const bool fullWidth = command.rect.size.x >= 1199;
if (!box || !visible || isWindowBackground || fullWidth) continue;
CHECK(command.cornerRadius > 0);
}
}
}
TEST(NoControlStaysHoveredOrPressedAfterRandomPointerJourneys) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
uint32_t seed = 12345;
const auto random = [&seed](halnf limit) {
seed = seed * 1664525u + 1013904223u;
return (halnf) (seed >> 8) / (halnf) (1u << 24) * limit;
};
for (size_t tab = 0; tab < gallery.tabs.tabCount(); tab++) {
driver.click(gallery.tabs.tabButton(tab));
for (int step = 0; step < 120; step++) {
driver.moveTo(Vec2F(random(1000), random(700)));
if (step % 9 == 0) driver.press();
if (step % 9 == 3) driver.release();
driver.advance(16);
}
driver.release();
driver.key(InputID::ESCAPE);
driver.key(InputID::ESCAPE);
driver.key(InputID::ESCAPE);
driver.moveTo(Vec2F(-100.0f, -100.0f));
driver.settle();
CHECK(driver.ui().hoveredWidget() == nullptr);
CHECK(driver.ui().pointerCapture() == nullptr);
CHECK(!driver.ui().needsFrame());
const std::string tree = driver.dump();
CHECK(tree.find("hovered") == std::string::npos);
}
}
TEST(TooltipsInTheRealApp) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
driver.hoverFor(gallery.controls.clickMe, 1000);
CHECK(driver.ui().openTooltip() != nullptr);
CHECK(driver.paint().hasText("Counts how many times it was clicked"));
}
TEST(ResizingWindowKeepsEverythingInside) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
driver.resize({ 640, 480 });
CHECK_CLOSE(640, gallery.tabs.size().x, 0.01f);
CHECK(isInside(gallery.menuBar, gallery));
CHECK(isInside(gallery.statusBar, gallery));
CHECK(driver.paint().isBalanced());
}
}
SUITE(GalleryInputPage) {
TEST(TypingNameUpdatesGreeting) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
driver.click(gallery.tabs.tabButton(4));
driver.click(gallery.inputPage.name);
driver.type("Ada");
CHECK_EQUAL("Ada", gallery.state.userName);
CHECK(driver.paint().hasText("Hello, Ada - brush 14 px"));
}
TEST(NumberFieldStepsBrushSize) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
driver.click(gallery.tabs.tabButton(4));
driver.click(gallery.inputPage.size.incrementButton());
CHECK_CLOSE(15, gallery.state.brushSize, 0.001f);
CHECK_EQUAL("15 px", gallery.inputPage.size.field().text());
}
TEST(ConfirmDialogCountsConfirmations) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
driver.click(gallery.tabs.tabButton(4));
driver.click(gallery.inputPage.askConfirm);
CHECK(gallery.inputPage.confirm.isOpen());
driver.key(InputID::ENTER);
CHECK_EQUAL(1, gallery.state.confirmations);
driver.click(gallery.inputPage.openDialog);
CHECK(gallery.inputPage.dialog.isOpen());
driver.key(InputID::ESCAPE);
CHECK(!gallery.inputPage.dialog.isOpen());
}
TEST(ShortcutsRunMenuActionsAndMenuShowsThem) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
Modifiers control;
control.control = true;
driver.key(InputID::O, control);
CHECK_EQUAL("open", gallery.state.lastAction);
CHECK_EQUAL("Ctrl+O", itemNamed(gallery.fileMenu, "Open...").entry().shortcut);
driver.holdKey(InputID::TAB, 2, control);
CHECK_EQUAL(3, gallery.tabs.currentIndex());
}
TEST(DynamicMenuEntryShowsLastAction) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
driver.click(*gallery.menuBar.titles()[1]);
CHECK_EQUAL("No action yet", gallery.viewMenu.items()[1]->text());
driver.key(InputID::ESCAPE);
Modifiers control;
control.control = true;
driver.key(InputID::N, control);
driver.click(*gallery.menuBar.titles()[1]);
CHECK_EQUAL("Last action: new", gallery.viewMenu.items()[1]->text());
}
}
SUITE(GalleryNotice) {
TEST(NoticeButtonShowsTimedBalloon) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
driver.click(gallery.tabs.tabButton(4));
driver.click(gallery.inputPage.showNotice);
CHECK(gallery.inputPage.notice.isShown());
CHECK_EQUAL("Saved the reference app's settings", gallery.inputPage.notice.text());
driver.advance(3000);
CHECK(!gallery.inputPage.notice.isShown());
}
}
SUITE(GalleryReorderPage) {
TEST(DraggingTaskReordersAndReportsMove) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
driver.click(gallery.tabs.tabButton(5));
ReorderableList& list = gallery.reorderPage.list;
CHECK_EQUAL("Nothing moved yet", gallery.reorderPage.status.text());
const Vec2F from = list.windowPosition() + list.gripRect(0).center();
const Vec2F to = list.windowPosition() + list.gripRect(2).center() + Vec2F(0, 8);
driver.drag(from, to, 6);
CHECK_EQUAL("Line art", static_cast<Checkbox&>(list.item(0)).text());
CHECK_EQUAL("Sketch", static_cast<Checkbox&>(list.item(2)).text());
CHECK_EQUAL("Moved \"Sketch\" from 1 to 3", gallery.reorderPage.status.text());
}
TEST(ClickingTaskStillTogglesIt) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
driver.click(gallery.tabs.tabButton(5));
Checkbox& first = *gallery.reorderPage.tasks[0];
driver.click(first);
CHECK(first.isChecked());
}
}
SUITE(GalleryNestedCollapsibles) {
TEST(NestedGroupsOpenInsideTheScrollableList) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 600 });
driver.click(gallery.tabs.tabButton(1));
ListPage& page = gallery.listPage;
REQUIRE CHECK_EQUAL(3u, page.groups.size());
REQUIRE CHECK_EQUAL(9u, page.subgroups.size());
Collapsible& brushes = *page.groups[0];
Collapsible& favorites = *page.subgroups[1];
const halnf firstRowBefore = page.allRows.windowRect().pos.y;
CHECK(!brushes.isExpanded());
CHECK(!favorites.isExpanded());
driver.click(brushes.header());
CHECK(brushes.isBodyShown());
CHECK(!favorites.isBodyShown());
const halnf afterGroup = page.allRows.windowRect().pos.y;
CHECK(afterGroup > firstRowBefore + 60);
driver.click(favorites.header());
CHECK(favorites.isExpanded());
CHECK(page.allRows.windowRect().pos.y > afterGroup + 100);
Button& nested = *page.groupRows[5];
CHECK_EQUAL("Brushes / Favorites 1", nested.text());
CHECK(nested.windowRect().pos.x > favorites.header().windowRect().pos.x);
CHECK_CLOSE(
favorites.body().windowRect().pos.x + favorites.body().windowRect().size.x - ReorderableList::kGripWidth,
nested.windowRect().pos.x + nested.windowRect().size.x,
0.5f
);
driver.click(brushes.header());
CHECK(!brushes.isBodyShown());
CHECK(favorites.isExpanded());
CHECK_CLOSE(firstRowBefore, page.allRows.windowRect().pos.y, 0.5f);
}
TEST(ScrollingReachesRowsInsideAnOpenGroup) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 400 });
driver.click(gallery.tabs.tabButton(1));
ListPage& page = gallery.listPage;
Collapsible& documents = *page.groups[2];
driver.click(documents.header());
Collapsible& archived = *page.subgroups[8];
driver.moveTo(page.scroll);
for (int step = 0; step < 100 && !isInside(archived.header(), page.scroll); step++) {
driver.wheel(-1);
driver.settle();
}
REQUIRE CHECK(isInside(archived.header(), page.scroll));
driver.click(archived.header());
CHECK(archived.isExpanded());
Button& last = *page.groupRows.back();
for (int step = 0; step < 100 && !isInside(last, page.scroll); step++) {
driver.wheel(-1);
driver.settle();
}
CHECK(isInside(last, page.scroll));
int clicked = 0;
last.setOnActivate([&clicked]() { clicked++; });
driver.click(last);
CHECK_EQUAL(1, clicked);
}
}
SUITE(GalleryReorderableGroups) {
TEST(RootGroupsCanBeReordered) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
driver.click(gallery.tabs.tabButton(1));
ListPage& page = gallery.listPage;
ReorderableList& list = page.list;
REQUIRE CHECK_EQUAL(6u, list.itemCount());
CHECK(&list.item(0) == page.groups[0].get());
CHECK(&list.item(3) == &page.allRows);
const Vec2F from = list.windowPosition() + list.gripRect(0).center();
const RectF third = list.item(2).windowRect();
driver.drag(from, { from.x, third.pos.y + third.size.y - 2 }, 10);
CHECK(&list.item(0) == page.groups[1].get());
CHECK(&list.item(2) == page.groups[0].get());
CHECK(!page.groups[0]->isExpanded());
}
TEST(DraggingAGroupHeaderDoesNotToggleIt) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 700 });
driver.click(gallery.tabs.tabButton(1));
ListPage& page = gallery.listPage;
Collapsible& layers = *page.groups[1];
const Vec2F start = driver.centerOf(layers.header());
driver.drag(start, start - Vec2F(0, 40), 8);
CHECK(&page.list.item(0) == &layers);
CHECK(!layers.isExpanded());
driver.click(layers.header());
CHECK(layers.isExpanded());
}
TEST(OpenGroupMovesWithItsContent) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 900 });
driver.click(gallery.tabs.tabButton(1));
ListPage& page = gallery.listPage;
Collapsible& documents = *page.groups[2];
driver.click(documents.header());
driver.click(*page.subgroups[6]);
REQUIRE CHECK(documents.isExpanded());
const Vec2F from = page.list.windowPosition() + page.list.gripRect(2).center();
const Vec2F to = page.list.windowPosition() + page.list.gripRect(0).center() - Vec2F(0, 10);
driver.drag(from, to, 10);
CHECK(&page.list.item(0) == &documents);
CHECK(documents.isExpanded());
CHECK(page.groupRows[30]->windowRect().pos.y > documents.header().windowRect().pos.y);
}
}
SUITE(GalleryListShowcases) {
TEST(DraggableListReordersAndReportsTheMove) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 900 });
driver.click(gallery.tabs.tabButton(1));
ListPage& page = gallery.listPage;
driver.click(page.draggableSection.header());
REQUIRE CHECK(page.draggableSection.isExpanded());
Checkbox& sketch = *page.draggableItems[0];
const Vec2F from = driver.centerOf(sketch);
const RectF third = page.draggableItems[2]->windowRect();
driver.drag(from, { from.x, third.pos.y + third.size.y - 2 }, 10);
CHECK(&page.draggable.item(2) == &sketch);
CHECK(!sketch.isChecked());
CHECK_EQUAL("Moved \"Sketch\" from 1 to 3", page.draggableStatus.text());
}
TEST(StaticListKeepsItsOrderAndStillToggles) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 900 });
driver.click(gallery.tabs.tabButton(1));
ListPage& page = gallery.listPage;
driver.click(page.staticSection.header());
Checkbox& sketch = *page.staticItems[0];
const Vec2F from = driver.centerOf(sketch);
driver.drag(from, from + Vec2F(0, 60), 10);
CHECK(page.staticList.children()[0] == &sketch);
CHECK(sketch.windowRect().pos.y < page.staticItems[1]->windowRect().pos.y);
driver.click(*page.staticItems[1]);
CHECK(page.staticItems[1]->isChecked());
}
}
SUITE(GalleryNestedReorder) {
TEST(FavoritesInsideAGroupAreReorderable) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 900 });
driver.click(gallery.tabs.tabButton(1));
ListPage& page = gallery.listPage;
REQUIRE CHECK_EQUAL(3u, page.favoriteLists.size());
Collapsible& layers = *page.groups[1];
Collapsible& favorites = *page.subgroups[4];
CHECK_EQUAL("Favorites", favorites.header().title());
driver.click(layers.header());
driver.click(favorites.header());
ReorderableList& items = *page.favoriteLists[1];
REQUIRE CHECK_EQUAL(5u, items.itemCount());
Button& first = static_cast<Button&>(items.item(0));
CHECK_EQUAL("Layers / Favorites 1", first.text());
const Vec2F start = items.windowPosition() + items.gripRect(0).center();
const RectF third = items.item(2).windowRect();
driver.drag(start, { start.x, third.pos.y + third.size.y / 2 + 4 }, 10);
CHECK(&items.item(2) == &first);
CHECK(&page.list.item(1) == &layers);
CHECK(favorites.isExpanded());
CHECK(layers.isExpanded());
}
TEST(OtherSubgroupsStayPlain) {
Gallery gallery;
UiDriver driver(gallery, { 1000, 900 });
driver.click(gallery.tabs.tabButton(1));
ListPage& page = gallery.listPage;
Collapsible& recent = *page.subgroups[0];
CHECK(page.groupRows[0]->parent() == &recent.body());
CHECK(dynamic_cast<ReorderableList*>(page.groupRows[5]->parent()) != nullptr);
}
}
SUITE(GalleryFilesPage) {
TEST(FilesTabBrowsesHomeAndOpensTheSaveDialog) {
Gallery gallery;
UiDriver driver(gallery, { 1200, 800 });
size_t filesTab = gallery.tabs.tabCount();
for (size_t tab = 0; tab < gallery.tabs.tabCount(); tab++) {
if (gallery.tabs.tabButton(tab).text() == "Files") filesTab = tab;
}
REQUIRE CHECK(filesTab < gallery.tabs.tabCount());
driver.click(gallery.tabs.tabButton(filesTab));
driver.settle();
FilesPage& page = gallery.filesPage;
CHECK_EQUAL(FileSystem::local().home(), page.browser.directory());
CHECK(isInside(page.browser, gallery.tabs));
CHECK(page.browser.windowRect().size.y > 200);
driver.click(page.saveFile);
driver.settle();
REQUIRE CHECK(page.dialog.isOpen());
CHECK_EQUAL("Save as", page.dialog.title());
CHECK_EQUAL("untitled.txt", page.dialog.name.text());
driver.key(InputID::ESCAPE);
page.dialog.close();
driver.click(page.chooseFolder);
driver.settle();
REQUIRE CHECK(page.dialog.isOpen());
page.dialog.confirm();
CHECK_EQUAL("Chose " + page.browser.directory(), page.status.text());
}
}