mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
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>
63 lines
2.2 KiB
TypeScript
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',
|
|
}),
|
|
);
|
|
});
|
|
});
|