open-design/apps/web/tests/components/GenUISurfaceRenderer.test.tsx
Cursor Agent adc2afd769
feat(web): plugin composer surface — applyPlugin + Rail + Inputs + GenUI renderer
Plan §3.C1–§3.C4.

Web composer integration for the plugin system:

- apps/web/src/state/projects.ts gains:
  * applyPlugin(pluginId, { inputs?, projectId?, grantCaps? }) — wraps
    POST /api/plugins/:id/apply and returns the typed ApplyResult.
  * listPlugins() — wraps GET /api/plugins.
  * renderPluginBriefTemplate(template, inputs) — substitutes
    {{var}} placeholders inside useCase.query as the user types so the
    composer's brief textarea re-renders live.

- New components:
  * InlinePluginsRail — the card strip that lives below the input box
    on Home and inside ChatComposer. Supports 'wide' / 'strip' layouts
    + taskKind / mode filters.
  * ContextChipStrip — typed ContextItem chips above the brief input.
    Optional onRemove for clearing the applied plugin.
  * PluginInputsForm — JSON-Schema-light form rendered between the
    input and Send. Required fields gate Send via onValidityChange;
    string/text/select/number/boolean field types are supported.
  * GenUISurfaceRenderer — first-class confirmation + oauth-prompt
    surfaces (form + choice fall back to a JSON Schema preview +
    free-form textarea until Phase 2A.5).
  * GenUIInbox — drawer that lists every persisted surface answer for
    a project; revoke calls POST /api/projects/:id/genui/:sid/revoke.

- jsdom tests under apps/web/tests/components/:
  * InlinePluginsRail (mount fetch, click → applyPlugin → onApplied,
    taskKind filter)
  * PluginInputsForm (validity gating, default hydration, select
    options)
  * GenUISurfaceRenderer (confirmation true/false branches; oauth
    surface forwards { authorized, connectorId } per spec §10.3.1)

Web test suite: 567 → 575 (added 8 plugin component cases). The
NewProjectPanel / ChatComposer / ProjectView mounts will land in the
follow-up commit so this PR's diff stays reviewable.

Co-authored-by: Tom Huang <1043269994@qq.com>
2026-05-09 11:47:12 +00:00

63 lines
2.2 KiB
TypeScript

// @vitest-environment jsdom
// Plan §3.C3 / §3.C4 — GenUISurfaceRenderer unit test.
//
// Confirms:
// - confirmation surface renders Continue / Cancel buttons; each
// forwards the matching boolean through onAnswered.
// - oauth-prompt surface forwards { authorized: true, connectorId }
// for the connector route, matching the daemon's
// genui_surfaces.value_json contract from spec §10.3.1.
import { afterEach, describe, expect, it, vi } from 'vitest';
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { GenUISurfaceRenderer } from '../../src/components/GenUISurfaceRenderer';
import type { GenUISurfaceSpec } from '@open-design/contracts';
afterEach(() => cleanup());
describe('GenUISurfaceRenderer', () => {
it('confirmation surface emits true on Continue and false on Cancel', async () => {
const surface: GenUISurfaceSpec = {
id: 'media-spend-approval',
kind: 'confirmation',
persist: 'run',
prompt: 'Approve generating up to 4 image variants?',
};
const onAnswered = vi.fn();
render(
<GenUISurfaceRenderer
pending={{ surface, runId: 'run-1' }}
onAnswered={onAnswered}
/>,
);
fireEvent.click(screen.getByTestId('genui-confirm'));
await waitFor(() => expect(onAnswered).toHaveBeenCalledWith(true));
fireEvent.click(screen.getByTestId('genui-cancel'));
await waitFor(() => expect(onAnswered).toHaveBeenLastCalledWith(false));
});
it('oauth-prompt surface forwards the connectorId on Authorize', async () => {
const surface: GenUISurfaceSpec = {
id: '__auto_connector_slack',
kind: 'oauth-prompt',
persist: 'project',
capabilitiesRequired: ['connector:slack'],
oauth: { route: 'connector', connectorId: 'slack' },
};
const onAnswered = vi.fn();
render(
<GenUISurfaceRenderer
pending={{ surface, runId: 'run-1' }}
onAnswered={onAnswered}
/>,
);
fireEvent.click(screen.getByTestId('genui-authorize'));
await waitFor(() =>
expect(onAnswered).toHaveBeenCalledWith({
authorized: true,
connectorId: 'slack',
}),
);
});
});