ktx/packages/cli/test/prompt-navigation.test.ts

116 lines
4.8 KiB
TypeScript
Raw Permalink Normal View History

2026-05-10 23:12:26 +02:00
import { describe, expect, it } from 'vitest';
feat(cli): setup progress spinners, Tab-to-select, and banner polish (#296) * fix(cli): double the height of the setup banner t crossbar * fix(cli): unify setup multi-select hints and make Tab the select key The six interactive multi-select surfaces in `ktx setup` documented three different hint voices, one had no hint at all, and they named two different select keys (Space vs Tab). Tab is the only key that can toggle selection without colliding with type-to-search input, so make it the single documented select key everywhere and compose every hint from one shared fragment vocabulary in prompt-navigation.ts. - Register `updateSettings({ aliases: { tab: 'space' } })` so Tab toggles flat multiselects; the alias applies only to non-text prompts, leaving typed search input (schema/Notion) untouched. - Add the missing hint to the agent-targets prompt and drop the stray "Space to select … Esc …" info line plus the now-dead writeSetupInfo helper. - Replace the schema-scope ad-hoc hint with the searchable-multiselect voice and standardize "filter" -> "search" vocabulary. - Delete DEFAULT_TREE_PICKER_HELP_TEXT and the unused TreePickerChrome.helpText seam; render the shared tree hint instead. * refactor(cli): show LLM check progress for every setup backend Rename runLlmHealthCheckWithProgress to validateModelWithProgress and wrap the Claude subscription and Codex auth probes in the same spinner progress as the Anthropic API and Vertex backends, so each backend shows consistent "Checking <provider> LLM" output during setup. * feat(cli): add ktx-orange progress spinners to setup steps Add a shared runWithCliSpinner helper and a TTY-aware createCliSpinner: an animated clack spinner in a terminal, and a static stderr-only spinner before raw-mode pickers (the table tree picker and demo tour), where the animated spinner's stdin grab would otherwise corrupt the next prompt. Wrap the slow setup waits in progress spinners: managed runtime install, embedding daemon start + first-run model download, embeddings health check, the connection-test gate, and source validation / dbt clone / Metabase discovery. Recolor every spinner frame from clack's magenta to the ktx mascot orange (#FF8A4C) via the static helper and clack's styleFrame option.
2026-06-12 16:43:10 +02:00
import {
FLAT_MULTISELECT_NAVIGATION_HINT,
MULTISELECT_NAVIGATION_FRAGMENTS,
SEARCHABLE_MULTISELECT_NAVIGATION_HINT,
TREE_PICKER_NAVIGATION_HINT,
withMenuOptionSpacing,
withMultiselectNavigation,
withSearchableMultiselectNavigation,
withTextInputNavigation,
} from '../src/prompt-navigation.js';
2026-05-10 23:12:26 +02:00
describe('prompt navigation helpers', () => {
it('leaves compact single-line menu prompts unchanged', () => {
expect(withMenuOptionSpacing('What do you want to do?')).toBe('What do you want to do?');
});
it('adds a blank separator between multiline menu copy and the option list', () => {
expect(withMenuOptionSpacing('Which embedding option should ktx use?\n\nktx uses embeddings for search.')).toBe(
'Which embedding option should ktx use?\n\nktx uses embeddings for search.\n',
2026-05-10 23:12:26 +02:00
);
});
it('does not duplicate an existing option-list separator', () => {
expect(withMenuOptionSpacing('Question\n\nContext\n')).toBe('Question\n\nContext\n');
});
it('keeps multiselect navigation copy multiline so menu renderers can separate it from options', () => {
expect(withMultiselectNavigation('Which sources?')).toBe(
feat(cli): setup progress spinners, Tab-to-select, and banner polish (#296) * fix(cli): double the height of the setup banner t crossbar * fix(cli): unify setup multi-select hints and make Tab the select key The six interactive multi-select surfaces in `ktx setup` documented three different hint voices, one had no hint at all, and they named two different select keys (Space vs Tab). Tab is the only key that can toggle selection without colliding with type-to-search input, so make it the single documented select key everywhere and compose every hint from one shared fragment vocabulary in prompt-navigation.ts. - Register `updateSettings({ aliases: { tab: 'space' } })` so Tab toggles flat multiselects; the alias applies only to non-text prompts, leaving typed search input (schema/Notion) untouched. - Add the missing hint to the agent-targets prompt and drop the stray "Space to select … Esc …" info line plus the now-dead writeSetupInfo helper. - Replace the schema-scope ad-hoc hint with the searchable-multiselect voice and standardize "filter" -> "search" vocabulary. - Delete DEFAULT_TREE_PICKER_HELP_TEXT and the unused TreePickerChrome.helpText seam; render the shared tree hint instead. * refactor(cli): show LLM check progress for every setup backend Rename runLlmHealthCheckWithProgress to validateModelWithProgress and wrap the Claude subscription and Codex auth probes in the same spinner progress as the Anthropic API and Vertex backends, so each backend shows consistent "Checking <provider> LLM" output during setup. * feat(cli): add ktx-orange progress spinners to setup steps Add a shared runWithCliSpinner helper and a TTY-aware createCliSpinner: an animated clack spinner in a terminal, and a static stderr-only spinner before raw-mode pickers (the table tree picker and demo tour), where the animated spinner's stdin grab would otherwise corrupt the next prompt. Wrap the slow setup waits in progress spinners: managed runtime install, embedding daemon start + first-run model download, embeddings health check, the connection-test gate, and source validation / dbt clone / Metabase discovery. Recolor every spinner frame from clack's magenta to the ktx mascot orange (#FF8A4C) via the static helper and clack's styleFrame option.
2026-06-12 16:43:10 +02:00
'Which sources?\nUp/Down to move, Tab to select or unselect, Enter to confirm, Escape to go back, Ctrl+C to exit.',
2026-05-10 23:12:26 +02:00
);
});
feat(cli): setup progress spinners, Tab-to-select, and banner polish (#296) * fix(cli): double the height of the setup banner t crossbar * fix(cli): unify setup multi-select hints and make Tab the select key The six interactive multi-select surfaces in `ktx setup` documented three different hint voices, one had no hint at all, and they named two different select keys (Space vs Tab). Tab is the only key that can toggle selection without colliding with type-to-search input, so make it the single documented select key everywhere and compose every hint from one shared fragment vocabulary in prompt-navigation.ts. - Register `updateSettings({ aliases: { tab: 'space' } })` so Tab toggles flat multiselects; the alias applies only to non-text prompts, leaving typed search input (schema/Notion) untouched. - Add the missing hint to the agent-targets prompt and drop the stray "Space to select … Esc …" info line plus the now-dead writeSetupInfo helper. - Replace the schema-scope ad-hoc hint with the searchable-multiselect voice and standardize "filter" -> "search" vocabulary. - Delete DEFAULT_TREE_PICKER_HELP_TEXT and the unused TreePickerChrome.helpText seam; render the shared tree hint instead. * refactor(cli): show LLM check progress for every setup backend Rename runLlmHealthCheckWithProgress to validateModelWithProgress and wrap the Claude subscription and Codex auth probes in the same spinner progress as the Anthropic API and Vertex backends, so each backend shows consistent "Checking <provider> LLM" output during setup. * feat(cli): add ktx-orange progress spinners to setup steps Add a shared runWithCliSpinner helper and a TTY-aware createCliSpinner: an animated clack spinner in a terminal, and a static stderr-only spinner before raw-mode pickers (the table tree picker and demo tour), where the animated spinner's stdin grab would otherwise corrupt the next prompt. Wrap the slow setup waits in progress spinners: managed runtime install, embedding daemon start + first-run model download, embeddings health check, the connection-test gate, and source validation / dbt clone / Metabase discovery. Recolor every spinner frame from clack's magenta to the ktx mascot orange (#FF8A4C) via the static helper and clack's styleFrame option.
2026-06-12 16:43:10 +02:00
it('appends the searchable hint for autocomplete multiselect prompts', () => {
expect(withSearchableMultiselectNavigation('Choose schemas')).toBe(
'Choose schemas\nUp/Down to move, Tab to select or unselect, Type to search, Enter to confirm, Escape to go back, Ctrl+C to exit.',
);
});
it('does not duplicate the searchable hint when applied twice', () => {
const once = withSearchableMultiselectNavigation('Choose schemas');
expect(withSearchableMultiselectNavigation(once)).toBe(once);
});
it('matches the approved hint wording for each multi-select surface', () => {
expect(FLAT_MULTISELECT_NAVIGATION_HINT).toBe(
'Up/Down to move, Tab to select or unselect, Enter to confirm, Escape to go back, Ctrl+C to exit.',
);
expect(SEARCHABLE_MULTISELECT_NAVIGATION_HINT).toBe(
'Up/Down to move, Tab to select or unselect, Type to search, Enter to confirm, Escape to go back, Ctrl+C to exit.',
);
expect(TREE_PICKER_NAVIGATION_HINT).toBe(
'Up/Down to move, Right/Left to expand or collapse, Tab to select or unselect, Type to search, Enter to confirm, Escape to clear search or go back, Ctrl+C to exit.',
);
});
it('composes every hint from the shared fragment vocabulary so wording cannot drift', () => {
const hints = [
FLAT_MULTISELECT_NAVIGATION_HINT,
SEARCHABLE_MULTISELECT_NAVIGATION_HINT,
TREE_PICKER_NAVIGATION_HINT,
];
const sharedFragments = [
MULTISELECT_NAVIGATION_FRAGMENTS.move,
MULTISELECT_NAVIGATION_FRAGMENTS.select,
MULTISELECT_NAVIGATION_FRAGMENTS.confirm,
MULTISELECT_NAVIGATION_FRAGMENTS.exit,
];
for (const fragment of sharedFragments) {
for (const hint of hints) {
expect(hint).toContain(fragment);
}
}
expect(MULTISELECT_NAVIGATION_FRAGMENTS.select).toBe('Tab to select or unselect');
for (const hint of hints) {
expect(hint).not.toContain('Space');
}
});
2026-05-10 23:12:26 +02:00
it('adds a blank separator between text input helper copy and the editable value', () => {
expect(
withTextInputNavigation(
'Name this PostgreSQL connection\nktx will use this short name in commands and config. You can rename it now.',
2026-05-10 23:12:26 +02:00
),
).toBe(
'Name this PostgreSQL connection\n│\n│ ktx will use this short name in commands and config. You can rename it now.\n│ Press Escape to go back.\n│',
2026-05-10 23:12:26 +02:00
);
});
it('adds a blank separator before compact text input values', () => {
expect(withTextInputNavigation('Project folder path')).toBe('Project folder path\n│ Press Escape to go back.\n│');
2026-05-10 23:12:26 +02:00
});
it('normalizes already hinted text input prompts without duplicating the hint', () => {
expect(
withTextInputNavigation(
'Name this PostgreSQL connection\nktx will use this short name in commands and config. You can rename it now.\nPress Escape to go back.',
2026-05-10 23:12:26 +02:00
),
).toBe(
'Name this PostgreSQL connection\n│\n│ ktx will use this short name in commands and config. You can rename it now.\n│ Press Escape to go back.\n│',
2026-05-10 23:12:26 +02:00
);
});
it('is idempotent when text input navigation is applied twice', () => {
const once = withTextInputNavigation('Project folder path');
expect(withTextInputNavigation(once)).toBe(once);
});
it('is idempotent when text input navigation with body is applied twice', () => {
const once = withTextInputNavigation(
'Name this PostgreSQL connection\nktx will use this short name in commands and config.',
);
expect(withTextInputNavigation(once)).toBe(once);
});
2026-05-10 23:12:26 +02:00
});