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); });