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>
198 lines
8.1 KiB
JavaScript
198 lines
8.1 KiB
JavaScript
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\(\(任意\)\)/);
|
||
});
|