mirror of https://github.com/nocodb/nocodb
Pranav C
2 years ago
64 changed files with 2433 additions and 355 deletions
@ -0,0 +1,180 @@
|
||||
import type { Ref } from 'vue' |
||||
import rfdc from 'rfdc' |
||||
import type { ProjectType, TableType, ViewType } from 'nocodb-sdk' |
||||
import { createSharedComposable, ref, useRouter } from '#imports' |
||||
import type { UndoRedoAction } from '~/lib' |
||||
|
||||
export const useUndoRedo = createSharedComposable(() => { |
||||
const clone = rfdc() |
||||
|
||||
const router = useRouter() |
||||
|
||||
const route = $(router.currentRoute) |
||||
|
||||
// keys: projectType | projectId | type | title | viewTitle
|
||||
const scope = computed<{ key: string; param: string }[]>(() => { |
||||
const tempScope: { key: string; param: string }[] = [{ key: 'root', param: 'root' }] |
||||
for (const [key, param] of Object.entries(route.params)) { |
||||
if (Array.isArray(param)) { |
||||
tempScope.push({ key, param: param.join(',') }) |
||||
} else { |
||||
tempScope.push({ key, param }) |
||||
} |
||||
} |
||||
|
||||
return tempScope |
||||
}) |
||||
|
||||
const isSameScope = (sc: { key: string; param: string }[]) => { |
||||
return sc.every((s) => { |
||||
return scope.value.some( |
||||
// viewTitle is optional for default view
|
||||
(s2) => |
||||
(s.key === 'viewTitle' && s2.key === 'viewTitle' && s2.param === '') || (s.key === s2.key && s.param === s2.param), |
||||
) |
||||
}) |
||||
} |
||||
|
||||
const undoQueue: Ref<UndoRedoAction[]> = ref([]) |
||||
|
||||
const redoQueue: Ref<UndoRedoAction[]> = ref([]) |
||||
|
||||
const addUndo = (action: UndoRedoAction, fromRedo = false) => { |
||||
// remove all redo actions that are in the same scope
|
||||
if (!fromRedo) redoQueue.value = redoQueue.value.filter((a) => !isSameScope(a.scope || [])) |
||||
undoQueue.value.push(action) |
||||
} |
||||
|
||||
const addRedo = (action: UndoRedoAction) => { |
||||
redoQueue.value.push(action) |
||||
} |
||||
|
||||
const undo = async () => { |
||||
let actionIndex = -1 |
||||
for (let i = undoQueue.value.length - 1; i >= 0; i--) { |
||||
const elScope = undoQueue.value[i].scope || [{ key: 'root', param: 'root' }] |
||||
if (isSameScope(elScope)) { |
||||
actionIndex = i |
||||
break |
||||
} |
||||
} |
||||
|
||||
if (actionIndex === -1) return |
||||
|
||||
const action = undoQueue.value.splice(actionIndex, 1)[0] |
||||
if (action) { |
||||
try { |
||||
await action.undo.fn.apply(action, action.undo.args) |
||||
addRedo(action) |
||||
} catch (e) { |
||||
message.warn('Error while undoing action, it is skipped.') |
||||
} |
||||
} |
||||
} |
||||
|
||||
const redo = async () => { |
||||
let actionIndex = -1 |
||||
for (let i = redoQueue.value.length - 1; i >= 0; i--) { |
||||
const elScope = redoQueue.value[i].scope || [{ key: 'root', param: 'root' }] |
||||
if (isSameScope(elScope)) { |
||||
actionIndex = i |
||||
break |
||||
} |
||||
} |
||||
|
||||
if (actionIndex === -1) return |
||||
|
||||
const action = redoQueue.value.splice(actionIndex, 1)[0] |
||||
if (action) { |
||||
try { |
||||
await action.redo.fn.apply(action, action.redo.args) |
||||
addUndo(action, true) |
||||
} catch (e) { |
||||
message.warn('Error while redoing action, it is skipped.') |
||||
} |
||||
} |
||||
} |
||||
|
||||
const defineRootScope = () => { |
||||
return [{ key: 'root', param: 'root' }] |
||||
} |
||||
|
||||
const defineProjectScope = (param: { project?: ProjectType; model?: TableType; view?: ViewType; project_id?: string }) => { |
||||
if (param.project) { |
||||
return [{ key: 'projectId', param: param.project.id! }] |
||||
} else if (param.model) { |
||||
return [{ key: 'projectId', param: param.model.project_id! }] |
||||
} else if (param.view) { |
||||
return [{ key: 'projectId', param: param.view.project_id! }] |
||||
} else { |
||||
return [{ key: 'projectId', param: param.project_id! }] |
||||
} |
||||
} |
||||
|
||||
const defineModelScope = (param: { model?: TableType; view?: ViewType; project_id?: string; model_id?: string }) => { |
||||
if (param.model) { |
||||
return [ |
||||
{ key: 'projectId', param: param.model.project_id! }, |
||||
{ key: 'title', param: param.model.id! }, |
||||
] |
||||
} else if (param.view) { |
||||
return [ |
||||
{ key: 'projectId', param: param.view.project_id! }, |
||||
{ key: 'title', param: param.view.fk_model_id! }, |
||||
] |
||||
} else { |
||||
return [ |
||||
{ key: 'projectId', param: param.project_id! }, |
||||
{ key: 'title', param: param.model_id! }, |
||||
] |
||||
} |
||||
} |
||||
|
||||
const defineViewScope = (param: { view?: ViewType; project_id?: string; model_id?: string; title?: string }) => { |
||||
if (param.view) { |
||||
return [ |
||||
{ key: 'projectId', param: param.view.project_id! }, |
||||
{ key: 'title', param: param.view.fk_model_id! }, |
||||
{ key: 'viewTitle', param: param.view.title! }, |
||||
] |
||||
} else { |
||||
return [ |
||||
{ key: 'projectId', param: param.project_id! }, |
||||
{ key: 'title', param: param.model_id! }, |
||||
{ key: 'viewTitle', param: param.title! }, |
||||
] |
||||
} |
||||
} |
||||
|
||||
useEventListener(document, 'keydown', async (e: KeyboardEvent) => { |
||||
const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey |
||||
if (cmdOrCtrl && !e.altKey) { |
||||
switch (e.keyCode) { |
||||
case 90: { |
||||
e.preventDefault() |
||||
// CMD + z and CMD + shift + z
|
||||
if (!e.shiftKey) { |
||||
if (undoQueue.value.length) { |
||||
undo() |
||||
} |
||||
} else { |
||||
if (redoQueue.value.length) { |
||||
redo() |
||||
} |
||||
} |
||||
break |
||||
} |
||||
} |
||||
} |
||||
}) |
||||
|
||||
return { |
||||
addUndo, |
||||
undo, |
||||
clone, |
||||
defineRootScope, |
||||
defineProjectScope, |
||||
defineModelScope, |
||||
defineViewScope, |
||||
} |
||||
}) |
@ -1 +0,0 @@
|
||||
declare module 'just-clone' |
@ -0,0 +1,684 @@
|
||||
import { expect, Page, test } from '@playwright/test'; |
||||
import { DashboardPage } from '../pages/Dashboard'; |
||||
import setup from '../setup'; |
||||
import { Api, UITypes } from 'nocodb-sdk'; |
||||
import { rowMixedValue } from '../setup/xcdb-records'; |
||||
import { GridPage } from '../pages/Dashboard/Grid'; |
||||
import { ToolbarPage } from '../pages/Dashboard/common/Toolbar'; |
||||
|
||||
let dashboard: DashboardPage, |
||||
grid: GridPage, |
||||
toolbar: ToolbarPage, |
||||
context: any, |
||||
api: Api<any>, |
||||
records: Record<string, any>, |
||||
table: any, |
||||
cityTable: any, |
||||
countryTable: any; |
||||
|
||||
const validateResponse = false; |
||||
|
||||
/** |
||||
This change provides undo/redo on multiple actions over UI. |
||||
|
||||
Scope Actions |
||||
------------------------------ |
||||
Row Create, Update, Delete |
||||
LTAR Link, Unlink |
||||
Fields Show/hide, Reorder |
||||
Sort Add, Update, Delete |
||||
Filters Add, Update, Delete (Excluding Filter Groups) |
||||
Row Height Update |
||||
Column width Update |
||||
View Rename |
||||
Table Rename |
||||
|
||||
**/ |
||||
async function undo({ page }: { page: Page }) { |
||||
const isMac = await grid.isMacOs(); |
||||
|
||||
if (validateResponse) { |
||||
await dashboard.grid.waitForResponse({ |
||||
uiAction: () => page.keyboard.press(isMac ? 'Meta+z' : 'Control+z'), |
||||
httpMethodsToMatch: ['GET'], |
||||
requestUrlPathToMatch: `/api/v1/db/data/noco/`, |
||||
responseJsonMatcher: json => json.pageInfo, |
||||
}); |
||||
} else { |
||||
await page.keyboard.press(isMac ? 'Meta+z' : 'Control+z'); |
||||
await page.waitForTimeout(100); |
||||
} |
||||
} |
||||
|
||||
test.describe('Undo Redo', () => { |
||||
test.beforeEach(async ({ page }) => { |
||||
context = await setup({ page, isEmptyProject: true }); |
||||
dashboard = new DashboardPage(page, context.project); |
||||
grid = dashboard.grid; |
||||
toolbar = dashboard.grid.toolbar; |
||||
|
||||
api = new Api({ |
||||
baseURL: `http://localhost:8080/`, |
||||
headers: { |
||||
'xc-auth': context.token, |
||||
}, |
||||
}); |
||||
|
||||
const columns = [ |
||||
{ |
||||
column_name: 'Id', |
||||
title: 'Id', |
||||
uidt: UITypes.ID, |
||||
}, |
||||
{ |
||||
column_name: 'Number', |
||||
title: 'Number', |
||||
uidt: UITypes.Number, |
||||
pv: true, |
||||
}, |
||||
{ |
||||
column_name: 'Decimal', |
||||
title: 'Decimal', |
||||
uidt: UITypes.Decimal, |
||||
}, |
||||
{ |
||||
column_name: 'Currency', |
||||
title: 'Currency', |
||||
uidt: UITypes.Currency, |
||||
}, |
||||
]; |
||||
|
||||
try { |
||||
const project = await api.project.read(context.project.id); |
||||
table = await api.base.tableCreate(context.project.id, project.bases?.[0].id, { |
||||
table_name: 'numberBased', |
||||
title: 'numberBased', |
||||
columns: columns, |
||||
}); |
||||
const rowAttributes = []; |
||||
for (let i = 0; i < 10; i++) { |
||||
const row = { |
||||
Number: rowMixedValue(columns[1], i), |
||||
Decimal: rowMixedValue(columns[2], i), |
||||
Currency: rowMixedValue(columns[3], i), |
||||
}; |
||||
rowAttributes.push(row); |
||||
} |
||||
|
||||
await api.dbTableRow.bulkCreate('noco', context.project.id, table.id, rowAttributes); |
||||
records = await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 100 }); |
||||
} catch (e) { |
||||
console.log(e); |
||||
} |
||||
|
||||
// reload page after api calls
|
||||
await page.reload(); |
||||
}); |
||||
|
||||
async function verifyRecords(values: any[] = []) { |
||||
// inserted values
|
||||
const expectedValues = [33, NaN, 456, 333, 267, 34, 8754, 3234, 44, 33, ...values]; |
||||
|
||||
const currentRecords: Record<string, any> = await api.dbTableRow.list('noco', context.project.id, table.id, { |
||||
fields: ['Number'], |
||||
limit: 100, |
||||
}); |
||||
|
||||
// verify if expectedValues are same as currentRecords
|
||||
expect(currentRecords.list.map(r => parseInt(r.Number))).toEqual(expectedValues); |
||||
|
||||
// verify row count
|
||||
await dashboard.grid.verifyTotalRowCount({ count: expectedValues.length }); |
||||
} |
||||
|
||||
test('Row: Create, Update, Delete', async ({ page }) => { |
||||
await dashboard.closeTab({ title: 'Team & Auth' }); |
||||
await dashboard.treeView.openTable({ title: 'numberBased' }); |
||||
|
||||
// Row.Create
|
||||
await grid.addNewRow({ index: 10, value: '333', columnHeader: 'Number', networkValidation: true }); |
||||
await grid.addNewRow({ index: 11, value: '444', columnHeader: 'Number', networkValidation: true }); |
||||
await verifyRecords([333, 444]); |
||||
|
||||
// Row.Update
|
||||
await grid.editRow({ index: 10, value: '555', columnHeader: 'Number', networkValidation: true }); |
||||
await grid.editRow({ index: 11, value: '666', columnHeader: 'Number', networkValidation: true }); |
||||
await verifyRecords([555, 666]); |
||||
|
||||
// Row.Delete
|
||||
await grid.deleteRow(10, 'Number'); |
||||
await grid.deleteRow(10, 'Number'); |
||||
await verifyRecords([]); |
||||
|
||||
// Undo : Row.Delete
|
||||
await undo({ page }); |
||||
await verifyRecords([666]); |
||||
await undo({ page }); |
||||
await verifyRecords([555, 666]); |
||||
|
||||
// Undo : Row.Update
|
||||
await undo({ page }); |
||||
await verifyRecords([555, 444]); |
||||
await undo({ page }); |
||||
await verifyRecords([333, 444]); |
||||
|
||||
// Undo : Row.Create
|
||||
await undo({ page }); |
||||
await verifyRecords([333]); |
||||
await undo({ page }); |
||||
await verifyRecords([]); |
||||
}); |
||||
|
||||
test('Fields: Hide, Show, Reorder', async ({ page }) => { |
||||
async function verifyFieldsOrder(fields: string[]) { |
||||
const fieldTitles = await toolbar.fields.getFieldsTitles(); |
||||
expect(fieldTitles).toEqual(fields); |
||||
} |
||||
|
||||
await dashboard.closeTab({ title: 'Team & Auth' }); |
||||
await dashboard.treeView.openTable({ title: 'numberBased' }); |
||||
|
||||
await verifyFieldsOrder(['Number', 'Decimal', 'Currency']); |
||||
|
||||
// Hide Decimal
|
||||
await toolbar.fields.toggle({ title: 'Decimal', isLocallySaved: false }); |
||||
await verifyFieldsOrder(['Number', 'Currency']); |
||||
|
||||
// Hide Currency
|
||||
await toolbar.fields.toggle({ title: 'Currency', isLocallySaved: false }); |
||||
await verifyFieldsOrder(['Number']); |
||||
|
||||
// Un hide Decimal
|
||||
await toolbar.fields.toggle({ title: 'Decimal', isLocallySaved: false }); |
||||
await verifyFieldsOrder(['Number', 'Decimal']); |
||||
|
||||
// Un hide Currency
|
||||
await toolbar.fields.toggle({ title: 'Currency', isLocallySaved: false }); |
||||
await verifyFieldsOrder(['Number', 'Decimal', 'Currency']); |
||||
|
||||
// Undo : un hide Currency
|
||||
await undo({ page }); |
||||
await verifyFieldsOrder(['Number', 'Decimal']); |
||||
|
||||
// Undo : un hide Decimal
|
||||
await undo({ page }); |
||||
await verifyFieldsOrder(['Number']); |
||||
|
||||
// Undo : hide Currency
|
||||
await undo({ page }); |
||||
await verifyFieldsOrder(['Number', 'Currency']); |
||||
|
||||
// Undo : hide Decimal
|
||||
await undo({ page }); |
||||
await verifyFieldsOrder(['Number', 'Decimal', 'Currency']); |
||||
|
||||
// reorder test
|
||||
await toolbar.fields.dragDropFields({ from: 1, to: 0 }); |
||||
await verifyFieldsOrder(['Number', 'Currency', 'Decimal']); |
||||
|
||||
// Undo : reorder
|
||||
await undo({ page }); |
||||
await verifyFieldsOrder(['Number', 'Decimal', 'Currency']); |
||||
}); |
||||
|
||||
test('Fields: Sort', async ({ page }) => { |
||||
await dashboard.closeTab({ title: 'Team & Auth' }); |
||||
await dashboard.treeView.openTable({ title: 'numberBased' }); |
||||
|
||||
async function verifyRecords({ sorted }: { sorted: boolean }) { |
||||
// inserted values
|
||||
const expectedSorted = [NaN, 33, 33, 34, 44, 267, 333, 456, 3234, 8754]; |
||||
const expectedUnsorted = [33, NaN, 456, 333, 267, 34, 8754, 3234, 44, 33]; |
||||
|
||||
const currentRecords: Record<string, any> = await api.dbTableRow.list('noco', context.project.id, table.id, { |
||||
fields: ['Number'], |
||||
limit: 100, |
||||
sort: sorted ? ['Number'] : [], |
||||
}); |
||||
|
||||
// verify if expectedValues are same as currentRecords
|
||||
expect(currentRecords.list.map(r => parseInt(r.Number))).toEqual(sorted ? expectedSorted : expectedUnsorted); |
||||
} |
||||
|
||||
await toolbar.sort.add({ title: 'Number', ascending: true, locallySaved: false }); |
||||
await verifyRecords({ sorted: true }); |
||||
await toolbar.sort.reset(); |
||||
await verifyRecords({ sorted: false }); |
||||
|
||||
await undo({ page }); |
||||
await verifyRecords({ sorted: true }); |
||||
await undo({ page }); |
||||
await verifyRecords({ sorted: false }); |
||||
}); |
||||
|
||||
test('Fields: Filter', async ({ page }) => { |
||||
await dashboard.closeTab({ title: 'Team & Auth' }); |
||||
await dashboard.treeView.openTable({ title: 'numberBased' }); |
||||
|
||||
async function verifyRecords({ filtered }: { filtered: boolean }) { |
||||
// inserted values
|
||||
const expectedFiltered = [33, 33]; |
||||
const expectedUnfiltered = [33, NaN, 456, 333, 267, 34, 8754, 3234, 44, 33]; |
||||
|
||||
const currentRecords: Record<string, any> = await api.dbTableRow.list('noco', context.project.id, table.id, { |
||||
fields: ['Number'], |
||||
limit: 100, |
||||
where: filtered ? '(Number,eq,33)' : '', |
||||
}); |
||||
|
||||
// verify if expectedValues are same as currentRecords
|
||||
expect(currentRecords.list.map(r => parseInt(r.Number))).toEqual( |
||||
filtered ? expectedFiltered : expectedUnfiltered |
||||
); |
||||
} |
||||
|
||||
await toolbar.clickFilter(); |
||||
await toolbar.filter.add({ title: 'Number', operation: '=', value: '33' }); |
||||
await toolbar.clickFilter(); |
||||
|
||||
await verifyRecords({ filtered: true }); |
||||
await toolbar.filter.reset(); |
||||
await verifyRecords({ filtered: false }); |
||||
|
||||
await undo({ page }); |
||||
await verifyRecords({ filtered: true }); |
||||
await undo({ page }); |
||||
await verifyRecords({ filtered: false }); |
||||
}); |
||||
|
||||
test('Row height', async ({ page }) => { |
||||
async function verifyRowHeight({ height }: { height: string }) { |
||||
await dashboard.grid.rowPage.getRecordHeight(0).then(readValue => { |
||||
expect(readValue).toBe(height); |
||||
}); |
||||
} |
||||
|
||||
// close 'Team & Auth' tab
|
||||
await dashboard.closeTab({ title: 'Team & Auth' }); |
||||
await dashboard.treeView.openTable({ title: 'numberBased' }); |
||||
|
||||
const timeOut = 200; |
||||
|
||||
await verifyRowHeight({ height: '1.5rem' }); |
||||
|
||||
// set row height & verify
|
||||
await toolbar.clickRowHeight(); |
||||
await toolbar.rowHeight.click({ title: 'Tall' }); |
||||
await new Promise(resolve => setTimeout(resolve, timeOut)); |
||||
await verifyRowHeight({ height: '6rem' }); |
||||
|
||||
await toolbar.clickRowHeight(); |
||||
await toolbar.rowHeight.click({ title: 'Medium' }); |
||||
await new Promise(resolve => setTimeout(resolve, timeOut)); |
||||
await verifyRowHeight({ height: '3rem' }); |
||||
|
||||
await undo({ page }); |
||||
await new Promise(resolve => setTimeout(resolve, timeOut)); |
||||
await verifyRowHeight({ height: '6rem' }); |
||||
|
||||
await undo({ page }); |
||||
await new Promise(resolve => setTimeout(resolve, timeOut)); |
||||
await verifyRowHeight({ height: '1.5rem' }); |
||||
}); |
||||
|
||||
test('Column width', async ({ page }) => { |
||||
// close 'Team & Auth' tab
|
||||
await dashboard.closeTab({ title: 'Team & Auth' }); |
||||
await dashboard.treeView.openTable({ title: 'numberBased' }); |
||||
|
||||
const originalWidth = await dashboard.grid.column.getWidth({ title: 'Number' }); |
||||
|
||||
await dashboard.grid.column.resize({ src: 'Number', dst: 'Decimal' }); |
||||
await dashboard.rootPage.waitForTimeout(100); |
||||
|
||||
const modifiedWidth = await dashboard.grid.column.getWidth({ title: 'Number' }); |
||||
expect(modifiedWidth).toBeGreaterThan(originalWidth); |
||||
|
||||
await undo({ page }); |
||||
expect(await dashboard.grid.column.getWidth({ title: 'Number' })).toBe(originalWidth); |
||||
}); |
||||
}); |
||||
|
||||
test.describe('Undo Redo - Table & view rename operations', () => { |
||||
test.beforeEach(async ({ page }) => { |
||||
context = await setup({ page, isEmptyProject: true }); |
||||
dashboard = new DashboardPage(page, context.project); |
||||
grid = dashboard.grid; |
||||
toolbar = dashboard.grid.toolbar; |
||||
|
||||
api = new Api({ |
||||
baseURL: `http://localhost:8080/`, |
||||
headers: { |
||||
'xc-auth': context.token, |
||||
}, |
||||
}); |
||||
|
||||
const columns = [ |
||||
{ |
||||
column_name: 'Id', |
||||
title: 'Id', |
||||
uidt: UITypes.ID, |
||||
}, |
||||
{ |
||||
column_name: 'Number', |
||||
title: 'Number', |
||||
uidt: UITypes.Number, |
||||
pv: true, |
||||
}, |
||||
{ |
||||
column_name: 'SingleSelect', |
||||
title: 'SingleSelect', |
||||
uidt: UITypes.SingleSelect, |
||||
dtxp: "'jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'", |
||||
}, |
||||
]; |
||||
|
||||
try { |
||||
const project = await api.project.read(context.project.id); |
||||
table = await api.base.tableCreate(context.project.id, project.bases?.[0].id, { |
||||
table_name: 'selectBased', |
||||
title: 'selectBased', |
||||
columns: columns, |
||||
}); |
||||
const rowAttributes = []; |
||||
for (let i = 0; i < 10; i++) { |
||||
const row = { |
||||
Number: rowMixedValue(columns[1], i), |
||||
SingleSelect: rowMixedValue(columns[2], i), |
||||
}; |
||||
rowAttributes.push(row); |
||||
} |
||||
|
||||
await api.dbTableRow.bulkCreate('noco', context.project.id, table.id, rowAttributes); |
||||
records = await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 100 }); |
||||
} catch (e) { |
||||
console.log(e); |
||||
} |
||||
|
||||
// reload page after api calls
|
||||
await page.reload(); |
||||
}); |
||||
|
||||
test('Table & View rename', async ({ page }) => { |
||||
// close 'Team & Auth' tab
|
||||
await dashboard.closeTab({ title: 'Team & Auth' }); |
||||
await dashboard.treeView.openTable({ title: 'selectBased' }); |
||||
|
||||
// table rename
|
||||
await dashboard.treeView.renameTable({ title: 'selectBased', newTitle: 'newNameForTest' }); |
||||
await dashboard.treeView.verifyTable({ title: 'newNameForTest' }); |
||||
await dashboard.rootPage.waitForTimeout(100); |
||||
|
||||
await undo({ page }); |
||||
await dashboard.rootPage.waitForTimeout(100); |
||||
await dashboard.treeView.verifyTable({ title: 'selectBased' }); |
||||
|
||||
// View rename
|
||||
const viewTypes = ['Grid', 'Gallery', 'Form', 'Kanban']; |
||||
for (let i = 0; i < viewTypes.length; i++) { |
||||
switch (viewTypes[i]) { |
||||
case 'Grid': |
||||
await dashboard.viewSidebar.createGridView({ |
||||
title: 'Grid', |
||||
}); |
||||
break; |
||||
case 'Gallery': |
||||
await dashboard.viewSidebar.createGalleryView({ |
||||
title: 'Gallery', |
||||
}); |
||||
break; |
||||
case 'Form': |
||||
await dashboard.viewSidebar.createFormView({ |
||||
title: 'Form', |
||||
}); |
||||
break; |
||||
case 'Kanban': |
||||
await dashboard.viewSidebar.createKanbanView({ |
||||
title: 'Kanban', |
||||
}); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
await dashboard.viewSidebar.renameView({ title: viewTypes[i], newTitle: 'newNameForTest' }); |
||||
await dashboard.viewSidebar.verifyView({ title: 'newNameForTest', index: 1 }); |
||||
await new Promise(resolve => setTimeout(resolve, 100)); |
||||
await undo({ page }); |
||||
await dashboard.viewSidebar.verifyView({ title: viewTypes[i], index: 1 }); |
||||
await dashboard.viewSidebar.deleteView({ title: viewTypes[i] }); |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
test.describe('Undo Redo - LTAR', () => { |
||||
test.beforeEach(async ({ page }) => { |
||||
context = await setup({ page, isEmptyProject: true }); |
||||
dashboard = new DashboardPage(page, context.project); |
||||
grid = dashboard.grid; |
||||
|
||||
api = new Api({ |
||||
baseURL: `http://localhost:8080/`, |
||||
headers: { |
||||
'xc-auth': context.token, |
||||
}, |
||||
}); |
||||
|
||||
const cityColumns = [ |
||||
{ |
||||
column_name: 'Id', |
||||
title: 'Id', |
||||
uidt: UITypes.ID, |
||||
}, |
||||
{ |
||||
column_name: 'City', |
||||
title: 'City', |
||||
uidt: UITypes.SingleLineText, |
||||
pv: true, |
||||
}, |
||||
]; |
||||
const countryColumns = [ |
||||
{ |
||||
column_name: 'Id', |
||||
title: 'Id', |
||||
uidt: UITypes.ID, |
||||
}, |
||||
{ |
||||
column_name: 'Country', |
||||
title: 'Country', |
||||
uidt: UITypes.SingleLineText, |
||||
pv: true, |
||||
}, |
||||
]; |
||||
|
||||
try { |
||||
const project = await api.project.read(context.project.id); |
||||
cityTable = await api.base.tableCreate(context.project.id, project.bases?.[0].id, { |
||||
table_name: 'City', |
||||
title: 'City', |
||||
columns: cityColumns, |
||||
}); |
||||
countryTable = await api.base.tableCreate(context.project.id, project.bases?.[0].id, { |
||||
table_name: 'Country', |
||||
title: 'Country', |
||||
columns: countryColumns, |
||||
}); |
||||
|
||||
const cityRowAttributes = [{ City: 'Mumbai' }, { City: 'Pune' }, { City: 'Delhi' }, { City: 'Bangalore' }]; |
||||
await api.dbTableRow.bulkCreate('noco', context.project.id, cityTable.id, cityRowAttributes); |
||||
|
||||
const countryRowAttributes = [ |
||||
{ Country: 'India' }, |
||||
{ Country: 'USA' }, |
||||
{ Country: 'UK' }, |
||||
{ Country: 'Australia' }, |
||||
]; |
||||
await api.dbTableRow.bulkCreate('noco', context.project.id, countryTable.id, countryRowAttributes); |
||||
|
||||
// create LTAR Country has-many City
|
||||
await api.dbTableColumn.create(countryTable.id, { |
||||
column_name: 'CityList', |
||||
title: 'CityList', |
||||
uidt: UITypes.LinkToAnotherRecord, |
||||
parentId: countryTable.id, |
||||
childId: cityTable.id, |
||||
type: 'hm', |
||||
}); |
||||
|
||||
// await api.dbTableRow.nestedAdd('noco', context.project.id, countryTable.id, '1', 'hm', 'CityList', '1');
|
||||
} catch (e) { |
||||
console.log(e); |
||||
} |
||||
|
||||
// reload page after api calls
|
||||
await page.reload(); |
||||
}); |
||||
|
||||
async function verifyRecords(values: any[] = []) { |
||||
// inserted values
|
||||
const expectedValues = [...values]; |
||||
|
||||
const currentRecords: Record<string, any> = await api.dbTableRow.list('noco', context.project.id, countryTable.id, { |
||||
fields: ['CityList'], |
||||
limit: 100, |
||||
}); |
||||
|
||||
// verify if expectedValues array includes all the values in currentRecords
|
||||
// currentRecords [ { Id: 1, City: 'Mumbai' }, { Id: 3, City: 'Delhi' } ]
|
||||
// expectedValues [ 'Mumbai', 'Delhi' ]
|
||||
currentRecords.list[0].CityList.forEach((record: any) => { |
||||
expect(expectedValues).toContain(record.City); |
||||
}); |
||||
expect(currentRecords.list[0].CityList.length).toBe(expectedValues.length); |
||||
} |
||||
|
||||
async function undo({ page, values }: { page: Page; values: string[] }) { |
||||
const isMac = await grid.isMacOs(); |
||||
await dashboard.grid.waitForResponse({ |
||||
uiAction: () => page.keyboard.press(isMac ? 'Meta+z' : 'Control+z'), |
||||
httpMethodsToMatch: ['GET'], |
||||
requestUrlPathToMatch: `/api/v1/db/data/noco/`, |
||||
responseJsonMatcher: json => json.pageInfo, |
||||
}); |
||||
await verifyRecords(values); |
||||
} |
||||
|
||||
test('Row: Link, Unlink', async ({ page }) => { |
||||
await dashboard.closeTab({ title: 'Team & Auth' }); |
||||
await dashboard.treeView.openTable({ title: 'Country' }); |
||||
|
||||
await grid.cell.inCellAdd({ index: 0, columnHeader: 'CityList' }); |
||||
await dashboard.linkRecord.select('Mumbai'); |
||||
|
||||
await grid.cell.inCellAdd({ index: 0, columnHeader: 'CityList' }); |
||||
await dashboard.linkRecord.select('Delhi'); |
||||
|
||||
await grid.cell.unlinkVirtualCell({ index: 0, columnHeader: 'CityList' }); |
||||
await grid.cell.unlinkVirtualCell({ index: 0, columnHeader: 'CityList' }); |
||||
|
||||
await verifyRecords([]); |
||||
await undo({ page, values: ['Delhi'] }); |
||||
await undo({ page, values: ['Mumbai', 'Delhi'] }); |
||||
await undo({ page, values: ['Mumbai'] }); |
||||
await undo({ page, values: [] }); |
||||
}); |
||||
}); |
||||
|
||||
test.describe('Undo Redo - Select based', () => { |
||||
test.beforeEach(async ({ page }) => { |
||||
context = await setup({ page, isEmptyProject: true }); |
||||
dashboard = new DashboardPage(page, context.project); |
||||
grid = dashboard.grid; |
||||
|
||||
api = new Api({ |
||||
baseURL: `http://localhost:8080/`, |
||||
headers: { |
||||
'xc-auth': context.token, |
||||
}, |
||||
}); |
||||
|
||||
const columns = [ |
||||
{ |
||||
column_name: 'Id', |
||||
title: 'Id', |
||||
uidt: UITypes.ID, |
||||
}, |
||||
{ |
||||
column_name: 'Title', |
||||
title: 'Title', |
||||
uidt: UITypes.SingleLineText, |
||||
pv: true, |
||||
}, |
||||
{ |
||||
column_name: 'select', |
||||
title: 'select', |
||||
uidt: UITypes.SingleSelect, |
||||
dtxp: "'jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'", |
||||
}, |
||||
]; |
||||
|
||||
try { |
||||
const project = await api.project.read(context.project.id); |
||||
table = await api.base.tableCreate(context.project.id, project.bases?.[0].id, { |
||||
table_name: 'selectSample', |
||||
title: 'selectSample', |
||||
columns, |
||||
}); |
||||
|
||||
const RowAttributes = [ |
||||
{ Title: 'Mumbai', select: 'jan' }, |
||||
{ Title: 'Pune', select: 'feb' }, |
||||
{ Title: 'Delhi', select: 'mar' }, |
||||
{ Title: 'Bangalore', select: 'jan' }, |
||||
]; |
||||
await api.dbTableRow.bulkCreate('noco', context.project.id, table.id, RowAttributes); |
||||
} catch (e) { |
||||
console.log(e); |
||||
} |
||||
|
||||
// reload page after api calls
|
||||
await page.reload(); |
||||
}); |
||||
|
||||
test.skip('Kanban', async ({ page }) => { |
||||
await dashboard.closeTab({ title: 'Team & Auth' }); |
||||
await dashboard.treeView.openTable({ title: 'selectSample' }); |
||||
|
||||
await dashboard.viewSidebar.createKanbanView({ |
||||
title: 'Kanban', |
||||
}); |
||||
|
||||
const kanban = dashboard.kanban; |
||||
|
||||
// Drag drop stack
|
||||
await kanban.verifyStackOrder({ |
||||
order: ['Uncategorized', 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'], |
||||
}); |
||||
// verify drag drop stack
|
||||
await kanban.dragDropStack({ |
||||
from: 1, // jan
|
||||
to: 2, // feb
|
||||
}); |
||||
await kanban.verifyStackOrder({ |
||||
order: ['Uncategorized', 'feb', 'jan', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'], |
||||
}); |
||||
// undo drag drop stack
|
||||
await undo({ page }); |
||||
await kanban.verifyStackOrder({ |
||||
order: ['Uncategorized', 'jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'], |
||||
}); |
||||
|
||||
// drag drop card
|
||||
await kanban.verifyCardCount({ |
||||
count: [0, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], |
||||
}); |
||||
await kanban.dragDropCard({ from: { stack: 1, card: 0 }, to: { stack: 2, card: 0 } }); |
||||
await kanban.verifyCardCount({ |
||||
count: [0, 1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], |
||||
}); |
||||
// undo drag drop card
|
||||
await undo({ page }); |
||||
await kanban.verifyCardCount({ |
||||
count: [0, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], |
||||
}); |
||||
}); |
||||
}); |
Loading…
Reference in new issue