Merge origin/main into feat/tools-area-breakdowns

Resolve the CHANGELOG.md conflict by keeping both new entries: this
branch's group_by/stats entry above main's GBFS v3 station-name entry.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
quanticsoul4772andClaude Opus 5.5 committed 2026-10-14 06:01:04 -07:00
commit 166190b710
64 files changed
+4576 -177

No files matched your search

+5
View File
@@ -37,6 +37,11 @@ const PROVIDER_ORIGINS = Object.freeze([
'https://fonts.googleapis.com',
'https://fonts.gstatic.com',
'https://graph.mapillary.com',
// Google Street View's tiles, controls and contributed photos (the Maps
// JavaScript API itself comes from maps.googleapis.com above).
'https://streetviewpixels-pa.googleapis.com',
'https://maps.gstatic.com',
'https://*.ggpht.com',
// Mapillary photos come from regional hosts, e.g. scontent-man2-1.xx.fbcdn.net.
'https://*.xx.fbcdn.net',
]);
+17
View File
@@ -169,3 +169,20 @@ test('the package exports the panel resource and its runtime together', async ()
assert.equal(panel.PANEL_BASE, '/panel/');
assert.match(resource.text, /panelRuntime|function/);
});
test('the panel lets Street Level reach Google Street View from inside the embed', () => {
const { csp } = createGlobePanelResource({
runtime: panelRuntime,
panelKey: 'test-key',
})._meta.ui;
// The API script and lookups, panorama tiles, control images, contributed photos.
for (const url of [
'https://maps.googleapis.com/maps/api/js?key=k&v=weekly',
'https://streetviewpixels-pa.googleapis.com/v1/tile?panoid=p&x=0&y=0&zoom=1',
'https://maps.gstatic.com/mapfiles/api-3/images/cb_scout5_hdpi.png',
'https://lh3.ggpht.com/p/photo.jpg',
]) {
assert.ok(allows(csp.connectDomains, url), `connect-src allows ${url}`);
assert.ok(allows(csp.resourceDomains, url), `img-src allows ${url}`);
}
});
+28
View File
@@ -106,6 +106,34 @@ test('bike share joins station information with live status inside the area', as
assert.equal(result.data.rows[1].capacity, 10);
});
test('bike share returns localized names without losing stations with malformed names', async () => {
const informationUrl = 'https://gbfs.example/austin/station_information.json';
const information = structuredClone(documents[informationUrl]);
information.version = '3.0';
information.data.stations[0].name = [
{ language: 'hu', text: 'Magyar név' },
{ language: 'en', text: 'Congress & 2nd' },
];
information.data.stations[2].name = [null, { language: 42, text: [] }];
const source = bikeshare([system('austin', 'Austin, TX', 30.27, -97.74)]);
const catalog = composeCatalog({
tools: coreTools,
services: {
bikeshare: {
...source,
getStations: (url) =>
url === informationUrl ? information : source.getStations(url),
},
},
});
const result = await catalog.call('get_bike_share', { area: downtown });
assert.equal(result.data.total, 2);
assert.equal(result.data.rows[0].name, 'Congress & 2nd');
assert.equal(result.data.rows[0].bikes_available, 4);
assert.equal(result.data.rows[1].name, null);
assert.doesNotMatch(result.summary, /unavailable/);
});
test('bike share reports uncovered areas and failed systems honestly', async () => {
const none = composeCatalog({
tools: coreTools,
+21
View File
@@ -30,6 +30,27 @@ test('TLE text parses into named entries with catalog numbers', () => {
assert.equal(tleCatalogNumber('1 xx'), null);
});
for (const missingLine of [1, 2]) {
test(`TLE parsing resumes after a set missing line ${missingLine}`, () => {
const sets = Array.from({ length: 8 }, (_, i) => {
const lines = TLE.trim().split('\n').slice(0, 3);
lines[0] = `SAT ${i}`;
return lines;
});
sets[1].splice(missingLine, 1);
const entries = parseTleText(sets.flat().join('\r\n'));
assert.deepEqual(
entries.map((entry) => entry.name),
['SAT 0', 'SAT 2', 'SAT 3', 'SAT 4', 'SAT 5', 'SAT 6', 'SAT 7'],
);
assert.ok(
entries.every(
(entry) => entry.line1.startsWith('1 ') && entry.line2.startsWith('2 '),
),
);
});
}
test('the next pass defaults to the ISS and reports times, peak and direction', async () => {
const catalog = composeCatalog({
tools: coreTools,