@ryan_nookpi/pi-extension-ask-user-question 0.3.0 → 0.3.1
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/coerce.test.ts +72 -0
- package/package.json +35 -35
- package/schema.test.ts +20 -0
- package/schema.ts +20 -6
package/coerce.test.ts
CHANGED
|
@@ -118,4 +118,76 @@ describe("coerceAskUserQuestionParams", () => {
|
|
|
118
118
|
expect(coerceAskUserQuestionParams({ questions: "not-json" }).questions).toEqual([]);
|
|
119
119
|
expect(coerceAskUserQuestionParams({ questions: '{"not":"array"}' }).questions).toEqual([]);
|
|
120
120
|
});
|
|
121
|
+
|
|
122
|
+
it("checkbox default가 배열이면 문자열만 남겨 보존한다", () => {
|
|
123
|
+
const result = coerceAskUserQuestionParams({
|
|
124
|
+
questions: [
|
|
125
|
+
{
|
|
126
|
+
type: "checkbox",
|
|
127
|
+
prompt: "포함할 항목",
|
|
128
|
+
options: ["a", "b", "c"],
|
|
129
|
+
default: ["a", 42, "b", null],
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
});
|
|
133
|
+
expect(result.questions[0].default).toEqual(["a", "b"]);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("default 배열에서 유효 문자열이 없으면 기본값을 비워 둔다", () => {
|
|
137
|
+
const result = coerceAskUserQuestionParams({
|
|
138
|
+
questions: [
|
|
139
|
+
{
|
|
140
|
+
type: "checkbox",
|
|
141
|
+
prompt: "포함할 항목",
|
|
142
|
+
options: ["a", "b"],
|
|
143
|
+
default: [1, 2, null],
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
expect(result.questions[0].default).toBeUndefined();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("질문 수준의 label, empty/non-string 옵션, JSON 되는 빈 문자열 등 세부 변형을 사장 없이 무시한다", () => {
|
|
151
|
+
const result = coerceAskUserQuestionParams({
|
|
152
|
+
questions: [
|
|
153
|
+
{
|
|
154
|
+
id: "labelled",
|
|
155
|
+
type: "radio",
|
|
156
|
+
prompt: "완전한 질문",
|
|
157
|
+
label: "Short label",
|
|
158
|
+
options: [
|
|
159
|
+
" ", // empty string option -> dropped
|
|
160
|
+
42, // non-string, non-object option -> dropped
|
|
161
|
+
{ label: "Label only" }, // label -> promoted to value
|
|
162
|
+
{ value: "raw-value" }, // value -> promoted to label
|
|
163
|
+
{ description: "no label/value" }, // dropped
|
|
164
|
+
{ value: "v", label: "L", description: "with description" },
|
|
165
|
+
],
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(result.questions[0].label).toBe("Short label");
|
|
171
|
+
expect(result.questions[0].options).toEqual([
|
|
172
|
+
{ value: "Label only", label: "Label only" },
|
|
173
|
+
{ value: "raw-value", label: "raw-value" },
|
|
174
|
+
{ value: "v", label: "L", description: "with description" },
|
|
175
|
+
]);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("빈 문자열 questions 필드는 빈 배열로 간주한다", () => {
|
|
179
|
+
expect(coerceAskUserQuestionParams({ questions: " " }).questions).toEqual([]);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("공백만 있는 prompt/label 문자열은 없는 것으로 간주한다", () => {
|
|
183
|
+
const result = coerceAskUserQuestionParams({
|
|
184
|
+
questions: [
|
|
185
|
+
{ type: "radio", prompt: " " }, // whitespace prompt -> dropped
|
|
186
|
+
{ type: "text", prompt: "valid", label: " " }, // whitespace label -> ignored
|
|
187
|
+
],
|
|
188
|
+
});
|
|
189
|
+
expect(result.questions).toHaveLength(1);
|
|
190
|
+
expect(result.questions[0]).toMatchObject({ type: "text", prompt: "valid" });
|
|
191
|
+
expect(result.questions[0].label).toBeUndefined();
|
|
192
|
+
});
|
|
121
193
|
});
|
package/package.json
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
2
|
+
"name": "@ryan_nookpi/pi-extension-ask-user-question",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "AskUserQuestion tool extension for pi.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/Jonghakseo/pi-extension.git",
|
|
9
|
+
"directory": "packages/ask-user-question"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/Jonghakseo/pi-extension/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/Jonghakseo/pi-extension/tree/main/packages/ask-user-question#readme",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"pi-package"
|
|
18
|
+
],
|
|
19
|
+
"files": [
|
|
20
|
+
"*.ts",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"pi": {
|
|
24
|
+
"extensions": [
|
|
25
|
+
"./index.ts"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
30
|
+
"@mariozechner/pi-tui": "*",
|
|
31
|
+
"@sinclair/typebox": "*"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/schema.test.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Value } from "@sinclair/typebox/value";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
|
|
4
|
+
import { AskUserQuestionParams } from "./schema.ts";
|
|
5
|
+
|
|
6
|
+
describe("ask-user-question/schema", () => {
|
|
7
|
+
it("질문 배열 JSON 문자열과 느슨한 질문 필드를 허용한다", () => {
|
|
8
|
+
const payload = {
|
|
9
|
+
questions: JSON.stringify([
|
|
10
|
+
{
|
|
11
|
+
question: "오늘 기분이 어떠세요?",
|
|
12
|
+
type: "radio",
|
|
13
|
+
options: ["좋음", "보통", "나쁨"],
|
|
14
|
+
},
|
|
15
|
+
]),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
expect(Value.Check(AskUserQuestionParams, payload)).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
});
|
package/schema.ts
CHANGED
|
@@ -7,14 +7,25 @@ export const OptionSchema = Type.Object({
|
|
|
7
7
|
description: Type.Optional(Type.String({ description: "옵션 아래에 표시할 보조 설명" })),
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
+
export const LooseOptionSchema = Type.Union([
|
|
11
|
+
OptionSchema,
|
|
12
|
+
Type.String({ description: "문자열 선택지. value/label이 같은 값으로 처리됨" }),
|
|
13
|
+
]);
|
|
14
|
+
|
|
10
15
|
export const QuestionSchema = Type.Object({
|
|
11
|
-
id: Type.String({ description: "질문 고유
|
|
16
|
+
id: Type.Optional(Type.String({ description: "질문 고유 식별자. 생략 시 q1, q2...로 자동 생성" })),
|
|
12
17
|
type: StringEnum(["radio", "checkbox", "text"] as const, {
|
|
13
18
|
description: "질문 유형: radio(단일 선택), checkbox(복수 선택), text(자유 입력)",
|
|
14
19
|
}),
|
|
15
|
-
prompt: Type.String({ description: "사용자에게 표시할 질문 문구" }),
|
|
20
|
+
prompt: Type.Optional(Type.String({ description: "사용자에게 표시할 질문 문구" })),
|
|
21
|
+
question: Type.Optional(Type.String({ description: "prompt 별칭" })),
|
|
16
22
|
label: Type.Optional(Type.String({ description: "탭 바에 표시할 짧은 라벨(기본값: Q1, Q2...)" })),
|
|
17
|
-
options: Type.Optional(
|
|
23
|
+
options: Type.Optional(
|
|
24
|
+
Type.Union([
|
|
25
|
+
Type.Array(LooseOptionSchema, { description: "radio/checkbox용 선택지 목록" }),
|
|
26
|
+
Type.String({ description: "선택지 배열을 직렬화한 JSON 문자열" }),
|
|
27
|
+
]),
|
|
28
|
+
),
|
|
18
29
|
allowOther: Type.Optional(
|
|
19
30
|
Type.Boolean({ description: "'기타...' 직접 입력 옵션 추가 여부 (radio/checkbox 기본값: true)" }),
|
|
20
31
|
),
|
|
@@ -30,7 +41,10 @@ export const QuestionSchema = Type.Object({
|
|
|
30
41
|
export const AskUserQuestionParams = Type.Object({
|
|
31
42
|
title: Type.Optional(Type.String({ description: "폼 상단에 표시할 제목" })),
|
|
32
43
|
description: Type.Optional(Type.String({ description: "폼 상단에 표시할 안내 문구" })),
|
|
33
|
-
questions: Type.
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
questions: Type.Union([
|
|
45
|
+
Type.Array(QuestionSchema, {
|
|
46
|
+
description: "질문 목록. radio는 단일 선택, checkbox는 복수 선택, text는 자유 입력",
|
|
47
|
+
}),
|
|
48
|
+
Type.String({ description: "질문 배열을 직렬화한 JSON 문자열" }),
|
|
49
|
+
]),
|
|
36
50
|
});
|