diff --git a/frontend/e2e/helpers.ts b/frontend/e2e/helpers.ts index 50aad53..5a7e998 100644 --- a/frontend/e2e/helpers.ts +++ b/frontend/e2e/helpers.ts @@ -26,13 +26,21 @@ export async function selectValue(page: Page, selector: string, value: string) { }, value) } +interface CyNode { + id(): string + position(k: 'x' | 'y'): number + data(key: string): unknown + pstyle(key: string): { value: unknown } +} + declare global { interface Window { __norma: { cy: { nodes(): { length: number - map(f: (n: { id(): string; position(k: 'x' | 'y'): number }) => T): T[] + map(f: (n: CyNode) => T): T[] + find(f: (n: CyNode) => boolean): CyNode | undefined } edges(): { length: number } } diff --git a/frontend/e2e/smoke.spec.ts b/frontend/e2e/smoke.spec.ts index 545a14d..c395b14 100644 --- a/frontend/e2e/smoke.spec.ts +++ b/frontend/e2e/smoke.spec.ts @@ -267,3 +267,61 @@ test('OmniPath search is case-insensitive: a lowercase query is sent to the API await expect.poll(() => requestedUrl).toContain('partners=EGFR') expect(requestedUrl).not.toContain('partners=egfr') }) + +test('Upload Data panels report loading progress at the bottom of their own section', async ({ + page, +}) => { + await page.goto('/') + + // Examples: opening one reports progress in #examplesStatus, not #normaStatus + await page.locator('.section[data-tint="examples"] h3').click() + await page.locator('#btnSample').click() + await expect(page.locator('#examplesStatus .note.ok')).toContainText(/opened/i) + + // Files: reading an uploaded file reports progress in #normaStatus (already + // covered structurally elsewhere; here we just confirm it still ends well) + await page.locator('#filesSection h3').click() + await page.setInputFiles('#normaFileInput', { + name: 'network2.txt', + mimeType: 'text/plain', + buffer: Buffer.from('Source\tTarget\nX\tY\n'), + }) + await expect(page.locator('#normaStatus .note.ok').first()).toContainText(/added/i) + + // Open saved work: a settings file reports progress in #savedWorkStatus + await page.locator('.section[data-tint="saved"] h3').click() + await page.setInputFiles('#configFileInput', { + name: 'norma-settings.json', + mimeType: 'application/json', + buffer: Buffer.from(JSON.stringify({ themeSelect: 'dark' })), + }) + await expect(page.locator('#savedWorkStatus .note.ok')).toContainText(/applied settings/i) +}) + +test('multi-group nodes keep pie-slice coloring under WebGL rendering', async ({ page }) => { + // Cytoscape's WebGL renderer takes a "simple shape" fast path (solid + // background, drawn as WebGL geometry) for any node whose background-fill + // is 'solid' -- that check never looks at pie-*-background-* styles, so a + // node with pie slices used to render as a flat color under WebGL. The fix + // gives such nodes a one-stop gradient fill, which is enough to route them + // through the full (pie-aware) draw path. + await page.goto(`/norma.html?example=${EXAMPLE}&webgl=1`) + await expect(page.locator('#tabNetwork')).toHaveAttribute('aria-selected', 'true') + await expect.poll(() => nodeCount(page)).toBeGreaterThan(0) + await selectValue(page, '#nodeFillSelect', 'groups') + + const styles = await page.evaluate(() => { + const nodes = window.__norma.cy.nodes() + const multi = nodes.find((n) => n.data('pieSize2') !== '0%') + const single = nodes.find((n) => n.data('pieSize2') === '0%') + return { + multiFill: multi?.pstyle('background-fill').value, + multiPieSize2: multi?.data('pieSize2'), + singleFill: single?.pstyle('background-fill').value, + } + }) + expect(styles.multiPieSize2).not.toBe('0%') + expect(styles.multiFill).toBe('radial-gradient') + // single-group nodes stay on the fast "simple shape" path + expect(styles.singleFill).toBe('solid') +}) diff --git a/frontend/norma.html b/frontend/norma.html index 92907c0..75c7364 100644 --- a/frontend/norma.html +++ b/frontend/norma.html @@ -135,6 +135,7 @@