{
/>
-
+
{{ $t('activity.deleteKanbanStack') }}
diff --git a/packages/nc-gui/components/smartsheet/expanded-form/Header.vue b/packages/nc-gui/components/smartsheet/expanded-form/Header.vue
index 5141bc8313..a9c5107a66 100644
--- a/packages/nc-gui/components/smartsheet/expanded-form/Header.vue
+++ b/packages/nc-gui/components/smartsheet/expanded-form/Header.vue
@@ -3,6 +3,7 @@ import { message } from 'ant-design-vue'
import type { ViewType } from 'nocodb-sdk'
import {
ReloadRowDataHookInj,
+ isMac,
useExpandedFormStoreOrThrow,
useSmartsheetRowStoreOrThrow,
useSmartsheetStoreOrThrow,
@@ -58,6 +59,19 @@ const copyRecordUrl = () => {
)
message.success('Copied to clipboard')
}
+
+useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
+ const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey
+ if (cmdOrCtrl) {
+ switch (e.key) {
+ case 'Enter': {
+ if (isUIAllowed('tableRowUpdate')) {
+ await save()
+ }
+ }
+ }
+ }
+})
diff --git a/packages/nc-gui/components/smartsheet/expanded-form/index.vue b/packages/nc-gui/components/smartsheet/expanded-form/index.vue
index d345b73281..2f1b27ac46 100644
--- a/packages/nc-gui/components/smartsheet/expanded-form/index.vue
+++ b/packages/nc-gui/components/smartsheet/expanded-form/index.vue
@@ -167,7 +167,7 @@ export default {
diff --git a/packages/nc-gui/components/smartsheet/toolbar/Erd.vue b/packages/nc-gui/components/smartsheet/toolbar/Erd.vue
index ea1b24bc49..2dac0c07bd 100644
--- a/packages/nc-gui/components/smartsheet/toolbar/Erd.vue
+++ b/packages/nc-gui/components/smartsheet/toolbar/Erd.vue
@@ -17,6 +17,7 @@ const selectedView = inject(ActiveViewInj)
{
isRtlLang(locale.value as any))
{
{
{
{
:footer="null"
centered
:visible="show"
+ :class="{ active: show }"
:closable="false"
width="max(50vw, 44rem)"
wrap-class-name="nc-modal-invite-user-and-share-base"
@@ -177,7 +178,7 @@ const emailField = (inputEl: typeof Input) => {
-
+
{{ inviteUrl }}
diff --git a/packages/nc-gui/components/virtual-cell/Formula.vue b/packages/nc-gui/components/virtual-cell/Formula.vue
index 079e563d1f..f0afb25e59 100644
--- a/packages/nc-gui/components/virtual-cell/Formula.vue
+++ b/packages/nc-gui/components/virtual-cell/Formula.vue
@@ -34,12 +34,10 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activ
{{ result }}
-
- Warning: Formula fields should be configured in the field menu dropdown.
+ {{ $t('msg.info.computedFieldEditWarning') }}
-
- Warning: Computed field - unable to clear text.
+ {{ $t('msg.info.computedFieldDeleteWarning') }}
diff --git a/packages/nc-gui/components/virtual-cell/Lookup.vue b/packages/nc-gui/components/virtual-cell/Lookup.vue
index 4ba3a394b3..b358296361 100644
--- a/packages/nc-gui/components/virtual-cell/Lookup.vue
+++ b/packages/nc-gui/components/virtual-cell/Lookup.vue
@@ -78,6 +78,7 @@ provide(CellUrlDisableOverlayInj, ref(true))
const timeout = 3000 // in ms
const showEditWarning = refAutoReset(false, timeout)
+
const showClearWarning = refAutoReset(false, timeout)
useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEvent) => {
@@ -85,16 +86,15 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
case 'Enter':
showEditWarning.value = true
break
- case 'Delete':
+ default:
showClearWarning.value = true
- break
}
})
-
+
@@ -134,12 +134,10 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
-
- Warning: Computed field - unable to edit content.
+ {{ $t('msg.info.computedFieldEditWarning') }}
-
- Warning: Computed field - unable to clear content.
+ {{ $t('msg.info.computedFieldDeleteWarning') }}
diff --git a/packages/nc-gui/components/virtual-cell/Rollup.vue b/packages/nc-gui/components/virtual-cell/Rollup.vue
index dea3a85e7c..ad77e9578f 100644
--- a/packages/nc-gui/components/virtual-cell/Rollup.vue
+++ b/packages/nc-gui/components/virtual-cell/Rollup.vue
@@ -6,18 +6,10 @@ const value = inject(CellValueInj)
const timeout = 3000 // in ms
const showEditWarning = refAutoReset(false, timeout)
+
const showClearWarning = refAutoReset(false, timeout)
-useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEvent) => {
- switch (e.key) {
- case 'Enter':
- showEditWarning.value = true
- break
- case 'Delete':
- showClearWarning.value = true
- break
- }
-})
+useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), () => (showClearWarning.value = true))
@@ -28,12 +20,10 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
-
- Warning: Computed field - unable to edit content.
+ {{ $t('msg.info.computedFieldEditWarning') }}
-
- Warning: Computed field - unable to clear content.
+ {{ $t('msg.info.computedFieldDeleteWarning') }}
diff --git a/packages/nc-gui/components/virtual-cell/components/ListItems.vue b/packages/nc-gui/components/virtual-cell/components/ListItems.vue
index 147627bc1b..63519f5835 100644
--- a/packages/nc-gui/components/virtual-cell/components/ListItems.vue
+++ b/packages/nc-gui/components/virtual-cell/components/ListItems.vue
@@ -24,6 +24,8 @@ const vModel = useVModel(props, 'modelValue', emit)
const column = inject(ColumnInj)
+const filterQueryRef = ref()
+
const {
childrenExcludedList,
loadChildrenExcludedList,
@@ -141,6 +143,12 @@ useSelectedCellKeyupListener(vModel, (e: KeyboardEvent) => {
}
}
break
+ default: {
+ const el = filterQueryRef.value?.$el
+ if (el) {
+ filterQueryRef.value.$el.focus()
+ }
+ }
}
})
const activeRow = (vNode?: InstanceType ) => {
@@ -151,6 +159,7 @@ const activeRow = (vNode?: InstanceType) => {
) => {
import tinycolor from 'tinycolor2'
import {
+ TabType,
computed,
definePageMeta,
+ extractSdkResponseErrorMsg,
+ isDrawerOrModalExist,
+ isMac,
message,
navigateTo,
onBeforeMount,
@@ -12,7 +16,9 @@ import {
openLink,
projectThemeColors,
ref,
+ resolveComponent,
useCopy,
+ useDialog,
useGlobal,
useI18n,
useProject,
@@ -23,8 +29,6 @@ import {
useTheme,
useUIPermission,
} from '#imports'
-import { TabType } from '~/lib'
-import { extractSdkResponseErrorMsg } from '~/utils'
definePageMeta({
hideHeader: true,
@@ -34,6 +38,8 @@ const { theme, defaultTheme } = useTheme()
const { t } = useI18n()
+const { $e } = useNuxtApp()
+
const route = useRoute()
const router = useRouter()
@@ -182,6 +188,47 @@ onMounted(() => {
})
onBeforeUnmount(reset)
+
+function openKeyboardShortcutDialog() {
+ $e('a:actions:keyboard-shortcut')
+
+ const isOpen = ref(true)
+
+ const { close } = useDialog(resolveComponent('DlgKeyboardShortcuts'), {
+ 'modelValue': isOpen,
+ 'onUpdate:modelValue': closeDialog,
+ })
+
+ function closeDialog() {
+ isOpen.value = false
+ close(1000)
+ }
+}
+
+useEventListener(document, 'keydown', async (e: KeyboardEvent) => {
+ const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey
+ if (e.altKey && !e.shiftKey && !cmdOrCtrl) {
+ switch (e.keyCode) {
+ case 188: {
+ // ALT + ,
+ if (isUIAllowed('settings') && !isDrawerOrModalExist()) {
+ e.preventDefault()
+ toggleDialog(true, 'teamAndAuth')
+ }
+ break
+ }
+ }
+ }
+ if (cmdOrCtrl) {
+ switch (e.key) {
+ case '/':
+ if (!isDrawerOrModalExist()) {
+ openKeyboardShortcutDialog()
+ }
+ break
+ }
+ }
+})
diff --git a/packages/nc-gui/pages/[projectType]/form/[viewId].vue b/packages/nc-gui/pages/[projectType]/form/[viewId].vue
index b94c7df761..7645e223f6 100644
--- a/packages/nc-gui/pages/[projectType]/form/[viewId].vue
+++ b/packages/nc-gui/pages/[projectType]/form/[viewId].vue
@@ -64,6 +64,7 @@ watch(
{
{
/Mac/i.test(navigator.platform)
+export const isDrawerOrModalExist = () => document.querySelector('.ant-modal.active, .ant-drawer-open')
diff --git a/packages/nc-gui/utils/viewUtils.ts b/packages/nc-gui/utils/viewUtils.ts
index 2be0cb34b0..e197e766ed 100644
--- a/packages/nc-gui/utils/viewUtils.ts
+++ b/packages/nc-gui/utils/viewUtils.ts
@@ -37,3 +37,7 @@ export function applyLanguageDirection(dir: typeof rtl | typeof ltr) {
document.body.classList.add(dir)
document.body.style.direction = dir
}
+
+export function applyNonSelectable() {
+ document.body.classList.add('non-selectable')
+}
diff --git a/tests/playwright/pages/Base.ts b/tests/playwright/pages/Base.ts
index 043c04e614..4aeed1e144 100644
--- a/tests/playwright/pages/Base.ts
+++ b/tests/playwright/pages/Base.ts
@@ -84,4 +84,12 @@ export default abstract class BasePage {
async getClipboardText() {
return await this.rootPage.evaluate(() => navigator.clipboard.readText());
}
+
+ async os() {
+ return await this.rootPage.evaluate(() => navigator.platform);
+ }
+
+ async isMacOs() {
+ return (await this.os()).includes('Mac');
+ }
}
diff --git a/tests/playwright/pages/Dashboard/ExpandedForm/index.ts b/tests/playwright/pages/Dashboard/ExpandedForm/index.ts
index 05ba80d9c7..cc6dc16cde 100644
--- a/tests/playwright/pages/Dashboard/ExpandedForm/index.ts
+++ b/tests/playwright/pages/Dashboard/ExpandedForm/index.ts
@@ -93,9 +93,11 @@ export class ExpandedFormPage extends BasePage {
await this.rootPage.locator('[data-testid="grid-load-spinner"]').waitFor({ state: 'hidden' });
}
- async verify({ header, url }: { header: string; url: string }) {
+ async verify({ header, url }: { header: string; url?: string }) {
await expect(this.get().locator(`.nc-expanded-form-header`).last()).toContainText(header);
- await expect.poll(() => this.rootPage.url()).toContain(url);
+ if (url) {
+ await expect.poll(() => this.rootPage.url()).toContain(url);
+ }
}
async escape() {
diff --git a/tests/playwright/pages/Dashboard/Grid/Column/index.ts b/tests/playwright/pages/Dashboard/Grid/Column/index.ts
index e3825850cc..b34cacd235 100644
--- a/tests/playwright/pages/Dashboard/Grid/Column/index.ts
+++ b/tests/playwright/pages/Dashboard/Grid/Column/index.ts
@@ -17,6 +17,14 @@ export class ColumnPageObject extends BasePage {
return this.rootPage.locator('form[data-testid="add-or-edit-column"]');
}
+ private getColumnHeader(title: string) {
+ return this.grid.get().locator(`th[data-title="${title}"]`);
+ }
+
+ async clickColumnHeader({ title }: { title: string }) {
+ await this.getColumnHeader(title).click();
+ }
+
async create({
title,
type = 'SingleLineText',
@@ -162,7 +170,7 @@ export class ColumnPageObject extends BasePage {
}
async delete({ title }: { title: string }) {
- await this.grid.get().locator(`th[data-title="${title}"] >> svg.ant-dropdown-trigger`).click();
+ await this.getColumnHeader(title).locator('svg.ant-dropdown-trigger').click();
// await this.rootPage.locator('li[role="menuitem"]:has-text("Delete")').waitFor();
await this.rootPage.locator('li[role="menuitem"]:has-text("Delete")').click();
@@ -183,7 +191,7 @@ export class ColumnPageObject extends BasePage {
formula?: string;
format?: string;
}) {
- await this.grid.get().locator(`th[data-title="${title}"] .nc-ui-dt-dropdown`).click();
+ await this.getColumnHeader(title).locator('.nc-ui-dt-dropdown').click();
await this.rootPage.locator('li[role="menuitem"]:has-text("Edit")').click();
await this.get().waitFor({ state: 'visible' });
@@ -222,9 +230,9 @@ export class ColumnPageObject extends BasePage {
async verify({ title, isVisible = true }: { title: string; isVisible?: boolean }) {
if (!isVisible) {
- return await expect(await this.rootPage.locator(`th[data-title="${title}"]`)).not.toBeVisible();
+ return await expect(this.getColumnHeader(title)).not.toBeVisible();
}
- await await expect(this.rootPage.locator(`th[data-title="${title}"]`)).toContainText(title);
+ await expect(this.getColumnHeader(title)).toContainText(title);
}
async verifyRoleAccess(param: { role: string }) {
diff --git a/tests/playwright/pages/Dashboard/Grid/index.ts b/tests/playwright/pages/Dashboard/Grid/index.ts
index 9e49bc7a8f..66a85bee3b 100644
--- a/tests/playwright/pages/Dashboard/Grid/index.ts
+++ b/tests/playwright/pages/Dashboard/Grid/index.ts
@@ -114,7 +114,11 @@ export class GridPage extends BasePage {
await this.waitForResponse({
uiAction: clickOnColumnHeaderToSave,
requestUrlPathToMatch: 'api/v1/db/data/noco',
- httpMethodsToMatch: ['PATCH'],
+ httpMethodsToMatch: [
+ 'PATCH',
+ // since edit row on an empty row will emit POST request
+ 'POST',
+ ],
responseJsonMatcher: resJson => resJson?.[columnHeader] === value,
});
} else {
diff --git a/tests/playwright/pages/Dashboard/Settings/Teams.ts b/tests/playwright/pages/Dashboard/Settings/Teams.ts
index 26a42a5cf7..581bc5d698 100644
--- a/tests/playwright/pages/Dashboard/Settings/Teams.ts
+++ b/tests/playwright/pages/Dashboard/Settings/Teams.ts
@@ -28,10 +28,11 @@ export class TeamsPage extends BasePage {
return this.rootPage.getByTestId('nc-share-base-sub-modal');
}
- async invite({ email, role }: { email: string; role: string }) {
+ async invite({ email, role, skipOpeningModal }: { email: string; role: string; skipOpeningModal?: boolean }) {
email = this.prefixEmail(email);
- await this.inviteTeamBtn.click();
+ if (!skipOpeningModal) await this.inviteTeamBtn.click();
+
await this.inviteTeamModal.locator(`input[placeholder="E-mail"]`).fill(email);
await this.inviteTeamModal.locator(`.nc-user-roles`).click();
const userRoleModal = this.rootPage.locator(`.nc-dropdown-user-role`);
@@ -78,6 +79,10 @@ export class TeamsPage extends BasePage {
return await this.getSharedBaseSubModal().locator(`.nc-url:visible`).textContent();
}
+ async getInvitationUrl() {
+ return await this.rootPage.getByTestId('invite-modal-invitation-url').textContent();
+ }
+
async sharedBaseActions({ action }: { action: string }) {
const actionMenu = ['reload', 'copy url', 'open tab', 'copy embed code'];
const index = actionMenu.indexOf(action);
diff --git a/tests/playwright/pages/Dashboard/TreeView.ts b/tests/playwright/pages/Dashboard/TreeView.ts
index 6b07007ba8..25efc080bc 100644
--- a/tests/playwright/pages/Dashboard/TreeView.ts
+++ b/tests/playwright/pages/Dashboard/TreeView.ts
@@ -20,6 +20,18 @@ export class TreeViewPage extends BasePage {
return this.dashboard.get().locator('.nc-treeview-container');
}
+ async isVisible() {
+ return await this.get().isVisible();
+ }
+
+ async verifyVisibility({ isVisible }: { isVisible: boolean }) {
+ if (isVisible) {
+ await expect(this.get()).toBeVisible();
+ } else {
+ await expect(this.get()).not.toBeVisible();
+ }
+ }
+
async focusTable({ title }: { title: string }) {
await this.get().locator(`.nc-project-tree-tbl-${title}`).focus();
}
@@ -43,8 +55,8 @@ export class TreeViewPage extends BasePage {
await this.dashboard.waitForTabRender({ title, mode });
}
- async createTable({ title }: { title: string }) {
- await this.get().locator('.nc-add-new-table').click();
+ async createTable({ title, skipOpeningModal }: { title: string; skipOpeningModal?: boolean }) {
+ if (!skipOpeningModal) await this.get().locator('.nc-add-new-table').click();
await this.dashboard.get().locator('.nc-modal-table-create').locator('.ant-modal-body').waitFor();
@@ -63,13 +75,13 @@ export class TreeViewPage extends BasePage {
async verifyTable({ title, index, exists = true }: { title: string; index?: number; exists?: boolean }) {
if (exists) {
- await expect(this.get().locator(`.nc-project-tree-tbl-${title}`)).toBeVisible();
+ await expect(this.get().getByTestId(`tree-view-table-${title}`)).toHaveCount(1);
if (index) {
- await expect(await this.get().locator('.nc-tbl-title').nth(index)).toHaveText(title);
+ await expect(this.get().locator('.nc-tbl-title').nth(index)).toHaveText(title);
}
} else {
- await expect(this.get().locator(`.nc-project-tree-tbl-${title}`)).toHaveCount(0);
+ await expect(this.get().getByTestId(`tree-view-table-${title}`)).toHaveCount(0);
}
}
diff --git a/tests/playwright/pages/Dashboard/ViewSidebar/index.ts b/tests/playwright/pages/Dashboard/ViewSidebar/index.ts
index cf75e34dc7..cf2791ce1b 100644
--- a/tests/playwright/pages/Dashboard/ViewSidebar/index.ts
+++ b/tests/playwright/pages/Dashboard/ViewSidebar/index.ts
@@ -23,6 +23,18 @@ export class ViewSidebarPage extends BasePage {
return this.dashboard.get().locator('.nc-view-sidebar');
}
+ async isVisible() {
+ return await this.get().isVisible();
+ }
+
+ async verifyVisibility({ isVisible }: { isVisible: boolean }) {
+ if (isVisible) {
+ await expect(this.get()).toBeVisible();
+ } else {
+ await expect(this.get()).not.toBeVisible();
+ }
+ }
+
private async createView({ title, locator }: { title: string; locator: Locator }) {
await locator.click();
await this.rootPage.locator('input[id="form_item_title"]:visible').fill(title);
diff --git a/tests/playwright/pages/Dashboard/common/Cell/index.ts b/tests/playwright/pages/Dashboard/common/Cell/index.ts
index 46e628795e..204375dcda 100644
--- a/tests/playwright/pages/Dashboard/common/Cell/index.ts
+++ b/tests/playwright/pages/Dashboard/common/Cell/index.ts
@@ -31,7 +31,8 @@ export class CellPageObject extends BasePage {
}
async click({ index, columnHeader }: { index: number; columnHeader: string }) {
- return await this.get({ index, columnHeader }).click();
+ await this.get({ index, columnHeader }).click();
+ await (await this.get({ index, columnHeader }).elementHandle()).waitForElementState('stable');
}
async dblclick({ index, columnHeader }: { index?: number; columnHeader: string }) {
@@ -60,6 +61,14 @@ export class CellPageObject extends BasePage {
await this.get({ index, columnHeader }).locator('.nc-action-icon.nc-plus').click();
}
+ async verifyCellActiveSelected({ index, columnHeader }: { index: number; columnHeader: string }) {
+ await expect(this.get({ index, columnHeader })).toHaveClass(/active/);
+ }
+
+ async verifyCellEditable({ index, columnHeader }: { index: number; columnHeader: string }) {
+ await this.get({ index, columnHeader }).isEditable();
+ }
+
async verify({ index, columnHeader, value }: { index: number; columnHeader: string; value: string | string[] }) {
const _verify = async text => {
await expect
diff --git a/tests/playwright/tests/keyboardShortcuts.spec.ts b/tests/playwright/tests/keyboardShortcuts.spec.ts
new file mode 100644
index 0000000000..168bbe948c
--- /dev/null
+++ b/tests/playwright/tests/keyboardShortcuts.spec.ts
@@ -0,0 +1,104 @@
+import { expect, test } from '@playwright/test';
+import { DashboardPage } from '../pages/Dashboard';
+import { GridPage } from '../pages/Dashboard/Grid';
+import setup from '../setup';
+
+test.describe('Verify shortcuts', () => {
+ let dashboard: DashboardPage, grid: GridPage;
+ let context: any;
+
+ test.beforeEach(async ({ page }) => {
+ context = await setup({ page });
+ dashboard = new DashboardPage(page, context.project);
+ grid = dashboard.grid;
+ });
+
+ test('Verify shortcuts', async ({ page }) => {
+ await dashboard.treeView.openTable({ title: 'Country' });
+ // create new table
+ await page.keyboard.press('Alt+t');
+ await dashboard.treeView.createTable({ title: 'New Table', skipOpeningModal: true });
+ await dashboard.treeView.verifyTable({ title: 'New Table' });
+
+ // create new row
+ await grid.column.clickColumnHeader({ title: 'Title' });
+ await page.waitForTimeout(2000);
+ await page.keyboard.press('Alt+r');
+ await grid.editRow({ index: 0, value: 'New Row' });
+ await grid.verifyRowCount({ count: 1 });
+
+ // create new column
+ await page.keyboard.press('Alt+c');
+ await grid.column.fillTitle({ title: 'New Column' });
+ await grid.column.save();
+ await grid.column.verify({ title: 'New Column' });
+
+ // fullscreen
+ await page.keyboard.press('Alt+f');
+ await dashboard.treeView.verifyVisibility({
+ isVisible: false,
+ });
+ await dashboard.viewSidebar.verifyVisibility({
+ isVisible: false,
+ });
+ await page.keyboard.press('Alt+f');
+ await dashboard.treeView.verifyVisibility({
+ isVisible: true,
+ });
+ await dashboard.viewSidebar.verifyVisibility({
+ isVisible: true,
+ });
+
+ // invite team member
+ await page.keyboard.press('Alt+i');
+ await dashboard.settings.teams.invite({
+ email: 'new@example.com',
+ role: 'editor',
+ skipOpeningModal: true,
+ });
+ const url = await dashboard.settings.teams.getInvitationUrl();
+ // await dashboard.settings.teams.closeInvite();
+ expect(url).toContain('signup');
+ await page.waitForTimeout(1000);
+ await dashboard.settings.teams.closeInvite();
+
+ // Cmd + Right arrow
+ await dashboard.treeView.openTable({ title: 'Country' });
+ await page.waitForTimeout(1500);
+ await grid.cell.click({ index: 0, columnHeader: 'Country' });
+ await page.waitForTimeout(1500);
+ await page.keyboard.press((await grid.isMacOs()) ? 'Meta+ArrowRight' : 'Control+ArrowRight');
+ await grid.cell.verifyCellActiveSelected({ index: 0, columnHeader: 'City List' });
+
+ // Cmd + Right arrow
+ await page.keyboard.press((await grid.isMacOs()) ? 'Meta+ArrowLeft' : 'Control+ArrowLeft');
+ await grid.cell.verifyCellActiveSelected({ index: 0, columnHeader: 'Country' });
+
+ // Cmd + up arrow
+ await grid.cell.click({ index: 24, columnHeader: 'Country' });
+ await page.keyboard.press((await grid.isMacOs()) ? 'Meta+ArrowUp' : 'Control+ArrowUp');
+ await grid.cell.verifyCellActiveSelected({ index: 0, columnHeader: 'Country' });
+
+ // Cmd + down arrow
+ await page.keyboard.press((await grid.isMacOs()) ? 'Meta+ArrowDown' : 'Control+ArrowDown');
+ await grid.cell.verifyCellActiveSelected({ index: 24, columnHeader: 'Country' });
+
+ // Enter to edit and Esc to cancel
+ await grid.cell.click({ index: 0, columnHeader: 'Country' });
+ await page.keyboard.press('Enter');
+ await page.keyboard.type('New');
+ await page.keyboard.press('Escape');
+ await grid.cell.verify({ index: 0, columnHeader: 'Country', value: 'AfghanistanNew' });
+
+ // Space to open expanded row and Meta + Space to save
+ await grid.cell.click({ index: 1, columnHeader: 'Country' });
+ await page.keyboard.press('Space');
+ await dashboard.expandedForm.verify({
+ header: 'Algeria',
+ });
+ await dashboard.expandedForm.fillField({ columnTitle: 'Country', value: 'NewAlgeria' });
+ await page.keyboard.press((await grid.isMacOs()) ? 'Meta+Enter' : 'Control+Enter');
+ await page.waitForTimeout(2000);
+ await grid.cell.verify({ index: 1, columnHeader: 'Country', value: 'NewAlgeria' });
+ });
+});
|