From 372b4da88437b4fe67c4415e3395a3702dbaffad Mon Sep 17 00:00:00 2001 From: Peng Xiao Date: Thu, 22 Feb 2024 09:37:55 +0000 Subject: [PATCH] test(core): add tests for page info ui (#5769) ![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/T2klNLEk0wxLh4NRDzhk/64419f79-46e8-4171-b853-5908841f827a.png) --- .../confirm-delete-property-modal.tsx | 58 +++++ .../affine/page-properties/icons-selector.tsx | 2 +- .../affine/page-properties/menu-items.tsx | 4 +- .../page-properties-manager.ts | 4 + .../affine/page-properties/styles.css.ts | 2 +- .../affine/page-properties/table.tsx | 90 ++++--- .../page-properties/tags-inline-editor.tsx | 8 +- .../setting-modal/setting-sidebar/index.tsx | 1 + .../workspace-setting/properties/index.tsx | 54 +--- .../components/page-list/docs/page-tags.tsx | 8 +- packages/frontend/i18n/src/resources/en.json | 2 +- tests/affine-local/e2e/all-page.spec.ts | 2 +- .../e2e/local-first-collections-items.spec.ts | 21 -- .../e2e/local-first-delete-page.spec.ts | 1 + .../e2e/local-first-favorites-items.spec.ts | 4 +- .../affine-local/e2e/page-properties.spec.ts | 243 ++++++++++++++++++ tests/kit/utils/filter.ts | 5 +- tests/kit/utils/page-logic.ts | 1 - tests/kit/utils/properties.ts | 178 +++++++++++++ 19 files changed, 574 insertions(+), 114 deletions(-) create mode 100644 packages/frontend/core/src/components/affine/page-properties/confirm-delete-property-modal.tsx create mode 100644 tests/affine-local/e2e/page-properties.spec.ts create mode 100644 tests/kit/utils/properties.ts diff --git a/packages/frontend/core/src/components/affine/page-properties/confirm-delete-property-modal.tsx b/packages/frontend/core/src/components/affine/page-properties/confirm-delete-property-modal.tsx new file mode 100644 index 0000000000..adff7cf13f --- /dev/null +++ b/packages/frontend/core/src/components/affine/page-properties/confirm-delete-property-modal.tsx @@ -0,0 +1,58 @@ +import { ConfirmModal } from '@affine/component'; +import { WorkspacePropertiesAdapter } from '@affine/core/modules/workspace'; +import type { PageInfoCustomPropertyMeta } from '@affine/core/modules/workspace/properties/schema'; +import { Trans } from '@affine/i18n'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { useService } from '@toeverything/infra/di'; +import { useMemo } from 'react'; + +import { PagePropertiesMetaManager } from './page-properties-manager'; + +export const ConfirmDeletePropertyModal = ({ + onConfirm, + onCancel, + property, + show, +}: { + property: PageInfoCustomPropertyMeta; + show: boolean; + onConfirm: () => void; + onCancel: () => void; +}) => { + const t = useAFFiNEI18N(); + const adapter = useService(WorkspacePropertiesAdapter); + const count = useMemo(() => { + const manager = new PagePropertiesMetaManager(adapter); + return manager.getPropertyRelatedPages(property.id)?.size || 0; + }, [adapter, property.id]); + + return ( + + The {{ name: property.name } as any} property will be + removed from count doc(s). This action cannot be undone. + + } + onConfirm={onConfirm} + cancelButtonOptions={{ + onClick: onCancel, + }} + confirmButtonOptions={{ + type: 'error', + children: t['Confirm'](), + }} + /> + ); +}; diff --git a/packages/frontend/core/src/components/affine/page-properties/icons-selector.tsx b/packages/frontend/core/src/components/affine/page-properties/icons-selector.tsx index a0ba47d48c..8ebd539eee 100644 --- a/packages/frontend/core/src/components/affine/page-properties/icons-selector.tsx +++ b/packages/frontend/core/src/components/affine/page-properties/icons-selector.tsx @@ -34,7 +34,7 @@ export const IconsSelectorPanel = ({ const t = useAFFiNEI18N(); return ( -
+
{t['com.affine.page-properties.icons']()}
diff --git a/packages/frontend/core/src/components/affine/page-properties/menu-items.tsx b/packages/frontend/core/src/components/affine/page-properties/menu-items.tsx index e26b0a5288..1bba6bcf80 100644 --- a/packages/frontend/core/src/components/affine/page-properties/menu-items.tsx +++ b/packages/frontend/core/src/components/affine/page-properties/menu-items.tsx @@ -90,7 +90,9 @@ export const EditPropertyNameMenuItem = ({ const iconName = getSafeIconName(property.icon, property.type); const onKeyDown: KeyboardEventHandler = useCallback( e => { - e.stopPropagation(); + if (e.key !== 'Escape') { + e.stopPropagation(); + } if (e.key === 'Enter') { e.preventDefault(); onBlur(e.currentTarget.value); diff --git a/packages/frontend/core/src/components/affine/page-properties/page-properties-manager.ts b/packages/frontend/core/src/components/affine/page-properties/page-properties-manager.ts index 26e598ca4e..1e929c2d3b 100644 --- a/packages/frontend/core/src/components/affine/page-properties/page-properties-manager.ts +++ b/packages/frontend/core/src/components/affine/page-properties/page-properties-manager.ts @@ -132,6 +132,10 @@ export class PagePropertiesMetaManager { } return mapping; } + + getPropertyRelatedPages(id: string) { + return this.getPropertyStatistics().get(id); + } } export class PagePropertiesManager { diff --git a/packages/frontend/core/src/components/affine/page-properties/styles.css.ts b/packages/frontend/core/src/components/affine/page-properties/styles.css.ts index 3c2b49a6e1..64c27dfcef 100644 --- a/packages/frontend/core/src/components/affine/page-properties/styles.css.ts +++ b/packages/frontend/core/src/components/affine/page-properties/styles.css.ts @@ -212,7 +212,7 @@ export const propertyRowCell = style({ position: 'relative', borderRadius: 4, fontSize: cssVar('fontSm'), - lineHeight: '20px', + lineHeight: '22px', userSelect: 'none', ':focus-visible': { outline: 'none', diff --git a/packages/frontend/core/src/components/affine/page-properties/table.tsx b/packages/frontend/core/src/components/affine/page-properties/table.tsx index 84dee7f325..ade00f6039 100644 --- a/packages/frontend/core/src/components/affine/page-properties/table.tsx +++ b/packages/frontend/core/src/components/affine/page-properties/table.tsx @@ -60,6 +60,7 @@ import { import { AffinePageReference } from '../reference-link'; import { managerContext, pageInfoCollapsedAtom } from './common'; +import { ConfirmDeletePropertyModal } from './confirm-delete-property-modal'; import { getDefaultIconName, nameToIcon, @@ -281,7 +282,11 @@ const VisibilityModeSelector = ({ }, }} > -
+
{required ? ( t['com.affine.page-properties.property.required']() ) : ( @@ -305,7 +310,11 @@ export const PagePropertiesSettingsPopup = ({ const menuItems = useMemo(() => { const options: MenuItemOption[] = []; options.push( -
+
{t['com.affine.page-properties.settings.title']()}
); @@ -327,7 +336,12 @@ export const PagePropertiesSettingsPopup = ({ -
{name}
+
+ {name} +
); @@ -407,6 +421,8 @@ export const PagePropertyRowNameMenu = ({ const [localProperty, setLocalProperty] = useState(() => ({ ...property })); const nextVisibility = rotateVisibility(localProperty.visibility); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const handleFinishEditing = useCallback(() => { onFinishEditing(); manager.updateCustomPropertyMeta(meta.id, localPropertyMeta); @@ -445,14 +461,9 @@ export const PagePropertyRowNameMenu = ({ }, [nextVisibility] ); - const handleDelete = useCallback( - (e: MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - manager.removeCustomProperty(property.id); - }, - [manager, property.id] - ); + const handleDelete = useCallback(() => { + manager.removeCustomProperty(property.id); + }, [manager, property.id]); const handleIconChange = useCallback( (icon: PagePropertyIcon) => { @@ -496,12 +507,11 @@ export const PagePropertyRowNameMenu = ({ type: 'danger', icon: , text: t['com.affine.page-properties.property.remove-property'](), - onClick: handleDelete, + onClick: () => setShowDeleteModal(true), }); } return renderMenuItemOptions(options); }, [ - handleDelete, handleIconChange, handleNameBlur, handleNameChange, @@ -513,20 +523,36 @@ export const PagePropertyRowNameMenu = ({ ]); return ( - - {children} - + <> + + {children} + + { + setShowDeleteModal(false); + handleDelete(); + }} + onCancel={() => setShowDeleteModal(false)} + show={showDeleteModal} + property={meta} + /> + ); }; @@ -607,7 +633,11 @@ export const PagePropertiesTableHeader = ({
{properties.length === 0 || manager.readonly ? null : ( - } /> + } + /> )}
@@ -802,7 +832,7 @@ export const PagePropertiesCreatePropertyMenuItems = ({ return useMemo(() => { const options: MenuItemOption[] = []; options.push( -
+
{t['com.affine.page-properties.create-property.menu.header']()}
); @@ -864,7 +894,7 @@ const PagePropertiesAddPropertyMenuItems = ({ const menuItems = useMemo(() => { const options: MenuItemOption[] = []; options.push( -
+
{t['com.affine.page-properties.add-property.menu.header']()}
); diff --git a/packages/frontend/core/src/components/affine/page-properties/tags-inline-editor.tsx b/packages/frontend/core/src/components/affine/page-properties/tags-inline-editor.tsx index 8ef29cbc5f..6287c648a2 100644 --- a/packages/frontend/core/src/components/affine/page-properties/tags-inline-editor.tsx +++ b/packages/frontend/core/src/components/affine/page-properties/tags-inline-editor.tsx @@ -47,7 +47,7 @@ const InlineTagsList = ({ children, }: PropsWithChildren) => { return ( -
+
{value.map((tagId, idx) => { const tag = options.find(t => t.id === tagId); if (!tag) { @@ -251,7 +251,7 @@ export const TagsEditor = ({ ); return ( -
+
{ onAddTag(tag.id); }} @@ -296,6 +299,7 @@ export const TagsEditor = ({ })} {exactMatch || !inputValue ? null : (
{ setInputValue(''); diff --git a/packages/frontend/core/src/components/affine/setting-modal/setting-sidebar/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/setting-sidebar/index.tsx index 34a0432607..a225416d63 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/setting-sidebar/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/setting-sidebar/index.tsx @@ -269,6 +269,7 @@ const WorkspaceListItem = ({ .map(({ key, title }) => { return (
{ onClick(key); }} diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/properties/index.tsx b/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/properties/index.tsx index 309a4590ac..bd7a4be0f9 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/properties/index.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/properties/index.tsx @@ -1,4 +1,4 @@ -import { Button, ConfirmModal, IconButton, Menu } from '@affine/component'; +import { Button, IconButton, Menu } from '@affine/component'; import { SettingHeader } from '@affine/component/setting-components'; import { useWorkspacePropertiesAdapter } from '@affine/core/hooks/use-affine-adapter'; import { useWorkspace } from '@affine/core/hooks/use-workspace'; @@ -28,6 +28,7 @@ import { PagePropertiesMetaManager, type PagePropertyIcon, } from '../../../page-properties'; +import { ConfirmDeletePropertyModal } from '../../../page-properties/confirm-delete-property-modal'; import { EditPropertyNameMenuItem, type MenuItemOption, @@ -54,57 +55,9 @@ const Divider = () => { return
; }; -const ConfirmDeletePropertyModal = ({ - onConfirm, - onCancel, - property, - count, - show, -}: { - property: PageInfoCustomPropertyMeta; - count: number; - show: boolean; - onConfirm: () => void; - onCancel: () => void; -}) => { - const t = useAFFiNEI18N(); - - return ( - - The {{ name: property.name } as any} property will be - removed from count doc(s). This action cannot be undone. - - } - onConfirm={onConfirm} - cancelButtonOptions={{ - onClick: onCancel, - }} - confirmButtonOptions={{ - type: 'error', - children: t['Confirm'](), - }} - /> - ); -}; - const EditPropertyButton = ({ property, - count, }: { - count: number; property: PageInfoCustomPropertyMeta; }) => { const t = useAFFiNEI18N(); @@ -234,7 +187,6 @@ const EditPropertyButton = ({ onCancel={() => setShowDeleteModal(false)} show={showDeleteModal} property={property} - count={count} /> ); @@ -281,7 +233,7 @@ const CustomPropertyRow = ({ {t['com.affine.page-properties.property.required']()}
) : null} - +
); }; diff --git a/packages/frontend/core/src/components/page-list/docs/page-tags.tsx b/packages/frontend/core/src/components/page-list/docs/page-tags.tsx index 82551f5889..20504f4baf 100644 --- a/packages/frontend/core/src/components/page-list/docs/page-tags.tsx +++ b/packages/frontend/core/src/components/page-list/docs/page-tags.tsx @@ -44,6 +44,8 @@ export const TagItem = ({ data-testid="page-tag" className={styles.tag} data-idx={idx} + data-tag-id={tag.id} + data-tag-value={tag.value} title={tag.value} style={style} > @@ -59,7 +61,11 @@ export const TagItem = ({ />
{tag.value}
{onRemoved ? ( -
+
) : null} diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 9bc0441ad7..5188f01dfb 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -1107,7 +1107,7 @@ "com.affine.page-properties.backlinks": "Backlinks", "com.affine.page-properties.page-info": "Page Info", "com.affine.page-properties.settings.title": "customize properties", - "com.affine.page-properties.add-property": "Add a property", + "com.affine.page-properties.add-property": "Add property", "com.affine.page-properties.add-property.menu.header": "Properties", "com.affine.page-properties.create-property.menu.header": "Type", "com.affine.page-properties.add-property.menu.create": "Create property", diff --git a/tests/affine-local/e2e/all-page.spec.ts b/tests/affine-local/e2e/all-page.spec.ts index 2e61b86893..868d2d1659 100644 --- a/tests/affine-local/e2e/all-page.spec.ts +++ b/tests/affine-local/e2e/all-page.spec.ts @@ -106,7 +106,7 @@ test('use monthpicker to modify the month of datepicker', async ({ page }) => { await checkDatePickerMonth(page, nextMonth); }); -test.skip('allow creation of filters by tags', async ({ page }) => { +test('allow creation of filters by tags', async ({ page }) => { await openHomePage(page); await waitForEditorLoad(page); await clickSideBarAllPageButton(page); diff --git a/tests/affine-local/e2e/local-first-collections-items.spec.ts b/tests/affine-local/e2e/local-first-collections-items.spec.ts index 7d0878a0fa..55d7ff54f3 100644 --- a/tests/affine-local/e2e/local-first-collections-items.spec.ts +++ b/tests/affine-local/e2e/local-first-collections-items.spec.ts @@ -137,27 +137,6 @@ test('edit collection and change filter date', async ({ page }) => { expect(await first.textContent()).toBe('123'); }); -test.skip('create temporary filter by click tag', async ({ page }) => { - await clickNewPageButton(page); - await getBlockSuiteEditorTitle(page).click(); - await getBlockSuiteEditorTitle(page).fill('test page'); - await page.locator('page-meta-tags').click(); - await page.locator('.add-tag').click(); - await page.keyboard.type('TODO Tag'); - await page.keyboard.press('Enter'); - await page.keyboard.press('Escape'); - await page.locator('.tag', { hasText: 'TODO Tag' }).click(); - const cell = page.getByTestId('page-list-item-title').getByText('test page'); - await expect(cell).toBeVisible(); - expect(await page.getByTestId('page-list-item').count()).toBe(1); - await page.getByTestId('filter-arg').click(); - - await page.getByTestId('multi-select-TODO Tag').click(); - expect( - await page.getByTestId('page-list-item').count() - ).toBeGreaterThanOrEqual(2); -}); - test('add collection from sidebar', async ({ page }) => { await removeOnboardingPages(page); await clickNewPageButton(page); diff --git a/tests/affine-local/e2e/local-first-delete-page.spec.ts b/tests/affine-local/e2e/local-first-delete-page.spec.ts index 64318b12f8..2510e03ac4 100644 --- a/tests/affine-local/e2e/local-first-delete-page.spec.ts +++ b/tests/affine-local/e2e/local-first-delete-page.spec.ts @@ -48,6 +48,7 @@ test('page delete -> create new page -> refresh page -> new page should be appea page, workspace, }) => { + test.slow(); await openHomePage(page); await waitForEditorLoad(page); await clickNewPageButton(page); diff --git a/tests/affine-local/e2e/local-first-favorites-items.spec.ts b/tests/affine-local/e2e/local-first-favorites-items.spec.ts index 8214ade77a..60e70221df 100644 --- a/tests/affine-local/e2e/local-first-favorites-items.spec.ts +++ b/tests/affine-local/e2e/local-first-favorites-items.spec.ts @@ -1,4 +1,5 @@ import { test } from '@affine-test/kit/playwright'; +import { clickPageModeButton } from '@affine-test/kit/utils/editor'; import { openHomePage } from '@affine-test/kit/utils/load-page'; import { clickNewPageButton, @@ -135,8 +136,9 @@ test("Deleted page's reference will not be shown in sidebar", async ({ test('Add new favorite page via sidebar', async ({ page }) => { await openHomePage(page); await waitForEditorLoad(page); - await clickNewPageButton(page); + await page.getByTestId('slider-bar-add-favorite-button').first().click(); + await clickPageModeButton(page); await waitForEmptyEditor(page); // enter random page title diff --git a/tests/affine-local/e2e/page-properties.spec.ts b/tests/affine-local/e2e/page-properties.spec.ts new file mode 100644 index 0000000000..41e61fb677 --- /dev/null +++ b/tests/affine-local/e2e/page-properties.spec.ts @@ -0,0 +1,243 @@ +/* eslint-disable unicorn/prefer-dom-node-dataset */ +import { test } from '@affine-test/kit/playwright'; +import { clickPageModeButton } from '@affine-test/kit/utils/editor'; +import { openHomePage } from '@affine-test/kit/utils/load-page'; +import { dragTo, waitForEditorLoad } from '@affine-test/kit/utils/page-logic'; +import { + addCustomProperty, + changePropertyVisibility, + clickPropertyValue, + closeTagsEditor, + expectTagsVisible, + getPropertyValueLocator, + openTagsEditor, + openWorkspaceProperties, + removeSelectedTag, + searchAndCreateTag, +} from '@affine-test/kit/utils/properties'; +import { expect } from '@playwright/test'; + +test.beforeEach(async ({ page }) => { + await openHomePage(page); + await clickPageModeButton(page); + await waitForEditorLoad(page); +}); + +test('allow create tag', async ({ page }) => { + await openTagsEditor(page); + await searchAndCreateTag(page, 'Test1'); + await searchAndCreateTag(page, 'Test2'); + await closeTagsEditor(page); + await expectTagsVisible(page, ['Test1', 'Test2']); + + await openTagsEditor(page); + await removeSelectedTag(page, 'Test1'); + await closeTagsEditor(page); + await expectTagsVisible(page, ['Test2']); +}); + +test('add custom property', async ({ page }) => { + await addCustomProperty(page, 'Text'); + await addCustomProperty(page, 'Number'); + await addCustomProperty(page, 'Date'); + await addCustomProperty(page, 'Checkbox'); +}); + +test('add custom property & edit', async ({ page }) => { + await addCustomProperty(page, 'Checkbox'); + await expect( + getPropertyValueLocator(page, 'Checkbox').locator('input') + ).not.toBeChecked(); + await clickPropertyValue(page, 'Checkbox'); + await expect( + getPropertyValueLocator(page, 'Checkbox').locator('input') + ).toBeChecked(); +}); + +test('property table reordering', async ({ page }) => { + await addCustomProperty(page, 'Text'); + await addCustomProperty(page, 'Number'); + await addCustomProperty(page, 'Date'); + await addCustomProperty(page, 'Checkbox'); + + await dragTo( + page, + page.locator('[data-testid="page-property-row-name"]:has-text("Text")'), + page.locator( + '[data-testid="page-property-row-name"]:has-text("Checkbox") + div' + ) + ); + + // new order should be (Tags), Number, Date, Checkbox, Text + for (const [index, property] of [ + 'Tags', + 'Number', + 'Date', + 'Checkbox', + 'Text', + ].entries()) { + await expect( + page + .getByTestId('page-property-row') + .nth(index) + .getByTestId('page-property-row-name') + ).toHaveText(property); + } +}); + +test('page info show more will not should by default when there is no properties', async ({ + page, +}) => { + // by default page info show more should not show + await expect(page.getByTestId('page-info-show-more')).not.toBeVisible(); +}); + +test('page info show more will show all properties', async ({ page }) => { + await addCustomProperty(page, 'Text'); + await addCustomProperty(page, 'Number'); + await addCustomProperty(page, 'Date'); + await addCustomProperty(page, 'Checkbox'); + + await expect(page.getByTestId('page-info-show-more')).toBeVisible(); + await page.click('[data-testid="page-info-show-more"]'); + await expect( + page.getByRole('heading', { + name: 'customize properties', + }) + ).toBeVisible(); + + // new order should be (Tags), Number, Date, Checkbox, Text + for (const [index, property] of [ + 'Text', + 'Number', + 'Date', + 'Checkbox', + ].entries()) { + await expect( + page + .getByTestId('page-properties-settings-menu-item') + .nth(index) + .getByTestId('page-property-setting-row-name') + ).toHaveText(property); + } +}); + +test('change page properties visibility', async ({ page }) => { + await addCustomProperty(page, 'Text'); + await addCustomProperty(page, 'Number'); + await addCustomProperty(page, 'Date'); + await addCustomProperty(page, 'Checkbox'); + + // add some number to number property + await clickPropertyValue(page, 'Number'); + await page.locator('input[type=number]').fill('123'); + + await changePropertyVisibility(page, 'Text', 'Hide in view'); + await changePropertyVisibility(page, 'Number', 'Hide in view when empty'); + + // text property should not be visible + await expect( + page.locator('[data-testid="page-property-row-name"]:has-text("Text")') + ).not.toBeVisible(); + + // number property should be visible + await expect( + page.locator('[data-testid="page-property-row-name"]:has-text("Number")') + ).toBeVisible(); +}); + +test('check if added property is also in workspace settings', async ({ + page, +}) => { + await addCustomProperty(page, 'Text'); + await openWorkspaceProperties(page); + await expect( + page.locator('[data-testid=custom-property-row]:has-text("Text")') + ).toBeVisible(); +}); + +test('edit property name', async ({ page }) => { + await addCustomProperty(page, 'Text'); + await page + .locator('[data-testid="page-property-row-name"]:has-text("Text")') + .click(); + await expect(page.locator('[data-radix-menu-content]')).toBeVisible(); + await expect(page.locator('[data-radix-menu-content] input')).toHaveValue( + 'Text' + ); + await page.locator('[data-radix-menu-content] input').fill('New Text'); + await page.keyboard.press('Enter'); + await expect(page.locator('[data-radix-menu-content] input')).toHaveValue( + 'New Text' + ); + await page.keyboard.press('Escape'); + + // check if the property name is also updated in workspace settings + await openWorkspaceProperties(page); + await expect( + page.locator('[data-testid=custom-property-row]:has-text("New Text")') + ).toBeVisible(); +}); + +test('delete property via property popup', async ({ page }) => { + await addCustomProperty(page, 'Text'); + await page + .locator('[data-testid="page-property-row-name"]:has-text("Text")') + .click(); + await expect(page.locator('[data-radix-menu-content]')).toBeVisible(); + await page + .locator('[data-radix-menu-content]') + .getByRole('menuitem', { + name: 'Remove property', + }) + .click(); + // confirm delete dialog should show + await expect(page.getByRole('dialog')).toContainText( + `The "Text" property will be remove from 1 doc(s). This action cannot be undone.` + ); + await page + .getByRole('button', { + name: 'Confirm', + }) + .click(); + // check if the property is removed + await expect( + page.locator('[data-testid="page-property-row-name"]:has-text("Text")') + ).not.toBeVisible(); +}); + +test('create a required property', async ({ page }) => { + await openWorkspaceProperties(page); + await addCustomProperty(page, 'Text', true); + + await page + .locator('[data-testid="custom-property-row"]:has-text("Text")') + .getByRole('button') + .click(); + + await page + .getByRole('menuitem', { + name: 'Set as required property', + }) + .click(); + + await expect( + page.locator('[data-testid="custom-property-row"]:has-text("Text")') + ).toContainText('Required'); + + // close workspace settings + await page.keyboard.press('Escape'); + + // check if the property is also required in page properties + await expect( + page.locator('[data-testid="page-property-row-name"]:has-text("Text")') + ).toBeVisible(); + + // check if the required property is also listed in the show more menu + await page.click('[data-testid="page-info-show-more"]'); + await expect( + page.locator( + '[data-testid="page-properties-settings-menu-item"]:has-text("Text")' + ) + ).toContainText('Required'); +}); diff --git a/tests/kit/utils/filter.ts b/tests/kit/utils/filter.ts index 92e5918cdb..02c5696c17 100644 --- a/tests/kit/utils/filter.ts +++ b/tests/kit/utils/filter.ts @@ -126,8 +126,9 @@ export const createPageWithTag = async ( await clickNewPageButton(page); await getBlockSuiteEditorTitle(page).click(); await getBlockSuiteEditorTitle(page).fill('test page'); - await page.locator('page-meta-tags').click(); - await page.locator('.add-tag').click(); + await page + .locator('[data-testid="page-property-row"][data-property="tags"]') + .click(); for (const name of options.tags) { await createTag(page, name); } diff --git a/tests/kit/utils/page-logic.ts b/tests/kit/utils/page-logic.ts index 9ca0ef6f94..84f6f04358 100644 --- a/tests/kit/utils/page-logic.ts +++ b/tests/kit/utils/page-logic.ts @@ -26,7 +26,6 @@ export async function clickNewPageButton(page: Page) { await page.getByTestId('sidebar-new-page-button').click({ delay: 100, }); - await expect(page.locator('.doc-title-container-empty')).toBeVisible(); await waitForEmptyEditor(page); } diff --git a/tests/kit/utils/properties.ts b/tests/kit/utils/properties.ts new file mode 100644 index 0000000000..f7f53cf98d --- /dev/null +++ b/tests/kit/utils/properties.ts @@ -0,0 +1,178 @@ +import { expect, type Page } from '@playwright/test'; + +export const getPropertyValueLocator = (page: Page, property: string) => { + return page.locator( + `[data-testid="page-property-row-name"]:has-text("${property}") + *` + ); +}; + +export const clickPropertyValue = async (page: Page, property: string) => { + await getPropertyValueLocator(page, property).click(); +}; + +export const openTagsEditor = async (page: Page) => { + await clickPropertyValue(page, 'tags'); + await expect(page.getByTestId('tags-editor-popup')).toBeVisible(); +}; + +export const closeTagsEditor = async (page: Page) => { + await page.keyboard.press('Escape'); + await expect(page.getByTestId('tags-editor-popup')).not.toBeVisible(); +}; + +export const clickTagFromSelector = async (page: Page, name: string) => { + // assume that the tags editor is already open + await page + .locator(`[data-testid="tag-selector-item"][data-tag-value="${name}"]`) + .click(); +}; + +export const removeSelectedTag = async (page: Page, name: string) => { + await page + .locator( + `[data-testid="tags-editor-popup"] [data-testid="inline-tags-list"] [data-tag-value="${name}"] [data-testid="remove-tag-button"]` + ) + .click(); +}; + +export const filterTags = async (page: Page, filter: string) => { + await page + .locator( + '[data-testid="tags-editor-popup"] [data-testid="inline-tags-list"] input' + ) + .fill(filter); +}; + +export const searchAndCreateTag = async (page: Page, name: string) => { + await filterTags(page, name); + await page + .locator( + '[data-testid="tags-editor-popup"] [data-testid="tag-selector-item"]:has-text("Create ")' + ) + .click(); +}; + +export const expectTagsVisible = async (page: Page, tags: string[]) => { + const tagListPanel = page + .getByTestId('page-property-row') + .getByTestId('inline-tags-list'); + + expect(await tagListPanel.locator('[data-tag-value]').count()).toBe( + tags.length + ); + + for (const tag of tags) { + await expect( + tagListPanel.locator(`[data-tag-value="${tag}"]`) + ).toBeVisible(); + } +}; + +export const clickAddPropertyButton = async (page: Page) => { + await page + .getByRole('button', { + name: 'Add property', + }) + .click(); +}; + +export const addCustomProperty = async ( + page: Page, + type: string, + inSettings?: boolean +) => { + await clickAddPropertyButton(page); + if (!inSettings) { + await expect( + page.getByRole('heading', { + name: 'Properties', + }) + ).toBeVisible(); + await page + .getByRole('menuitem', { + name: 'Create property', + }) + .click(); + } + await expect( + page.getByRole('heading', { + name: 'Type', + }) + ).toBeVisible(); + await page + .getByRole('menuitem', { + name: type, + }) + .click(); + if (!inSettings) { + await expect( + page + .getByRole('menuitem', { + name: type, + }) + .locator('.selected') + ).toBeVisible(); + await page.keyboard.press('Escape', { + delay: 100, + }); + } +}; + +export const expectPropertyOrdering = async ( + page: Page, + properties: string[] +) => { + for (let i = 0; i < properties.length; i++) { + await expect( + page.locator(`[data-testid="page-property-row-name"])`).nth(i) + ).toHaveText(properties[i]); + } +}; + +export const openWorkspaceProperties = async (page: Page) => { + await page.getByTestId('slider-bar-workspace-setting-button').click(); + await page + .locator('[data-testid="workspace-list-item"] .setting-name') + .click(); + await page.getByTestId('workspace-list-item-properties').click(); +}; + +export const selectVisibilitySelector = async ( + page: Page, + name: string, + option: string +) => { + await page + .getByRole('menu') + .locator( + `[data-testid="page-properties-settings-menu-item"]:has-text("${name}")` + ) + .getByRole('button') + .click(); + + await page + .getByRole('menu') + .last() + .getByRole('menuitem', { + name: option, + exact: true, + }) + .click(); + + await page.keyboard.press('Escape'); +}; + +export const changePropertyVisibility = async ( + page: Page, + name: string, + option: string +) => { + await expect(page.getByTestId('page-info-show-more')).toBeVisible(); + await page.click('[data-testid="page-info-show-more"]'); + await expect( + page.getByRole('heading', { + name: 'customize properties', + }) + ).toBeVisible(); + await selectVisibilitySelector(page, name, option); +};