@xenonbyte/da-vinci-workflow 0.1.21 → 0.1.22

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.
@@ -0,0 +1,178 @@
1
+ const assert = require("assert/strict");
2
+ const fs = require("fs");
3
+ const os = require("os");
4
+ const path = require("path");
5
+ const {
6
+ MATERIAL_METADATA_URL,
7
+ LUCIDE_TREE_URL,
8
+ FEATHER_TREE_URL,
9
+ PHOSPHOR_TREE_URL,
10
+ getDefaultCatalogPath,
11
+ resolveCatalogPath,
12
+ loadIconCatalog,
13
+ syncIconCatalog,
14
+ formatIconSyncReport,
15
+ parseGoogleMaterialMetadata,
16
+ parseGitHubTreeIcons,
17
+ dedupeIconRecords,
18
+ summarizeSourceResults
19
+ } = require("../lib/icon-sync");
20
+
21
+ async function runTest(name, fn) {
22
+ try {
23
+ await fn();
24
+ console.log(`PASS ${name}`);
25
+ } catch (error) {
26
+ console.error(`FAIL ${name}`);
27
+ throw error;
28
+ }
29
+ }
30
+
31
+ function createMockFetch({ failLucide = false } = {}) {
32
+ return async (url) => {
33
+ if (url === MATERIAL_METADATA_URL) {
34
+ return `)]}'\n{"icons":[{"name":"settings","unsupported_families":[]}]}`;
35
+ }
36
+ if (url === LUCIDE_TREE_URL) {
37
+ if (failLucide) {
38
+ throw new Error("mock lucide outage");
39
+ }
40
+ return JSON.stringify({
41
+ tree: [{ path: "icons/settings.json" }]
42
+ });
43
+ }
44
+ if (url === FEATHER_TREE_URL) {
45
+ return JSON.stringify({
46
+ tree: [{ path: "icons/lock.svg" }]
47
+ });
48
+ }
49
+ if (url === PHOSPHOR_TREE_URL) {
50
+ return JSON.stringify({
51
+ tree: [{ path: "assets/regular/vault.svg" }]
52
+ });
53
+ }
54
+ throw new Error(`Unexpected URL: ${url}`);
55
+ };
56
+ }
57
+
58
+ (async () => {
59
+ await runTest("google metadata parser extracts material variants", () => {
60
+ const raw = `)]}'\n{"icons":[{"name":"settings","unsupported_families":[]},{"name":"lock_open","unsupported_families":["Material Icons Sharp"]}]}`;
61
+ const records = parseGoogleMaterialMetadata(raw);
62
+ const keys = new Set(records.map((record) => `${record.family}/${record.name}`));
63
+ assert.ok(keys.has("Material Symbols Rounded/settings"));
64
+ assert.ok(keys.has("Material Symbols Outlined/settings"));
65
+ assert.ok(keys.has("Material Symbols Sharp/settings"));
66
+ assert.ok(keys.has("Material Symbols Rounded/lock_open"));
67
+ assert.equal(keys.has("Material Symbols Sharp/lock_open"), false);
68
+ });
69
+
70
+ await runTest("github tree parser extracts icon names from paths", () => {
71
+ const raw = JSON.stringify({
72
+ tree: [
73
+ { path: "icons/settings.json" },
74
+ { path: "icons/lock-open.json" },
75
+ { path: "icons/nested/skip.json" },
76
+ { path: "readme.md" }
77
+ ]
78
+ });
79
+ const records = parseGitHubTreeIcons(raw, {
80
+ family: "lucide",
81
+ prefix: "icons/",
82
+ suffix: ".json"
83
+ });
84
+ const names = records.map((record) => record.name);
85
+ assert.deepEqual(names, ["settings", "lock-open"]);
86
+ });
87
+
88
+ await runTest("dedupe keeps first unique family/name pair", () => {
89
+ const deduped = dedupeIconRecords([
90
+ { family: "lucide", name: "settings" },
91
+ { family: "lucide", name: "settings" },
92
+ { family: "feather", name: "settings" }
93
+ ]);
94
+ assert.equal(deduped.length, 2);
95
+ });
96
+
97
+ await runTest("catalog path resolves to home .da-vinci directory by default", () => {
98
+ const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-icon-home-"));
99
+ const resolved = getDefaultCatalogPath(tempHome);
100
+ assert.match(resolved, /\.da-vinci[\/\\]icon-catalog\.json$/);
101
+ });
102
+
103
+ await runTest("load icon catalog reads valid schema", () => {
104
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-icon-catalog-"));
105
+ const catalogPath = path.join(tempDir, "icon-catalog.json");
106
+ fs.writeFileSync(
107
+ catalogPath,
108
+ JSON.stringify(
109
+ {
110
+ schema: 1,
111
+ generatedAt: "2026-03-29T00:00:00.000Z",
112
+ iconCount: 1,
113
+ icons: [{ family: "lucide", name: "settings", semantic: "settings", tags: [] }]
114
+ },
115
+ null,
116
+ 2
117
+ )
118
+ );
119
+
120
+ const loaded = loadIconCatalog({
121
+ catalogPath
122
+ });
123
+ assert.ok(loaded.catalog);
124
+ assert.equal(loaded.catalog.icons.length, 1);
125
+ assert.equal(resolveCatalogPath({ catalogPath }), path.resolve(catalogPath));
126
+ });
127
+
128
+ await runTest("source summary marks degraded when at least one source errors", () => {
129
+ const summary = summarizeSourceResults({
130
+ material: { status: "ok" },
131
+ lucide: { status: "error" },
132
+ feather: { status: "ok" }
133
+ });
134
+ assert.deepEqual(summary, {
135
+ total: 3,
136
+ okCount: 2,
137
+ errorCount: 1,
138
+ degraded: true
139
+ });
140
+ });
141
+
142
+ await runTest("icon sync keeps flow non-blocking by default on partial source failure", async () => {
143
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-icon-sync-"));
144
+ const outputPath = path.join(tempDir, "catalog.json");
145
+ const result = await syncIconCatalog({
146
+ outputPath,
147
+ fetchText: createMockFetch({ failLucide: true })
148
+ });
149
+
150
+ assert.equal(result.catalog.syncStatus, "degraded");
151
+ assert.equal(result.catalog.sourceResults.lucide.status, "error");
152
+ assert.ok(result.catalog.iconCount > 0);
153
+ assert.equal(fs.existsSync(outputPath), true);
154
+ assert.match(formatIconSyncReport(result), /Status: DEGRADED/i);
155
+ });
156
+
157
+ await runTest("icon sync strict mode fails on partial source failure and does not write partial catalog", async () => {
158
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "da-vinci-icon-sync-strict-"));
159
+ const outputPath = path.join(tempDir, "catalog.json");
160
+
161
+ await assert.rejects(
162
+ () =>
163
+ syncIconCatalog({
164
+ outputPath,
165
+ strict: true,
166
+ fetchText: createMockFetch({ failLucide: true })
167
+ }),
168
+ /strict mode failed/i
169
+ );
170
+
171
+ assert.equal(fs.existsSync(outputPath), false);
172
+ });
173
+
174
+ console.log("All icon-sync tests passed.");
175
+ })().catch((error) => {
176
+ console.error(error.stack || error.message);
177
+ process.exit(1);
178
+ });
@@ -17,6 +17,7 @@ const {
17
17
  } = require("../lib/pencil-lock");
18
18
 
19
19
  const fixturePath = path.join(__dirname, "fixtures", "complex-sample.pen");
20
+ const mockPencilPath = path.join(__dirname, "fixtures", "mock-pencil.js");
20
21
 
21
22
  function runTest(name, fn) {
22
23
  try {
@@ -65,7 +66,8 @@ runTest("writePenFromPayloadFiles writes and reopens a complex sample", () => {
65
66
  nodesFile,
66
67
  variablesFile,
67
68
  version: fixture.version,
68
- verifyWithPencil: true
69
+ verifyWithPencil: true,
70
+ pencilBin: mockPencilPath
69
71
  });
70
72
 
71
73
  const written = JSON.parse(fs.readFileSync(outputPath, "utf8"));
@@ -82,7 +84,8 @@ runTest("snapshotPenFile round-trips a complex sample", () => {
82
84
  const result = snapshotPenFile({
83
85
  inputPath: fixturePath,
84
86
  outputPath,
85
- verifyWithPencil: true
87
+ verifyWithPencil: true,
88
+ pencilBin: mockPencilPath
86
89
  });
87
90
 
88
91
  const written = JSON.parse(fs.readFileSync(outputPath, "utf8"));
@@ -97,7 +100,8 @@ runTest("ensurePenFile seeds a missing project-local .pen and state", () => {
97
100
 
98
101
  const result = ensurePenFile({
99
102
  outputPath,
100
- verifyWithPencil: true
103
+ verifyWithPencil: true,
104
+ pencilBin: mockPencilPath
101
105
  });
102
106
 
103
107
  const written = JSON.parse(fs.readFileSync(outputPath, "utf8"));