You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
settings/tests/ui/datetime_test.cpp

162 lines
5.3 KiB
C++

// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <gtest/gtest.h>
#include <algorithm>
#include <chrono>
#include <cmath>
#include "cutefish/timedate/time_date.h"
#include "pages/datetime/timezonedialog.h"
#include "pages/datetime/timezonemap.h"
#include "ui/keys.h"
#include "ui/test_input.h"
#include "ui/text_field.h"
#include "ui/window.h"
#include "window/settingswindow.h"
using namespace cutefish;
using namespace settings;
namespace {
// The map without timedated, so nothing here reaches the system.
struct TimeZoneMapFixture : ::testing::Test
{
timedate::TimeDate timeDate{nullptr};
TimeZoneMap map{timeDate};
};
} // namespace
TEST_F(TimeZoneMapFixture, FindsAZoneAtItsOwnSpot)
{
const std::string zone = "Asia/Singapore";
const ui::PointF point = map.position(zone, 3600, 1800);
ASSERT_GE(point.x, 0);
ASSERT_GE(point.y, 0);
EXPECT_EQ(map.nearest(int(std::lround(point.x)), int(std::lround(point.y)), 3600, 1800), zone);
EXPECT_EQ(map.position("Nowhere/Land", 3600, 1800).x, -1);
}
TEST_F(TimeZoneMapFixture, AClickPicksTheZonesAroundIt)
{
// Berlin, Copenhagen, Prague and their neighbours lie within a few pixels
// of each other on a small map: all of them are offered, sorted.
const ui::PointF berlin = map.position("Europe/Berlin", 400, 200);
map.clicked(int(berlin.x), int(berlin.y), 400, 200);
const auto& zones = map.availableList();
ASSERT_GT(zones.size(), 1u);
EXPECT_TRUE(std::is_sorted(zones.begin(), zones.end()));
EXPECT_NE(std::find(zones.begin(), zones.end(), "Europe/Berlin"), zones.end());
// Far from every zone, the nearest one alone.
map.clicked(0, 199, 400, 200);
EXPECT_EQ(map.availableList().size(), 1u);
map.select("America/New_York");
EXPECT_EQ(map.availableList(), std::vector<std::string>{"America/New_York"});
}
TEST_F(TimeZoneMapFixture, NamesAndSearch)
{
EXPECT_EQ(TimeZoneMap::localeTimeZoneName("America/Los_Angeles"), "Los Angeles");
EXPECT_EQ(TimeZoneMap::localeTimeZoneName("UTC"), "UTC");
EXPECT_EQ(TimeZoneMap::territoryName("US"), "United States");
EXPECT_EQ(TimeZoneMap::territoryName("SG"), "Singapore");
// City first, then the id, then a match inside either, then the country.
const auto newYork = map.search("new york");
ASSERT_FALSE(newYork.empty());
EXPECT_EQ(newYork.front().id, "America/New_York");
EXPECT_EQ(newYork.front().region, "United States");
const auto byCode = map.search(" sg ");
ASSERT_FALSE(byCode.empty());
EXPECT_EQ(byCode.front().id, "Asia/Singapore");
EXPECT_EQ(map.search("america").size(), 12u);
EXPECT_TRUE(map.search(" ").empty());
EXPECT_TRUE(map.search("no such place").empty());
}
TEST_F(TimeZoneMapFixture, InfoSaysTheDifferenceInWords)
{
const auto info = map.info("Asia/Kolkata");
ASSERT_EQ(info.id, "Asia/Kolkata");
EXPECT_EQ(info.name, "Kolkata");
EXPECT_EQ(info.region, "India");
EXPECT_EQ(info.offsetSeconds, 5 * 3600 + 1800);
EXPECT_FALSE(info.time.empty());
if (map.currentTimeZoneId() == "Asia/Singapore")
EXPECT_EQ(info.difference, "2 h 30 min behind");
EXPECT_EQ(map.info(map.currentTimeZoneId()).difference, "Same time as now");
EXPECT_TRUE(map.info("Nowhere/Land").id.empty());
}
TEST_F(TimeZoneMapFixture, EveryZoneIsAPoint)
{
const auto points = map.points();
ASSERT_GT(points.size(), 300u);
for (const auto& point : points) {
EXPECT_GE(point.x, -0.1);
EXPECT_LE(point.x, 1.1);
}
}
namespace {
// The view of the type with the window coordinates of its rect.
template<typename T>
T* find(ui::View* view)
{
if (auto* found = dynamic_cast<T*>(view))
return found;
for (const auto& child : view->children()) {
if (T* found = find<T>(child.get()))
return found;
}
return nullptr;
}
ui::RectF windowRect(ui::View* view)
{
const ui::PointF origin = view->mapToWindow({0, 0});
return {origin.x, origin.y, view->geometry().width, view->geometry().height};
}
} // namespace
TEST_F(TimeZoneMapFixture, DialogLayout)
{
SettingsWindow window;
TimeZoneDialog dialog(window, map);
dialog.window()->setScale(1.5);
dialog.show();
ui::Window& dialogWindow = *dialog.window();
dialogWindow.grab();
EXPECT_EQ(dialogWindow.size().width, 900);
EXPECT_EQ(dialogWindow.size().height, 640);
// The search box in the middle of the window, on the title bar's band.
ui::TextField* field = find<ui::TextField>(dialogWindow.contentView());
ASSERT_TRUE(field);
const ui::RectF search = windowRect(field->parent());
EXPECT_EQ(search.x, 310);
EXPECT_EQ(search.y, 9);
EXPECT_EQ(search.width, 280);
EXPECT_EQ(search.height, 30);
// The zone in use is selected and its card shown.
EXPECT_EQ(dialog.selectedZone(), map.currentTimeZoneId());
EXPECT_FALSE(dialog.isChanged());
// Typing lists matches; Down moves through them, Return picks.
ui::TestInput::click(dialogWindow, {search.x + 140, search.y + 15});
ui::TestInput::typeText(dialogWindow, "berl");
ui::TestInput::typeKey(dialogWindow, ui::key::Return);
dialogWindow.grab();
EXPECT_EQ(dialog.selectedZone(), "Europe/Berlin");
EXPECT_TRUE(dialog.isChanged());
EXPECT_EQ(field->text(), "");
}