From 9b03754f8809b5904fb36eb5a848dca086302eb9 Mon Sep 17 00:00:00 2001 From: Cyril Date: Mon, 1 Dec 2025 15:23:50 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=85(e2e)=20add=20test=20for=20accessible?= =?UTF-8?q?=20html=20export=20from=20export=20modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit checks generated zip contains html and embedded media files Signed-off-by: Cyril --- CHANGELOG.md | 1 + .../__tests__/app-impress/doc-export.spec.ts | 83 +++++++++++++++++- .../__tests__/app-impress/doc-header.spec.ts | 34 -------- .../doc-export/__tests__/ExportMIT.test.tsx | 4 +- .../doc-header/__tests__/DocToolBox.spec.tsx | 86 ------------------- .../__tests__/DocToolBoxLicence.spec.tsx | 7 +- 6 files changed, 89 insertions(+), 126 deletions(-) delete mode 100644 src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 36af2298..395eb6d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to - ♿(frontend) improve accessibility: - ♿(frontend) add skip to content button for keyboard accessibility #1624 +- ⚡️(frontend) Enhance/html copy to download #1669 ### Fixed 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 52af85ad..a54d9e2a 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,6 +2,7 @@ import path from 'path'; import { expect, test } from '@playwright/test'; import cs from 'convert-stream'; +import JSZip from 'jszip'; import { PDFParse } from 'pdf-parse'; import { @@ -31,7 +32,7 @@ test.describe('Doc Export', () => { await expect(page.getByTestId('modal-export-title')).toBeVisible(); await expect( - page.getByText('Download your document in a .docx, .odt or .pdf format.'), + page.getByText(/Download your document in a \.docx, \.odt.*format\./i), ).toBeVisible(); await expect( page.getByRole('combobox', { name: 'Template' }), @@ -187,6 +188,86 @@ test.describe('Doc Export', () => { expect(download.suggestedFilename()).toBe(`${randomDoc}.odt`); }); + test('it exports the doc to html zip', async ({ page, browserName }) => { + const [randomDoc] = await createDoc( + page, + 'doc-editor-html-zip', + browserName, + 1, + ); + + await verifyDocName(page, randomDoc); + + // Add some content and at least one image so that the ZIP contains media files. + await page.locator('.ProseMirror.bn-editor').click(); + await page.locator('.ProseMirror.bn-editor').fill('Hello HTML ZIP'); + + await page.keyboard.press('Enter'); + await page.locator('.bn-block-outer').last().fill('/'); + await page.getByText('Resizable image with caption').click(); + + const fileChooserPromise = page.waitForEvent('filechooser'); + await page.getByText('Upload image').click(); + + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles(path.join(__dirname, 'assets/test.svg')); + + const image = page + .locator('.--docs--editor-container img.bn-visual-media') + .first(); + + // Wait for the image to be attached and have a valid src (aria-hidden prevents toBeVisible on Chromium) + await expect(image).toBeAttached({ timeout: 10000 }); + await expect(image).toHaveAttribute('src', /.*\.svg/); + + // Give some time for the image to be fully processed + await page.waitForTimeout(1000); + + await page + .getByRole('button', { + name: 'Export the document', + }) + .click(); + + await page.getByRole('combobox', { name: 'Format' }).click(); + await page.getByRole('option', { name: 'HTML' }).click(); + + await expect(page.getByTestId('doc-export-download-button')).toBeVisible(); + + const downloadPromise = page.waitForEvent('download', (download) => { + return download.suggestedFilename().includes(`${randomDoc}.zip`); + }); + + void page.getByTestId('doc-export-download-button').click(); + + const download = await downloadPromise; + expect(download.suggestedFilename()).toBe(`${randomDoc}.zip`); + + const zipBuffer = await cs.toBuffer(await download.createReadStream()); + // Unzip and inspect contents + const zip = await JSZip.loadAsync(zipBuffer); + + // Check that index.html exists + const indexHtml = zip.file('index.html'); + expect(indexHtml).not.toBeNull(); + + // Read and verify HTML content + const htmlContent = await indexHtml!.async('string'); + expect(htmlContent).toContain('Hello HTML ZIP'); + + // Check for media files (they are at the root of the ZIP, not in a media/ folder) + // Media files are named like "1-test.svg" or "media-1.png" by deriveMediaFilename + const allFiles = Object.keys(zip.files); + const mediaFiles = allFiles.filter( + (name) => name !== 'index.html' && !name.endsWith('/'), + ); + expect(mediaFiles.length).toBeGreaterThan(0); + + // Verify the SVG image is included + const svgFile = mediaFiles.find((name) => name.endsWith('.svg')); + expect(svgFile).toBeDefined(); + }); + /** * This test tell us that the export to pdf is working with images * but it does not tell us if the images are being displayed correctly diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts index 6a4e01b1..34f0b4c4 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts @@ -408,40 +408,6 @@ test.describe('Doc Header', () => { expect(clipboardContent.trim()).toBe('# Hello World'); }); - test('It checks the copy as HTML button', async ({ page, browserName }) => { - test.skip( - browserName === 'webkit', - 'navigator.clipboard is not working with webkit and playwright', - ); - - // create page and navigate to it - await page - .getByRole('button', { - name: 'New doc', - }) - .click(); - - // Add dummy content to the doc - const editor = page.locator('.ProseMirror'); - const docFirstBlock = editor.locator('.bn-block-content').first(); - await docFirstBlock.click(); - await page.keyboard.type('# Hello World', { delay: 100 }); - const docFirstBlockContent = docFirstBlock.locator('h1'); - await expect(docFirstBlockContent).toHaveText('Hello World'); - - // Copy content to clipboard - await page.getByLabel('Open the document options').click(); - await page.getByRole('menuitem', { name: 'Copy as HTML' }).click(); - await expect(page.getByText('Copied to clipboard')).toBeVisible(); - - // Test that clipboard is in HTML format - const handle = await page.evaluateHandle(() => - navigator.clipboard.readText(), - ); - const clipboardContent = await handle.jsonValue(); - expect(clipboardContent.trim()).toBe(`

Hello World

`); - }); - test('it checks the copy link button', async ({ page, browserName }) => { test.skip( browserName === 'webkit', diff --git a/src/frontend/apps/impress/src/features/docs/doc-export/__tests__/ExportMIT.test.tsx b/src/frontend/apps/impress/src/features/docs/doc-export/__tests__/ExportMIT.test.tsx index 8c3efaa0..9ae3e731 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-export/__tests__/ExportMIT.test.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-export/__tests__/ExportMIT.test.tsx @@ -16,12 +16,12 @@ describe('useModuleExport', () => { const Export = await import('@/features/docs/doc-export/'); expect(Export.default).toBeUndefined(); - }, 10000); + }, 15000); it('should load modules when NEXT_PUBLIC_PUBLISH_AS_MIT is false', async () => { process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'false'; const Export = await import('@/features/docs/doc-export/'); expect(Export.default).toHaveProperty('ModalExport'); - }); + }, 15000); }); diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx deleted file mode 100644 index f6245ab5..00000000 --- a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBox.spec.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import React, { Fragment } from 'react'; -import { beforeEach, describe, expect, vi } from 'vitest'; - -import { AbstractAnalytic, Analytics } from '@/libs'; -import { AppWrapper } from '@/tests/utils'; - -import { DocToolBox } from '../components/DocToolBox'; - -let flag = true; -class TestAnalytic extends AbstractAnalytic { - public constructor() { - super(); - } - - public Provider() { - return ; - } - - public trackEvent() {} - - public isFeatureFlagActivated(flagName: string): boolean { - if (flagName === 'CopyAsHTML') { - return flag; - } - - return true; - } -} - -vi.mock('next/router', async () => ({ - ...(await vi.importActual('next/router')), - useRouter: () => ({ - push: vi.fn(), - }), -})); - -const doc = { - nb_accesses: 1, - abilities: { - versions_list: true, - destroy: true, - }, -}; - -beforeEach(() => { - Analytics.clearAnalytics(); - process.env.NEXT_PUBLIC_PUBLISH_AS_MIT = 'false'; -}); - -describe('DocToolBox "Copy as HTML" option', () => { - test('renders "Copy as HTML" option when feature flag is enabled', async () => { - new TestAnalytic(); - - render(, { - wrapper: AppWrapper, - }); - const optionsButton = await screen.findByLabelText( - 'Open the document options', - ); - await userEvent.click(optionsButton); - expect(await screen.findByText('Copy as HTML')).toBeInTheDocument(); - }); - - test('does not render "Copy as HTML" option when feature flag is disabled', async () => { - flag = false; - new TestAnalytic(); - - render(, { - wrapper: AppWrapper, - }); - const optionsButton = screen.getByLabelText('Open the document options'); - await userEvent.click(optionsButton); - expect(screen.queryByText('Copy as HTML')).not.toBeInTheDocument(); - }); - - test('render "Copy as HTML" option when we did not add analytics', async () => { - render(, { - wrapper: AppWrapper, - }); - const optionsButton = screen.getByLabelText('Open the document options'); - await userEvent.click(optionsButton); - expect(screen.getByText('Copy as HTML')).toBeInTheDocument(); - }); -}); diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxLicence.spec.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxLicence.spec.tsx index 9b1b2996..54029d7f 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxLicence.spec.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/__tests__/DocToolBoxLicence.spec.tsx @@ -42,10 +42,11 @@ describe('DocToolBox - Licence', () => { }); const optionsButton = await screen.findByLabelText('Export the document'); await userEvent.click(optionsButton); + + // Wait for the export modal to be visible, then assert on its content text. + await screen.findByTestId('modal-export-title'); expect( - await screen.findByText( - 'Download your document in a .docx, .odt or .pdf format.', - ), + screen.getByText(/Download your document in a .docx, .odt.*format\./i), ).toBeInTheDocument(); }, 10000);