Browse Source

Merge pull request #6384 from nocodb/fix/context

fix: missing ui features in context menu
pull/6478/head
Raju Udava 12 months ago committed by GitHub
parent
commit
b560622a28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/nc-gui/assets/style.scss
  2. 1
      packages/nc-gui/components.d.ts
  3. 5
      packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
  4. 2
      packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue
  5. 4
      packages/nc-gui/components/nc/MenuItem.vue
  6. 159
      packages/nc-gui/components/smartsheet/grid/Table.vue
  7. 4
      packages/nc-gui/components/smartsheet/header/Menu.vue
  8. 266
      packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue
  9. 19
      packages/nocodb-sdk/src/lib/Api.ts
  10. 3
      tests/playwright/pages/Dashboard/Grid/Group.ts
  11. 6
      tests/playwright/pages/Dashboard/Grid/index.ts
  12. 3
      tests/playwright/tests/db/columns/columnAttachments.spec.ts

2
packages/nc-gui/assets/style.scss

@ -177,7 +177,7 @@ a {
// menu item styling
.nc-menu-item {
@apply cursor-pointer text-xs flex items-center gap-2 px-4 py-3 relative after:(content-[''] absolute top-0 left-0 bottom-0 w-full h-full right-0 bg-current opacity-0 transition transition-opactity duration-100) hover:(after:(opacity-5));
@apply cursor-pointer text-sm flex items-center gap-2 px-4 py-3 relative after:(content-[''] absolute top-0 left-0 bottom-0 w-full h-full right-0 bg-current opacity-0 transition transition-opactity duration-100) hover:(after:(opacity-5));
&.disabled {
@apply text-black text-opacity-25 bg-[#f5f5f5] cursor-not-allowed text-shadow-none box-shadow-none border-[#d9d9d9];

1
packages/nc-gui/components.d.ts vendored

@ -52,6 +52,7 @@ declare module '@vue/runtime-core' {
APagination: typeof import('ant-design-vue/es')['Pagination']
APopover: typeof import('ant-design-vue/es')['Popover']
ARadio: typeof import('ant-design-vue/es')['Radio']
ARadioButton: typeof import('ant-design-vue/es')['RadioButton']
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
ARate: typeof import('ant-design-vue/es')['Rate']
ARow: typeof import('ant-design-vue/es')['Row']

5
packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue

@ -530,7 +530,6 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
<GeneralIcon icon="settings" class="group-hover:text-black" />
{{ $t('activity.settings') }}
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('projectDelete', { roles: [stringifyRolesObj(orgRoles), projectRole].join() })"
data-testid="nc-sidebar-project-delete"
@ -712,8 +711,8 @@ const DlgProjectDuplicateOnOk = async (jobData: { id: string; project_id: string
{{ $t('general.duplicate') }}
</div>
</NcMenuItem>
<NcMenuItem v-if="isUIAllowed('tableDelete')" @click="isTableDeleteDialogVisible = true">
<NcDivider />
<NcMenuItem v-if="isUIAllowed('table-delete')" class="!hover:bg-red-50" @click="isTableDeleteDialogVisible = true">
<div class="nc-project-option-item text-red-600">
<GeneralIcon icon="delete" />
{{ $t('general.delete') }}

2
packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue

@ -293,7 +293,7 @@ function onRef(el: HTMLElement) {
<NcDivider />
<template v-if="!vModel.is_default">
<NcMenuItem class="!text-red-500" l @click.stop="onDelete">
<NcMenuItem class="!text-red-500 !hover:bg-red-50" @click.stop="onDelete">
<GeneralIcon icon="delete" class="text-sm nc-view-delete-icon" />
<div class="-ml-0.25">Delete</div>
</NcMenuItem>

4
packages/nc-gui/components/nc/MenuItem.vue

@ -8,11 +8,11 @@
<style lang="scss">
.ant-dropdown-menu-item.nc-menu-item {
@apply py-2 px-2 mx-1.5 font-normal text-sm rounded-md overflow-hidden hover:bg-gray-100;
@apply py-2 px-2 mx-1.5 font-normal text-dropdown rounded-md overflow-hidden hover:bg-gray-100;
}
.nc-menu-item-inner {
@apply flex flex-row items-center gap-x-2;
@apply flex flex-row items-center gap-x-2 text-sm;
}
.nc-menu-item > .ant-dropdown-menu-title-content {

159
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -1136,7 +1136,7 @@ const expandAndLooseFocus = (row: Row, col: Record<string, any>) => {
<template>
<div class="flex flex-col" :class="`${headerOnly !== true ? 'h-full w-full' : ''}`">
<div ref="gridWrapper" class="nc-grid-wrapper min-h-0 flex-1 relative" :class="gridWrapperClass">
<a-dropdown
<NcDropdown
v-model:visible="contextMenu"
:trigger="isSqlView ? [] : ['contextmenu']"
overlay-class-name="nc-dropdown-grid-context-menu"
@ -1220,7 +1220,7 @@ const expandAndLooseFocus = (row: Row, col: Record<string, any>) => {
</div>
<template v-if="isEeUI && persistMenu" #overlay>
<a-menu>
<NcMenu>
<a-sub-menu v-if="predictedNextColumn?.length" key="predict-column">
<template #title>
<div class="flex flex-row items-center py-3">
@ -1230,33 +1230,33 @@ const expandAndLooseFocus = (row: Row, col: Record<string, any>) => {
</div>
</template>
<template #expandIcon></template>
<a-menu>
<NcMenu>
<template v-for="col in predictedNextColumn" :key="`predict-${col.title}-${col.type}`">
<a-menu-item>
<NcMenuItem>
<div class="flex flex-row items-center py-3" @click="loadColumn(col.title, col.type)">
<div class="text-xs pl-2">{{ col.title }}</div>
</div>
</a-menu-item>
</NcMenuItem>
</template>
<a-menu-item>
<NcMenuItem>
<div class="flex flex-row items-center py-3" @click="predictNextColumn">
<div class="text-red-500 text-xs pl-2">
<MdiReload />
Generate Again
</div>
</div>
</a-menu-item>
</a-menu>
</NcMenuItem>
</NcMenu>
</a-sub-menu>
<a-menu-item v-else>
<NcMenuItem v-else>
<!-- Predict Columns -->
<div class="flex flex-row items-center py-3" @click="predictNextColumn">
<MdiReload v-if="predictingNextColumn" class="animate-infinite animate-spin" />
<MdiTableColumnPlusAfter v-else class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">Predict Columns</div>
</div>
</a-menu-item>
</NcMenuItem>
<a-sub-menu v-if="predictedNextFormulas" key="predict-formula">
<template #title>
<div class="flex flex-row items-center py-3">
@ -1266,28 +1266,28 @@ const expandAndLooseFocus = (row: Row, col: Record<string, any>) => {
</div>
</template>
<template #expandIcon></template>
<a-menu>
<NcMenu>
<template v-for="col in predictedNextFormulas" :key="`predict-${col.title}-formula`">
<a-menu-item>
<NcMenuItem>
<div
class="flex flex-row items-center py-3"
@click="loadColumn(col.title, 'Formula', { formula_raw: col.formula })"
>
<div class="text-xs pl-2">{{ col.title }}</div>
</div>
</a-menu-item>
</NcMenuItem>
</template>
</a-menu>
</NcMenu>
</a-sub-menu>
<a-menu-item v-else>
<NcMenuItem v-else>
<!-- Predict Formulas -->
<div class="flex flex-row items-center py-3" @click="predictNextFormulas">
<MdiReload v-if="predictingNextFormulas" class="animate-infinite animate-spin" />
<MdiCalculatorVariant v-else class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">Predict Formulas</div>
</div>
</a-menu-item>
</a-menu>
</NcMenuItem>
</NcMenu>
</template>
<template v-else #overlay>
<SmartsheetColumnEditOrAddProvider
@ -1484,89 +1484,98 @@ const expandAndLooseFocus = (row: Row, col: Record<string, any>) => {
</div>
<template v-if="!isLocked && hasEditPermission" #overlay>
<a-menu class="shadow !rounded !py-0" @click="contextMenu = false">
<a-menu-item
<NcMenu class="!rounded !py-0" @click="contextMenu = false">
<NcMenuItem
v-if="isEeUI && !contextMenuClosing && !contextMenuTarget && data.some((r) => r.rowMeta.selected)"
v-e="['a:row:update-bulk']"
@click="emits('bulkUpdateDlg')"
>
<div v-e="['a:row:update-bulk']" class="nc-project-menu-item">
<component :is="iconMap.edit" />
<!-- TODO i18n -->
Update Selected Rows
</div>
</a-menu-item>
<component :is="iconMap.edit" />
<!-- TODO i18n -->
Update Selected Rows
</NcMenuItem>
<a-menu-item
<NcMenuItem
v-if="!contextMenuClosing && !contextMenuTarget && data.some((r) => r.rowMeta.selected)"
v-e="['a:row:delete-bulk']"
class="nc-project-menu-item !text-red-600 !hover:bg-red-50"
data-testid="nc-delete-row"
@click="deleteSelectedRows"
>
<div v-e="['a:row:delete-bulk']" class="nc-project-menu-item">
<component :is="iconMap.delete" />
<!-- Delete Selected Rows -->
{{ $t('activity.deleteSelectedRow') }}
</div>
</a-menu-item>
<!-- <a-menu-item v-if="contextMenuTarget && selectedRange.isSingleCell()" @click="addEmptyRow(contextMenuTarget.row + 1)"> -->
<!-- <div v-e="['a:row:insert']" class="nc-project-menu-item"> -->
<!-- <GeneralIcon icon="plus" /> -->
<!-- &lt;!&ndash; Insert New Row &ndash;&gt; -->
<!-- {{ $t('activity.insertRow') }} -->
<!-- </div> -->
<!-- </a-menu-item> -->
<a-menu-item v-if="contextMenuTarget" data-testid="context-menu-item-copy" @click="copyValue(contextMenuTarget)">
<div v-e="['a:row:copy']" class="nc-project-menu-item">
<GeneralIcon icon="copy" />
<!-- Copy -->
{{ $t('general.copy') }}
</div>
</a-menu-item>
<component :is="iconMap.delete" />
<!-- Delete Selected Rows -->
{{ $t('activity.deleteSelectedRow') }}
</NcMenuItem>
<!-- <NcMenuItem -->
<!-- v-if="contextMenuTarget && selectedRange.isSingleCell()" -->
<!-- v-e="['a:row:insert']" -->
<!-- class="nc-project-menu-item" -->
<!-- @click="addEmptyRow(contextMenuTarget.row + 1)" -->
<!-- > -->
<!-- <GeneralIcon icon="plus" /> -->
<!-- Insert New Row -->
<!-- {{ $t('activity.insertRow') }} -->
<!-- </NcMenuItem> -->
<NcMenuItem
v-if="contextMenuTarget"
v-e="['a:row:copy']"
class="nc-project-menu-item"
data-testid="context-menu-item-copy"
@click="copyValue(contextMenuTarget)"
>
<GeneralIcon icon="copy" />
<!-- Copy -->
{{ $t('general.copy') }}
</NcMenuItem>
<!-- Clear cell -->
<a-menu-item
<NcMenuItem
v-if="
contextMenuTarget &&
selectedRange.isSingleCell() &&
(isLinksOrLTAR(fields[contextMenuTarget.col]) || !isVirtualCol(fields[contextMenuTarget.col]))
"
v-e="['a:row:clear']"
class="nc-project-menu-item"
@click="clearCell(contextMenuTarget)"
>
<div v-e="['a:row:clear']" class="nc-project-menu-item">
<GeneralIcon icon="close" />
{{ $t('general.clear') }}
</div>
</a-menu-item>
<GeneralIcon icon="close" />
{{ $t('general.clear') }}
</NcMenuItem>
<!-- Clear cell -->
<a-menu-item v-else-if="contextMenuTarget" @click="clearSelectedRangeOfCells()">
<div v-e="['a:row:clear-range']" class="nc-project-menu-item">
<GeneralIcon icon="closeBox" class="text-gray-500" />
Clear
</div>
</a-menu-item>
<a-menu-item
<NcMenuItem
v-else-if="contextMenuTarget"
v-e="['a:row:clear-range']"
class="nc-project-menu-item"
@click="clearSelectedRangeOfCells()"
>
<GeneralIcon icon="closeBox" class="text-gray-500" />
Clear
</NcMenuItem>
<NcDivider v-if="!(!contextMenuClosing && !contextMenuTarget && data.some((r) => r.rowMeta.selected))" />
<NcMenuItem
v-if="contextMenuTarget && (selectedRange.isSingleCell() || selectedRange.isSingleRow())"
v-e="['a:row:delete']"
class="nc-project-menu-item !text-red-600 !hover:bg-red-50"
@click="confirmDeleteRow(contextMenuTarget.row)"
>
<div v-e="['a:row:delete']" class="nc-project-menu-item text-red-600">
<GeneralIcon icon="delete" />
<!-- Delete Row -->
{{ $t('activity.deleteRow') }}
</div>
</a-menu-item>
<a-menu-item v-else-if="contextMenuTarget && deleteRangeOfRows" @click="deleteSelectedRangeOfRows">
<div v-e="['a:row:delete']" class="nc-project-menu-item text-red-600">
<GeneralIcon icon="delete" />
<!-- Delete Row -->
{{ $t('activity.deleteRow') }}
</NcMenuItem>
<div v-else-if="contextMenuTarget && deleteRangeOfRows">
<NcMenuItem v-e="['a:row:delete']" class="nc-project-menu-item text-red-600" @click="deleteSelectedRangeOfRows">
<GeneralIcon icon="delete" class="text-gray-500 text-error" />
<!-- Delete Rows -->
Delete rows
</div>
</a-menu-item>
</a-menu>
</NcMenuItem>
</div>
</NcMenu>
</template>
</a-dropdown>
</NcDropdown>
</div>
<div v-if="showSkeleton && headerOnly !== true" class="flex flex-row justify-center item-center min-h-10">

4
packages/nc-gui/components/smartsheet/header/Menu.vue

@ -360,7 +360,7 @@ const onInsertAfter = () => {
</a-menu-item>
<a-divider class="!my-0" />
<a-menu-item v-if="!column?.pv" @click="handleDelete">
<a-menu-item v-if="!column?.pv" @click="handleDelete" class="!hover:bg-red-50">
<div class="nc-column-delete nc-header-menu-item my-0.75 text-red-600">
<component :is="iconMap.delete" class="ml-0.75 mr-1" />
<!-- Delete -->
@ -375,7 +375,7 @@ const onInsertAfter = () => {
<style scoped>
.nc-header-menu-item {
@apply text-xs flex items-center px-1 py-2 gap-1;
@apply text-dropdown flex items-center px-1 py-2 gap-1;
}
.nc-column-options {

266
packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue

@ -0,0 +1,266 @@
<script lang="ts" setup>
import type { VNodeRef } from '@vue/runtime-core'
import type { KanbanType, ViewType, ViewTypes } from 'nocodb-sdk'
import type { WritableComputedRef } from '@vue/reactivity'
import { IsLockedInj, inject, message, onKeyStroke, useDebounceFn, useNuxtApp, useRoles, useVModel } from '#imports'
interface Props {
view: ViewType
onValidate: (view: ViewType) => boolean | string
}
interface Emits {
(event: 'update:view', data: Record<string, any>): void
(event: 'selectIcon', icon: string): void
(event: 'changeView', view: Record<string, any>): void
(event: 'rename', view: ViewType, title: string | undefined): void
(event: 'delete', view: ViewType): void
(event: 'openModal', data: { type: ViewTypes; title?: string; copyViewId?: string; groupingFieldColumnId?: string }): void
}
const props = defineProps<Props>()
const emits = defineEmits<Emits>()
const vModel = useVModel(props, 'view', emits) as WritableComputedRef<ViewType & { alias?: string; is_default: boolean }>
const { $e } = useNuxtApp()
const { isUIAllowed } = useRoles()
const activeView = inject(ActiveViewInj, ref())
const isLocked = inject(IsLockedInj, ref(false))
const { rightSidebarState } = storeToRefs(useSidebarStore())
const isDropdownOpen = ref(false)
const isEditing = ref(false)
/** Is editing the view name enabled */
/** Helper to check if editing was disabled before the view navigation timeout triggers */
const isStopped = ref(false)
/** Original view title when editing the view name */
const _title = ref<string | undefined>()
/** Debounce click handler, so we can potentially enable editing view name {@see onDblClick} */
const onClick = useDebounceFn(() => {
if (isEditing.value || isStopped.value) return
emits('changeView', vModel.value)
}, 250)
/** Enable editing view name on dbl click */
function onDblClick() {
if (!isUIAllowed('viewCreateOrEdit')) return
if (!isEditing.value) {
isEditing.value = true
_title.value = vModel.value.title
$e('c:view:rename', { view: vModel.value?.type })
}
}
/** Handle keydown on input field */
function onKeyDown(event: KeyboardEvent) {
if (event.key === 'Escape') {
onKeyEsc(event)
} else if (event.key === 'Enter') {
onKeyEnter(event)
}
}
/** Rename view when enter is pressed */
function onKeyEnter(event: KeyboardEvent) {
event.stopImmediatePropagation()
event.preventDefault()
onRename()
}
/** Disable renaming view when escape is pressed */
function onKeyEsc(event: KeyboardEvent) {
event.stopImmediatePropagation()
event.preventDefault()
onCancel()
}
onKeyStroke('Enter', (event) => {
if (isEditing.value) {
onKeyEnter(event)
}
})
const focusInput: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
/** Duplicate a view */
// todo: This is not really a duplication, maybe we need to implement a true duplication?
function onDuplicate() {
isDropdownOpen.value = false
emits('openModal', {
type: vModel.value.type!,
title: vModel.value.title,
copyViewId: vModel.value.id,
groupingFieldColumnId: (vModel.value.view as KanbanType).fk_grp_col_id!,
})
$e('c:view:copy', { view: vModel.value.type })
}
/** Delete a view */
async function onDelete() {
isDropdownOpen.value = false
emits('delete', vModel.value)
}
/** Rename a view */
async function onRename() {
isDropdownOpen.value = false
if (!isEditing.value) return
const isValid = props.onValidate({ ...vModel.value, title: _title.value! })
if (isValid !== true) {
message.error(isValid)
onCancel()
return
}
if (vModel.value.title === '' || vModel.value.title === _title.value) {
onCancel()
return
}
const originalTitle = vModel.value.title
vModel.value.title = _title.value || ''
emits('rename', vModel.value, originalTitle)
onStopEdit()
}
/** Cancel renaming view */
function onCancel() {
if (!isEditing.value) return
// vModel.value.title = _title || ''
onStopEdit()
}
/** Stop editing view name, timeout makes sure that view navigation (click trigger) does not pick up before stop is done */
function onStopEdit() {
isStopped.value = true
isEditing.value = false
_title.value = ''
setTimeout(() => {
isStopped.value = false
}, 250)
}
watch(rightSidebarState, () => {
if (rightSidebarState.value === 'peekCloseEnd') {
isDropdownOpen.value = false
}
})
</script>
<template>
<NcMenuItem
class="!min-h-8 !max-h-8 !mb-0.25 select-none group text-gray-700 !flex !items-center !mt-0 hover:(!bg-gray-100 !text-gray-900)"
:data-testid="`view-sidebar-view-${vModel.alias || vModel.title}`"
@dblclick.stop="onDblClick"
@click="onClick"
>
<div v-e="['a:view:open', { view: vModel.type }]" class="text-xs flex items-center w-full gap-1" data-testid="view-item">
<div class="flex min-w-6" :data-testid="`view-sidebar-drag-handle-${vModel.alias || vModel.title}`">
<LazyGeneralEmojiPicker
class="nc-table-icon"
:emoji="props.view?.meta?.icon"
size="small"
:clearable="true"
@emoji-selected="emits('selectIcon', $event)"
>
<template #default>
<GeneralViewIcon :meta="props.view" class="nc-view-icon" />
</template>
</LazyGeneralEmojiPicker>
</div>
<a-input
v-if="isEditing"
:ref="focusInput"
v-model:value="_title"
class="!bg-transparent !text-xs !border-0 !ring-0 !outline-transparent !border-transparent"
:class="{
'font-medium': activeView?.id === vModel.id,
}"
@blur="onRename"
@keydown.stop="onKeyDown($event)"
/>
<div
v-else
class="capitalize text-ellipsis overflow-hidden select-none w-full"
data-testid="sidebar-view-title"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"
>
{{ vModel.alias || vModel.title }}
</div>
<div class="flex-1" />
<template v-if="!isEditing && !isLocked && isUIAllowed('viewCreateOrEdit')">
<NcDropdown v-model:visible="isDropdownOpen" overlay-class-name="!rounded-lg">
<div
class="invisible !group-hover:visible"
:class="{
'!visible': isDropdownOpen,
}"
>
<NcButton
size="xxsmall"
type="text"
class="nc-view-sidebar-node-context-btn !px-1 !border-none !bg-inherit !tr !hover:text-gray-900"
@click.stop="isDropdownOpen = !isDropdownOpen"
>
<GeneralIcon icon="threeDotVertical" class="-mt-0.5" />
</NcButton>
</div>
<template #overlay>
<NcMenu :data-testid="`view-sidebar-view-actions-${vModel.alias || vModel.title}`">
<NcMenuItem size="small" :centered="false" @click.stop="onDblClick">
<GeneralIcon icon="edit" />
Rename
</NcMenuItem>
<NcMenuItem size="small" :centered="false" class="nc-view-copy-icon" @click.stop="onDuplicate">
<GeneralIcon icon="copy" class="text-base" />
Duplicate
</NcMenuItem>
<template v-if="!vModel.is_default">
<NcDivider />
<NcMenuItem size="small" class="nc-view-delete-icon !text-red-600 !hover:bg-red-50" @click.stop="onDelete">
<GeneralIcon icon="delete" />
Delete
</NcMenuItem>
</template>
</NcMenu>
</template>
</NcDropdown>
</template>
</div>
</NcMenuItem>
</template>

19
packages/nocodb-sdk/src/lib/Api.ts

@ -2903,6 +2903,25 @@ export class HttpClient<SecurityDataType = unknown> {
export class Api<
SecurityDataType extends unknown
> extends HttpClient<SecurityDataType> {
userProfile = {
/**
* @description Update User Profile
*
* @tags User profile
* @name Update
* @summary Update User Profile
* @request PATCH:/api/v1/user/profile
* @response `200` `UserType`
*/
update: (data: UserType, params: RequestParams = {}) =>
this.request<UserType, any>({
path: `/api/v1/user/profile`,
method: 'PATCH',
body: data,
type: ContentType.Json,
...params,
}),
};
auth = {
/**
* @description Create a new user with provided email and password and first user is marked as super admin.

3
tests/playwright/pages/Dashboard/Grid/Group.ts

@ -121,8 +121,7 @@ export class GroupPageObject extends BasePage {
});
// Click text=Delete Row
await this.rootPage.locator('text=Delete Row').click();
await this.rootPage.locator('.ant-dropdown-menu-item:has-text("Delete row")').click();
// todo: improve selector
await this.rootPage
.locator('span.ant-dropdown-menu-title-content > nc-project-menu-item')

6
tests/playwright/pages/Dashboard/Grid/index.ts

@ -198,7 +198,7 @@ export class GridPage extends BasePage {
});
// Click text=Delete Row
await this.rootPage.locator('text=Delete Row').click();
await this.rootPage.locator('.ant-dropdown-menu-item:has-text("Delete row")').click();
// todo: improve selector
await this.rootPage
@ -217,7 +217,7 @@ export class GridPage extends BasePage {
await cell.click({ button: 'right' });
// Click text=Insert New Row
await this.rootPage.locator('text=Insert New Row').click();
await this.rootPage.locator('.insert-row').click();
await expect(this.get().locator('.nc-grid-row')).toHaveCount(rowCount + 1);
}
@ -259,7 +259,7 @@ export class GridPage extends BasePage {
await this.get().locator('[data-testid="nc-check-all"]').nth(0).click({
button: 'right',
});
await this.rootPage.locator('text=Delete Selected Rows').click();
await this.rootPage.locator('[data-testid="nc-delete-row"]').click();
await this.dashboard.waitForLoaderToDisappear();
}

3
tests/playwright/tests/db/columns/columnAttachments.spec.ts

@ -46,6 +46,9 @@ test.describe('Attachment column', () => {
columnHeader: 'testAttach',
});
// Kludge: tooltip somehow persists. fix me!
await dashboard.rootPage.reload();
await dashboard.viewSidebar.createFormView({
title: 'Form 1',
});

Loading…
Cancel
Save