Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions e2e-tests/fixtures/Plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ export class Plan {
async goto(planId = this.plans.planId) {
await this.page.goto(`/plans/${planId}`, { waitUntil: 'load' });
await this.page.waitForURL(`/plans/${planId}`, { waitUntil: 'load' });
await this.planTitle.waitFor({ state: 'visible' });
await this.waitForTimelineLoading();
}

Expand Down
18 changes: 2 additions & 16 deletions e2e-tests/tests/plan-external-source.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import test, { expect } from '@playwright/test';
import { ExternalSources } from '../fixtures/ExternalSources.js';
import { PanelNames, Plan } from '../fixtures/Plan.js';
import { anyCanvasHasContent } from '../utilities/canvas.js';
import {
cleanupApiResources,
closeBrowserResources,
Expand Down Expand Up @@ -157,22 +158,7 @@ test.describe.serial('Plan External Sources', () => {
});

test('Zero-duration events are properly drawn in the timeline', async () => {
// Get the current timeline canvas' pixels - use a set to just determine that non-0 RGB values exist
const doPixelsExist: boolean = await setup.page.evaluate(() => {
const canvas = document.querySelector('canvas');
if (canvas !== null && canvas !== undefined) {
const context = canvas.getContext('2d');
if (context !== null && context !== undefined) {
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
const pixelData = Array.from(imageData.data);
return pixelData.length > 0 ? true : false;
// Assert that the number of unique RGB pixel values for the canvas is more than 0 (i.e., not empty)
}
}
return false;
});

expect(doPixelsExist).toBeTruthy();
expect(await anyCanvasHasContent(setup.page, '[data-component-name="TimelinePanel"] canvas')).toBeTruthy();
});

test('Linked derivation groups should be expandable in panel', async () => {
Expand Down
23 changes: 23 additions & 0 deletions e2e-tests/tests/simulation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import test, { expect } from '@playwright/test';
import { Status } from '../../src/enums/status.js';
import { PanelNames } from '../fixtures/Plan.js';
import { setupTest, teardownTest, type FullSetupResult } from '../utilities/api.js';
import { anyCanvasHasContent } from '../utilities/canvas.js';

let setup: FullSetupResult;

Expand Down Expand Up @@ -61,9 +62,31 @@ test.describe.serial('Simulation', async () => {
await setup.plan.showPanel(PanelNames.SIMULATION, true);
});

// Smoke test for the windowed-pull pipeline: indicator settles cleanly and
// canvases render non-transparent content (catches the "blank plot" bug an
// indicator-only check would miss).
test(`Streaming pipeline: indicator settles + canvases render across two re-sims`, async () => {
const timelineErrorIndicator = setup.plan.page.getByRole('status', { name: 'Timeline data error' });
const timelineLoadingIndicator = setup.plan.page.getByRole('status', { name: 'Timeline loading' });
const timelineCanvasContent = () => anyCanvasHasContent(setup.page, '[data-component-name="TimelinePanel"] canvas');

await setup.plan.reRunSimulation();
await expect(timelineErrorIndicator).not.toBeVisible();
await expect(timelineLoadingIndicator).not.toBeVisible();
await expect.poll(timelineCanvasContent, { timeout: 10000 }).toBe(true);

await setup.plan.reRunSimulation();
await expect(timelineErrorIndicator).not.toBeVisible();
await expect(timelineLoadingIndicator).not.toBeVisible();
await expect.poll(timelineCanvasContent, { timeout: 10000 }).toBe(true);
});

test(`Plans with an invalid activity should fail simulation`, async () => {
const timelineLoadingIndicator = setup.plan.page.getByRole('status', { name: 'Timeline loading' });
await setup.plan.addActivityByDragAndDrop('BakeBananaBread');
await setup.plan.runSimulation(Status.Failed);
// Regression: indicator must settle for terminal-null sims too.
await expect(timelineLoadingIndicator).not.toBeVisible();
});

test(`Modified plans should indicate that simulation is out of date`, async () => {
Expand Down
25 changes: 25 additions & 0 deletions e2e-tests/utilities/canvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { Page } from '@playwright/test';

/**
* True if any canvas matched by `selector` has a pixel with non-zero alpha.
* Stronger than checking the canvas exists — a transparent canvas would
* pass that. Pair with `expect.poll` for post-async-update checks.
*/
export function anyCanvasHasContent(page: Page, selector: string = 'canvas'): Promise<boolean> {
return page.evaluate(sel => {
const canvases = document.querySelectorAll<HTMLCanvasElement>(sel);
for (const canvas of Array.from(canvases)) {
const ctx = canvas.getContext('2d');
if (!ctx) {
continue;
}
const data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
for (let i = 3; i < data.length; i += 4) {
if (data[i] > 0) {
return true;
}
}
}
return false;
}, selector);
}
9 changes: 2 additions & 7 deletions src/components/ResourceList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@
import CloseIcon from '@nasa-jpl/stellar/icons/close.svg?component';
import UploadIcon from '@nasa-jpl/stellar/icons/upload.svg?component';
import { plan } from '../stores/plan';
import {
allResourceTypes,
fetchingResourcesExternal,
resourceTypesLoading,
simulationDatasetId,
} from '../stores/simulation';
import { allResourceTypes, resourceTypesLoading, simulationDatasetId } from '../stores/simulation';
import type { User } from '../types/app';
import type { ResourceType } from '../types/simulation';
import type { TimelineItemType } from '../types/timeline';
Expand All @@ -34,7 +29,7 @@
let loading: boolean = false;

$: resourceDataTypes = [...new Set($allResourceTypes.map(t => t.schema.type))];
$: loading = $fetchingResourcesExternal || $resourceTypesLoading;
$: loading = $resourceTypesLoading;
$: if (user !== null && $plan !== null) {
hasUploadPermission = featurePermissions.externalResources.canCreate(user, $plan);
}
Expand Down
Loading
Loading