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
10 changes: 9 additions & 1 deletion zeppelin-web-angular/e2e/models/published-paragraph-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,23 @@ import { navigateToNotebookWithFallback } from '../utils';
import { BasePage } from './base-page';

export class PublishedParagraphPage extends BasePage {
readonly confirmationModal: Locator;
readonly angularRenderer: Locator;
readonly reactWidget: Locator;
readonly reactWidgetOrEmptyState: Locator;
private readonly errorModalContent: Locator;
private readonly errorModalOkButton: Locator;
readonly confirmationModal: Locator;

constructor(page: Page) {
super(page);
this.errorModalContent = this.page.locator('.ant-modal-body', { hasText: 'Paragraph Not Found' }).last();
this.errorModalOkButton = page.getByRole('button', { name: 'OK' }).last();
this.confirmationModal = page.locator('div.ant-modal-confirm').last();
// The result count is 0 in both modes, so dynamic-forms is the discriminator: it renders only in Angular mode.
this.angularRenderer = page.locator('zeppelin-notebook-paragraph-dynamic-forms');
this.reactWidget = page.locator('[data-testid="react-published-paragraph"]');
// Without paragraph data the remote mounts an <Empty>, so tests that only assert "React took over" accept either.
this.reactWidgetOrEmptyState = this.reactWidget.or(page.locator('.ant-alert'));
}

async navigateToNotebook(noteId: string): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,36 @@ test.describe('React Paragraph Footer', () => {
await expect(page.locator('[data-testid="angular-paragraph-footer"]')).toHaveCount(0);
});

test('with a bare reactFooter flag (no value), React footer renders', async ({ page }) => {
const { noteId } = testNotebook;

await test.step('When I open the notebook with a valueless reactFooter flag', async () => {
await page.goto(`/#/notebook/${noteId}?reactFooter`);
await waitForZeppelinReady(page);
});

await test.step('Then the React footer renders and the Angular one does not', async () => {
await expect(page.locator('[data-testid="react-paragraph-footer-content"]').first()).toBeAttached({
timeout: 15000
});
await expect(page.locator('[data-testid="angular-paragraph-footer"]')).toHaveCount(0);
});
});

test('with an explicit reactFooter=false, Angular footer renders', async ({ page }) => {
const { noteId } = testNotebook;

await test.step('When I navigate with an explicit reactFooter=false', async () => {
await page.goto(`/#/notebook/${noteId}?reactFooter=false`);
await waitForZeppelinReady(page);
});

await test.step('Then the flag disables React and Angular renders', async () => {
await expect(page.locator('[data-testid="angular-paragraph-footer"]').first()).toBeAttached({ timeout: 15000 });
await expect(page.locator('[data-testid="react-paragraph-footer"]')).toHaveCount(0);
});
});

test('reactFooter=true preserves the paragraph query param', async ({ page }) => {
const { noteId, paragraphId } = testNotebook;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,33 @@ test.describe('Published Paragraph', () => {
await waitForZeppelinReady(page);
});

await test.step('Then Angular result component should not be rendered', async () => {
await expect(page.locator('zeppelin-notebook-paragraph-result')).toHaveCount(0, { timeout: 10000 });
await test.step('Then the Angular renderer should not be used', async () => {
await expect(publishedParagraphPage.angularRenderer).toHaveCount(0, { timeout: 10000 });
});

await test.step('And React widget should be mounted in the container', async () => {
// React mount() renders <div data-testid="react-published-paragraph"> or <Empty> (Alert)
const reactContent = page.locator('[data-testid="react-published-paragraph"], .ant-alert');
// JUSTIFIED: compound selector covers React success + error fallback (.ant-alert); either may render
await expect(reactContent).toBeAttached({ timeout: 15000 });
await expect(publishedParagraphPage.reactWidgetOrEmptyState).toBeAttached({ timeout: 15000 });
});
});

test('when the remote fails to load, the published paragraph falls back to Angular', async ({ page }) => {
const { noteId, paragraphId } = testNotebook;

// Dead remote: every remoteEntry.js request fails, so the mount directive's onError fires.
await page.route('**/remoteEntry.js', route => route.abort());

await test.step('When the remote entry is requested and fails', async () => {
// Angular is the default renderer, so the assertions below pass even if React was never enabled.
// Awaiting the request is what proves this is a real fallback.
const remoteRequested = page.waitForRequest('**/remoteEntry.js');
await page.goto(`/#/notebook/${noteId}/paragraph/${paragraphId}?react=true`);
await remoteRequested;
await waitForZeppelinReady(page);
});

await test.step('Then the Angular renderer takes over and React never mounts', async () => {
await expect(publishedParagraphPage.angularRenderer).toHaveCount(1, { timeout: 15000 });
await expect(publishedParagraphPage.reactWidget).toHaveCount(0);
});
});
});
Expand Down
Loading
Loading