mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-06 20:10:24 +08:00
372 lines
11 KiB
TypeScript
372 lines
11 KiB
TypeScript
import { test } from '@affine-test/kit/playwright';
|
|
import {
|
|
clickEdgelessModeButton,
|
|
clickPageModeButton,
|
|
clickView,
|
|
createEdgelessNoteBlock,
|
|
locateElementToolbar,
|
|
} from '@affine-test/kit/utils/editor';
|
|
import {
|
|
pressBackspace,
|
|
pressEnter,
|
|
selectAllByKeyboard,
|
|
} from '@affine-test/kit/utils/keyboard';
|
|
import { openHomePage } from '@affine-test/kit/utils/load-page';
|
|
import {
|
|
clickNewPageButton,
|
|
type,
|
|
waitForEditorLoad,
|
|
} from '@affine-test/kit/utils/page-logic';
|
|
import {
|
|
closeSettingModal,
|
|
confirmExperimentalPrompt,
|
|
openExperimentalFeaturesPanel,
|
|
openSettingModal,
|
|
} from '@affine-test/kit/utils/setting';
|
|
import { openRightSideBar } from '@affine-test/kit/utils/sidebar';
|
|
import { expect, type Locator, type Page } from '@playwright/test';
|
|
|
|
import {
|
|
createHeadings,
|
|
createTitle,
|
|
getVerticalCenterFromLocator,
|
|
} from './utils';
|
|
|
|
async function openTocPanel(page: Page) {
|
|
await openRightSideBar(page, 'outline');
|
|
const toc = page.locator('affine-outline-panel');
|
|
await toc.waitFor({ state: 'visible' });
|
|
return toc;
|
|
}
|
|
|
|
function getTocHeading(panel: Locator, level: number) {
|
|
return panel.locator(`.h${level} span`);
|
|
}
|
|
|
|
async function dragNoteCard(page: Page, fromCard: Locator, toCard: Locator) {
|
|
const fromRect = await fromCard.boundingBox();
|
|
const toRect = await toCard.boundingBox();
|
|
|
|
await page.mouse.move(fromRect!.x + 10, fromRect!.y + 10);
|
|
await page.mouse.down();
|
|
await page.mouse.move(toRect!.x + 5, toRect!.y + 5, { steps: 20 });
|
|
await page.mouse.up();
|
|
}
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await openHomePage(page);
|
|
await clickNewPageButton(page);
|
|
await waitForEditorLoad(page);
|
|
});
|
|
|
|
test('should display title and headings when there are non-empty headings in editor', async ({
|
|
page,
|
|
}) => {
|
|
await createTitle(page);
|
|
await createHeadings(page);
|
|
|
|
const toc = await openTocPanel(page);
|
|
|
|
await expect(toc.locator('.title')).toBeVisible();
|
|
for (let i = 1; i <= 6; i++) {
|
|
await expect(getTocHeading(toc, i)).toBeVisible();
|
|
await expect(getTocHeading(toc, i)).toContainText(`Heading ${i}`);
|
|
}
|
|
});
|
|
|
|
test('should display placeholder when no headings', async ({ page }) => {
|
|
const toc = await openTocPanel(page);
|
|
const noHeadingPlaceholder = toc.locator('.note-placeholder');
|
|
|
|
await createTitle(page);
|
|
await pressEnter(page);
|
|
await type(page, 'hello world');
|
|
|
|
await expect(noHeadingPlaceholder).toBeVisible();
|
|
});
|
|
|
|
test('should not display headings when there are only empty headings', async ({
|
|
page,
|
|
}) => {
|
|
await createTitle(page);
|
|
|
|
// create empty headings
|
|
for (let i = 1; i <= 6; i++) {
|
|
await type(page, `${'#'.repeat(i)} `);
|
|
await pressEnter(page);
|
|
}
|
|
|
|
const toc = await openTocPanel(page);
|
|
|
|
await expect(toc.locator('.title')).toBeHidden();
|
|
for (let i = 1; i <= 6; i++) {
|
|
await expect(getTocHeading(toc, i)).toBeHidden();
|
|
}
|
|
});
|
|
|
|
test('should update panel when modify or clear title or headings', async ({
|
|
page,
|
|
}) => {
|
|
const title = await createTitle(page);
|
|
const headings = await createHeadings(page);
|
|
|
|
const toc = await openTocPanel(page);
|
|
|
|
await title.scrollIntoViewIfNeeded();
|
|
await title.click();
|
|
await type(page, 'xxx');
|
|
await expect(toc.locator('.title')).toContainText(['Titlexxx']);
|
|
await selectAllByKeyboard(page);
|
|
await pressBackspace(page);
|
|
await expect(toc.locator('.title')).toBeHidden();
|
|
|
|
for (let i = 1; i <= 6; i++) {
|
|
await headings[i - 1].click();
|
|
await type(page, 'xxx');
|
|
await expect(getTocHeading(toc, i)).toContainText(`Heading ${i}xxx`);
|
|
await selectAllByKeyboard(page);
|
|
await pressBackspace(page);
|
|
await expect(getTocHeading(toc, i)).toBeHidden();
|
|
}
|
|
});
|
|
|
|
test('should add padding to sub-headings', async ({ page }) => {
|
|
await createHeadings(page);
|
|
|
|
const toc = await openTocPanel(page);
|
|
|
|
let prev = getTocHeading(toc, 1);
|
|
for (let i = 2; i <= 6; i++) {
|
|
const curr = getTocHeading(toc, i);
|
|
|
|
const prevRect = await prev.boundingBox();
|
|
const currRect = await curr.boundingBox();
|
|
|
|
expect(prevRect).not.toBeNull();
|
|
expect(currRect).not.toBeNull();
|
|
|
|
expect(prevRect!.x).toBeLessThan(currRect!.x);
|
|
prev = curr;
|
|
}
|
|
});
|
|
|
|
test('should highlight heading when scroll to area before viewport center', async ({
|
|
page,
|
|
}) => {
|
|
const title = await createTitle(page);
|
|
for (let i = 0; i < 3; i++) {
|
|
await pressEnter(page);
|
|
}
|
|
const headings = await createHeadings(page, 10);
|
|
await title.scrollIntoViewIfNeeded();
|
|
|
|
const toc = await openTocPanel(page);
|
|
|
|
const viewportCenter = await getVerticalCenterFromLocator(
|
|
page.locator('body')
|
|
);
|
|
|
|
const activeHeadingContainer = toc.locator(
|
|
'affine-outline-panel-body .active'
|
|
);
|
|
|
|
await title.click();
|
|
await expect(activeHeadingContainer).toContainText('Title');
|
|
|
|
for (let i = 0; i < headings.length; i++) {
|
|
const lastHeadingCenter = await getVerticalCenterFromLocator(headings[i]);
|
|
await page.mouse.wheel(0, lastHeadingCenter - viewportCenter + 20);
|
|
await page.waitForTimeout(10);
|
|
|
|
await expect(activeHeadingContainer).toContainText(`Heading ${i + 1}`);
|
|
}
|
|
});
|
|
|
|
test('should scroll to heading and highlight heading when click item in outline panel', async ({
|
|
page,
|
|
}) => {
|
|
const headings = await createHeadings(page, 10);
|
|
const toc = await openTocPanel(page);
|
|
|
|
const activeHeadingContainer = toc.locator(
|
|
'affine-outline-panel-body .active'
|
|
);
|
|
|
|
const headingsInPanel = Array.from({ length: 6 }, (_, i) =>
|
|
getTocHeading(toc, i + 1)
|
|
);
|
|
|
|
await headingsInPanel[2].click();
|
|
await expect(headings[2]).toBeVisible();
|
|
await expect(activeHeadingContainer).toContainText('Heading 3');
|
|
});
|
|
|
|
test('should scroll to title when click title in outline panel', async ({
|
|
page,
|
|
}) => {
|
|
const title = await createTitle(page);
|
|
await pressEnter(page);
|
|
await createHeadings(page, 10);
|
|
|
|
const toc = await openTocPanel(page);
|
|
|
|
const titleInPanel = toc.locator('.title');
|
|
|
|
await expect(title).not.toBeInViewport();
|
|
await titleInPanel.click();
|
|
await expect(title).toBeVisible();
|
|
});
|
|
|
|
test('visibility sorting should be enabled in edgeless mode and disabled in page mode by default, and can be changed', async ({
|
|
page,
|
|
}) => {
|
|
await pressEnter(page);
|
|
await type(page, '# Heading 1');
|
|
|
|
const toc = await openTocPanel(page);
|
|
|
|
const sortingButton = toc.locator('.note-sorting-button');
|
|
await expect(sortingButton).not.toHaveClass(/active/);
|
|
expect(toc.locator('[data-sortable="false"]')).toHaveCount(1);
|
|
|
|
await clickEdgelessModeButton(page);
|
|
await expect(sortingButton).toHaveClass(/active/);
|
|
expect(toc.locator('[data-sortable="true"]')).toHaveCount(1);
|
|
|
|
await sortingButton.click();
|
|
await expect(sortingButton).not.toHaveClass(/active/);
|
|
expect(toc.locator('[data-sortable="false"]')).toHaveCount(1);
|
|
});
|
|
|
|
test('should reorder notes when drag and drop note in outline panel', async ({
|
|
page,
|
|
}) => {
|
|
await clickEdgelessModeButton(page);
|
|
await createEdgelessNoteBlock(page, [100, 100]);
|
|
await type(page, 'hello');
|
|
await createEdgelessNoteBlock(page, [200, 200]);
|
|
await type(page, 'world');
|
|
|
|
const toc = await openTocPanel(page);
|
|
|
|
const docVisibleCards = toc.locator(
|
|
'.card-container[data-invisible="false"]'
|
|
);
|
|
const docInvisibleCards = toc.locator(
|
|
'.card-container[data-invisible="true"]'
|
|
);
|
|
|
|
await expect(docVisibleCards).toHaveCount(1);
|
|
await expect(docInvisibleCards).toHaveCount(2);
|
|
|
|
while ((await docInvisibleCards.count()) > 0) {
|
|
const card = docInvisibleCards.first();
|
|
await card.hover();
|
|
await card.locator('.display-mode-button').click();
|
|
await card.locator('note-display-mode-panel').locator('.item.both').click();
|
|
}
|
|
|
|
await expect(docVisibleCards).toHaveCount(3);
|
|
const noteCard3 = docVisibleCards.nth(2);
|
|
const noteCard1 = docVisibleCards.nth(0);
|
|
|
|
await dragNoteCard(page, noteCard3, noteCard1);
|
|
|
|
await clickPageModeButton(page);
|
|
const paragraphs = page
|
|
.locator('affine-paragraph')
|
|
.locator('[data-v-text="true"]');
|
|
await expect(paragraphs).toHaveCount(3);
|
|
await expect(paragraphs.nth(0)).toContainText('world');
|
|
await expect(paragraphs.nth(1)).toContainText('');
|
|
await expect(paragraphs.nth(2)).toContainText('hello');
|
|
|
|
// FIXME(@L-Sun): drag and drop is not working in page mode
|
|
await dragNoteCard(page, noteCard3, noteCard1);
|
|
|
|
await expect(paragraphs.nth(0)).toContainText('hello');
|
|
await expect(paragraphs.nth(1)).toContainText('world');
|
|
await expect(paragraphs.nth(2)).toContainText('');
|
|
});
|
|
|
|
test.describe('advanced visibility control', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await openSettingModal(page);
|
|
await openExperimentalFeaturesPanel(page);
|
|
await confirmExperimentalPrompt(page);
|
|
await page.getByTestId('enable_advanced_block_visibility').click();
|
|
await closeSettingModal(page);
|
|
await page.reload();
|
|
});
|
|
|
|
test('should update notes when change note display mode from note toolbar', async ({
|
|
page,
|
|
}) => {
|
|
await clickEdgelessModeButton(page);
|
|
await createEdgelessNoteBlock(page, [100, 100]);
|
|
await type(page, 'hello');
|
|
await clickView(page, [200, 200]);
|
|
|
|
const toc = await openTocPanel(page);
|
|
|
|
const docVisibleCard = toc.locator(
|
|
'.card-container[data-invisible="false"]'
|
|
);
|
|
const docInvisibleCard = toc.locator(
|
|
'.card-container[data-invisible="true"]'
|
|
);
|
|
|
|
await expect(docVisibleCard).toHaveCount(1);
|
|
await expect(docInvisibleCard).toHaveCount(1);
|
|
|
|
await clickView(page, [100, 100]);
|
|
const noteButtons = locateElementToolbar(page).locator(
|
|
'edgeless-change-note-button'
|
|
);
|
|
|
|
await noteButtons.getByRole('button', { name: 'Mode' }).click();
|
|
await noteButtons.locator('note-display-mode-panel .item.both').click();
|
|
|
|
await expect(docVisibleCard).toHaveCount(2);
|
|
await expect(docInvisibleCard).toHaveCount(0);
|
|
});
|
|
|
|
test('should update notes after slicing note', async ({ page }) => {
|
|
await clickEdgelessModeButton(page);
|
|
await createEdgelessNoteBlock(page, [200, 100]);
|
|
await type(page, 'hello');
|
|
await pressEnter(page);
|
|
await type(page, 'world');
|
|
|
|
const toc = await openTocPanel(page);
|
|
|
|
const docVisibleCard = toc.locator(
|
|
'.card-container[data-invisible="false"]'
|
|
);
|
|
const docInvisibleCard = toc.locator(
|
|
'.card-container[data-invisible="true"]'
|
|
);
|
|
|
|
await expect(docVisibleCard).toHaveCount(1);
|
|
await expect(docInvisibleCard).toHaveCount(1);
|
|
|
|
await docInvisibleCard.hover();
|
|
await docInvisibleCard.locator('.display-mode-button').click();
|
|
await docInvisibleCard
|
|
.locator('note-display-mode-panel .item.both')
|
|
.click();
|
|
|
|
await expect(docVisibleCard).toHaveCount(2);
|
|
|
|
await clickView(page, [200, 100]);
|
|
const changeNoteButtons = locateElementToolbar(page).locator(
|
|
'edgeless-change-note-button'
|
|
);
|
|
await changeNoteButtons.getByRole('button', { name: 'Slicer' }).click();
|
|
await expect(page.locator('.note-slicer-button')).toBeVisible();
|
|
await page.locator('.note-slicer-button').click();
|
|
|
|
await expect(docVisibleCard).toHaveCount(3);
|
|
});
|
|
});
|