mirror of https://github.com/nocodb/nocodb
Raju Udava
1 year ago
committed by
GitHub
16 changed files with 1252 additions and 22 deletions
@ -0,0 +1,508 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import type { TableType, ViewType } from 'nocodb-sdk' |
||||||
|
import { RelationTypes, UITypes, isSystemColumn, isVirtualCol } from 'nocodb-sdk' |
||||||
|
import Draggable from 'vuedraggable' |
||||||
|
import { |
||||||
|
CellClickHookInj, |
||||||
|
IsExpandedFormOpenInj, |
||||||
|
IsFormInj, |
||||||
|
MetaInj, |
||||||
|
PaginationDataInj, |
||||||
|
provide, |
||||||
|
ref, |
||||||
|
toRef, |
||||||
|
useVModel, |
||||||
|
} from '#imports' |
||||||
|
import type { Row } from '~/lib' |
||||||
|
|
||||||
|
interface Props { |
||||||
|
modelValue: boolean |
||||||
|
meta: TableType |
||||||
|
view?: ViewType |
||||||
|
bulkUpdateRows?: Function |
||||||
|
bulkUpdateView?: Function |
||||||
|
selectedAllRecords?: boolean |
||||||
|
rows?: Row[] |
||||||
|
} |
||||||
|
|
||||||
|
const props = defineProps<Props>() |
||||||
|
|
||||||
|
const emits = defineEmits(['update:modelValue', 'cancel']) |
||||||
|
|
||||||
|
enum BulkUpdateMode { |
||||||
|
ALL = 0, |
||||||
|
SELECTED = 1, |
||||||
|
} |
||||||
|
|
||||||
|
const meta = toRef(props, 'meta') |
||||||
|
|
||||||
|
const isExpanded = useVModel(props, 'modelValue', emits, { |
||||||
|
defaultValue: false, |
||||||
|
}) |
||||||
|
|
||||||
|
// override cell click hook to avoid unexpected behavior at form fields |
||||||
|
provide(CellClickHookInj, null) |
||||||
|
|
||||||
|
provide(MetaInj, meta) |
||||||
|
|
||||||
|
provide(IsFormInj, ref(true)) |
||||||
|
|
||||||
|
provide(IsExpandedFormOpenInj, isExpanded) |
||||||
|
|
||||||
|
const formState: Record<string, any> = reactive({}) |
||||||
|
|
||||||
|
const updateMode = ref(BulkUpdateMode.ALL) |
||||||
|
|
||||||
|
const moved = ref(false) |
||||||
|
|
||||||
|
const drag = ref(false) |
||||||
|
|
||||||
|
const editColumns = ref<Record<string, any>[]>([]) |
||||||
|
|
||||||
|
const tempRow = ref<Row>({ |
||||||
|
row: {}, |
||||||
|
oldRow: {}, |
||||||
|
rowMeta: {}, |
||||||
|
}) |
||||||
|
|
||||||
|
useProvideSmartsheetRowStore(meta, tempRow) |
||||||
|
|
||||||
|
const fields = computed(() => { |
||||||
|
return (meta.value.columns ?? []).filter( |
||||||
|
(col) => |
||||||
|
!isSystemColumn(col) && |
||||||
|
!isVirtualCol(col) && |
||||||
|
!col.pk && |
||||||
|
!col.unique && |
||||||
|
editColumns.value.find((c) => c.id === col.id) === undefined, |
||||||
|
) |
||||||
|
}) |
||||||
|
|
||||||
|
const paginatedData = inject(PaginationDataInj)! |
||||||
|
|
||||||
|
const editCount = computed(() => { |
||||||
|
if (updateMode.value === BulkUpdateMode.SELECTED) { |
||||||
|
return props.rows!.length |
||||||
|
} else { |
||||||
|
return paginatedData.value?.totalRows ?? Infinity |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
function isRequired(_columnObj: Record<string, any>, required = false) { |
||||||
|
let columnObj = _columnObj |
||||||
|
if ( |
||||||
|
columnObj.uidt === UITypes.LinkToAnotherRecord && |
||||||
|
columnObj.colOptions && |
||||||
|
columnObj.colOptions.type === RelationTypes.BELONGS_TO |
||||||
|
) { |
||||||
|
columnObj = (meta?.value?.columns || []).find( |
||||||
|
(c: Record<string, any>) => c.id === columnObj.colOptions.fk_child_column_id, |
||||||
|
) as Record<string, any> |
||||||
|
} |
||||||
|
|
||||||
|
return required || (columnObj && columnObj.rqd && !columnObj.cdf) |
||||||
|
} |
||||||
|
|
||||||
|
function onMove(event: any) { |
||||||
|
const { element } = event.added || event.moved || event.removed |
||||||
|
|
||||||
|
if (event.added) { |
||||||
|
if (editColumns.value.find((c) => c.id === element.id)) { |
||||||
|
return |
||||||
|
} |
||||||
|
editColumns.value.push(element) |
||||||
|
formState[element.title] = null |
||||||
|
} |
||||||
|
|
||||||
|
if (event.removed) { |
||||||
|
delete formState[element.title] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function handleMouseUp(col: Record<string, any>) { |
||||||
|
if (!moved.value) { |
||||||
|
if (editColumns.value.find((c) => c.id === col.id)) { |
||||||
|
return |
||||||
|
} |
||||||
|
editColumns.value.push(col) |
||||||
|
formState[col.title] = null |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function handleRemove(col: Record<string, any>) { |
||||||
|
const index = editColumns.value.findIndex((c) => c.id === col.id) |
||||||
|
if (index > -1) { |
||||||
|
editColumns.value.splice(index, 1) |
||||||
|
delete formState[col.title] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const save = () => { |
||||||
|
Modal.confirm({ |
||||||
|
title: |
||||||
|
updateMode.value === BulkUpdateMode.SELECTED |
||||||
|
? `Do you want to update selected ${editCount.value} records?` |
||||||
|
: h('div', {}, [ |
||||||
|
`Do you want to update all ${editCount.value} records in current view?`, |
||||||
|
h('br'), |
||||||
|
h('div', { class: 'text-gray-500 text-xs mt-2' }, `Note: Undo on bulk update ALL is not supported`), |
||||||
|
]), |
||||||
|
type: 'warn', |
||||||
|
onOk: async () => { |
||||||
|
if (updateMode.value === BulkUpdateMode.SELECTED) { |
||||||
|
if (props.rows && props.bulkUpdateRows) { |
||||||
|
const propsToUpdate = Object.keys(formState) |
||||||
|
for (const row of props.rows) { |
||||||
|
for (const prop of Object.keys(row.row)) { |
||||||
|
if (propsToUpdate.includes(prop)) { |
||||||
|
row.row[prop] = formState[prop] |
||||||
|
row.rowMeta.selected = false |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
await props.bulkUpdateRows(props.rows, propsToUpdate) |
||||||
|
} |
||||||
|
} else { |
||||||
|
if (props.bulkUpdateView) { |
||||||
|
await props.bulkUpdateView(formState) |
||||||
|
} |
||||||
|
} |
||||||
|
isExpanded.value = false |
||||||
|
}, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const addAllColumns = () => { |
||||||
|
for (const col of fields.value) { |
||||||
|
if (editColumns.value.find((c) => c.id === col.id)) { |
||||||
|
continue |
||||||
|
} |
||||||
|
if (!col || !col.title) continue |
||||||
|
editColumns.value.push(col) |
||||||
|
formState[col.title] = null |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const removeAllColumns = () => { |
||||||
|
for (const col of editColumns.value) { |
||||||
|
delete formState[col.title] |
||||||
|
} |
||||||
|
editColumns.value = [] |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
if (!props.selectedAllRecords && !props.rows) { |
||||||
|
isExpanded.value = false |
||||||
|
return |
||||||
|
} |
||||||
|
if (props.selectedAllRecords && props.selectedAllRecords === true) { |
||||||
|
updateMode.value = BulkUpdateMode.ALL |
||||||
|
} else { |
||||||
|
if (props.rows && props.rows.length) { |
||||||
|
updateMode.value = BulkUpdateMode.SELECTED |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<a-drawer |
||||||
|
v-model:visible="isExpanded" |
||||||
|
:footer="null" |
||||||
|
width="min(90vw,900px)" |
||||||
|
:body-style="{ 'padding': 0, 'display': 'flex', 'flex-direction': 'column' }" |
||||||
|
:closable="false" |
||||||
|
class="nc-drawer-bulk-update" |
||||||
|
:class="{ active: isExpanded }" |
||||||
|
> |
||||||
|
<div class="flex p-2 items-center gap-2 p-4 nc-bulk-update-header"> |
||||||
|
<h5 class="text-lg font-weight-medium flex items-center gap-1 mb-0 min-w-0 overflow-x-hidden truncate"> |
||||||
|
<GeneralTableIcon :style="{ color: iconColor }" :meta="meta" class="mx-2" /> |
||||||
|
|
||||||
|
<template v-if="meta"> |
||||||
|
{{ meta.title }} |
||||||
|
</template> |
||||||
|
<!-- TODO i18n --> |
||||||
|
<div>: Bulk Update ({{ editCount }} records)</div> |
||||||
|
</h5> |
||||||
|
|
||||||
|
<div class="flex-1" /> |
||||||
|
<a-button |
||||||
|
v-if="updateMode === BulkUpdateMode.ALL" |
||||||
|
class="nc-bulk-update-save-btn" |
||||||
|
type="primary" |
||||||
|
:disabled="!editColumns.length" |
||||||
|
@click="save" |
||||||
|
> |
||||||
|
<div class="flex items-center"> |
||||||
|
<component :is="iconMap.contentSaveExit" class="mr-1" /> |
||||||
|
<!-- TODO i18n --> |
||||||
|
Bulk Update All |
||||||
|
</div> |
||||||
|
</a-button> |
||||||
|
<a-button |
||||||
|
v-else-if="updateMode === BulkUpdateMode.SELECTED" |
||||||
|
class="nc-bulk-update-save-btn" |
||||||
|
type="primary" |
||||||
|
:disabled="!editColumns.length" |
||||||
|
@click="save" |
||||||
|
> |
||||||
|
<div class="flex items-center"> |
||||||
|
<component :is="iconMap.contentSaveStay" class="mr-1" /> |
||||||
|
<!-- TODO i18n --> |
||||||
|
Bulk Update Selected |
||||||
|
</div> |
||||||
|
</a-button> |
||||||
|
<a-dropdown> |
||||||
|
<component :is="iconMap.threeDotVertical" class="nc-icon-transition" /> |
||||||
|
<template #overlay> |
||||||
|
<a-menu> |
||||||
|
<a-menu-item @click="isExpanded = false"> |
||||||
|
<div v-e="['c:row-expand:delete']" class="py-2 flex gap-2 items-center"> |
||||||
|
<component |
||||||
|
:is="iconMap.closeCircle" |
||||||
|
class="nc-icon-transition cursor-pointer select-none nc-delete-row text-gray-500 mx-1 min-w-4" |
||||||
|
/> |
||||||
|
{{ $t('general.close') }} |
||||||
|
</div> |
||||||
|
</a-menu-item> |
||||||
|
</a-menu> |
||||||
|
</template> |
||||||
|
</a-dropdown> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="flex w-full !bg-gray-100 flex-1"> |
||||||
|
<div class="form w-2/3 p-4"> |
||||||
|
<Draggable |
||||||
|
ref="draggableRef" |
||||||
|
:list="editColumns" |
||||||
|
item-key="fk_column_id" |
||||||
|
draggable=".item" |
||||||
|
group="form-inputs" |
||||||
|
class="h-full" |
||||||
|
:move="onMoveCallback" |
||||||
|
@change="onMove($event)" |
||||||
|
@start="drag = true" |
||||||
|
@end="drag = false" |
||||||
|
> |
||||||
|
<template #item="{ element }"> |
||||||
|
<div |
||||||
|
class="color-transition nc-editable item cursor-pointer hover:(bg-primary bg-opacity-10 ring-1 ring-accent ring-opacity-100) px-4 lg:px-12 py-4 relative" |
||||||
|
:class="[`nc-bulk-update-drag-${element.title.replaceAll(' ', '')}`]" |
||||||
|
data-testid="nc-bulk-update-fields" |
||||||
|
> |
||||||
|
<div class="text-gray group absolute top-4 right-12"> |
||||||
|
<component |
||||||
|
:is="iconMap.eyeSlash" |
||||||
|
class="opacity-0 nc-field-remove-icon group-hover:text-red-500 cursor-pointer !text-xl" |
||||||
|
data-testid="nc-bulk-update-fields-remove-icon" |
||||||
|
@click="handleRemove(element)" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div> |
||||||
|
<LazySmartsheetHeaderVirtualCell |
||||||
|
v-if="isVirtualCol(element)" |
||||||
|
:column="{ ...element, title: element.label || element.title }" |
||||||
|
:required="isRequired(element, element.required)" |
||||||
|
:hide-menu="true" |
||||||
|
data-testid="nc-bulk-update-input-label" |
||||||
|
/> |
||||||
|
|
||||||
|
<LazySmartsheetHeaderCell |
||||||
|
v-else |
||||||
|
:column="{ ...element, title: element.label || element.title }" |
||||||
|
:required="isRequired(element, element.required)" |
||||||
|
:hide-menu="true" |
||||||
|
data-testid="nc-bulk-update-input-label" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<a-form-item |
||||||
|
v-if="isVirtualCol(element)" |
||||||
|
:name="element.title" |
||||||
|
class="!mb-0 nc-input-required-error" |
||||||
|
:rules="[ |
||||||
|
{ |
||||||
|
required: isRequired(element, element.required), |
||||||
|
message: `${element.label || element.title} is required`, |
||||||
|
}, |
||||||
|
]" |
||||||
|
> |
||||||
|
<LazySmartsheetVirtualCell |
||||||
|
v-model="formState[element.title]" |
||||||
|
class="nc-input" |
||||||
|
:class="`nc-bulk-update-input-${element.title.replaceAll(' ', '')}`" |
||||||
|
:data-testid="`nc-bulk-update-input-${element.title.replaceAll(' ', '')}`" |
||||||
|
:column="element" |
||||||
|
/> |
||||||
|
</a-form-item> |
||||||
|
|
||||||
|
<a-form-item |
||||||
|
v-else |
||||||
|
:name="element.title" |
||||||
|
class="!mb-0 nc-input-required-error" |
||||||
|
:rules="[ |
||||||
|
{ |
||||||
|
required: isRequired(element, element.required), |
||||||
|
message: `${element.label || element.title} is required`, |
||||||
|
}, |
||||||
|
]" |
||||||
|
> |
||||||
|
<LazySmartsheetDivDataCell class="!bg-white rounded px-1 min-h-[35px] flex items-center mt-2 relative"> |
||||||
|
<LazySmartsheetCell |
||||||
|
v-model="formState[element.title]" |
||||||
|
:data-testid="`nc-bulk-update-input-${element.title.replaceAll(' ', '')}`" |
||||||
|
:column="element" |
||||||
|
:edit-enabled="true" |
||||||
|
:active="true" |
||||||
|
/> |
||||||
|
</LazySmartsheetDivDataCell> |
||||||
|
</a-form-item> |
||||||
|
|
||||||
|
<div class="nc-bulk-update-help-text text-gray-500 text-xs" data-testid="nc-bulk-update-input-help-text-label"> |
||||||
|
{{ element.description }} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<template #footer> |
||||||
|
<div v-if="!editColumns.length" class="mt-4 border-dashed border-2 border-gray-400 py-3 text-gray-400 text-center"> |
||||||
|
<!-- TODO i18n --> |
||||||
|
Drag and drop fields here to edit |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
</Draggable> |
||||||
|
</div> |
||||||
|
<div class="nc-columns-drawer w-1/3 p-3 flex flex-col bg-[#eceff1]" :class="{ active: columnsDrawer }"> |
||||||
|
<div class="text-bold uppercase text-gray-500 font-weight-bold !mb-2"> |
||||||
|
<!-- TODO i18n --> |
||||||
|
Select columns to Edit |
||||||
|
</div> |
||||||
|
<div class="flex flex-wrap gap-2 mb-4"> |
||||||
|
<button |
||||||
|
v-if="fields.length > editColumns.length" |
||||||
|
type="button" |
||||||
|
class="nc-bulk-update-add-all color-transition bg-white transform hover:(text-primary ring-1 ring-primary ring-opacity-100) active:translate-y-[1px] px-2 py-1 shadow-md rounded" |
||||||
|
data-testid="nc-bulk-update-add-all" |
||||||
|
tabindex="-1" |
||||||
|
@click="addAllColumns" |
||||||
|
> |
||||||
|
<!-- Add all --> |
||||||
|
{{ $t('general.addAll') }} |
||||||
|
</button> |
||||||
|
|
||||||
|
<button |
||||||
|
v-if="editColumns.length" |
||||||
|
type="button" |
||||||
|
class="nc-bulk-update-remove-all color-transition bg-white transform hover:(text-primary ring-1 ring-primary ring-opacity-100) active:translate-y-[1px] px-2 py-1 shadow-md rounded" |
||||||
|
data-testid="nc-bulk-update-remove-all" |
||||||
|
tabindex="-1" |
||||||
|
@click="removeAllColumns" |
||||||
|
> |
||||||
|
<!-- Remove all --> |
||||||
|
{{ $t('general.removeAll') }} |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
|
||||||
|
<Draggable |
||||||
|
:list="fields" |
||||||
|
item-key="id" |
||||||
|
draggable=".item" |
||||||
|
group="form-inputs" |
||||||
|
class="flex flex-col gap-2 flex-1" |
||||||
|
@start="drag = true" |
||||||
|
@end="drag = false" |
||||||
|
> |
||||||
|
<template #item="{ element }"> |
||||||
|
<a-card |
||||||
|
size="small" |
||||||
|
class="!border-0 color-transition cursor-pointer item hover:(bg-primary ring-1 ring-accent ring-opacity-100) bg-opacity-10 !rounded !shadow-lg" |
||||||
|
:data-testid="`nc-bulk-update-hidden-column-${element.label || element.title}`" |
||||||
|
@mousedown="moved = false" |
||||||
|
@mousemove="moved = false" |
||||||
|
@mouseup="handleMouseUp(element)" |
||||||
|
> |
||||||
|
<div class="flex"> |
||||||
|
<div class="flex flex-1"> |
||||||
|
<LazySmartsheetHeaderVirtualCell |
||||||
|
v-if="isVirtualCol(element)" |
||||||
|
:column="{ ...element, title: element.label || element.title }" |
||||||
|
:required="isRequired(element, element.required)" |
||||||
|
:hide-menu="true" |
||||||
|
/> |
||||||
|
|
||||||
|
<LazySmartsheetHeaderCell |
||||||
|
v-else |
||||||
|
:column="{ ...element, title: element.label || element.title }" |
||||||
|
:required="isRequired(element, element.required)" |
||||||
|
:hide-menu="true" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</a-card> |
||||||
|
</template> |
||||||
|
</Draggable> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</a-drawer> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
:deep(input, select, textarea) { |
||||||
|
@apply !bg-white; |
||||||
|
} |
||||||
|
|
||||||
|
.nc-bulk-update-wrapper { |
||||||
|
max-height: max(calc(100vh - 65px), 600px); |
||||||
|
height: max-content !important; |
||||||
|
} |
||||||
|
|
||||||
|
.nc-editable:hover { |
||||||
|
:deep(.nc-field-remove-icon) { |
||||||
|
@apply opacity-100; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.nc-input { |
||||||
|
@apply appearance-none w-full !bg-white rounded px-2 py-2 my-2 border-solid border-1 border-primary border-opacity-50; |
||||||
|
|
||||||
|
:deep(input) { |
||||||
|
@apply !px-1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.form-meta-input::placeholder { |
||||||
|
@apply text-[#3d3d3d] italic; |
||||||
|
} |
||||||
|
|
||||||
|
.nc-bulk-update-input-label, |
||||||
|
.nc-bulk-update-input-help-text { |
||||||
|
&::placeholder { |
||||||
|
@apply !text-gray-500 !text-xs; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.nc-bulk-update-help-text, |
||||||
|
.nc-input-required-error { |
||||||
|
max-width: 100%; |
||||||
|
word-break: break-all; |
||||||
|
white-space: pre-line; |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.nc-cell-attachment) { |
||||||
|
@apply p-0; |
||||||
|
|
||||||
|
.nc-attachment-cell { |
||||||
|
@apply px-4 min-h-[75px] w-full h-full; |
||||||
|
|
||||||
|
.nc-attachment { |
||||||
|
@apply md: (w-[50px] h-[50px]) lg:(w-[75px] h-[75px]) min-h-[50px] min-w-[50px]; |
||||||
|
} |
||||||
|
|
||||||
|
.nc-attachment-cell-dropzone { |
||||||
|
@apply rounded bg-gray-400/75; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,192 @@ |
|||||||
|
import { expect, Locator } from '@playwright/test'; |
||||||
|
import BasePage from '../../Base'; |
||||||
|
import { DashboardPage } from '..'; |
||||||
|
import { DateTimeCellPageObject } from '../common/Cell/DateTimeCell'; |
||||||
|
import { getTextExcludeIconText } from '../../../tests/utils/general'; |
||||||
|
|
||||||
|
export class BulkUpdatePage extends BasePage { |
||||||
|
readonly dashboard: DashboardPage; |
||||||
|
readonly bulkUpdateButton: Locator; |
||||||
|
readonly formHeader: Locator; |
||||||
|
readonly columnsDrawer: Locator; |
||||||
|
readonly form: Locator; |
||||||
|
|
||||||
|
constructor(dashboard: DashboardPage) { |
||||||
|
super(dashboard.rootPage); |
||||||
|
this.dashboard = dashboard; |
||||||
|
this.bulkUpdateButton = this.dashboard.get().locator('.nc-bulk-update-save-btn'); |
||||||
|
this.formHeader = this.dashboard.get().locator('.nc-bulk-update-bulk-update-header'); |
||||||
|
this.columnsDrawer = this.dashboard.get().locator('.nc-columns-drawer'); |
||||||
|
this.form = this.dashboard.get().locator('div.form'); |
||||||
|
} |
||||||
|
|
||||||
|
get() { |
||||||
|
return this.dashboard.get().locator(`.nc-drawer-bulk-update`); |
||||||
|
} |
||||||
|
|
||||||
|
async close() { |
||||||
|
return this.dashboard.rootPage.keyboard.press('Escape'); |
||||||
|
} |
||||||
|
|
||||||
|
async getInactiveColumn(index: number) { |
||||||
|
const inactiveColumns = await this.columnsDrawer.locator('.ant-card'); |
||||||
|
return inactiveColumns.nth(index); |
||||||
|
} |
||||||
|
|
||||||
|
async getActiveColumn(index: number) { |
||||||
|
const activeColumns = await this.form.locator('[data-testid="nc-bulk-update-fields"]'); |
||||||
|
return activeColumns.nth(index); |
||||||
|
} |
||||||
|
|
||||||
|
async getInactiveColumns() { |
||||||
|
const inactiveColumns = await this.columnsDrawer.locator('.ant-card'); |
||||||
|
const inactiveColumnsCount = await inactiveColumns.count(); |
||||||
|
const inactiveColumnsTitles = []; |
||||||
|
// get title for each inactive column
|
||||||
|
for (let i = 0; i < inactiveColumnsCount; i++) { |
||||||
|
const title = await getTextExcludeIconText(inactiveColumns.nth(i).locator('.ant-card-body')); |
||||||
|
inactiveColumnsTitles.push(title); |
||||||
|
} |
||||||
|
|
||||||
|
return inactiveColumnsTitles; |
||||||
|
} |
||||||
|
|
||||||
|
async getActiveColumns() { |
||||||
|
const activeColumns = await this.form.locator('[data-testid="nc-bulk-update-fields"]'); |
||||||
|
const activeColumnsCount = await activeColumns.count(); |
||||||
|
const activeColumnsTitles = []; |
||||||
|
// get title for each active column
|
||||||
|
for (let i = 0; i < activeColumnsCount; i++) { |
||||||
|
const title = await getTextExcludeIconText(activeColumns.nth(i).locator('[data-testid="nc-bulk-update-input-label"]')); |
||||||
|
activeColumnsTitles.push(title); |
||||||
|
} |
||||||
|
|
||||||
|
return activeColumnsTitles; |
||||||
|
} |
||||||
|
|
||||||
|
async removeField(index: number) { |
||||||
|
const removeFieldButton = await this.form.locator('[data-testid="nc-bulk-update-fields"]'); |
||||||
|
const removeFieldButtonCount = await removeFieldButton.count(); |
||||||
|
await removeFieldButton.nth(index).locator('[data-testid="nc-bulk-update-fields-remove-icon"]').click(); |
||||||
|
const newRemoveFieldButtonCount = await removeFieldButton.count(); |
||||||
|
expect(newRemoveFieldButtonCount).toBe(removeFieldButtonCount - 1); |
||||||
|
} |
||||||
|
|
||||||
|
async addField(index: number) { |
||||||
|
const addFieldButton = await this.columnsDrawer.locator('.ant-card'); |
||||||
|
const addFieldButtonCount = await addFieldButton.count(); |
||||||
|
await addFieldButton.nth(index).click(); |
||||||
|
const newAddFieldButtonCount = await addFieldButton.count(); |
||||||
|
expect(newAddFieldButtonCount).toBe(addFieldButtonCount - 1); |
||||||
|
} |
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
async fillField({ columnTitle, value, type = 'text' }: { columnTitle: string; value: string; type?: string }) { |
||||||
|
let picker = null; |
||||||
|
const field = this.form.locator(`[data-testid="nc-bulk-update-input-${columnTitle}"]`); |
||||||
|
await field.scrollIntoViewIfNeeded(); |
||||||
|
await field.hover(); |
||||||
|
if (type !== 'checkbox' && type !== 'attachment') { |
||||||
|
await field.click(); |
||||||
|
} |
||||||
|
switch (type) { |
||||||
|
case 'text': |
||||||
|
await field.locator('input').waitFor(); |
||||||
|
await field.locator('input').fill(value); |
||||||
|
break; |
||||||
|
case 'longText': |
||||||
|
await field.locator('textarea').waitFor(); |
||||||
|
await field.locator('textarea').fill(value); |
||||||
|
break; |
||||||
|
case 'rating': |
||||||
|
await field |
||||||
|
.locator('.ant-rate-star') |
||||||
|
.nth(Number(value) - 1) |
||||||
|
.click(); |
||||||
|
break; |
||||||
|
case 'year': |
||||||
|
picker = this.rootPage.locator('.ant-picker-dropdown.active'); |
||||||
|
await picker.waitFor(); |
||||||
|
await picker.locator(`td[title="${value}"]`).click(); |
||||||
|
break; |
||||||
|
case 'time': |
||||||
|
picker = this.rootPage.locator('.ant-picker-dropdown.active'); |
||||||
|
await picker.waitFor(); |
||||||
|
// eslint-disable-next-line no-case-declarations
|
||||||
|
const time = value.split(':'); |
||||||
|
// eslint-disable-next-line no-case-declarations
|
||||||
|
const timePanel = picker.locator('.ant-picker-time-panel-column'); |
||||||
|
await timePanel.nth(0).locator('li').nth(+time[0]).click(); |
||||||
|
await timePanel.nth(1).locator('li').nth(+time[1]).click(); |
||||||
|
await picker.locator('.ant-picker-ok').click(); |
||||||
|
break; |
||||||
|
case 'singleSelect': |
||||||
|
picker = this.rootPage.locator('.ant-select-dropdown.active'); |
||||||
|
await picker.waitFor(); |
||||||
|
await picker.locator(`.nc-select-option-SingleSelect-${value}`).click(); |
||||||
|
break; |
||||||
|
case 'multiSelect': |
||||||
|
picker = this.rootPage.locator('.ant-select-dropdown.active'); |
||||||
|
await picker.waitFor(); |
||||||
|
for (const val of value.split(',')) { |
||||||
|
await picker.locator(`.nc-select-option-MultiSelect-${val}`).click(); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'checkbox': |
||||||
|
if (value === 'true') { |
||||||
|
await field.click(); |
||||||
|
} |
||||||
|
break; |
||||||
|
case 'attachment': |
||||||
|
// eslint-disable-next-line no-case-declarations
|
||||||
|
const attachFileAction = field.locator('[data-testid="attachment-cell-file-picker-button"]').click(); |
||||||
|
await this.attachFile({ filePickUIAction: attachFileAction, filePath: value }); |
||||||
|
break; |
||||||
|
case 'date': |
||||||
|
{ |
||||||
|
const values = value.split('-'); |
||||||
|
const { year, month, day } = { year: values[0], month: values[1], day: values[2] }; |
||||||
|
picker = this.rootPage.locator('.ant-picker-dropdown.active'); |
||||||
|
const monthBtn = picker.locator('.ant-picker-month-btn'); |
||||||
|
const yearBtn = picker.locator('.ant-picker-year-btn'); |
||||||
|
|
||||||
|
await yearBtn.click(); |
||||||
|
await picker.waitFor(); |
||||||
|
await picker.locator(`td[title="${year}"]`).click(); |
||||||
|
|
||||||
|
await monthBtn.click(); |
||||||
|
await picker.waitFor(); |
||||||
|
await picker.locator(`td[title="${year}-${month}"]`).click(); |
||||||
|
|
||||||
|
await picker.waitFor(); |
||||||
|
await picker.locator(`td[title="${year}-${month}-${day}"]`).click(); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
async save({ |
||||||
|
awaitResponse = true, |
||||||
|
}: { |
||||||
|
awaitResponse?: boolean; |
||||||
|
} = {}) { |
||||||
|
await this.bulkUpdateButton.click(); |
||||||
|
const confirmModal = await this.rootPage.locator('.ant-modal-confirm'); |
||||||
|
|
||||||
|
const saveRowAction = () => confirmModal.locator('.ant-btn-primary').click(); |
||||||
|
if (!awaitResponse) { |
||||||
|
await saveRowAction(); |
||||||
|
} else { |
||||||
|
await this.waitForResponse({ |
||||||
|
uiAction: saveRowAction, |
||||||
|
requestUrlPathToMatch: 'api/v1/db/data/noco/', |
||||||
|
httpMethodsToMatch: ['GET'], |
||||||
|
responseJsonMatcher: json => json['pageInfo'], |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
await this.get().waitFor({ state: 'hidden' }); |
||||||
|
await this.rootPage.locator('[data-testid="grid-load-spinner"]').waitFor({ state: 'hidden' }); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
import { CellPageObject } from '.'; |
||||||
|
import BasePage from '../../../Base'; |
||||||
|
import { expect } from '@playwright/test'; |
||||||
|
|
||||||
|
export class TimeCellPageObject extends BasePage { |
||||||
|
readonly cell: CellPageObject; |
||||||
|
|
||||||
|
constructor(cell: CellPageObject) { |
||||||
|
super(cell.rootPage); |
||||||
|
this.cell = cell; |
||||||
|
} |
||||||
|
|
||||||
|
get({ index, columnHeader }: { index?: number; columnHeader: string }) { |
||||||
|
return this.cell.get({ index, columnHeader }); |
||||||
|
} |
||||||
|
|
||||||
|
async verify({ index, columnHeader, value }: { index: number; columnHeader: string; value: string }) { |
||||||
|
const cell = await this.get({ index, columnHeader }); |
||||||
|
await cell.scrollIntoViewIfNeeded(); |
||||||
|
await cell.locator(`input[title="${value}"]`).waitFor({ state: 'visible' }); |
||||||
|
await expect(cell.locator(`[title="${value}"]`)).toBeVisible(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
import { CellPageObject } from '.'; |
||||||
|
import BasePage from '../../../Base'; |
||||||
|
import { expect } from '@playwright/test'; |
||||||
|
|
||||||
|
export class YearCellPageObject extends BasePage { |
||||||
|
readonly cell: CellPageObject; |
||||||
|
|
||||||
|
constructor(cell: CellPageObject) { |
||||||
|
super(cell.rootPage); |
||||||
|
this.cell = cell; |
||||||
|
} |
||||||
|
|
||||||
|
get({ index, columnHeader }: { index?: number; columnHeader: string }) { |
||||||
|
return this.cell.get({ index, columnHeader }); |
||||||
|
} |
||||||
|
|
||||||
|
async verify({ index, columnHeader, value }: { index: number; columnHeader: string; value: number }) { |
||||||
|
const cell = await this.get({ index, columnHeader }); |
||||||
|
await cell.scrollIntoViewIfNeeded(); |
||||||
|
await cell.locator(`input[title="${value}"]`).waitFor({ state: 'visible' }); |
||||||
|
await expect(cell.locator(`[title="${value}"]`)).toBeVisible(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,365 @@ |
|||||||
|
import { expect, test } from '@playwright/test'; |
||||||
|
import setup from '../../setup'; |
||||||
|
import { DashboardPage } from '../../pages/Dashboard'; |
||||||
|
import { Api } from 'nocodb-sdk'; |
||||||
|
import { createDemoTable } from '../../setup/demoTable'; |
||||||
|
import { BulkUpdatePage } from '../../pages/Dashboard/BulkUpdate'; |
||||||
|
|
||||||
|
let bulkUpdateForm: BulkUpdatePage; |
||||||
|
async function updateBulkFields(fields) { |
||||||
|
// move all fields to active
|
||||||
|
for (let i = 0; i < fields.length; i++) { |
||||||
|
await bulkUpdateForm.addField(0); |
||||||
|
} |
||||||
|
|
||||||
|
// fill all fields
|
||||||
|
for (let i = 0; i < fields.length; i++) { |
||||||
|
await bulkUpdateForm.fillField({ columnTitle: fields[i].title, value: fields[i].value, type: fields[i].type }); |
||||||
|
} |
||||||
|
|
||||||
|
// save form
|
||||||
|
await bulkUpdateForm.save({ awaitResponse: true }); |
||||||
|
} |
||||||
|
|
||||||
|
test.describe('Bulk update', () => { |
||||||
|
let dashboard: DashboardPage; |
||||||
|
let context: any; |
||||||
|
let api: Api<any>; |
||||||
|
let table; |
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => { |
||||||
|
context = await setup({ page, isEmptyProject: true }); |
||||||
|
dashboard = new DashboardPage(page, context.project); |
||||||
|
bulkUpdateForm = dashboard.bulkUpdateForm; |
||||||
|
|
||||||
|
api = new Api({ |
||||||
|
baseURL: `http://localhost:8080/`, |
||||||
|
headers: { |
||||||
|
'xc-auth': context.token, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
table = await createDemoTable({ context, type: 'textBased', recordCnt: 50 }); |
||||||
|
await page.reload(); |
||||||
|
|
||||||
|
await dashboard.treeView.openTable({ title: 'textBased' }); |
||||||
|
|
||||||
|
// Open bulk update form
|
||||||
|
await dashboard.grid.updateAll(); |
||||||
|
}); |
||||||
|
|
||||||
|
test('General- Click to add & remove', async () => { |
||||||
|
let inactiveColumns = await bulkUpdateForm.getInactiveColumns(); |
||||||
|
expect(inactiveColumns).toEqual(['SingleLineText', 'MultiLineText', 'Email', 'PhoneNumber', 'URL']); |
||||||
|
|
||||||
|
let activeColumns = await bulkUpdateForm.getActiveColumns(); |
||||||
|
expect(activeColumns).toEqual([]); |
||||||
|
|
||||||
|
await bulkUpdateForm.addField(0); |
||||||
|
await bulkUpdateForm.addField(0); |
||||||
|
|
||||||
|
inactiveColumns = await bulkUpdateForm.getInactiveColumns(); |
||||||
|
expect(inactiveColumns).toEqual(['Email', 'PhoneNumber', 'URL']); |
||||||
|
|
||||||
|
activeColumns = await bulkUpdateForm.getActiveColumns(); |
||||||
|
expect(activeColumns).toEqual(['SingleLineText', 'MultiLineText']); |
||||||
|
}); |
||||||
|
|
||||||
|
test('General- Drag drop', async () => { |
||||||
|
const src = await bulkUpdateForm.getInactiveColumn(0); |
||||||
|
const dst = await bulkUpdateForm.form; |
||||||
|
|
||||||
|
await src.dragTo(dst); |
||||||
|
expect(await bulkUpdateForm.getActiveColumns()).toEqual(['SingleLineText']); |
||||||
|
expect(await bulkUpdateForm.getInactiveColumns()).toEqual(['MultiLineText', 'Email', 'PhoneNumber', 'URL']); |
||||||
|
|
||||||
|
const src2 = await bulkUpdateForm.getActiveColumn(0); |
||||||
|
const dst2 = await bulkUpdateForm.columnsDrawer; |
||||||
|
|
||||||
|
await src2.dragTo(dst2); |
||||||
|
expect(await bulkUpdateForm.getActiveColumns()).toEqual([]); |
||||||
|
expect(await bulkUpdateForm.getInactiveColumns()).toEqual([ |
||||||
|
'SingleLineText', |
||||||
|
'MultiLineText', |
||||||
|
'Email', |
||||||
|
'PhoneNumber', |
||||||
|
'URL', |
||||||
|
]); |
||||||
|
}); |
||||||
|
|
||||||
|
test('Text based', async () => { |
||||||
|
const fields = [ |
||||||
|
{ title: 'SingleLineText', value: 'SingleLineText', type: 'text' }, |
||||||
|
{ title: 'Email', value: 'a@b.com', type: 'text' }, |
||||||
|
{ title: 'PhoneNumber', value: '987654321', type: 'text' }, |
||||||
|
{ title: 'URL', value: 'https://www.google.com', type: 'text' }, |
||||||
|
{ |
||||||
|
title: 'MultiLineText', |
||||||
|
value: 'Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. Long text. ', |
||||||
|
type: 'longText', |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
await updateBulkFields(fields); |
||||||
|
|
||||||
|
// verify data on grid
|
||||||
|
for (let i = 0; i < fields.length; i++) { |
||||||
|
await dashboard.grid.cell.verify({ index: 5, columnHeader: fields[i].title, value: fields[i].value }); |
||||||
|
} |
||||||
|
|
||||||
|
// verify api response
|
||||||
|
const updatedRecords = (await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 50 })).list; |
||||||
|
for (let i = 0; i < updatedRecords.length; i++) { |
||||||
|
for (let j = 0; j < fields.length; j++) { |
||||||
|
expect(updatedRecords[i][fields[j].title]).toEqual(fields[j].value); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
test.describe('Bulk update', () => { |
||||||
|
let dashboard: DashboardPage; |
||||||
|
let context: any; |
||||||
|
let api: Api<any>; |
||||||
|
let table; |
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => { |
||||||
|
context = await setup({ page, isEmptyProject: true }); |
||||||
|
dashboard = new DashboardPage(page, context.project); |
||||||
|
bulkUpdateForm = dashboard.bulkUpdateForm; |
||||||
|
|
||||||
|
api = new Api({ |
||||||
|
baseURL: `http://localhost:8080/`, |
||||||
|
headers: { |
||||||
|
'xc-auth': context.token, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
table = await createDemoTable({ context, type: 'numberBased', recordCnt: 50 }); |
||||||
|
await page.reload(); |
||||||
|
|
||||||
|
await dashboard.treeView.openTable({ title: 'numberBased' }); |
||||||
|
|
||||||
|
// Open bulk update form
|
||||||
|
await dashboard.grid.updateAll(); |
||||||
|
}); |
||||||
|
|
||||||
|
test('Number based', async () => { |
||||||
|
const fields = [ |
||||||
|
{ title: 'Number', value: '1', type: 'text' }, |
||||||
|
{ title: 'Decimal', value: '1.1', type: 'text' }, |
||||||
|
{ title: 'Currency', value: '1.1', type: 'text' }, |
||||||
|
{ title: 'Percent', value: '10', type: 'text' }, |
||||||
|
{ title: 'Duration', value: '16:40', type: 'text' }, |
||||||
|
{ title: 'Rating', value: '3', type: 'rating' }, |
||||||
|
{ title: 'Year', value: '2024', type: 'year' }, |
||||||
|
{ title: 'Time', value: '10:10', type: 'time' }, |
||||||
|
]; |
||||||
|
|
||||||
|
await updateBulkFields(fields); |
||||||
|
|
||||||
|
// verify data on grid
|
||||||
|
for (let i = 0; i < fields.length; i++) { |
||||||
|
if (fields[i].type === 'rating') { |
||||||
|
await dashboard.grid.cell.rating.verify({ index: 5, columnHeader: fields[i].title, rating: +fields[i].value }); |
||||||
|
} else if (fields[i].type === 'year') { |
||||||
|
await dashboard.grid.cell.year.verify({ index: 5, columnHeader: fields[i].title, value: +fields[i].value }); |
||||||
|
} else if (fields[i].type === 'time') { |
||||||
|
await dashboard.grid.cell.time.verify({ index: 5, columnHeader: fields[i].title, value: fields[i].value }); |
||||||
|
} else { |
||||||
|
await dashboard.grid.cell.verify({ index: 5, columnHeader: fields[i].title, value: fields[i].value }); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// verify api response
|
||||||
|
// duration in seconds
|
||||||
|
const APIResponse = [1, 1.1, 1.1, 10, 60000, 3, 2024, '10:10:00']; |
||||||
|
const updatedRecords = (await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 50 })).list; |
||||||
|
for (let i = 0; i < updatedRecords.length; i++) { |
||||||
|
for (let j = 0; j < fields.length; j++) { |
||||||
|
if (fields[j].title === 'Time') { |
||||||
|
expect(updatedRecords[i][fields[j].title]).toContain(APIResponse[j]); |
||||||
|
} else { |
||||||
|
expect(+updatedRecords[i][fields[j].title]).toEqual(APIResponse[j]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
test.describe('Bulk update', () => { |
||||||
|
let dashboard: DashboardPage; |
||||||
|
let context: any; |
||||||
|
let api: Api<any>; |
||||||
|
let table; |
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => { |
||||||
|
context = await setup({ page, isEmptyProject: true }); |
||||||
|
dashboard = new DashboardPage(page, context.project); |
||||||
|
bulkUpdateForm = dashboard.bulkUpdateForm; |
||||||
|
|
||||||
|
api = new Api({ |
||||||
|
baseURL: `http://localhost:8080/`, |
||||||
|
headers: { |
||||||
|
'xc-auth': context.token, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
table = await createDemoTable({ context, type: 'selectBased', recordCnt: 50 }); |
||||||
|
await page.reload(); |
||||||
|
|
||||||
|
await dashboard.treeView.openTable({ title: 'selectBased' }); |
||||||
|
|
||||||
|
// Open bulk update form
|
||||||
|
await dashboard.grid.updateAll(); |
||||||
|
}); |
||||||
|
|
||||||
|
test('Select based', async () => { |
||||||
|
const fields = [ |
||||||
|
{ title: 'SingleSelect', value: 'jan', type: 'singleSelect' }, |
||||||
|
{ title: 'MultiSelect', value: 'jan,feb,mar', type: 'multiSelect' }, |
||||||
|
]; |
||||||
|
|
||||||
|
await updateBulkFields(fields); |
||||||
|
|
||||||
|
// verify data on grid
|
||||||
|
const displayOptions = ['jan', 'feb', 'mar']; |
||||||
|
for (let i = 0; i < fields.length; i++) { |
||||||
|
if (fields[i].type === 'singleSelect') { |
||||||
|
await dashboard.grid.cell.selectOption.verify({ |
||||||
|
index: 5, |
||||||
|
columnHeader: fields[i].title, |
||||||
|
option: fields[i].value, |
||||||
|
}); |
||||||
|
} else { |
||||||
|
await dashboard.grid.cell.selectOption.verifyOptions({ |
||||||
|
index: 5, |
||||||
|
columnHeader: fields[i].title, |
||||||
|
options: displayOptions, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// verify api response
|
||||||
|
const updatedRecords = (await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 50 })).list; |
||||||
|
for (let i = 0; i < updatedRecords.length; i++) { |
||||||
|
for (let j = 0; j < fields.length; j++) { |
||||||
|
expect(updatedRecords[i][fields[j].title]).toContain(fields[j].value); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
test.describe('Bulk update', () => { |
||||||
|
let dashboard: DashboardPage; |
||||||
|
let context: any; |
||||||
|
let api: Api<any>; |
||||||
|
let table; |
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => { |
||||||
|
context = await setup({ page, isEmptyProject: true }); |
||||||
|
dashboard = new DashboardPage(page, context.project); |
||||||
|
bulkUpdateForm = dashboard.bulkUpdateForm; |
||||||
|
|
||||||
|
api = new Api({ |
||||||
|
baseURL: `http://localhost:8080/`, |
||||||
|
headers: { |
||||||
|
'xc-auth': context.token, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
table = await createDemoTable({ context, type: 'miscellaneous', recordCnt: 50 }); |
||||||
|
await page.reload(); |
||||||
|
|
||||||
|
await dashboard.treeView.openTable({ title: 'miscellaneous' }); |
||||||
|
|
||||||
|
// Open bulk update form
|
||||||
|
await dashboard.grid.updateAll(); |
||||||
|
}); |
||||||
|
|
||||||
|
test('Miscellaneous (Checkbox, attachment)', async () => { |
||||||
|
const fields = [ |
||||||
|
{ title: 'Checkbox', value: 'true', type: 'checkbox' }, |
||||||
|
{ title: 'Attachment', value: `${process.cwd()}/fixtures/sampleFiles/1.json`, type: 'attachment' }, |
||||||
|
]; |
||||||
|
|
||||||
|
await updateBulkFields(fields); |
||||||
|
|
||||||
|
// verify data on grid
|
||||||
|
for (let i = 0; i < fields.length; i++) { |
||||||
|
if (fields[i].type === 'checkbox') { |
||||||
|
await dashboard.grid.cell.checkbox.verifyChecked({ |
||||||
|
index: 5, |
||||||
|
columnHeader: fields[i].title, |
||||||
|
}); |
||||||
|
} else { |
||||||
|
await dashboard.grid.cell.attachment.verifyFileCount({ |
||||||
|
index: 5, |
||||||
|
columnHeader: fields[i].title, |
||||||
|
count: 1, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// verify api response
|
||||||
|
const updatedRecords = (await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 50 })).list; |
||||||
|
for (let i = 0; i < updatedRecords.length; i++) { |
||||||
|
for (let j = 0; j < fields.length; j++) { |
||||||
|
expect(+updatedRecords[i]['Checkbox']).toBe(1); |
||||||
|
expect(updatedRecords[i]['Attachment'][0].title).toBe('1.json'); |
||||||
|
expect(updatedRecords[i]['Attachment'][0].mimetype).toBe('application/json'); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
test.describe('Bulk update', () => { |
||||||
|
let dashboard: DashboardPage; |
||||||
|
let context: any; |
||||||
|
let api: Api<any>; |
||||||
|
let table; |
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => { |
||||||
|
context = await setup({ page, isEmptyProject: true }); |
||||||
|
dashboard = new DashboardPage(page, context.project); |
||||||
|
bulkUpdateForm = dashboard.bulkUpdateForm; |
||||||
|
|
||||||
|
api = new Api({ |
||||||
|
baseURL: `http://localhost:8080/`, |
||||||
|
headers: { |
||||||
|
'xc-auth': context.token, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
table = await createDemoTable({ context, type: 'dateTimeBased', recordCnt: 50 }); |
||||||
|
await page.reload(); |
||||||
|
|
||||||
|
await dashboard.treeView.openTable({ title: 'dateTimeBased' }); |
||||||
|
|
||||||
|
// Open bulk update form
|
||||||
|
await dashboard.grid.updateAll(); |
||||||
|
}); |
||||||
|
|
||||||
|
test('Date Time Based', async () => { |
||||||
|
const fields = [{ title: 'Date', value: '2024-08-04', type: 'date' }]; |
||||||
|
|
||||||
|
await updateBulkFields(fields); |
||||||
|
|
||||||
|
// verify data on grid
|
||||||
|
for (let i = 0; i < fields.length; i++) { |
||||||
|
await dashboard.grid.cell.date.verify({ |
||||||
|
index: 5, |
||||||
|
columnHeader: fields[i].title, |
||||||
|
date: fields[i].value, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// verify api response
|
||||||
|
const updatedRecords = (await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 50 })).list; |
||||||
|
for (let i = 0; i < updatedRecords.length; i++) { |
||||||
|
for (let j = 0; j < fields.length; j++) { |
||||||
|
expect(updatedRecords[i]['Date']).toBe(fields[j].value); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
}); |
Loading…
Reference in new issue