From e55e81dcb02af5440c95fee61511556b1845ac3a Mon Sep 17 00:00:00 2001 From: zzxming Date: Sun, 15 Dec 2024 21:54:13 +0800 Subject: [PATCH] test: playwright --- .github/workflows/ci.yml | 10 +- .gitignore | 7 +- docs/test.html | 60 ++++++++++ docs/test.js | 127 ++++++++++++++++++++ package.json | 9 +- playwright.config.ts | 56 +++++++++ pnpm-lock.yaml | 38 ++++++ server.js | 39 ++++++ src/__tests__/e2e/custom-creator.test.ts | 44 +++++++ src/__tests__/e2e/table-align.test.ts | 39 ++++++ src/__tests__/e2e/table-resize.test.ts | 138 ++++++++++++++++++++++ src/__tests__/e2e/table-selection.test.ts | 83 +++++++++++++ src/__tests__/e2e/utils.ts | 6 + 13 files changed, 650 insertions(+), 6 deletions(-) create mode 100644 docs/test.html create mode 100644 docs/test.js create mode 100644 playwright.config.ts create mode 100644 server.js create mode 100644 src/__tests__/e2e/custom-creator.test.ts create mode 100644 src/__tests__/e2e/table-align.test.ts create mode 100644 src/__tests__/e2e/table-resize.test.ts create mode 100644 src/__tests__/e2e/table-selection.test.ts create mode 100644 src/__tests__/e2e/utils.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e05c19..22f39bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,8 +28,14 @@ jobs: - name: Install run: pnpm i + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Build run: pnpm build - - name: Typecheck - run: pnpm test + - name: Unit Tests + run: pnpm test:unit + + - name: Run Playwright tests + run: npx playwright test diff --git a/.gitignore b/.gitignore index 9132915..93c5f98 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,9 @@ coverage docs/*.css docs/*umd.js -docs/*umd.js.map \ No newline at end of file +docs/*umd.js.map + +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/docs/test.html b/docs/test.html new file mode 100644 index 0000000..ddf1a86 --- /dev/null +++ b/docs/test.html @@ -0,0 +1,60 @@ + + + + + Quill Table Module Demo + + + + + + + + + + + + + +
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ +
+
+
+
+ + + diff --git a/docs/test.js b/docs/test.js new file mode 100644 index 0000000..8470bb1 --- /dev/null +++ b/docs/test.js @@ -0,0 +1,127 @@ +/* eslint-disable no-undef */ +const Quill = window.Quill; +const { + default: TableUp, + TableAlign, + TableVirtualScrollbar, + TableResizeLine, + TableResizeBox, + TableMenuContextmenu, + TableMenuSelect, + TableResizeScale, + defaultCustomSelect, + TableSelection, +} = window.TableUp; + +Quill.register({ + [`modules/${TableUp.moduleName}`]: TableUp, +}, true); + +const toolbarConfig = [ + ['bold', 'italic', 'underline', 'strike'], + ['blockquote', 'code-block', 'code'], + ['link', 'image', 'video', 'formula'], + [{ list: 'ordered' }, { list: 'bullet' }, { list: 'check' }], + [{ script: 'sub' }, { script: 'super' }], + [{ indent: '-1' }, { indent: '+1' }], + [{ direction: 'rtl' }], + [{ size: ['small', false, 'large', 'huge'] }], + [{ header: [1, 2, 3, 4, 5, 6, false] }], + [{ color: [] }, { background: [] }], + [{ font: [] }], + [{ align: [] }], + [{ [TableUp.toolName]: [] }], + ['clean'], +]; + +const quills = [ + { + full: false, + scrollbar: TableVirtualScrollbar, + align: TableAlign, + resize: TableResizeLine, + resizeScale: TableResizeScale, + customSelect: defaultCustomSelect, + customBtn: true, + selection: TableSelection, + selectionOptions: { + tableMenu: TableMenuContextmenu, + }, + }, + { + full: false, + scrollbar: TableVirtualScrollbar, + align: TableAlign, + resize: TableResizeBox, + resizeScale: TableResizeScale, + customSelect: defaultCustomSelect, + customBtn: true, + selection: TableSelection, + selectionOptions: { + tableMenu: TableMenuSelect, + }, + }, + { + full: true, + scrollbar: TableVirtualScrollbar, + align: TableAlign, + resize: TableResizeLine, + resizeScale: TableResizeScale, + customSelect: defaultCustomSelect, + customBtn: true, + selection: TableSelection, + selectionOptions: { + tableMenu: TableMenuContextmenu, + }, + }, + { + full: true, + scrollbar: TableVirtualScrollbar, + align: TableAlign, + resize: TableResizeBox, + resizeScale: TableResizeScale, + customSelect: defaultCustomSelect, + customBtn: true, + selection: TableSelection, + selectionOptions: { + tableMenu: TableMenuSelect, + }, + }, +].map((ops, i) => { + return new Quill(`#editor${i + 1}`, { + // debug: 'info', + theme: 'snow', + modules: { + toolbar: toolbarConfig, + [TableUp.moduleName]: ops, + }, + }); +}); + +window.quills = quills; + +const output = [ + output1, + output2, + output3, + output4, +]; + +for (const [i, btn] of [ + btn1, + btn2, + btn3, + btn4, +].entries()) { + btn.addEventListener('click', () => { + const content = quills[i].getContents(); + console.log(content); + output[i].innerHTML = ''; + // eslint-disable-next-line unicorn/no-array-for-each + content.forEach((content) => { + const item = document.createElement('li'); + item.textContent = `${JSON.stringify(content)},`; + output[i].appendChild(item); + }); + }); +} diff --git a/package.json b/package.json index a04ac8c..7e388ae 100644 --- a/package.json +++ b/package.json @@ -33,9 +33,11 @@ "lint:fix": "eslint . --fix", "build": "gulp --require @esbuild-kit/cjs-loader", "dev": "gulp --require @esbuild-kit/cjs-loader dev", - "test": "vitest", - "test:ui": "vitest --ui", - "test:cover": "vitest --coverage" + "server": "node ./server.js", + "test:unit": "vitest", + "test:unit-ui": "vitest --ui", + "test:e2e": "playwright test", + "test:e2e-ui": "playwright test --ui" }, "peerDependencies": { "quill": "^2.0.0" @@ -45,6 +47,7 @@ }, "devDependencies": { "@esbuild-kit/cjs-loader": "^2.4.4", + "@playwright/test": "^1.49.1", "@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-typescript": "^11.1.6", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..5abb602 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,56 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './src/__tests__/e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + trace: 'on-first-retry', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + webServer: { + command: 'npm run server', + port: 5500, + reuseExistingServer: !process.env.CI, + stdout: 'ignore', + stderr: 'pipe', + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 18b9f84..79db937 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,6 +18,9 @@ importers: '@esbuild-kit/cjs-loader': specifier: ^2.4.4 version: 2.4.4 + '@playwright/test': + specifier: ^1.49.1 + version: 1.49.1 '@rollup/plugin-node-resolve': specifier: ^15.3.0 version: 15.3.0(rollup@4.18.1) @@ -682,6 +685,11 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@playwright/test@1.49.1': + resolution: {integrity: sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==} + engines: {node: '>=18'} + hasBin: true + '@polka/url@1.0.0-next.25': resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} @@ -1963,6 +1971,11 @@ packages: os: [darwin] deprecated: Upgrade to fsevents v2 to mitigate potential security issues + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -2875,6 +2888,16 @@ packages: resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} engines: {node: '>=0.10.0'} + playwright-core@1.49.1: + resolution: {integrity: sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.49.1: + resolution: {integrity: sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==} + engines: {node: '>=18'} + hasBin: true + plugin-error@1.0.1: resolution: {integrity: sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==} engines: {node: '>= 0.10'} @@ -4123,6 +4146,10 @@ snapshots: '@pkgr/core@0.1.1': {} + '@playwright/test@1.49.1': + dependencies: + playwright: 1.49.1 + '@polka/url@1.0.0-next.25': {} '@rollup/plugin-node-resolve@15.3.0(rollup@4.18.1)': @@ -5641,6 +5668,9 @@ snapshots: nan: 2.20.0 optional: true + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -6608,6 +6638,14 @@ snapshots: pinkie@2.0.4: {} + playwright-core@1.49.1: {} + + playwright@1.49.1: + dependencies: + playwright-core: 1.49.1 + optionalDependencies: + fsevents: 2.3.2 + plugin-error@1.0.1: dependencies: ansi-colors: 1.1.0 diff --git a/server.js b/server.js new file mode 100644 index 0000000..7d8b4d0 --- /dev/null +++ b/server.js @@ -0,0 +1,39 @@ +import fs from 'node:fs'; +import http from 'node:http'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const hostname = '127.0.0.1'; +const PORT = 5500; +const server = http.createServer((req, res) => { + const filePath = path.join(__dirname, req.url === '/' ? 'index.html' : req.url); + const extname = String(path.extname(filePath)).toLowerCase(); + const mimeTypes = { + '.html': 'text/html', + '.js': 'text/javascript', + '.css': 'text/css', + }; + const contentType = mimeTypes[extname] || 'application/octet-stream'; + fs.readFile(filePath, (err, content) => { + if (err) { + if (err.code === 'ENOENT') { + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('404 Not Found'); + } + else { + res.writeHead(500); + res.end(`Server Error: ${err.code}`); + } + } + else { + res.writeHead(200, { 'Content-Type': contentType }); + res.end(content, 'utf8'); + } + }); +}); + +server.listen(PORT, hostname, () => { + console.log(`Server is running on http://localhost:${PORT}`); +}); diff --git a/src/__tests__/e2e/custom-creator.test.ts b/src/__tests__/e2e/custom-creator.test.ts new file mode 100644 index 0000000..a956929 --- /dev/null +++ b/src/__tests__/e2e/custom-creator.test.ts @@ -0,0 +1,44 @@ +import { expect, test } from '@playwright/test'; +import { createTableBySelect } from './utils'; + +test.beforeEach(async ({ page }) => { + await page.goto('http://127.0.0.1:5500/docs/test.html'); + page.locator('#editor1.ql-container.ql-snow'); +}); + +test('custom selecor should work', async ({ page }) => { + await createTableBySelect(page, 'container1', 3, 3); + + const isVisible = await page.locator('#editor1.ql-container .ql-table-wrapper').isVisible(); + expect(isVisible).toBe(true); + const colCount = await page.locator('#editor1.ql-container .ql-table-wrapper col').count(); + expect(colCount).toBe(3); + const rowCount = await page.locator('#editor1.ql-container .ql-table-wrapper tr').count(); + expect(rowCount).toBe(3); + const cellCount = await page.locator('#editor1.ql-container .ql-table-wrapper td').count(); + expect(cellCount).toBe(9); +}); + +test('custom button should work', async ({ page }) => { + await page.locator('#container1 .ql-toolbar .ql-table-up > .ql-picker-label').first().click(); + await page.locator('#container1 .ql-toolbar .ql-table-up .ql-custom-select').getByText('Custom').click(); + + await page.locator('.table-up-dialog .table-up-button.confirm').click(); + const rowInput = page.locator('.table-up-dialog .table-up-input__input').first(); + expect(rowInput).toHaveClass(/error/); + const errorText = await page.locator('.table-up-dialog .table-up-input__input').first().locator('.table-up-input__error-tip').textContent(); + expect(errorText).toBe('Please enter a positive integer'); + + await page.locator('.table-up-input__item').nth(0).locator('input').fill('3'); + await page.locator('.table-up-input__item').nth(1).locator('input').fill('3'); + await page.getByRole('button', { name: 'Confirm' }).click(); + + const isVisible = await page.locator('#editor1.ql-container .ql-table-wrapper').isVisible(); + expect(isVisible).toBe(true); + const colCount = await page.locator('#editor1.ql-container .ql-table-wrapper col').count(); + expect(colCount).toBe(3); + const rowCount = await page.locator('#editor1.ql-container .ql-table-wrapper tr').count(); + expect(rowCount).toBe(3); + const cellCount = await page.locator('#editor1.ql-container .ql-table-wrapper td').count(); + expect(cellCount).toBe(9); +}); diff --git a/src/__tests__/e2e/table-align.test.ts b/src/__tests__/e2e/table-align.test.ts new file mode 100644 index 0000000..260b3b4 --- /dev/null +++ b/src/__tests__/e2e/table-align.test.ts @@ -0,0 +1,39 @@ +import { expect, test } from '@playwright/test'; +import { createTableBySelect } from './utils'; + +test.beforeEach(async ({ page }) => { + await page.goto('http://127.0.0.1:5500/docs/test.html'); + page.locator('.ql-container.ql-snow'); +}); + +test('test TableAlign', async ({ page }) => { + await createTableBySelect(page, 'container1', 3, 3); + const centerCell = page.locator('#editor1').getByRole('cell').nth(4); + await centerCell.click(); + const cellBounding = (await centerCell.boundingBox())!; + expect(cellBounding).not.toBeNull(); + await page.mouse.move(cellBounding.x, cellBounding.y); + const colBoundingBox = (await page.locator('#editor1 .table-up-resize-line__col').boundingBox())!; + expect(colBoundingBox).not.toBeNull(); + await page.mouse.move(colBoundingBox.x + colBoundingBox.width / 2, colBoundingBox.y + colBoundingBox.height / 2); + await page.mouse.down(); + await page.mouse.move(colBoundingBox.x + colBoundingBox.width / 2 - 200, colBoundingBox.y + colBoundingBox.height / 2); + await page.mouse.up(); + await centerCell.click(); + + const isVisible = await page.locator('#editor1 .table-up-align.table-up-align--active').isVisible(); + expect(isVisible).toBeTruthy(); + + const table = page.locator('#editor1 .ql-editor .ql-table'); + await page.locator('#editor1 .table-up-align .table-up-align__item[data-align="center"]').click(); + await expect(table).toHaveCSS('margin-left', `100px`); + await expect(table).toHaveCSS('margin-right', `100px`); + + await page.locator('#editor1 .table-up-align .table-up-align__item[data-align="right"]').click(); + await expect(table).toHaveCSS('margin-left', `200px`); + await expect(table).toHaveCSS('margin-right', '0px'); + + await page.locator('#editor1 .table-up-align .table-up-align__item[data-align="left"]').click(); + await expect(table).toHaveCSS('margin-left', '0px'); + await expect(table).toHaveCSS('margin-right', '200px'); +}); diff --git a/src/__tests__/e2e/table-resize.test.ts b/src/__tests__/e2e/table-resize.test.ts new file mode 100644 index 0000000..36fe710 --- /dev/null +++ b/src/__tests__/e2e/table-resize.test.ts @@ -0,0 +1,138 @@ +import { expect, test } from '@playwright/test'; +import { createTableBySelect } from './utils'; + +test.beforeEach(async ({ page }) => { + await page.goto('http://127.0.0.1:5500/docs/test.html'); + page.locator('.ql-container.ql-snow'); +}); + +test('test TableResizeLine fixed width', async ({ page }) => { + await createTableBySelect(page, 'container1', 3, 3); + const centerCell = page.locator('#editor1').getByRole('cell').nth(4); + await centerCell.click(); + const cellBounding = (await centerCell.boundingBox())!; + expect(cellBounding).not.toBeNull(); + + // trigger mousemove in table to set resize line position + await page.mouse.move(cellBounding.x, cellBounding.y); + // col + const colBoundingBox = (await page.locator('#editor1 .table-up-resize-line__col').boundingBox())!; + expect(colBoundingBox).not.toBeNull(); + await page.mouse.move(colBoundingBox.x + colBoundingBox.width / 2, colBoundingBox.y + colBoundingBox.height / 2); + await page.mouse.down(); + await page.mouse.move(colBoundingBox.x + colBoundingBox.width / 2 + 100, colBoundingBox.y + colBoundingBox.height / 2); + await page.mouse.up(); + expect(page.locator('#editor1 .ql-table-wrapper col').nth(1)).toHaveAttribute('width', `${Math.floor(cellBounding.width) + 100}px`); + + await page.mouse.move(cellBounding.x, cellBounding.y); + // row + const rowBoundingBox = (await page.locator('#editor1 .table-up-resize-line__row').boundingBox())!; + expect(rowBoundingBox).not.toBeNull(); + await page.mouse.move(rowBoundingBox.x + rowBoundingBox.width / 2, rowBoundingBox.y + rowBoundingBox.height / 2); + await page.mouse.down(); + await page.mouse.move(rowBoundingBox.x + rowBoundingBox.width / 2, rowBoundingBox.y + rowBoundingBox.height / 2 + 100); + await page.mouse.up(); + const cells = await page.locator('#editor1 .ql-table-wrapper tr').nth(1).locator('td').all(); + + for (const cell of cells) { + await expect(cell).toHaveCSS('height', `${Math.floor(cellBounding.height) + 100}px`); + } +}); + +test('test TableResizeBox fixed width', async ({ page }) => { + await createTableBySelect(page, 'container2', 3, 3); + const centerCell = page.locator('#editor2').getByRole('cell').nth(4); + await centerCell.click(); + const cellBounding = (await centerCell.boundingBox())!; + expect(cellBounding).not.toBeNull(); + + // col + const colBoundingBox = (await page.locator('#editor2 .table-up-resize-box__col-separator').nth(1).boundingBox())!; + expect(colBoundingBox).not.toBeNull(); + // -4 for sure click on the separator + await page.mouse.move(colBoundingBox.x + colBoundingBox.width - 4, colBoundingBox.y + 4); + await page.mouse.down(); + await page.mouse.move(colBoundingBox.x + colBoundingBox.width - 4 + 100, colBoundingBox.y + 4); + await page.mouse.up(); + expect(page.locator('#editor2 .ql-table-wrapper col').nth(1)).toHaveAttribute('width', `${Math.floor(cellBounding.width - 4) + 100}px`); + + // row + const rowBoundingBox = (await page.locator('#editor2 .table-up-resize-box__row-separator').nth(1).boundingBox())!; + expect(rowBoundingBox).not.toBeNull(); + // -4 for sure click on the separator + await page.mouse.move(rowBoundingBox.x, rowBoundingBox.y + rowBoundingBox.height - 4); + await page.mouse.down(); + await page.mouse.move(rowBoundingBox.x, rowBoundingBox.y + rowBoundingBox.height - 4 + 100); + await page.mouse.up(); + const cells = await page.locator('#editor2 .ql-table-wrapper tr').nth(1).locator('td').all(); + expect(cells.length).toEqual(3); + for (const cell of cells) { + await expect(cell).toHaveCSS('height', `${Math.floor(cellBounding.height - 4) + 100}px`); + } +}); + +test('test TableResizeLine full width', async ({ page }) => { + await createTableBySelect(page, 'container3', 4, 4); + const centerCell = page.locator('#editor3').getByRole('cell').nth(1); + await centerCell.click(); + const cellBounding = (await centerCell.boundingBox())!; + const tableBounding = (await page.locator('#editor3 .ql-table').boundingBox())!; + expect(cellBounding).not.toBeNull(); + expect(tableBounding).not.toBeNull(); + + // trigger mousemove in table to set resize line position + await page.mouse.move(cellBounding.x, cellBounding.y); + const colBoundingBox = (await page.locator('#editor3 .table-up-resize-line__col').boundingBox())!; + expect(colBoundingBox).not.toBeNull(); + await page.mouse.move(colBoundingBox.x + colBoundingBox.width / 2, colBoundingBox.y + colBoundingBox.height / 2); + await page.mouse.down(); + await page.mouse.move(colBoundingBox.x + colBoundingBox.width / 2 + tableBounding.width * 0.05, colBoundingBox.y + colBoundingBox.height / 2); + await page.mouse.up(); + const cols = page.locator('#editor3 .ql-table-wrapper col'); + await expect(cols.nth(1)).toHaveAttribute('width', '30%'); + await expect(cols.nth(2)).toHaveAttribute('width', '20%'); +}); + +test('test TableResizeBox full width', async ({ page }) => { + await createTableBySelect(page, 'container4', 4, 4); + const centerCell = page.locator('#editor4').getByRole('cell').nth(1); + await centerCell.click(); + const cellBounding = (await centerCell.boundingBox())!; + const tableBounding = (await page.locator('#editor4 .ql-table').boundingBox())!; + expect(cellBounding).not.toBeNull(); + expect(tableBounding).not.toBeNull(); + + const colBoundingBox = (await page.locator('#editor4 .table-up-resize-box__col-separator').nth(1).boundingBox())!; + expect(colBoundingBox).not.toBeNull(); + // -4 for sure click on the separator + await page.mouse.move(colBoundingBox.x + colBoundingBox.width - 4, colBoundingBox.y); + await page.mouse.down(); + await page.mouse.move(colBoundingBox.x + colBoundingBox.width - 4 + tableBounding.width * 0.05, colBoundingBox.y); + await page.mouse.up(); + const cols = page.locator('#editor4 .ql-table-wrapper col'); + await expect(cols.nth(1)).toHaveAttribute('width', '30%'); + await expect(cols.nth(2)).toHaveAttribute('width', '20%'); +}); + +test('test TableResizeScale', async ({ page }) => { + await createTableBySelect(page, 'container1', 3, 3); + const centerCell = page.locator('#editor1').getByRole('cell').nth(4); + await centerCell.click(); + const cellBounding = (await centerCell.boundingBox())!; + const scaleBtnBounding = (await page.locator('#editor1 .table-up-scale__block').boundingBox())!; + expect(cellBounding).not.toBeNull(); + expect(scaleBtnBounding).not.toBeNull(); + await page.mouse.move(scaleBtnBounding.x + scaleBtnBounding.width / 2, scaleBtnBounding.y + scaleBtnBounding.height / 2); + await page.mouse.down(); + await page.mouse.move(scaleBtnBounding.x + scaleBtnBounding.width / 2 + 90, scaleBtnBounding.y + scaleBtnBounding.height / 2 + 90); + await page.mouse.up(); + const cols = page.locator('#editor1 .ql-table-wrapper col'); + for (const col of await cols.all()) { + await expect(col).toHaveAttribute('width', `${Math.floor(cellBounding.width + 30)}px`); + } + + const cells = page.locator('#editor1 .ql-table-wrapper td'); + for (const cell of await cells.all()) { + await expect(cell).toHaveCSS('height', `${Math.floor(cellBounding.height + 30)}px`); + } +}); diff --git a/src/__tests__/e2e/table-selection.test.ts b/src/__tests__/e2e/table-selection.test.ts new file mode 100644 index 0000000..80fb6e9 --- /dev/null +++ b/src/__tests__/e2e/table-selection.test.ts @@ -0,0 +1,83 @@ +import { expect, test } from '@playwright/test'; +import { createTableBySelect } from './utils'; + +test.beforeEach(async ({ page }) => { + await page.goto('http://127.0.0.1:5500/docs/test.html'); + page.locator('.ql-container.ql-snow'); +}); + +test('test TableSelection horizontal', async ({ page }) => { + await createTableBySelect(page, 'container1', 5, 5); + const cell = page.locator('#editor1 .ql-editor .ql-table td').nth(0); + await cell.click(); + const cellBounding = (await cell.boundingBox())!; + expect(cellBounding).not.toBeNull(); + + await cell.click(); + const selectionLine = page.locator('#editor1 .table-up-selection__line'); + await expect(selectionLine).toBeVisible(); + await page.mouse.down(); + await page.mouse.move(cellBounding.x + cellBounding.width * 3, cellBounding.y + cellBounding.height / 2); + await page.mouse.up(); + + await expect(selectionLine).toBeVisible(); + expect( + Number.parseFloat(await selectionLine.evaluate(el => getComputedStyle(el).width)), + ).toBeCloseTo(cellBounding.width * 3, -1); + expect( + Number.parseFloat(await selectionLine.evaluate(el => getComputedStyle(el).height)), + ).toBeCloseTo(cellBounding.height, -1); + + await page.locator('#editor1 .ql-editor .ql-table').click({ button: 'right' }); + await page.locator('.table-up-menu.is-contextmenu .table-up-menu__item').filter({ hasText: 'Merge Cell' }).first().click(); + + await page.locator('#editor1 .ql-editor .ql-table td').nth(3).click(); + await page.locator('#editor1 .ql-editor .ql-table td').nth(3).click(); + await expect(selectionLine).toBeVisible(); + await page.mouse.down(); + await page.mouse.move(cellBounding.x, cellBounding.y); + await page.mouse.up(); + + const mergeCellBounding = (await page.locator('#editor1 .ql-editor .ql-table td').nth(0).boundingBox())!; + expect(mergeCellBounding).not.toBeNull(); + expect( + Number.parseFloat(await selectionLine.evaluate(el => getComputedStyle(el).width)), + ).toBeCloseTo(mergeCellBounding.width, -1); + expect( + Number.parseFloat(await selectionLine.evaluate(el => getComputedStyle(el).height)), + ).toBeCloseTo(mergeCellBounding.height + cellBounding.height, -1); +}); + +test('test TableSelection vertical', async ({ page }) => { + await createTableBySelect(page, 'container1', 5, 5); + const cell = page.locator('#editor1 .ql-editor .ql-table td').nth(0); + const cellBounding = (await cell.boundingBox())!; + expect(cellBounding).not.toBeNull(); + await cell.click(); + await cell.click(); + const selectionLine = page.locator('#editor1 .table-up-selection__line'); + await expect(selectionLine).toBeVisible(); + + await page.locator('#editor1 .ql-editor .ql-table td').nth(0).click(); + await page.locator('#editor1 .ql-editor .ql-table td').nth(0).click(); + await expect(selectionLine).toBeVisible(); + await page.mouse.down(); + await page.mouse.move(cellBounding.x, cellBounding.y + cellBounding.height * 3); + await page.mouse.up(); + await page.locator('#editor1 .ql-editor .ql-table').click({ button: 'right' }); + await page.locator('.table-up-menu.is-contextmenu .table-up-menu__item').filter({ hasText: 'Merge Cell' }).first().click(); + + await page.locator('#editor1 .ql-editor .ql-table td').nth(0).click(); + await page.locator('#editor1 .ql-editor .ql-table td').nth(0).click(); + await expect(selectionLine).toBeVisible(); + await page.mouse.down(); + await page.mouse.move(cellBounding.x + cellBounding.width * 2, cellBounding.y); + await page.mouse.up(); + + expect( + Number.parseFloat(await selectionLine.evaluate(el => getComputedStyle(el).width)), + ).toBeCloseTo(cellBounding.width * 2, -1); + expect( + Number.parseFloat(await selectionLine.evaluate(el => getComputedStyle(el).height)), + ).toBeCloseTo(cellBounding.height * 3, -1); +}); diff --git a/src/__tests__/e2e/utils.ts b/src/__tests__/e2e/utils.ts new file mode 100644 index 0000000..3df8cd2 --- /dev/null +++ b/src/__tests__/e2e/utils.ts @@ -0,0 +1,6 @@ +import type { Page } from '@playwright/test'; + +export const createTableBySelect = async (page: Page, container: string, row: number, col: number) => { + await page.locator(`#${container} .ql-toolbar .ql-table-up.ql-picker`).click(); + await page.locator(`#${container} .ql-toolbar .ql-custom-select .table-up-select-box__item[data-row="${row}"][data-col="${col}"]`).click(); +};