|
|
|
// Fields
|
|
|
|
|
|
|
|
import BasePage from '../../Base';
|
|
|
|
import { expect, Locator } from '@playwright/test';
|
|
|
|
import { DetailsPage } from './index';
|
|
|
|
import { UITypes } from 'nocodb-sdk';
|
|
|
|
|
|
|
|
export class FieldsPage extends BasePage {
|
|
|
|
readonly detailsPage: DetailsPage;
|
|
|
|
|
|
|
|
readonly searchFieldInput: Locator;
|
|
|
|
readonly addNewFieldButton: Locator;
|
|
|
|
readonly resetFieldChangesButton: Locator;
|
|
|
|
readonly saveChangesButton: Locator;
|
|
|
|
|
|
|
|
readonly addOrEditColumn: Locator;
|
|
|
|
readonly fieldListWrapper: Locator;
|
|
|
|
|
|
|
|
constructor(details: DetailsPage) {
|
|
|
|
super(details.rootPage);
|
|
|
|
this.detailsPage = details;
|
|
|
|
this.searchFieldInput = this.get().getByTestId('nc-field-search-input');
|
|
|
|
this.addNewFieldButton = this.get().getByTestId('nc-field-add-new');
|
|
|
|
this.resetFieldChangesButton = this.get().getByTestId('nc-field-reset');
|
|
|
|
this.saveChangesButton = this.get().getByTestId('nc-field-save-changes');
|
|
|
|
this.addOrEditColumn = this.get().getByTestId('add-or-edit-column');
|
|
|
|
this.fieldListWrapper = this.get().getByTestId('nc-field-list-wrapper');
|
|
|
|
}
|
|
|
|
|
|
|
|
get() {
|
|
|
|
return this.detailsPage.get().locator('.nc-fields-wrapper');
|
|
|
|
}
|
|
|
|
|
|
|
|
async fillSearch({ title }: { title: string }) {
|
|
|
|
const searchInput = this.get().getByTestId('nc-field-search-input');
|
|
|
|
await searchInput.click();
|
|
|
|
await searchInput.fill(title);
|
|
|
|
}
|
|
|
|
|
|
|
|
async clearSearch() {
|
|
|
|
await this.get().getByTestId('nc-field-clear-search').click();
|
|
|
|
}
|
|
|
|
|
|
|
|
async clickNewField() {
|
|
|
|
await this.addNewFieldButton.click();
|
|
|
|
}
|
|
|
|
|
|
|
|
async clickRestoreField({ title }: { title: string }) {
|
|
|
|
await this.getField({ title }).getByTestId('nc-field-restore-changes').click();
|
|
|
|
}
|
|
|
|
|
|
|
|
async createOrUpdate({
|
|
|
|
title,
|
|
|
|
type = UITypes.SingleLineText,
|
|
|
|
isUpdateMode = false,
|
|
|
|
saveChanges = true,
|
|
|
|
formula = '',
|
|
|
|
qrCodeValueColumnTitle = '',
|
|
|
|
barcodeValueColumnTitle = '',
|
|
|
|
barcodeFormat = '',
|
|
|
|
childTable = '',
|
|
|
|
childColumn = '',
|
|
|
|
relationType = '',
|
|
|
|
rollupType = '',
|
|
|
|
format = '',
|
|
|
|
dateFormat = 'YYYY-MM-DD',
|
|
|
|
timeFormat = 'HH:mm',
|
|
|
|
insertAboveColumnTitle,
|
|
|
|
insertBelowColumnTitle,
|
|
|
|
}: {
|
|
|
|
title: string;
|
|
|
|
type?: UITypes;
|
|
|
|
isUpdateMode?: boolean;
|
|
|
|
saveChanges?: boolean;
|
|
|
|
formula?: string;
|
|
|
|
qrCodeValueColumnTitle?: string;
|
|
|
|
barcodeValueColumnTitle?: string;
|
|
|
|
barcodeFormat?: string;
|
|
|
|
childTable?: string;
|
|
|
|
childColumn?: string;
|
|
|
|
relationType?: string;
|
|
|
|
rollupType?: string;
|
|
|
|
format?: string;
|
|
|
|
dateFormat?: string;
|
|
|
|
timeFormat?: string;
|
|
|
|
insertAboveColumnTitle?: string;
|
|
|
|
insertBelowColumnTitle?: string;
|
|
|
|
}) {
|
|
|
|
if (!isUpdateMode) {
|
|
|
|
if (insertAboveColumnTitle) {
|
|
|
|
await this.selectFieldAction({ title: insertAboveColumnTitle, action: 'insert-above' });
|
|
|
|
} else if (insertBelowColumnTitle) {
|
|
|
|
await this.selectFieldAction({ title: insertBelowColumnTitle, action: 'insert-below' });
|
|
|
|
} else {
|
|
|
|
await this.clickNewField();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await this.addOrEditColumn.waitFor({ state: 'visible' });
|
|
|
|
|
|
|
|
await this.fillTitle({ title });
|
|
|
|
|
|
|
|
await this.selectType({ type });
|
|
|
|
await this.rootPage.waitForTimeout(500);
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
case 'SingleSelect':
|
|
|
|
case 'MultiSelect':
|
|
|
|
break;
|
|
|
|
case 'Duration':
|
|
|
|
if (format) {
|
|
|
|
await this.addOrEditColumn.locator('.ant-select-single').nth(1).click();
|
|
|
|
await this.rootPage
|
|
|
|
.locator(`.ant-select-item`, {
|
|
|
|
hasText: format,
|
|
|
|
})
|
|
|
|
.click();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'Date':
|
|
|
|
await this.addOrEditColumn.locator('.nc-date-select').click();
|
|
|
|
await this.rootPage.locator('.nc-date-select').pressSequentially(dateFormat);
|
|
|
|
await this.rootPage.locator('.ant-select-item').locator(`text="${dateFormat}"`).click();
|
|
|
|
break;
|
|
|
|
case 'DateTime':
|
|
|
|
// Date Format
|
|
|
|
await this.addOrEditColumn.locator('.nc-date-select').click();
|
|
|
|
await this.rootPage.locator('.ant-select-item').locator(`text="${dateFormat}"`).click();
|
|
|
|
// Time Format
|
|
|
|
await this.addOrEditColumn.locator('.nc-time-select').click();
|
|
|
|
await this.rootPage.locator('.ant-select-item').locator(`text="${timeFormat}"`).click();
|
|
|
|
break;
|
|
|
|
case 'Formula':
|
|
|
|
await this.addOrEditColumn.locator('.nc-formula-input').fill(formula);
|
|
|
|
break;
|
|
|
|
case 'QrCode':
|
|
|
|
await this.addOrEditColumn.locator('.ant-select-single').nth(1).click();
|
|
|
|
await this.rootPage
|
|
|
|
.locator(`.ant-select-item`)
|
|
|
|
.locator(`[data-testid="nc-qr-${qrCodeValueColumnTitle}"]`)
|
|
|
|
.click();
|
|
|
|
break;
|
|
|
|
case 'Barcode':
|
|
|
|
await this.addOrEditColumn.locator('.ant-select-single').nth(1).click();
|
|
|
|
await this.rootPage
|
|
|
|
.locator(`.ant-select-item`, {
|
|
|
|
hasText: new RegExp(`^${barcodeValueColumnTitle}$`),
|
|
|
|
})
|
|
|
|
.click();
|
|
|
|
break;
|
|
|
|
case 'Lookup':
|
|
|
|
await this.addOrEditColumn.locator('.ant-select-single').nth(1).click();
|
|
|
|
await this.rootPage
|
|
|
|
.locator(`.ant-select-item`, {
|
|
|
|
hasText: childTable,
|
|
|
|
})
|
|
|
|
.click();
|
|
|
|
await this.addOrEditColumn.locator('.ant-select-single').nth(2).click();
|
|
|
|
await this.rootPage
|
|
|
|
.locator(`.ant-select-item`, {
|
|
|
|
hasText: childColumn,
|
|
|
|
})
|
|
|
|
.last()
|
|
|
|
.click();
|
|
|
|
break;
|
|
|
|
case 'Rollup':
|
|
|
|
await this.addOrEditColumn.locator('.ant-select-single').nth(1).click();
|
|
|
|
await this.rootPage
|
|
|
|
.locator(`.ant-select-item`, {
|
|
|
|
hasText: childTable,
|
|
|
|
})
|
|
|
|
.click();
|
|
|
|
await this.addOrEditColumn.locator('.ant-select-single').nth(2).click();
|
|
|
|
await this.rootPage
|
|
|
|
.locator(`.nc-dropdown-relation-column >> .ant-select-item`, {
|
|
|
|
hasText: childColumn,
|
|
|
|
})
|
|
|
|
.click();
|
|
|
|
await this.addOrEditColumn.locator('.ant-select-single').nth(3).click();
|
|
|
|
await this.rootPage
|
|
|
|
.locator(`.nc-dropdown-rollup-function >> .ant-select-item`, {
|
|
|
|
hasText: rollupType,
|
|
|
|
})
|
|
|
|
.nth(0)
|
|
|
|
.click();
|
|
|
|
break;
|
|
|
|
case 'Links':
|
|
|
|
await this.addOrEditColumn
|
|
|
|
.locator('.nc-ltar-relation-type >> .ant-radio')
|
|
|
|
.nth(relationType === 'Has Many' ? 1 : 2)
|
|
|
|
.click();
|
|
|
|
await this.addOrEditColumn.locator('.ant-select-single').nth(1).click();
|
|
|
|
await this.rootPage.locator(`.nc-ltar-child-table >> input[type="search"]`).fill(childTable);
|
|
|
|
await this.rootPage
|
|
|
|
.locator(`.nc-dropdown-ltar-child-table >> .ant-select-item`, {
|
|
|
|
hasText: childTable,
|
|
|
|
})
|
|
|
|
.nth(0)
|
|
|
|
.click();
|
|
|
|
break;
|
|
|
|
case 'User':
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (saveChanges) {
|
|
|
|
await this.saveChanges();
|
|
|
|
|
|
|
|
const fieldsText = await this.getAllFieldText();
|
|
|
|
|
|
|
|
if (insertAboveColumnTitle) {
|
|
|
|
// verify field inserted above the target field
|
|
|
|
expect(fieldsText[fieldsText.findIndex(title => title.startsWith(insertAboveColumnTitle)) - 1]).toBe(title);
|
|
|
|
} else if (insertBelowColumnTitle) {
|
|
|
|
// verify field inserted below the target field
|
|
|
|
expect(fieldsText[fieldsText.findIndex(title => title.startsWith(insertBelowColumnTitle)) + 1]).toBe(title);
|
|
|
|
} else {
|
|
|
|
// verify field inserted at the end
|
|
|
|
expect(fieldsText[fieldsText.length - 1]).toBe(title);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fillTitle({ title }: { title: string }) {
|
|
|
|
const fieldTitleInput = this.addOrEditColumn.locator('.nc-fields-input');
|
|
|
|
await fieldTitleInput.click();
|
|
|
|
await fieldTitleInput.fill(title);
|
|
|
|
}
|
|
|
|
|
|
|
|
async selectType({ type }: { type: string }) {
|
|
|
|
await this.addOrEditColumn.locator('.ant-select-selector > .ant-select-selection-item').click();
|
|
|
|
|
|
|
|
await this.addOrEditColumn.locator('.ant-select-selection-search-input[aria-expanded="true"]').waitFor();
|
|
|
|
await this.addOrEditColumn.locator('.ant-select-selection-search-input[aria-expanded="true"]').fill(type);
|
|
|
|
|
|
|
|
// Select column type
|
|
|
|
await this.rootPage.locator('.rc-virtual-list-holder-inner > div').locator(`text="${type}"`).click();
|
|
|
|
}
|
|
|
|
|
|
|
|
async saveChanges() {
|
|
|
|
await this.waitForResponse({
|
|
|
|
uiAction: async () => await this.saveChangesButton.click(),
|
|
|
|
requestUrlPathToMatch: 'api/v1/db/meta/tables/',
|
|
|
|
httpMethodsToMatch: ['GET'],
|
|
|
|
responseJsonMatcher: json => json['hash'],
|
|
|
|
});
|
|
|
|
await this.rootPage.waitForTimeout(1000);
|
|
|
|
}
|
|
|
|
|
|
|
|
getField({ title }: { title: string }) {
|
|
|
|
return this.fieldListWrapper.getByTestId(`nc-field-item-${title}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getFieldVisibilityCheckbox({ title }: { title: string }) {
|
|
|
|
return this.getField({ title }).getByTestId('nc-field-visibility-checkbox');
|
|
|
|
}
|
|
|
|
|
|
|
|
async selectFieldAction({
|
|
|
|
title,
|
|
|
|
action,
|
|
|
|
isDisplayValueField = false,
|
|
|
|
}: {
|
|
|
|
title: string;
|
|
|
|
action: 'copy-id' | 'duplicate' | 'insert-above' | 'insert-below' | 'delete';
|
|
|
|
isDisplayValueField?: boolean;
|
|
|
|
}) {
|
|
|
|
const field = this.getField({ title });
|
|
|
|
await field.scrollIntoViewIfNeeded();
|
|
|
|
|
|
|
|
await field.hover();
|
|
|
|
// await field.getByTestId('nc-field-item-action-button').waitFor({ state: 'visible' });
|
|
|
|
await field.getByTestId('nc-field-item-action-button').click();
|
|
|
|
|
|
|
|
const fieldActionDropdown = isDisplayValueField
|
|
|
|
? this.rootPage.locator('.nc-field-item-action-dropdown-display-column')
|
|
|
|
: this.rootPage.locator('.nc-field-item-action-dropdown');
|
|
|
|
|
|
|
|
await fieldActionDropdown.waitFor({ state: 'visible' });
|
|
|
|
await fieldActionDropdown.getByTestId(`nc-field-item-action-${action}`).click();
|
|
|
|
|
|
|
|
if (action === 'copy-id') {
|
|
|
|
await field.getByTestId('nc-field-item-action-button').click({
|
|
|
|
force: true,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
await fieldActionDropdown.waitFor({ state: 'hidden' });
|
|
|
|
}
|
|
|
|
|
|
|
|
async getAllFieldText() {
|
|
|
|
const fieldsText = [];
|
|
|
|
const locator = this.fieldListWrapper.locator('>div');
|
|
|
|
const count = await locator.count();
|
|
|
|
for (let i = 0; i < count; i++) {
|
|
|
|
await locator.nth(i).scrollIntoViewIfNeeded();
|
|
|
|
const text = await locator.nth(i).getByTestId('nc-field-title').textContent();
|
|
|
|
fieldsText.push(text);
|
|
|
|
}
|
|
|
|
return fieldsText;
|
|
|
|
}
|
|
|
|
|
|
|
|
async getFieldId({ title, isDisplayValueField = false }: { title: string; isDisplayValueField?: boolean }) {
|
|
|
|
const field = this.getField({ title });
|
|
|
|
await field.scrollIntoViewIfNeeded();
|
|
|
|
|
|
|
|
await field.hover();
|
|
|
|
await field.getByTestId('nc-field-item-action-button').waitFor({ state: 'visible' });
|
|
|
|
await field.getByTestId('nc-field-item-action-button').click();
|
|
|
|
|
|
|
|
const fieldActionDropdown = isDisplayValueField
|
|
|
|
? this.rootPage.locator('.nc-field-item-action-dropdown-display-column')
|
|
|
|
: this.rootPage.locator('.nc-field-item-action-dropdown');
|
|
|
|
|
|
|
|
await fieldActionDropdown.waitFor({ state: 'visible' });
|
|
|
|
const fieldId = await fieldActionDropdown.getByTestId('nc-field-item-id').textContent();
|
|
|
|
await field.getByTestId('nc-field-item-action-button').click();
|
|
|
|
await fieldActionDropdown.waitFor({ state: 'hidden' });
|
|
|
|
return fieldId;
|
|
|
|
}
|
|
|
|
}
|