mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
style: format Location component boundary
This commit is contained in:
1 parent
ea77b49482
commit
82d922ae87
7 files changed
+557
-275
No files matched your search
+52
-160
@@ -10,47 +10,27 @@
|
|||||||
"src/data/localGeojsonCore.js",
|
"src/data/localGeojsonCore.js",
|
||||||
"src/data/localGeojsonLod.js"
|
"src/data/localGeojsonLod.js"
|
||||||
],
|
],
|
||||||
"external": [
|
"external": ["cesium"]
|
||||||
"cesium"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"application": {
|
"application": {
|
||||||
"exports": [
|
"exports": ["./application"],
|
||||||
"./application"
|
"modules": ["src/app/application.js"],
|
||||||
],
|
|
||||||
"modules": [
|
|
||||||
"src/app/application.js"
|
|
||||||
],
|
|
||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"viewer": {
|
"viewer": {
|
||||||
"exports": [
|
"exports": ["./application/viewer"],
|
||||||
"./application/viewer"
|
"modules": ["src/app/viewer.js"],
|
||||||
],
|
"external": ["cesium"]
|
||||||
"modules": [
|
|
||||||
"src/app/viewer.js"
|
|
||||||
],
|
|
||||||
"external": [
|
|
||||||
"cesium"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"vite-build": {
|
"vite-build": {
|
||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": [
|
"exports": ["./build/vite"],
|
||||||
"./build/vite"
|
"modules": ["build/vite.js"],
|
||||||
],
|
"external": ["vite-plugin-cesium"]
|
||||||
"modules": [
|
|
||||||
"build/vite.js"
|
|
||||||
],
|
|
||||||
"external": [
|
|
||||||
"vite-plugin-cesium"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"live-providers": {
|
"live-providers": {
|
||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": [
|
"exports": ["./server/providers/live"],
|
||||||
"./server/providers/live"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"server/providers/live.js",
|
"server/providers/live.js",
|
||||||
"server/providers/common/http.js",
|
"server/providers/common/http.js",
|
||||||
@@ -65,24 +45,16 @@
|
|||||||
"src/data/aisStreamAdapter.js",
|
"src/data/aisStreamAdapter.js",
|
||||||
"src/data/aisWatchdog.js"
|
"src/data/aisWatchdog.js"
|
||||||
],
|
],
|
||||||
"external": [
|
"external": ["ws"]
|
||||||
"ws"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"adsb-lol-source": {
|
"adsb-lol-source": {
|
||||||
"exports": [
|
"exports": ["./sources/adsb-lol"],
|
||||||
"./sources/adsb-lol"
|
"modules": ["src/data/adsbLolFallback.js"],
|
||||||
],
|
|
||||||
"modules": [
|
|
||||||
"src/data/adsbLolFallback.js"
|
|
||||||
],
|
|
||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"place-providers": {
|
"place-providers": {
|
||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": [
|
"exports": ["./server/providers/places"],
|
||||||
"./server/providers/places"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"server/providers/places.js",
|
"server/providers/places.js",
|
||||||
"server/providers/places/google.js",
|
"server/providers/places/google.js",
|
||||||
@@ -97,19 +69,13 @@
|
|||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"place-payloads": {
|
"place-payloads": {
|
||||||
"exports": [
|
"exports": ["./sources/places"],
|
||||||
"./sources/places"
|
"modules": ["src/data/placeProviderPayloads.js"],
|
||||||
],
|
|
||||||
"modules": [
|
|
||||||
"src/data/placeProviderPayloads.js"
|
|
||||||
],
|
|
||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"space-providers": {
|
"space-providers": {
|
||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": [
|
"exports": ["./server/providers/space"],
|
||||||
"./server/providers/space"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"server/providers/space.js",
|
"server/providers/space.js",
|
||||||
"server/providers/space/celestrak.js",
|
"server/providers/space/celestrak.js",
|
||||||
@@ -120,19 +86,13 @@
|
|||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"space-requests": {
|
"space-requests": {
|
||||||
"exports": [
|
"exports": ["./sources/space"],
|
||||||
"./sources/space"
|
"modules": ["src/data/spaceProviderRequests.js"],
|
||||||
],
|
|
||||||
"modules": [
|
|
||||||
"src/data/spaceProviderRequests.js"
|
|
||||||
],
|
|
||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"terrain-provider": {
|
"terrain-provider": {
|
||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": [
|
"exports": ["./server/providers/terrain"],
|
||||||
"./server/providers/terrain"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"server/providers/terrain.js",
|
"server/providers/terrain.js",
|
||||||
"src/data/terrainHeightsProxy.js"
|
"src/data/terrainHeightsProxy.js"
|
||||||
@@ -140,50 +100,30 @@
|
|||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"terrain-source": {
|
"terrain-source": {
|
||||||
"exports": [
|
"exports": ["./sources/terrain"],
|
||||||
"./sources/terrain"
|
"modules": ["src/data/terrainHeightsProxy.js"],
|
||||||
],
|
|
||||||
"modules": [
|
|
||||||
"src/data/terrainHeightsProxy.js"
|
|
||||||
],
|
|
||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"traffic-provider": {
|
"traffic-provider": {
|
||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": [
|
"exports": ["./server/providers/traffic"],
|
||||||
"./server/providers/traffic"
|
"modules": ["server/providers/traffic.js", "src/data/tomtomTiles.js"],
|
||||||
],
|
|
||||||
"modules": [
|
|
||||||
"server/providers/traffic.js",
|
|
||||||
"src/data/tomtomTiles.js"
|
|
||||||
],
|
|
||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"traffic-source": {
|
"traffic-source": {
|
||||||
"exports": [
|
"exports": ["./sources/traffic"],
|
||||||
"./sources/traffic"
|
"modules": ["src/data/tomtomTiles.js"],
|
||||||
],
|
|
||||||
"modules": [
|
|
||||||
"src/data/tomtomTiles.js"
|
|
||||||
],
|
|
||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"firms-provider": {
|
"firms-provider": {
|
||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": [
|
"exports": ["./server/providers/firms"],
|
||||||
"./server/providers/firms"
|
"modules": ["server/providers/firms.js", "src/data/firmsCsv.js"],
|
||||||
],
|
|
||||||
"modules": [
|
|
||||||
"server/providers/firms.js",
|
|
||||||
"src/data/firmsCsv.js"
|
|
||||||
],
|
|
||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"gbfs-provider": {
|
"gbfs-provider": {
|
||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": [
|
"exports": ["./server/providers/gbfs"],
|
||||||
"./server/providers/gbfs"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"server/providers/gbfs.js",
|
"server/providers/gbfs.js",
|
||||||
"server/providers/common/http.js",
|
"server/providers/common/http.js",
|
||||||
@@ -192,19 +132,13 @@
|
|||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"gbfs-source": {
|
"gbfs-source": {
|
||||||
"exports": [
|
"exports": ["./sources/gbfs"],
|
||||||
"./sources/gbfs"
|
"modules": ["src/data/gbfsSource.js"],
|
||||||
],
|
|
||||||
"modules": [
|
|
||||||
"src/data/gbfsSource.js"
|
|
||||||
],
|
|
||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"cctv-provider": {
|
"cctv-provider": {
|
||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": [
|
"exports": ["./server/providers/cctv"],
|
||||||
"./server/providers/cctv"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"server/providers/cctv.js",
|
"server/providers/cctv.js",
|
||||||
"server/providers/cctv/catalog.js",
|
"server/providers/cctv/catalog.js",
|
||||||
@@ -221,9 +155,7 @@
|
|||||||
},
|
},
|
||||||
"radio-provider": {
|
"radio-provider": {
|
||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": [
|
"exports": ["./server/providers/radio"],
|
||||||
"./server/providers/radio"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"server/providers/radio.js",
|
"server/providers/radio.js",
|
||||||
"server/providers/radio/catalog.js",
|
"server/providers/radio/catalog.js",
|
||||||
@@ -237,9 +169,7 @@
|
|||||||
},
|
},
|
||||||
"overpass-provider": {
|
"overpass-provider": {
|
||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": [
|
"exports": ["./server/providers/overpass"],
|
||||||
"./server/providers/overpass"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"server/providers/common/geo.js",
|
"server/providers/common/geo.js",
|
||||||
"server/providers/common/http.js",
|
"server/providers/common/http.js",
|
||||||
@@ -258,9 +188,7 @@
|
|||||||
},
|
},
|
||||||
"military-installations-provider": {
|
"military-installations-provider": {
|
||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": [
|
"exports": ["./server/providers/military-installations"],
|
||||||
"./server/providers/military-installations"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"server/providers/common/http.js",
|
"server/providers/common/http.js",
|
||||||
"server/providers/common/query.js",
|
"server/providers/common/query.js",
|
||||||
@@ -277,9 +205,7 @@
|
|||||||
},
|
},
|
||||||
"regional-provider": {
|
"regional-provider": {
|
||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": [
|
"exports": ["./server/providers/regional"],
|
||||||
"./server/providers/regional"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"server/providers/common/http.js",
|
"server/providers/common/http.js",
|
||||||
"server/providers/common/query.js",
|
"server/providers/common/query.js",
|
||||||
@@ -298,9 +224,7 @@
|
|||||||
},
|
},
|
||||||
"openai-provider": {
|
"openai-provider": {
|
||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": [
|
"exports": ["./server/providers/openai"],
|
||||||
"./server/providers/openai"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"server/providers/common/rate-limit.js",
|
"server/providers/common/rate-limit.js",
|
||||||
"server/providers/common/request.js",
|
"server/providers/common/request.js",
|
||||||
@@ -320,9 +244,7 @@
|
|||||||
},
|
},
|
||||||
"standalone-key-setup": {
|
"standalone-key-setup": {
|
||||||
"runtime": "node",
|
"runtime": "node",
|
||||||
"exports": [
|
"exports": ["./server/standalone/key-setup"],
|
||||||
"./server/standalone/key-setup"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"scripts/pinokio-environment.mjs",
|
"scripts/pinokio-environment.mjs",
|
||||||
"server/providers/common/source-root.js",
|
"server/providers/common/source-root.js",
|
||||||
@@ -333,9 +255,7 @@
|
|||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"place-search": {
|
"place-search": {
|
||||||
"exports": [
|
"exports": ["./search"],
|
||||||
"./search"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"src/search/index.js",
|
"src/search/index.js",
|
||||||
"src/search/placeSearch.js",
|
"src/search/placeSearch.js",
|
||||||
@@ -345,27 +265,17 @@
|
|||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"panel-disclosure": {
|
"panel-disclosure": {
|
||||||
"exports": [
|
"exports": ["./ui/panels"],
|
||||||
"./ui/panels"
|
"modules": ["src/ui/panelDisclosure.js"],
|
||||||
],
|
|
||||||
"modules": [
|
|
||||||
"src/ui/panelDisclosure.js"
|
|
||||||
],
|
|
||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"surface-keyboard": {
|
"surface-keyboard": {
|
||||||
"exports": [
|
"exports": ["./ui/surfaces"],
|
||||||
"./ui/surfaces"
|
"modules": ["src/ui/surfaceKeyboard.js"],
|
||||||
],
|
|
||||||
"modules": [
|
|
||||||
"src/ui/surfaceKeyboard.js"
|
|
||||||
],
|
|
||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"panel-rail-layout": {
|
"panel-rail-layout": {
|
||||||
"exports": [
|
"exports": ["./ui/layout"],
|
||||||
"./ui/layout"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"src/ui/panelRails.js",
|
"src/ui/panelRails.js",
|
||||||
"src/ui/leftPanelRail.js",
|
"src/ui/leftPanelRail.js",
|
||||||
@@ -377,9 +287,7 @@
|
|||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"visual-input": {
|
"visual-input": {
|
||||||
"exports": [
|
"exports": ["./ui/input"],
|
||||||
"./ui/input"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"src/ui/visualInput.js",
|
"src/ui/visualInput.js",
|
||||||
"src/ui/applicationShortcuts.js",
|
"src/ui/applicationShortcuts.js",
|
||||||
@@ -388,19 +296,12 @@
|
|||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"display-controls": {
|
"display-controls": {
|
||||||
"exports": [
|
"exports": ["./ui/display"],
|
||||||
"./ui/display"
|
"modules": ["src/ui/displayControls.js"],
|
||||||
],
|
|
||||||
"modules": [
|
|
||||||
"src/ui/displayControls.js"
|
|
||||||
],
|
|
||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"visual-effects": {
|
"visual-effects": {
|
||||||
"exports": [
|
"exports": ["./ui/effects", "./ui/effects/bloom"],
|
||||||
"./ui/effects",
|
|
||||||
"./ui/effects/bloom"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"src/ui/effects.js",
|
"src/ui/effects.js",
|
||||||
"src/ui/visualEffects.js",
|
"src/ui/visualEffects.js",
|
||||||
@@ -413,14 +314,10 @@
|
|||||||
"src/styles/noir.js",
|
"src/styles/noir.js",
|
||||||
"src/styles/snow.js"
|
"src/styles/snow.js"
|
||||||
],
|
],
|
||||||
"external": [
|
"external": ["cesium"]
|
||||||
"cesium"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"map-source-controls": {
|
"map-source-controls": {
|
||||||
"exports": [
|
"exports": ["./ui/maps"],
|
||||||
"./ui/maps"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"src/ui/mapSource.js",
|
"src/ui/mapSource.js",
|
||||||
"src/ui/mapSourceControls.js",
|
"src/ui/mapSourceControls.js",
|
||||||
@@ -430,10 +327,7 @@
|
|||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"layer-panel": {
|
"layer-panel": {
|
||||||
"exports": [
|
"exports": ["./ui/layers", "./ui/layers/feedback"],
|
||||||
"./ui/layers",
|
|
||||||
"./ui/layers/feedback"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"src/ui/layers.js",
|
"src/ui/layers.js",
|
||||||
"src/ui/layerPanel.js",
|
"src/ui/layerPanel.js",
|
||||||
@@ -444,9 +338,7 @@
|
|||||||
"external": []
|
"external": []
|
||||||
},
|
},
|
||||||
"location-controls": {
|
"location-controls": {
|
||||||
"exports": [
|
"exports": ["./ui/location"],
|
||||||
"./ui/location"
|
|
||||||
],
|
|
||||||
"modules": [
|
"modules": [
|
||||||
"src/ui/location.js",
|
"src/ui/location.js",
|
||||||
"src/ui/locationControls.js",
|
"src/ui/locationControls.js",
|
||||||
|
|||||||
@@ -2,53 +2,213 @@
|
|||||||
/** Location UI acceptance; controlled search results isolate UI races from geocoder availability. */
|
/** Location UI acceptance; controlled search results isolate UI races from geocoder availability. */
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import puppeteer from 'puppeteer';
|
import puppeteer from 'puppeteer';
|
||||||
const browser = await puppeteer.launch({headless:true,args:['--no-sandbox',...(process.platform === 'darwin' ? ['--use-angle=metal','--enable-gpu'] : ['--use-gl=angle','--use-angle=swiftshader'])]});
|
const browser = await puppeteer.launch({
|
||||||
const page=await browser.newPage();const errors=[];page.on('pageerror',e=>errors.push(e.message));let failures=0;
|
headless: true,
|
||||||
const check=(name,ok)=>{console.log(`[${ok?'PASS':'FAIL'}] ${name}`);if(!ok)failures++};
|
args: [
|
||||||
|
'--no-sandbox',
|
||||||
|
...(process.platform === 'darwin'
|
||||||
|
? ['--use-angle=metal', '--enable-gpu']
|
||||||
|
: ['--use-gl=angle', '--use-angle=swiftshader']),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const errors = [];
|
||||||
|
page.on('pageerror', (e) => errors.push(e.message));
|
||||||
|
let failures = 0;
|
||||||
|
const check = (name, ok) => {
|
||||||
|
console.log(`[${ok ? 'PASS' : 'FAIL'}] ${name}`);
|
||||||
|
if (!ok) failures++;
|
||||||
|
};
|
||||||
try {
|
try {
|
||||||
await page.setViewport({width:1440,height:900});
|
await page.setViewport({ width: 1440, height: 900 });
|
||||||
await page.goto(`${process.env.QA_BASE_URL||'http://localhost:4173'}/?welcome=0`,{waitUntil:'domcontentloaded'});
|
await page.goto(
|
||||||
await page.waitForFunction(()=>window.__godsEyeView?.styleManager?._locationControls&&document.getElementById('loading-screen')?.classList.contains('hidden'),{timeout:60000});
|
`${process.env.QA_BASE_URL || 'http://localhost:4173'}/?welcome=0`,
|
||||||
|
{ waitUntil: 'domcontentloaded' },
|
||||||
|
);
|
||||||
|
await page.waitForFunction(
|
||||||
|
() =>
|
||||||
|
window.__godsEyeView?.styleManager?._locationControls &&
|
||||||
|
document.getElementById('loading-screen')?.classList.contains('hidden'),
|
||||||
|
{ timeout: 60000 },
|
||||||
|
);
|
||||||
await page.click('#location-bar-toggle');
|
await page.click('#location-bar-toggle');
|
||||||
await page.click('[data-pin-target="location-bar"]');
|
await page.click('[data-pin-target="location-bar"]');
|
||||||
const cityId=await page.$eval('.location-pill',node=>node.dataset.locationId);
|
const cityId = await page.$eval(
|
||||||
|
'.location-pill',
|
||||||
|
(node) => node.dataset.locationId,
|
||||||
|
);
|
||||||
await page.click('.location-pill');
|
await page.click('.location-pill');
|
||||||
await page.waitForFunction(()=>document.getElementById('poi-row').classList.contains('expanded'));
|
await page.waitForFunction(() =>
|
||||||
check('native city selection highlights and expands its POIs',await page.evaluate(id=>window.__godsEyeView.styleManager._activeLocationId===id&&document.querySelector('.location-pill.active')?.dataset.locationId===id,cityId));
|
document.getElementById('poi-row').classList.contains('expanded'),
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
'native city selection highlights and expands its POIs',
|
||||||
|
await page.evaluate(
|
||||||
|
(id) =>
|
||||||
|
window.__godsEyeView.styleManager._activeLocationId === id &&
|
||||||
|
document.querySelector('.location-pill.active')?.dataset.locationId ===
|
||||||
|
id,
|
||||||
|
cityId,
|
||||||
|
),
|
||||||
|
);
|
||||||
await page.keyboard.press('w');
|
await page.keyboard.press('w');
|
||||||
check('POI keyboard action selects the second landmark',await page.evaluate(()=>window.__godsEyeView.styleManager._activePoiIndex===1&&document.querySelector('.poi-pill.active')?.dataset.poiIndex==='1'));
|
check(
|
||||||
check('orbit controls and indicator agree',await page.evaluate(()=>{const ui=window.__godsEyeView.styleManager;ui.setOrbit(true);const active=ui.orbitController.active&&document.getElementById('orbit-indicator').classList.contains('active');ui.setOrbit(false);return active&&!ui.orbitController.active&&!document.getElementById('orbit-indicator').classList.contains('active')}));
|
'POI keyboard action selects the second landmark',
|
||||||
|
await page.evaluate(
|
||||||
|
() =>
|
||||||
|
window.__godsEyeView.styleManager._activePoiIndex === 1 &&
|
||||||
|
document.querySelector('.poi-pill.active')?.dataset.poiIndex === '1',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
'orbit controls and indicator agree',
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const ui = window.__godsEyeView.styleManager;
|
||||||
|
ui.setOrbit(true);
|
||||||
|
const active =
|
||||||
|
ui.orbitController.active &&
|
||||||
|
document.getElementById('orbit-indicator').classList.contains('active');
|
||||||
|
ui.setOrbit(false);
|
||||||
|
return (
|
||||||
|
active &&
|
||||||
|
!ui.orbitController.active &&
|
||||||
|
!document.getElementById('orbit-indicator').classList.contains('active')
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
await page.click('#search-toggle');
|
await page.click('#search-toggle');
|
||||||
await page.type('#location-search','qwerty');
|
await page.type('#location-search', 'qwerty');
|
||||||
check('typing in search leaves POI selection unchanged',await page.evaluate(()=>window.__godsEyeView.styleManager._activePoiIndex===1));
|
check(
|
||||||
await page.evaluate(()=>{
|
'typing in search leaves POI selection unchanged',
|
||||||
const lookup=window.__godsEyeView.styleManager._locationLookup;
|
await page.evaluate(
|
||||||
window.__qaSearchRequests=[];
|
() => window.__godsEyeView.styleManager._activePoiIndex === 1,
|
||||||
lookup.search=(query,options)=>new Promise(resolve=>window.__qaSearchRequests.push({query,options,resolve}));
|
),
|
||||||
document.getElementById('location-search').value='First';
|
);
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const lookup = window.__godsEyeView.styleManager._locationLookup;
|
||||||
|
window.__qaSearchRequests = [];
|
||||||
|
lookup.search = (query, options) =>
|
||||||
|
new Promise((resolve) =>
|
||||||
|
window.__qaSearchRequests.push({ query, options, resolve }),
|
||||||
|
);
|
||||||
|
document.getElementById('location-search').value = 'First';
|
||||||
});
|
});
|
||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
await page.waitForFunction(()=>window.__qaSearchRequests.length===1);
|
await page.waitForFunction(() => window.__qaSearchRequests.length === 1);
|
||||||
await page.evaluate(()=>{document.getElementById('location-search').value='Second';document.getElementById('location-search').focus()});
|
await page.evaluate(() => {
|
||||||
|
document.getElementById('location-search').value = 'Second';
|
||||||
|
document.getElementById('location-search').focus();
|
||||||
|
});
|
||||||
await page.keyboard.press('Enter');
|
await page.keyboard.press('Enter');
|
||||||
await page.waitForFunction(()=>window.__qaSearchRequests.length===2);
|
await page.waitForFunction(() => window.__qaSearchRequests.length === 2);
|
||||||
check('second native submit aborts the first request and keeps current busy state',await page.evaluate(()=>window.__qaSearchRequests[0].options.signal.aborted&&document.getElementById('location-search').classList.contains('searching')));
|
check(
|
||||||
await page.evaluate(()=>window.__qaSearchRequests[1].resolve({label:'Second landmark, Test city'}));
|
'second native submit aborts the first request and keeps current busy state',
|
||||||
await page.waitForFunction(()=>window.__godsEyeView.styleManager._searchedLocationLabel==='Second landmark, Test city');
|
await page.evaluate(
|
||||||
await page.evaluate(()=>window.__qaSearchRequests[0].resolve({label:'First landmark, Test city'}));
|
() =>
|
||||||
check('late result cannot overwrite the current location readout or style',await page.evaluate(()=>{const ui=window.__godsEyeView.styleManager;return ui._searchedLocationLabel==='Second landmark, Test city'&&ui._locationMiniPoi.textContent.includes('Second landmark')&&ui.activeStyle==='normal'&&!ui._locationSearch.classList.contains('searching')}));
|
window.__qaSearchRequests[0].options.signal.aborted &&
|
||||||
check('cancelled POI expansion stays closed after a frame',await page.evaluate(async id=>{const ui=window.__godsEyeView.styleManager;ui._expandPOIRow(id);ui._collapsePOIRow();await new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve)));return !ui._poiRow.classList.contains('expanded')&&!ui._locationBarDivider.classList.contains('visible')},cityId));
|
document
|
||||||
await page.evaluate(id=>window.__godsEyeView.styleManager._onCityPillClick(id),cityId);
|
.getElementById('location-search')
|
||||||
await page.waitForFunction(()=>document.getElementById('poi-row').classList.contains('expanded'));
|
.classList.contains('searching'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await page.evaluate(() =>
|
||||||
|
window.__qaSearchRequests[1].resolve({
|
||||||
|
label: 'Second landmark, Test city',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
await page.waitForFunction(
|
||||||
|
() =>
|
||||||
|
window.__godsEyeView.styleManager._searchedLocationLabel ===
|
||||||
|
'Second landmark, Test city',
|
||||||
|
);
|
||||||
|
await page.evaluate(() =>
|
||||||
|
window.__qaSearchRequests[0].resolve({
|
||||||
|
label: 'First landmark, Test city',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
'late result cannot overwrite the current location readout or style',
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const ui = window.__godsEyeView.styleManager;
|
||||||
|
return (
|
||||||
|
ui._searchedLocationLabel === 'Second landmark, Test city' &&
|
||||||
|
ui._locationMiniPoi.textContent.includes('Second landmark') &&
|
||||||
|
ui.activeStyle === 'normal' &&
|
||||||
|
!ui._locationSearch.classList.contains('searching')
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
'cancelled POI expansion stays closed after a frame',
|
||||||
|
await page.evaluate(async (id) => {
|
||||||
|
const ui = window.__godsEyeView.styleManager;
|
||||||
|
ui._expandPOIRow(id);
|
||||||
|
ui._collapsePOIRow();
|
||||||
|
await new Promise((resolve) =>
|
||||||
|
requestAnimationFrame(() => requestAnimationFrame(resolve)),
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
!ui._poiRow.classList.contains('expanded') &&
|
||||||
|
!ui._locationBarDivider.classList.contains('visible')
|
||||||
|
);
|
||||||
|
}, cityId),
|
||||||
|
);
|
||||||
|
await page.evaluate(
|
||||||
|
(id) => window.__godsEyeView.styleManager._onCityPillClick(id),
|
||||||
|
cityId,
|
||||||
|
);
|
||||||
|
await page.waitForFunction(() =>
|
||||||
|
document.getElementById('poi-row').classList.contains('expanded'),
|
||||||
|
);
|
||||||
// Finish the camera's existing city flight before inspecting both layouts.
|
// Finish the camera's existing city flight before inspecting both layouts.
|
||||||
await page.waitForFunction(()=>!window.__godsEyeView.viewer.camera._currentFlight,{timeout:10000});
|
await page.waitForFunction(
|
||||||
fs.mkdirSync('qa-shots/location-controls',{recursive:true});
|
() => !window.__godsEyeView.viewer.camera._currentFlight,
|
||||||
await page.screenshot({path:'qa-shots/location-controls/desktop.png'});
|
{ timeout: 10000 },
|
||||||
await page.setViewport({width:620,height:900});
|
);
|
||||||
await page.waitForFunction(()=>document.getElementById('left-panel-stack')?.dataset.layoutMode==='mobile');
|
fs.mkdirSync('qa-shots/location-controls', { recursive: true });
|
||||||
await page.screenshot({path:'qa-shots/location-controls/narrow.png'});
|
await page.screenshot({ path: 'qa-shots/location-controls/desktop.png' });
|
||||||
check('native reset returns through the existing camera handoff',await page.evaluate(async()=>{const ui=window.__godsEyeView.styleManager;ui._resetGlobeBtn.click();const pending=ui._globeResetPromise;if(!pending)return false;await pending;return !ui.orbitController.active&&!window.__godsEyeView.viewer.trackedEntity}));
|
await page.setViewport({ width: 620, height: 900 });
|
||||||
check('destroyed controls cannot navigate or submit another lookup',await page.evaluate(()=>{const ui=window.__godsEyeView.styleManager;const count=window.__qaSearchRequests.length;ui._locationControls.destroy();ui._locationLookup.destroy();const before=ui._navigationGeneration;ui._locationPills.querySelector('button').click();ui._resetGlobeBtn.click();ui._locationSearch.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',bubbles:true}));return before===ui._navigationGeneration&&count===window.__qaSearchRequests.length}));
|
await page.waitForFunction(
|
||||||
check('no uncaught browser errors',errors.length===0);
|
() =>
|
||||||
}finally{await browser.close()}
|
document.getElementById('left-panel-stack')?.dataset.layoutMode ===
|
||||||
console.log(`RESULT: ${failures} failures`);process.exitCode=failures?1:0;
|
'mobile',
|
||||||
|
);
|
||||||
|
await page.screenshot({ path: 'qa-shots/location-controls/narrow.png' });
|
||||||
|
check(
|
||||||
|
'native reset returns through the existing camera handoff',
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
const ui = window.__godsEyeView.styleManager;
|
||||||
|
ui._resetGlobeBtn.click();
|
||||||
|
const pending = ui._globeResetPromise;
|
||||||
|
if (!pending) return false;
|
||||||
|
await pending;
|
||||||
|
return (
|
||||||
|
!ui.orbitController.active && !window.__godsEyeView.viewer.trackedEntity
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
'destroyed controls cannot navigate or submit another lookup',
|
||||||
|
await page.evaluate(() => {
|
||||||
|
const ui = window.__godsEyeView.styleManager;
|
||||||
|
const count = window.__qaSearchRequests.length;
|
||||||
|
ui._locationControls.destroy();
|
||||||
|
ui._locationLookup.destroy();
|
||||||
|
const before = ui._navigationGeneration;
|
||||||
|
ui._locationPills.querySelector('button').click();
|
||||||
|
ui._resetGlobeBtn.click();
|
||||||
|
ui._locationSearch.dispatchEvent(
|
||||||
|
new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }),
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
before === ui._navigationGeneration &&
|
||||||
|
count === window.__qaSearchRequests.length
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
check('no uncaught browser errors', errors.length === 0);
|
||||||
|
} finally {
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
console.log(`RESULT: ${failures} failures`);
|
||||||
|
process.exitCode = failures ? 1 : 0;
|
||||||
@@ -55,7 +55,10 @@ export function locationMiniStatus({
|
|||||||
// The remaining address is the place's context ("Japan", "Minato City,
|
// The remaining address is the place's context ("Japan", "Minato City,
|
||||||
// Tokyo, Japan"); the readout is ellipsised in CSS, so a long tail is
|
// Tokyo, Japan"); the readout is ellipsised in CSS, so a long tail is
|
||||||
// safe. A one-segment geocode ("Japan") has no context to show.
|
// safe. A one-segment geocode ("Japan") has no context to show.
|
||||||
poi: segments.length > 1 ? segments.slice(1).join(', ') : 'Searched location',
|
poi:
|
||||||
|
segments.length > 1
|
||||||
|
? segments.slice(1).join(', ')
|
||||||
|
: 'Searched location',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+58
-14
@@ -3,8 +3,30 @@ const POI_KEYS = ['Q', 'W', 'E', 'R', 'T'];
|
|||||||
|
|
||||||
/** Location DOM, keyboard handling and pending row animation over supplied actions. */
|
/** Location DOM, keyboard handling and pending row animation over supplied actions. */
|
||||||
export class LocationControls {
|
export class LocationControls {
|
||||||
constructor({ elements, cities, getExpandedCity, onCity, onPoi, onSearch, onReset, doc = document, requestFrame = requestAnimationFrame, cancelFrame = cancelAnimationFrame }) {
|
constructor({
|
||||||
Object.assign(this, { elements, cities, getExpandedCity, onCity, onPoi, onSearch, onReset, doc, requestFrame, cancelFrame });
|
elements,
|
||||||
|
cities,
|
||||||
|
getExpandedCity,
|
||||||
|
onCity,
|
||||||
|
onPoi,
|
||||||
|
onSearch,
|
||||||
|
onReset,
|
||||||
|
doc = document,
|
||||||
|
requestFrame = requestAnimationFrame,
|
||||||
|
cancelFrame = cancelAnimationFrame,
|
||||||
|
}) {
|
||||||
|
Object.assign(this, {
|
||||||
|
elements,
|
||||||
|
cities,
|
||||||
|
getExpandedCity,
|
||||||
|
onCity,
|
||||||
|
onPoi,
|
||||||
|
onSearch,
|
||||||
|
onReset,
|
||||||
|
doc,
|
||||||
|
requestFrame,
|
||||||
|
cancelFrame,
|
||||||
|
});
|
||||||
this.removers = [];
|
this.removers = [];
|
||||||
this.poiRemovers = [];
|
this.poiRemovers = [];
|
||||||
this.frame = null;
|
this.frame = null;
|
||||||
@@ -20,24 +42,34 @@ export class LocationControls {
|
|||||||
this.bind(pill, 'click', () => onCity(id));
|
this.bind(pill, 'click', () => onCity(id));
|
||||||
elements.pills.appendChild(pill);
|
elements.pills.appendChild(pill);
|
||||||
}
|
}
|
||||||
this.bind(doc, 'keydown', event => {
|
this.bind(doc, 'keydown', (event) => {
|
||||||
const cityId = getExpandedCity();
|
const cityId = getExpandedCity();
|
||||||
if (!cityId || event.target?.matches?.('select, input, textarea') || event.target === elements.search) return;
|
if (
|
||||||
|
!cityId ||
|
||||||
|
event.target?.matches?.('select, input, textarea') ||
|
||||||
|
event.target === elements.search
|
||||||
|
)
|
||||||
|
return;
|
||||||
const index = POI_KEYS.indexOf(event.key.toUpperCase());
|
const index = POI_KEYS.indexOf(event.key.toUpperCase());
|
||||||
if (index !== -1 && index < (cities[cityId]?.pois.length || 0)) onPoi(cityId, index);
|
if (index !== -1 && index < (cities[cityId]?.pois.length || 0))
|
||||||
|
onPoi(cityId, index);
|
||||||
});
|
});
|
||||||
this.bind(elements.searchToggle, 'click', () => {
|
this.bind(elements.searchToggle, 'click', () => {
|
||||||
elements.search.classList.toggle('expanded');
|
elements.search.classList.toggle('expanded');
|
||||||
if (elements.search.classList.contains('expanded')) elements.search.focus();
|
if (elements.search.classList.contains('expanded'))
|
||||||
|
elements.search.focus();
|
||||||
});
|
});
|
||||||
this.bind(elements.search, 'keydown', event => {
|
this.bind(elements.search, 'keydown', (event) => {
|
||||||
if (event.key === 'Enter') void onSearch(elements.search.value);
|
if (event.key === 'Enter') void onSearch(elements.search.value);
|
||||||
});
|
});
|
||||||
for (const button of elements.resetButtons) this.bind(button, 'click', onReset);
|
for (const button of elements.resetButtons)
|
||||||
|
this.bind(button, 'click', onReset);
|
||||||
}
|
}
|
||||||
bind(element, event, handler, removers = this.removers) {
|
bind(element, event, handler, removers = this.removers) {
|
||||||
if (!element) return;
|
if (!element) return;
|
||||||
const listener = (...args) => { if (!this.destroyed) return handler(...args); };
|
const listener = (...args) => {
|
||||||
|
if (!this.destroyed) return handler(...args);
|
||||||
|
};
|
||||||
element.addEventListener(event, listener);
|
element.addEventListener(event, listener);
|
||||||
removers.push(() => element.removeEventListener(event, listener));
|
removers.push(() => element.removeEventListener(event, listener));
|
||||||
}
|
}
|
||||||
@@ -66,7 +98,12 @@ export class LocationControls {
|
|||||||
label.className = 'poi-pill-name';
|
label.className = 'poi-pill-name';
|
||||||
label.textContent = poi.name;
|
label.textContent = poi.name;
|
||||||
pill.append(key, label);
|
pill.append(key, label);
|
||||||
this.bind(pill, 'click', () => this.onPoi(cityId, index), this.poiRemovers);
|
this.bind(
|
||||||
|
pill,
|
||||||
|
'click',
|
||||||
|
() => this.onPoi(cityId, index),
|
||||||
|
this.poiRemovers,
|
||||||
|
);
|
||||||
this.elements.poiRow.appendChild(pill);
|
this.elements.poiRow.appendChild(pill);
|
||||||
});
|
});
|
||||||
this.frame = this.requestFrame(() => {
|
this.frame = this.requestFrame(() => {
|
||||||
@@ -84,14 +121,17 @@ export class LocationControls {
|
|||||||
}
|
}
|
||||||
highlightPoi(index) {
|
highlightPoi(index) {
|
||||||
if (this.destroyed) return;
|
if (this.destroyed) return;
|
||||||
for (const pill of this.elements.poiRow.querySelectorAll('.poi-pill')) pill.classList.toggle('active', Number(pill.dataset.poiIndex) === index);
|
for (const pill of this.elements.poiRow.querySelectorAll('.poi-pill'))
|
||||||
|
pill.classList.toggle('active', Number(pill.dataset.poiIndex) === index);
|
||||||
}
|
}
|
||||||
highlightCity(id) {
|
highlightCity(id) {
|
||||||
if (this.destroyed) return;
|
if (this.destroyed) return;
|
||||||
for (const pill of this.elements.pills.querySelectorAll('.location-pill')) pill.classList.toggle('active', pill.dataset.locationId === id);
|
for (const pill of this.elements.pills.querySelectorAll('.location-pill'))
|
||||||
|
pill.classList.toggle('active', pill.dataset.locationId === id);
|
||||||
}
|
}
|
||||||
renderStatus(state) {
|
renderStatus(state) {
|
||||||
if (this.destroyed || !this.elements.statusCity || !this.elements.statusPoi) return;
|
if (this.destroyed || !this.elements.statusCity || !this.elements.statusPoi)
|
||||||
|
return;
|
||||||
const lines = locationMiniStatus(state);
|
const lines = locationMiniStatus(state);
|
||||||
this.elements.statusCity.textContent = lines.city;
|
this.elements.statusCity.textContent = lines.city;
|
||||||
this.elements.statusPoi.textContent = lines.poi;
|
this.elements.statusPoi.textContent = lines.poi;
|
||||||
@@ -113,7 +153,11 @@ export class LocationControls {
|
|||||||
if (this.destroyed) return;
|
if (this.destroyed) return;
|
||||||
this.destroyed = true;
|
this.destroyed = true;
|
||||||
this.cancelExpansion();
|
this.cancelExpansion();
|
||||||
for (const remove of [...this.poiRemovers.splice(0), ...this.removers.splice(0)]) remove();
|
for (const remove of [
|
||||||
|
...this.poiRemovers.splice(0),
|
||||||
|
...this.removers.splice(0),
|
||||||
|
])
|
||||||
|
remove();
|
||||||
this.orbitIndicator?.remove();
|
this.orbitIndicator?.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,40 +5,145 @@ import { LocationControls } from './locationControls.js';
|
|||||||
function node() {
|
function node() {
|
||||||
const classes = new Set();
|
const classes = new Set();
|
||||||
return {
|
return {
|
||||||
children: [], dataset: {}, listeners: new Map(), className: '', textContent: '',
|
children: [],
|
||||||
classList: { add: (...names) => names.forEach(name => classes.add(name)), remove: (...names) => names.forEach(name => classes.delete(name)), contains: name => classes.has(name), toggle(name, enabled = !classes.has(name)) { if(enabled) classes.add(name); else classes.delete(name); } },
|
dataset: {},
|
||||||
addEventListener(name, callback) { if(!this.listeners.has(name)) this.listeners.set(name,new Set()); this.listeners.get(name).add(callback); },
|
listeners: new Map(),
|
||||||
removeEventListener(name, callback) { this.listeners.get(name)?.delete(callback); },
|
className: '',
|
||||||
fire(name,event={}) { for(const callback of this.listeners.get(name)||[]) callback(event); },
|
textContent: '',
|
||||||
appendChild(child) { this.children.push(child); child.parent=this; },
|
classList: {
|
||||||
append(...children) { for(const child of children) if(typeof child!=='string') this.appendChild(child); },
|
add: (...names) => names.forEach((name) => classes.add(name)),
|
||||||
replaceChildren() { this.children=[]; },
|
remove: (...names) => names.forEach((name) => classes.delete(name)),
|
||||||
remove() { if(this.parent) this.parent.children=this.parent.children.filter(child=>child!==this); },
|
contains: (name) => classes.has(name),
|
||||||
querySelectorAll(selector) { return this.children.flatMap(child=>[...(child.className===selector.slice(1)?[child]:[]),...child.querySelectorAll(selector)]); },
|
toggle(name, enabled = !classes.has(name)) {
|
||||||
focus() { this.focused=true; },
|
if (enabled) classes.add(name);
|
||||||
|
else classes.delete(name);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
addEventListener(name, callback) {
|
||||||
|
if (!this.listeners.has(name)) this.listeners.set(name, new Set());
|
||||||
|
this.listeners.get(name).add(callback);
|
||||||
|
},
|
||||||
|
removeEventListener(name, callback) {
|
||||||
|
this.listeners.get(name)?.delete(callback);
|
||||||
|
},
|
||||||
|
fire(name, event = {}) {
|
||||||
|
for (const callback of this.listeners.get(name) || []) callback(event);
|
||||||
|
},
|
||||||
|
appendChild(child) {
|
||||||
|
this.children.push(child);
|
||||||
|
child.parent = this;
|
||||||
|
},
|
||||||
|
append(...children) {
|
||||||
|
for (const child of children)
|
||||||
|
if (typeof child !== 'string') this.appendChild(child);
|
||||||
|
},
|
||||||
|
replaceChildren() {
|
||||||
|
this.children = [];
|
||||||
|
},
|
||||||
|
remove() {
|
||||||
|
if (this.parent)
|
||||||
|
this.parent.children = this.parent.children.filter(
|
||||||
|
(child) => child !== this,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
querySelectorAll(selector) {
|
||||||
|
return this.children.flatMap((child) => [
|
||||||
|
...(child.className === selector.slice(1) ? [child] : []),
|
||||||
|
...child.querySelectorAll(selector),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
focus() {
|
||||||
|
this.focused = true;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
function fixture() {
|
function fixture() {
|
||||||
const elements={pills:node(),poiRow:node(),divider:node(),search:node(),searchToggle:node(),resetButtons:[node(),node()],statusCity:node(),statusPoi:node()};
|
const elements = {
|
||||||
const doc=node();doc.createElement=node;doc.body=node();
|
pills: node(),
|
||||||
const cities={a:{name:'City A',pois:[{name:'First'},{name:'Second'}]},b:{name:'City B',pois:[{name:'Elsewhere'}]}};
|
poiRow: node(),
|
||||||
const calls=[];const frames=new Map();const cancelled=[];let next=0;
|
divider: node(),
|
||||||
const controls=new LocationControls({elements,cities,getExpandedCity:()=> 'a',onCity:id=>calls.push(['city',id]),onPoi:(id,index)=>calls.push(['poi',id,index]),onSearch:query=>calls.push(['search',query]),onReset:()=>calls.push(['reset']),doc,requestFrame:fn=>{const id=next++;frames.set(id,fn);return id},cancelFrame:id=>cancelled.push(id)});
|
search: node(),
|
||||||
return {elements,doc,controls,calls,frames,cancelled};
|
searchToggle: node(),
|
||||||
|
resetButtons: [node(), node()],
|
||||||
|
statusCity: node(),
|
||||||
|
statusPoi: node(),
|
||||||
|
};
|
||||||
|
const doc = node();
|
||||||
|
doc.createElement = node;
|
||||||
|
doc.body = node();
|
||||||
|
const cities = {
|
||||||
|
a: { name: 'City A', pois: [{ name: 'First' }, { name: 'Second' }] },
|
||||||
|
b: { name: 'City B', pois: [{ name: 'Elsewhere' }] },
|
||||||
|
};
|
||||||
|
const calls = [];
|
||||||
|
const frames = new Map();
|
||||||
|
const cancelled = [];
|
||||||
|
let next = 0;
|
||||||
|
const controls = new LocationControls({
|
||||||
|
elements,
|
||||||
|
cities,
|
||||||
|
getExpandedCity: () => 'a',
|
||||||
|
onCity: (id) => calls.push(['city', id]),
|
||||||
|
onPoi: (id, index) => calls.push(['poi', id, index]),
|
||||||
|
onSearch: (query) => calls.push(['search', query]),
|
||||||
|
onReset: () => calls.push(['reset']),
|
||||||
|
doc,
|
||||||
|
requestFrame: (fn) => {
|
||||||
|
const id = next++;
|
||||||
|
frames.set(id, fn);
|
||||||
|
return id;
|
||||||
|
},
|
||||||
|
cancelFrame: (id) => cancelled.push(id),
|
||||||
|
});
|
||||||
|
return { elements, doc, controls, calls, frames, cancelled };
|
||||||
}
|
}
|
||||||
test('hiding a POI row cancels frame zero and rejects an already queued expansion', () => {
|
test('hiding a POI row cancels frame zero and rejects an already queued expansion', () => {
|
||||||
const f=fixture();f.controls.showPois('a');const callback=f.frames.get(0);f.controls.hidePois();callback();
|
const f = fixture();
|
||||||
assert.deepEqual(f.cancelled,[0]);assert.equal(f.elements.poiRow.classList.contains('expanded'),false);assert.equal(f.elements.divider.classList.contains('visible'),false);
|
f.controls.showPois('a');
|
||||||
|
const callback = f.frames.get(0);
|
||||||
|
f.controls.hidePois();
|
||||||
|
callback();
|
||||||
|
assert.deepEqual(f.cancelled, [0]);
|
||||||
|
assert.equal(f.elements.poiRow.classList.contains('expanded'), false);
|
||||||
|
assert.equal(f.elements.divider.classList.contains('visible'), false);
|
||||||
});
|
});
|
||||||
test('replacing POIs removes old click actions and presents the final row', () => {
|
test('replacing POIs removes old click actions and presents the final row', () => {
|
||||||
const f=fixture();f.controls.showPois('a');const old=f.elements.poiRow.children[0];f.controls.showPois('b');old.fire('click');
|
const f = fixture();
|
||||||
assert.deepEqual(f.calls,[]);f.elements.poiRow.children[0].fire('click');assert.deepEqual(f.calls,[['poi','b',0]]);
|
f.controls.showPois('a');
|
||||||
f.frames.get(0)();assert.equal(f.elements.poiRow.classList.contains('expanded'),false);f.frames.get(1)();assert.equal(f.elements.poiRow.classList.contains('expanded'),true);
|
const old = f.elements.poiRow.children[0];
|
||||||
|
f.controls.showPois('b');
|
||||||
|
old.fire('click');
|
||||||
|
assert.deepEqual(f.calls, []);
|
||||||
|
f.elements.poiRow.children[0].fire('click');
|
||||||
|
assert.deepEqual(f.calls, [['poi', 'b', 0]]);
|
||||||
|
f.frames.get(0)();
|
||||||
|
assert.equal(f.elements.poiRow.classList.contains('expanded'), false);
|
||||||
|
f.frames.get(1)();
|
||||||
|
assert.equal(f.elements.poiRow.classList.contains('expanded'), true);
|
||||||
});
|
});
|
||||||
test('destruction revokes document, search, reset, city and POI actions and removes orbit UI', () => {
|
test('destruction revokes document, search, reset, city and POI actions and removes orbit UI', () => {
|
||||||
const f=fixture();f.controls.showPois('a');f.controls.createOrbitIndicator();assert.equal(f.doc.body.children.length,1);const city=f.elements.pills.children[0];const poi=f.elements.poiRow.children[0];f.controls.destroy();f.controls.destroy();
|
const f = fixture();
|
||||||
city.fire('click');poi.fire('click');f.elements.search.value='Place';f.elements.search.fire('keydown',{key:'Enter'});f.doc.fire('keydown',{key:'Q'});f.elements.resetButtons[0].fire('click');f.frames.get(0)();assert.deepEqual(f.calls,[]);assert.equal(f.doc.body.children.length,0);
|
f.controls.showPois('a');
|
||||||
|
f.controls.createOrbitIndicator();
|
||||||
|
assert.equal(f.doc.body.children.length, 1);
|
||||||
|
const city = f.elements.pills.children[0];
|
||||||
|
const poi = f.elements.poiRow.children[0];
|
||||||
|
f.controls.destroy();
|
||||||
|
f.controls.destroy();
|
||||||
|
city.fire('click');
|
||||||
|
poi.fire('click');
|
||||||
|
f.elements.search.value = 'Place';
|
||||||
|
f.elements.search.fire('keydown', { key: 'Enter' });
|
||||||
|
f.doc.fire('keydown', { key: 'Q' });
|
||||||
|
f.elements.resetButtons[0].fire('click');
|
||||||
|
f.frames.get(0)();
|
||||||
|
assert.deepEqual(f.calls, []);
|
||||||
|
assert.equal(f.doc.body.children.length, 0);
|
||||||
});
|
});
|
||||||
test('location and POI keys route once while form controls retain typing', () => {
|
test('location and POI keys route once while form controls retain typing', () => {
|
||||||
const f=fixture();f.doc.fire('keydown',{key:'W',target:{matches:()=>false}});f.doc.fire('keydown',{key:'Q',target:{matches:()=>true}});f.elements.resetButtons[1].fire('click');assert.deepEqual(f.calls,[['poi','a',1],['reset']]);
|
const f = fixture();
|
||||||
|
f.doc.fire('keydown', { key: 'W', target: { matches: () => false } });
|
||||||
|
f.doc.fire('keydown', { key: 'Q', target: { matches: () => true } });
|
||||||
|
f.elements.resetButtons[1].fire('click');
|
||||||
|
assert.deepEqual(f.calls, [['poi', 'a', 1], ['reset']]);
|
||||||
});
|
});
|
||||||
@@ -1,7 +1,29 @@
|
|||||||
/** One cancellable place lookup at a time, under the caller's camera authority. */
|
/** One cancellable place lookup at a time, under the caller's camera authority. */
|
||||||
export class LocationSearch {
|
export class LocationSearch {
|
||||||
constructor({ input, begin, isCurrent, beforeFly, search, onStart, onResult, onMissing, onError, onSettled }) {
|
constructor({
|
||||||
Object.assign(this, { input, begin, isCurrent, beforeFly, search, onStart, onResult, onMissing, onError, onSettled });
|
input,
|
||||||
|
begin,
|
||||||
|
isCurrent,
|
||||||
|
beforeFly,
|
||||||
|
search,
|
||||||
|
onStart,
|
||||||
|
onResult,
|
||||||
|
onMissing,
|
||||||
|
onError,
|
||||||
|
onSettled,
|
||||||
|
}) {
|
||||||
|
Object.assign(this, {
|
||||||
|
input,
|
||||||
|
begin,
|
||||||
|
isCurrent,
|
||||||
|
beforeFly,
|
||||||
|
search,
|
||||||
|
onStart,
|
||||||
|
onResult,
|
||||||
|
onMissing,
|
||||||
|
onError,
|
||||||
|
onSettled,
|
||||||
|
});
|
||||||
this.controller = null;
|
this.controller = null;
|
||||||
this.generation = 0;
|
this.generation = 0;
|
||||||
this.destroyed = false;
|
this.destroyed = false;
|
||||||
@@ -19,7 +41,10 @@ export class LocationSearch {
|
|||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
this.controller = controller;
|
this.controller = controller;
|
||||||
const generation = ++this.generation;
|
const generation = ++this.generation;
|
||||||
const current = () => !this.destroyed && generation === this.generation && this.isCurrent(authority);
|
const current = () =>
|
||||||
|
!this.destroyed &&
|
||||||
|
generation === this.generation &&
|
||||||
|
this.isCurrent(authority);
|
||||||
this.onStart(authority);
|
this.onStart(authority);
|
||||||
this.input.classList.add('searching');
|
this.input.classList.add('searching');
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -6,52 +6,105 @@ function fixture() {
|
|||||||
const calls = [];
|
const calls = [];
|
||||||
const requests = [];
|
const requests = [];
|
||||||
let authority = 0;
|
let authority = 0;
|
||||||
const input = { classList: { add: name => calls.push(['add',name]), remove: name => calls.push(['remove',name]) }, blur: () => calls.push(['blur']) };
|
const input = {
|
||||||
|
classList: {
|
||||||
|
add: (name) => calls.push(['add', name]),
|
||||||
|
remove: (name) => calls.push(['remove', name]),
|
||||||
|
},
|
||||||
|
blur: () => calls.push(['blur']),
|
||||||
|
};
|
||||||
const search = new LocationSearch({
|
const search = new LocationSearch({
|
||||||
input,
|
input,
|
||||||
begin: () => ++authority,
|
begin: () => ++authority,
|
||||||
isCurrent: ticket => ticket === authority,
|
isCurrent: (ticket) => ticket === authority,
|
||||||
beforeFly: ticket => { calls.push(['fly',ticket]); return true; },
|
beforeFly: (ticket) => {
|
||||||
search: (query, options) => new Promise((resolve,reject) => requests.push({query,options,resolve,reject})),
|
calls.push(['fly', ticket]);
|
||||||
onStart: ticket => calls.push(['start',ticket]),
|
return true;
|
||||||
onResult: (result,query) => calls.push(['result',result,query]),
|
},
|
||||||
|
search: (query, options) =>
|
||||||
|
new Promise((resolve, reject) =>
|
||||||
|
requests.push({ query, options, resolve, reject }),
|
||||||
|
),
|
||||||
|
onStart: (ticket) => calls.push(['start', ticket]),
|
||||||
|
onResult: (result, query) => calls.push(['result', result, query]),
|
||||||
onMissing: () => calls.push(['missing']),
|
onMissing: () => calls.push(['missing']),
|
||||||
onError: error => calls.push(['error',error.message]),
|
onError: (error) => calls.push(['error', error.message]),
|
||||||
onSettled: ticket => calls.push(['settled',ticket]),
|
onSettled: (ticket) => calls.push(['settled', ticket]),
|
||||||
});
|
});
|
||||||
return {search,requests,calls,takeCamera:()=>++authority};
|
return { search, requests, calls, takeCamera: () => ++authority };
|
||||||
}
|
}
|
||||||
|
|
||||||
test('a newer lookup aborts its predecessor and only its result is presented', async () => {
|
test('a newer lookup aborts its predecessor and only its result is presented', async () => {
|
||||||
const f=fixture();const first=f.search.run(' First ');const second=f.search.run('Second');
|
const f = fixture();
|
||||||
assert.equal(f.requests[0].query,'First');assert.equal(f.requests[0].options.signal.aborted,true);
|
const first = f.search.run(' First ');
|
||||||
assert.equal(f.requests[0].options.beforeFly(),false);
|
const second = f.search.run('Second');
|
||||||
assert.equal(f.requests[1].options.beforeFly(),true);
|
assert.equal(f.requests[0].query, 'First');
|
||||||
f.requests[1].resolve({label:'Second'});await second;
|
assert.equal(f.requests[0].options.signal.aborted, true);
|
||||||
f.requests[0].resolve({label:'First'});await first;
|
assert.equal(f.requests[0].options.beforeFly(), false);
|
||||||
assert.deepEqual(f.calls.filter(x=>x[0]==='result'),[['result',{label:'Second'},'Second']]);
|
assert.equal(f.requests[1].options.beforeFly(), true);
|
||||||
assert.equal(f.search.controller,null);
|
f.requests[1].resolve({ label: 'Second' });
|
||||||
|
await second;
|
||||||
|
f.requests[0].resolve({ label: 'First' });
|
||||||
|
await first;
|
||||||
|
assert.deepEqual(
|
||||||
|
f.calls.filter((x) => x[0] === 'result'),
|
||||||
|
[['result', { label: 'Second' }, 'Second']],
|
||||||
|
);
|
||||||
|
assert.equal(f.search.controller, null);
|
||||||
});
|
});
|
||||||
test('another camera action revokes a pending lookup without presenting an error', async () => {
|
test('another camera action revokes a pending lookup without presenting an error', async () => {
|
||||||
const f=fixture();const pending=f.search.run('Place');f.takeCamera();
|
const f = fixture();
|
||||||
assert.equal(f.requests[0].options.beforeFly(),false);
|
const pending = f.search.run('Place');
|
||||||
f.requests[0].reject(new Error('old failure'));await pending;
|
f.takeCamera();
|
||||||
assert.equal(f.calls.some(x=>x[0]==='error'||x[0]==='result'),false);
|
assert.equal(f.requests[0].options.beforeFly(), false);
|
||||||
|
f.requests[0].reject(new Error('old failure'));
|
||||||
|
await pending;
|
||||||
|
assert.equal(
|
||||||
|
f.calls.some((x) => x[0] === 'error' || x[0] === 'result'),
|
||||||
|
false,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
test('missing and failed current lookups settle the exact owning generation', async () => {
|
test('missing and failed current lookups settle the exact owning generation', async () => {
|
||||||
const f=fixture();let pending=f.search.run('Missing');f.requests[0].resolve(null);await pending;
|
const f = fixture();
|
||||||
assert.ok(f.calls.some(x=>x[0]==='missing'));
|
let pending = f.search.run('Missing');
|
||||||
pending=f.search.run('Failure');f.requests[1].reject(new Error('unavailable'));await pending;
|
f.requests[0].resolve(null);
|
||||||
assert.deepEqual(f.calls.filter(x=>x[0]==='error'),[['error','unavailable']]);
|
await pending;
|
||||||
assert.deepEqual(f.calls.filter(x=>x[0]==='settled'),[['settled',1],['settled',2]]);
|
assert.ok(f.calls.some((x) => x[0] === 'missing'));
|
||||||
|
pending = f.search.run('Failure');
|
||||||
|
f.requests[1].reject(new Error('unavailable'));
|
||||||
|
await pending;
|
||||||
|
assert.deepEqual(
|
||||||
|
f.calls.filter((x) => x[0] === 'error'),
|
||||||
|
[['error', 'unavailable']],
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
f.calls.filter((x) => x[0] === 'settled'),
|
||||||
|
[
|
||||||
|
['settled', 1],
|
||||||
|
['settled', 2],
|
||||||
|
],
|
||||||
|
);
|
||||||
});
|
});
|
||||||
test('destruction cancels work and rejects late presentation and future requests', async () => {
|
test('destruction cancels work and rejects late presentation and future requests', async () => {
|
||||||
const f=fixture();const pending=f.search.run('Place');f.search.destroy();f.search.destroy();
|
const f = fixture();
|
||||||
assert.equal(f.requests[0].options.signal.aborted,true);
|
const pending = f.search.run('Place');
|
||||||
f.requests[0].resolve({label:'Place'});await pending;await f.search.run('Later');
|
f.search.destroy();
|
||||||
assert.equal(f.requests.length,1);assert.equal(f.calls.some(x=>x[0]==='result'||x[0]==='settled'),false);
|
f.search.destroy();
|
||||||
|
assert.equal(f.requests[0].options.signal.aborted, true);
|
||||||
|
f.requests[0].resolve({ label: 'Place' });
|
||||||
|
await pending;
|
||||||
|
await f.search.run('Later');
|
||||||
|
assert.equal(f.requests.length, 1);
|
||||||
|
assert.equal(
|
||||||
|
f.calls.some((x) => x[0] === 'result' || x[0] === 'settled'),
|
||||||
|
false,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
test('empty queries and refused navigation do not issue a lookup', async () => {
|
test('empty queries and refused navigation do not issue a lookup', async () => {
|
||||||
const f=fixture();await f.search.run(' ');f.search.begin=()=>false;await f.search.run('Place');
|
const f = fixture();
|
||||||
assert.equal(f.requests.length,0);assert.ok(f.calls.some(x=>x[0]==='blur'));
|
await f.search.run(' ');
|
||||||
|
f.search.begin = () => false;
|
||||||
|
await f.search.run('Place');
|
||||||
|
assert.equal(f.requests.length, 0);
|
||||||
|
assert.ok(f.calls.some((x) => x[0] === 'blur'));
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user