203 lines
7.7 KiB
TypeScript
203 lines
7.7 KiB
TypeScript
import { expect, test } from '@playwright/test'
|
||
|
||
/**
|
||
* /ocean/booking 行编辑验证(修正版)。
|
||
*
|
||
* 上一版断言写错了:按 Enter 后期待 change 回写 —— 但 Enter 的语义是
|
||
* 「全选当前内容」(便于直接覆写),落库靠 **失焦**。见 utils/editable.ts:
|
||
* if (key === 'Enter') { preventDefault(); stopPropagation(); target.select() }
|
||
* onBlur: commitInputDraft(...) + notifyChange(...)
|
||
* 所以验证回写必须用 blur(点别处 / Tab 切走),不能只按 Enter。
|
||
*/
|
||
test.describe('/ocean/booking 行编辑', () => {
|
||
test.beforeEach(async ({ page }) => {
|
||
await page.goto('/ocean/booking')
|
||
await page.waitForTimeout(2000)
|
||
})
|
||
|
||
test('区域选择已启用(注册时机修复的正面证据)', async ({ page }) => {
|
||
const cls = await page.evaluate(
|
||
() => (document.querySelector('.g3-table .stk-table') as HTMLElement)?.className ?? '',
|
||
)
|
||
console.log('STK CLASSES: ' + cls)
|
||
expect(cls).toContain('area-selection')
|
||
})
|
||
|
||
test('单击单元格进入编辑,焦点落在该格', async ({ page }) => {
|
||
const cell = page
|
||
.locator('.stk-tbody-main tr:not(.padding-top-tr)')
|
||
.first()
|
||
.locator('td')
|
||
.nth(2)
|
||
const before = await cell.innerText()
|
||
console.log('CELL BEFORE: ' + before)
|
||
|
||
await cell.click()
|
||
await page.waitForTimeout(600)
|
||
|
||
// 编辑器出现
|
||
await expect(page.locator('.g3-table__cell-editor-root').first()).toBeVisible()
|
||
|
||
// 焦点在该格的 input 上,且带出原值
|
||
const focused = await page.evaluate(() => {
|
||
const a = document.activeElement as HTMLInputElement | null
|
||
return { tag: a?.tagName, value: a?.value ?? null }
|
||
})
|
||
console.log('FOCUSED: ' + JSON.stringify(focused))
|
||
expect(focused.tag).toBe('INPUT')
|
||
expect(focused.value).toBe(before)
|
||
})
|
||
|
||
test('输入后失焦落库并抛出 change', async ({ page }) => {
|
||
const cell = page
|
||
.locator('.stk-tbody-main tr:not(.padding-top-tr)')
|
||
.first()
|
||
.locator('td')
|
||
.nth(2)
|
||
|
||
await cell.click()
|
||
await page.waitForTimeout(500)
|
||
|
||
await page.keyboard.press('Control+A')
|
||
await page.keyboard.type('测试客户A')
|
||
await page.waitForTimeout(200)
|
||
|
||
// 关键:用失焦提交(不是 Enter —— Enter 的语义是"全选以便覆写",见下一条用例)
|
||
await page.locator('.booking__title').click()
|
||
await page.waitForTimeout(700)
|
||
|
||
const tip = await page.locator('.booking__tip').innerText()
|
||
console.log('TIP: ' + tip.replace(/\n/g, ' | '))
|
||
expect(tip).toContain('测试客户A')
|
||
|
||
// 校验新值已落到单元格上。
|
||
//
|
||
// 试过三种写法,前两种都是错的,记在这里免得再踩:
|
||
// 1) cell.innerText() —— 编辑态里是 <input>,innerText 取不到 value,得空串
|
||
// 2) 断言 input 数量为 0 —— 错。editingActive 一旦为 true,整行可编辑列都保留
|
||
// 编辑器(性能设计),input 一直在,不等于"还在编辑"
|
||
// 3) .g3-table__cell-display —— 也错。失焦后该行仍处编辑态(编辑器未卸载),
|
||
// 展示态节点根本不存在
|
||
//
|
||
// 真正可靠的做法:直接读编辑器 input 的 value(它就是该格当前值的真源),
|
||
// 并用「焦点是否还在该格」判断编辑是否结束。
|
||
const inputValue = await cell.locator('input').first().inputValue()
|
||
console.log('CELL INPUT VALUE AFTER BLUR: ' + inputValue)
|
||
expect(inputValue).toBe('测试客户A')
|
||
|
||
const stillFocused = await page.evaluate(() => {
|
||
const td = document.activeElement?.closest('td')
|
||
return !!td?.querySelector('.g3-table__cell-editor-root')
|
||
})
|
||
console.log('STILL FOCUSED IN EDITOR: ' + stillFocused)
|
||
expect(stillFocused).toBe(false)
|
||
})
|
||
|
||
test('Enter 的语义是全选而非提交', async ({ page }) => {
|
||
const cell = page
|
||
.locator('.stk-tbody-main tr:not(.padding-top-tr)')
|
||
.first()
|
||
.locator('td')
|
||
.nth(2)
|
||
|
||
await cell.click()
|
||
await page.waitForTimeout(500)
|
||
|
||
await page.keyboard.press('Enter')
|
||
await page.waitForTimeout(300)
|
||
|
||
// 全选:input 的 selectionStart/End 覆盖整个值
|
||
const sel = await page.evaluate(() => {
|
||
const a = document.activeElement as HTMLInputElement | null
|
||
return { len: a?.value.length ?? 0, s: a?.selectionStart ?? -1, e: a?.selectionEnd ?? -1 }
|
||
})
|
||
console.log('SELECTION AFTER ENTER: ' + JSON.stringify(sel))
|
||
expect(sel.s).toBe(0)
|
||
expect(sel.e).toBe(sel.len)
|
||
expect(sel.len).toBeGreaterThan(0)
|
||
})
|
||
|
||
/**
|
||
* Esc = **取消编辑并恢复原值**。
|
||
*
|
||
* 与 Excel / AG Grid / Handsontable / antd 的通用约定一致(Enter 提交、Esc 取消)。
|
||
*
|
||
* 改前的错误行为:Esc 只是 blur,而 blur 会走 onBlur → commit + notifyChange,
|
||
* 等于「按 Esc 反而保存了」(从参考实现 fms 原样照抄而来)。
|
||
* 现在 utils/editable.ts 的 Escape 分支会先 ctx.revertRow(row) 再 blur ——
|
||
* 回滚后 input 里是原值,随后的 blur 提交的就是原值,change 被去重挡掉。
|
||
*/
|
||
test('Esc 取消编辑并恢复原值', async ({ page }) => {
|
||
const cell = page
|
||
.locator('.stk-tbody-main tr:not(.padding-top-tr)')
|
||
.first()
|
||
.locator('td')
|
||
.nth(2)
|
||
|
||
const original = (await cell.innerText()).trim()
|
||
console.log('ORIGINAL VALUE: ' + original)
|
||
|
||
await cell.click()
|
||
await page.waitForTimeout(500)
|
||
|
||
await page.keyboard.press('Control+A')
|
||
await page.keyboard.type('不该保存')
|
||
await page.waitForTimeout(200)
|
||
|
||
// 输入过程中不该有 change(草稿机制)
|
||
const tipDuringTyping = await page.locator('.booking__tip').innerText()
|
||
expect(tipDuringTyping).not.toContain('不该保存')
|
||
|
||
await page.keyboard.press('Escape')
|
||
await page.waitForTimeout(700)
|
||
|
||
const tip = await page.locator('.booking__tip').innerText()
|
||
console.log('TIP AFTER ESC: ' + tip.replace(/\n/g, ' | '))
|
||
|
||
// 核心:Esc 不产生 change(值已回滚,被去重挡掉)
|
||
expect(tip).not.toContain('不该保存')
|
||
|
||
// 编辑器里的值应已恢复成原值
|
||
const valueAfterEsc = await cell.locator('input').first().inputValue()
|
||
console.log('INPUT VALUE AFTER ESC: ' + valueAfterEsc)
|
||
expect(valueAfterEsc).toBe(original)
|
||
|
||
// 焦点应已离开编辑器
|
||
const focusedInCell = await page.evaluate(() => {
|
||
const td = document.activeElement?.closest('td')
|
||
return !!td?.querySelector('.g3-table__cell-editor-root')
|
||
})
|
||
console.log('STILL FOCUSED IN EDITOR: ' + focusedInCell)
|
||
expect(focusedInCell).toBe(false)
|
||
})
|
||
|
||
test('Esc 回滚后仍可正常再次编辑并提交', async ({ page }) => {
|
||
const cell = page
|
||
.locator('.stk-tbody-main tr:not(.padding-top-tr)')
|
||
.first()
|
||
.locator('td')
|
||
.nth(2)
|
||
|
||
// 第一次:改后 Esc 回滚
|
||
await cell.click()
|
||
await page.waitForTimeout(400)
|
||
await page.keyboard.press('Control+A')
|
||
await page.keyboard.type('第一次改动')
|
||
await page.keyboard.press('Escape')
|
||
await page.waitForTimeout(600)
|
||
expect(await cell.locator('input').first().inputValue()).not.toBe('第一次改动')
|
||
|
||
// 第二次:正常提交应仍然生效(回滚不能把去重基线弄坏)
|
||
await cell.click()
|
||
await page.waitForTimeout(400)
|
||
await page.keyboard.press('Control+A')
|
||
await page.keyboard.type('第二次改动')
|
||
await page.locator('.booking__title').click()
|
||
await page.waitForTimeout(700)
|
||
|
||
const tip = await page.locator('.booking__tip').innerText()
|
||
console.log('TIP AFTER SECOND COMMIT: ' + tip.replace(/\n/g, ' | '))
|
||
expect(tip).toContain('第二次改动')
|
||
})
|
||
})
|