Skip to content
Merged
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
18 changes: 13 additions & 5 deletions playwright/e2e/database/duplicate-row-inline-db.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,20 @@ test.describe('Duplicate row with inline database', () => {
const dupGridBlock = dbBlocks(dupEditor).first();
await expect(dupGridBlock).toBeVisible({ timeout: 30000 });

// Edit the duplicated row's inline grid cell
await editCell(page, dupGridBlock, 'modified in duplicate');
await page.waitForTimeout(1000);
// Ensure the inline grid has finished hydrating before we try to edit:
// at least one data row must be present, and its primary cell must not be
// showing the loading spinner. Without this gate, the edit can target a
// not-yet-mounted TextCell and never commit to the duplicated inline DB.
await expect(dupGridBlock.locator('[data-testid^="grid-cell-"]').first()).toBeVisible({
timeout: 30000,
});
await expect(dupGridBlock.locator('[data-testid^="primary-cell-loading-"]')).toHaveCount(0, {
timeout: 30000,
});

// Verify the edit took effect
expect(await cellText(dupGridBlock)).toBe('modified in duplicate');
// editCell asserts the cell text becomes the typed value before returning,
// so a separate re-check would be redundant.
await editCell(page, dupGridBlock, 'modified in duplicate');

// Navigate back to the grid
await page.goBack();
Expand Down
77 changes: 72 additions & 5 deletions playwright/support/duplicate-test-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@
await page.keyboard.type('/', { delay: 50 });
}

await expect(slashPanel).toBeVisible({ timeout: 10000 });

Check failure on line 341 in playwright/support/duplicate-test-helpers.ts

View workflow job for this annotation

GitHub Actions / embedded

[chromium] › playwright/e2e/embeded/database/duplicate-doc-inline-and-linked-db.spec.ts:28:3 › Duplicate Document Inline And Linked Database › Filters on duplicated linked view do not leak when inline DB and linked view share the same database

2) [chromium] › playwright/e2e/embeded/database/duplicate-doc-inline-and-linked-db.spec.ts:28:3 › Duplicate Document Inline And Linked Database › Filters on duplicated linked view do not leak when inline DB and linked view share the same database Error: expect(locator).toBeVisible() failed Locator: getByTestId('slash-panel') Expected: visible Timeout: 10000ms Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 10000ms - waiting for getByTestId('slash-panel') at ../support/duplicate-test-helpers.ts:341 339 | } 340 | > 341 | await expect(slashPanel).toBeVisible({ timeout: 10000 }); | ^ 342 | } 343 | 344 | export async function insertInlineGridViaSlash(page: Page, docViewId: string, line: number = 0): Promise<void> { at openSlashMenuInEditor (/home/runner/work/AppFlowy-Web/AppFlowy-Web/playwright/support/duplicate-test-helpers.ts:341:28) at insertLinkedGridViaSlash (/home/runner/work/AppFlowy-Web/AppFlowy-Web/playwright/support/duplicate-test-helpers.ts:394:7) at /home/runner/work/AppFlowy-Web/AppFlowy-Web/playwright/e2e/embeded/database/duplicate-doc-inline-and-linked-db.spec.ts:44:5
}

export async function insertInlineGridViaSlash(page: Page, docViewId: string, line: number = 0): Promise<void> {
Expand Down Expand Up @@ -440,11 +440,78 @@
export async function editFirstGridCell(page: Page, gridBlock: Locator, text: string): Promise<void> {
const firstCell = gridBlock.locator('[data-testid^="grid-cell-"]').first();
await expect(firstCell).toBeVisible({ timeout: 15000 });
await firstCell.click({ force: true });
await page.waitForTimeout(300);
await page.keyboard.press(process.platform === 'darwin' ? 'Meta+A' : 'Control+A');
await page.keyboard.type(text);
await page.keyboard.press('Enter');

// The grid-row-cell wrapper owns the activation click handler. Clicking the
// inner data-testid div relies on bubbling, which can be eaten on slow CI if
// the row is still hydrating, so assert the editor is actually mounted.

// Wait for the primary cell's row data to finish hydrating. PrimaryCell.tsx
// renders a CircularProgress with testid `primary-cell-loading-<rowId>`
// until the row's collab content is loaded; clicking before then has no
// effect because the editable TextCell isn't mounted yet.
await expect(firstCell.locator('[data-testid^="primary-cell-loading-"]')).toHaveCount(0, {
timeout: 30000,
});

// The activation click handler lives on the .grid-row-cell wrapper (see
// GridVirtualColumn.tsx). Resolve it directly via the DOM rather than an
// ancestor xpath. Playwright's `ancestor::` returns the outermost match
// even with `.first()`, which is the document root, not the wrapper.
const editingTextarea = firstCell.locator('textarea');

let entered = false;

for (let attempt = 0; attempt < 4; attempt++) {
const handle = await firstCell.elementHandle();
const wrapper = handle
? await handle.evaluateHandle((el) => (el as HTMLElement).closest('.grid-row-cell') ?? el)
: null;
const wrapperElement = wrapper?.asElement();

if (wrapperElement) {
await wrapperElement.click({ force: true });
} else {
await firstCell.click({ force: true });
}

if (await editingTextarea.isVisible().catch(() => false)) {
entered = true;
break;
}

try {
await expect(editingTextarea).toBeVisible({ timeout: 3000 });
entered = true;
break;
} catch {
// Some browsers / cell types only enter edit mode on Enter after focus,
// not on raw click. Try that next.
await page.keyboard.press('Enter').catch(() => undefined);

if (await editingTextarea.isVisible({ timeout: 1500 }).catch(() => false)) {
entered = true;
break;
}

await page.waitForTimeout(500);
}
}

if (!entered) {
throw new Error(
'First grid cell did not enter edit mode after click. Is the grid readOnly, ' +
'still loading, or covered by another element?'
);
}

await editingTextarea.focus();
await editingTextarea.fill(text);
await expect(editingTextarea).toHaveValue(text, { timeout: 5000 });
await editingTextarea.press('Enter');
// After Enter, the textarea unmounts and the cell re-renders with the new
// value. Wait for the textarea to be gone so the next innerText() reads the
// committed display text, not stale empty editing markup.
await expect(editingTextarea).toHaveCount(0, { timeout: 10000 });
await expect
.poll(async () => firstGridCellText(gridBlock), {
timeout: 15000,
Expand Down
3 changes: 3 additions & 0 deletions src/components/editor/components/blocks/image/ImageRender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ function ImageRender({
width={rendered ? (newWidth ?? '100%') : 0}
imgRef={imgRef}
url={url}
// `ImageBlockData` has no caption field today, so let `Img` derive
// an alt from the URL filename. This avoids screen readers reading
// the raw blob URL or skipping the image entirely.
onLoad={() => {
setRendered(true);
}}
Expand Down
Loading
Loading