完成 0.2.0 预发布收口与中文安装器

This commit is contained in:
2026-09-09 01:20:45 +08:00
parent 90262aaf6d
commit 1f5e26c361
30 changed files with 1826 additions and 121 deletions
+140
View File
@@ -1,8 +1,34 @@
#include "TestHarness.h"
#include "src/weather/WeatherParser.h"
#include "src/weather/WeatherLocationResolver.h"
#include "src/weather/WeatherSummaryFormatter.h"
namespace
{
WeatherLocationCandidate candidate(
const QString &city,
const QString &admin,
const QString &country,
const QString &countryCode,
const QString &featureCode,
qint64 population,
double latitude,
double longitude)
{
WeatherLocationCandidate value;
value.cityName = city;
value.adminName = admin;
value.countryName = country;
value.countryCode = countryCode;
value.featureCode = featureCode;
value.population = population;
value.latitude = latitude;
value.longitude = longitude;
return value;
}
}
QA_TEST(WeatherParser, ExtractsExplicitCitiesAndDates)
{
const WeatherParser parser;
@@ -38,6 +64,120 @@ QA_TEST(WeatherParser, RejectsUnsupportedQueries)
QA_EXPECT(!parser.parse(QStringLiteral("明天穿什么")).unsupportedReason.isEmpty());
}
QA_TEST(WeatherLocationResolver, ResolvesChineseInternationalCities)
{
const WeatherLocationSearch newYork = WeatherLocationResolver::resolveSearch(QStringLiteral("纽约"));
const WeatherLocationSearch tokyo = WeatherLocationResolver::resolveSearch(QStringLiteral("东京"));
const WeatherLocationSearch japanTokyo = WeatherLocationResolver::resolveSearch(QStringLiteral("日本东京"));
const WeatherLocationSearch qualifiedTokyo = WeatherLocationResolver::resolveSearch(QStringLiteral("东京,日本"));
QA_EXPECT_EQ(newYork.queryName, QStringLiteral("New York"));
QA_EXPECT_EQ(newYork.countryCode, QStringLiteral("US"));
QA_EXPECT_EQ(tokyo.queryName, QStringLiteral("Tokyo"));
QA_EXPECT_EQ(tokyo.countryCode, QStringLiteral("JP"));
QA_EXPECT_EQ(japanTokyo.queryName, QStringLiteral("Tokyo"));
QA_EXPECT_EQ(japanTokyo.countryCode, QStringLiteral("JP"));
QA_EXPECT_EQ(qualifiedTokyo.queryName, QStringLiteral("Tokyo"));
QA_EXPECT_EQ(qualifiedTokyo.countryCode, QStringLiteral("JP"));
}
QA_TEST(WeatherLocationResolver, PreservesDomesticCityAndCountryQualifier)
{
const WeatherLocationSearch xian = WeatherLocationResolver::resolveSearch(QStringLiteral("西安市"));
const WeatherLocationSearch chinaXian = WeatherLocationResolver::resolveSearch(QStringLiteral("中国西安"));
QA_EXPECT_EQ(xian.queryName, QStringLiteral("西安"));
QA_EXPECT(xian.countryCode.isEmpty());
QA_EXPECT_EQ(chinaXian.queryName, QStringLiteral("西安"));
QA_EXPECT_EQ(chinaXian.countryCode, QStringLiteral("CN"));
}
QA_TEST(WeatherLocationResolver, SuppressesLowValueAlternatives)
{
const QVector<WeatherLocationCandidate> candidates = {
candidate(QStringLiteral("西安"), QStringLiteral("陕西"), QStringLiteral("中国"), QStringLiteral("CN"), QStringLiteral("PPLA"), 6500000, 34.26, 108.93),
candidate(QStringLiteral("西安"), QStringLiteral("湖南"), QStringLiteral("中国"), QStringLiteral("CN"), QStringLiteral("PPL"), 1200, 27.10, 111.20),
candidate(QStringLiteral("西安"), QStringLiteral("广东"), QStringLiteral("中国"), QStringLiteral("CN"), QStringLiteral("PPL"), -1, 23.10, 113.20),
};
const QVector<WeatherLocationCandidate> selected = WeatherLocationResolver::selectRelevantCandidates(
candidates,
WeatherLocationResolver::resolveSearch(QStringLiteral("西安")));
QA_EXPECT_EQ(selected.size(), 1);
QA_EXPECT(!WeatherLocationResolver::hasAmbiguity(selected));
}
QA_TEST(WeatherLocationResolver, KeepsPlausibleAmbiguity)
{
const QVector<WeatherLocationCandidate> candidates = {
candidate(QStringLiteral("Springfield"), QStringLiteral("Missouri"), QStringLiteral("United States"), QStringLiteral("US"), QStringLiteral("PPLA2"), 170000, 37.21, -93.29),
candidate(QStringLiteral("Springfield"), QStringLiteral("Illinois"), QStringLiteral("United States"), QStringLiteral("US"), QStringLiteral("PPLA"), 114000, 39.80, -89.64),
candidate(QStringLiteral("Springfield"), QStringLiteral("Georgia"), QStringLiteral("United States"), QStringLiteral("US"), QStringLiteral("PPL"), 2700, 32.37, -81.31),
};
const QVector<WeatherLocationCandidate> selected = WeatherLocationResolver::selectRelevantCandidates(
candidates,
WeatherLocationResolver::resolveSearch(QStringLiteral("Springfield")));
QA_EXPECT_EQ(selected.size(), 2);
QA_EXPECT(WeatherLocationResolver::hasAmbiguity(selected));
}
QA_TEST(WeatherLocationResolver, AppliesExplicitCountryFilter)
{
const QVector<WeatherLocationCandidate> candidates = {
candidate(QStringLiteral("Tokyo"), QStringLiteral("Tokyo"), QStringLiteral("Japan"), QStringLiteral("JP"), QStringLiteral("PPLC"), 8300000, 35.68, 139.69),
candidate(QStringLiteral("Tokyo"), QStringLiteral("Texas"), QStringLiteral("United States"), QStringLiteral("US"), QStringLiteral("PPL"), 1000, 33.90, -102.50),
};
const QVector<WeatherLocationCandidate> selected = WeatherLocationResolver::selectRelevantCandidates(
candidates,
WeatherLocationResolver::resolveSearch(QStringLiteral("日本东京")));
QA_EXPECT_EQ(selected.size(), 1);
QA_EXPECT_EQ(selected.first().countryCode, QStringLiteral("JP"));
}
QA_TEST(WeatherLocationResolver, ParsesBothIpLocationSchemas)
{
const QByteArray ipApiPayload = QByteArrayLiteral(
R"({"city":"Xi'an","region":"Shaanxi","country_name":"China","latitude":34.26,"longitude":108.94,"timezone":"Asia/Shanghai"})");
const QByteArray ipWhoIsPayload = QByteArrayLiteral(
R"({"success":true,"city":"Tokyo","region":"Tokyo","country":"Japan","latitude":35.68,"longitude":139.69,"timezone":{"id":"Asia/Tokyo"}})");
WeatherLocation ipApiLocation;
WeatherLocation ipWhoIsLocation;
QA_EXPECT(WeatherLocationResolver::parseIpLocationResponse(
ipApiPayload,
WeatherIpLocationProvider::IpApiCo,
&ipApiLocation));
QA_EXPECT(WeatherLocationResolver::parseIpLocationResponse(
ipWhoIsPayload,
WeatherIpLocationProvider::IpWhoIs,
&ipWhoIsLocation));
QA_EXPECT_EQ(ipApiLocation.cityName, QStringLiteral("Xi'an"));
QA_EXPECT_EQ(ipApiLocation.countryName, QStringLiteral("China"));
QA_EXPECT_EQ(ipApiLocation.timezone, QStringLiteral("Asia/Shanghai"));
QA_EXPECT_EQ(ipWhoIsLocation.cityName, QStringLiteral("Tokyo"));
QA_EXPECT_EQ(ipWhoIsLocation.countryName, QStringLiteral("Japan"));
QA_EXPECT_EQ(ipWhoIsLocation.timezone, QStringLiteral("Asia/Tokyo"));
}
QA_TEST(WeatherLocationResolver, RejectsBrokenIpLocationResponses)
{
WeatherLocation location;
QA_EXPECT(!WeatherLocationResolver::parseIpLocationResponse(
QByteArrayLiteral("{broken"),
WeatherIpLocationProvider::IpApiCo,
&location));
QA_EXPECT(!WeatherLocationResolver::parseIpLocationResponse(
QByteArrayLiteral(R"({"success":false,"message":"rate limited"})"),
WeatherIpLocationProvider::IpWhoIs,
&location));
QA_EXPECT(!WeatherLocationResolver::parseIpLocationResponse(
QByteArrayLiteral(R"({"city":"Tokyo","latitude":35.68})"),
WeatherIpLocationProvider::IpWhoIs,
&location));
}
QA_TEST(WeatherFormatter, MapsWeatherCodesAndWindDirections)
{
QA_EXPECT_EQ(WeatherSummaryFormatter::weatherCodeText(0), QStringLiteral("晴"));