Browse Source

Merge branch 'develop' into feat/ltar-rollup-on-creation

merge develop
pull/5848/head
Raju Udava 1 year ago
parent
commit
1646a5a37e
  1. 2
      packages/nc-gui/components/cell/Currency.vue
  2. 2
      packages/nc-gui/components/cell/DatePicker.vue
  3. 2
      packages/nc-gui/components/cell/DateTimePicker.vue
  4. 2
      packages/nc-gui/components/cell/Decimal.vue
  5. 2
      packages/nc-gui/components/cell/Duration.vue
  6. 2
      packages/nc-gui/components/cell/Email.vue
  7. 2
      packages/nc-gui/components/cell/Float.vue
  8. 9
      packages/nc-gui/components/cell/Integer.vue
  9. 2
      packages/nc-gui/components/cell/Percent.vue
  10. 2
      packages/nc-gui/components/cell/Text.vue
  11. 2
      packages/nc-gui/components/cell/TextArea.vue
  12. 2
      packages/nc-gui/components/cell/TimePicker.vue
  13. 2
      packages/nc-gui/components/cell/Url.vue
  14. 2
      packages/nc-gui/components/cell/YearPicker.vue
  15. 146
      packages/nc-gui/components/smartsheet/Grid.vue
  16. 2
      packages/nc-gui/components/smartsheet/TableDataCell.vue
  17. 18
      packages/nc-gui/composables/useMultiSelect/cellRange.ts
  18. 281
      packages/nc-gui/composables/useMultiSelect/index.ts
  19. 1
      tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts
  20. 1
      tests/playwright/pages/Dashboard/common/Cell/RatingCell.ts
  21. 4
      tests/playwright/pages/Dashboard/common/Cell/SelectOptionCell.ts
  22. 32
      tests/playwright/pages/Dashboard/common/Cell/TimeCell.ts
  23. 10
      tests/playwright/tests/db/columns/columnAttachments.spec.ts
  24. 6
      tests/playwright/tests/db/columns/columnBarcode.spec.ts
  25. 6
      tests/playwright/tests/db/columns/columnCheckbox.spec.ts
  26. 4
      tests/playwright/tests/db/columns/columnDateTime.spec.ts
  27. 4
      tests/playwright/tests/db/columns/columnDuration.spec.ts
  28. 6
      tests/playwright/tests/db/columns/columnFormula.spec.ts
  29. 6
      tests/playwright/tests/db/columns/columnGeoData.spec.ts
  30. 4
      tests/playwright/tests/db/columns/columnLinkToAnotherRecord.spec.ts
  31. 4
      tests/playwright/tests/db/columns/columnLookupRollup.spec.ts
  32. 8
      tests/playwright/tests/db/columns/columnLtarDragdrop.spec.ts
  33. 4
      tests/playwright/tests/db/columns/columnMenuOperations.spec.ts
  34. 8
      tests/playwright/tests/db/columns/columnMultiSelect.spec.ts
  35. 6
      tests/playwright/tests/db/columns/columnQrCode.spec.ts
  36. 6
      tests/playwright/tests/db/columns/columnRating.spec.ts
  37. 4
      tests/playwright/tests/db/columns/columnRelationalExtendedTests.spec.ts
  38. 8
      tests/playwright/tests/db/columns/columnSingleSelect.spec.ts
  39. 12
      tests/playwright/tests/db/features/baseShare.spec.ts
  40. 14
      tests/playwright/tests/db/features/erd.spec.ts
  41. 10
      tests/playwright/tests/db/features/expandedFormUrl.spec.ts
  42. 12
      tests/playwright/tests/db/features/filters.spec.ts
  43. 8
      tests/playwright/tests/db/features/findRowByScanner.spec.ts
  44. 10
      tests/playwright/tests/db/features/import.spec.ts
  45. 6
      tests/playwright/tests/db/features/keyboardShortcuts.spec.ts
  46. 8
      tests/playwright/tests/db/features/language.spec.ts
  47. 11
      tests/playwright/tests/db/features/metaLTAR.spec.ts
  48. 8
      tests/playwright/tests/db/features/metaSync.spec.ts
  49. 8
      tests/playwright/tests/db/features/mobileMode.spec.ts
  50. 4
      tests/playwright/tests/db/features/pagination.spec.ts
  51. 6
      tests/playwright/tests/db/features/swagger.spec.ts
  52. 12
      tests/playwright/tests/db/features/timezone.spec.ts
  53. 12
      tests/playwright/tests/db/features/undo-redo.spec.ts
  54. 12
      tests/playwright/tests/db/features/updateBulk.ts
  55. 258
      tests/playwright/tests/db/features/verticalFillHandle.spec.ts
  56. 12
      tests/playwright/tests/db/features/webhook.spec.ts
  57. 6
      tests/playwright/tests/db/general/cellSelection.spec.ts
  58. 2
      tests/playwright/tests/db/general/megaTable.spec.ts
  59. 16
      tests/playwright/tests/db/general/projectOperations.spec.ts
  60. 6
      tests/playwright/tests/db/general/tableColumnOperation.spec.ts
  61. 10
      tests/playwright/tests/db/general/tableOperations.spec.ts
  62. 6
      tests/playwright/tests/db/general/toolbarOperations.spec.ts
  63. 6
      tests/playwright/tests/db/general/viewMenu.spec.ts
  64. 6
      tests/playwright/tests/db/general/views.spec.ts
  65. 8
      tests/playwright/tests/db/users&Accounts/accountLicense.spec.ts
  66. 6
      tests/playwright/tests/db/users&Accounts/accountTokenManagement.spec.ts
  67. 12
      tests/playwright/tests/db/users&Accounts/accountUserManagement.spec.ts
  68. 10
      tests/playwright/tests/db/users&Accounts/accountUserSettings.spec.ts
  69. 16
      tests/playwright/tests/db/users&Accounts/authChangePassword.spec.ts
  70. 12
      tests/playwright/tests/db/users&Accounts/rolesCreate.spec.ts
  71. 8
      tests/playwright/tests/db/users&Accounts/rolesPreview.spec.ts
  72. 4
      tests/playwright/tests/db/users&Accounts/rolesSuperUser.spec.ts
  73. 12
      tests/playwright/tests/db/views/viewForm.spec.ts
  74. 6
      tests/playwright/tests/db/views/viewFormShareSurvey.spec.ts
  75. 6
      tests/playwright/tests/db/views/viewGridShare.spec.ts
  76. 8
      tests/playwright/tests/db/views/viewKanban.spec.ts
  77. 6
      tests/playwright/tests/db/views/viewMap.spec.ts

2
packages/nc-gui/components/cell/Currency.vue

@ -83,6 +83,8 @@ onMounted(() => {
@keydown.right.stop @keydown.right.stop
@keydown.up.stop @keydown.up.stop
@keydown.delete.stop @keydown.delete.stop
@keydown.ctrl.z.stop
@keydown.meta.z.stop
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
@contextmenu.stop @contextmenu.stop

2
packages/nc-gui/components/cell/DatePicker.vue

@ -70,6 +70,8 @@ watch(
(next) => { (next) => {
if (next) { if (next) {
onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false)) onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false))
} else {
editable.value = false
} }
}, },
{ flush: 'post' }, { flush: 'post' },

2
packages/nc-gui/components/cell/DateTimePicker.vue

@ -122,6 +122,8 @@ watch(
(next) => { (next) => {
if (next) { if (next) {
onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false)) onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false))
} else {
editable.value = false
} }
}, },
{ flush: 'post' }, { flush: 'post' },

2
packages/nc-gui/components/cell/Decimal.vue

@ -55,6 +55,8 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputEle
@keydown.right.stop @keydown.right.stop
@keydown.up.stop @keydown.up.stop
@keydown.delete.stop @keydown.delete.stop
@keydown.ctrl.z.stop
@keydown.meta.z.stop
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
/> />

2
packages/nc-gui/components/cell/Duration.vue

@ -96,6 +96,8 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputEle
@keydown.right.stop @keydown.right.stop
@keydown.up.stop @keydown.up.stop
@keydown.delete.stop @keydown.delete.stop
@keydown.ctrl.z.stop
@keydown.meta.z.stop
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
/> />

2
packages/nc-gui/components/cell/Email.vue

@ -64,6 +64,8 @@ watch(
@keydown.right.stop @keydown.right.stop
@keydown.up.stop @keydown.up.stop
@keydown.delete.stop @keydown.delete.stop
@keydown.ctrl.z.stop
@keydown.meta.z.stop
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
/> />

2
packages/nc-gui/components/cell/Float.vue

@ -55,6 +55,8 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputEle
@keydown.right.stop @keydown.right.stop
@keydown.up.stop @keydown.up.stop
@keydown.delete.stop @keydown.delete.stop
@keydown.ctrl.z.stop
@keydown.meta.z.stop
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
/> />

9
packages/nc-gui/components/cell/Integer.vue

@ -41,6 +41,15 @@ const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus() const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputElement)?.focus()
function onKeyDown(evt: KeyboardEvent) { function onKeyDown(evt: KeyboardEvent) {
const cmdOrCtrl = isMac() ? evt.metaKey : evt.ctrlKey
if (cmdOrCtrl && !evt.altKey) {
switch (evt.keyCode) {
case 90: {
evt.stopPropagation()
break
}
}
}
return evt.key === '.' && evt.preventDefault() return evt.key === '.' && evt.preventDefault()
} }
</script> </script>

2
packages/nc-gui/components/cell/Percent.vue

@ -46,6 +46,8 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputEle
@keydown.right.stop @keydown.right.stop
@keydown.up.stop @keydown.up.stop
@keydown.delete.stop @keydown.delete.stop
@keydown.ctrl.z.stop
@keydown.meta.z.stop
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
/> />

2
packages/nc-gui/components/cell/Text.vue

@ -38,6 +38,8 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLInputEle
@keydown.right.stop @keydown.right.stop
@keydown.up.stop @keydown.up.stop
@keydown.delete.stop @keydown.delete.stop
@keydown.ctrl.z.stop
@keydown.meta.z.stop
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
/> />

2
packages/nc-gui/components/cell/TextArea.vue

@ -37,6 +37,8 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && (el as HTMLTextArea
@keydown.right.stop @keydown.right.stop
@keydown.up.stop @keydown.up.stop
@keydown.delete.stop @keydown.delete.stop
@keydown.ctrl.z.stop
@keydown.meta.z.stop
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
/> />

2
packages/nc-gui/components/cell/TimePicker.vue

@ -69,6 +69,8 @@ watch(
(next) => { (next) => {
if (next) { if (next) {
onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false)) onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false))
} else {
editable.value = false
} }
}, },
{ flush: 'post' }, { flush: 'post' },

2
packages/nc-gui/components/cell/Url.vue

@ -93,6 +93,8 @@ watch(
@keydown.right.stop @keydown.right.stop
@keydown.up.stop @keydown.up.stop
@keydown.delete.stop @keydown.delete.stop
@keydown.ctrl.z.stop
@keydown.meta.z.stop
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
/> />

2
packages/nc-gui/components/cell/YearPicker.vue

@ -55,6 +55,8 @@ watch(
(next) => { (next) => {
if (next) { if (next) {
onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false)) onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, () => (open.value = false))
} else {
editable.value = false
} }
}, },
{ flush: 'post' }, { flush: 'post' },

146
packages/nc-gui/components/smartsheet/Grid.vue

@ -101,6 +101,8 @@ const contextMenu = computed({
}) })
const contextMenuClosing = ref(false) const contextMenuClosing = ref(false)
const scrolling = ref(false)
const bulkUpdateDlg = ref(false) const bulkUpdateDlg = ref(false)
const routeQuery = $computed(() => route.query as Record<string, string>) const routeQuery = $computed(() => route.query as Record<string, string>)
@ -111,6 +113,9 @@ const expandedFormRowState = ref<Record<string, any>>()
const gridWrapper = ref<HTMLElement>() const gridWrapper = ref<HTMLElement>()
const tableHeadEl = ref<HTMLElement>() const tableHeadEl = ref<HTMLElement>()
const tableBodyEl = ref<HTMLElement>() const tableBodyEl = ref<HTMLElement>()
const fillHandle = ref<HTMLElement>()
const gridRect = useElementBounding(gridWrapper)
const isAddingColumnAllowed = $computed(() => !readOnly.value && !isLocked.value && isUIAllowed('add-column') && !isSqlView.value) const isAddingColumnAllowed = $computed(() => !readOnly.value && !isLocked.value && isUIAllowed('add-column') && !isSqlView.value)
@ -209,6 +214,8 @@ const {
resetSelectedRange, resetSelectedRange,
makeActive, makeActive,
selectedRange, selectedRange,
isCellInFillRange,
isFillMode,
} = useMultiSelect( } = useMultiSelect(
meta, meta,
fields, fields,
@ -350,6 +357,7 @@ const {
await updateOrSaveRow(rowObj, ctx.updatedColumnTitle || columnObj.title) await updateOrSaveRow(rowObj, ctx.updatedColumnTitle || columnObj.title)
}, },
bulkUpdateRows, bulkUpdateRows,
fillHandle,
) )
function scrollToCell(row?: number | null, col?: number | null) { function scrollToCell(row?: number | null, col?: number | null) {
@ -651,10 +659,15 @@ useEventListener(document, 'keyup', async (e: KeyboardEvent) => {
} }
}) })
/** On clicking outside of table reset active cell */
const smartTable = ref(null) const smartTable = ref(null)
/** On clicking outside of table reset active cell */
onClickOutside(tableBodyEl, (e) => { onClickOutside(tableBodyEl, (e) => {
// do nothing if mousedown on the scrollbar (scrolling)
if (scrolling.value) {
return
}
// do nothing if context menu was open // do nothing if context menu was open
if (contextMenu.value) return if (contextMenu.value) return
@ -664,6 +677,9 @@ onClickOutside(tableBodyEl, (e) => {
if (editEnabled && (isVirtualCol(activeCol) || activeCol.uidt === UITypes.JSON)) return if (editEnabled && (isVirtualCol(activeCol) || activeCol.uidt === UITypes.JSON)) return
// skip if fill mode is active
if (isFillMode.value) return
// ignore unselecting if clicked inside or on the picker(Date, Time, DateTime, Year) // ignore unselecting if clicked inside or on the picker(Date, Time, DateTime, Year)
// or single/multi select options // or single/multi select options
const activePickerOrDropdownEl = document.querySelector( const activePickerOrDropdownEl = document.querySelector(
@ -865,6 +881,15 @@ const closeAddColumnDropdown = (scrollToLastCol = false) => {
const confirmDeleteRow = (row: number) => { const confirmDeleteRow = (row: number) => {
try { try {
deleteRow(row) deleteRow(row)
if (selectedRange.isRowInRange(row)) {
clearSelectedRange()
}
if (activeCell.row === row) {
activeCell.row = null
activeCell.col = null
}
} catch (e: any) { } catch (e: any) {
message.error(e.message) message.error(e.message)
} }
@ -883,10 +908,65 @@ function addEmptyRow(row?: number) {
nextTick().then(() => { nextTick().then(() => {
clearSelectedRange() clearSelectedRange()
makeActive(row ?? data.value.length - 1, 0) makeActive(row ?? data.value.length - 1, 0)
selectedRange.startRange({ row: activeCell.row!, col: activeCell.col! })
scrollToCell?.() scrollToCell?.()
}) })
return rowObj return rowObj
} }
const fillHandleTop = ref()
const fillHandleLeft = ref()
const cellRefs = ref<{ el: HTMLElement }[]>([])
const showFillHandle = computed(
() =>
!readOnly.value &&
!isLocked.value &&
!editEnabled &&
(!selectedRange.isEmpty() || (activeCell.row !== null && activeCell.col !== null)) &&
!data.value[(isNaN(selectedRange.end.row) ? activeCell.row : selectedRange.end.row) ?? -1]?.rowMeta?.new,
)
const refreshFillHandle = () => {
const cellRef = cellRefs.value.find(
(cell) =>
cell.el.dataset.rowIndex === String(isNaN(selectedRange.end.row) ? activeCell.row : selectedRange.end.row) &&
cell.el.dataset.colIndex === String(isNaN(selectedRange.end.col) ? activeCell.col : selectedRange.end.col),
)
if (cellRef) {
const cellRect = useElementBounding(cellRef.el)
if (!cellRect || !gridWrapper.value) return
fillHandleTop.value = cellRect.top.value + cellRect.height.value - gridRect.top.value + gridWrapper.value.scrollTop
fillHandleLeft.value = cellRect.left.value + cellRect.width.value - gridRect.left.value + gridWrapper.value.scrollLeft
}
}
watch(
[() => selectedRange.end.row, () => selectedRange.end.col, () => activeCell.row, () => activeCell.col],
([sr, sc, ar, ac], [osr, osc, oar, oac]) => {
if (sr !== osr || sc !== osc || ar !== oar || ac !== oac) {
refreshFillHandle()
}
},
)
useEventListener(gridWrapper, 'scroll', () => {
refreshFillHandle()
})
useEventListener(document, 'mousedown', (e) => {
if (e.offsetX > (e.target as HTMLElement)?.clientWidth || e.offsetY > (e.target as HTMLElement)?.clientHeight) {
scrolling.value = true
}
})
useEventListener(document, 'mouseup', () => {
// wait for click event to finish before setting scrolling to false
setTimeout(() => {
scrolling.value = false
}, 100)
})
</script> </script>
<template> <template>
@ -897,12 +977,13 @@ function addEmptyRow(row?: number) {
</div> </div>
</general-overlay> </general-overlay>
<div ref="gridWrapper" class="nc-grid-wrapper min-h-0 flex-1 scrollbar-thin-dull"> <div ref="gridWrapper" class="nc-grid-wrapper min-h-0 flex-1 scrollbar-thin-dull relative">
<a-dropdown <a-dropdown
v-model:visible="contextMenu" v-model:visible="contextMenu"
:trigger="isSqlView ? [] : ['contextmenu']" :trigger="isSqlView ? [] : ['contextmenu']"
overlay-class-name="nc-dropdown-grid-context-menu" overlay-class-name="nc-dropdown-grid-context-menu"
> >
<div class="table-overlay">
<table <table
ref="smartTable" ref="smartTable"
class="xc-row-table nc-grid backgroundColorDefault !h-auto bg-white" class="xc-row-table nc-grid backgroundColorDefault !h-auto bg-white"
@ -982,12 +1063,7 @@ function addEmptyRow(row?: number) {
:style="{ height: rowHeight ? `${rowHeight * 1.8}rem` : `1.8rem` }" :style="{ height: rowHeight ? `${rowHeight * 1.8}rem` : `1.8rem` }"
:data-testid="`grid-row-${rowIndex}`" :data-testid="`grid-row-${rowIndex}`"
> >
<td <td key="row-index" class="caption nc-grid-cell pl-5 pr-1" :data-testid="`cell-Id-${rowIndex}`">
key="row-index"
class="caption nc-grid-cell pl-5 pr-1"
:data-testid="`cell-Id-${rowIndex}`"
@contextmenu="contextMenuTarget = null"
>
<div class="items-center flex gap-1 min-w-[60px]"> <div class="items-center flex gap-1 min-w-[60px]">
<div <div
v-if="!readOnly || !isLocked" v-if="!readOnly || !isLocked"
@ -1043,18 +1119,26 @@ function addEmptyRow(row?: number) {
<SmartsheetTableDataCell <SmartsheetTableDataCell
v-for="(columnObj, colIndex) of fields" v-for="(columnObj, colIndex) of fields"
:key="columnObj.id" :key="columnObj.id"
ref="cellRefs"
class="cell relative nc-grid-cell" class="cell relative nc-grid-cell"
:class="{ :class="{
'cursor-pointer': hasEditPermission, 'cursor-pointer': hasEditPermission,
'active': hasEditPermission && isCellSelected(rowIndex, colIndex), 'active': hasEditPermission && isCellSelected(rowIndex, colIndex),
'active-cell':
hasEditPermission &&
((activeCell.row === rowIndex && activeCell.col === colIndex) ||
(selectedRange._start?.row === rowIndex && selectedRange._start?.col === colIndex)),
'nc-required-cell': isColumnRequiredAndNull(columnObj, row.row), 'nc-required-cell': isColumnRequiredAndNull(columnObj, row.row),
'align-middle': !rowHeight || rowHeight === 1, 'align-middle': !rowHeight || rowHeight === 1,
'align-top': rowHeight && rowHeight !== 1, 'align-top': rowHeight && rowHeight !== 1,
'filling': isCellInFillRange(rowIndex, colIndex),
}" }"
:data-testid="`cell-${columnObj.title}-${rowIndex}`" :data-testid="`cell-${columnObj.title}-${rowIndex}`"
:data-key="rowIndex + columnObj.id" :data-key="rowIndex + columnObj.id"
:data-col="columnObj.id" :data-col="columnObj.id"
:data-title="columnObj.title" :data-title="columnObj.title"
:data-row-index="rowIndex"
:data-col-index="colIndex"
@mousedown="handleMouseDown($event, rowIndex, colIndex)" @mousedown="handleMouseDown($event, rowIndex, colIndex)"
@mouseover="handleMouseOver($event, rowIndex, colIndex)" @mouseover="handleMouseOver($event, rowIndex, colIndex)"
@click="handleCellClick($event, rowIndex, colIndex)" @click="handleCellClick($event, rowIndex, colIndex)"
@ -1097,7 +1181,7 @@ function addEmptyRow(row?: number) {
<tr <tr
v-if="isAddingEmptyRowAllowed" v-if="isAddingEmptyRowAllowed"
v-e="['c:row:add:grid-bottom']" v-e="['c:row:add:grid-bottom']"
class="cursor-pointer" class="cursor-pointer relative z-3"
@mouseup.stop @mouseup.stop
@click="addEmptyRow()" @click="addEmptyRow()"
> >
@ -1111,6 +1195,20 @@ function addEmptyRow(row?: number) {
</tbody> </tbody>
</table> </table>
<!-- Fill Handle -->
<div
v-show="showFillHandle"
ref="fillHandle"
class="nc-fill-handle"
:class="
(!selectedRange.isEmpty() && selectedRange.end.col !== 0) || (selectedRange.isEmpty() && activeCell.col !== 0)
? 'z-3'
: 'z-4'
"
:style="{ top: `${fillHandleTop}px`, left: `${fillHandleLeft}px`, cursor: 'crosshair' }"
/>
</div>
<template v-if="!isLocked && hasEditPermission" #overlay> <template v-if="!isLocked && hasEditPermission" #overlay>
<a-menu class="shadow !rounded !py-0" @click="contextMenu = false"> <a-menu class="shadow !rounded !py-0" @click="contextMenu = false">
<a-menu-item <a-menu-item
@ -1291,9 +1389,29 @@ function addEmptyRow(row?: number) {
// todo: replace with css variable // todo: replace with css variable
td.active::after { td.active::after {
@apply text-primary border-current bg-primary bg-opacity-5;
}
td.active-cell::after {
@apply border-1 border-solid text-primary border-current bg-primary bg-opacity-5; @apply border-1 border-solid text-primary border-current bg-primary bg-opacity-5;
} }
td.filling::after {
content: '';
position: absolute;
z-index: 3;
height: calc(100% + 2px);
width: calc(100% + 2px);
left: -1px;
top: -1px;
pointer-events: none;
}
// todo: replace with css variable
td.filling::after {
@apply border-1 border-dashed text-primary border-current bg-gray-100 bg-opacity-50;
}
//td.active::before { //td.active::before {
// content: ''; // content: '';
// z-index:4; // z-index:4;
@ -1393,4 +1511,14 @@ tbody tr:hover {
.nc-required-cell { .nc-required-cell {
box-shadow: inset 0 0 2px #f00; box-shadow: inset 0 0 2px #f00;
} }
.nc-fill-handle {
@apply w-[6px] h-[6px] absolute rounded-full bg-red-500 !pointer-events-auto mt-[-4px] ml-[-4px];
}
.nc-fill-handle:hover,
.nc-fill-handle:active,
.nc-fill-handle:focus {
@apply w-[8px] h-[8px] mt-[-5px] ml-[-5px];
}
</style> </style>

2
packages/nc-gui/components/smartsheet/TableDataCell.vue

@ -8,6 +8,8 @@ const cellClickHook = createEventHook()
provide(CellClickHookInj, cellClickHook) provide(CellClickHookInj, cellClickHook)
provide(CurrentCellInj, el) provide(CurrentCellInj, el)
defineExpose({ el })
</script> </script>
<template> <template>

18
packages/nc-gui/composables/useMultiSelect/cellRange.ts

@ -24,6 +24,24 @@ export class CellRange {
return !this.isEmpty() && this._start?.row === this._end?.row return !this.isEmpty() && this._start?.row === this._end?.row
} }
isCellInRange(cell: Cell) {
return (
!this.isEmpty() &&
cell.row >= this.start.row &&
cell.row <= this.end.row &&
cell.col >= this.start.col &&
cell.col <= this.end.col
)
}
isRowInRange(row: number) {
return !this.isEmpty() && row >= this.start.row && row <= this.end.row
}
isColInRange(col: number) {
return !this.isEmpty() && col >= this.start.col && col <= this.end.col
}
get start(): Cell { get start(): Cell {
return { return {
row: Math.min(this._start?.row ?? NaN, this._end?.row ?? NaN), row: Math.min(this._start?.row ?? NaN, this._end?.row ?? NaN),

281
packages/nc-gui/composables/useMultiSelect/index.ts

@ -47,6 +47,7 @@ export function useMultiSelect(
keyEventHandler?: Function, keyEventHandler?: Function,
syncCellData?: Function, syncCellData?: Function,
bulkUpdateRows?: Function, bulkUpdateRows?: Function,
fillHandle?: MaybeRef<HTMLElement | undefined>,
) { ) {
const meta = ref(_meta) const meta = ref(_meta)
@ -58,14 +59,18 @@ export function useMultiSelect(
const { appInfo } = useGlobal() const { appInfo } = useGlobal()
const { isMysql } = useProject() const { isMysql, isPg } = useProject()
const editEnabled = ref(_editEnabled) const editEnabled = ref(_editEnabled)
let isMouseDown = $ref(false) const isMouseDown = ref(false)
const isFillMode = ref(false)
const selectedRange = reactive(new CellRange()) const selectedRange = reactive(new CellRange())
const fillRange = reactive(new CellRange())
const activeCell = reactive<Nullable<Cell>>({ row: null, col: null }) const activeCell = reactive<Nullable<Cell>>({ row: null, col: null })
const columnLength = $computed(() => unref(fields)?.length) const columnLength = $computed(() => unref(fields)?.length)
@ -119,7 +124,7 @@ export function useMultiSelect(
}) })
} }
if (columnObj.uidt === UITypes.DateTime || columnObj.uidt === UITypes.Time) { if (columnObj.uidt === UITypes.DateTime) {
// remove `"` // remove `"`
// e.g. "2023-05-12T08:03:53.000Z" -> 2023-05-12T08:03:53.000Z // e.g. "2023-05-12T08:03:53.000Z" -> 2023-05-12T08:03:53.000Z
textToCopy = textToCopy.replace(/["']/g, '') textToCopy = textToCopy.replace(/["']/g, '')
@ -138,16 +143,44 @@ export function useMultiSelect(
// users can change the datetime format in UI // users can change the datetime format in UI
// `textToCopy` would be always in YYYY-MM-DD HH:mm:ss(Z / +xx:yy) format // `textToCopy` would be always in YYYY-MM-DD HH:mm:ss(Z / +xx:yy) format
// therefore, here we reformat to the correct datetime format based on the meta // therefore, here we reformat to the correct datetime format based on the meta
textToCopy = d.format( textToCopy = d.format(constructDateTimeFormat(columnObj))
columnObj.uidt === UITypes.DateTime ? constructDateTimeFormat(columnObj) : constructTimeFormat(columnObj),
)
if (!dayjs(textToCopy).isValid()) { if (!dayjs(textToCopy).isValid()) {
// return empty string for invalid datetime / time // return empty string for invalid datetime
return '' return ''
} }
} }
if (columnObj.uidt === UITypes.Time) {
// remove `"`
// e.g. "2023-05-12T08:03:53.000Z" -> 2023-05-12T08:03:53.000Z
textToCopy = textToCopy.replace(/["']/g, '')
const isMySQL = isMysql(columnObj.base_id)
const isPostgres = isPg(columnObj.base_id)
let d = dayjs(textToCopy)
if (!d.isValid()) {
// insert a datetime value, copy the value without refreshing
// e.g. textToCopy = 2023-05-12T03:49:25.000Z
// feed custom parse format
d = dayjs(textToCopy, isMySQL ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ')
}
if (!d.isValid()) {
// MySQL and Postgres store time in HH:mm:ss format so we need to feed custom parse format
d = isMySQL || isPostgres ? dayjs(textToCopy, 'HH:mm:ss') : dayjs(textToCopy)
}
if (!d.isValid()) {
// return empty string for invalid time
return ''
}
textToCopy = d.format(constructTimeFormat(columnObj))
}
if (columnObj.uidt === UITypes.LongText) { if (columnObj.uidt === UITypes.LongText) {
textToCopy = `"${textToCopy.replace(/\"/g, '""')}"` textToCopy = `"${textToCopy.replace(/\"/g, '""')}"`
} }
@ -155,23 +188,33 @@ export function useMultiSelect(
return textToCopy return textToCopy
} }
const copyTable = async (rows: Row[], cols: ColumnType[]) => { const serializeRange = (rows: Row[], cols: ColumnType[]) => {
let copyHTML = '<table>' let html = '<table>'
let copyPlainText = '' let text = ''
const json: string[][] = []
rows.forEach((row, i) => { rows.forEach((row, i) => {
let copyRow = '<tr>' let copyRow = '<tr>'
const jsonRow: string[] = []
cols.forEach((col, i) => { cols.forEach((col, i) => {
const value = valueToCopy(row, col) const value = valueToCopy(row, col)
copyRow += `<td>${value}</td>` copyRow += `<td>${value}</td>`
copyPlainText = `${copyPlainText}${value}${cols.length - 1 !== i ? '\t' : ''}` text = `${text}${value}${cols.length - 1 !== i ? '\t' : ''}`
jsonRow.push(col.uidt === UITypes.LongText ? value.replace(/^"/, '').replace(/"$/, '').replace(/""/g, '"') : value)
}) })
copyHTML += `${copyRow}</tr>` html += `${copyRow}</tr>`
if (rows.length - 1 !== i) { if (rows.length - 1 !== i) {
copyPlainText = `${copyPlainText}\n` text = `${text}\n`
} }
json.push(jsonRow)
}) })
copyHTML += '</table>' html += '</table>'
return { html, text, json }
}
const copyTable = async (rows: Row[], cols: ColumnType[]) => {
const { html: copyHTML, text: copyPlainText } = serializeRange(rows, cols)
const blobHTML = new Blob([copyHTML], { type: 'text/html' }) const blobHTML = new Blob([copyHTML], { type: 'text/html' })
const blobPlainText = new Blob([copyPlainText], { type: 'text/plain' }) const blobPlainText = new Blob([copyPlainText], { type: 'text/plain' })
@ -213,20 +256,71 @@ export function useMultiSelect(
return true return true
} }
if (selectedRange.start === null || selectedRange.end === null) { return selectedRange.isCellInRange({ row, col })
}
function isCellInFillRange(row: number, col: number) {
if (fillRange._start === null || fillRange._end === null) {
return false return false
} }
return ( if (selectedRange.isCellInRange({ row, col })) {
col >= selectedRange.start.col && return false
col <= selectedRange.end.col && }
row >= selectedRange.start.row &&
row <= selectedRange.end.row return fillRange.isCellInRange({ row, col })
) }
const isPasteable = (row?: Row, col?: ColumnType, showInfo = false) => {
if (!row || !col) {
if (showInfo) {
message.info('Please select a cell to paste')
}
return false
}
// skip pasting virtual columns (including LTAR columns for now) and system columns
if (isVirtualCol(col) || isSystemColumn(col)) {
if (showInfo) {
message.info(t('msg.info.pasteNotSupported'))
}
return false
}
// skip pasting auto increment columns
if (col.ai) {
if (showInfo) {
message.info(t('msg.info.autoIncFieldNotEditable'))
}
return false
}
// skip pasting primary key columns
if (col.pk && !row.rowMeta.new) {
if (showInfo) {
message.info(t('msg.info.editingPKnotSupported'))
}
return false
}
return true
} }
function handleMouseOver(event: MouseEvent, row: number, col: number) { function handleMouseOver(event: MouseEvent, row: number, col: number) {
if (!isMouseDown) { if (isFillMode.value) {
const rw = unref(data)[row]
if (!selectedRange._start || !selectedRange._end) return
// fill is not supported for new rows yet
if (rw.rowMeta.new) return
fillRange.endRange({ row, col: selectedRange._end.col })
scrollToCell?.(row, col)
return
}
if (!isMouseDown.value) {
return return
} }
@ -248,7 +342,7 @@ export function useMultiSelect(
} }
// if edit is enabled, don't start the selection (some cells shrink after edit mode, which causes the selection to expand if flag is set) // if edit is enabled, don't start the selection (some cells shrink after edit mode, which causes the selection to expand if flag is set)
if (!editEnabled.value) isMouseDown = true if (!editEnabled.value) isMouseDown.value = true
contextMenu.value = false contextMenu.value = false
@ -286,8 +380,90 @@ export function useMultiSelect(
} }
const handleMouseUp = (_event: MouseEvent) => { const handleMouseUp = (_event: MouseEvent) => {
if (isMouseDown) { if (isFillMode.value) {
isMouseDown = false isFillMode.value = false
if (fillRange._start === null || fillRange._end === null) return
if (selectedRange._start !== null && selectedRange._end !== null) {
const tempActiveCell = { row: selectedRange._start.row, col: selectedRange._start.col }
const cprows = unref(data).slice(selectedRange.start.row, selectedRange.end.row + 1) // slice the selected rows for copy
const cpcols = unref(fields).slice(selectedRange.start.col, selectedRange.end.col + 1) // slice the selected cols for copy
const rawMatrix = serializeRange(cprows, cpcols).json
const fillDirection = fillRange._start.row <= fillRange._end.row ? 1 : -1
let fillIndex = fillDirection === 1 ? 0 : rawMatrix.length - 1
const rowsToPaste: Row[] = []
const propsToPaste: string[] = []
for (
let row = fillRange._start.row;
fillDirection === 1 ? row <= fillRange._end.row : row >= fillRange._end.row;
row += fillDirection
) {
if (isCellSelected(row, selectedRange.start.col)) {
continue
}
const rowObj = unref(data)[row]
let pasteIndex = 0
for (let col = fillRange.start.col; col <= fillRange.end.col; col++) {
const colObj = unref(fields)[col]
if (!isPasteable(rowObj, colObj)) {
pasteIndex++
continue
}
propsToPaste.push(colObj.title!)
const pasteValue = convertCellData(
{
value: rawMatrix[fillIndex][pasteIndex],
to: colObj.uidt as UITypes,
column: colObj,
appInfo: unref(appInfo),
},
isMysql(meta.value?.base_id),
true,
)
if (pasteValue !== undefined) {
rowObj.row[colObj.title!] = pasteValue
rowsToPaste.push(rowObj)
}
pasteIndex++
}
if (fillDirection === 1) {
fillIndex = fillIndex < rawMatrix.length - 1 ? fillIndex + 1 : 0
} else {
fillIndex = fillIndex >= 1 ? fillIndex - 1 : rawMatrix.length - 1
}
}
bulkUpdateRows?.(rowsToPaste, propsToPaste).then(() => {
if (fillRange._start === null || fillRange._end === null) return
selectedRange.startRange(tempActiveCell)
selectedRange.endRange(fillRange._end)
makeActive(tempActiveCell.row, tempActiveCell.col)
fillRange.clear()
})
} else {
fillRange.clear()
}
return
}
if (isMouseDown.value) {
isMouseDown.value = false
// timeout is needed, because we want to set cell as active AFTER all the child's click handler's called // timeout is needed, because we want to set cell as active AFTER all the child's click handler's called
// this is needed e.g. for date field edit, where two clicks had to be done - one to select cell, and another one to open date dropdown // this is needed e.g. for date field edit, where two clicks had to be done - one to select cell, and another one to open date dropdown
setTimeout(() => { setTimeout(() => {
@ -526,41 +702,6 @@ export function useMultiSelect(
const clearSelectedRange = selectedRange.clear.bind(selectedRange) const clearSelectedRange = selectedRange.clear.bind(selectedRange)
const isPasteable = (row?: Row, col?: ColumnType, showInfo = false) => {
if (!row || !col) {
if (showInfo) {
message.info('Please select a cell to paste')
}
return false
}
// skip pasting virtual columns (including LTAR columns for now) and system columns
if (isVirtualCol(col) || isSystemColumn(col)) {
if (showInfo) {
message.info(t('msg.info.pasteNotSupported'))
}
return false
}
// skip pasting auto increment columns
if (col.ai) {
if (showInfo) {
message.info(t('msg.info.autoIncFieldNotEditable'))
}
return false
}
// skip pasting primary key columns
if (col.pk && !row.rowMeta.new) {
if (showInfo) {
message.info(t('msg.info.editingPKnotSupported'))
}
return false
}
return true
}
const handlePaste = async (e: ClipboardEvent) => { const handlePaste = async (e: ClipboardEvent) => {
if (isDrawerOrModalExist()) { if (isDrawerOrModalExist()) {
return return
@ -747,10 +888,27 @@ export function useMultiSelect(
} }
} }
function fillHandleMouseDown(event: MouseEvent) {
if (event?.button !== MAIN_MOUSE_PRESSED) {
return
}
isFillMode.value = true
if (selectedRange._start && selectedRange._end) {
fillRange.startRange({ row: selectedRange._start?.row, col: selectedRange._start.col })
fillRange.endRange({ row: selectedRange._end?.row, col: selectedRange._end.col })
}
event.preventDefault()
}
useEventListener(document, 'keydown', handleKeyDown) useEventListener(document, 'keydown', handleKeyDown)
useEventListener(document, 'mouseup', handleMouseUp) useEventListener(document, 'mouseup', handleMouseUp)
useEventListener(document, 'paste', handlePaste) useEventListener(document, 'paste', handlePaste)
useEventListener(fillHandle, 'mousedown', fillHandleMouseDown)
return { return {
isCellActive, isCellActive,
handleMouseDown, handleMouseDown,
@ -763,5 +921,8 @@ export function useMultiSelect(
resetSelectedRange, resetSelectedRange,
selectedRange, selectedRange,
makeActive, makeActive,
isCellInFillRange,
isMouseDown,
isFillMode,
} }
} }

1
tests/playwright/pages/Dashboard/common/Cell/AttachmentCell.ts

@ -23,6 +23,7 @@ export class AttachmentCellPageObject extends BasePage {
// //
async addFile({ index, columnHeader, filePath }: { index?: number; columnHeader: string; filePath: string[] }) { async addFile({ index, columnHeader, filePath }: { index?: number; columnHeader: string; filePath: string[] }) {
await this.get({ index, columnHeader }).scrollIntoViewIfNeeded(); await this.get({ index, columnHeader }).scrollIntoViewIfNeeded();
await this.get({ index, columnHeader }).click({ position: { x: 1, y: 1 } });
const attachFileAction = this.get({ index, columnHeader }) const attachFileAction = this.get({ index, columnHeader })
.locator('[data-testid="attachment-cell-file-picker-button"]') .locator('[data-testid="attachment-cell-file-picker-button"]')
.click(); .click();

1
tests/playwright/pages/Dashboard/common/Cell/RatingCell.ts

@ -15,6 +15,7 @@ export class RatingCellPageObject extends BasePage {
} }
async select({ index, columnHeader, rating }: { index?: number; columnHeader: string; rating: number }) { async select({ index, columnHeader, rating }: { index?: number; columnHeader: string; rating: number }) {
await this.get({ index, columnHeader }).scrollIntoViewIfNeeded();
await this.waitForResponse({ await this.waitForResponse({
uiAction: () => this.get({ index, columnHeader }).locator('.ant-rate-star > div').nth(rating).click(), uiAction: () => this.get({ index, columnHeader }).locator('.ant-rate-star > div').nth(rating).click(),
httpMethodsToMatch: ['POST', 'PATCH'], httpMethodsToMatch: ['POST', 'PATCH'],

4
tests/playwright/pages/Dashboard/common/Cell/SelectOptionCell.ts

@ -115,7 +115,9 @@ export class SelectOptionCellPageObject extends BasePage {
const selectCell = this.get({ index, columnHeader }); const selectCell = this.get({ index, columnHeader });
// check if cell active // check if cell active
if (!(await selectCell.getAttribute('class')).includes('active')) { // drag based non-primary cell will have 'active' attribute
// primary cell with blue border will have 'active-cell' attribute
if (!(await selectCell.getAttribute('class')).includes('active-cell')) {
await selectCell.click(); await selectCell.click();
} }

32
tests/playwright/pages/Dashboard/common/Cell/TimeCell.ts

@ -20,4 +20,36 @@ export class TimeCellPageObject extends BasePage {
await cell.locator(`input[title="${value}"]`).waitFor({ state: 'visible' }); await cell.locator(`input[title="${value}"]`).waitFor({ state: 'visible' });
await expect(cell.locator(`[title="${value}"]`)).toBeVisible(); await expect(cell.locator(`[title="${value}"]`)).toBeVisible();
} }
async selectTime({
// hour: 0 - 23
// minute: 0 - 59
// second: 0 - 59
hour,
minute,
}: {
hour: number;
minute: number;
}) {
const timePanel = await this.rootPage.locator('.ant-picker-time-panel-column');
await timePanel.nth(0).locator('.ant-picker-time-panel-cell').nth(hour).click();
await timePanel.nth(1).locator('.ant-picker-time-panel-cell').nth(minute).click();
if (hour < 12) {
await timePanel.nth(2).locator('.ant-picker-time-panel-cell').nth(0).click();
} else {
await timePanel.nth(2).locator('.ant-picker-time-panel-cell').nth(1).click();
}
}
async save() {
await this.rootPage.locator('button:has-text("Ok"):visible').click();
}
async set({ index, columnHeader, value }: { index: number; columnHeader: string; value: string }) {
const [hour, minute, _second] = value.split(':');
await this.get({ index, columnHeader }).click();
await this.get({ index, columnHeader }).click();
await this.selectTime({ hour: +hour, minute: +minute });
await this.save();
}
} }

10
tests/playwright/tests/db/columnAttachments.spec.ts → tests/playwright/tests/db/columns/columnAttachments.spec.ts

@ -1,9 +1,9 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { SharedFormPage } from '../../pages/SharedForm'; import { SharedFormPage } from '../../../pages/SharedForm';
import setup from '../../setup'; import setup from '../../../setup';
import { AccountPage } from '../../pages/Account'; import { AccountPage } from '../../../pages/Account';
import { AccountLicensePage } from '../../pages/Account/License'; import { AccountLicensePage } from '../../../pages/Account/License';
test.describe('Attachment column', () => { test.describe('Attachment column', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

6
tests/playwright/tests/db/columnBarcode.spec.ts → tests/playwright/tests/db/columns/columnBarcode.spec.ts

@ -1,7 +1,7 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import { GridPage } from '../../pages/Dashboard/Grid'; import { GridPage } from '../../../pages/Dashboard/Grid';
interface ExpectedBarcodeData { interface ExpectedBarcodeData {
referencedValue: string; referencedValue: string;

6
tests/playwright/tests/db/columnCheckbox.spec.ts → tests/playwright/tests/db/columns/columnCheckbox.spec.ts

@ -1,7 +1,7 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import { UITypes } from 'nocodb-sdk'; import { UITypes } from 'nocodb-sdk';
import { Api } from 'nocodb-sdk'; import { Api } from 'nocodb-sdk';
let api: Api<any>; let api: Api<any>;

4
tests/playwright/tests/db/columnDateTime.spec.ts → tests/playwright/tests/db/columns/columnDateTime.spec.ts

@ -1,6 +1,6 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
const dateTimeData = [ const dateTimeData = [
{ {

4
tests/playwright/tests/db/columnDuration.spec.ts → tests/playwright/tests/db/columns/columnDuration.spec.ts

@ -1,6 +1,6 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
// Storing one additional dummy value "10" at end of every input array // Storing one additional dummy value "10" at end of every input array
// this will trigger update to previously committed data // this will trigger update to previously committed data

6
tests/playwright/tests/db/columnFormula.spec.ts → tests/playwright/tests/db/columns/columnFormula.spec.ts

@ -1,7 +1,7 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup, { NcContext } from '../../setup'; import setup, { NcContext } from '../../../setup';
import { isPg, isSqlite } from '../../setup/db'; import { isPg, isSqlite } from '../../../setup/db';
// Add formula to be verified here & store expected results for 5 rows // Add formula to be verified here & store expected results for 5 rows
// Column data from City table (Sakila DB) // Column data from City table (Sakila DB)

6
tests/playwright/tests/db/columnGeoData.spec.ts → tests/playwright/tests/db/columns/columnGeoData.spec.ts

@ -1,7 +1,7 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import { GridPage } from '../../pages/Dashboard/Grid'; import { GridPage } from '../../../pages/Dashboard/Grid';
test.describe('Geo Data column', () => { test.describe('Geo Data column', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

4
tests/playwright/tests/db/columnLinkToAnotherRecord.spec.ts → tests/playwright/tests/db/columns/columnLinkToAnotherRecord.spec.ts

@ -1,6 +1,6 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
test.describe('LTAR create & update', () => { test.describe('LTAR create & update', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

4
tests/playwright/tests/db/columnLookupRollup.spec.ts → tests/playwright/tests/db/columns/columnLookupRollup.spec.ts

@ -1,6 +1,6 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
test.describe('Virtual columns', () => { test.describe('Virtual columns', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

8
tests/playwright/tests/db/columnLtarDragdrop.spec.ts → tests/playwright/tests/db/columns/columnLtarDragdrop.spec.ts

@ -1,9 +1,9 @@
import { expect, Locator, test } from '@playwright/test'; import { expect, Locator, test } from '@playwright/test';
import setup from '../../setup'; import setup from '../../../setup';
import { Api, UITypes } from 'nocodb-sdk'; import { Api, UITypes } from 'nocodb-sdk';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { GridPage } from '../../pages/Dashboard/Grid'; import { GridPage } from '../../../pages/Dashboard/Grid';
import { getTextExcludeIconText } from '../utils/general'; import { getTextExcludeIconText } from '../../utils/general';
let api: Api<any>; let api: Api<any>;
const recordCount = 10; const recordCount = 10;

4
tests/playwright/tests/db/columnMenuOperations.spec.ts → tests/playwright/tests/db/columns/columnMenuOperations.spec.ts

@ -1,6 +1,6 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
const columns = [ const columns = [
{ {

8
tests/playwright/tests/db/columnMultiSelect.spec.ts → tests/playwright/tests/db/columns/columnMultiSelect.spec.ts

@ -1,8 +1,8 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { GridPage } from '../../pages/Dashboard/Grid'; import { GridPage } from '../../../pages/Dashboard/Grid';
import setup from '../../setup'; import setup from '../../../setup';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
test.describe('Multi select', () => { test.describe('Multi select', () => {
let dashboard: DashboardPage, grid: GridPage; let dashboard: DashboardPage, grid: GridPage;

6
tests/playwright/tests/db/columnQrCode.spec.ts → tests/playwright/tests/db/columns/columnQrCode.spec.ts

@ -1,7 +1,7 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import { GridPage } from '../../pages/Dashboard/Grid'; import { GridPage } from '../../../pages/Dashboard/Grid';
type ExpectedQrCodeData = { type ExpectedQrCodeData = {
referencedValue: string; referencedValue: string;

6
tests/playwright/tests/db/columnRating.spec.ts → tests/playwright/tests/db/columns/columnRating.spec.ts

@ -1,7 +1,7 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
test.describe('Rating - cell, filter, sort', () => { test.describe('Rating - cell, filter, sort', () => {
let dashboard: DashboardPage, toolbar: ToolbarPage; let dashboard: DashboardPage, toolbar: ToolbarPage;

4
tests/playwright/tests/db/columnRelationalExtendedTests.spec.ts → tests/playwright/tests/db/columns/columnRelationalExtendedTests.spec.ts

@ -1,6 +1,6 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
test.describe('Relational Columns', () => { test.describe('Relational Columns', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

8
tests/playwright/tests/db/columnSingleSelect.spec.ts → tests/playwright/tests/db/columns/columnSingleSelect.spec.ts

@ -1,8 +1,8 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { GridPage } from '../../pages/Dashboard/Grid'; import { GridPage } from '../../../pages/Dashboard/Grid';
import setup from '../../setup'; import setup from '../../../setup';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
test.describe('Single select', () => { test.describe('Single select', () => {
let dashboard: DashboardPage, grid: GridPage; let dashboard: DashboardPage, grid: GridPage;

12
tests/playwright/tests/db/baseShare.spec.ts → tests/playwright/tests/db/features/baseShare.spec.ts

@ -1,10 +1,10 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import { LoginPage } from '../../pages/LoginPage'; import { LoginPage } from '../../../pages/LoginPage';
import { ProjectsPage } from '../../pages/ProjectsPage'; import { ProjectsPage } from '../../../pages/ProjectsPage';
import { getDefaultPwd } from '../utils/general'; import { getDefaultPwd } from '../../utils/general';
test.describe('Shared base', () => { test.describe('Shared base', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

14
tests/playwright/tests/db/erd.spec.ts → tests/playwright/tests/db/features/erd.spec.ts

@ -5,13 +5,13 @@ import {
pgSakilaSqlViews, pgSakilaSqlViews,
pgSakilaTables, pgSakilaTables,
sqliteSakilaSqlViews, sqliteSakilaSqlViews,
} from '../utils/sakila'; } from '../../utils/sakila';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { SettingsSubTab, SettingTab } from '../../pages/Dashboard/Settings'; import { SettingsSubTab, SettingTab } from '../../../pages/Dashboard/Settings';
import setup from '../../setup'; import setup from '../../../setup';
import { isMysql, isPg, isSqlite } from '../../setup/db'; import { isMysql, isPg, isSqlite } from '../../../setup/db';
import { SettingsErdPage } from '../../pages/Dashboard/Settings/Erd'; import { SettingsErdPage } from '../../../pages/Dashboard/Settings/Erd';
import { defaultBaseName } from '../../constants'; import { defaultBaseName } from '../../../constants';
test.describe('Erd', () => { test.describe('Erd', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

10
tests/playwright/tests/db/expandedFormUrl.spec.ts → tests/playwright/tests/db/features/expandedFormUrl.spec.ts

@ -1,9 +1,9 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { GalleryPage } from '../../pages/Dashboard/Gallery'; import { GalleryPage } from '../../../pages/Dashboard/Gallery';
import { GridPage } from '../../pages/Dashboard/Grid'; import { GridPage } from '../../../pages/Dashboard/Grid';
import setup from '../../setup'; import setup from '../../../setup';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
test.describe('Expanded form URL', () => { test.describe('Expanded form URL', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

12
tests/playwright/tests/db/filters.spec.ts → tests/playwright/tests/db/features/filters.spec.ts

@ -1,13 +1,13 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import { UITypes } from 'nocodb-sdk'; import { UITypes } from 'nocodb-sdk';
import { Api } from 'nocodb-sdk'; import { Api } from 'nocodb-sdk';
import { rowMixedValue } from '../../setup/xcdb-records'; import { rowMixedValue } from '../../../setup/xcdb-records';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { createDemoTable } from '../../setup/demoTable'; import { createDemoTable } from '../../../setup/demoTable';
import { isPg } from '../../setup/db'; import { isPg } from '../../../setup/db';
let dashboard: DashboardPage, toolbar: ToolbarPage; let dashboard: DashboardPage, toolbar: ToolbarPage;
let context: any; let context: any;

8
tests/playwright/tests/db/findRowByScanner.spec.ts → tests/playwright/tests/db/features/findRowByScanner.spec.ts

@ -1,8 +1,8 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import { FormPage } from '../../pages/Dashboard/Form'; import { FormPage } from '../../../pages/Dashboard/Form';
import setup from '../../setup'; import setup from '../../../setup';
// Skip for now as it is not working in CI atm // Skip for now as it is not working in CI atm
test.describe.skip('Find row by scanner', () => { test.describe.skip('Find row by scanner', () => {

10
tests/playwright/tests/db/import.spec.ts → tests/playwright/tests/db/features/import.spec.ts

@ -1,9 +1,9 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { airtableApiBase, airtableApiKey } from '../../constants'; import { airtableApiBase, airtableApiKey } from '../../../constants';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { quickVerify } from '../../quickTests/commonTest'; import { quickVerify } from '../../../quickTests/commonTest';
import setup from '../../setup'; import setup from '../../../setup';
import { isPg, isSqlite } from '../../setup/db'; import { isPg, isSqlite } from '../../../setup/db';
test.describe('Import', () => { test.describe('Import', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

6
tests/playwright/tests/db/keyboardShortcuts.spec.ts → tests/playwright/tests/db/features/keyboardShortcuts.spec.ts

@ -1,7 +1,7 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { GridPage } from '../../pages/Dashboard/Grid'; import { GridPage } from '../../../pages/Dashboard/Grid';
import setup from '../../setup'; import setup from '../../../setup';
import { Api, UITypes } from 'nocodb-sdk'; import { Api, UITypes } from 'nocodb-sdk';
let api: Api<any>; let api: Api<any>;

8
tests/playwright/tests/db/language.spec.ts → tests/playwright/tests/db/features/language.spec.ts

@ -1,7 +1,7 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { ProjectsPage } from '../../pages/ProjectsPage'; import { ProjectsPage } from '../../../pages/ProjectsPage';
import setup from '../../setup'; import setup from '../../../setup';
const langMenu = [ const langMenu = [
'help-translate', 'help-translate',
@ -58,7 +58,7 @@ test.describe('Common', () => {
// Index is the order in which menu options appear // Index is the order in which menu options appear
for (let i = 1; i < langMenu.length; i++) { for (let i = 1; i < langMenu.length; i++) {
// scripts/playwright/tests/language.spec.ts // scripts/playwright/tests/language.spec.ts
const json = require(`../../../../packages/nc-gui/lang/${langMenu[i]}`); const json = require(`../../../../../packages/nc-gui/lang/${langMenu[i]}`);
await projectsPage.openLanguageMenu(); await projectsPage.openLanguageMenu();
await projectsPage.selectLanguage({ index: i }); await projectsPage.selectLanguage({ index: i });
await projectsPage.verifyLanguage({ json }); await projectsPage.verifyLanguage({ json });

11
tests/playwright/tests/db/metaLTAR.spec.ts → tests/playwright/tests/db/features/metaLTAR.spec.ts

@ -20,13 +20,12 @@
*/ */
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import setup from '../../setup'; import setup from '../../../setup';
import { Api, UITypes } from 'nocodb-sdk'; import { Api, UITypes } from 'nocodb-sdk';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { GridPage } from '../../pages/Dashboard/Grid'; import { GridPage } from '../../../pages/Dashboard/Grid';
import { createXcdb, deleteXcdb } from '../../setup/xcdbProject'; import { createXcdb, deleteXcdb } from '../../../setup/xcdbProject';
import { ProjectsPage } from '../../pages/ProjectsPage'; import { ProjectsPage } from '../../../pages/ProjectsPage';
import { isSqlite } from '../../setup/db';
let api: Api<any>; let api: Api<any>;
const recordCount = 10; const recordCount = 10;

8
tests/playwright/tests/db/metaSync.spec.ts → tests/playwright/tests/db/features/metaSync.spec.ts

@ -1,8 +1,8 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { SettingsPage, SettingTab } from '../../pages/Dashboard/Settings'; import { SettingsPage, SettingTab } from '../../../pages/Dashboard/Settings';
import setup, { NcContext } from '../../setup'; import setup, { NcContext } from '../../../setup';
import { isMysql, isPg, isSqlite, mysqlExec, pgExec, sqliteExec } from '../../setup/db'; import { isMysql, isPg, isSqlite, mysqlExec, pgExec, sqliteExec } from '../../../setup/db';
test.describe('Meta sync', () => { test.describe('Meta sync', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

8
tests/playwright/tests/db/mobileMode.spec.ts → tests/playwright/tests/db/features/mobileMode.spec.ts

@ -1,8 +1,8 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import { FormPage } from '../../pages/Dashboard/Form'; import { FormPage } from '../../../pages/Dashboard/Form';
import setup from '../../setup'; import setup from '../../../setup';
test.describe('Mobile Mode', () => { test.describe('Mobile Mode', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

4
tests/playwright/tests/db/pagination.spec.ts → tests/playwright/tests/db/features/pagination.spec.ts

@ -1,6 +1,6 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
test.describe('Grid pagination', () => { test.describe('Grid pagination', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

6
tests/playwright/tests/db/swagger.spec.ts → tests/playwright/tests/db/features/swagger.spec.ts

@ -1,7 +1,7 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { GridPage } from '../../pages/Dashboard/Grid'; import { GridPage } from '../../../pages/Dashboard/Grid';
import setup from '../../setup'; import setup from '../../../setup';
test.describe('Table Column Operations', () => { test.describe('Table Column Operations', () => {
let grid: GridPage, dashboard: DashboardPage; let grid: GridPage, dashboard: DashboardPage;

12
tests/playwright/tests/db/timezone.spec.ts → tests/playwright/tests/db/features/timezone.spec.ts

@ -1,12 +1,12 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import { knex } from 'knex'; import { knex } from 'knex';
import { Api, UITypes } from 'nocodb-sdk'; import { Api, UITypes } from 'nocodb-sdk';
import { ProjectsPage } from '../../pages/ProjectsPage'; import { ProjectsPage } from '../../../pages/ProjectsPage';
import { isMysql, isPg, isSqlite } from '../../setup/db'; import { isMysql, isPg, isSqlite } from '../../../setup/db';
import { getKnexConfig } from '../utils/config'; import { getKnexConfig } from '../../utils/config';
import { getBrowserTimezoneOffset } from '../utils/general'; import { getBrowserTimezoneOffset } from '../../utils/general';
let api: Api<any>, records: any[]; let api: Api<any>, records: any[];
const columns = [ const columns = [

12
tests/playwright/tests/db/undo-redo.spec.ts → tests/playwright/tests/db/features/undo-redo.spec.ts

@ -1,11 +1,11 @@
import { expect, Page, test } from '@playwright/test'; import { expect, Page, test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import { Api, UITypes } from 'nocodb-sdk'; import { Api, UITypes } from 'nocodb-sdk';
import { rowMixedValue } from '../../setup/xcdb-records'; import { rowMixedValue } from '../../../setup/xcdb-records';
import { GridPage } from '../../pages/Dashboard/Grid'; import { GridPage } from '../../../pages/Dashboard/Grid';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import { isSqlite } from '../../setup/db'; import { isSqlite } from '../../../setup/db';
let dashboard: DashboardPage, let dashboard: DashboardPage,
grid: GridPage, grid: GridPage,

12
tests/playwright/tests/db/updateBulk.ts → tests/playwright/tests/db/features/updateBulk.ts

@ -1,11 +1,11 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import setup from '../../setup'; import setup from '../../../setup';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { Api } from 'nocodb-sdk'; import { Api } from 'nocodb-sdk';
import { createDemoTable } from '../../setup/demoTable'; import { createDemoTable } from '../../../setup/demoTable';
import { BulkUpdatePage } from '../../pages/Dashboard/BulkUpdate'; import { BulkUpdatePage } from '../../../pages/Dashboard/BulkUpdate';
import { AccountLicensePage } from '../../pages/Account/License'; import { AccountLicensePage } from '../../../pages/Account/License';
import { AccountPage } from '../../pages/Account'; import { AccountPage } from '../../../pages/Account';
let bulkUpdateForm: BulkUpdatePage; let bulkUpdateForm: BulkUpdatePage;
let dashboard: DashboardPage; let dashboard: DashboardPage;

258
tests/playwright/tests/db/features/verticalFillHandle.spec.ts

@ -0,0 +1,258 @@
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 dashboard: DashboardPage;
let context: any;
let api: Api<any>;
let table;
async function dragDrop({ firstColumn, lastColumn }: { firstColumn: string; lastColumn: string }) {
await dashboard.grid.cell.get({ index: 0, columnHeader: firstColumn }).click();
await dashboard.rootPage.keyboard.press(
(await dashboard.grid.isMacOs()) ? 'Meta+Shift+ArrowRight' : 'Control+Shift+ArrowRight'
);
// get fill handle locator
const src = await dashboard.rootPage.locator(`.nc-fill-handle`);
const dst = await dashboard.grid.cell.get({ index: 3, columnHeader: lastColumn });
// drag and drop
await src.dragTo(dst);
}
async function beforeEachInit({ page, tableType }: { page: any; tableType: string }) {
context = await setup({ page, isEmptyProject: true });
dashboard = new DashboardPage(page, context.project);
api = new Api({
baseURL: `http://localhost:8080/`,
headers: {
'xc-auth': context.token,
},
});
table = await createDemoTable({ context, type: tableType, recordCnt: 10 });
await page.reload();
await dashboard.treeView.openTable({ title: tableType });
}
test.describe('Fill Handle', () => {
test.beforeEach(async ({ page }) => {
await beforeEachInit({ page, tableType: 'textBased' });
});
test('Text based', async () => {
const fields = [
{ title: 'SingleLineText', value: 'Afghanistan', type: 'text' },
{ title: 'Email', value: 'jbutt@gmail.com', type: 'text' },
{ title: 'PhoneNumber', value: '1-541-754-3010', type: 'text' },
{ title: 'URL', value: 'https://www.google.com', type: 'text' },
{ title: 'MultiLineText', value: 'Aberdeen, United Kingdom', type: 'longText' },
];
await dragDrop({ firstColumn: 'SingleLineText', lastColumn: 'URL' });
// verify data on grid (verifying just two rows)
for (let i = 0; i < fields.length; i++) {
for (let j = 0; j < 4; j++) {
await dashboard.grid.cell.verify({ index: j, columnHeader: fields[i].title, value: fields[i].value });
}
}
// verify api response
const updatedRecords = (await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 4 })).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('Fill Handle', () => {
test.beforeEach(async ({ page }) => {
await beforeEachInit({ page, tableType: 'numberBased' });
});
test('Number based', async () => {
const fields = [
{ title: 'Number', value: '33', type: 'text' },
{ title: 'Decimal', value: '33.3', type: 'text' },
{ title: 'Currency', value: '33.30', type: 'text' },
{ title: 'Percent', value: '33', type: 'text' },
{ title: 'Duration', value: '00:01', type: 'text' },
{ title: 'Rating', value: '3', type: 'rating' },
{ title: 'Year', value: '2023', type: 'year' },
{ title: 'Time', value: '02:02', type: 'time' },
];
// kludge: insert time from browser until mysql issue with timezone is fixed
await dashboard.grid.cell.time.set({ index: 0, columnHeader: 'Time', value: '02:02' });
// set rating for first record
await dashboard.grid.cell.rating.select({ index: 0, columnHeader: 'Rating', rating: 2 });
await dragDrop({ firstColumn: 'Number', lastColumn: 'Time' });
// verify data on grid
for (let i = 0; i < fields.length; i++) {
for (let j = 0; j < 4; j++) {
if (fields[i].type === 'rating') {
await dashboard.grid.cell.rating.verify({
index: j,
columnHeader: fields[i].title,
rating: +fields[i].value,
});
} else if (fields[i].type === 'year') {
await dashboard.grid.cell.year.verify({ index: j, columnHeader: fields[i].title, value: +fields[i].value });
} else if (fields[i].type === 'time') {
await dashboard.grid.cell.time.verify({ index: j, columnHeader: fields[i].title, value: fields[i].value });
} else {
await dashboard.grid.cell.verify({ index: j, columnHeader: fields[i].title, value: fields[i].value });
}
}
}
// verify api response
// duration in seconds
const APIResponse = [33, 33.3, 33.3, 33, 60, 3, 2023, '02:02:00'];
const updatedRecords = (await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 4 })).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('Fill Handle', () => {
test.beforeEach(async ({ page }) => {
await beforeEachInit({ page, tableType: 'selectBased' });
});
test('Select based', async () => {
const fields = [
{ title: 'SingleSelect', value: 'jan', type: 'singleSelect' },
{ title: 'MultiSelect', value: 'jan,feb,mar', type: 'multiSelect' },
];
await dragDrop({ firstColumn: 'SingleSelect', lastColumn: 'MultiSelect' });
// verify data on grid
const displayOptions = ['jan', 'feb', 'mar'];
for (let i = 0; i < fields.length; i++) {
for (let j = 0; j < 4; j++) {
if (fields[i].type === 'singleSelect') {
await dashboard.grid.cell.selectOption.verify({
index: j,
columnHeader: fields[i].title,
option: fields[i].value,
});
} else {
await dashboard.grid.cell.selectOption.verifyOptions({
index: j,
columnHeader: fields[i].title,
options: displayOptions,
});
}
}
}
// verify api response
const updatedRecords = (await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 4 })).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('Fill Handle', () => {
test.beforeEach(async ({ page }) => {
await beforeEachInit({ page, tableType: 'miscellaneous' });
});
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 dashboard.grid.cell.checkbox.click({ index: 0, columnHeader: 'Checkbox' });
const filepath = [`${process.cwd()}/fixtures/sampleFiles/1.json`];
await dashboard.grid.cell.attachment.addFile({
index: 0,
columnHeader: 'Attachment',
filePath: filepath,
});
await dragDrop({ firstColumn: 'Checkbox', lastColumn: 'Attachment' });
// verify data on grid
for (let i = 0; i < fields.length; i++) {
for (let j = 0; j < 4; j++) {
if (fields[i].type === 'checkbox') {
await dashboard.grid.cell.checkbox.verifyChecked({
index: j,
columnHeader: fields[i].title,
});
} else {
await dashboard.grid.cell.attachment.verifyFileCount({
index: j,
columnHeader: fields[i].title,
count: 1,
});
}
}
}
// verify api response
const updatedRecords = (await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 4 })).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('Fill Handle', () => {
test.beforeEach(async ({ page }) => {
await beforeEachInit({ page, tableType: 'dateTimeBased' });
});
test('Date Time Based', async () => {
const row0_date = await api.dbTableRow.read('noco', context.project.id, table.id, 1);
const fields = [{ title: 'Date', value: row0_date['Date'], type: 'date' }];
await dragDrop({ firstColumn: 'Date', lastColumn: 'Date' });
// verify data on grid
for (let i = 0; i < fields.length; i++) {
for (let j = 0; j < 4; j++) {
await dashboard.grid.cell.date.verify({
index: j,
columnHeader: fields[i].title,
date: fields[i].value,
});
}
}
// verify api response
const updatedRecords = (await api.dbTableRow.list('noco', context.project.id, table.id, { limit: 4 })).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);
}
}
});
});

12
tests/playwright/tests/db/01-webhook.spec.ts → tests/playwright/tests/db/features/webhook.spec.ts

@ -1,11 +1,11 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import makeServer from '../../setup/server'; import makeServer from '../../../setup/server';
import { WebhookFormPage } from '../../pages/Dashboard/WebhookForm'; import { WebhookFormPage } from '../../../pages/Dashboard/WebhookForm';
import { isSubset } from '../utils/general'; import { isSubset } from '../../utils/general';
import { Api, UITypes } from 'nocodb-sdk'; import { Api, UITypes } from 'nocodb-sdk';
import { isMysql, isPg, isSqlite } from '../../setup/db'; import { isMysql, isPg, isSqlite } from '../../../setup/db';
const hookPath = 'http://localhost:9090/hook'; const hookPath = 'http://localhost:9090/hook';
let api: Api<any>; let api: Api<any>;

6
tests/playwright/tests/db/cellSelection.spec.ts → tests/playwright/tests/db/general/cellSelection.spec.ts

@ -1,7 +1,7 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { GridPage } from '../../pages/Dashboard/Grid'; import { GridPage } from '../../../pages/Dashboard/Grid';
import setup from '../../setup'; import setup from '../../../setup';
test.describe('Verify cell selection', () => { test.describe('Verify cell selection', () => {
let dashboard: DashboardPage, grid: GridPage; let dashboard: DashboardPage, grid: GridPage;

2
tests/playwright/tests/db/megaTable.spec.ts → tests/playwright/tests/db/general/megaTable.spec.ts

@ -1,5 +1,5 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import setup from '../../setup'; import setup from '../../../setup';
import { UITypes } from 'nocodb-sdk'; import { UITypes } from 'nocodb-sdk';
import { Api } from 'nocodb-sdk'; import { Api } from 'nocodb-sdk';
let api: Api<any>; let api: Api<any>;

16
tests/playwright/tests/db/projectOperations.spec.ts → tests/playwright/tests/db/general/projectOperations.spec.ts

@ -1,13 +1,13 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { airtableApiBase, airtableApiKey } from '../../constants'; import { airtableApiBase, airtableApiKey } from '../../../constants';
import { quickVerify } from '../../quickTests/commonTest'; import { quickVerify } from '../../../quickTests/commonTest';
import setup from '../../setup'; import setup from '../../../setup';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import { ProjectsPage } from '../../pages/ProjectsPage'; import { ProjectsPage } from '../../../pages/ProjectsPage';
import { Api } from 'nocodb-sdk'; import { Api } from 'nocodb-sdk';
import { ProjectInfo, ProjectInfoApiUtil } from '../utils/projectInfoApiUtil'; import { ProjectInfo, ProjectInfoApiUtil } from '../../utils/projectInfoApiUtil';
import { deepCompare } from '../utils/objectCompareUtil'; import { deepCompare } from '../../utils/objectCompareUtil';
test.describe('Project operations', () => { test.describe('Project operations', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

6
tests/playwright/tests/db/tableColumnOperation.spec.ts → tests/playwright/tests/db/general/tableColumnOperation.spec.ts

@ -1,7 +1,7 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { GridPage } from '../../pages/Dashboard/Grid'; import { GridPage } from '../../../pages/Dashboard/Grid';
import setup from '../../setup'; import setup from '../../../setup';
test.describe('Table Column Operations', () => { test.describe('Table Column Operations', () => {
let grid: GridPage, dashboard: DashboardPage; let grid: GridPage, dashboard: DashboardPage;

10
tests/playwright/tests/db/tableOperations.spec.ts → tests/playwright/tests/db/general/tableOperations.spec.ts

@ -1,10 +1,10 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { Api, TableListType, TableType } from 'nocodb-sdk'; import { Api, TableListType, TableType } from 'nocodb-sdk';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { SettingsPage, SettingTab } from '../../pages/Dashboard/Settings'; import { SettingsPage, SettingTab } from '../../../pages/Dashboard/Settings';
import { deepCompare } from '../utils/objectCompareUtil'; import { deepCompare } from '../../utils/objectCompareUtil';
import setup from '../../setup'; import setup from '../../../setup';
import { ProjectInfoApiUtil, TableInfo } from '../utils/projectInfoApiUtil'; import { ProjectInfoApiUtil, TableInfo } from '../../utils/projectInfoApiUtil';
test.describe('Table Operations', () => { test.describe('Table Operations', () => {
let dashboard: DashboardPage, settings: SettingsPage; let dashboard: DashboardPage, settings: SettingsPage;

6
tests/playwright/tests/db/toolbarOperations.spec.ts → tests/playwright/tests/db/general/toolbarOperations.spec.ts

@ -1,7 +1,7 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import setup from '../../setup'; import setup from '../../../setup';
test.describe('Toolbar operations (GRID)', () => { test.describe('Toolbar operations (GRID)', () => {
let dashboard: DashboardPage, toolbar: ToolbarPage; let dashboard: DashboardPage, toolbar: ToolbarPage;

6
tests/playwright/tests/db/viewMenu.spec.ts → tests/playwright/tests/db/general/viewMenu.spec.ts

@ -1,7 +1,7 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import { isPg } from '../../setup/db'; import { isPg } from '../../../setup/db';
test.describe('Grid view locked', () => { test.describe('Grid view locked', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

6
tests/playwright/tests/db/views.spec.ts → tests/playwright/tests/db/general/views.spec.ts

@ -1,7 +1,7 @@
import { expect, test } from '@playwright/test'; import { expect, test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import setup from '../../setup'; import setup from '../../../setup';
test.describe('Views CRUD Operations', () => { test.describe('Views CRUD Operations', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

8
tests/playwright/tests/db/accountLicense.spec.ts → tests/playwright/tests/db/users&Accounts/accountLicense.spec.ts

@ -1,8 +1,8 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { AccountPage } from '../../pages/Account'; import { AccountPage } from '../../../pages/Account';
import setup from '../../setup'; import setup from '../../../setup';
import { AccountLicensePage } from '../../pages/Account/License'; import { AccountLicensePage } from '../../../pages/Account/License';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
test.describe('Enterprise License', () => { test.describe('Enterprise License', () => {
// @ts-ignore // @ts-ignore

6
tests/playwright/tests/db/accountTokenManagement.spec.ts → tests/playwright/tests/db/users&Accounts/accountTokenManagement.spec.ts

@ -1,7 +1,7 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { AccountPage } from '../../pages/Account'; import { AccountPage } from '../../../pages/Account';
import { AccountTokenPage } from '../../pages/Account/Token'; import { AccountTokenPage } from '../../../pages/Account/Token';
import setup from '../../setup'; import setup from '../../../setup';
test.describe('User roles', () => { test.describe('User roles', () => {
let accountTokenPage: AccountTokenPage; let accountTokenPage: AccountTokenPage;

12
tests/playwright/tests/db/accountUserManagement.spec.ts → tests/playwright/tests/db/users&Accounts/accountUserManagement.spec.ts

@ -1,10 +1,10 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { AccountPage } from '../../pages/Account'; import { AccountPage } from '../../../pages/Account';
import { AccountUsersPage } from '../../pages/Account/Users'; import { AccountUsersPage } from '../../../pages/Account/Users';
import { ProjectsPage } from '../../pages/ProjectsPage'; import { ProjectsPage } from '../../../pages/ProjectsPage';
import { SignupPage } from '../../pages/SignupPage'; import { SignupPage } from '../../../pages/SignupPage';
import setup from '../../setup'; import setup from '../../../setup';
import { getDefaultPwd } from '../utils/general'; import { getDefaultPwd } from '../../utils/general';
const roleDb = [ const roleDb = [
{ email: 'creator@nocodb.com', role: 'Organization Level Creator', url: '' }, { email: 'creator@nocodb.com', role: 'Organization Level Creator', url: '' },

10
tests/playwright/tests/db/accountUserSettings.spec.ts → tests/playwright/tests/db/users&Accounts/accountUserSettings.spec.ts

@ -1,9 +1,9 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { AccountPage } from '../../pages/Account'; import { AccountPage } from '../../../pages/Account';
import { AccountSettingsPage } from '../../pages/Account/Settings'; import { AccountSettingsPage } from '../../../pages/Account/Settings';
import { SignupPage } from '../../pages/SignupPage'; import { SignupPage } from '../../../pages/SignupPage';
import setup from '../../setup'; import setup from '../../../setup';
import { getDefaultPwd } from '../utils/general'; import { getDefaultPwd } from '../../utils/general';
test.describe('App settings', () => { test.describe('App settings', () => {
let accountSettingsPage: AccountSettingsPage; let accountSettingsPage: AccountSettingsPage;

16
tests/playwright/tests/db/authChangePassword.spec.ts → tests/playwright/tests/db/users&Accounts/authChangePassword.spec.ts

@ -1,12 +1,12 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import { LoginPage } from '../../pages/LoginPage'; import { LoginPage } from '../../../pages/LoginPage';
import { SettingsPage, SettingTab } from '../../pages/Dashboard/Settings'; import { SettingsPage, SettingTab } from '../../../pages/Dashboard/Settings';
import { SignupPage } from '../../pages/SignupPage'; import { SignupPage } from '../../../pages/SignupPage';
import { ProjectsPage } from '../../pages/ProjectsPage'; import { ProjectsPage } from '../../../pages/ProjectsPage';
import { AccountPage } from '../../pages/Account'; import { AccountPage } from '../../../pages/Account';
import { getDefaultPwd } from '../utils/general'; import { getDefaultPwd } from '../../utils/general';
test.describe('Auth', () => { test.describe('Auth', () => {
let context: any; let context: any;

12
tests/playwright/tests/db/rolesCreate.spec.ts → tests/playwright/tests/db/users&Accounts/rolesCreate.spec.ts

@ -1,10 +1,10 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import { SettingsPage, SettingTab } from '../../pages/Dashboard/Settings'; import { SettingsPage, SettingTab } from '../../../pages/Dashboard/Settings';
import { SignupPage } from '../../pages/SignupPage'; import { SignupPage } from '../../../pages/SignupPage';
import { ProjectsPage } from '../../pages/ProjectsPage'; import { ProjectsPage } from '../../../pages/ProjectsPage';
import { getDefaultPwd } from '../utils/general'; import { getDefaultPwd } from '../../utils/general';
const roleDb = [ const roleDb = [
{ email: 'creator@nocodb.com', role: 'creator', url: '' }, { email: 'creator@nocodb.com', role: 'creator', url: '' },

8
tests/playwright/tests/db/rolesPreview.spec.ts → tests/playwright/tests/db/users&Accounts/rolesPreview.spec.ts

@ -1,8 +1,8 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import { SettingsPage, SettingTab } from '../../pages/Dashboard/Settings'; import { SettingsPage, SettingTab } from '../../../pages/Dashboard/Settings';
const roles = ['Editor', 'Commenter', 'Viewer']; const roles = ['Editor', 'Commenter', 'Viewer'];

4
tests/playwright/tests/db/rolesSuperUser.spec.ts → tests/playwright/tests/db/users&Accounts/rolesSuperUser.spec.ts

@ -1,6 +1,6 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
test.describe('Super user', () => { test.describe('Super user', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

12
tests/playwright/tests/db/viewForm.spec.ts → tests/playwright/tests/db/views/viewForm.spec.ts

@ -1,10 +1,10 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import { FormPage } from '../../pages/Dashboard/Form'; import { FormPage } from '../../../pages/Dashboard/Form';
import { SharedFormPage } from '../../pages/SharedForm'; import { SharedFormPage } from '../../../pages/SharedForm';
import { AccountPage } from '../../pages/Account'; import { AccountPage } from '../../../pages/Account';
import { AccountAppStorePage } from '../../pages/Account/AppStore'; import { AccountAppStorePage } from '../../../pages/Account/AppStore';
import { Api, UITypes } from 'nocodb-sdk'; import { Api, UITypes } from 'nocodb-sdk';
let api: Api<any>; let api: Api<any>;

6
tests/playwright/tests/db/viewFormShareSurvey.spec.ts → tests/playwright/tests/db/views/viewFormShareSurvey.spec.ts

@ -1,7 +1,7 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { SurveyFormPage } from '../../pages/Dashboard/SurveyForm'; import { SurveyFormPage } from '../../../pages/Dashboard/SurveyForm';
import setup from '../../setup'; import setup from '../../../setup';
test.describe('Share form', () => { test.describe('Share form', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

6
tests/playwright/tests/db/viewGridShare.spec.ts → tests/playwright/tests/db/views/viewGridShare.spec.ts

@ -1,7 +1,7 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup from '../../setup'; import setup from '../../../setup';
import { isMysql, isPg, isSqlite } from '../../setup/db'; import { isMysql, isPg, isSqlite } from '../../../setup/db';
test.describe('Shared view', () => { test.describe('Shared view', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;

8
tests/playwright/tests/db/viewKanban.spec.ts → tests/playwright/tests/db/views/viewKanban.spec.ts

@ -1,9 +1,9 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import setup from '../../setup'; import setup from '../../../setup';
import { isPg, isSqlite } from '../../setup/db'; import { isPg, isSqlite } from '../../../setup/db';
const filmRatings = ['G', 'PG', 'PG-13', 'R', 'NC-17']; const filmRatings = ['G', 'PG', 'PG-13', 'R', 'NC-17'];

6
tests/playwright/tests/db/viewMap.spec.ts → tests/playwright/tests/db/views/viewMap.spec.ts

@ -1,8 +1,8 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import { ToolbarPage } from '../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import setup from '../../setup'; import setup from '../../../setup';
test.describe('Map View', () => { test.describe('Map View', () => {
let dashboard: DashboardPage, toolbar: ToolbarPage; let dashboard: DashboardPage, toolbar: ToolbarPage;
Loading…
Cancel
Save