diff --git a/src/agentkit/server/frontend/e2e/bitable-field-ops.spec.ts b/src/agentkit/server/frontend/e2e/bitable-field-ops.spec.ts index 533b57a..5333f4c 100644 --- a/src/agentkit/server/frontend/e2e/bitable-field-ops.spec.ts +++ b/src/agentkit/server/frontend/e2e/bitable-field-ops.spec.ts @@ -145,4 +145,144 @@ test.describe('Bitable Field Operations E2E', () => { // the select editor renders when editing a select-type cell await page.waitForTimeout(500) }) + + // ── U2: inline field configuration in column header menu ────────────── + + test('C6: edit menu opens InlineFieldConfigurator inline (no drawer)', async ({ page }) => { + await setupBitableWithTable(page, 'E2E内联编辑', '测试表') + + // Open the first column header dropdown + const headerMenu = page.locator('.column-header-menu').first() + await headerMenu.click() + + // Click "编辑字段" — should open the inline configurator, NOT the drawer + await page.getByText('编辑字段').click() + + // Inline configurator renders inside a popover (role=dialog) + await expect(page.locator('.inline-field-configurator')).toBeVisible({ + timeout: 5_000, + }) + + // The right-side drawer must NOT have opened + await expect(page.locator('.ant-drawer-content')).toHaveCount(0) + }) + + test('C7: rename field via inline config updates the grid label', async ({ page }) => { + await setupBitableWithTable(page, 'E2E重命名', '测试表') + + const headerMenu = page.locator('.column-header-menu').first() + await headerMenu.click() + await page.getByText('编辑字段').click() + + // The first input in the inline configurator is the field name + const nameInput = page.locator('.inline-field-configurator input').first() + await expect(nameInput).toBeVisible({ timeout: 5_000 }) + await nameInput.fill('') + await nameInput.fill('重命名后字段') + + // Save + await page.locator('.inline-field-configurator').getByRole('button', { name: '保存' }).click() + + // The grid header should now show the new label + await expect( + page.locator('.column-header-menu__title', { hasText: '重命名后字段' }), + ).toBeVisible({ timeout: 10_000 }) + }) + + test('C8: incompatible type change (text -> number) blocks submit', async ({ page }) => { + await setupBitableWithTable(page, 'E2E类型转换', '测试表') + + // Pick a text column with non-numeric values (the default "名称" field). + const headerMenu = page + .locator('.column-header-menu') + .filter({ hasText: '名称' }) + .first() + await headerMenu.click() + await page.getByText('编辑字段').click() + + // Switch type to number + await page.locator('.inline-field-configurator .ant-select').first().click() + await page.getByRole('option', { name: '数字' }).click() + + // Compatibility warning must appear + await expect( + page.locator('.inline-field-configurator__warning, .inline-field-configurator .ant-alert-warning'), + ).toBeVisible({ timeout: 5_000 }) + + // Save button must be disabled + const saveBtn = page + .locator('.inline-field-configurator') + .getByRole('button', { name: '保存' }) + await expect(saveBtn).toBeDisabled() + }) + + test('C9: select option management inline updates chips after save', async ({ page }) => { + await setupBitableWithTable(page, 'E2E选项管理', '测试表') + + // The "状态" field is a select field. Open its inline editor. + const statusHeader = page + .locator('.column-header-menu') + .filter({ hasText: '状态' }) + .first() + await statusHeader.click() + await page.getByText('编辑字段').click() + + // Add a new option + await page + .locator('.inline-field-configurator') + .getByRole('button', { name: /添加选项/ }) + .click() + const optionInputs = page.locator('.inline-field-configurator__option-row input') + const newOption = optionInputs.last() + await newOption.fill('新选项值') + + // Save + await page.locator('.inline-field-configurator').getByRole('button', { name: '保存' }).click() + + // Inline configurator closes after save + await expect(page.locator('.inline-field-configurator')).toHaveCount(0, { timeout: 10_000 }) + }) + + test('C10: keyboard nav — Tab to header, Enter opens inline editor', async ({ page }) => { + await setupBitableWithTable(page, 'E2E键盘导航', '测试表') + + // Tab until focus reaches the first column header menu (role=button) + for (let i = 0; i < 30; i++) { + await page.keyboard.press('Tab') + const focused = await page.locator(':focus').evaluate((el) => ({ + cls: el.className, + tag: el.tagName, + })) + if (focused.cls.includes('column-header-menu')) break + } + + // Enter opens the dropdown menu + await page.keyboard.press('Enter') + await expect(page.getByText('编辑字段')).toBeVisible({ timeout: 5_000 }) + + // Activate "编辑字段" via keyboard (a-menu supports arrow + Enter) + await page.keyboard.press('Enter') + await expect(page.locator('.inline-field-configurator')).toBeVisible({ timeout: 5_000 }) + + // Focus should be inside the inline configurator's first field + await expect(page.locator('.inline-field-configurator input').first()).toBeFocused() + + // Esc closes the inline configurator + await page.keyboard.press('Escape') + await expect(page.locator('.inline-field-configurator')).toHaveCount(0, { timeout: 5_000 }) + }) + + test('C11: batch management entry still opens FieldManagePanel', async ({ page }) => { + await setupBitableWithTable(page, 'E2E批量管理', '测试表') + + const headerMenu = page.locator('.column-header-menu').first() + await headerMenu.click() + + // Click "批量管理" — should open the right-side drawer + await page.getByText('批量管理').click() + await expect(page.locator('.ant-drawer-content')).toBeVisible({ timeout: 5_000 }) + + // The batch-management hint banner must be present + await expect(page.locator('.field-manage-panel__hint')).toBeVisible() + }) }) diff --git a/src/agentkit/server/frontend/src/components/bitable/BitableGrid.vue b/src/agentkit/server/frontend/src/components/bitable/BitableGrid.vue index c72fb85..5a72d52 100644 --- a/src/agentkit/server/frontend/src/components/bitable/BitableGrid.vue +++ b/src/agentkit/server/frontend/src/components/bitable/BitableGrid.vue @@ -36,18 +36,35 @@ :images="(row[f.id] as IAttachmentMeta[] | null | undefined)" /> - +