Files
docs/src/frontend/apps/e2e/__tests__/app-impress/doc-grid-move.spec.ts
Anthony LC b5d9c58761 💄(frontend) replace icons in tree children action
We replace icons with what is used in the ui kit.
By replacing the icons, we also fix the height
problems with Firefox.
2026-04-08 16:15:30 +02:00

579 lines
17 KiB
TypeScript

import { expect, test } from '@playwright/test';
import {
createDoc,
getGridRow,
getOtherBrowserName,
mockedListDocs,
toggleHeaderMenu,
verifyDocName,
} from './utils-common';
import { writeInEditor } from './utils-editor';
import {
addNewMember,
connectOtherUserToDoc,
updateShareLink,
} from './utils-share';
import { createRootSubPage } from './utils-sub-pages';
test.describe('Doc grid move', () => {
test('it checks drag and drop functionality', async ({
page,
browserName,
}) => {
await page.goto('/');
await createDoc(page, 'Draggable doc', browserName, 1);
await page.getByRole('button', { name: 'Back to homepage' }).click();
await createDoc(page, 'Droppable doc', browserName, 1);
await page.getByRole('button', { name: 'Back to homepage' }).click();
const response = await page.waitForResponse(
(response) =>
response.url().endsWith('documents/?page=1') &&
response.status() === 200,
);
const responseJson = await response.json();
const items = responseJson.results;
const docsGrid = page.getByTestId('docs-grid');
await expect(docsGrid).toBeVisible();
await expect(page.getByTestId('grid-loader')).toBeHidden();
const draggableElement = page.getByTestId(`draggable-doc-${items[1].id}`);
const dropZone = page.getByTestId(`droppable-doc-${items[0].id}`);
await expect(draggableElement).toBeVisible();
await expect(dropZone).toBeVisible();
// Get the position of the elements
const draggableBoundingBox = await draggableElement.boundingBox();
const dropZoneBoundingBox = await dropZone.boundingBox();
expect(draggableBoundingBox).toBeDefined();
expect(dropZoneBoundingBox).toBeDefined();
if (!draggableBoundingBox || !dropZoneBoundingBox) {
throw new Error('Unable to determine the position of the elements');
}
await page.mouse.move(
draggableBoundingBox.x + draggableBoundingBox.width / 2,
draggableBoundingBox.y + draggableBoundingBox.height / 2,
);
await page.mouse.down();
// Move to the target zone
await page.mouse.move(
dropZoneBoundingBox.x + dropZoneBoundingBox.width / 2,
dropZoneBoundingBox.y + dropZoneBoundingBox.height / 2,
{ steps: 10 }, // Make the movement smoother
);
const dragOverlay = page.getByTestId('drag-doc-overlay');
await expect(dragOverlay).toBeVisible();
await expect(dragOverlay).toHaveText(items[1].title as string);
await page.mouse.up();
await expect(dragOverlay).toBeHidden();
});
test("it checks can't drop when we have not the minimum role", async ({
page,
}) => {
await mockedListDocs(page, data);
await page.goto('/');
const docsGrid = page.getByTestId('docs-grid');
await expect(docsGrid).toBeVisible();
await expect(page.getByTestId('grid-loader')).toBeHidden();
const canDropAndDrag = page.getByTestId('droppable-doc-can-drop-and-drag');
const noDropAndNoDrag = page.getByTestId(
'droppable-doc-no-drop-and-no-drag',
);
await expect(canDropAndDrag).toBeVisible();
await expect(noDropAndNoDrag).toBeVisible();
const canDropAndDragBoundigBox = await canDropAndDrag.boundingBox();
const noDropAndNoDragBoundigBox = await noDropAndNoDrag.boundingBox();
if (!canDropAndDragBoundigBox || !noDropAndNoDragBoundigBox) {
throw new Error('Unable to determine the position of the elements');
}
await page.mouse.move(
canDropAndDragBoundigBox.x + canDropAndDragBoundigBox.width / 2,
canDropAndDragBoundigBox.y + canDropAndDragBoundigBox.height / 2,
);
await page.mouse.down();
await page.mouse.move(
noDropAndNoDragBoundigBox.x + noDropAndNoDragBoundigBox.width / 2,
noDropAndNoDragBoundigBox.y + noDropAndNoDragBoundigBox.height / 2,
{ steps: 10 },
);
const dragOverlay = page.getByTestId('drag-doc-overlay');
await expect(dragOverlay).toBeVisible();
await expect(dragOverlay).toHaveText(
'You must be at least the administrator of the target document',
);
await page.mouse.up();
});
test("it checks can't drag when we have not the minimum role", async ({
page,
}) => {
await mockedListDocs(page, data);
await page.goto('/');
const docsGrid = page.getByTestId('docs-grid');
await expect(docsGrid).toBeVisible();
await expect(page.getByTestId('grid-loader')).toBeHidden();
const canDropAndDrag = page.getByTestId('droppable-doc-can-drop-and-drag');
const noDropAndNoDrag = page.getByTestId(
'droppable-doc-no-drop-and-no-drag',
);
await expect(canDropAndDrag).toBeVisible();
await expect(noDropAndNoDrag).toBeVisible();
const canDropAndDragBoundigBox = await canDropAndDrag.boundingBox();
const noDropAndNoDragBoundigBox = await noDropAndNoDrag.boundingBox();
if (!canDropAndDragBoundigBox || !noDropAndNoDragBoundigBox) {
throw new Error('Unable to determine the position of the elements');
}
await page.mouse.move(
noDropAndNoDragBoundigBox.x + noDropAndNoDragBoundigBox.width / 2,
noDropAndNoDragBoundigBox.y + noDropAndNoDragBoundigBox.height / 2,
);
await page.mouse.down();
await page.mouse.move(
canDropAndDragBoundigBox.x + canDropAndDragBoundigBox.width / 2,
canDropAndDragBoundigBox.y + canDropAndDragBoundigBox.height / 2,
{ steps: 10 },
);
const dragOverlay = page.getByTestId('drag-doc-overlay');
await expect(dragOverlay).toBeVisible();
await expect(dragOverlay).toHaveText(
'You must be the owner to move the document',
);
await page.mouse.up();
});
test('it moves a doc from the doc search modal', async ({
page,
browserName,
}) => {
await page.goto('/');
const [titleDoc1] = await createDoc(page, 'Draggable doc', browserName, 1);
const otherBrowserName = getOtherBrowserName(browserName);
await page.getByRole('button', { name: 'Share' }).click();
await addNewMember(page, 0, 'Administrator', otherBrowserName);
await page
.getByRole('dialog')
.getByRole('button', { name: 'close' })
.click();
await page.getByRole('button', { name: 'Back to homepage' }).click();
const [titleDoc2] = await createDoc(page, 'Droppable doc', browserName, 1);
await page.getByRole('button', { name: 'Back to homepage' }).click();
const docsGrid = page.getByTestId('docs-grid');
await expect(docsGrid.getByText(titleDoc1)).toBeVisible();
await expect(docsGrid.getByText(titleDoc2)).toBeVisible();
const row = await getGridRow(page, titleDoc1);
await row.getByRole('button', { name: /Open the menu of actions/ }).click();
await page.getByRole('menuitem', { name: 'Move into a doc' }).click();
await expect(
page.getByRole('dialog').getByRole('heading', { name: 'Move' }),
).toBeVisible();
const input = page.getByRole('combobox', { name: 'Quick search input' });
await input.click();
await input.fill(titleDoc2);
await expect(
page.getByRole('option').first().getByText(titleDoc2),
).toBeVisible();
// Select the first result
await page.keyboard.press('Enter');
// The CTA should get the focus
await page.keyboard.press('Tab');
// Validate the move action
await page.keyboard.press('Enter');
await expect(
page
.getByRole('dialog')
.getByText('it will lose its current access rights'),
).toBeVisible();
await page
.getByRole('dialog')
.getByRole('button', { name: 'Move', exact: true })
.first()
.click();
await expect(docsGrid.getByText(titleDoc1)).toBeHidden();
await docsGrid
.getByRole('link', { name: `Open document ${titleDoc2}` })
.click();
await verifyDocName(page, titleDoc2);
const docTree = page.getByTestId('doc-tree');
await expect(docTree.getByText(titleDoc1)).toBeVisible();
});
test('it proposes an access request when moving a doc without sufficient permissions', async ({
page,
browserName,
}) => {
test.slow();
await page.goto('/');
const [titleDoc1] = await createDoc(page, 'Move doc', browserName, 1);
const { otherPage, cleanup } = await connectOtherUserToDoc({
docUrl: '/',
browserName,
});
// Another user creates a doc
const [titleDoc2] = await createDoc(otherPage, 'Drop doc', browserName, 1);
await writeInEditor({
page: otherPage,
text: 'Hello world',
});
// Make it public
await otherPage.getByRole('button', { name: 'Share' }).click();
await updateShareLink(otherPage, 'Public');
await otherPage
.getByRole('dialog')
.getByRole('button', { name: 'close' })
.click();
const otherPageUrl = otherPage.url();
// The first user visit the doc to have it in his grid list
await page.goto(otherPageUrl);
await expect(page.getByText('Hello world')).toBeVisible();
await page.waitForTimeout(1000);
await page.getByRole('button', { name: 'Back to homepage' }).click();
const docsGrid = page.getByTestId('docs-grid');
await expect(docsGrid.getByText(titleDoc1)).toBeVisible();
await expect(docsGrid.getByText(titleDoc2)).toBeVisible();
const row = await getGridRow(page, titleDoc1);
await row.getByRole('button', { name: /Open the menu of actions/ }).click();
await page.getByRole('menuitem', { name: 'Move into a doc' }).click();
await expect(
page.getByRole('dialog').getByRole('heading', { name: 'Move' }),
).toBeVisible();
const input = page.getByRole('combobox', { name: 'Quick search input' });
await input.click();
await input.fill(titleDoc2);
await expect(
page.getByRole('option').first().getByText(titleDoc2),
).toBeVisible();
// Select the first result
await page.keyboard.press('Enter');
// The CTA should get the focus
await page.keyboard.press('Tab');
// Validate the move action
await page.keyboard.press('Enter');
// Request access modal should be visible
await expect(
page
.getByRole('dialog')
.getByText(
'You need edit access to the destination. Request access, then try again.',
),
).toBeVisible();
await page
.getByRole('dialog')
.getByRole('button', { name: 'Request access', exact: true })
.first()
.click();
// The other user should receive the access request and be able to approve it
await otherPage.getByRole('button', { name: 'Share' }).click();
await expect(otherPage.getByText('Access Requests')).toBeVisible();
await expect(
otherPage.getByText(
process.env[`USERNAME_${browserName.toUpperCase()}`] || '',
),
).toBeVisible();
const emailRequest =
process.env[`SIGN_IN_USERNAME_${browserName.toUpperCase()}`] || '';
await expect(otherPage.getByText(emailRequest)).toBeVisible();
const container = otherPage.getByTestId(
`doc-share-access-request-row-${emailRequest}`,
);
await container.getByTestId('doc-role-dropdown').click();
await otherPage
.getByRole('menuitemradio', { name: 'Administrator' })
.click();
await container.getByRole('button', { name: 'Approve' }).click();
await expect(otherPage.getByText('Access Requests')).toBeHidden();
await expect(otherPage.getByText('Share with 2 users')).toBeVisible();
await expect(
otherPage.getByText(
process.env[`USERNAME_${browserName.toUpperCase()}`] || '',
),
).toBeVisible();
// The first user should now be able to move the doc
await page.reload();
await row.getByRole('button', { name: /Open the menu of actions/ }).click();
await page.getByRole('menuitem', { name: 'Move into a doc' }).click();
await expect(
page.getByRole('dialog').getByRole('heading', { name: 'Move' }),
).toBeVisible();
await input.click();
await input.fill(titleDoc2);
await expect(
page.getByRole('option').first().getByText(titleDoc2),
).toBeVisible();
// Select the first result
await page.keyboard.press('Enter');
// The CTA should get the focus
await page.keyboard.press('Tab');
// Validate the move action
await page.keyboard.press('Enter');
await expect(docsGrid.getByText(titleDoc1)).toBeHidden();
await docsGrid
.getByRole('link', { name: `Open document ${titleDoc2}` })
.click();
await verifyDocName(page, titleDoc2);
const docTree = page.getByTestId('doc-tree');
await expect(docTree.getByText(titleDoc1)).toBeVisible({
timeout: 15000,
});
await cleanup();
});
});
test.describe('Doc grid dnd mobile', () => {
test.use({ viewport: { width: 500, height: 1200 } });
test('DND is deactivated on mobile', async ({ page, browserName }) => {
await page.goto('/');
const docsGrid = page.getByTestId('docs-grid');
await expect(page.getByTestId('docs-grid')).toBeVisible();
await expect(page.getByTestId('grid-loader')).toBeHidden();
await expect(docsGrid.getByRole('listitem').first()).toBeVisible();
await expect(docsGrid.locator('.--docs--grid-droppable')).toHaveCount(0);
await createDoc(page, 'Draggable doc mobile', browserName, 1, true);
await createRootSubPage(
page,
browserName,
'Draggable doc mobile child',
true,
);
await toggleHeaderMenu(page);
await expect(page.locator('.--docs-sub-page-item').first()).toHaveAttribute(
'draggable',
'false',
);
});
});
const data = [
{
id: 'can-drop-and-drag',
abilities: {
accesses_manage: true,
accesses_view: true,
ai_transform: true,
ai_translate: true,
attachment_upload: true,
children_list: true,
children_create: true,
collaboration_auth: true,
descendants: true,
destroy: true,
favorite: true,
link_configuration: true,
invite_owner: true,
move: true,
partial_update: true,
restore: true,
retrieve: true,
media_auth: true,
link_select_options: {
restricted: ['reader', 'editor'],
authenticated: ['reader', 'editor'],
public: ['reader', 'editor'],
},
tree: true,
update: true,
versions_destroy: true,
versions_list: true,
versions_retrieve: true,
},
created_at: '2025-03-14T14:45:22.527221Z',
creator: 'bc6895e0-8f6d-4b00-827d-c143aa6b2ecb',
depth: 1,
excerpt: null,
is_favorite: false,
link_role: 'reader',
link_reach: 'restricted',
nb_accesses_ancestors: 1,
nb_accesses_direct: 1,
numchild: 5,
path: '000000o',
title: 'Can drop and drag',
updated_at: '2025-03-14T14:45:27.699542Z',
user_roles: ['owner'],
user_role: 'owner',
},
{
id: 'can-only-drop',
title: 'Can only drop',
abilities: {
accesses_manage: true,
accesses_view: true,
ai_transform: true,
ai_translate: true,
attachment_upload: true,
children_list: true,
children_create: true,
collaboration_auth: true,
descendants: true,
destroy: true,
favorite: true,
link_configuration: true,
invite_owner: true,
move: true,
partial_update: true,
restore: true,
retrieve: true,
media_auth: true,
link_select_options: {
restricted: ['reader', 'editor'],
authenticated: ['reader', 'editor'],
public: ['reader', 'editor'],
},
tree: true,
update: true,
versions_destroy: true,
versions_list: true,
versions_retrieve: true,
},
created_at: '2025-03-14T14:45:22.527221Z',
creator: 'bc6895e0-8f6d-4b00-827d-c143aa6b2ecb',
depth: 1,
excerpt: null,
is_favorite: false,
link_role: 'reader',
link_reach: 'restricted',
nb_accesses_ancestors: 1,
nb_accesses_direct: 1,
numchild: 5,
path: '000000o',
updated_at: '2025-03-14T14:45:27.699542Z',
user_roles: ['editor'],
user_role: 'editor',
},
{
id: 'no-drop-and-no-drag',
abilities: {
accesses_manage: false,
accesses_view: true,
ai_transform: false,
ai_translate: false,
attachment_upload: false,
children_list: true,
children_create: false,
collaboration_auth: true,
descendants: true,
destroy: false,
favorite: true,
link_configuration: false,
invite_owner: false,
move: false,
partial_update: false,
restore: false,
retrieve: true,
media_auth: true,
link_select_options: {
restricted: ['reader', 'editor'],
authenticated: ['reader', 'editor'],
public: ['reader', 'editor'],
},
tree: true,
update: false,
versions_destroy: false,
versions_list: true,
versions_retrieve: true,
},
created_at: '2025-03-14T14:44:16.032773Z',
creator: '9264f420-f018-4bd6-96ae-4788f41af56d',
depth: 1,
excerpt: null,
is_favorite: false,
link_role: 'reader',
link_reach: 'restricted',
nb_accesses_ancestors: 14,
nb_accesses_direct: 14,
numchild: 0,
path: '000000l',
title: 'No drop and no drag',
updated_at: '2025-03-14T14:44:16.032774Z',
user_roles: ['reader'],
user_role: 'reader',
},
];