ken_nogi/Pleasanter/.claude/js/generate-process-flowchart.test.js
Kenichiro NOGI ce58cb4be4 初回コミット: dev配下(NodeSrv/Pleasanter等)をGitea管理下に統合
GitHub(nextgroup2706/ken_nogi)は今後使わず自社Gitea運用に切替え。
NodeSrvは旧リポジトリの履歴を破棄しファイルのみ統合(Dokploy用サービスアカウントは
別途mygit-admin/NodeSrv.gitに履歴あり)。notepmエクスポート(12GB)とPleasanter
インストーラzip(208MB)はサイズが大きいため.gitignoreで除外。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 15:37:06 +09:00

198 lines
8.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const test = require("node:test");
const assert = require("node:assert/strict");
const path = require("path");
const os = require("os");
const fs = require("fs");
const {
parseChoicesText,
resolveSiteDir,
formatEdgeLabel,
getFilterStatusList,
buildEdges,
loadStatusLabels,
buildMermaid,
} = require("./generate-process-flowchart");
test("parseChoicesText: 値,ラベル,短縮ラベル,色 形式を 値→ラベル のMapへ変換する", () => {
const input = "100,新規,新規,new-status-white\n450,依頼者質疑中,質疑中,new-status-yellow";
const result = parseChoicesText(input);
assert.equal(result.get("100"), "新規");
assert.equal(result.get("450"), "依頼者質疑中");
assert.equal(result.size, 2);
});
test("parseChoicesText: undefinedやから文字列は空Mapを返す", () => {
assert.equal(parseChoicesText(undefined).size, 0);
assert.equal(parseChoicesText("").size, 0);
});
test("parseChoicesText: ラベル省略時は値をラベルとして使う", () => {
const result = parseChoicesText("999");
assert.equal(result.get("999"), "999");
});
test("resolveSiteDir: site-{id}_{name}形式のフォルダを前方一致で解決する", () => {
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), "gpf-test-"));
fs.mkdirSync(path.join(tmpBase, "configs", "site-999_テストサイト"), { recursive: true });
const result = resolveSiteDir(tmpBase, "site-999");
assert.equal(result, path.join(tmpBase, "configs", "site-999_テストサイト"));
});
test("formatEdgeLabel: Depts・Notifications無しはプロセス名のみ", () => {
assert.equal(formatEdgeLabel({ Id: 2, DisplayName: "回答送信" }), "回答送信");
});
test("formatEdgeLabel: Depts指定時は (Dept番号) を付記する", () => {
assert.equal(formatEdgeLabel({ Id: 3, DisplayName: "営業積算確認", Depts: [25] }), "営業積算確認 (Dept25)");
});
test("formatEdgeLabel: Notifications指定時は先頭に📧を付ける", () => {
const process = { Id: 6, DisplayName: "遅延連絡", Depts: [25], Notifications: [{ Type: 1 }] };
assert.equal(formatEdgeLabel(process), "📧遅延連絡 (Dept25)");
});
test("formatEdgeLabel: DisplayName省略時はNameを使う", () => {
assert.equal(formatEdgeLabel({ Id: 1, Name: "質疑送信" }), "質疑送信");
});
test("getFilterStatusList: View.ColumnFilterHash.Statusを文字列配列で返す", () => {
const process = { View: { ColumnFilterHash: { Status: '["100","400"]' } } };
assert.deepEqual(getFilterStatusList(process), ["100", "400"]);
});
test("getFilterStatusList: Status指定が無ければnullを返す", () => {
assert.equal(getFilterStatusList({ View: { ColumnFilterHash: { ClassC: '["Own"]' } } }), null);
assert.equal(getFilterStatusList({}), null);
});
test("buildEdges: 通常遷移CurrentStatus≠-1, ChangedStatus≠-1", () => {
const processes = [{ Id: 3, DisplayName: "営業積算確認", CurrentStatus: 450, ChangedStatus: 500, Depts: [25] }];
const { edgeLines, statusValues, needsAnyNode } = buildEdges(processes);
assert.deepEqual(edgeLines, [' 450 -->|"営業積算確認 (Dept25)"| 500']);
assert.deepEqual([...statusValues].sort(), ["450", "500"]);
assert.equal(needsAnyNode, false);
});
test("buildEdges: 任意状態からの遷移・ColumnFilterHash.Statusあり", () => {
const processes = [
{
Id: 1,
DisplayName: "質疑送信",
CurrentStatus: -1,
ChangedStatus: 450,
View: { ColumnFilterHash: { Status: '["100","400"]' } },
},
];
const { edgeLines, statusValues, needsAnyNode } = buildEdges(processes);
assert.deepEqual(edgeLines, [' 100 -->|"質疑送信"| 450', ' 400 -->|"質疑送信"| 450']);
assert.deepEqual([...statusValues].sort(), ["100", "400", "450"]);
assert.equal(needsAnyNode, false);
});
test("buildEdges: 任意状態からの遷移・ColumnFilterHash無しはanyード起点", () => {
const processes = [{ Id: 9, DisplayName: "サンプル", CurrentStatus: -1, ChangedStatus: 500 }];
const { edgeLines, needsAnyNode } = buildEdges(processes);
assert.deepEqual(edgeLines, [' any -->|"サンプル"| 500']);
assert.equal(needsAnyNode, true);
});
test("buildEdges: 状態維持アクションChangedStatus=-1は自己ループ", () => {
const processes = [{ Id: 4, DisplayName: "質疑回答削除", CurrentStatus: 450, ChangedStatus: -1, Depts: [25] }];
const { edgeLines, statusValues } = buildEdges(processes);
assert.deepEqual(edgeLines, [' 450 -->|"質疑回答削除 (Dept25)"| 450']);
assert.deepEqual([...statusValues], ["450"]);
});
test("buildEdges: 状態変化なし・ColumnFilterHash.Statusありは各値へ自己ループ", () => {
const processes = [
{
Id: 7,
DisplayName: "見積回答",
CurrentStatus: -1,
ChangedStatus: -1,
Depts: [25],
Notifications: [{ Type: 1 }],
View: { ColumnFilterHash: { Status: '["800","900","901"]' } },
},
];
const { edgeLines, needsAnyNode } = buildEdges(processes);
assert.deepEqual(edgeLines, [
' 800 -->|"📧見積回答 (Dept25)"| 800',
' 900 -->|"📧見積回答 (Dept25)"| 900',
' 901 -->|"📧見積回答 (Dept25)"| 901',
]);
assert.equal(needsAnyNode, false);
});
test("buildEdges: 状態変化なし・ColumnFilterHash無しはanyードへ自己ループ", () => {
const processes = [
{ Id: 6, DisplayName: "遅延連絡", CurrentStatus: -1, ChangedStatus: -1, Depts: [25], Notifications: [{ Type: 1 }] },
];
const { edgeLines, needsAnyNode } = buildEdges(processes);
assert.deepEqual(edgeLines, [' any -->|"📧遅延連絡 (Dept25)"| any']);
assert.equal(needsAnyNode, true);
});
test("loadStatusLabels: latest.jsonのStatus列ChoicesTextを解決する", () => {
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), "gpf-test-"));
fs.mkdirSync(path.join(tmpBase, "siteSettingJsons"), { recursive: true });
const fakeLatest = {
Response: {
Data: {
SiteSettings: {
Columns: [
{ ColumnName: "ClassA", LabelText: "親番" },
{ ColumnName: "Status", ChoicesText: "100,新規,新規,white\n450,依頼者質疑中,質疑中,yellow" },
],
},
},
},
};
fs.writeFileSync(
path.join(tmpBase, "siteSettingJsons", "site-999_latest.json"),
JSON.stringify(fakeLatest),
"utf-8"
);
const result = loadStatusLabels(tmpBase, "999");
assert.equal(result.get("100"), "新規");
assert.equal(result.get("450"), "依頼者質疑中");
});
test("loadStatusLabels: latest.jsonが無ければ空Mapを返す", () => {
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), "gpf-test-"));
const result = loadStatusLabels(tmpBase, "000");
assert.equal(result.size, 0);
});
test("buildMermaid: ノード定義とエッジ定義を含む完成文字列を返す", () => {
const processes = [{ Id: 3, DisplayName: "営業積算確認", CurrentStatus: 450, ChangedStatus: 500, Depts: [25] }];
const statusLabels = new Map([
["450", "依頼者質疑中"],
["500", "積算対応中"],
]);
const result = buildMermaid(processes, statusLabels);
assert.match(result, /^```mermaid\nflowchart LR\n/);
assert.match(result, /450\(\(依頼者質疑中<br\/>450\)\)/);
assert.match(result, /500\(\(積算対応中<br\/>500\)\)/);
assert.match(result, /450 -->\|"営業積算確認 \(Dept25\)"\| 500/);
assert.match(result, /```$/);
});
test("buildMermaid: statusLabelsに無い値は値のみのラベルにフォールバックする", () => {
const processes = [{ Id: 3, DisplayName: "テスト", CurrentStatus: 100, ChangedStatus: 200 }];
const result = buildMermaid(processes, new Map());
assert.match(result, /100\(\(100<br\/>100\)\)/);
assert.match(result, /200\(\(200<br\/>200\)\)/);
});
test("buildMermaid: anyードが必要な場合のみ ((任意)) を含める", () => {
const withAny = buildMermaid([{ Id: 6, DisplayName: "遅延連絡", CurrentStatus: -1, ChangedStatus: -1 }], new Map());
assert.match(withAny, /any\(\(任意\)\)/);
const withoutAny = buildMermaid(
[{ Id: 3, DisplayName: "営業積算確認", CurrentStatus: 450, ChangedStatus: 500 }],
new Map()
);
assert.doesNotMatch(withoutAny, /any\(\(任意\)\)/);
});