mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 18:36:00 -10:00
test(web-shell): witness the downstream click, attention word and cascade order
- click the downstream 'Unblocks' reference in the step-details panel and assert the named node gains aria-pressed, so a dropped onClick in the selectedDependents block turns red - render a node into attention and assert its sr-only span carries the status word beside "Needs attention"; pin the inspector's self-block exclusion beside the dedup test - extend the cascade-order guard to all six status selectors, not just blocked, so regrouping any status tone below the attention rule fails Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmtnn5c4cjw
This commit is contained in:
1 parent
ee08bf95e8
commit
18f9fa0721
3 files changed
+133
-8
No files matched your search
@@ -82,16 +82,27 @@ describe('PlanExecutionView stylesheet', () => {
|
||||
expect(title).toMatch(/color:\s*var\(--foreground\)/);
|
||||
});
|
||||
|
||||
it('declares the attention tone after the blocked/ready tone', () => {
|
||||
// Both selectors carry the same specificity, so the cascade lets the
|
||||
// later rule win: attention must stay declared after the status tones,
|
||||
// or a blocked node that also needs attention would wear the muted
|
||||
// ring instead of the attention tone its comment promises.
|
||||
it('declares the attention tone after every status tone', () => {
|
||||
// All these selectors carry the same specificity, so the cascade lets
|
||||
// the later rule win. Attention co-occurs with every status (a linked
|
||||
// agent task can fail under any of them), so the attention rule must
|
||||
// stay declared after every status tone, or a node that needs attention
|
||||
// would wear its status ring instead of the attention tone its comment
|
||||
// promises — whichever status block a later edit regroups.
|
||||
const attentionAt = planCss.indexOf(".node[data-attention='true']");
|
||||
const neutralAt = planCss.indexOf(".node[data-status='blocked']");
|
||||
expect(attentionAt).toBeGreaterThan(-1);
|
||||
expect(neutralAt).toBeGreaterThan(-1);
|
||||
expect(attentionAt).toBeGreaterThan(neutralAt);
|
||||
for (const status of [
|
||||
'completed',
|
||||
'running',
|
||||
'in_progress',
|
||||
'paused',
|
||||
'blocked',
|
||||
'ready',
|
||||
]) {
|
||||
const statusAt = planCss.indexOf(`.node[data-status='${status}']`);
|
||||
expect(statusAt).toBeGreaterThan(-1);
|
||||
expect(attentionAt).toBeGreaterThan(statusAt);
|
||||
}
|
||||
});
|
||||
|
||||
it('narrows the DAG lane at two viewport steps', () => {
|
||||
|
||||
@@ -594,6 +594,33 @@ describe('PlanExecutionView', () => {
|
||||
act(() => button?.click());
|
||||
expect(onOpen).toHaveBeenCalledWith(agentTool('build'));
|
||||
|
||||
// The downstream block wires its own onClick; witness it before the
|
||||
// upstream click below moves the selection off `build`. Dropping that
|
||||
// handler must turn this red.
|
||||
act(() =>
|
||||
details
|
||||
?.querySelector<HTMLButtonElement>('[data-plan-dependency="verify"]')
|
||||
?.click(),
|
||||
);
|
||||
expect(
|
||||
container
|
||||
.querySelector('[data-plan-node-id="verify"]')
|
||||
?.getAttribute('aria-pressed'),
|
||||
).toBe('true');
|
||||
// That click selected `verify` and re-rendered the panel around it, so
|
||||
// bring the selection back to `build` before exercising the upstream
|
||||
// reference.
|
||||
act(() => {
|
||||
container
|
||||
.querySelector<HTMLButtonElement>('[data-plan-node-id="build"]')
|
||||
?.click();
|
||||
});
|
||||
expect(
|
||||
container
|
||||
.querySelector('[data-plan-node-id="build"]')
|
||||
?.getAttribute('aria-pressed'),
|
||||
).toBe('true');
|
||||
|
||||
// Last, because it moves the selection: following an upstream reference
|
||||
// selects the step it names, which is what makes the dependency list the
|
||||
// graph's navigation rather than a run of text.
|
||||
@@ -663,6 +690,46 @@ describe('PlanExecutionView', () => {
|
||||
container.remove();
|
||||
});
|
||||
|
||||
it('announces attention beside the status word, not instead of it', () => {
|
||||
// A failed descendant puts the node into attention while its own status
|
||||
// stays running. The sr-only span must keep announcing the status word
|
||||
// too — attention is additive for assistive tech, never a replacement.
|
||||
const runningRoot = task('running');
|
||||
const failedChild = task('failed', {
|
||||
id: 'agent-child',
|
||||
toolUseId: 'call-child',
|
||||
parentAgentId: runningRoot.id,
|
||||
});
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
const root = createRoot(container);
|
||||
act(() => {
|
||||
root.render(
|
||||
<I18nProvider language="en">
|
||||
<PlanExecutionView
|
||||
todos={todos}
|
||||
tools={[agentTool('build')]}
|
||||
tasks={[runningRoot, failedChild]}
|
||||
/>
|
||||
</I18nProvider>,
|
||||
);
|
||||
});
|
||||
|
||||
const buildNode = container
|
||||
.querySelector('[data-plan-node-id="build"]')
|
||||
?.closest('article');
|
||||
expect(buildNode?.textContent).toContain('Running');
|
||||
expect(buildNode?.textContent).toContain('Needs attention');
|
||||
expect(
|
||||
buildNode
|
||||
?.querySelector(`.${styles.nodeStatusText}`)
|
||||
?.textContent?.trim(),
|
||||
).toBe('Running, Needs attention');
|
||||
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
});
|
||||
|
||||
// R7-2: a replayed transcript of an interrupted session carries Agent tool
|
||||
// calls still in_progress (or paused) with NO live daemon task. The node
|
||||
// badges render Running/Paused off executionStatus's transcript fallback;
|
||||
|
||||
@@ -266,6 +266,53 @@ describe('SessionWorkflowInspector', () => {
|
||||
container.remove();
|
||||
});
|
||||
|
||||
it('drops a self-reference from the upstream list', () => {
|
||||
// The projection's dependentsByTodo builder skips self-blocks
|
||||
// (session-workflow-model.ts), and the DAG draws no edge for them; the
|
||||
// inspector's upstream filter must apply the same rule, or a todo
|
||||
// naming itself in blockedBy renders a chip that only re-selects the
|
||||
// already-selected step.
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
const root = createRoot(container);
|
||||
const todos: TodoItem[] = [
|
||||
{ id: 'prepare', content: 'Prepare inputs', status: 'completed' },
|
||||
{
|
||||
id: 'ship',
|
||||
content: 'Ship result',
|
||||
status: 'in_progress',
|
||||
blockedBy: ['ship', 'prepare'],
|
||||
},
|
||||
];
|
||||
|
||||
act(() => {
|
||||
root.render(
|
||||
<I18nProvider language="en">
|
||||
<SessionWorkflowInspector
|
||||
todos={todos}
|
||||
tools={[]}
|
||||
tasks={[]}
|
||||
artifacts={[]}
|
||||
selectedTodoId="ship"
|
||||
onSelectedTodoIdChange={vi.fn()}
|
||||
onExpandGraph={vi.fn()}
|
||||
onOpenSubagent={vi.fn()}
|
||||
/>
|
||||
</I18nProvider>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(
|
||||
container.querySelector('[data-testid="workflow-dependency-ship"]'),
|
||||
).toBeNull();
|
||||
expect(
|
||||
container.querySelectorAll('[data-testid="workflow-dependency-prepare"]'),
|
||||
).toHaveLength(1);
|
||||
|
||||
act(() => root.unmount());
|
||||
container.remove();
|
||||
});
|
||||
|
||||
it('keeps every activity row reachable past the preview cap', () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
Reference in new issue
Block a user