feat(globe): port to CeliumJS

This commit is contained in:
gili8420 2026-08-03 22:12:32 +09:00
parent ec03425067
commit eb7698e034
51 changed files with 3521 additions and 1992 deletions

123
tests/e2e/bbox.spec.ts Normal file
View file

@ -0,0 +1,123 @@
import { test, expect, login, sceneObjectCount } from './fixtures';
/**
* Browser-side checks for the restricted-area box. The geometry itself is
* covered in tests/unit/boundingBox.spec.ts, which runs in Node against the pure
* module; what only a browser can answer is whether the thing rasterises and
* what the operator ends up reading on screen.
*/
test.beforeEach(async ({ context }) => {
await login(context);
});
/** Seed one workspace at the given launch point, run it, and draw its box. */
async function runAt(page: import('@playwright/test').Page, lat: number, lng: number) {
await page.goto('/');
await page.evaluate(
({ lat, lng }) => {
const ws = {
id: crypto.randomUUID(),
name: `bbox-${lat}`,
color: '#dc3545',
opacity: 1,
visible: true,
flightParameters: {
ascent_rate: 5,
burst_altitude: 30000,
dataset: '',
descent_rate: 5,
format: 'json',
launch_altitude: 0,
launch_latitude: lat,
launch_longitude: lng,
profile: 'standard_profile',
version: 2,
},
launchDate: new Date().toISOString().split('T')[0],
launchTime: '12:00:00',
result: null,
bboxMargin: 5,
bboxVisible: false,
};
localStorage.setItem('workspaces', JSON.stringify({ items: [ws], activeId: ws.id }));
},
{ lat, lng },
);
await page.goto('/predict');
await page
.locator('.map-container canvas')
.first()
.waitFor({ state: 'attached', timeout: 60_000 });
const panel = page.locator('.panel-container-right');
await panel
.locator('.workspace-row')
.first()
.getByRole('button', { name: /Рассчитать|Run/ })
.click();
await expect
.poll(() => sceneObjectCount(page, 'ws/'), { timeout: 90_000, intervals: [1000, 2000, 3000] })
.toBeGreaterThan(0);
await panel.getByRole('button', { name: /Построить рамку|Generate bounding box/ }).click();
await expect.poll(() => sceneObjectCount(page, 'bbox'), { timeout: 15_000 }).toBeGreaterThan(0);
return panel;
}
/**
* Regression: the box must actually rasterise.
*
* An earlier attempt made a pole-touching box circumpolar with west=-180 and
* east=180. Both meridional edges then lay on the antimeridian, and Cesium's
* splitLongitude pass which cuts geometry at the IDL produced mismatched
* attribute lists and killed the render loop:
*
* DeveloperError: All attribute lists must have the same number of attributes.
* at k.splitLongitude / S.combineGeometry
*
* Assertions on polyline.positions could not see this, because that is the entity
* definition and geometry is combined later in a worker. This listens for the
* render failure itself.
*/
test('drawing a polar box does not break the renderer', async ({ page }) => {
test.setTimeout(150_000);
await runAt(page, 89.99, 30);
await page.evaluate(() => {
/* eslint-disable @typescript-eslint/no-explicit-any */
const v: any = (window as any)._lsvMap;
(window as any).__renderErrors = [];
v.scene.renderError.addEventListener((_s: unknown, e: any) =>
(window as any).__renderErrors.push(String(e?.message ?? e)),
);
/* eslint-enable @typescript-eslint/no-explicit-any */
});
await page.waitForTimeout(6000);
const errors = await page.evaluate(
() => (window as never as { __renderErrors: string[] }).__renderErrors,
);
expect(errors).toEqual([]);
// Cesium swaps in an error panel when the render loop dies.
await expect(page.locator('.cesium-widget-errorPanel')).toHaveCount(0);
});
test('the panel reports a corridor-sized area at the pole, not a polar cap', async ({ page }) => {
test.setTimeout(150_000);
const panel = await runAt(page, 89.99, 30);
// Near a pole the four corners are unreadable on their own — a box whose north
// edge passes over the pole comes back down the far side — so the size line is
// what the operator actually checks. The lat/lon-rectangle form reported the
// whole cap north of 88.93, 44 200 km^2, for a corridor of about 1 700.
const size = panel.locator('.font-monospace', { hasText: 'km @' }).first();
await expect(size).toBeVisible();
const text = (await size.textContent()) ?? '';
const [w, h] = text.split('km @')[0].split('×').map((s) => parseFloat(s));
expect(w).toBeGreaterThan(0);
expect(h).toBeGreaterThan(0);
expect(w * h).toBeLessThan(10_000);
});