mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
Merge pull request #3111 from deepseek-harness/worktree/3003-instant-pending-submit
feat(web): 图片询问点击发送即回显,压缩与传输转入后台
This commit is contained in:
@@ -25,6 +25,7 @@ import { connectFreshWorkspace, newEnglishPage, REPO_ROOT, saveFailureShot } fro
|
||||
const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/fresh-round-trip', import.meta.url))
|
||||
const FIXTURE = fileURLToPath(new URL('../../../snapshots/web/fresh-round-trip/session.jsonl', import.meta.url))
|
||||
const UI_EXPECTED = fileURLToPath(new URL('../../../snapshots/web/fresh-round-trip/ui.expected.md', import.meta.url))
|
||||
const ECHO_EXPECTED = fileURLToPath(new URL('../../../snapshots/web/fresh-round-trip/submission-echo.expected.md', import.meta.url))
|
||||
const WEB_CONTEXT_EXPECTED = fileURLToPath(new URL('../../../snapshots/web/fresh-round-trip/web-context.expected.md', import.meta.url))
|
||||
const MODE = webSnapshotMode()
|
||||
|
||||
@@ -72,7 +73,21 @@ describe('web e2e: fresh round trip through the real assembly', () => {
|
||||
// Arm the host-side settled barrier BEFORE the send click.
|
||||
const settled = scaffold.whenTurnSettled()
|
||||
await input.fill(PROMPT)
|
||||
await input.press('Enter')
|
||||
const echoSnapshot = await input.evaluate(async (element, prompt) => {
|
||||
element.dispatchEvent(new KeyboardEvent('keydown', {
|
||||
key: 'Enter', code: 'Enter', bubbles: true, cancelable: true,
|
||||
}))
|
||||
// sendSession registered its paint yield first. This frame observes the
|
||||
// committed echo while admission remains queued on the following task.
|
||||
await new Promise<void>((resolve) => { requestAnimationFrame(() => { resolve() }) })
|
||||
const echo = document.querySelector<HTMLElement>('[data-submission-echo]')
|
||||
return [
|
||||
`echo: ${echo?.textContent?.includes(prompt) === true ? prompt : '(missing)'}`,
|
||||
`composer: ${JSON.stringify(element.textContent ?? '')}`,
|
||||
`contenteditable: ${element.getAttribute('contenteditable')}`,
|
||||
].join('\n')
|
||||
}, PROMPT)
|
||||
await compareOrRefreshGolden(ECHO_EXPECTED, echoSnapshot, MODE)
|
||||
const sessionId = await settled
|
||||
settledSessionId = sessionId
|
||||
if (MODE === 'record') {
|
||||
@@ -188,6 +203,7 @@ describe('web e2e: fresh round trip through the real assembly', () => {
|
||||
expect(tripwire.warnings).toEqual([])
|
||||
await assertFixtureInventory(SNAPSHOT_DIR, [
|
||||
'session.jsonl',
|
||||
'submission-echo.expected.md',
|
||||
'system-prompt.expected.md',
|
||||
'tool-schemas.expected.json',
|
||||
'web-context.expected.md',
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// @vitest-environment jsdom
|
||||
// Local submission echo over the BUILT client graph (keyless FixtureApiClient
|
||||
// transport): a text-plus-image send paints its echo bubble synchronously on
|
||||
// the submit keystroke — before serialization, transport, or the fixture's
|
||||
// durable admission — with the composer already cleared and editable, and the
|
||||
// durable user/message replaces the echo without a duplicate. The fixture host
|
||||
// echoes the prompt requestId as the durable source's rpcId, so the retirement
|
||||
// path here is the production correlation, not a test hook.
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { expect, it } from 'vitest'
|
||||
import { installAssembledBootEnv, mountAssembledApp } from './assembled-boot.ts'
|
||||
|
||||
installAssembledBootEnv()
|
||||
|
||||
it('paints the submission echo on the send keystroke and swaps it for the durable node', async () => {
|
||||
mountAssembledApp()
|
||||
|
||||
const tree = await screen.findByRole('tree', { name: 'Sessions' }, { timeout: 10_000 })
|
||||
const start = tree.querySelector<HTMLButtonElement>('button[aria-label="New session in fixture"]')
|
||||
if (start === null) throw new Error('fixture Workspace new-session action missing')
|
||||
fireEvent.click(start)
|
||||
|
||||
const composer = await waitFor(() => {
|
||||
const surface = document.querySelector<HTMLElement>('[data-composer-input]')
|
||||
if (surface === null) throw new Error('composer surface missing')
|
||||
return surface
|
||||
}, { timeout: 10_000 })
|
||||
const image = new File([new Uint8Array([137, 80, 78, 71])], 'echoed.png', { type: 'image/png' })
|
||||
fireEvent.paste(composer, {
|
||||
clipboardData: {
|
||||
items: [{ kind: 'file', type: 'image/png', getAsFile: () => image }],
|
||||
getData: () => '',
|
||||
},
|
||||
})
|
||||
await waitFor(() => {
|
||||
if (document.querySelector('[role="group"][aria-label="Pending images"] img') === null) {
|
||||
throw new Error('attachment rail missing')
|
||||
}
|
||||
}, { timeout: 5_000 })
|
||||
fireEvent.paste(composer, {
|
||||
clipboardData: { items: [], getData: () => '回显这条消息' },
|
||||
})
|
||||
await waitFor(() => { expect(composer.textContent).toBe('回显这条消息') })
|
||||
fireEvent.keyDown(composer, { key: 'Enter' })
|
||||
|
||||
// Synchronously after the keystroke: the echo bubble is in the flow with
|
||||
// the draft text and the object-URL preview, while the prompt has not even
|
||||
// been serialized yet (it starts after a paint yield). The composer is
|
||||
// already cleared, editable, and free of the rail.
|
||||
const echo = document.querySelector<HTMLElement>('[data-submission-echo]')
|
||||
if (echo === null) throw new Error('submission echo missing on the send keystroke')
|
||||
expect(echo.textContent).toContain('回显这条消息')
|
||||
expect(echo.querySelector('img')?.getAttribute('src')?.split(':')[0]).toBe('blob')
|
||||
expect(composer.textContent).toBe('')
|
||||
expect(composer.getAttribute('contenteditable')).toBe('true')
|
||||
expect(document.querySelector('[role="group"][aria-label="Pending images"]')).toBeNull()
|
||||
|
||||
// The fixture's durable user/message (source.rpcId echoes the prompt
|
||||
// requestId) replaces the echo: one bubble, no marker left, and the image
|
||||
// now renders from the durable gallery.
|
||||
await waitFor(() => {
|
||||
if (document.querySelector('[data-submission-echo]') !== null) {
|
||||
throw new Error('submission echo still present after the durable node arrived')
|
||||
}
|
||||
}, { timeout: 10_000 })
|
||||
expect(screen.getAllByText('回显这条消息')).toHaveLength(1)
|
||||
await waitFor(() => {
|
||||
if (document.querySelector('[data-align="end"] img') === null) {
|
||||
throw new Error('durable user gallery missing')
|
||||
}
|
||||
}, { timeout: 10_000 })
|
||||
})
|
||||
Reference in New Issue
Block a user