diff --git a/packages/nc-gui/components/cell/attachment/Modal.vue b/packages/nc-gui/components/cell/attachment/Modal.vue
index e25ac28674..ae89941f80 100644
--- a/packages/nc-gui/components/cell/attachment/Modal.vue
+++ b/packages/nc-gui/components/cell/attachment/Modal.vue
@@ -89,6 +89,7 @@ function onRemoveFileClick(title: any, i: number) {
diff --git a/packages/nc-gui/components/smartsheet/column/AttachmentOptions.vue b/packages/nc-gui/components/smartsheet/column/AttachmentOptions.vue
index 55991daee2..faf8c43906 100644
--- a/packages/nc-gui/components/smartsheet/column/AttachmentOptions.vue
+++ b/packages/nc-gui/components/smartsheet/column/AttachmentOptions.vue
@@ -73,13 +73,13 @@ watch(searchValue, (value) => {
-
+
-
+
diff --git a/tests/playwright/fixtures/sampleFiles/Image/1.jpeg b/tests/playwright/fixtures/sampleFiles/Image/1.jpeg
new file mode 100644
index 0000000000..38f6296de1
Binary files /dev/null and b/tests/playwright/fixtures/sampleFiles/Image/1.jpeg differ
diff --git a/tests/playwright/fixtures/sampleFiles/Image/2.png b/tests/playwright/fixtures/sampleFiles/Image/2.png
new file mode 100644
index 0000000000..8a8062b9ad
Binary files /dev/null and b/tests/playwright/fixtures/sampleFiles/Image/2.png differ
diff --git a/tests/playwright/fixtures/sampleFiles/Image/3.jpeg b/tests/playwright/fixtures/sampleFiles/Image/3.jpeg
new file mode 100644
index 0000000000..f3a232b77e
Binary files /dev/null and b/tests/playwright/fixtures/sampleFiles/Image/3.jpeg differ
diff --git a/tests/playwright/fixtures/sampleFiles/Image/4.jpeg b/tests/playwright/fixtures/sampleFiles/Image/4.jpeg
new file mode 100644
index 0000000000..72fe2b94d9
Binary files /dev/null and b/tests/playwright/fixtures/sampleFiles/Image/4.jpeg differ
diff --git a/tests/playwright/fixtures/sampleFiles/Image/5.jpeg b/tests/playwright/fixtures/sampleFiles/Image/5.jpeg
new file mode 100644
index 0000000000..becc1dcdb9
Binary files /dev/null and b/tests/playwright/fixtures/sampleFiles/Image/5.jpeg differ
diff --git a/tests/playwright/fixtures/sampleFiles/Image/6_bigSize.png b/tests/playwright/fixtures/sampleFiles/Image/6_bigSize.png
new file mode 100644
index 0000000000..9fb77d9311
Binary files /dev/null and b/tests/playwright/fixtures/sampleFiles/Image/6_bigSize.png differ
diff --git a/tests/playwright/pages/Dashboard/Grid/Column/Attachment.ts b/tests/playwright/pages/Dashboard/Grid/Column/Attachment.ts
new file mode 100644
index 0000000000..0eb5f82185
--- /dev/null
+++ b/tests/playwright/pages/Dashboard/Grid/Column/Attachment.ts
@@ -0,0 +1,158 @@
+import { ColumnPageObject } from '.';
+import BasePage from '../../../Base';
+import { expect } from '@playwright/test';
+
+export class AttachmentColumnPageObject extends BasePage {
+ readonly column: ColumnPageObject;
+
+ constructor(column: ColumnPageObject) {
+ super(column.rootPage);
+ this.column = column;
+ }
+
+ get() {
+ return this.column.get();
+ }
+
+ async advanceConfig({
+ columnTitle,
+ fileCount,
+ fileSize,
+ fileTypesExcludeList,
+ }: {
+ columnTitle: string;
+ fileCount?: number;
+ fileSize?: number;
+ fileTypesExcludeList?: string[];
+ }) {
+ await this.column.openEdit({ title: columnTitle });
+ await this.column.editMenuShowMore();
+
+ // text box : nc-attachment-max-count
+ // text box : nc-attachment-max-size
+ // checkbox : ant-tree-checkbox
+ // Checkbox order: Application, Audio, Image, Video, Misc
+
+ if (fileCount) {
+ const inputMaxCount = await this.column.get().locator(`.nc-attachment-max-count`);
+ await inputMaxCount.locator(`input`).fill(fileCount.toString());
+ }
+
+ if (fileSize) {
+ const inputMaxSize = await this.column.get().locator(`.nc-attachment-max-size`);
+ await inputMaxSize.locator(`input`).fill(fileSize.toString());
+ }
+
+ if (fileTypesExcludeList) {
+ const treeList = await this.column.get().locator(`.ant-tree-list`);
+ const checkboxList = await treeList.locator(`.ant-tree-treenode`);
+
+ // print count of treenode
+ const count = await checkboxList.count();
+ console.log(`count: ${count}`);
+
+ // log checkboxList
+ for (let i = 0; i < count; i++) {
+ const checkbox = await checkboxList.nth(i);
+ const text = await checkbox.innerText();
+ console.log(`text: ${text}`);
+ }
+
+ for (let i = 0; i < fileTypesExcludeList.length; i++) {
+ const fileType = fileTypesExcludeList[i];
+ switch (fileType) {
+ case 'Application':
+ await checkboxList.nth(0).locator(`.ant-tree-checkbox`).click();
+ break;
+ case 'Audio':
+ await checkboxList.nth(1).locator(`.ant-tree-checkbox`).click();
+ break;
+ case 'Image':
+ await checkboxList.nth(2).locator(`.ant-tree-checkbox`).click();
+ break;
+ case 'Video':
+ await checkboxList.nth(3).locator(`.ant-tree-checkbox`).click();
+ break;
+ case 'Misc':
+ await checkboxList.nth(4).locator(`.ant-tree-checkbox`).click();
+ break;
+ default:
+ break;
+ }
+ }
+
+ await this.rootPage.waitForTimeout(1000);
+ }
+
+ await this.column.save({ isUpdated: true });
+ }
+
+ // add multiple options at once after column creation is completed
+ //
+ async addOptions({ columnTitle, options }: { columnTitle: string; options: string[] }) {
+ await this.column.openEdit({ title: columnTitle });
+ for (let i = 0; i < options.length; i++) {
+ await this.column.get().locator('button:has-text("Add option")').click();
+ await this.column.get().locator(`[data-testid="select-column-option-input-${i}"]`).click();
+ await this.column.get().locator(`[data-testid="select-column-option-input-${i}"]`).fill(options[i]);
+ }
+ await this.column.save({ isUpdated: true });
+ }
+
+ async editOption({ columnTitle, index, newOption }: { index: number; columnTitle: string; newOption: string }) {
+ await this.column.openEdit({ title: columnTitle });
+
+ await this.column.get().locator(`[data-testid="select-column-option-input-${index}"]`).click();
+ await this.column.get().locator(`[data-testid="select-column-option-input-${index}"]`).fill(newOption);
+
+ await this.column.save({ isUpdated: true });
+ }
+
+ async deleteOption({ columnTitle, index }: { index: number; columnTitle: string }) {
+ await this.column.openEdit({ title: columnTitle });
+
+ await this.column.get().locator(`svg[data-testid="select-column-option-remove-${index}"]`).click();
+
+ await expect(this.column.get().getByTestId(`select-column-option-${index}`)).toHaveClass(/removed/);
+
+ await this.column.save({ isUpdated: true });
+ }
+
+ async deleteOptionWithUndo({ columnTitle, index }: { index: number; columnTitle: string }) {
+ await this.column.openEdit({ title: columnTitle });
+
+ await this.column.get().locator(`svg[data-testid="select-column-option-remove-${index}"]`).click();
+
+ await expect(this.column.get().getByTestId(`select-column-option-${index}`)).toHaveClass(/removed/);
+
+ await this.column.get().locator(`svg[data-testid="select-column-option-remove-undo-${index}"]`).click();
+
+ await expect(this.column.get().getByTestId(`select-column-option-${index}`)).not.toHaveClass(/removed/);
+
+ await this.column.save({ isUpdated: true });
+ }
+
+ async reorderOption({
+ columnTitle,
+ sourceOption,
+ destinationOption,
+ }: {
+ columnTitle: string;
+ sourceOption: string;
+ destinationOption: string;
+ }) {
+ await this.column.openEdit({ title: columnTitle });
+
+ await this.column.rootPage.waitForTimeout(150);
+
+ await this.column.rootPage.dragAndDrop(
+ `svg[data-testid="select-option-column-handle-icon-${sourceOption}"]`,
+ `svg[data-testid="select-option-column-handle-icon-${destinationOption}"]`,
+ {
+ force: true,
+ }
+ );
+
+ await this.column.save({ isUpdated: true });
+ }
+}
diff --git a/tests/playwright/pages/Dashboard/Grid/Column/index.ts b/tests/playwright/pages/Dashboard/Grid/Column/index.ts
index 483e6945a3..064b2093b5 100644
--- a/tests/playwright/pages/Dashboard/Grid/Column/index.ts
+++ b/tests/playwright/pages/Dashboard/Grid/Column/index.ts
@@ -2,15 +2,18 @@ import { expect } from '@playwright/test';
import { GridPage } from '..';
import BasePage from '../../../Base';
import { SelectOptionColumnPageObject } from './SelectOptionColumn';
+import { AttachmentColumnPageObject } from './Attachment';
export class ColumnPageObject extends BasePage {
readonly grid: GridPage;
readonly selectOption: SelectOptionColumnPageObject;
+ readonly attachmentColumnPageObject: AttachmentColumnPageObject;
constructor(grid: GridPage) {
super(grid.rootPage);
this.grid = grid;
this.selectOption = new SelectOptionColumnPageObject(this);
+ this.attachmentColumnPageObject = new AttachmentColumnPageObject(this);
}
get() {
@@ -298,6 +301,10 @@ export class ColumnPageObject extends BasePage {
}
}
+ async editMenuShowMore() {
+ await this.rootPage.locator('.nc-more-options').click();
+ }
+
async duplicateColumn({ title, expectedTitle = `${title}_copy` }: { title: string; expectedTitle?: string }) {
await this.grid.get().locator(`th[data-title="${title}"] .nc-ui-dt-dropdown`).click();
await this.rootPage.locator('li[role="menuitem"]:has-text("Duplicate"):visible').click();
diff --git a/tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts b/tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts
index 34ffc416be..bb12fa8e1e 100644
--- a/tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts
+++ b/tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts
@@ -28,7 +28,38 @@ export class AttachmentCellPageObject extends BasePage {
return await this.attachFile({ filePickUIAction: attachFileAction, filePath });
}
+ async expandModalAddFile({ filePath }: { filePath: string[] }) {
+ const attachFileAction = this.rootPage
+ .locator('.ant-modal.nc-attachment-modal.active')
+ .locator('[data-testid="attachment-cell-file-picker-button"]')
+ .click();
+ return await this.attachFile({ filePickUIAction: attachFileAction, filePath });
+ }
+
+ async expandModalOpen({ index, columnHeader }: { index?: number; columnHeader: string }) {
+ return this.get({ index, columnHeader })
+ .locator('.nc-cell > .nc-attachment-cell > .group.cursor-pointer')
+ .last()
+ .click();
+ }
+
async verifyFile({ index, columnHeader }: { index: number; columnHeader: string }) {
await expect(await this.get({ index, columnHeader }).locator('.nc-attachment')).toBeVisible();
}
+
+ async verifyFileCount({ index, columnHeader, count }: { index: number; columnHeader: string; count: number }) {
+ const attachments = await this.get({ index, columnHeader }).locator(
+ '.nc-cell > .nc-attachment-cell > .flex > .nc-attachment'
+ );
+
+ console.log(await attachments.count());
+ expect(await attachments.count()).toBe(count);
+
+ // attachments should be of count 'count'
+ // await expect(await attachments.count()).toBe(count);
+ }
+
+ async expandModalClose() {
+ return this.rootPage.locator('.ant-modal.nc-attachment-modal.active').press('Escape');
+ }
}
diff --git a/tests/playwright/tests/columnAttachments.spec.ts b/tests/playwright/tests/columnAttachments.spec.ts
index 3a378bd9e2..92b549db91 100644
--- a/tests/playwright/tests/columnAttachments.spec.ts
+++ b/tests/playwright/tests/columnAttachments.spec.ts
@@ -2,14 +2,18 @@ import { expect, test } from '@playwright/test';
import { DashboardPage } from '../pages/Dashboard';
import { SharedFormPage } from '../pages/SharedForm';
import setup from '../setup';
+import { AccountPage } from '../pages/Account';
+import { AccountLicensePage } from '../pages/Account/License';
test.describe('Attachment column', () => {
let dashboard: DashboardPage;
- let context: any;
+ let accountLicensePage: AccountLicensePage, accountPage: AccountPage, context: any;
test.beforeEach(async ({ page }) => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
+ accountPage = new AccountPage(page);
+ accountLicensePage = new AccountLicensePage(accountPage);
});
test('Create and verify atttachent column, verify it in shared form,', async ({ page, context }) => {
@@ -90,4 +94,118 @@ test.describe('Attachment column', () => {
await expect(cells[1]).toBe('South Hill');
await expect(cells[2].includes('4.json(http://localhost:8080/download/')).toBe(true);
});
+
+ test('Attachment enterprise features,', async ({ page, context }) => {
+ // configure enterprise key
+ test.slow();
+ await accountLicensePage.goto();
+ await accountLicensePage.saveLicenseKey('1234567890');
+ await dashboard.goto();
+
+ await dashboard.treeView.openTable({ title: 'Country' });
+ await dashboard.grid.column.create({
+ title: 'testAttach',
+ type: 'Attachment',
+ });
+ await dashboard.grid.column.attachmentColumnPageObject.advanceConfig({
+ columnTitle: 'testAttach',
+ fileCount: 2,
+ fileSize: 1,
+ // allow only image type
+ fileTypesExcludeList: ['Application', 'Video', 'Audio', 'Misc'],
+ });
+
+ // in-cell, add big file, should get rejected
+ const bigFile = [`${process.cwd()}/fixtures/sampleFiles/Image/6_bigSize.png`];
+ await dashboard.grid.cell.attachment.addFile({
+ index: 1,
+ columnHeader: 'testAttach',
+ filePath: bigFile,
+ });
+ // The size of ${file.name} exceeds the maximum file size ${attachmentMeta.maxAttachmentSize} MB.
+ await dashboard.verifyToast({ message: 'The size of 6_bigSize.png exceeds the maximum file size 1 MB.' });
+
+ // in-cell, add 2 files, should get accepted
+ const twoFileArray = [
+ `${process.cwd()}/fixtures/sampleFiles/Image/1.jpeg`,
+ `${process.cwd()}/fixtures/sampleFiles/Image/2.png`,
+ ];
+ await dashboard.grid.cell.attachment.addFile({
+ index: 1,
+ columnHeader: 'testAttach',
+ filePath: twoFileArray,
+ });
+ await dashboard.rootPage.waitForTimeout(2000);
+ await dashboard.grid.cell.attachment.verifyFileCount({
+ index: 1,
+ columnHeader: 'testAttach',
+ count: 2,
+ });
+
+ // add another file, should get rejected
+ const oneFileArray = [`${process.cwd()}/fixtures/sampleFiles/Image/3.jpeg`];
+ await dashboard.grid.cell.attachment.addFile({
+ index: 1,
+ columnHeader: 'testAttach',
+ filePath: oneFileArray,
+ });
+ // wait for toast 'You can only upload at most 2 files to this cell'
+ await dashboard.verifyToast({ message: 'You can only upload at most 2 files to this cell' });
+
+ // try to upload 3 files in one go, should get rejected
+ const threeFileArray = [
+ `${process.cwd()}/fixtures/sampleFiles/Image/1.jpeg`,
+ `${process.cwd()}/fixtures/sampleFiles/Image/2.png`,
+ `${process.cwd()}/fixtures/sampleFiles/Image/3.jpeg`,
+ ];
+ await dashboard.grid.cell.attachment.addFile({
+ index: 2,
+ columnHeader: 'testAttach',
+ filePath: threeFileArray,
+ });
+ await dashboard.verifyToast({ message: 'You can only upload at most 2 files to this cell' });
+
+ // open expand modal, try to insert file type not supported
+ // message: ${file.name} has the mime type ${file.type} which is not allowed in this column.
+ await dashboard.grid.cell.attachment.addFile({
+ index: 3,
+ columnHeader: 'testAttach',
+ filePath: [`${process.cwd()}/fixtures/sampleFiles/1.json`],
+ });
+ await dashboard.verifyToast({
+ message: '1.json has the mime type application/json which is not allowed in this column.',
+ });
+
+ // Expand modal
+
+ // open expand modal, try to insert more files
+ await dashboard.grid.cell.attachment.expandModalOpen({
+ index: 1,
+ columnHeader: 'testAttach',
+ });
+ await dashboard.grid.cell.attachment.expandModalAddFile({
+ filePath: oneFileArray,
+ });
+ await dashboard.verifyToast({ message: 'You can only upload at most 2 files to this cell' });
+
+ // open expand modal, try to insert file type not supported
+ // message: ${file.name} has the mime type ${file.type} which is not allowed in this column.
+ await dashboard.grid.cell.attachment.expandModalAddFile({
+ filePath: [`${process.cwd()}/fixtures/sampleFiles/1.json`],
+ });
+ await dashboard.verifyToast({
+ message: '1.json has the mime type application/json which is not allowed in this column.',
+ });
+
+ // open expand modal, try to insert big file
+ // message: The size of ${file.name} exceeds the maximum file size ${attachmentMeta.maxAttachmentSize} MB.
+ await dashboard.grid.cell.attachment.expandModalAddFile({
+ filePath: bigFile,
+ });
+ await dashboard.verifyToast({ message: 'The size of 6_bigSize.png exceeds the maximum file size 1 MB.' });
+ await dashboard.grid.cell.attachment.expandModalClose();
+
+ // wait for timeout
+ // await dashboard.rootPage.waitForTimeout(20000);
+ });
});