mirror of https://github.com/nocodb/nocodb
Daniel Spaude
2 years ago
4 changed files with 474 additions and 3 deletions
@ -0,0 +1,140 @@
|
||||
import { expect } from '@playwright/test'; |
||||
import { DashboardPage } from '..'; |
||||
import BasePage from '../../Base'; |
||||
import { ToolbarPage } from '../common/Toolbar'; |
||||
|
||||
export class MapPage extends BasePage { |
||||
readonly dashboard: DashboardPage; |
||||
readonly toolbar: ToolbarPage; |
||||
|
||||
constructor(dashboard: DashboardPage) { |
||||
super(dashboard.rootPage); |
||||
this.dashboard = dashboard; |
||||
this.toolbar = new ToolbarPage(this); |
||||
} |
||||
|
||||
get() { |
||||
return this.dashboard.get().locator('[data-testid="nc-kanban-wrapper"]'); |
||||
} |
||||
|
||||
card(index: number) { |
||||
return this.get().locator(`.ant-card`).nth(index); |
||||
} |
||||
|
||||
async openExpandedRow({ index }: { index: number }) { |
||||
await this.card(index).click(); |
||||
await (await this.rootPage.locator('.ant-drawer-body').elementHandle())?.waitForElementState('stable'); |
||||
} |
||||
|
||||
// todo: Implement
|
||||
async addOption() {} |
||||
|
||||
// todo: Implement
|
||||
async dragDropCard(param: { from: string; to: string }) { |
||||
// const { from, to } = param;
|
||||
// const srcStack = await this.get().locator(`.nc-kanban-stack`).nth(1);
|
||||
// const dstStack = await this.get().locator(`.nc-kanban-stack`).nth(2);
|
||||
// const fromCard = await srcStack.locator(`.nc-kanban-item`).nth(1);
|
||||
// const toCard = await dstStack.locator(`.nc-kanban-item`).nth(1);
|
||||
// const [fromCard, toCard] = await Promise.all([
|
||||
// srcStack.locator(`.nc-kanban-item[data-draggable="true"]`).nth(0),
|
||||
// dstStack.locator(`.nc-kanban-item[data-draggable="true"]`).nth(0),
|
||||
// ]);
|
||||
// const fromCard = await this.get().locator(`.nc-kanban-item`).nth(0);
|
||||
// const toCard = await this.get().locator(`.nc-kanban-item`).nth(25);
|
||||
// await fromCard.dragTo(toCard);
|
||||
} |
||||
|
||||
async dragDropStack(param: { from: number; to: number }) { |
||||
const { from, to } = param; |
||||
const [fromStack, toStack] = await Promise.all([ |
||||
this.rootPage.locator(`.nc-kanban-stack-head`).nth(from), |
||||
this.rootPage.locator(`.nc-kanban-stack-head`).nth(to), |
||||
]); |
||||
await fromStack.dragTo(toStack); |
||||
} |
||||
|
||||
async verifyStackCount(param: { count: number }) { |
||||
const { count } = param; |
||||
await expect(this.get().locator(`.nc-kanban-stack`)).toHaveCount(count); |
||||
} |
||||
|
||||
async verifyStackOrder(param: { order: string[] }) { |
||||
const { order } = param; |
||||
const stacks = await this.get().locator(`.nc-kanban-stack`).count(); |
||||
for (let i = 0; i < stacks; i++) { |
||||
const stack = await this.get().locator(`.nc-kanban-stack`).nth(i); |
||||
// Since otherwise stack title will be repeated as title is in two divs, with one having hidden class
|
||||
const stackTitle = await stack.locator(`.nc-kanban-stack-head >> [data-testid="truncate-label"]`); |
||||
await expect(stackTitle).toHaveText(order[i], { ignoreCase: true }); |
||||
} |
||||
} |
||||
|
||||
async verifyStackFooter(param: { count: number[] }) { |
||||
const { count } = param; |
||||
const stacks = await this.get().locator(`.nc-kanban-stack`).count(); |
||||
for (let i = 0; i < stacks; i++) { |
||||
const stack = await this.get().locator(`.nc-kanban-stack`).nth(i); |
||||
const stackFooter = await stack.locator(`.nc-kanban-data-count`).innerText(); |
||||
await expect(stackFooter).toContain(`${count[i]} record${count[i] !== 1 ? 's' : ''}`); |
||||
} |
||||
} |
||||
|
||||
async verifyCardCount(param: { count: number[] }) { |
||||
const { count } = param; |
||||
const stacks = await this.get().locator(`.nc-kanban-stack`).count(); |
||||
for (let i = 0; i < stacks; i++) { |
||||
const stack = await this.get().locator(`.nc-kanban-stack`).nth(i); |
||||
const stackCards = stack.locator(`.nc-kanban-item`); |
||||
await expect(stackCards).toHaveCount(count[i]); |
||||
} |
||||
} |
||||
|
||||
async verifyCardOrder(param: { order: string[]; stackIndex: number }) { |
||||
const { order, stackIndex } = param; |
||||
const stack = await this.get().locator(`.nc-kanban-stack`).nth(stackIndex); |
||||
for (let i = 0; i < order.length; i++) { |
||||
const card = await stack.locator(`.nc-kanban-item`).nth(i); |
||||
const cardTitle = await card.locator(`.nc-cell`); |
||||
await expect(cardTitle).toHaveText(order[i]); |
||||
} |
||||
} |
||||
|
||||
// todo: Wait for render to complete
|
||||
async waitLoading() { |
||||
await this.rootPage.waitForTimeout(1000); |
||||
} |
||||
|
||||
async addNewStack(param: { title: string }) { |
||||
await this.toolbar.clickAddEditStack(); |
||||
await this.toolbar.addEditStack.addOption({ title: param.title }); |
||||
} |
||||
|
||||
async collapseStack(param: { index: number }) { |
||||
await this.get().locator(`.nc-kanban-stack-head`).nth(param.index).click(); |
||||
const modal = await this.rootPage.locator(`.nc-dropdown-kanban-stack-context-menu`); |
||||
await modal.locator('.ant-dropdown-menu-item:has-text("Collapse Stack")').click(); |
||||
} |
||||
|
||||
async expandStack(param: { index: number }) { |
||||
await this.rootPage.locator(`.nc-kanban-collapsed-stack`).nth(param.index).click(); |
||||
} |
||||
|
||||
async verifyCollapseStackCount(param: { count: number }) { |
||||
await expect(this.rootPage.locator('.nc-kanban-collapsed-stack')).toHaveCount(param.count); |
||||
} |
||||
|
||||
async addCard(param: { stackIndex: number }) { |
||||
await this.get().locator(`.nc-kanban-stack-head`).nth(param.stackIndex).click(); |
||||
const modal = await this.rootPage.locator(`.nc-dropdown-kanban-stack-context-menu`); |
||||
await modal.locator('.ant-dropdown-menu-item:has-text("Add new record")').click(); |
||||
} |
||||
|
||||
async deleteStack(param: { index: number }) { |
||||
await this.get().locator(`.nc-kanban-stack-head`).nth(param.index).click(); |
||||
const modal = await this.rootPage.locator(`.nc-dropdown-kanban-stack-context-menu`); |
||||
await modal.locator('.ant-dropdown-menu-item:has-text("Delete Stack")').click(); |
||||
const confirmationModal = await this.rootPage.locator(`.nc-modal-kanban-delete-stack`); |
||||
await confirmationModal.locator(`button:has-text("Delete")`).click(); |
||||
} |
||||
} |
@ -0,0 +1,330 @@
|
||||
import { test } from '@playwright/test'; |
||||
import { DashboardPage } from '../pages/Dashboard'; |
||||
import { ToolbarPage } from '../pages/Dashboard/common/Toolbar'; |
||||
|
||||
import setup from '../setup'; |
||||
import { isPg, isSqlite } from '../setup/db'; |
||||
|
||||
const filmRatings = ['G', 'PG', 'PG-13', 'R', 'NC-17']; |
||||
|
||||
test.describe('Map View', () => { |
||||
let dashboard: DashboardPage, toolbar: ToolbarPage; |
||||
let context: any; |
||||
|
||||
test.beforeEach(async ({ page }) => { |
||||
context = await setup({ page }); |
||||
dashboard = new DashboardPage(page, context.project); |
||||
toolbar = toolbar = dashboard.map.toolbar; |
||||
|
||||
// close 'Team & Auth' tab
|
||||
await dashboard.closeTab({ title: 'Team & Auth' }); |
||||
await dashboard.treeView.openTable({ title: 'Film' }); |
||||
|
||||
if (isSqlite(context)) { |
||||
await dashboard.treeView.deleteTable({ title: 'FilmList' }); |
||||
} |
||||
|
||||
if (isPg(context)) { |
||||
// Since these view depend on the Ratings column of the Film table
|
||||
await dashboard.treeView.deleteTable({ title: 'NicerButSlowerFilmList' }); |
||||
await dashboard.treeView.deleteTable({ title: 'FilmList' }); |
||||
} |
||||
|
||||
if (isSqlite(context) || isPg(context)) { |
||||
await dashboard.grid.column.openEdit({ title: 'Rating' }); |
||||
await dashboard.grid.column.selectType({ type: 'SingleSelect' }); |
||||
let count = 0; |
||||
for (const rating of filmRatings) { |
||||
await dashboard.grid.column.selectOption.addOption({ |
||||
index: count, |
||||
option: rating, |
||||
skipColumnModal: true, |
||||
}); |
||||
count = count + 1; |
||||
} |
||||
await dashboard.grid.column.save(); |
||||
} |
||||
}); |
||||
|
||||
test('Kanban', async () => { |
||||
await dashboard.viewSidebar.createKanbanView({ |
||||
title: 'Film Kanban', |
||||
}); |
||||
await dashboard.viewSidebar.verifyView({ |
||||
title: 'Film Kanban', |
||||
index: 1, |
||||
}); |
||||
|
||||
// configure stack-by field
|
||||
await toolbar.clickStackByField(); |
||||
await toolbar.stackBy.click({ title: 'Rating' }); |
||||
// click again to close menu
|
||||
await toolbar.clickStackByField(); |
||||
|
||||
const kanban = dashboard.kanban; |
||||
await kanban.verifyStackCount({ count: 6 }); |
||||
await kanban.verifyStackOrder({ |
||||
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'], |
||||
}); |
||||
await kanban.verifyStackFooter({ |
||||
count: [0, 178, 194, 223, 195, 210], |
||||
}); |
||||
await kanban.verifyCardCount({ |
||||
count: [0, 25, 25, 25, 25, 25], |
||||
}); |
||||
|
||||
// hide fields
|
||||
await toolbar.fields.hideAll(); |
||||
await toolbar.fields.toggle({ title: 'Title' }); |
||||
await kanban.verifyCardCount({ |
||||
count: [0, 25, 25, 25, 25, 25], |
||||
}); |
||||
|
||||
// verify card order
|
||||
const order = [ |
||||
['ACE GOLDFINGER', 'AFFAIR PREJUDICE', 'AFRICAN EGG'], |
||||
['ACADEMY DINOSAUR', 'AGENT TRUMAN', 'ALASKA PHANTOM'], |
||||
['AIRPLANE SIERRA', 'ALABAMA DEVIL', 'ALTER VICTORY'], |
||||
['AIRPORT POLLOCK', 'ALONE TRIP', 'AMELIE HELLFIGHTERS'], |
||||
['ADAPTATION HOLES', 'ALADDIN CALENDAR', 'ALICE FANTASIA'], |
||||
]; |
||||
for (let i = 1; i <= order.length; i++) |
||||
await kanban.verifyCardOrder({ |
||||
stackIndex: i, |
||||
order: order[i - 1], |
||||
}); |
||||
|
||||
// // verify inter stack drag-drop
|
||||
// await kanban.dragDropCard({
|
||||
// from: "ACE GOLDFINGER",
|
||||
// to: "ACADEMY DINOSAUR",
|
||||
// });
|
||||
|
||||
// verify drag drop stack
|
||||
await kanban.dragDropStack({ |
||||
from: 1, // G
|
||||
to: 2, // PG
|
||||
}); |
||||
await kanban.verifyStackOrder({ |
||||
order: ['Uncategorized', 'PG', 'G', 'PG-13', 'R', 'NC-17'], |
||||
}); |
||||
// verify drag drop stack
|
||||
await kanban.dragDropStack({ |
||||
from: 2, // G
|
||||
to: 1, // PG
|
||||
}); |
||||
await kanban.verifyStackOrder({ |
||||
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'], |
||||
}); |
||||
|
||||
// verify sort
|
||||
await toolbar.sort.add({ |
||||
columnTitle: 'Title', |
||||
isAscending: false, |
||||
isLocallySaved: false, |
||||
}); |
||||
// verify card order
|
||||
const order2 = [ |
||||
['YOUNG LANGUAGE', 'WEST LION'], |
||||
['WORST BANGER', 'WORDS HUNTER'], |
||||
]; |
||||
for (let i = 1; i <= order2.length; i++) |
||||
await kanban.verifyCardOrder({ |
||||
stackIndex: i, |
||||
order: order2[i - 1], |
||||
}); |
||||
await toolbar.sort.reset(); |
||||
// verify card order
|
||||
const order3 = [ |
||||
['ACE GOLDFINGER', 'AFFAIR PREJUDICE', 'AFRICAN EGG'], |
||||
['ACADEMY DINOSAUR', 'AGENT TRUMAN', 'ALASKA PHANTOM'], |
||||
]; |
||||
for (let i = 1; i <= order3.length; i++) |
||||
await kanban.verifyCardOrder({ |
||||
stackIndex: i, |
||||
order: order3[i - 1], |
||||
}); |
||||
|
||||
// verify filter
|
||||
await toolbar.clickFilter({ |
||||
networkValidation: true, |
||||
}); |
||||
await toolbar.filter.add({ |
||||
columnTitle: 'Title', |
||||
opType: 'is like', |
||||
value: 'BA', |
||||
isLocallySaved: false, |
||||
}); |
||||
await toolbar.clickFilter(); |
||||
|
||||
// verify card order
|
||||
const order4 = [ |
||||
['BAKED CLEOPATRA', 'BALLROOM MOCKINGBIRD'], |
||||
['ARIZONA BANG', 'EGYPT TENENBAUMS'], |
||||
]; |
||||
for (let i = 1; i <= order4.length; i++) |
||||
await kanban.verifyCardOrder({ |
||||
stackIndex: i, |
||||
order: order4[i - 1], |
||||
}); |
||||
await toolbar.filter.reset(); |
||||
const order5 = [ |
||||
['ACE GOLDFINGER', 'AFFAIR PREJUDICE', 'AFRICAN EGG'], |
||||
['ACADEMY DINOSAUR', 'AGENT TRUMAN', 'ALASKA PHANTOM'], |
||||
]; |
||||
for (let i = 1; i <= order5.length; i++) |
||||
await kanban.verifyCardOrder({ |
||||
stackIndex: i, |
||||
order: order5[i - 1], |
||||
}); |
||||
|
||||
await dashboard.rootPage.waitForTimeout(1000); |
||||
}); |
||||
|
||||
test('Kanban view operations', async () => { |
||||
test.slow(); |
||||
|
||||
await dashboard.viewSidebar.createKanbanView({ |
||||
title: 'Film Kanban', |
||||
}); |
||||
await dashboard.viewSidebar.verifyView({ |
||||
title: 'Film Kanban', |
||||
index: 1, |
||||
}); |
||||
|
||||
await toolbar.sort.add({ |
||||
columnTitle: 'Title', |
||||
isAscending: false, |
||||
isLocallySaved: false, |
||||
}); |
||||
|
||||
await toolbar.clickFilter(); |
||||
await toolbar.filter.add({ |
||||
columnTitle: 'Title', |
||||
opType: 'is like', |
||||
value: 'BA', |
||||
isLocallySaved: false, |
||||
}); |
||||
await toolbar.clickFilter(); |
||||
|
||||
await toolbar.fields.hideAll(); |
||||
await toolbar.fields.toggle({ title: 'Title' }); |
||||
|
||||
await dashboard.viewSidebar.copyView({ title: 'Film Kanban' }); |
||||
await dashboard.viewSidebar.verifyView({ |
||||
title: 'Kanban-1', |
||||
index: 2, |
||||
}); |
||||
const kanban = dashboard.kanban; |
||||
await kanban.verifyStackCount({ count: 6 }); |
||||
await kanban.verifyStackOrder({ |
||||
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'], |
||||
}); |
||||
await kanban.verifyStackFooter({ |
||||
count: [0, 4, 5, 8, 6, 6], |
||||
}); |
||||
await kanban.verifyCardCount({ |
||||
count: [0, 4, 5, 8, 6, 6], |
||||
}); |
||||
// verify card order
|
||||
const order2 = [ |
||||
['BAREFOOT MANCHURIAN', 'BARBARELLA STREETCAR'], |
||||
['WORST BANGER', 'PRESIDENT BANG'], |
||||
]; |
||||
for (let i = 1; i <= order2.length; i++) |
||||
await kanban.verifyCardOrder({ |
||||
stackIndex: i, |
||||
order: order2[i - 1], |
||||
}); |
||||
|
||||
await dashboard.viewSidebar.changeViewIcon({ |
||||
title: 'Kanban-1', |
||||
icon: 'american-football', |
||||
}); |
||||
|
||||
await dashboard.viewSidebar.deleteView({ title: 'Kanban-1' }); |
||||
///////////////////////////////////////////////
|
||||
|
||||
await dashboard.viewSidebar.openView({ title: 'Film Kanban' }); |
||||
|
||||
// add new stack
|
||||
await kanban.addNewStack({ title: 'Test' }); |
||||
await dashboard.rootPage.waitForTimeout(1000); |
||||
await kanban.verifyStackCount({ count: 7 }); |
||||
await kanban.verifyStackOrder({ |
||||
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17', 'Test'], |
||||
}); |
||||
|
||||
// collapse stack
|
||||
await kanban.verifyCollapseStackCount({ count: 0 }); |
||||
await kanban.collapseStack({ index: 0 }); |
||||
await kanban.verifyCollapseStackCount({ count: 1 }); |
||||
await kanban.expandStack({ index: 0 }); |
||||
await kanban.verifyCollapseStackCount({ count: 0 }); |
||||
|
||||
// add record to stack & verify
|
||||
await toolbar.fields.hideAll(); |
||||
await toolbar.fields.toggleShowSystemFields(); |
||||
await toolbar.fields.toggle({ title: 'LanguageId' }); |
||||
await toolbar.fields.toggle({ title: 'Title' }); |
||||
await toolbar.sort.reset(); |
||||
await toolbar.filter.reset(); |
||||
|
||||
await kanban.addCard({ stackIndex: 6 }); |
||||
await dashboard.expandedForm.fillField({ |
||||
columnTitle: 'Title', |
||||
value: 'New record', |
||||
}); |
||||
await dashboard.expandedForm.fillField({ |
||||
columnTitle: 'LanguageId', |
||||
value: '1', |
||||
}); |
||||
// todo: Check why kanban doesnt reload the rows data
|
||||
await dashboard.expandedForm.save({ waitForRowsData: false }); |
||||
|
||||
await kanban.verifyStackCount({ count: 7 }); |
||||
await kanban.verifyStackOrder({ |
||||
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17', 'Test'], |
||||
}); |
||||
await kanban.verifyCardCount({ |
||||
count: [0, 25, 25, 25, 25, 25, 1], |
||||
}); |
||||
|
||||
// delete stack
|
||||
await kanban.deleteStack({ index: 6 }); |
||||
await dashboard.rootPage.waitForTimeout(1000); |
||||
await kanban.verifyStackCount({ count: 6 }); |
||||
await kanban.verifyStackOrder({ |
||||
order: ['Uncategorized', 'G', 'PG', 'PG-13', 'R', 'NC-17'], |
||||
}); |
||||
await kanban.verifyCardCount({ |
||||
count: [1, 25, 25, 25, 25, 25], |
||||
}); |
||||
}); |
||||
|
||||
test('Kanban shared view operations', async ({ page }) => { |
||||
test.slow(); |
||||
|
||||
await dashboard.viewSidebar.createKanbanView({ |
||||
title: 'Film Kanban', |
||||
}); |
||||
await dashboard.viewSidebar.verifyView({ |
||||
title: 'Film Kanban', |
||||
index: 1, |
||||
}); |
||||
|
||||
// Share view
|
||||
await toolbar.fields.toggle({ title: 'Rating' }); |
||||
await toolbar.clickShareView(); |
||||
const sharedLink = await toolbar.shareView.getShareLink(); |
||||
await toolbar.shareView.close(); |
||||
|
||||
// sign-out
|
||||
await dashboard.signOut(); |
||||
|
||||
// Open shared view & verify stack count
|
||||
await page.goto(sharedLink); |
||||
const kanban = dashboard.kanban; |
||||
await kanban.verifyStackCount({ count: 6 }); |
||||
}); |
||||
}); |
Loading…
Reference in new issue