mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
Merge remote-tracking branch 'origin/main' into german/hal-639-de
Conflicts in es, ru, zh-CN and zh-Hant catalogs: both sides appended keys at the end (cyclone errors from this branch, routing refusals from main); kept both. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
commit
2f12cbf5c8
78 files changed
+3326
-303
No files matched your search
@@ -716,18 +716,24 @@ export class LayerPanel {
|
||||
});
|
||||
}
|
||||
if (feedState === 'stale') {
|
||||
// A layer drawing cached upstream data (Traffic's proxy tiles) dates the
|
||||
// row by the oldest of it, not by the load that re-served it.
|
||||
const dataAgo =
|
||||
Number(stats.oldestTileAt) > 0
|
||||
? this._timeAgo(stats.oldestTileAt)
|
||||
: ago;
|
||||
if (typeof stats.retryInSec === 'number' && stats.retryInSec > 0) {
|
||||
return t('{state} · {source} · {ago} · retrying in {seconds}s', {
|
||||
state: stateLabel,
|
||||
source,
|
||||
ago,
|
||||
ago: dataAgo,
|
||||
seconds: stats.retryInSec,
|
||||
});
|
||||
}
|
||||
return t('{state} · {source} · {ago}', {
|
||||
state: stateLabel,
|
||||
source,
|
||||
ago,
|
||||
ago: dataAgo,
|
||||
});
|
||||
}
|
||||
if (typeof stats.loadingLabel === 'string' && stats.loadingLabel.trim()) {
|
||||
|
||||
@@ -93,6 +93,27 @@ test('partial feed controls distinguish incomplete records from stale data and o
|
||||
assert.equal(button.textContent, 'OFF');
|
||||
});
|
||||
|
||||
test('a stale row dates cached data by its oldest tile, not the last load', async () => {
|
||||
const { LayerPanel } = await import('./layerPanel.js');
|
||||
const panel = LayerPanel.prototype;
|
||||
const layer = {
|
||||
id: 'traffic',
|
||||
name: 'Street Traffic',
|
||||
source: 'TomTom',
|
||||
enabled: true,
|
||||
stats: {
|
||||
mode: 'live',
|
||||
stale: true,
|
||||
count: 4,
|
||||
lastUpdate: Date.now(),
|
||||
oldestTileAt: Date.now() - 3 * 3600_000,
|
||||
},
|
||||
};
|
||||
assert.match(panel._buildMetaText(layer), /^STALE · TomTom · 3h ago$/);
|
||||
delete layer.stats.oldestTileAt;
|
||||
assert.match(panel._buildMetaText(layer), /^STALE · TomTom · just now$/);
|
||||
});
|
||||
|
||||
test('readout rows contain only toggles and metadata; ordinary rows retain controls', async () => {
|
||||
const { LayerPanel } = await import('./layerPanel.js');
|
||||
const { railFixture } = await import('./railTestFixture.mjs');
|
||||
|
||||
+35
-1
@@ -112,12 +112,46 @@ const FEED_ERROR_PATTERNS = [
|
||||
],
|
||||
];
|
||||
|
||||
// Directions route failures (layers/directions/index.js), English data with
|
||||
// the route proxy's refusal or an HTTP status in them. Ahead of the feed
|
||||
// patterns, which would take "Routing failed: rate limited" for a source.
|
||||
// The route proxy's refusal texts (server/providers/places/routes.js) show
|
||||
// translated; any other text in {error} shows its catalog entry, or as given.
|
||||
const ROUTING_REFUSALS = new Set([
|
||||
msg('rate limited'),
|
||||
msg('routing service is rate limited'),
|
||||
msg('route leg too long'),
|
||||
msg('route too long'),
|
||||
msg('routing busy — too many routes at once'),
|
||||
msg('route proxy error'),
|
||||
]);
|
||||
const routingRefusal = (text) =>
|
||||
ROUTING_REFUSALS.has(text) ? t(text) : translateKnown(text);
|
||||
const ROUTING_ERROR_PATTERNS = [
|
||||
[
|
||||
/^Routing unavailable \(HTTP (\d+)\)$/,
|
||||
([, status]) => t('Routing unavailable (HTTP {status})', { status }),
|
||||
],
|
||||
[
|
||||
/^Routing failed: (.+)$/,
|
||||
([, error]) =>
|
||||
t('Routing failed: {error}', { error: routingRefusal(error) }),
|
||||
],
|
||||
[
|
||||
/^(.+) — try again in a moment$/,
|
||||
([, error]) =>
|
||||
t('{error} — try again in a moment', { error: routingRefusal(error) }),
|
||||
],
|
||||
];
|
||||
|
||||
const ERROR_PATTERNS = [...ROUTING_ERROR_PATTERNS, ...FEED_ERROR_PATTERNS];
|
||||
|
||||
/** One error text in the UI language, or null when nothing matches. */
|
||||
function feedErrorText(text) {
|
||||
if (FEED_ERRORS.has(text)) return t(text);
|
||||
const known = translateKnown(text);
|
||||
if (known !== text) return known;
|
||||
for (const [pattern, format] of FEED_ERROR_PATTERNS) {
|
||||
for (const [pattern, format] of ERROR_PATTERNS) {
|
||||
const match = pattern.exec(text);
|
||||
if (match) return format(match);
|
||||
}
|
||||
|
||||
@@ -7,10 +7,8 @@ import { setLocale, t, translateKnown } from '../i18n/index.js';
|
||||
import { presentStreetLevelPanel } from './streetLevelPresentation.js';
|
||||
import { providerSnapshot } from '../testSupport/streetLevelFakes.mjs';
|
||||
import { createWeatherSource } from '../layers/weather/source.js';
|
||||
import {
|
||||
presentRoutingFailure,
|
||||
presentRoutingRefusal,
|
||||
} from '../layers/directions/index.js';
|
||||
import { presentRoutingFailure } from '../layers/directions/index.js';
|
||||
import { translateFeedError } from './statusText.js';
|
||||
import { AIM_AT_GROUND_MESSAGE } from '../layers/streetLevel/policy.js';
|
||||
|
||||
const NO_IMAGERY = 'No street-level imagery within 50 m of that point';
|
||||
@@ -53,9 +51,13 @@ for (const locale of ['es', 'ru']) {
|
||||
test(`${locale}: the route proxy's refusals are shown translated`, () => {
|
||||
setLocale(locale);
|
||||
try {
|
||||
const shown = t('Routing failed: {error}', {
|
||||
error: presentRoutingRefusal('route leg too long'),
|
||||
});
|
||||
// Directions stores these in English; the Layers panel shows them
|
||||
// through translateFeedError.
|
||||
const shown = translateFeedError('Routing failed: route leg too long');
|
||||
assert.equal(
|
||||
shown,
|
||||
t('Routing failed: {error}', { error: t('route leg too long') }),
|
||||
);
|
||||
assert.doesNotMatch(shown, /route leg too long/);
|
||||
const limited = presentRoutingFailure(
|
||||
Object.assign(new Error('rate limited — try again in a moment'), {
|
||||
@@ -63,16 +65,46 @@ for (const locale of ['es', 'ru']) {
|
||||
refusal: 'rate limited',
|
||||
}),
|
||||
);
|
||||
assert.doesNotMatch(limited, /rate limited|try again in a moment/);
|
||||
assert.equal(limited, 'rate limited — try again in a moment');
|
||||
assert.doesNotMatch(
|
||||
translateFeedError(limited),
|
||||
/rate limited|try again in a moment/,
|
||||
);
|
||||
const http = presentRoutingFailure(
|
||||
Object.assign(new Error('Routing unavailable (HTTP 503)'), {
|
||||
status: 503,
|
||||
}),
|
||||
);
|
||||
assert.notEqual(http, 'Routing unavailable (HTTP 503)');
|
||||
assert.match(http, /503/);
|
||||
assert.equal(http, 'Routing unavailable (HTTP 503)');
|
||||
assert.notEqual(translateFeedError(http), http);
|
||||
assert.match(translateFeedError(http), /503/);
|
||||
// A text the proxy never sends is shown as given.
|
||||
assert.equal(presentRoutingRefusal('something else'), 'something else');
|
||||
assert.equal(
|
||||
translateFeedError('Routing failed: something else'),
|
||||
t('Routing failed: {error}', { error: 'something else' }),
|
||||
);
|
||||
// Every refusal the proxy sends (server/providers/places/routes.js),
|
||||
// and "rate limited" after "Routing failed:", which the feed pattern
|
||||
// "<source> rate limited" would otherwise take.
|
||||
for (const [english, shown] of [
|
||||
[
|
||||
'routing busy — too many routes at once — try again in a moment',
|
||||
t('{error} — try again in a moment', {
|
||||
error: t('routing busy — too many routes at once'),
|
||||
}),
|
||||
],
|
||||
[
|
||||
'Routing failed: route proxy error',
|
||||
t('Routing failed: {error}', { error: t('route proxy error') }),
|
||||
],
|
||||
[
|
||||
'Routing failed: rate limited',
|
||||
t('Routing failed: {error}', { error: t('rate limited') }),
|
||||
],
|
||||
]) {
|
||||
assert.equal(translateFeedError(english), shown);
|
||||
assert.doesNotMatch(shown, /routing busy|route proxy|rate limited/);
|
||||
}
|
||||
} finally {
|
||||
setLocale('en');
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user