diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts index 66678310..0289f336 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts @@ -2,12 +2,11 @@ import path from 'path'; import { expect, test } from '@playwright/test'; import cs from 'convert-stream'; -import { pdf } from 'pdf-parse'; +import { PDFParse } from 'pdf-parse'; import { TestLanguage, createDoc, - randomName, verifyDocName, waitForLanguageSwitch, } from './utils-common'; @@ -86,11 +85,16 @@ test.describe('Doc Export', () => { expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`); const pdfBuffer = await cs.toBuffer(await download.createReadStream()); - const pdfData = await pdf(pdfBuffer); + const pdfParse = new PDFParse({ data: pdfBuffer }); + const pdfInfo = await pdfParse.getInfo(); + const pdfText = await pdfParse.getText(); - expect(pdfData.total).toBe(2); - expect(pdfData.text).toContain('Hello\n\nWorld\n\n'); // This is the doc text - expect(pdfData.info?.Title).toBe(randomDoc); + expect(pdfInfo.total).toBe(2); + expect(pdfText.pages).toStrictEqual([ + { text: 'Hello', num: 1 }, + { text: 'World', num: 2 }, + ]); + expect(pdfInfo?.info.Title).toBe(randomDoc); }); test('it exports the doc to docx', async ({ page, browserName }) => { @@ -219,10 +223,10 @@ test.describe('Doc Export', () => { expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`); const pdfBuffer = await cs.toBuffer(await download.createReadStream()); - const pdfExport = await pdf(pdfBuffer); - const pdfText = pdfExport.text; - expect(pdfText).toContain('Hello World'); + const pdfParse = new PDFParse({ data: pdfBuffer }); + const pdfText = await pdfParse.getText(); + expect(pdfText.text).toContain('Hello World'); }); test('it exports the doc with quotes', async ({ page, browserName }) => { @@ -265,9 +269,9 @@ test.describe('Doc Export', () => { expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`); const pdfBuffer = await cs.toBuffer(await download.createReadStream()); - const pdfData = await pdf(pdfBuffer); - - expect(pdfData.text).toContain('Hello World'); // This is the pdf text + const pdfParse = new PDFParse({ data: pdfBuffer }); + const pdfText = await pdfParse.getText(); + expect(pdfText.text).toContain('Hello World'); }); test('it exports the doc with multi columns', async ({ @@ -320,46 +324,33 @@ test.describe('Doc Export', () => { expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`); const pdfBuffer = await cs.toBuffer(await download.createReadStream()); - const pdfData = await pdf(pdfBuffer); - expect(pdfData.text).toContain('Column 1'); - expect(pdfData.text).toContain('Column 2'); - expect(pdfData.text).toContain('Column 3'); + const pdfParse = new PDFParse({ data: pdfBuffer }); + const pdfText = await pdfParse.getText(); + expect(pdfText.text).toContain('Column 1'); + expect(pdfText.text).toContain('Column 2'); + expect(pdfText.text).toContain('Column 3'); }); test('it injects the correct language attribute into PDF export', async ({ page, browserName, }) => { + const [randomDocFrench] = await createDoc( + page, + 'doc-language-export-french', + browserName, + 1, + ); + await waitForLanguageSwitch(page, TestLanguage.French); // Wait for the page to be ready after language switch await page.waitForLoadState('domcontentloaded'); - const header = page.locator('header').first(); - await header.locator('h1').getByText('Docs').click(); - - const randomDocFrench = randomName( - 'doc-language-export-french', - browserName, - 1, - )[0]; - - await page - .getByRole('button', { - name: 'Nouveau doc', - }) - .click(); - - const input = page.getByRole('textbox', { name: 'Titre du document' }); - await expect(input).toBeVisible(); - await expect(input).toHaveText('', { timeout: 10000 }); - await input.click(); - await input.fill(randomDocFrench); - await input.blur(); - - const editor = page.locator('.ProseMirror.bn-editor'); - await editor.click(); - await editor.fill('Contenu de test pour export en français'); + await writeInEditor({ + page, + text: 'Contenu de test pour export en français', + }); await page .getByRole('button', { @@ -447,8 +438,8 @@ test.describe('Doc Export', () => { expect(download.suggestedFilename()).toBe(`${docChild}.pdf`); const pdfBuffer = await cs.toBuffer(await download.createReadStream()); - const pdfData = await pdf(pdfBuffer); - - expect(pdfData.text).toContain(randomDoc); + const pdfParse = new PDFParse({ data: pdfBuffer }); + const pdfText = await pdfParse.getText(); + expect(pdfText.text).toContain(randomDoc); }); }); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts index 1e12421f..61535fff 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts @@ -1,6 +1,7 @@ import { expect, test } from '@playwright/test'; -import { createDoc, getGridRow } from './utils-common'; +import { createDoc, getGridRow, verifyDocName } from './utils-common'; +import { addNewMember, connectOtherUserToDoc } from './utils-share'; type SmallDoc = { id: string; @@ -11,7 +12,7 @@ test.describe('Documents Grid mobile', () => { test.use({ viewport: { width: 500, height: 1200 } }); test('it checks the grid when mobile', async ({ page }) => { - await page.route('**/documents/**', async (route) => { + await page.route(/.*\/documents\/.*/, async (route) => { const request = route.request(); if (request.method().includes('GET') && request.url().includes('page=')) { await route.fulfill({ @@ -161,7 +162,7 @@ test.describe('Document grid item options', () => { test("it checks if the delete option is disabled if we don't have the destroy capability", async ({ page, }) => { - await page.route('*/**/api/v1.0/documents/?page=1', async (route) => { + await page.route(/.*\/api\/v1.0\/documents\/\?page=1/, async (route) => { await route.fulfill({ json: { results: [ @@ -192,90 +193,68 @@ test.describe('Document grid item options', () => { }); await page.goto('/'); - const button = page.getByTestId( - `docs-grid-actions-button-mocked-document-id`, - ); + const button = page + .getByTestId(`docs-grid-actions-button-mocked-document-id`) + .first(); await expect(button).toBeVisible(); await button.click(); - const removeButton = page.getByTestId( - `docs-grid-actions-remove-mocked-document-id`, - ); + const removeButton = page + .getByTestId(`docs-grid-actions-remove-mocked-document-id`) + .first(); await expect(removeButton).toBeVisible(); await removeButton.isDisabled(); }); }); test.describe('Documents filters', () => { - test('it checks the prebuild left panel filters', async ({ page }) => { + test('it checks the left panel filters', async ({ page, browserName }) => { void page.goto('/'); + // Create my doc + const [docName] = await createDoc(page, 'my-doc', browserName, 1); + await verifyDocName(page, docName); + + // Another user create a doc and share it with me + const { cleanup, otherPage, otherBrowserName } = + await connectOtherUserToDoc({ + browserName, + docUrl: '/', + }); + + const [docShareName] = await createDoc( + otherPage, + 'my-share-doc', + otherBrowserName, + 1, + ); + + await verifyDocName(otherPage, docShareName); + + await otherPage.getByRole('button', { name: 'Share' }).click(); + + await addNewMember(otherPage, 0, 'Editor', browserName); + + // Let's check the filters + await page.getByRole('button', { name: 'Back to homepage' }).click(); + + const row = await getGridRow(page, docName); + const rowShare = await getGridRow(page, docShareName); + // All Docs - const response = await page.waitForResponse( - (response) => - response.url().endsWith('documents/?page=1') && - response.status() === 200, - ); - const result = await response.json(); - const allCount = result.count as number; - await expect(page.getByTestId('grid-loader')).toBeHidden(); + await expect(row).toBeVisible(); + await expect(rowShare).toBeVisible(); - const allDocs = page.getByLabel('All docs'); - const myDocs = page.getByLabel('My docs'); - const sharedWithMe = page.getByLabel('Shared with me'); - - // Initial state - await expect(allDocs).toBeVisible(); - await expect(allDocs).toHaveAttribute('aria-current', 'page'); - - await expect(myDocs).toBeVisible(); - await expect(myDocs).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); - await expect(myDocs).not.toHaveAttribute('aria-current'); - - await expect(sharedWithMe).toBeVisible(); - await expect(sharedWithMe).toHaveCSS( - 'background-color', - 'rgba(0, 0, 0, 0)', - ); - await expect(sharedWithMe).not.toHaveAttribute('aria-current'); - - await allDocs.click(); - - await page.waitForURL('**/?target=all_docs'); - - let url = new URL(page.url()); - let target = url.searchParams.get('target'); - expect(target).toBe('all_docs'); - - // My docs - await myDocs.click(); - url = new URL(page.url()); - target = url.searchParams.get('target'); - expect(target).toBe('my_docs'); - const responseMyDocs = await page.waitForResponse( - (response) => - response.url().endsWith('documents/?page=1&is_creator_me=true') && - response.status() === 200, - ); - const resultMyDocs = await responseMyDocs.json(); - const countMyDocs = resultMyDocs.count as number; - await expect(page.getByTestId('grid-loader')).toBeHidden(); - expect(countMyDocs).toBeLessThanOrEqual(allCount); + // My Docs + await page.getByRole('link', { name: 'My docs' }).click(); + await expect(row).toBeVisible(); + await expect(rowShare).toBeHidden(); // Shared with me - await sharedWithMe.click(); - url = new URL(page.url()); - target = url.searchParams.get('target'); - expect(target).toBe('shared_with_me'); - const responseSharedWithMe = await page.waitForResponse( - (response) => - response.url().includes('documents/?page=1&is_creator_me=false') && - response.status() === 200, - ); - const resultSharedWithMe = await responseSharedWithMe.json(); - const countSharedWithMe = resultSharedWithMe.count as number; - await expect(page.getByTestId('grid-loader')).toBeHidden(); - expect(countSharedWithMe).toBeLessThanOrEqual(allCount); - expect(countSharedWithMe + countMyDocs).toEqual(allCount); + await page.getByRole('link', { name: 'Shared with me' }).click(); + await expect(row).toBeHidden(); + await expect(rowShare).toBeVisible(); + + await cleanup(); }); }); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-member-list.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-member-list.spec.ts index 761b9a9d..9e0ee759 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-member-list.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-member-list.spec.ts @@ -31,7 +31,7 @@ test.describe('Document list members', () => { return cleanUrl.split('/').pop() || ''; })(); - await page.route('**/documents/**/accesses/', async (route) => { + await page.route(/.*\/documents\/.*\/accesses\//, async (route) => { const request = route.request(); const url = new URL(request.url()); const pageId = url.searchParams.get('page') ?? '1'; diff --git a/src/frontend/apps/e2e/__tests__/app-impress/types/pdf-parse.d.ts b/src/frontend/apps/e2e/__tests__/app-impress/types/pdf-parse.d.ts deleted file mode 100644 index dbf8c77c..00000000 --- a/src/frontend/apps/e2e/__tests__/app-impress/types/pdf-parse.d.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Type definitions for pdf-parse library - * The library doesn't export complete type definitions for the parsed PDF data - */ - -declare module 'pdf-parse' { - export interface PdfInfo { - Title?: string; - Author?: string; - Subject?: string; - Keywords?: string; - Creator?: string; - Producer?: string; - CreationDate?: string; - ModDate?: string; - [key: string]: unknown; - } - - export interface PdfData { - /** Total number of pages */ - numpages: number; - /** Alias for numpages */ - total?: number; - /** Extracted text content from the PDF */ - text: string; - /** PDF metadata information */ - info?: PdfInfo; - /** PDF metadata (alternative structure) */ - metadata?: unknown; - /** PDF version */ - version?: string; - } - - export function pdf(buffer: Buffer): Promise; - - export default pdf; -} diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts index 6377c07e..c8262367 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts @@ -31,7 +31,7 @@ export const overrideConfig = async ( page: Page, newConfig: { [_K in keyof typeof CONFIG]?: unknown }, ) => - await page.route('**/api/v1.0/config/', async (route) => { + await page.route(/.*\/api\/v1.0\/config\/.*/, async (route) => { const request = route.request(); if (request.method().includes('GET')) { await route.fulfill({ @@ -204,7 +204,7 @@ export const waitForResponseCreateDoc = (page: Page) => { }; export const mockedDocument = async (page: Page, data: object) => { - await page.route('**/documents/**/', async (route) => { + await page.route(/\**\/documents\/\**/, async (route) => { const request = route.request(); if ( request.method().includes('GET') && @@ -256,7 +256,7 @@ export const mockedDocument = async (page: Page, data: object) => { }; export const mockedListDocs = async (page: Page, data: object[] = []) => { - await page.route('**/documents/**/', async (route) => { + await page.route(/\**\/documents\/\**/, async (route) => { const request = route.request(); if (request.method().includes('GET') && request.url().includes('page=')) { await route.fulfill({ @@ -301,7 +301,7 @@ export async function waitForLanguageSwitch( page: Page, lang: TestLanguageValue, ) { - await page.route('**/api/v1.0/users/**', async (route, request) => { + await page.route(/\**\/api\/v1.0\/users\/\**/, async (route, request) => { if (request.method().includes('PATCH')) { await route.fulfill({ json: { diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts index 030d8251..d1305f2f 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts @@ -118,12 +118,16 @@ export const connectOtherUserToDoc = async ({ await otherPage.goto(docUrl); if (!withoutSignIn) { - await otherPage + const loginFromApp = otherPage .getByRole('main', { name: 'Main content' }) - .getByLabel('Login') - .click({ - timeout: 15000, - }); + .getByLabel('Login'); + const loginFromHome = otherPage.getByRole('button', { + name: 'Start Writing', + }); + + await loginFromApp.or(loginFromHome).first().click({ + timeout: 15000, + }); await keyCloakSignIn(otherPage, otherBrowserName, false); } @@ -159,7 +163,7 @@ export const mockedInvitations = async (page: Page, json?: object) => { ...json, }, ]; - await page.route('**/invitations/**/', async (route) => { + await page.route(/.*\/invitations\/.*/, async (route) => { const request = route.request(); if ( request.method().includes('GET') && @@ -180,7 +184,7 @@ export const mockedInvitations = async (page: Page, json?: object) => { }); await page.route( - '**/invitations/120ec765-43af-4602-83eb-7f4e1224548a/**/', + /.*\/invitations\/120ec765-43af-4602-83eb-7f4e1224548a\/.*/, async (route) => { const request = route.request(); if (request.method().includes('DELETE')) { @@ -195,7 +199,7 @@ export const mockedInvitations = async (page: Page, json?: object) => { }; export const mockedAccesses = async (page: Page, json?: object) => { - await page.route('**/accesses/**/', async (route) => { + await page.route(/.*\/accesses\/.*/, async (route) => { const request = route.request(); if ( diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/__tests__/utils.test.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/__tests__/utils.test.tsx index 37b911d8..4c545d50 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/__tests__/utils.test.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/__tests__/utils.test.tsx @@ -10,13 +10,16 @@ import { getEmojiAndTitle, } from '../utils'; -// Mock Y.js -vi.mock('yjs', () => ({ - Doc: vi.fn().mockImplementation(() => ({ - getXmlFragment: vi.fn().mockReturnValue('mocked-xml-fragment'), - })), - applyUpdate: vi.fn(), -})); +vi.mock('yjs', () => { + class MockDoc { + getXmlFragment = vi.fn().mockReturnValue('mocked-xml-fragment'); + } + + return { + Doc: MockDoc, + applyUpdate: vi.fn(), + }; +}); describe('doc-management utils', () => { beforeEach(() => { @@ -26,45 +29,30 @@ describe('doc-management utils', () => { describe('base64ToYDoc', () => { it('should convert base64 string to Y.Doc', () => { const base64String = 'dGVzdA=='; // "test" in base64 - const mockYDoc = { getXmlFragment: vi.fn() }; - - (Y.Doc as any).mockReturnValue(mockYDoc); const result = base64ToYDoc(base64String); - expect(Y.Doc).toHaveBeenCalled(); - expect(Y.applyUpdate).toHaveBeenCalledWith(mockYDoc, expect.any(Buffer)); - expect(result).toBe(mockYDoc); + expect(result).toBeInstanceOf(Y.Doc); + expect(Y.applyUpdate).toHaveBeenCalledWith(result, expect.any(Buffer)); }); it('should handle empty base64 string', () => { const base64String = ''; - const mockYDoc = { getXmlFragment: vi.fn() }; - - (Y.Doc as any).mockReturnValue(mockYDoc); const result = base64ToYDoc(base64String); - expect(Y.Doc).toHaveBeenCalled(); - expect(Y.applyUpdate).toHaveBeenCalledWith(mockYDoc, expect.any(Buffer)); - expect(result).toBe(mockYDoc); + expect(result).toBeInstanceOf(Y.Doc); + expect(Y.applyUpdate).toHaveBeenCalledWith(result, expect.any(Buffer)); }); }); describe('base64ToBlocknoteXmlFragment', () => { it('should convert base64 to Blocknote XML fragment', () => { const base64String = 'dGVzdA=='; - const mockYDoc = { - getXmlFragment: vi.fn().mockReturnValue('mocked-xml-fragment'), - }; - - (Y.Doc as any).mockReturnValue(mockYDoc); const result = base64ToBlocknoteXmlFragment(base64String); - expect(Y.Doc).toHaveBeenCalled(); - expect(Y.applyUpdate).toHaveBeenCalledWith(mockYDoc, expect.any(Buffer)); - expect(mockYDoc.getXmlFragment).toHaveBeenCalledWith('document-store'); + expect(Y.applyUpdate).toHaveBeenCalled(); expect(result).toBe('mocked-xml-fragment'); }); }); diff --git a/src/frontend/servers/y-provider/__tests__/hocuspocusWS.test.ts b/src/frontend/servers/y-provider/__tests__/hocuspocusWS.test.ts index 5642d2a2..eaaa785e 100644 --- a/src/frontend/servers/y-provider/__tests__/hocuspocusWS.test.ts +++ b/src/frontend/servers/y-provider/__tests__/hocuspocusWS.test.ts @@ -7,8 +7,8 @@ import { import { v1 as uuidv1, v4 as uuidv4 } from 'uuid'; import { afterAll, + afterEach, beforeAll, - beforeEach, describe, expect, test, @@ -45,7 +45,8 @@ import { hocuspocusServer, initApp } from '@/servers'; describe('Server Tests', () => { let server: Server; - beforeEach(() => { + afterEach(() => { + vi.clearAllMocks(); vi.restoreAllMocks(); }); @@ -203,11 +204,12 @@ describe('Server Tests', () => { test('WebSocket connection fails if user can not access document', () => { const { promise, done } = promiseDone(); + const room = uuidv4(); + const fetchDocumentMock = vi .spyOn(CollaborationBackend, 'fetchDocument') .mockRejectedValue(new Error('some error')); - const room = uuidv4(); const wsHocus = new HocuspocusProviderWebsocket({ url: `ws://localhost:${portWS}/?room=${room}`, WebSocketPolyfill: WebSocket,