@aigne/doc-smith 0.7.2 → 0.8.0
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/.github/workflows/ci.yml +3 -0
- package/CHANGELOG.md +8 -0
- package/README.md +5 -0
- package/agents/check-detail-result.mjs +7 -0
- package/agents/detail-regenerator.yaml +3 -0
- package/agents/find-items-by-paths.mjs +9 -1
- package/agents/input-generator.mjs +3 -3
- package/agents/load-sources.mjs +2 -1
- package/agents/save-docs.mjs +1 -5
- package/codecov.yml +9 -1
- package/package.json +1 -1
- package/prompts/document/custom-components.md +36 -12
- package/tests/check-detail-result.test.mjs +656 -17
- package/tests/conflict-resolution.test.mjs +118 -0
- package/tests/input-generator.test.mjs +594 -1
- package/tests/kroki-utils.test.mjs +588 -0
- package/tests/load-sources.test.mjs +362 -2
- package/tests/mermaid-validation.test.mjs +541 -0
- package/tests/save-docs.test.mjs +1 -1
- package/tests/utils.test.mjs +2020 -2
- package/utils/conflict-detector.mjs +0 -59
- package/utils/file-utils.mjs +5 -0
- package/utils/kroki-utils.mjs +4 -0
- package/utils/markdown-checker.mjs +3 -3
- package/utils/mermaid-validator.mjs +0 -13
- package/utils/utils.mjs +11 -5
- package/tests/all-validation-cases.test.mjs +0 -686
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
2
5
|
import { parse as parseYAML } from "yaml";
|
|
3
|
-
import { generateYAML } from "../agents/input-generator.mjs";
|
|
6
|
+
import init, { generateYAML } from "../agents/input-generator.mjs";
|
|
4
7
|
|
|
5
8
|
describe("generateYAML", () => {
|
|
6
9
|
// Helper function to parse YAML and verify it's valid
|
|
@@ -938,3 +941,593 @@ describe("generateYAML", () => {
|
|
|
938
941
|
});
|
|
939
942
|
});
|
|
940
943
|
});
|
|
944
|
+
|
|
945
|
+
describe("init", () => {
|
|
946
|
+
// Helper function to create mock prompts
|
|
947
|
+
function createMockPrompts(responses) {
|
|
948
|
+
return {
|
|
949
|
+
checkbox: (options) => {
|
|
950
|
+
const key = options.message.match(/\[(\d+)\/\d+\]/)?.[1] || "default";
|
|
951
|
+
const response = responses[`checkbox_${key}`] || responses["checkbox"] || [];
|
|
952
|
+
return Promise.resolve(response);
|
|
953
|
+
},
|
|
954
|
+
select: (options) => {
|
|
955
|
+
const key = options.message.match(/\[(\d+)\/\d+\]/)?.[1] || "default";
|
|
956
|
+
const response = responses[`select_${key}`] || responses["select"] || "";
|
|
957
|
+
return Promise.resolve(response);
|
|
958
|
+
},
|
|
959
|
+
input: (options) => {
|
|
960
|
+
const key = options.message.match(/\[(\d+)\/\d+\]/)?.[1] || "default";
|
|
961
|
+
const response = responses[`input_${key}`] || responses["input"] || options.default || "";
|
|
962
|
+
return Promise.resolve(response);
|
|
963
|
+
},
|
|
964
|
+
search: () => {
|
|
965
|
+
const response = responses["search"] || "";
|
|
966
|
+
return Promise.resolve(response);
|
|
967
|
+
},
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// Helper function to create temporary directory
|
|
972
|
+
async function createTempDir() {
|
|
973
|
+
const tempDir = join(tmpdir(), `aigne-test-${Date.now()}`);
|
|
974
|
+
await fs.mkdir(tempDir, { recursive: true });
|
|
975
|
+
return tempDir;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Helper function to cleanup temp directory
|
|
979
|
+
async function cleanupTempDir(tempDir) {
|
|
980
|
+
try {
|
|
981
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
982
|
+
} catch {
|
|
983
|
+
// Ignore cleanup errors since they don't affect test results
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
describe("Complete init workflow", () => {
|
|
988
|
+
test("should complete full init workflow with typical developer responses", async () => {
|
|
989
|
+
const tempDir = await createTempDir();
|
|
990
|
+
|
|
991
|
+
try {
|
|
992
|
+
const mockResponses = {
|
|
993
|
+
checkbox_1: ["getStarted", "findAnswers"], // Document purpose
|
|
994
|
+
checkbox_2: ["developers"], // Target audience
|
|
995
|
+
select_3: "domainFamiliar", // Reader knowledge level
|
|
996
|
+
select_4: "balancedCoverage", // Documentation depth
|
|
997
|
+
select_5: "en", // Primary language
|
|
998
|
+
checkbox_6: ["zh", "ja"], // Translation languages
|
|
999
|
+
input_7: join(tempDir, "docs"), // Documentation directory
|
|
1000
|
+
search: "", // Source paths (empty to finish)
|
|
1001
|
+
};
|
|
1002
|
+
|
|
1003
|
+
const mockPrompts = createMockPrompts(mockResponses);
|
|
1004
|
+
const options = { prompts: mockPrompts };
|
|
1005
|
+
|
|
1006
|
+
const result = await init(
|
|
1007
|
+
{
|
|
1008
|
+
outputPath: tempDir,
|
|
1009
|
+
fileName: "config.yaml",
|
|
1010
|
+
skipIfExists: false,
|
|
1011
|
+
},
|
|
1012
|
+
options,
|
|
1013
|
+
);
|
|
1014
|
+
|
|
1015
|
+
// Check that function completed successfully
|
|
1016
|
+
expect(result).toEqual({});
|
|
1017
|
+
|
|
1018
|
+
// Check that config file was created
|
|
1019
|
+
const configPath = join(tempDir, "config.yaml");
|
|
1020
|
+
const configExists = await fs
|
|
1021
|
+
.access(configPath)
|
|
1022
|
+
.then(() => true)
|
|
1023
|
+
.catch(() => false);
|
|
1024
|
+
expect(configExists).toBe(true);
|
|
1025
|
+
|
|
1026
|
+
// Verify the generated config content
|
|
1027
|
+
const configContent = await fs.readFile(configPath, "utf8");
|
|
1028
|
+
const config = parseYAML(configContent);
|
|
1029
|
+
|
|
1030
|
+
expect(config.documentPurpose).toEqual(["getStarted", "findAnswers"]);
|
|
1031
|
+
expect(config.targetAudienceTypes).toEqual(["developers"]);
|
|
1032
|
+
expect(config.readerKnowledgeLevel).toBe("domainFamiliar");
|
|
1033
|
+
expect(config.documentationDepth).toBe("balancedCoverage");
|
|
1034
|
+
expect(config.locale).toBe("en");
|
|
1035
|
+
expect(config.translateLanguages).toEqual(["zh", "ja"]);
|
|
1036
|
+
expect(config.docsDir).toBe(join(tempDir, "docs"));
|
|
1037
|
+
expect(config.sourcesPath).toEqual(["./"]); // Default when no paths provided
|
|
1038
|
+
} finally {
|
|
1039
|
+
await cleanupTempDir(tempDir);
|
|
1040
|
+
}
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
test("should handle mixed purpose workflow with priority selection", async () => {
|
|
1044
|
+
const tempDir = await createTempDir();
|
|
1045
|
+
|
|
1046
|
+
try {
|
|
1047
|
+
const mockResponses = {
|
|
1048
|
+
checkbox_1: ["mixedPurpose"], // Document purpose - triggers follow-up
|
|
1049
|
+
checkbox: ["completeTasks", "findAnswers"], // Top priorities after mixedPurpose
|
|
1050
|
+
checkbox_2: ["developers", "devops"], // Target audience
|
|
1051
|
+
select_3: "experiencedUsers", // Reader knowledge level
|
|
1052
|
+
select_4: "comprehensive", // Documentation depth
|
|
1053
|
+
select_5: "zh-CN", // Primary language
|
|
1054
|
+
checkbox_6: ["en"], // Translation languages
|
|
1055
|
+
input_7: join(tempDir, "documentation"), // Documentation directory
|
|
1056
|
+
search: "", // Source paths (empty to finish)
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
const mockPrompts = createMockPrompts(mockResponses);
|
|
1060
|
+
const options = { prompts: mockPrompts };
|
|
1061
|
+
|
|
1062
|
+
const result = await init(
|
|
1063
|
+
{
|
|
1064
|
+
outputPath: tempDir,
|
|
1065
|
+
fileName: "test-config.yaml",
|
|
1066
|
+
skipIfExists: false,
|
|
1067
|
+
},
|
|
1068
|
+
options,
|
|
1069
|
+
);
|
|
1070
|
+
|
|
1071
|
+
expect(result).toEqual({});
|
|
1072
|
+
|
|
1073
|
+
// Verify the generated config
|
|
1074
|
+
const configPath = join(tempDir, "test-config.yaml");
|
|
1075
|
+
const configContent = await fs.readFile(configPath, "utf8");
|
|
1076
|
+
const config = parseYAML(configContent);
|
|
1077
|
+
|
|
1078
|
+
expect(config.documentPurpose).toEqual(["completeTasks", "findAnswers"]);
|
|
1079
|
+
expect(config.targetAudienceTypes).toEqual(["developers", "devops"]);
|
|
1080
|
+
expect(config.readerKnowledgeLevel).toBe("experiencedUsers");
|
|
1081
|
+
expect(config.documentationDepth).toBe("comprehensive");
|
|
1082
|
+
expect(config.locale).toBe("zh-CN");
|
|
1083
|
+
expect(config.translateLanguages).toEqual(["en"]);
|
|
1084
|
+
} finally {
|
|
1085
|
+
await cleanupTempDir(tempDir);
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
test("should handle end-user focused minimal configuration", async () => {
|
|
1090
|
+
const tempDir = await createTempDir();
|
|
1091
|
+
|
|
1092
|
+
try {
|
|
1093
|
+
const mockResponses = {
|
|
1094
|
+
checkbox_1: ["getStarted"], // Document purpose
|
|
1095
|
+
checkbox_2: ["endUsers"], // Target audience
|
|
1096
|
+
select_3: "completeBeginners", // Reader knowledge level
|
|
1097
|
+
select_4: "essentialOnly", // Documentation depth
|
|
1098
|
+
select_5: "en", // Primary language
|
|
1099
|
+
checkbox_6: [], // No translation languages
|
|
1100
|
+
input_7: join(tempDir, "simple-docs"), // Documentation directory
|
|
1101
|
+
search: "", // Source paths (empty to finish)
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
const mockPrompts = createMockPrompts(mockResponses);
|
|
1105
|
+
const options = { prompts: mockPrompts };
|
|
1106
|
+
|
|
1107
|
+
const result = await init(
|
|
1108
|
+
{
|
|
1109
|
+
outputPath: tempDir,
|
|
1110
|
+
fileName: "simple-config.yaml",
|
|
1111
|
+
skipIfExists: false,
|
|
1112
|
+
},
|
|
1113
|
+
options,
|
|
1114
|
+
);
|
|
1115
|
+
|
|
1116
|
+
expect(result).toEqual({});
|
|
1117
|
+
|
|
1118
|
+
const configPath = join(tempDir, "simple-config.yaml");
|
|
1119
|
+
const configContent = await fs.readFile(configPath, "utf8");
|
|
1120
|
+
const config = parseYAML(configContent);
|
|
1121
|
+
|
|
1122
|
+
expect(config.documentPurpose).toEqual(["getStarted"]);
|
|
1123
|
+
expect(config.targetAudienceTypes).toEqual(["endUsers"]);
|
|
1124
|
+
expect(config.readerKnowledgeLevel).toBe("completeBeginners");
|
|
1125
|
+
expect(config.documentationDepth).toBe("essentialOnly");
|
|
1126
|
+
expect(config.locale).toBe("en");
|
|
1127
|
+
expect(config.translateLanguages).toBeUndefined();
|
|
1128
|
+
} finally {
|
|
1129
|
+
await cleanupTempDir(tempDir);
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
describe("Source paths handling", () => {
|
|
1135
|
+
test("should handle multiple source paths input when search returns valid paths", async () => {
|
|
1136
|
+
const tempDir = await createTempDir();
|
|
1137
|
+
|
|
1138
|
+
try {
|
|
1139
|
+
let searchCallCount = 0;
|
|
1140
|
+
const sourcePaths = ["./src", "./lib", "./packages", ""];
|
|
1141
|
+
|
|
1142
|
+
const mockPrompts = {
|
|
1143
|
+
checkbox: () => Promise.resolve(["getStarted"]),
|
|
1144
|
+
select: () => Promise.resolve("en"),
|
|
1145
|
+
input: () => Promise.resolve(join(tempDir, "docs")),
|
|
1146
|
+
search: () => {
|
|
1147
|
+
const response = sourcePaths[searchCallCount];
|
|
1148
|
+
searchCallCount++;
|
|
1149
|
+
return Promise.resolve(response);
|
|
1150
|
+
},
|
|
1151
|
+
};
|
|
1152
|
+
|
|
1153
|
+
const options = { prompts: mockPrompts };
|
|
1154
|
+
|
|
1155
|
+
// First let's create the directories that will be searched for
|
|
1156
|
+
await fs.mkdir(join(process.cwd(), "src"), { recursive: true }).catch(() => {
|
|
1157
|
+
// Ignore if directory already exists
|
|
1158
|
+
});
|
|
1159
|
+
await fs.mkdir(join(process.cwd(), "lib"), { recursive: true }).catch(() => {
|
|
1160
|
+
// Ignore if directory already exists
|
|
1161
|
+
});
|
|
1162
|
+
await fs.mkdir(join(process.cwd(), "packages"), { recursive: true }).catch(() => {
|
|
1163
|
+
// Ignore if directory already exists
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
try {
|
|
1167
|
+
const result = await init(
|
|
1168
|
+
{ outputPath: tempDir, fileName: "config.yaml", skipIfExists: false },
|
|
1169
|
+
options,
|
|
1170
|
+
);
|
|
1171
|
+
|
|
1172
|
+
expect(result).toEqual({});
|
|
1173
|
+
|
|
1174
|
+
const configPath = join(tempDir, "config.yaml");
|
|
1175
|
+
const configContent = await fs.readFile(configPath, "utf8");
|
|
1176
|
+
const config = parseYAML(configContent);
|
|
1177
|
+
|
|
1178
|
+
// Should contain the paths that were added before empty string
|
|
1179
|
+
expect(config.sourcesPath.length).toBeGreaterThan(0);
|
|
1180
|
+
} finally {
|
|
1181
|
+
// Clean up test directories
|
|
1182
|
+
await fs.rm(join(process.cwd(), "src"), { recursive: true, force: true }).catch(() => {
|
|
1183
|
+
// Ignore cleanup errors since directories may not exist
|
|
1184
|
+
});
|
|
1185
|
+
await fs.rm(join(process.cwd(), "lib"), { recursive: true, force: true }).catch(() => {
|
|
1186
|
+
// Ignore cleanup errors since directories may not exist
|
|
1187
|
+
});
|
|
1188
|
+
await fs
|
|
1189
|
+
.rm(join(process.cwd(), "packages"), { recursive: true, force: true })
|
|
1190
|
+
.catch(() => {
|
|
1191
|
+
// Ignore cleanup errors since directories may not exist
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
} finally {
|
|
1195
|
+
await cleanupTempDir(tempDir);
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1199
|
+
test("should use default source path when no paths provided", async () => {
|
|
1200
|
+
const tempDir = await createTempDir();
|
|
1201
|
+
|
|
1202
|
+
try {
|
|
1203
|
+
const mockPrompts = {
|
|
1204
|
+
checkbox: () => Promise.resolve(["getStarted"]),
|
|
1205
|
+
select: () => Promise.resolve("en"),
|
|
1206
|
+
input: () => Promise.resolve(join(tempDir, "docs")),
|
|
1207
|
+
search: () => Promise.resolve(""), // Immediately finish without adding paths
|
|
1208
|
+
};
|
|
1209
|
+
|
|
1210
|
+
const options = { prompts: mockPrompts };
|
|
1211
|
+
|
|
1212
|
+
const result = await init(
|
|
1213
|
+
{ outputPath: tempDir, fileName: "config.yaml", skipIfExists: false },
|
|
1214
|
+
options,
|
|
1215
|
+
);
|
|
1216
|
+
|
|
1217
|
+
expect(result).toEqual({});
|
|
1218
|
+
|
|
1219
|
+
const configPath = join(tempDir, "config.yaml");
|
|
1220
|
+
const configContent = await fs.readFile(configPath, "utf8");
|
|
1221
|
+
const config = parseYAML(configContent);
|
|
1222
|
+
|
|
1223
|
+
expect(config.sourcesPath).toEqual(["./"]); // Default value
|
|
1224
|
+
} finally {
|
|
1225
|
+
await cleanupTempDir(tempDir);
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
describe("Skip existing configuration", () => {
|
|
1231
|
+
test("should skip if config exists and skipIfExists is true", async () => {
|
|
1232
|
+
const tempDir = await createTempDir();
|
|
1233
|
+
|
|
1234
|
+
try {
|
|
1235
|
+
// Create existing config file
|
|
1236
|
+
const configPath = join(tempDir, "config.yaml");
|
|
1237
|
+
const existingConfig = 'projectName: "Existing Project"\nlocale: "zh"';
|
|
1238
|
+
await fs.writeFile(configPath, existingConfig, "utf8");
|
|
1239
|
+
|
|
1240
|
+
const mockPrompts = {
|
|
1241
|
+
checkbox: () => Promise.resolve(["getStarted"]),
|
|
1242
|
+
select: () => Promise.resolve("en"),
|
|
1243
|
+
input: () => Promise.resolve("docs"),
|
|
1244
|
+
search: () => Promise.resolve(""),
|
|
1245
|
+
};
|
|
1246
|
+
|
|
1247
|
+
const options = { prompts: mockPrompts };
|
|
1248
|
+
|
|
1249
|
+
const result = await init(
|
|
1250
|
+
{ outputPath: tempDir, fileName: "config.yaml", skipIfExists: true },
|
|
1251
|
+
options,
|
|
1252
|
+
);
|
|
1253
|
+
|
|
1254
|
+
expect(result).toEqual({});
|
|
1255
|
+
|
|
1256
|
+
// Config should remain unchanged
|
|
1257
|
+
const configContent = await fs.readFile(configPath, "utf8");
|
|
1258
|
+
expect(configContent).toBe(existingConfig);
|
|
1259
|
+
} finally {
|
|
1260
|
+
await cleanupTempDir(tempDir);
|
|
1261
|
+
}
|
|
1262
|
+
});
|
|
1263
|
+
|
|
1264
|
+
test("should not skip if config file is empty even when skipIfExists is true", async () => {
|
|
1265
|
+
const tempDir = await createTempDir();
|
|
1266
|
+
|
|
1267
|
+
try {
|
|
1268
|
+
// Create empty config file
|
|
1269
|
+
const configPath = join(tempDir, "config.yaml");
|
|
1270
|
+
await fs.writeFile(configPath, "", "utf8");
|
|
1271
|
+
|
|
1272
|
+
const mockPrompts = {
|
|
1273
|
+
checkbox: () => Promise.resolve(["getStarted"]),
|
|
1274
|
+
select: () => Promise.resolve("en"),
|
|
1275
|
+
input: () => Promise.resolve(join(tempDir, "docs")),
|
|
1276
|
+
search: () => Promise.resolve(""),
|
|
1277
|
+
};
|
|
1278
|
+
|
|
1279
|
+
const options = { prompts: mockPrompts };
|
|
1280
|
+
|
|
1281
|
+
const result = await init(
|
|
1282
|
+
{ outputPath: tempDir, fileName: "config.yaml", skipIfExists: true },
|
|
1283
|
+
options,
|
|
1284
|
+
);
|
|
1285
|
+
|
|
1286
|
+
expect(result).toEqual({});
|
|
1287
|
+
|
|
1288
|
+
// Config should be generated since original was empty
|
|
1289
|
+
const configContent = await fs.readFile(configPath, "utf8");
|
|
1290
|
+
expect(configContent).toContain("projectName:");
|
|
1291
|
+
expect(configContent).toContain("locale: en");
|
|
1292
|
+
} finally {
|
|
1293
|
+
await cleanupTempDir(tempDir);
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
});
|
|
1297
|
+
|
|
1298
|
+
describe("Validation error handling", () => {
|
|
1299
|
+
test("should handle empty document purpose validation", async () => {
|
|
1300
|
+
const tempDir = await createTempDir();
|
|
1301
|
+
|
|
1302
|
+
try {
|
|
1303
|
+
// Mock prompts that will trigger validation errors by calling validate function
|
|
1304
|
+
let validateCalled = false;
|
|
1305
|
+
const mockPrompts = {
|
|
1306
|
+
checkbox: (options) => {
|
|
1307
|
+
if (options.message.includes("[1/8]") && options.validate) {
|
|
1308
|
+
// Test the validation function directly
|
|
1309
|
+
const validationResult = options.validate([]);
|
|
1310
|
+
expect(validationResult).toBe("Please select at least one purpose.");
|
|
1311
|
+
validateCalled = true;
|
|
1312
|
+
// Return valid result after testing validation
|
|
1313
|
+
return Promise.resolve(["getStarted"]);
|
|
1314
|
+
}
|
|
1315
|
+
return Promise.resolve(["getStarted"]);
|
|
1316
|
+
},
|
|
1317
|
+
select: () => Promise.resolve("en"),
|
|
1318
|
+
input: () => Promise.resolve(join(tempDir, "docs")),
|
|
1319
|
+
search: () => Promise.resolve(""),
|
|
1320
|
+
};
|
|
1321
|
+
|
|
1322
|
+
const options = { prompts: mockPrompts };
|
|
1323
|
+
|
|
1324
|
+
const result = await init(
|
|
1325
|
+
{ outputPath: tempDir, fileName: "config.yaml", skipIfExists: false },
|
|
1326
|
+
options,
|
|
1327
|
+
);
|
|
1328
|
+
|
|
1329
|
+
expect(result).toEqual({});
|
|
1330
|
+
expect(validateCalled).toBe(true);
|
|
1331
|
+
} finally {
|
|
1332
|
+
await cleanupTempDir(tempDir);
|
|
1333
|
+
}
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
test("should handle empty target audience validation", async () => {
|
|
1337
|
+
const tempDir = await createTempDir();
|
|
1338
|
+
|
|
1339
|
+
try {
|
|
1340
|
+
let audienceValidateCalled = false;
|
|
1341
|
+
const mockPrompts = {
|
|
1342
|
+
checkbox: (options) => {
|
|
1343
|
+
if (options.message.includes("[1/8]")) {
|
|
1344
|
+
return Promise.resolve(["getStarted"]); // Valid document purpose
|
|
1345
|
+
}
|
|
1346
|
+
if (options.message.includes("[2/8]") && options.validate) {
|
|
1347
|
+
// Test the validation function for target audience
|
|
1348
|
+
const validationResult = options.validate([]);
|
|
1349
|
+
expect(validationResult).toBe("Please select at least one audience.");
|
|
1350
|
+
audienceValidateCalled = true;
|
|
1351
|
+
return Promise.resolve(["developers"]); // Valid result after testing
|
|
1352
|
+
}
|
|
1353
|
+
return Promise.resolve(["developers"]);
|
|
1354
|
+
},
|
|
1355
|
+
select: () => Promise.resolve("en"),
|
|
1356
|
+
input: () => Promise.resolve(join(tempDir, "docs")),
|
|
1357
|
+
search: () => Promise.resolve(""),
|
|
1358
|
+
};
|
|
1359
|
+
|
|
1360
|
+
const options = { prompts: mockPrompts };
|
|
1361
|
+
|
|
1362
|
+
const result = await init(
|
|
1363
|
+
{ outputPath: tempDir, fileName: "config.yaml", skipIfExists: false },
|
|
1364
|
+
options,
|
|
1365
|
+
);
|
|
1366
|
+
|
|
1367
|
+
expect(result).toEqual({});
|
|
1368
|
+
expect(audienceValidateCalled).toBe(true);
|
|
1369
|
+
} finally {
|
|
1370
|
+
await cleanupTempDir(tempDir);
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
test("should handle mixed purpose priority validation errors", async () => {
|
|
1375
|
+
const tempDir = await createTempDir();
|
|
1376
|
+
|
|
1377
|
+
try {
|
|
1378
|
+
let priorityValidateCalled = false;
|
|
1379
|
+
const mockPrompts = {
|
|
1380
|
+
checkbox: (options) => {
|
|
1381
|
+
if (options.message.includes("[1/8]")) {
|
|
1382
|
+
return Promise.resolve(["mixedPurpose"]); // Trigger follow-up question
|
|
1383
|
+
}
|
|
1384
|
+
// This is the follow-up priority selection
|
|
1385
|
+
if (options.message.includes("Which is most important?") && options.validate) {
|
|
1386
|
+
// Test validation for empty selection
|
|
1387
|
+
let validationResult = options.validate([]);
|
|
1388
|
+
expect(validationResult).toBe("Please select at least one priority.");
|
|
1389
|
+
|
|
1390
|
+
// Test validation for too many selections
|
|
1391
|
+
validationResult = options.validate(["getStarted", "completeTasks", "findAnswers"]);
|
|
1392
|
+
expect(validationResult).toBe("Please select maximum 2 priorities.");
|
|
1393
|
+
|
|
1394
|
+
// Test validation for valid selection
|
|
1395
|
+
validationResult = options.validate(["getStarted", "completeTasks"]);
|
|
1396
|
+
expect(validationResult).toBe(true);
|
|
1397
|
+
|
|
1398
|
+
priorityValidateCalled = true;
|
|
1399
|
+
return Promise.resolve(["getStarted", "completeTasks"]); // Valid selection
|
|
1400
|
+
}
|
|
1401
|
+
return Promise.resolve(["getStarted", "completeTasks"]);
|
|
1402
|
+
},
|
|
1403
|
+
select: () => Promise.resolve("en"),
|
|
1404
|
+
input: () => Promise.resolve(join(tempDir, "docs")),
|
|
1405
|
+
search: () => Promise.resolve(""),
|
|
1406
|
+
};
|
|
1407
|
+
|
|
1408
|
+
const options = { prompts: mockPrompts };
|
|
1409
|
+
|
|
1410
|
+
const result = await init(
|
|
1411
|
+
{ outputPath: tempDir, fileName: "config.yaml", skipIfExists: false },
|
|
1412
|
+
options,
|
|
1413
|
+
);
|
|
1414
|
+
|
|
1415
|
+
expect(result).toEqual({});
|
|
1416
|
+
expect(priorityValidateCalled).toBe(true);
|
|
1417
|
+
} finally {
|
|
1418
|
+
await cleanupTempDir(tempDir);
|
|
1419
|
+
}
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1422
|
+
test("should handle file write errors", async () => {
|
|
1423
|
+
const invalidPath = "/invalid/path/that/does/not/exist";
|
|
1424
|
+
|
|
1425
|
+
const mockPrompts = {
|
|
1426
|
+
checkbox: () => Promise.resolve(["getStarted"]),
|
|
1427
|
+
select: () => Promise.resolve("en"),
|
|
1428
|
+
input: () => Promise.resolve("docs"),
|
|
1429
|
+
search: () => Promise.resolve(""),
|
|
1430
|
+
};
|
|
1431
|
+
|
|
1432
|
+
const options = { prompts: mockPrompts };
|
|
1433
|
+
|
|
1434
|
+
const result = await init(
|
|
1435
|
+
{ outputPath: invalidPath, fileName: "config.yaml", skipIfExists: false },
|
|
1436
|
+
options,
|
|
1437
|
+
);
|
|
1438
|
+
|
|
1439
|
+
expect(result).toHaveProperty("inputGeneratorStatus", false);
|
|
1440
|
+
expect(result).toHaveProperty("inputGeneratorError");
|
|
1441
|
+
expect(typeof result.inputGeneratorError).toBe("string");
|
|
1442
|
+
});
|
|
1443
|
+
});
|
|
1444
|
+
|
|
1445
|
+
describe("Advanced source path scenarios", () => {
|
|
1446
|
+
test("should handle source path validation and duplicate detection", async () => {
|
|
1447
|
+
const tempDir = await createTempDir();
|
|
1448
|
+
|
|
1449
|
+
try {
|
|
1450
|
+
let searchCallCount = 0;
|
|
1451
|
+
const responses = [
|
|
1452
|
+
"invalid/path/that/does/not/exist", // Should trigger validation error
|
|
1453
|
+
"invalid/path/that/does/not/exist", // Duplicate path
|
|
1454
|
+
join(tempDir, "valid-path"), // Valid path after creating it
|
|
1455
|
+
join(tempDir, "valid-path"), // Duplicate of valid path
|
|
1456
|
+
"**/*.glob", // Glob pattern (should be accepted)
|
|
1457
|
+
"**/*.glob", // Duplicate glob pattern
|
|
1458
|
+
"", // End input
|
|
1459
|
+
];
|
|
1460
|
+
|
|
1461
|
+
// Create a valid directory for testing
|
|
1462
|
+
await fs.mkdir(join(tempDir, "valid-path"), { recursive: true });
|
|
1463
|
+
|
|
1464
|
+
const mockPrompts = {
|
|
1465
|
+
checkbox: () => Promise.resolve(["getStarted"]),
|
|
1466
|
+
select: () => Promise.resolve("en"),
|
|
1467
|
+
input: () => Promise.resolve(join(tempDir, "docs")),
|
|
1468
|
+
search: () => {
|
|
1469
|
+
const response = responses[searchCallCount];
|
|
1470
|
+
searchCallCount++;
|
|
1471
|
+
return Promise.resolve(response);
|
|
1472
|
+
},
|
|
1473
|
+
};
|
|
1474
|
+
|
|
1475
|
+
const options = { prompts: mockPrompts };
|
|
1476
|
+
|
|
1477
|
+
const result = await init(
|
|
1478
|
+
{ outputPath: tempDir, fileName: "config.yaml", skipIfExists: false },
|
|
1479
|
+
options,
|
|
1480
|
+
);
|
|
1481
|
+
|
|
1482
|
+
expect(result).toEqual({});
|
|
1483
|
+
|
|
1484
|
+
const configPath = join(tempDir, "config.yaml");
|
|
1485
|
+
const configContent = await fs.readFile(configPath, "utf8");
|
|
1486
|
+
const config = parseYAML(configContent);
|
|
1487
|
+
|
|
1488
|
+
// Should contain the valid paths that were successfully added
|
|
1489
|
+
expect(config.sourcesPath.length).toBeGreaterThan(0);
|
|
1490
|
+
} finally {
|
|
1491
|
+
await cleanupTempDir(tempDir);
|
|
1492
|
+
}
|
|
1493
|
+
});
|
|
1494
|
+
|
|
1495
|
+
test("should handle search source function callback", async () => {
|
|
1496
|
+
const tempDir = await createTempDir();
|
|
1497
|
+
|
|
1498
|
+
try {
|
|
1499
|
+
const mockPrompts = {
|
|
1500
|
+
checkbox: () => Promise.resolve(["getStarted"]),
|
|
1501
|
+
select: () => Promise.resolve("en"),
|
|
1502
|
+
input: () => Promise.resolve(join(tempDir, "docs")),
|
|
1503
|
+
search: (options) => {
|
|
1504
|
+
// Test the source callback function
|
|
1505
|
+
if (options?.source) {
|
|
1506
|
+
// Call the source function with different inputs to test the logic
|
|
1507
|
+
const sourceResults1 = options.source("");
|
|
1508
|
+
const sourceResults2 = options.source("src");
|
|
1509
|
+
const sourceResults3 = options.source("**/*.js");
|
|
1510
|
+
|
|
1511
|
+
// Verify these are promises
|
|
1512
|
+
expect(sourceResults1).toBeInstanceOf(Promise);
|
|
1513
|
+
expect(sourceResults2).toBeInstanceOf(Promise);
|
|
1514
|
+
expect(sourceResults3).toBeInstanceOf(Promise);
|
|
1515
|
+
}
|
|
1516
|
+
return Promise.resolve(""); // End input
|
|
1517
|
+
},
|
|
1518
|
+
};
|
|
1519
|
+
|
|
1520
|
+
const options = { prompts: mockPrompts };
|
|
1521
|
+
|
|
1522
|
+
const result = await init(
|
|
1523
|
+
{ outputPath: tempDir, fileName: "config.yaml", skipIfExists: false },
|
|
1524
|
+
options,
|
|
1525
|
+
);
|
|
1526
|
+
|
|
1527
|
+
expect(result).toEqual({});
|
|
1528
|
+
} finally {
|
|
1529
|
+
await cleanupTempDir(tempDir);
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
1532
|
+
});
|
|
1533
|
+
});
|