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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to
### Added

- ✨(backend) support creating subdoc from file #1987
- ✨(frontend) comment side panel #2279
- ✨(buildpack) add PaaS deployment support, tested with Scalingo #2293
- 🔧(backend) allow configuring settings OIDC_OP_USER_ENDPOINT_FORMAT

Expand Down
163 changes: 162 additions & 1 deletion src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import {
getOtherBrowserName,
verifyDocName,
} from './utils-common';
import { getEditor, writeInEditor } from './utils-editor';
import {
getEditor,
tryFocusEditorContent,
writeInEditor,
} from './utils-editor';
import {
addNewMember,
connectOtherUserToDoc,
Expand Down Expand Up @@ -430,3 +434,160 @@ test.describe('Doc Comments mobile', () => {
await expect(thread.getByText('This is a comment').first()).toBeVisible();
});
});

test.describe('Doc Comments Side Panel', () => {
test('it checks comments side bar interaction', async ({
page,
browserName,
}) => {
await createDoc(page, 'comment-doc-panel', browserName, 1);

await expect(
page.getByRole('button', { name: 'Show the comments sidebar' }),
).toBeHidden();

// Create comment thread
const editor = await writeInEditor({ page, text: 'Hello World' });
await editor.getByText('Hello').selectText();
await page.getByRole('button', { name: 'Add comment' }).click();

const thread = page.locator('.bn-thread');
await thread.getByRole('paragraph').first().fill('This is a comment');
await thread.locator('[data-test="save"]').click();

// Open comment side panel and check comment is visible in side panel
await page
.getByRole('button', { name: 'Show the comments sidebar' })
.click();

const elCommentsSidePanel = page.getByLabel('Comments side panel');
await expect(
elCommentsSidePanel.getByText('This is a comment'),
).toBeVisible();

// Click on comment in side panel and check it scrolls to the comment in the doc
await tryFocusEditorContent({ page });
for (let i = 0; i < 20; i++) {
await page.keyboard.press('Enter');
}
await writeInEditor({
page,
text: 'New paragraph',
});

await expect(editor.getByText('New paragraph')).toBeInViewport();
await elCommentsSidePanel.getByText('This is a comment').click();

await expect(editor.getByText('Hello World')).toBeVisible();
await expect(editor.getByText('New paragraph')).not.toBeInViewport();
await expect(editor.getByText('Hello World')).toHaveClass(
'bn-thread-mark-selected',
);

// Add a comment in the side panel
await elCommentsSidePanel
.locator(
'.bn-editor[contenteditable="true"] div[data-content-type="paragraph"]',
)
.first()
.fill('This is another comment');
await elCommentsSidePanel.locator('[data-test="save"]').click();
await expect(
elCommentsSidePanel
.locator(
'.bn-editor[contenteditable="false"] div[data-content-type="paragraph"]',
)
.getByText('This is another comment'),
).toBeVisible();

// Close the side panel and check the comments in the doc
await page
.getByRole('button', { name: 'Close the comments sidebar' })
.click();
await expect(elCommentsSidePanel).toBeHidden();
await editor.getByText('Hello World').click();
await expect(thread.getByText('This is another comment')).toBeVisible();

// Resolve the comment and check it disappears from the side panel and the doc
await thread.getByText('This is a comment').first().hover();
await thread.locator('[data-test="resolve"]').click();
await expect(thread).toBeHidden();
await page
.getByRole('button', { name: 'Show the comments sidebar' })
.click();
await expect(
elCommentsSidePanel.getByText('This is a comment'),
).toBeHidden();

// Goto resolved part, the comment should be visible in the side panel
await elCommentsSidePanel
.getByRole('button', { name: 'Filter comments' })
.click();
await page.getByRole('menuitem', { name: 'Resolved' }).click();
await elCommentsSidePanel.getByText('This is a comment').click();
await expect(editor.getByText('Hello World')).toHaveClass(
'bn-thread-mark-selected',
);

// Unresolve the comment and check it does not appears in the side panel resolved part
await thread.getByText('This is a comment').first().hover();
await thread.locator('[data-test="re-open"]').click();
await expect(
elCommentsSidePanel.getByText('This is a comment'),
).toBeHidden();

// It should be back in the side panel and in the doc
await page.getByRole('button', { name: 'Filter comments' }).click();
await page.getByRole('menuitem', { name: 'Open' }).click();
await expect(
elCommentsSidePanel.getByText('This is a comment'),
).toBeVisible();
await elCommentsSidePanel.getByText('This is a comment').click();
await expect(editor.getByText('Hello World')).toHaveClass(
'bn-thread-mark-selected',
);
});

test('it checks comments accessibility', async ({ page, browserName }) => {
await createDoc(page, 'comment-doc-panel', browserName, 1);

// Create comment thread
const editor = await writeInEditor({ page, text: 'Hello World' });
await editor.getByText('Hello').selectText();
await page.getByRole('button', { name: 'Add comment' }).click();

const thread = page.locator('.bn-thread');
await thread.getByRole('paragraph').first().fill('This is a comment');
await thread.locator('[data-test="save"]').click();

// Open comment side panel and check aria attributes
await page
.getByRole('button', { name: 'Show the comments sidebar' })
.click();

const elCommentsSidePanel = page.getByLabel('Comments side panel');
await expect(elCommentsSidePanel).not.toHaveAttribute('inert');

// Check panel get the focus when opening
await page.keyboard.press('Tab');
await expect(
elCommentsSidePanel.getByRole('button', { name: 'Filter comments' }),
).toBeFocused();
await page.keyboard.press('Tab');

// Check the focus goes back to the button that open the side panel
await expect(
elCommentsSidePanel.getByRole('button', {
name: 'Close the comments sidebar',
}),
).toBeFocused();
await page.keyboard.press('Enter');
await expect(elCommentsSidePanel).toBeHidden();
await expect(
page.getByRole('complementary', { name: 'Side panel' }),
).toHaveAttribute('inert');
await expect(
page.getByRole('button', { name: 'Show the comments sidebar' }),
).toBeFocused();
});
});
Original file line number Diff line number Diff line change
@@ -1,47 +1,66 @@
import { expect, test } from '@playwright/test';

import { createDoc, verifyDocName } from './utils-common';
import { createDoc } from './utils-common';
import { tryFocusEditorContent } from './utils-editor';

test.beforeEach(async ({ page }) => {
await page.goto('/');
});

test.describe('Doc Table Content', () => {
test('it checks the doc table content', async ({ page, browserName }) => {
const [randomDoc] = await createDoc(
page,
'doc-table-content',
browserName,
1,
);

await verifyDocName(page, randomDoc);

await page.locator('.ProseMirror').click();
await createDoc(page, 'doc-table-content', browserName, 1);

await expect(
page.getByRole('button', { name: 'Show the table of contents' }),
page.getByRole('button', { name: 'Show the table of contents sidebar' }),
).toBeHidden();

await page.keyboard.type('# Level 1\n## Level 2\n### Level 3');
const editor = await tryFocusEditorContent({ page });
await page.keyboard.type('# Level 1');
for (let i = 0; i < 20; i++) {
await page.keyboard.press('Enter');
}
await page.keyboard.type('## Level 2');
for (let i = 0; i < 20; i++) {
await page.keyboard.press('Enter');
}
await page.keyboard.type('### Level 3');

await page
.getByRole('button', { name: 'Show the table of contents sidebar' })
.click();

const summaryContainer = page.locator('#summaryContainer');
await summaryContainer.click();
const elSidePanel = page.getByLabel('Table of contents side panel');

const level1 = summaryContainer.getByText('Level 1');
const level2 = summaryContainer.getByText('Level 2');
const level3 = summaryContainer.getByText('Level 3');
const level1 = elSidePanel.getByText('Level 1');
const editorLevel1 = editor.getByText('Level 1');
const level2 = elSidePanel.getByText('Level 2');
const editorLevel2 = editor.getByText('Level 2');
const level3 = elSidePanel.getByText('Level 3');

await expect(level1).toBeVisible();
await expect(level1).toHaveCSS('padding', /4px 0px/);
await expect(level1).toHaveAttribute('aria-selected', 'true');
await expect(level1).toHaveCSS('padding', /0px 0px 0px 8px/);
await expect(editorLevel1).not.toBeInViewport();
await expect(level1).toHaveAttribute('aria-selected', 'false');

await expect(level2).toBeVisible();
await expect(level2).toHaveCSS('padding-left', /14.4px/);
await expect(level2).toHaveAttribute('aria-selected', 'false');
await expect(editorLevel2).toBeInViewport();
await expect(level2).toHaveAttribute('aria-selected', 'true');

await expect(level3).toBeVisible();
await expect(level3).toHaveCSS('padding-left', /24px/);
await expect(level3).toHaveAttribute('aria-selected', 'false');
Comment thread
AntoLC marked this conversation as resolved.

await level1.click();
await expect(editorLevel1).toBeInViewport();
await expect(level1).toHaveAttribute('aria-selected', 'true');
await expect(level2).toHaveAttribute('aria-selected', 'false');

await level2.click();
await expect(editorLevel1).not.toBeInViewport();
await expect(editorLevel2).toBeInViewport();
await expect(level2).toHaveAttribute('aria-selected', 'true');
await expect(level1).toHaveAttribute('aria-selected', 'false');
});
});
72 changes: 60 additions & 12 deletions src/frontend/apps/e2e/__tests__/app-impress/left-panel.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect, test } from '@playwright/test';

import { createDoc, goToGridDoc, verifyDocName } from './utils-common';
import { tryFocusEditorContent } from './utils-editor';
import { createRootSubPage } from './utils-sub-pages';

test.describe('Left panel desktop', () => {
Expand Down Expand Up @@ -84,16 +85,13 @@ test.describe('Left panel desktop', () => {
});
});

test.describe('Left panel mobile', () => {
test.use({ viewport: { width: 500, height: 1200 } });

test.beforeEach(async ({ page }) => {
await page.goto('/');
});

test('checks all the desktop elements are hidden and all mobile elements are visible', async ({
test.describe('Left panel responsive', () => {
test('checks elements visibility on different screen sizes', async ({
page,
}) => {
await page.setViewportSize({ width: 500, height: 1200 });
await page.goto('/');

await expect(page.getByTestId('left-panel-desktop')).toBeHidden();
await expect(page.getByTestId('left-panel-mobile')).not.toBeInViewport();

Expand All @@ -120,12 +118,27 @@ test.describe('Left panel mobile', () => {
await expect(newDocButton).toBeInViewport();
await expect(languageButton).toBeInViewport();
await expect(logoutButton).toBeInViewport();

await header.getByLabel('Close the header menu').click();

// Tablet size - like in desktop, left panel should be visible
await page.setViewportSize({ width: 900, height: 1200 });
await page.goto('/');

await expect(page.getByRole('link', { name: 'All docs' })).toBeInViewport();
await expect(newDocButton).toBeInViewport();
await expect(languageButton).toBeInViewport();
await expect(logoutButton).toBeInViewport();
await expect(header.getByLabel('Open the header menu')).toBeHidden();
});

test('checks panel closes when clicking on a subdoc', async ({
page,
browserName,
}) => {
await page.setViewportSize({ width: 500, height: 1200 });
await page.goto('/');

const [docTitle] = await createDoc(
page,
'mobile-doc-test',
Expand Down Expand Up @@ -163,11 +176,46 @@ test.describe('Left panel mobile', () => {
await expect(page.getByTestId('left-panel-mobile')).not.toBeInViewport();
});

test('checks resize handle is not present on mobile', async ({ page }) => {
test('checks panel coordination on tablet sizes', async ({
page,
browserName,
}) => {
await page.setViewportSize({ width: 900, height: 1200 });
await page.goto('/');

// Verify the resize handle is NOT present on mobile
const resizeHandle = page.locator('[data-panel-resize-handle-id]');
await expect(resizeHandle).toBeHidden();
await createDoc(page, 'tablet-doc-test', browserName, 1);

const leftPanel = page.locator('.--docs--resizable-left-panel');
const rightPanel = page.getByLabel('Table of contents side panel');

// Initially, left panel should be visible and right panel should be hidden
await expect(leftPanel).toBeInViewport();
await expect(rightPanel).not.toBeInViewport();
await tryFocusEditorContent({ page });
await page.keyboard.type('# Level 1');

// Open right panel, the left panel should hide
await page
.getByRole('button', { name: 'Show the table of contents sidebar' })
.click();
await expect(rightPanel).toBeInViewport();
await expect(leftPanel).toBeHidden();

// Open left panel, the right panel should hide
await page.getByRole('button', { name: /Show the side panel/ }).click();
await expect(leftPanel).toBeInViewport();
await expect(rightPanel).not.toBeInViewport();

// Close the left panel, the right panel should show
await page.getByRole('button', { name: /Hide the side panel/ }).click();
await expect(leftPanel).toBeHidden();
await expect(rightPanel).toBeInViewport();

// Close right panel, the left panel should stay closed
await page
.getByRole('button', { name: 'Hide the table of contents sidebar' })
.click();
await expect(rightPanel).not.toBeInViewport();
await expect(leftPanel).toBeHidden();
});
});
Loading
Loading