feat(ask): add initial ask tool prototype and project setup
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { renderResultText } from "../src/result.ts";
|
||||
import {
|
||||
applyNumberShortcut,
|
||||
createInitialState,
|
||||
submitCustomAnswer,
|
||||
summarizeResult,
|
||||
toAskResult,
|
||||
} from "../src/state.ts";
|
||||
|
||||
test("summarizeResult formats selected answers", () => {
|
||||
let state = createInitialState({
|
||||
questions: [
|
||||
{
|
||||
id: "style",
|
||||
label: "Style",
|
||||
prompt: "How should I frame it?",
|
||||
options: [
|
||||
{ value: "minimal", label: "Minimal" },
|
||||
{ value: "rich", label: "Rich" },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
state = applyNumberShortcut(state, 2);
|
||||
const result = toAskResult(state);
|
||||
|
||||
assert.equal(summarizeResult(result), "Style: Rich");
|
||||
assert.equal(renderResultText(result), "✓ Style: Rich");
|
||||
});
|
||||
|
||||
test("summaries include custom text answers", () => {
|
||||
let state = createInitialState({
|
||||
questions: [
|
||||
{
|
||||
id: "notes",
|
||||
label: "Notes",
|
||||
prompt: "Anything else?",
|
||||
options: [{ value: "none", label: "None" }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
state = applyNumberShortcut(state, 2);
|
||||
state = submitCustomAnswer(state, "Please include examples");
|
||||
const result = toAskResult(state);
|
||||
|
||||
assert.equal(summarizeResult(result), "Notes: Please include examples");
|
||||
assert.equal(
|
||||
renderResultText(result),
|
||||
"✓ Notes: (wrote) Please include examples",
|
||||
);
|
||||
});
|
||||
|
||||
test("cancelled result renders as cancelled", () => {
|
||||
const result = {
|
||||
title: "Interview",
|
||||
cancelled: true,
|
||||
questions: [],
|
||||
answers: {},
|
||||
};
|
||||
|
||||
assert.equal(renderResultText(result), "Cancelled");
|
||||
});
|
||||
@@ -0,0 +1,136 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import {
|
||||
allRequiredAnswered,
|
||||
applyNumberShortcut,
|
||||
cancelFlow,
|
||||
confirmCurrentSelection,
|
||||
createInitialState,
|
||||
getRenderableOptions,
|
||||
moveOption,
|
||||
moveTab,
|
||||
submitCustomAnswer,
|
||||
toggleCurrentMultiOption,
|
||||
} from "../src/state.ts";
|
||||
import type { AskParams } from "../src/types.ts";
|
||||
|
||||
function sampleParams(): AskParams {
|
||||
return {
|
||||
title: "Interview",
|
||||
questions: [
|
||||
{
|
||||
id: "lang",
|
||||
label: "Language",
|
||||
prompt: "What language?",
|
||||
options: [
|
||||
{ value: "py", label: "Python" },
|
||||
{ value: "ts", label: "TypeScript" },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "fe",
|
||||
label: "Frontend",
|
||||
prompt: "Which frameworks?",
|
||||
type: "multi",
|
||||
options: [
|
||||
{ value: "react", label: "React" },
|
||||
{ value: "vue", label: "Vue" },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
test("normalize defaults via initial state", () => {
|
||||
const state = createInitialState({
|
||||
questions: [
|
||||
{
|
||||
id: "q1",
|
||||
prompt: "Question?",
|
||||
options: [{ value: "a", label: "A" }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
assert.equal(state.questions[0].label, "Q1");
|
||||
assert.equal(state.questions[0].type, "single");
|
||||
assert.equal(state.questions[0].allowOther, true);
|
||||
assert.equal(state.questions[0].required, true);
|
||||
assert.equal(
|
||||
getRenderableOptions(state.questions[0]).at(-1)?.label,
|
||||
"Type something.",
|
||||
);
|
||||
});
|
||||
|
||||
test("single-select number shortcut stores answer and advances to next tab", () => {
|
||||
let state = createInitialState(sampleParams());
|
||||
state = applyNumberShortcut(state, 2);
|
||||
|
||||
assert.equal(state.answers.lang.labels[0], "TypeScript");
|
||||
assert.equal(state.currentTab, 1);
|
||||
assert.equal(state.mode, "navigate");
|
||||
});
|
||||
|
||||
test("multi-select space toggles current option and enter advances to submit", () => {
|
||||
let state = createInitialState(sampleParams());
|
||||
state = applyNumberShortcut(state, 1);
|
||||
state = toggleCurrentMultiOption(state);
|
||||
assert.deepEqual(state.answers.fe.labels, ["React"]);
|
||||
|
||||
state = moveOption(state, 1);
|
||||
state = toggleCurrentMultiOption(state);
|
||||
assert.deepEqual(state.answers.fe.labels, ["React", "Vue"]);
|
||||
|
||||
state = confirmCurrentSelection(state);
|
||||
assert.equal(state.mode, "submit");
|
||||
assert.equal(state.currentTab, 2);
|
||||
assert.equal(allRequiredAnswered(state), true);
|
||||
});
|
||||
|
||||
test("selecting other enters input mode and custom submit stores typed answer", () => {
|
||||
let state = createInitialState({
|
||||
questions: [
|
||||
{
|
||||
id: "q1",
|
||||
prompt: "Other?",
|
||||
options: [{ value: "a", label: "A" }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
state = applyNumberShortcut(state, 2);
|
||||
assert.equal(state.mode, "input");
|
||||
state = submitCustomAnswer(state, "my custom answer");
|
||||
|
||||
assert.equal(state.answers.q1.customText, "my custom answer");
|
||||
assert.equal(state.mode, "submit");
|
||||
});
|
||||
|
||||
test("tab navigation wraps including submit tab", () => {
|
||||
let state = createInitialState(sampleParams());
|
||||
state = moveTab(state, -1);
|
||||
assert.equal(state.currentTab, 2);
|
||||
assert.equal(state.mode, "submit");
|
||||
|
||||
state = moveTab(state, 1);
|
||||
assert.equal(state.currentTab, 0);
|
||||
assert.equal(state.mode, "navigate");
|
||||
});
|
||||
|
||||
test("escape cancels from main flow but only exits input mode when typing", () => {
|
||||
let state = createInitialState(sampleParams());
|
||||
state = cancelFlow(state);
|
||||
assert.equal(state.cancelled, true);
|
||||
assert.equal(state.completed, true);
|
||||
|
||||
state = createInitialState({
|
||||
questions: [
|
||||
{ id: "q", prompt: "Q?", options: [{ value: "a", label: "A" }] },
|
||||
],
|
||||
});
|
||||
state = applyNumberShortcut(state, 2);
|
||||
assert.equal(state.mode, "input");
|
||||
state = cancelFlow(state);
|
||||
assert.equal(state.mode, "navigate");
|
||||
assert.equal(state.cancelled, false);
|
||||
});
|
||||
Reference in New Issue
Block a user