diff --git a/e2e/document-renderer.playwright.ts b/e2e/document-renderer.playwright.ts index 0c8769b..c6ef8a0 100644 --- a/e2e/document-renderer.playwright.ts +++ b/e2e/document-renderer.playwright.ts @@ -19,6 +19,7 @@ const UNCTRL = { doc: env('RBA_UNCTRL_DOC'), title: env('RBA_UNCTRL_TITLE') } const GENERAL = { id: env('RBA_GENERAL_PROFILE'), version: env('RBA_GENERAL_VERSION') } const TEMPLATE_PROFILE = `company.${env('RBA_TEMPLATE')}` const ANNOTATION = `§11 批注 ${RUN}:请补充目标量化指标` +const RANGE_ANNOTATION = `§11 范围批注 ${RUN}:「目标」需可量化` const REVISION_AFTER = `§11 修订 ${RUN}:目标改为三项可量化指标` test.skip(!process.env.PLAYWRIGHT_BASE_URL || !OUT, 'set PLAYWRIGHT_BASE_URL and RBA_OUT (browser-acceptance.sh drives this suite)') @@ -433,6 +434,37 @@ test('§11-5b 审核人添加批注与修订', async ({ browser }) => { await expect(review.locator('.drw-diff-add', { hasText: REVISION_AFTER })).toBeVisible({ timeout: 30_000 }) await expect(review.locator('.drw-anno-group .drw-anno-node').first()).toHaveText('项目内容、目标及意义') await expect(page.locator('.drw-canvas-node[title="项目内容、目标及意义"] .drw-badge.review').first()).toBeVisible() + + // 范围批注(06 §2):阅读视图选中节点正文「目标」(码点 5..7),载荷带偏移与摘录,不带 textHash/range。 + await page.getByRole('tab', { name: '阅读视图' }).click() + const nodeText = page.locator('section.drw-reader-node.sel [data-anchor-node]') + await expect(nodeText).toHaveText('项目内容、目标及意义') + await nodeText.evaluate(el => { + const range = document.createRange() + range.setStart(el.firstChild!, 5) + range.setEnd(el.firstChild!, 7) + const sel = window.getSelection()! + sel.removeAllRanges() + sel.addRange(range) + el.dispatchEvent(new MouseEvent('mouseup', { bubbles: true })) + }) + await expect(review.locator('.drw-composer-quote')).toHaveText('「目标」') + await review.getByLabel('批注内容').fill(RANGE_ANNOTATION) + await review.getByRole('button', { name: '添加批注' }).click() + const annotateCalls = () => s.net.filter(n => n.url.includes('/api/commands/rdm.render.review.annotate') && parseJson(n.request)?.params?.text === RANGE_ANNOTATION) + await expect.poll(() => annotateCalls().length, { message: '应分发范围批注', timeout: 30_000 }).toBeGreaterThan(0) + const rangeCall = annotateCalls().at(-1)! + const rangeParams = parseJson(rangeCall.request)?.params + expect(rangeParams, '范围批注载荷').toMatchObject({ textStart: 5, textEnd: 7, quote: '目标' }) + expect(rangeParams).not.toHaveProperty('textHash') + expect(rangeParams).not.toHaveProperty('range') + note(`范围批注回执:${rangeCall.body.slice(0, 300)}`) + const rangeItem = review.locator('.drw-anno', { hasText: RANGE_ANNOTATION }) + const anchored = await rangeItem.locator('.drw-anno-quote').waitFor({ timeout: 30_000 }).then(() => true, () => false) + if (anchored) await expect(rangeItem.locator('.drw-anno-quote')).toHaveText('目标') + expect.soft(anchored, `GAP[RDM] 范围批注未按所选文字保存(回执:${rangeCall.body.slice(0, 200)})`).toBe(true) + await page.getByRole('tab', { name: '结构画布' }).click() + await expectReadableFailures(page) await shot(page, 'item5b-review-annotations') await s.close() diff --git a/src/components/DocumentRenderWorkspace.tsx b/src/components/DocumentRenderWorkspace.tsx index a006b39..90b3c89 100644 --- a/src/components/DocumentRenderWorkspace.tsx +++ b/src/components/DocumentRenderWorkspace.tsx @@ -16,14 +16,14 @@ import { groupDiagnostics, hasFacts, layoutMindMap, loadDeliveryDiagnostics, locateDiagnostic, locateNodeId, mergeDeliveries, moveSelection, nextOperation, nodeAnchorId, nodeFacts, normalizeDeliveries, normalizeDownload, normalizeProfiles, normalizeRenderTrace, normalizeReviewView, partitionAnnotations, partitionRevisions, profileOptionViews, receiptStateView, renderScope, - reviewTargetNode, revisePayload, submissionIdOf, traceAnchor, traceEndpointLabel, traceTypeLabel, truncateMiddle, + reviewTargetNode, revisePayload, selectionAnchor, submissionIdOf, traceAnchor, traceEndpointLabel, traceTypeLabel, truncateMiddle, validateReviewText, visibleNodeIds, visibleOrder, walkDocTree, decisionEntries, decisionPayload, normalizeDocSubmission, currentBindings, bindableRules, normalizeCandidateRules, profileBindPayload, profileUnbindPayload, projectSettingsAllowed, docTypeKeyOf, diagnosticCodeLabel, createSingleFlight, type BindSource, type DecisionKind, type RuleRef, type DocFilter, type DocTree, type DownloadKind, type NodeFacts, type OperationKind, type RenderDelivery, type RenderDiagnostic, type RenderOperation, type RenderProfiles, type RenderTrace, - type RenderTraceEntry, type ReviewAnnotation, type ReviewRevision, + type RenderTraceEntry, type ReviewAnnotation, type ReviewRevision, type SelectionHit, type TextAnchor, } from '../documentRenderPresentation' // ---- 文件保存(交付物与审计导出共用;类型取服务端 contentType,不在客户端重算校验) ---- @@ -170,12 +170,19 @@ function Byline({ name, at }: { name: string; at: string }) { function AnnotationItem({ a, onReply }: { a: ReviewAnnotation; onReply?: (a: ReviewAnnotation) => void }) { return ( -
{a.quote}} + {a.needsReanchor && ( +