// SPDX-FileCopyrightText: 2026 Reion Wong // SPDX-License-Identifier: BSD-3-Clause #include #include #include #include #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{"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 T* find(ui::View* view) { if (auto* found = dynamic_cast(view)) return found; for (const auto& child : view->children()) { if (T* found = find(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(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(), ""); }