qwen-code/packages/web-shell/client/utils/queueDrain.test.ts
carffuca fce79dd10b
feat(web-shell): friendlier Esc interruption + queued-prompt UX (#6025)
* fix(web-shell): prevent queued-prompt loss from drain race

The auto-drain effect popped a queued prompt, called setQueuedPrompts,
then submitted via setTimeout(0). Because the daemon flips streamingState
asynchronously, the setState re-render could re-run the effect and pop a
second prompt before the first registered as streaming — both submitted
back-to-back and the first was lost.

Arm an "awaiting turn start" gate synchronously at pop so the re-run is
blocked until streamingState goes non-idle, released by a dedicated
effect with a safety-net timer for a prompt that never streams (e.g. a
queued slash command). Cleanup no longer cancels/re-queues the pending
submit while the gate is armed.

* feat(web-shell): friendlier Esc interruption + queued-prompt UX

* refactor(web-shell): tidy Esc/queue code per review

Behavior-preserving cleanups addressing review feedback on the Esc-interruption
and queued-prompt changes:

- Remove the now-dead queue.footer i18n key (EN + ZH) and the unreferenced
  .queuedHint CSS, orphaned when the Esc-clears-queue behavior was dropped.
- Co-locate the queued-prompt styles in QueuedPromptDisplay.module.css instead
  of reaching into the parent App.module.css.
- Make the Esc confirm-window constants the single source of truth: export them
  from escapeIntent.ts and drive the countdown-ring duration from one of them
  via a CSS custom property.
- Nudge the queue-drain safety net with a dedicated tick counter instead of
  cloning queuedPrompts, so it no longer re-renders the composer for a no-op.
- Drop a redundant !compact guard in StatusBar left over from flattening a
  ternary.
- Document the pop/gate-arm ordering invariant in the drain effect.
2026-06-30 01:27:16 +00:00

34 lines
1.1 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { canDrainQueue, type QueueDrainGate } from './queueDrain';
// A gate where every condition is satisfied — the next prompt should drain.
const ready: QueueDrainGate = {
draining: false,
awaitingTurnStart: false,
connected: true,
streaming: false,
interactionBlocked: false,
pendingApproval: false,
queueLength: 1,
};
describe('canDrainQueue', () => {
it('drains when every condition is satisfied', () => {
expect(canDrainQueue(ready)).toBe(true);
});
it('holds the queue when it is empty', () => {
expect(canDrainQueue({ ...ready, queueLength: 0 })).toBe(false);
});
it.each([
['a drain is already in flight', { draining: true }],
['waiting for the prior turn to start', { awaitingTurnStart: true }],
['the connection is down', { connected: false }],
['a turn is streaming', { streaming: true }],
['interaction is blocked', { interactionBlocked: true }],
['a tool approval is pending', { pendingApproval: true }],
] as const)('holds the queue when %s', (_label, override) => {
expect(canDrainQueue({ ...ready, ...override })).toBe(false);
});
});