@robota-sdk/agent-ui-terminal 3.0.0-beta.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1204 -0
- package/LICENSE +661 -0
- package/README.md +50 -0
- package/dist/node/index.d.ts +1224 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +33 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +88 -0
- package/src/App.tsx +157 -0
- package/src/AppPresentation.tsx +190 -0
- package/src/AppView.tsx +26 -0
- package/src/BackgroundTaskPanel.tsx +73 -0
- package/src/CjkTextInput.tsx +336 -0
- package/src/ConfirmPrompt.tsx +140 -0
- package/src/ContextWarningBanner.tsx +45 -0
- package/src/ExecutionWorkspaceDetailPane.tsx +85 -0
- package/src/ExecutionWorkspaceSwitcher.tsx +213 -0
- package/src/HistorySearchOverlay.tsx +150 -0
- package/src/InputArea.tsx +338 -0
- package/src/ListPicker.tsx +158 -0
- package/src/MenuSelect.tsx +159 -0
- package/src/MessageList.tsx +308 -0
- package/src/MultiSelectList.tsx +294 -0
- package/src/PendingActionPrompt.tsx +121 -0
- package/src/PermissionPrompt.tsx +236 -0
- package/src/PluginTUI.tsx +258 -0
- package/src/RoleLabel.tsx +72 -0
- package/src/SafeText.tsx +66 -0
- package/src/SessionEventNotices.tsx +27 -0
- package/src/SessionPicker.tsx +92 -0
- package/src/SessionStatusBar.tsx +80 -0
- package/src/SlashAutocomplete.tsx +154 -0
- package/src/StatusBar.tsx +308 -0
- package/src/StreamingIndicator.tsx +145 -0
- package/src/SupervisedSessionView.tsx +872 -0
- package/src/TextPrompt.tsx +112 -0
- package/src/ThemePicker.tsx +366 -0
- package/src/ToolCommandOutput.tsx +48 -0
- package/src/ToolDiffBlock.tsx +44 -0
- package/src/TransportTUI.tsx +166 -0
- package/src/TuiInteractionChannel.ts +530 -0
- package/src/UpdateNotice.tsx +18 -0
- package/src/UsageSummaryEntry.tsx +49 -0
- package/src/WaveText.tsx +70 -0
- package/src/__tests__/ListPicker.test.tsx +188 -0
- package/src/__tests__/MenuSelect.test.tsx +127 -0
- package/src/__tests__/PendingActionPrompt.test.tsx +193 -0
- package/src/__tests__/PluginTUI.test.tsx +167 -0
- package/src/__tests__/SlashAutocomplete.test.tsx +162 -0
- package/src/__tests__/SupervisedSessionView-render.test.tsx +52 -0
- package/src/__tests__/SupervisedSessionView.test.tsx +1112 -0
- package/src/__tests__/TextPrompt.test.tsx +112 -0
- package/src/__tests__/TransportTUI.test.tsx +124 -0
- package/src/__tests__/TuiInteractionChannel.askUser.test.ts +124 -0
- package/src/__tests__/TuiInteractionChannel.display-contract.test.ts +294 -0
- package/src/__tests__/TuiInteractionChannel.lifecycle.test.ts +650 -0
- package/src/__tests__/UpdateNotice.test.tsx +15 -0
- package/src/__tests__/__fixtures__/korean-text.json +17 -0
- package/src/__tests__/abort-after-permission.test.tsx +160 -0
- package/src/__tests__/abort-streaming-e2e.test.tsx +183 -0
- package/src/__tests__/attached-app-session-switch.test.tsx +317 -0
- package/src/__tests__/attention-bell-and-marks.test.ts +144 -0
- package/src/__tests__/autocomplete-editing-keys.test.tsx +127 -0
- package/src/__tests__/background-task-panel-countdown.test.tsx +75 -0
- package/src/__tests__/background-task-panel.test.tsx +98 -0
- package/src/__tests__/background-task-row-format.test.ts +152 -0
- package/src/__tests__/channel-factory-integration.test.ts +150 -0
- package/src/__tests__/checkpoint-store-per-channel.test.ts +81 -0
- package/src/__tests__/cjk-defer-submit.test.tsx +68 -0
- package/src/__tests__/cjk-fallback-render.test.tsx +89 -0
- package/src/__tests__/cjk-text-input-flow.test.ts +187 -0
- package/src/__tests__/cjk-text-input.test.ts +196 -0
- package/src/__tests__/command-handoff-pty-e2e.test.ts +107 -0
- package/src/__tests__/command-output-summary.test.ts +95 -0
- package/src/__tests__/command-result-handler.test.ts +171 -0
- package/src/__tests__/compact-event-bridge.test.ts +20 -0
- package/src/__tests__/confirm-permission-flow.test.ts +149 -0
- package/src/__tests__/confirm-prompt.test.tsx +103 -0
- package/src/__tests__/contextual-keybindings-input.test.tsx +158 -0
- package/src/__tests__/default-tui-cli-adapter-host-settings.test.ts +47 -0
- package/src/__tests__/effort-reaches-the-session.test.ts +41 -0
- package/src/__tests__/execution-workspace-switcher.test.tsx +147 -0
- package/src/__tests__/execution-workspace-view-model.test.ts +94 -0
- package/src/__tests__/external-prompt-notice.test.tsx +100 -0
- package/src/__tests__/fixtures/command-handoff-driver.tsx +114 -0
- package/src/__tests__/fixtures/fake-editor.sh +7 -0
- package/src/__tests__/fixtures/terminal-handoff-driver.tsx +90 -0
- package/src/__tests__/fork-attach.test.tsx +235 -0
- package/src/__tests__/grant-history-reaches-the-session.test.ts +31 -0
- package/src/__tests__/helpers/terminal-profiles.ts +152 -0
- package/src/__tests__/helpers/vt-cursor-interpreter.ts +175 -0
- package/src/__tests__/history-clear-broadcast.test.ts +66 -0
- package/src/__tests__/history-search-overlay.test.tsx +329 -0
- package/src/__tests__/humanize-tool-name.test.ts +30 -0
- package/src/__tests__/input-area-bottom-border.test.tsx +45 -0
- package/src/__tests__/input-area-flow.test.ts +199 -0
- package/src/__tests__/input-area-focus-handoff.test.tsx +77 -0
- package/src/__tests__/interaction-channel-charter.test.ts +13 -0
- package/src/__tests__/key-hint-consistency.test.tsx +249 -0
- package/src/__tests__/key-hint-footer.test.tsx +69 -0
- package/src/__tests__/live-trace-reaches-session.test.ts +14 -0
- package/src/__tests__/message-list-rendering.test.tsx +474 -0
- package/src/__tests__/numbered-selection-chunk.test.tsx +41 -0
- package/src/__tests__/org-policy-reaches-the-channel.test.ts +77 -0
- package/src/__tests__/output-style-reaches-session.test.ts +35 -0
- package/src/__tests__/palette-consistency.test.ts +329 -0
- package/src/__tests__/permission-prompt-arming.test.tsx +153 -0
- package/src/__tests__/permission-prompt-availability.test.tsx +23 -0
- package/src/__tests__/permission-prompt-peer.test.tsx +34 -0
- package/src/__tests__/permission-prompt-requester.test.tsx +34 -0
- package/src/__tests__/product-display-name.test.tsx +79 -0
- package/src/__tests__/prompt-queue.test.tsx +260 -0
- package/src/__tests__/provider-definitions-reach-the-session.test.ts +75 -0
- package/src/__tests__/pty/ask-user-question.ptytest.ts +72 -0
- package/src/__tests__/pty/background-work-switcher.ptytest.ts +53 -0
- package/src/__tests__/pty/behavior-2437-git.ptytest.ts +350 -0
- package/src/__tests__/pty/fixtures/ask-user-question.jsonl +3 -0
- package/src/__tests__/pty/fixtures/replay-conversation.jsonl +4 -0
- package/src/__tests__/pty/fixtures/screen-005-ask-shapes.jsonl +5 -0
- package/src/__tests__/pty/fixtures/screen-005-permission.jsonl +3 -0
- package/src/__tests__/pty/fixtures/screen-006-colors.jsonl +3 -0
- package/src/__tests__/pty/fixtures/screen-2002-themes.jsonl +5 -0
- package/src/__tests__/pty/flag-tui.ptytest.ts +56 -0
- package/src/__tests__/pty/flow-2006-deep-link.ptytest.ts +320 -0
- package/src/__tests__/pty/ime-cursor-tmux.ptytest.ts +211 -0
- package/src/__tests__/pty/ime-cursor.ptytest.ts +187 -0
- package/src/__tests__/pty/isolated-home.ts +97 -0
- package/src/__tests__/pty/org-policy.ptytest.ts +71 -0
- package/src/__tests__/pty/provider-setup.ptytest.ts +61 -0
- package/src/__tests__/pty/pty-driver.ts +155 -0
- package/src/__tests__/pty/replay-conversation.ptytest.ts +68 -0
- package/src/__tests__/pty/screen-005-prompt-footers.ptytest.ts +135 -0
- package/src/__tests__/pty/screen-006-no-color.ptytest.ts +140 -0
- package/src/__tests__/pty/screen-010-scrollback.ptytest.ts +123 -0
- package/src/__tests__/pty/screen-1993-scrollback.ptytest.ts +124 -0
- package/src/__tests__/pty/screen-2002-themes.ptytest.ts +292 -0
- package/src/__tests__/pty/screen-2670-prepark.ptytest.ts +228 -0
- package/src/__tests__/pty/screen-reader-mode.ptytest.ts +228 -0
- package/src/__tests__/pty/session-attach.ptytest.ts +182 -0
- package/src/__tests__/pty/settings-screen.ptytest.ts +49 -0
- package/src/__tests__/pty/spawn-pty.test.ts +120 -0
- package/src/__tests__/pty/spawn-pty.ts +229 -0
- package/src/__tests__/pty/terminal-handoff.ptytest.ts +79 -0
- package/src/__tests__/pty/trust-prompt.ptytest.ts +134 -0
- package/src/__tests__/pty/tui-pty.ptytest.ts +65 -0
- package/src/__tests__/pty-driver-dispose.test.ts +43 -0
- package/src/__tests__/real-cursor-positioning.test.tsx +264 -0
- package/src/__tests__/rename-broadcast-persistence.test.tsx +140 -0
- package/src/__tests__/render-channel-options.test.ts +201 -0
- package/src/__tests__/render-lifecycle.test.ts +36 -0
- package/src/__tests__/render-markdown.test.ts +164 -0
- package/src/__tests__/rendered-markdown-styling.test.tsx +98 -0
- package/src/__tests__/safe-text-boundary.test.tsx +75 -0
- package/src/__tests__/sandbox-reaches-the-session.test.ts +37 -0
- package/src/__tests__/screen-reader-input.test.tsx +106 -0
- package/src/__tests__/screen-reader-labels.test.ts +91 -0
- package/src/__tests__/screen-reader-menus.test.tsx +200 -0
- package/src/__tests__/screen-reader-pacing.test.ts +230 -0
- package/src/__tests__/screen-reader-prepark-echo.test.tsx +200 -0
- package/src/__tests__/screen-reader-render-options.test.ts +262 -0
- package/src/__tests__/screen-reader-stdout.test.ts +322 -0
- package/src/__tests__/screen-reader-turn-marks-port.test.tsx +85 -0
- package/src/__tests__/screen-reader-turn-signals.test.tsx +125 -0
- package/src/__tests__/sec-019-terminal-sanitizer.test.ts +276 -0
- package/src/__tests__/sec-019-tool-label-render.test.tsx +207 -0
- package/src/__tests__/selection-flow.test.ts +61 -0
- package/src/__tests__/session-event-notices.test.tsx +31 -0
- package/src/__tests__/session-init-poller.test.ts +102 -0
- package/src/__tests__/session-picker.test.tsx +93 -0
- package/src/__tests__/session-switch-channel.test.tsx +470 -0
- package/src/__tests__/shutdown-signal.test.ts +34 -0
- package/src/__tests__/status-activity.test.ts +71 -0
- package/src/__tests__/status-bar.test.tsx +290 -0
- package/src/__tests__/status-glyph.test.ts +30 -0
- package/src/__tests__/streaming-indicator.test.tsx +178 -0
- package/src/__tests__/terminal-capabilities.test.ts +153 -0
- package/src/__tests__/terminal-handoff-controller.test.ts +173 -0
- package/src/__tests__/terminal-handoff-pty-e2e.test.ts +77 -0
- package/src/__tests__/text-prompt-flow.test.ts +77 -0
- package/src/__tests__/theme-picker.test.tsx +345 -0
- package/src/__tests__/transport-boundary.test.ts +11 -0
- package/src/__tests__/tui-app-channel-port.test.ts +31 -0
- package/src/__tests__/tui-channel-init-failure.test.ts +70 -0
- package/src/__tests__/tui-channel-lifecycle-coordinator.test.ts +258 -0
- package/src/__tests__/tui-interaction-queues.test.ts +149 -0
- package/src/__tests__/tui-preset-capabilities.test.ts +33 -0
- package/src/__tests__/tui-state-manager.error-stall.test.ts +169 -0
- package/src/__tests__/tui-state-manager.test.ts +482 -0
- package/src/__tests__/ui-intent-rendering.test.tsx +291 -0
- package/src/__tests__/wave-text.test.tsx +133 -0
- package/src/__tests__/wire-tui-channel.test.ts +1457 -0
- package/src/__tests__/wire-tui-client-commands.test.ts +243 -0
- package/src/app-banner.tsx +42 -0
- package/src/app-static-items.ts +35 -0
- package/src/app-view-model.ts +140 -0
- package/src/attached-session-connection.ts +14 -0
- package/src/attention/__tests__/attention-coordinator.test.ts +166 -0
- package/src/attention/__tests__/attention-tracker.test.ts +103 -0
- package/src/attention/__tests__/countdown.test.ts +15 -0
- package/src/attention/__tests__/focus-input-filter.test.ts +93 -0
- package/src/attention/__tests__/interval-recap.test.ts +49 -0
- package/src/attention/attention-coordinator.ts +71 -0
- package/src/attention/attention-tracker.ts +124 -0
- package/src/attention/countdown.ts +24 -0
- package/src/attention/focus-input-filter.ts +131 -0
- package/src/attention/interval-recap.ts +148 -0
- package/src/attention/time-units.ts +6 -0
- package/src/attention-bell.ts +79 -0
- package/src/attributed-user-echo.ts +30 -0
- package/src/background-task-row-format.ts +99 -0
- package/src/command-interaction.ts +8 -0
- package/src/command-output-summary.ts +129 -0
- package/src/create-default-tui-cli-adapter.ts +45 -0
- package/src/execution-workspace-switcher-selection.ts +118 -0
- package/src/execution-workspace-view-model.ts +139 -0
- package/src/external-prompt-notice.tsx +38 -0
- package/src/flows/__tests__/background-focus-flow.test.ts +48 -0
- package/src/flows/__tests__/defer-submit.test.ts +88 -0
- package/src/flows/__tests__/real-cursor-flow.test.ts +139 -0
- package/src/flows/background-focus-flow.ts +44 -0
- package/src/flows/cjk-cursor-motion.ts +75 -0
- package/src/flows/cjk-text-deletion.ts +53 -0
- package/src/flows/cjk-text-input-flow.ts +248 -0
- package/src/flows/cjk-text-input-types.ts +24 -0
- package/src/flows/confirm-prompt-flow.ts +90 -0
- package/src/flows/defer-submit.ts +67 -0
- package/src/flows/fork-attach-flow.ts +62 -0
- package/src/flows/input-area-flow.ts +188 -0
- package/src/flows/permission-prompt-flow.ts +108 -0
- package/src/flows/real-cursor-flow.ts +83 -0
- package/src/flows/selection-flow.ts +205 -0
- package/src/flows/session-init-poller.ts +76 -0
- package/src/flows/text-prompt-flow.ts +93 -0
- package/src/history-search/__tests__/history-search-flow.test.ts +86 -0
- package/src/history-search/history-search-flow.ts +94 -0
- package/src/history-search/useHistorySearch.ts +336 -0
- package/src/hooks/__tests__/use-app-detach-key.test.tsx +72 -0
- package/src/hooks/__tests__/use-app-loop-escape.test.tsx +87 -0
- package/src/hooks/__tests__/use-plugin-callbacks.test.ts +18 -0
- package/src/hooks/__tests__/use-tui-channel-stability.test.tsx +100 -0
- package/src/hooks/__tests__/useAppThemeState.test.tsx +130 -0
- package/src/hooks/command-result-handler.ts +44 -0
- package/src/hooks/side-effects-types.ts +45 -0
- package/src/hooks/useAppController.ts +271 -0
- package/src/hooks/useAppInputBindings.ts +163 -0
- package/src/hooks/useAppInteractionState.ts +84 -0
- package/src/hooks/useAppLifecycleState.ts +81 -0
- package/src/hooks/useAppOverlays.ts +90 -0
- package/src/hooks/useAppScreenState.ts +81 -0
- package/src/hooks/useAppSubmissionState.ts +45 -0
- package/src/hooks/useAppThemeState.ts +157 -0
- package/src/hooks/useAppWorkspaceState.ts +149 -0
- package/src/hooks/useAppearanceSettings.ts +30 -0
- package/src/hooks/useAutocomplete.ts +101 -0
- package/src/hooks/useBackgroundPanel.ts +58 -0
- package/src/hooks/useCountdownTick.ts +24 -0
- package/src/hooks/useExecutionDetailPage.ts +69 -0
- package/src/hooks/useForkAttach.ts +60 -0
- package/src/hooks/useInputAreaHistorySearch.ts +72 -0
- package/src/hooks/useInputAreaKeys.ts +114 -0
- package/src/hooks/useNumberedSelection.ts +93 -0
- package/src/hooks/usePluginCallbacks.ts +38 -0
- package/src/hooks/usePluginScreenData.ts +85 -0
- package/src/hooks/useRealCursorPosition.ts +125 -0
- package/src/hooks/useRenderMarkdown.ts +38 -0
- package/src/hooks/useScreenReaderTurnSignals.ts +113 -0
- package/src/hooks/useSideEffects.ts +144 -0
- package/src/hooks/useStatusLineSettings.ts +50 -0
- package/src/hooks/useTerminalHandoffSuspension.ts +30 -0
- package/src/hooks/useTuiChannel.ts +141 -0
- package/src/humanize-tool-name.ts +51 -0
- package/src/index.ts +72 -0
- package/src/input-area-rules.tsx +71 -0
- package/src/json-value.ts +18 -0
- package/src/key-hint-footer.tsx +54 -0
- package/src/keybindings/__tests__/keybinding-registry.test.ts +228 -0
- package/src/keybindings/__tests__/node-keybindings-source.test.ts +118 -0
- package/src/keybindings/keybinding-catalogue.ts +171 -0
- package/src/keybindings/keybinding-registry.ts +303 -0
- package/src/keybindings/keybinding-syntax.ts +155 -0
- package/src/keybindings/keybindings-context.tsx +124 -0
- package/src/keybindings/node-keybindings-source.ts +189 -0
- package/src/numbered-list.tsx +113 -0
- package/src/plugin-tui-handlers.ts +163 -0
- package/src/product-display-name-context.tsx +30 -0
- package/src/render-markdown.ts +209 -0
- package/src/render.tsx +746 -0
- package/src/sanitize-terminal-text.ts +171 -0
- package/src/screen-reader-announcement.ts +52 -0
- package/src/screen-reader-context.tsx +32 -0
- package/src/screen-reader-labels.ts +64 -0
- package/src/screen-reader-pacing-context.tsx +47 -0
- package/src/screen-reader-pacing.ts +178 -0
- package/src/screen-reader-stdout.ts +328 -0
- package/src/short-session-id.ts +9 -0
- package/src/shutdown-signal.ts +34 -0
- package/src/slash-command-input.ts +11 -0
- package/src/status-activity.ts +70 -0
- package/src/status-glyph.ts +78 -0
- package/src/terminal-capabilities-context.tsx +27 -0
- package/src/terminal-capabilities.ts +77 -0
- package/src/terminal-focus-reporting.ts +50 -0
- package/src/terminal-handoff-controller.ts +98 -0
- package/src/terminal-marks.ts +63 -0
- package/src/theme/__tests__/built-in-themes.test.ts +117 -0
- package/src/theme/__tests__/color-vision.test.ts +82 -0
- package/src/theme/__tests__/theme-context.test.tsx +146 -0
- package/src/theme/__tests__/theme-document.test.ts +222 -0
- package/src/theme/__tests__/theme-registry.test.ts +138 -0
- package/src/theme/__tests__/theme-styles.test.ts +233 -0
- package/src/theme/built-in-themes.ts +342 -0
- package/src/theme/color-vision.ts +171 -0
- package/src/theme/index.ts +45 -0
- package/src/theme/theme-context.tsx +81 -0
- package/src/theme/theme-contracts.ts +139 -0
- package/src/theme/theme-document.ts +278 -0
- package/src/theme/theme-registry.ts +127 -0
- package/src/theme/theme-styles.ts +177 -0
- package/src/tool-summary-status.ts +59 -0
- package/src/tui-app-channel-port.ts +111 -0
- package/src/tui-channel-lifecycle-coordinator.ts +149 -0
- package/src/tui-channel-options.ts +184 -0
- package/src/tui-cli-adapter-context.tsx +13 -0
- package/src/tui-cli-adapter.ts +24 -0
- package/src/tui-interaction-queues.ts +171 -0
- package/src/tui-session-binding.ts +52 -0
- package/src/tui-session-event-projector.ts +149 -0
- package/src/tui-session-events.ts +89 -0
- package/src/tui-session-notice-store.ts +43 -0
- package/src/tui-session-options.ts +134 -0
- package/src/tui-state-manager.ts +347 -0
- package/src/types.ts +21 -0
- package/src/use-terminal-title.ts +29 -0
- package/src/utils/__tests__/edit-diff.test.ts +426 -0
- package/src/utils/__tests__/paste-detection.test.ts +116 -0
- package/src/utils/__tests__/paste-labels.test.ts +46 -0
- package/src/utils/__tests__/tool-call-extractor.test.ts +227 -0
- package/src/utils/__tests__/tool-diff-summary.test.ts +104 -0
- package/src/utils/edit-diff.ts +151 -0
- package/src/utils/input-top-border.ts +19 -0
- package/src/utils/paste-labels.ts +9 -0
- package/src/utils/tool-call-extractor.ts +92 -0
- package/src/utils/tool-diff-summary.ts +75 -0
- package/src/waiting-loop-stop-notice.ts +23 -0
- package/src/wire-history-sync.ts +159 -0
- package/src/wire-tui-channel.ts +963 -0
- package/src/wire-tui-client-commands.ts +142 -0
- package/src/wire-tui-projection.ts +136 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
IME_CURSOR_SETTINGS,
|
|
4
|
+
TERMINAL_PROFILES,
|
|
5
|
+
REPRESENTATIVE_IME_PTY_CELLS,
|
|
6
|
+
cellLabel,
|
|
7
|
+
expectedImeCursorEnabled,
|
|
8
|
+
} from './helpers/terminal-profiles.js';
|
|
9
|
+
import {
|
|
10
|
+
isInteractiveColorTerminal,
|
|
11
|
+
supportsImeCursorPositioning,
|
|
12
|
+
} from '../terminal-capabilities.js';
|
|
13
|
+
|
|
14
|
+
const ORIG_TTY = process.stdout.isTTY;
|
|
15
|
+
|
|
16
|
+
function setTty(value: boolean): void {
|
|
17
|
+
Object.defineProperty(process.stdout, 'isTTY', { value, configurable: true });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('isInteractiveColorTerminal', () => {
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
vi.unstubAllEnvs();
|
|
23
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: ORIG_TTY, configurable: true });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('NO_COLOR present disables even when empty (the SCREEN-008 regression)', () => {
|
|
27
|
+
vi.stubEnv('FORCE_COLOR', undefined);
|
|
28
|
+
setTty(true);
|
|
29
|
+
vi.stubEnv('NO_COLOR', '');
|
|
30
|
+
expect(isInteractiveColorTerminal()).toBe(false);
|
|
31
|
+
vi.stubEnv('NO_COLOR', '1');
|
|
32
|
+
expect(isInteractiveColorTerminal()).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('honors FORCE_COLOR (0 → off, non-zero → on)', () => {
|
|
36
|
+
vi.stubEnv('NO_COLOR', undefined);
|
|
37
|
+
setTty(false);
|
|
38
|
+
vi.stubEnv('FORCE_COLOR', '0');
|
|
39
|
+
expect(isInteractiveColorTerminal()).toBe(false);
|
|
40
|
+
vi.stubEnv('FORCE_COLOR', '1');
|
|
41
|
+
expect(isInteractiveColorTerminal()).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('falls back to stdout.isTTY when no env override', () => {
|
|
45
|
+
vi.stubEnv('NO_COLOR', undefined);
|
|
46
|
+
vi.stubEnv('FORCE_COLOR', undefined);
|
|
47
|
+
setTty(true);
|
|
48
|
+
expect(isInteractiveColorTerminal()).toBe(true);
|
|
49
|
+
setTty(false);
|
|
50
|
+
expect(isInteractiveColorTerminal()).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('supportsImeCursorPositioning (CLI-062)', () => {
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
vi.unstubAllEnvs();
|
|
57
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: ORIG_TTY, configurable: true });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('on for a plain interactive TTY, off for a non-TTY', () => {
|
|
61
|
+
vi.stubEnv('PRODUCT_IME_CURSOR', undefined);
|
|
62
|
+
vi.stubEnv('TERM_PROGRAM', undefined);
|
|
63
|
+
setTty(true);
|
|
64
|
+
expect(supportsImeCursorPositioning()).toBe(true);
|
|
65
|
+
setTty(false);
|
|
66
|
+
expect(supportsImeCursorPositioning()).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('I5: Apple_Terminal is OFF by default (Korean-IME SIGSEGV is an Apple-side bug)', () => {
|
|
70
|
+
vi.stubEnv('PRODUCT_IME_CURSOR', undefined);
|
|
71
|
+
setTty(true);
|
|
72
|
+
vi.stubEnv('TERM_PROGRAM', 'Apple_Terminal');
|
|
73
|
+
expect(supportsImeCursorPositioning()).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('I5: PRODUCT_IME_CURSOR=1 opts Apple_Terminal in explicitly', () => {
|
|
77
|
+
setTty(true);
|
|
78
|
+
vi.stubEnv('TERM_PROGRAM', 'Apple_Terminal');
|
|
79
|
+
vi.stubEnv('PRODUCT_IME_CURSOR', '1');
|
|
80
|
+
expect(supportsImeCursorPositioning({ override: true })).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('ignores an ambient Fixture Agent override without a host choice', () => {
|
|
84
|
+
setTty(true);
|
|
85
|
+
vi.stubEnv('TERM_PROGRAM', 'Apple_Terminal');
|
|
86
|
+
vi.stubEnv('PRODUCT_IME_CURSOR', '1');
|
|
87
|
+
expect(supportsImeCursorPositioning()).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('PRODUCT_IME_CURSOR=0 is a kill switch even on a capable terminal', () => {
|
|
91
|
+
setTty(true);
|
|
92
|
+
vi.stubEnv('TERM_PROGRAM', undefined);
|
|
93
|
+
vi.stubEnv('PRODUCT_IME_CURSOR', '0');
|
|
94
|
+
expect(supportsImeCursorPositioning({ override: false })).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* CLI-062 terminal matrix — capability half.
|
|
100
|
+
*
|
|
101
|
+
* Terminal detection is ASSERTED here, never assumed: every emulator in the matrix is applied as
|
|
102
|
+
* the environment handshake it really exports (see helpers/terminal-profiles.ts for how each row's
|
|
103
|
+
* values were obtained), and the gate's verdict is compared against the shared expectation the pty
|
|
104
|
+
* matrix asserts behaviourally. The macOS rows (Terminal.app, iTerm2) cannot run their emulator on
|
|
105
|
+
* a non-macOS machine, but their env branch — including Apple_Terminal's default-off I5 branch —
|
|
106
|
+
* is fully exercised by stubbing the variables those terminals set.
|
|
107
|
+
*/
|
|
108
|
+
describe('CLI-062 terminal matrix — supportsImeCursorPositioning per terminal', () => {
|
|
109
|
+
afterEach(() => {
|
|
110
|
+
vi.unstubAllEnvs();
|
|
111
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: ORIG_TTY, configurable: true });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
for (const profile of TERMINAL_PROFILES) {
|
|
115
|
+
for (const override of IME_CURSOR_SETTINGS) {
|
|
116
|
+
const expected = expectedImeCursorEnabled(profile, override);
|
|
117
|
+
it(`${cellLabel(profile, override)} → ${expected ? 'ENABLED' : 'disabled'}`, () => {
|
|
118
|
+
setTty(true);
|
|
119
|
+
// A real terminal exports only its own variables; anything the matrix does not set must
|
|
120
|
+
// be genuinely absent, or a leaked TERM_PROGRAM would silently decide the verdict.
|
|
121
|
+
for (const key of ['TERM', 'TERM_PROGRAM', 'TERM_PROGRAM_VERSION', 'COLORTERM']) {
|
|
122
|
+
vi.stubEnv(key, undefined);
|
|
123
|
+
}
|
|
124
|
+
for (const [key, value] of Object.entries(profile.env)) vi.stubEnv(key, value);
|
|
125
|
+
vi.stubEnv('PRODUCT_IME_CURSOR', override);
|
|
126
|
+
|
|
127
|
+
expect(
|
|
128
|
+
supportsImeCursorPositioning({
|
|
129
|
+
override: override === '1' ? true : override === '0' ? false : undefined,
|
|
130
|
+
}),
|
|
131
|
+
).toBe(expected);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
it('every matrix row keeps its provenance honest (measured rows name a real capture)', () => {
|
|
137
|
+
for (const profile of TERMINAL_PROFILES) {
|
|
138
|
+
expect(profile.note.length, profile.id).toBeGreaterThan(0);
|
|
139
|
+
if (profile.provenance === 'measured') {
|
|
140
|
+
expect(profile.note, profile.id).toMatch(/Captured|pty harness/);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('keeps the process-spawning PTY tier to four boundary representatives', () => {
|
|
146
|
+
expect(REPRESENTATIVE_IME_PTY_CELLS).toEqual([
|
|
147
|
+
{ profileId: 'ghostty', override: undefined },
|
|
148
|
+
{ profileId: 'apple-terminal', override: undefined },
|
|
149
|
+
{ profileId: 'apple-terminal', override: '1' },
|
|
150
|
+
{ profileId: 'ghostty', override: '0' },
|
|
151
|
+
]);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TERM-002: unit tests for the TUI terminal-handoff controller LOGIC.
|
|
3
|
+
*
|
|
4
|
+
* The real Ink suspend/resume + child-process TTY handoff can only be validated on a live terminal
|
|
5
|
+
* (manual / User Execution evidence). These tests pin the controller's observable contract: TTY +
|
|
6
|
+
* mounted-App gating, and the suspend → clear → fn → resume order (including resume-on-throw).
|
|
7
|
+
*/
|
|
8
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
9
|
+
|
|
10
|
+
import { TerminalHandoffController } from '../terminal-handoff-controller.js';
|
|
11
|
+
|
|
12
|
+
const origStdin = Object.getOwnPropertyDescriptor(process.stdin, 'isTTY');
|
|
13
|
+
const origStdout = Object.getOwnPropertyDescriptor(process.stdout, 'isTTY');
|
|
14
|
+
|
|
15
|
+
function setTty(stdin: boolean, stdout: boolean): void {
|
|
16
|
+
Object.defineProperty(process.stdin, 'isTTY', { value: stdin, configurable: true });
|
|
17
|
+
Object.defineProperty(process.stdout, 'isTTY', { value: stdout, configurable: true });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
if (origStdin) Object.defineProperty(process.stdin, 'isTTY', origStdin);
|
|
22
|
+
if (origStdout) Object.defineProperty(process.stdout, 'isTTY', origStdout);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('TerminalHandoffController', () => {
|
|
26
|
+
it('canHandoffTerminal requires a TTY on both streams AND registered App hooks', () => {
|
|
27
|
+
const c = new TerminalHandoffController();
|
|
28
|
+
setTty(true, true);
|
|
29
|
+
expect(c.canHandoffTerminal).toBe(false); // no hooks yet
|
|
30
|
+
|
|
31
|
+
const unregister = c.registerSuspendHooks({ suspend: async () => {}, resume: () => {} });
|
|
32
|
+
expect(c.canHandoffTerminal).toBe(true);
|
|
33
|
+
|
|
34
|
+
setTty(false, true);
|
|
35
|
+
expect(c.canHandoffTerminal).toBe(false); // stdin not a TTY
|
|
36
|
+
|
|
37
|
+
setTty(true, true);
|
|
38
|
+
unregister();
|
|
39
|
+
expect(c.canHandoffTerminal).toBe(false); // App unmounted
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('runs suspend → clear → fn → resume in order', async () => {
|
|
43
|
+
const order: string[] = [];
|
|
44
|
+
const c = new TerminalHandoffController();
|
|
45
|
+
setTty(true, true);
|
|
46
|
+
c.registerSuspendHooks({
|
|
47
|
+
suspend: async () => {
|
|
48
|
+
order.push('suspend');
|
|
49
|
+
},
|
|
50
|
+
resume: () => order.push('resume'),
|
|
51
|
+
});
|
|
52
|
+
c.setInkInstance({ clear: () => order.push('clear') });
|
|
53
|
+
|
|
54
|
+
const result = await c.runWithTerminal(async () => {
|
|
55
|
+
order.push('child');
|
|
56
|
+
return 7;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(result).toBe(7);
|
|
60
|
+
expect(order).toEqual(['suspend', 'clear', 'child', 'resume']);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('resumes even when the child fn throws', async () => {
|
|
64
|
+
const order: string[] = [];
|
|
65
|
+
const c = new TerminalHandoffController();
|
|
66
|
+
setTty(true, true);
|
|
67
|
+
c.registerSuspendHooks({
|
|
68
|
+
suspend: async () => void order.push('suspend'),
|
|
69
|
+
resume: () => order.push('resume'),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await expect(
|
|
73
|
+
c.runWithTerminal(async () => {
|
|
74
|
+
throw new Error('child failed');
|
|
75
|
+
}),
|
|
76
|
+
).rejects.toThrow('child failed');
|
|
77
|
+
expect(order).toEqual(['suspend', 'resume']);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('rejects (does not run fn) when no interactive TTY is available', async () => {
|
|
81
|
+
const c = new TerminalHandoffController();
|
|
82
|
+
setTty(false, false);
|
|
83
|
+
c.registerSuspendHooks({ suspend: async () => {}, resume: () => {} });
|
|
84
|
+
const fn = vi.fn();
|
|
85
|
+
await expect(c.runWithTerminal(fn as never)).rejects.toThrow(/unavailable/);
|
|
86
|
+
expect(fn).not.toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/** SCREEN-1992 TC-06: the negotiated terminal modes bracket the handoff. */
|
|
91
|
+
describe('TerminalHandoffController terminal-mode hooks', () => {
|
|
92
|
+
it('runs preSuspend after suspend and postResume after resume, even when the child throws', async () => {
|
|
93
|
+
const order: string[] = [];
|
|
94
|
+
const c = new TerminalHandoffController();
|
|
95
|
+
setTty(true, true);
|
|
96
|
+
c.registerSuspendHooks({
|
|
97
|
+
suspend: async () => {
|
|
98
|
+
order.push('suspend');
|
|
99
|
+
},
|
|
100
|
+
resume: () => order.push('resume'),
|
|
101
|
+
});
|
|
102
|
+
c.setInkInstance({ clear: () => order.push('clear') });
|
|
103
|
+
c.setTerminalModeHooks({
|
|
104
|
+
preSuspend: () => {
|
|
105
|
+
order.push('mode-off');
|
|
106
|
+
},
|
|
107
|
+
postResume: () => {
|
|
108
|
+
order.push('mode-on');
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await expect(
|
|
113
|
+
c.runWithTerminal(async () => {
|
|
114
|
+
order.push('child');
|
|
115
|
+
throw new Error('child failed');
|
|
116
|
+
}),
|
|
117
|
+
).rejects.toThrow('child failed');
|
|
118
|
+
expect(order).toEqual(['suspend', 'mode-off', 'clear', 'child', 'resume', 'mode-on']);
|
|
119
|
+
|
|
120
|
+
c.setTerminalModeHooks(undefined);
|
|
121
|
+
order.length = 0;
|
|
122
|
+
await c.runWithTerminal(async () => order.push('child'));
|
|
123
|
+
expect(order).toEqual(['suspend', 'clear', 'child', 'resume']);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* SCREEN-2670: `preSuspend` may be async — it drains the parked screen-reader output — and the
|
|
128
|
+
* screen is cleared only after it settles. Without the await, `clear` and the child would run
|
|
129
|
+
* while the drain was still pending, and parked frames would land on top of the child's output.
|
|
130
|
+
*/
|
|
131
|
+
it('awaits an async preSuspend before clearing the screen and starting the child', async () => {
|
|
132
|
+
const order: string[] = [];
|
|
133
|
+
const c = new TerminalHandoffController();
|
|
134
|
+
setTty(true, true);
|
|
135
|
+
c.registerSuspendHooks({
|
|
136
|
+
suspend: async () => {
|
|
137
|
+
order.push('suspend');
|
|
138
|
+
},
|
|
139
|
+
resume: () => order.push('resume'),
|
|
140
|
+
});
|
|
141
|
+
c.setInkInstance({ clear: () => order.push('clear') });
|
|
142
|
+
let finishDrain: () => void = () => {};
|
|
143
|
+
const drained = new Promise<void>((resolve) => {
|
|
144
|
+
finishDrain = resolve;
|
|
145
|
+
});
|
|
146
|
+
c.setTerminalModeHooks({
|
|
147
|
+
preSuspend: async () => {
|
|
148
|
+
order.push('drain-start');
|
|
149
|
+
await drained;
|
|
150
|
+
order.push('drain-end');
|
|
151
|
+
},
|
|
152
|
+
postResume: () => {
|
|
153
|
+
order.push('mode-on');
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const run = c.runWithTerminal(async () => order.push('child'));
|
|
158
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
159
|
+
// The drain has not settled: nothing past it may have run yet.
|
|
160
|
+
expect(order).toEqual(['suspend', 'drain-start']);
|
|
161
|
+
finishDrain();
|
|
162
|
+
await run;
|
|
163
|
+
expect(order).toEqual([
|
|
164
|
+
'suspend',
|
|
165
|
+
'drain-start',
|
|
166
|
+
'drain-end',
|
|
167
|
+
'clear',
|
|
168
|
+
'child',
|
|
169
|
+
'resume',
|
|
170
|
+
'mode-on',
|
|
171
|
+
]);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TEST-007 / TERM-002: real-terminal handoff, end-to-end through a PTY.
|
|
3
|
+
*
|
|
4
|
+
* Drives `fixtures/terminal-handoff-driver.tsx` (a real Ink app using the real
|
|
5
|
+
* `TerminalHandoffController` + `useTerminalHandoffSuspension`) inside a pseudo-terminal. This is the
|
|
6
|
+
* automated replacement for the former manual-only smoke: it proves on a real TTY that
|
|
7
|
+
*
|
|
8
|
+
* 1. the handoff is available (`canHandoffTerminal === true` under a PTY),
|
|
9
|
+
* 2. Ink releases raw mode so the inherited child receives the driver's keystrokes, and
|
|
10
|
+
* 3. `runWithTerminal` RETURNS after the child exits and the TUI resumes (regression guard for the
|
|
11
|
+
* event-loop-starvation hang fixed in the controller — without the stdin release the child's
|
|
12
|
+
* exit is never observed and the TUI hangs forever).
|
|
13
|
+
*/
|
|
14
|
+
import { mkdtempSync, readFileSync, rmSync, realpathSync } from 'node:fs';
|
|
15
|
+
import { tmpdir } from 'node:os';
|
|
16
|
+
import { join } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
20
|
+
|
|
21
|
+
import { createPtyEnv } from './pty/isolated-home.js';
|
|
22
|
+
import { spawnPtyFixture } from './pty/spawn-pty.js';
|
|
23
|
+
|
|
24
|
+
import type { IPtyRunSession } from './pty/spawn-pty.js';
|
|
25
|
+
|
|
26
|
+
const FIXTURE = fileURLToPath(new URL('./fixtures/terminal-handoff-driver.tsx', import.meta.url));
|
|
27
|
+
const PACKAGE_DIR = fileURLToPath(new URL('../..', import.meta.url));
|
|
28
|
+
const TEST_TIMEOUT_MS = 30_000;
|
|
29
|
+
|
|
30
|
+
const tempDirs: string[] = [];
|
|
31
|
+
const sessions: IPtyRunSession[] = [];
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
for (const session of sessions.splice(0)) session.dispose();
|
|
35
|
+
for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('terminal handoff PTY E2E', () => {
|
|
39
|
+
it(
|
|
40
|
+
'hands the real terminal to a child, receives its input, and resumes the TUI',
|
|
41
|
+
async () => {
|
|
42
|
+
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'agent-test-handoff-pty-')));
|
|
43
|
+
tempDirs.push(dir);
|
|
44
|
+
const outputPath = join(dir, 'result.json');
|
|
45
|
+
|
|
46
|
+
const session = spawnPtyFixture(FIXTURE, {
|
|
47
|
+
argv: [outputPath],
|
|
48
|
+
cwd: PACKAGE_DIR,
|
|
49
|
+
// HARNESS-025: isolated HOME — the child must not read the developer's `~`.
|
|
50
|
+
env: createPtyEnv(),
|
|
51
|
+
});
|
|
52
|
+
sessions.push(session);
|
|
53
|
+
|
|
54
|
+
await session.waitFor('READY canHandoff=true', 10_000);
|
|
55
|
+
await session.waitFor('HANDOFF_STARTED', 10_000);
|
|
56
|
+
// Let the child reach its read() before sending the line.
|
|
57
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
58
|
+
await session.sendKeys('hello-from-pty', 10);
|
|
59
|
+
session.write('\r');
|
|
60
|
+
|
|
61
|
+
// The crux: the handoff must RETURN and the TUI must resume (no hang).
|
|
62
|
+
await session.waitFor('RESUMED exit=0', 10_000);
|
|
63
|
+
expect(await session.expectExit(5_000)).toBe(0);
|
|
64
|
+
|
|
65
|
+
const snapshot = session.snapshot();
|
|
66
|
+
expect(snapshot).toContain('CHILD_GOT:[hello-from-pty]');
|
|
67
|
+
|
|
68
|
+
const result = JSON.parse(readFileSync(outputPath, 'utf8')) as {
|
|
69
|
+
canHandoff: boolean;
|
|
70
|
+
exitCode: number;
|
|
71
|
+
returned: boolean;
|
|
72
|
+
};
|
|
73
|
+
expect(result).toEqual({ canHandoff: true, exitCode: 0, returned: true });
|
|
74
|
+
},
|
|
75
|
+
TEST_TIMEOUT_MS,
|
|
76
|
+
);
|
|
77
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
applyTextPromptInput,
|
|
4
|
+
createTextPromptFlowState,
|
|
5
|
+
getTextPromptInputAction,
|
|
6
|
+
type ITextPromptFlowState,
|
|
7
|
+
} from '../flows/text-prompt-flow.js';
|
|
8
|
+
|
|
9
|
+
describe('text prompt flow', () => {
|
|
10
|
+
it('Given typed text When submit is applied Then it emits trimmed submit value', () => {
|
|
11
|
+
const typed = applyTextPromptInput(
|
|
12
|
+
createTextPromptFlowState(),
|
|
13
|
+
{ type: 'insert', value: ' hello ' },
|
|
14
|
+
{ allowEmpty: false },
|
|
15
|
+
).state;
|
|
16
|
+
|
|
17
|
+
const result = applyTextPromptInput(typed, { type: 'submit' }, { allowEmpty: false });
|
|
18
|
+
|
|
19
|
+
expect(result.effect).toEqual({ type: 'submit', value: 'hello' });
|
|
20
|
+
expect(result.state.resolved).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('Given empty required text When submit is applied Then validation error stays in state', () => {
|
|
24
|
+
const result = applyTextPromptInput(
|
|
25
|
+
createTextPromptFlowState(),
|
|
26
|
+
{ type: 'submit' },
|
|
27
|
+
{ allowEmpty: false, validate: (value) => (value.length === 0 ? 'Required' : undefined) },
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
expect(result.effect).toEqual({ type: 'none' });
|
|
31
|
+
expect(result.state.error).toBe('Required');
|
|
32
|
+
expect(result.state.resolved).toBe(false);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('Given defaultable empty text When submit is applied Then empty submit is allowed', () => {
|
|
36
|
+
const result = applyTextPromptInput(
|
|
37
|
+
createTextPromptFlowState(),
|
|
38
|
+
{ type: 'submit' },
|
|
39
|
+
{ allowEmpty: true },
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
expect(result.effect).toEqual({ type: 'submit', value: '' });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('Given text with an error When delete or insert is applied Then the error is cleared', () => {
|
|
46
|
+
const state: ITextPromptFlowState = { value: 'ab', error: 'Invalid', resolved: false };
|
|
47
|
+
|
|
48
|
+
const deleted = applyTextPromptInput(state, { type: 'delete' }, { allowEmpty: false });
|
|
49
|
+
const inserted = applyTextPromptInput(
|
|
50
|
+
state,
|
|
51
|
+
{ type: 'insert', value: 'c' },
|
|
52
|
+
{ allowEmpty: false },
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
expect(deleted.state).toMatchObject({ value: 'a', error: undefined });
|
|
56
|
+
expect(inserted.state).toMatchObject({ value: 'abc', error: undefined });
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('Given prompt is already resolved When input is applied Then no second effect is emitted', () => {
|
|
60
|
+
const state: ITextPromptFlowState = { value: 'done', resolved: true };
|
|
61
|
+
|
|
62
|
+
const result = applyTextPromptInput(state, { type: 'submit' }, { allowEmpty: false });
|
|
63
|
+
|
|
64
|
+
expect(result.effect).toEqual({ type: 'none' });
|
|
65
|
+
expect(result.state).toBe(state);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('Given raw Ink key info When mapped Then terminal details become prompt actions', () => {
|
|
69
|
+
expect(getTextPromptInputAction('', { escape: true })).toEqual({ type: 'cancel' });
|
|
70
|
+
expect(getTextPromptInputAction('', { return: true })).toEqual({ type: 'submit' });
|
|
71
|
+
expect(getTextPromptInputAction('', { backspace: true })).toEqual({ type: 'delete' });
|
|
72
|
+
expect(getTextPromptInputAction('x', { ctrl: false, meta: false })).toEqual({
|
|
73
|
+
type: 'insert',
|
|
74
|
+
value: 'x',
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|