215 lines
5.2 KiB
TypeScript
215 lines
5.2 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import { createInitialState } from "../src/state/create.ts";
|
|
import { getRenderableOptions } from "../src/state/selectors.ts";
|
|
import {
|
|
applyNumberShortcut,
|
|
enterQuestionNoteMode,
|
|
} from "../src/state/transitions.ts";
|
|
import { renderQuestionScreen } from "../src/ui/render-question.ts";
|
|
|
|
function mockTheme() {
|
|
return {
|
|
fg(color: string, text: string) {
|
|
return `<${color}>${text}</${color}>`;
|
|
},
|
|
bg(color: string, text: string) {
|
|
return `{${color}}${text}{/${color}}`;
|
|
},
|
|
bold(text: string) {
|
|
return text;
|
|
},
|
|
} as never;
|
|
}
|
|
|
|
function mockEditor(text = "", renderedLines?: string[]) {
|
|
return {
|
|
getText() {
|
|
return text;
|
|
},
|
|
render() {
|
|
return renderedLines ?? [];
|
|
},
|
|
} as never;
|
|
}
|
|
|
|
test("custom option stays labeled before selection", () => {
|
|
const state = createInitialState({
|
|
questions: [
|
|
{
|
|
id: "q1",
|
|
prompt: "Pick one",
|
|
options: [{ value: "a", label: "A" }],
|
|
},
|
|
],
|
|
});
|
|
|
|
const lines: string[] = [];
|
|
renderQuestionScreen({
|
|
editor: mockEditor(),
|
|
lines,
|
|
options: getRenderableOptions(state.questions[0]),
|
|
question: state.questions[0],
|
|
state,
|
|
theme: mockTheme(),
|
|
width: 80,
|
|
});
|
|
|
|
assert(lines.some((line) => line.includes("Type your own")));
|
|
assert(!lines.some((line) => line.includes("Type your answer...")));
|
|
});
|
|
|
|
test("selected custom option keeps its label and renders editor below", () => {
|
|
let state = createInitialState({
|
|
questions: [
|
|
{
|
|
id: "q1",
|
|
prompt: "Pick one",
|
|
options: [{ value: "a", label: "A" }],
|
|
},
|
|
],
|
|
});
|
|
state = applyNumberShortcut(state, 2);
|
|
|
|
const lines: string[] = [];
|
|
renderQuestionScreen({
|
|
editor: mockEditor("", ["┌────┐", "", "└────┘"]),
|
|
lines,
|
|
options: getRenderableOptions(state.questions[0]),
|
|
question: state.questions[0],
|
|
state,
|
|
theme: mockTheme(),
|
|
width: 80,
|
|
});
|
|
|
|
assert(lines.some((line) => line.includes("Type your own")));
|
|
assert(lines.some((line) => line.includes("Type answer...")));
|
|
});
|
|
|
|
test("open question note renders inline label and editor", () => {
|
|
const state = enterQuestionNoteMode(
|
|
createInitialState({
|
|
questions: [
|
|
{
|
|
id: "q1",
|
|
prompt: "Pick any extra things to include.",
|
|
options: [{ value: "a", label: "A" }],
|
|
},
|
|
],
|
|
}),
|
|
"q1"
|
|
);
|
|
|
|
const lines: string[] = [];
|
|
renderQuestionScreen({
|
|
editor: mockEditor("", ["┌────┐", "", "└────┘"]),
|
|
lines,
|
|
options: getRenderableOptions(state.questions[0]),
|
|
question: state.questions[0],
|
|
state,
|
|
theme: mockTheme(),
|
|
width: 80,
|
|
});
|
|
|
|
const promptIndex = lines.findIndex((line) =>
|
|
line.includes("Pick any extra things to include.")
|
|
);
|
|
const inputIndex = lines.findIndex((line) => line.includes("Add a note..."));
|
|
|
|
assert.notEqual(promptIndex, -1);
|
|
assert.equal(inputIndex, promptIndex + 1);
|
|
assert(!lines.some((line) => line.includes("Note:")));
|
|
});
|
|
|
|
test("selected multiline custom option renders full editor block", () => {
|
|
let state = createInitialState({
|
|
questions: [
|
|
{
|
|
id: "q1",
|
|
prompt: "Pick one",
|
|
options: [{ value: "a", label: "A" }],
|
|
},
|
|
],
|
|
});
|
|
state = applyNumberShortcut(state, 2);
|
|
|
|
const lines: string[] = [];
|
|
renderQuestionScreen({
|
|
editor: mockEditor("first line\nsecond line", [
|
|
"┌────┐",
|
|
"first line",
|
|
"second line",
|
|
"└────┘",
|
|
]),
|
|
lines,
|
|
options: getRenderableOptions(state.questions[0]),
|
|
question: state.questions[0],
|
|
state,
|
|
theme: mockTheme(),
|
|
width: 80,
|
|
});
|
|
|
|
assert(lines.some((line) => line.includes("Type your own")));
|
|
assert(lines.some((line) => line.includes("first line")));
|
|
assert(lines.some((line) => line.includes("second line")));
|
|
assert(!lines.some((line) => line.includes("first line second line")));
|
|
});
|
|
|
|
test("preview questions also show the custom answer option", () => {
|
|
const state = createInitialState({
|
|
questions: [
|
|
{
|
|
id: "q1",
|
|
prompt: "Pick one",
|
|
type: "preview",
|
|
options: [{ value: "a", label: "A", preview: "Preview A" }],
|
|
},
|
|
],
|
|
});
|
|
|
|
const lines: string[] = [];
|
|
renderQuestionScreen({
|
|
editor: mockEditor(),
|
|
lines,
|
|
options: getRenderableOptions(state.questions[0]),
|
|
question: state.questions[0],
|
|
state,
|
|
theme: mockTheme(),
|
|
width: 80,
|
|
});
|
|
|
|
assert(lines.some((line) => line.includes("Type your own")));
|
|
});
|
|
|
|
test("preview custom option reuses the normal inline editor", () => {
|
|
let state = createInitialState({
|
|
questions: [
|
|
{
|
|
id: "q1",
|
|
prompt: "Pick one",
|
|
type: "preview",
|
|
options: [{ value: "a", label: "A", preview: "Preview A" }],
|
|
},
|
|
],
|
|
});
|
|
state = applyNumberShortcut(state, 2);
|
|
|
|
const lines: string[] = [];
|
|
renderQuestionScreen({
|
|
editor: mockEditor("", ["┌────┐", "", "└────┘"]),
|
|
lines,
|
|
options: getRenderableOptions(state.questions[0]),
|
|
question: state.questions[0],
|
|
state,
|
|
theme: mockTheme(),
|
|
width: 80,
|
|
});
|
|
|
|
assert(lines.some((line) => line.includes("Type your own")));
|
|
assert(lines.some((line) => line.includes("Type answer...")));
|
|
const optionIndex = lines.findIndex((line) => line.includes("Type your own"));
|
|
const inputIndex = lines.findIndex((line) => line.includes("Type answer..."));
|
|
assert.equal(inputIndex, optionIndex + 1);
|
|
assert(!lines.some((line) => line.includes("Preview A")));
|
|
});
|