@codeyam/codeyam-cli 0.1.0-staging.8778565 → 0.1.0-staging.8b51541
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/analyzer-template/.build-info.json +7 -7
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/packages/database/src/lib/kysely/tables/editorScenariosTable.ts +56 -0
- package/analyzer-template/packages/database/src/lib/loadEntities.ts +0 -6
- package/analyzer-template/packages/database/src/lib/updateCommitMetadata.ts +0 -65
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts +3 -0
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js +56 -0
- package/analyzer-template/packages/github/dist/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js +0 -6
- package/analyzer-template/packages/github/dist/database/src/lib/loadEntities.js.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js +0 -25
- package/analyzer-template/packages/github/dist/database/src/lib/updateCommitMetadata.js.map +1 -1
- package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js +45 -0
- package/codeyam-cli/src/commands/__tests__/editor.stepDispatch.test.js.map +1 -0
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js +101 -47
- package/codeyam-cli/src/commands/__tests__/init.gitignore.test.js.map +1 -1
- package/codeyam-cli/src/commands/editor.js +799 -211
- package/codeyam-cli/src/commands/editor.js.map +1 -1
- package/codeyam-cli/src/commands/init.js +68 -34
- package/codeyam-cli/src/commands/init.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js +173 -0
- package/codeyam-cli/src/utils/__tests__/analyzerFinalization.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/editorApi.test.js +18 -8
- package/codeyam-cli/src/utils/__tests__/editorApi.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js +353 -1
- package/codeyam-cli/src/utils/__tests__/editorAudit.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js +128 -1
- package/codeyam-cli/src/utils/__tests__/editorLoaderHelpers.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js +88 -1
- package/codeyam-cli/src/utils/__tests__/editorPreview.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js +47 -1
- package/codeyam-cli/src/utils/__tests__/editorProxySession.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js +785 -1
- package/codeyam-cli/src/utils/__tests__/editorScenarios.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js +63 -1
- package/codeyam-cli/src/utils/__tests__/entityChangeStatus.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js +30 -2
- package/codeyam-cli/src/utils/__tests__/parseRegisterArg.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js +227 -0
- package/codeyam-cli/src/utils/__tests__/scenarioCoverage.test.js.map +1 -0
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js +426 -218
- package/codeyam-cli/src/utils/__tests__/scenariosManifest.test.js.map +1 -1
- package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js +1 -0
- package/codeyam-cli/src/utils/__tests__/setupClaudeCodeSettings.test.js.map +1 -1
- package/codeyam-cli/src/utils/analyzer.js +9 -0
- package/codeyam-cli/src/utils/analyzer.js.map +1 -1
- package/codeyam-cli/src/utils/analyzerFinalization.js +100 -0
- package/codeyam-cli/src/utils/analyzerFinalization.js.map +1 -0
- package/codeyam-cli/src/utils/backgroundServer.js +2 -8
- package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
- package/codeyam-cli/src/utils/database.js +37 -2
- package/codeyam-cli/src/utils/database.js.map +1 -1
- package/codeyam-cli/src/utils/editorApi.js +11 -5
- package/codeyam-cli/src/utils/editorApi.js.map +1 -1
- package/codeyam-cli/src/utils/editorAudit.js +51 -0
- package/codeyam-cli/src/utils/editorAudit.js.map +1 -1
- package/codeyam-cli/src/utils/editorLoaderHelpers.js +32 -0
- package/codeyam-cli/src/utils/editorLoaderHelpers.js.map +1 -1
- package/codeyam-cli/src/utils/editorPreview.js +31 -0
- package/codeyam-cli/src/utils/editorPreview.js.map +1 -1
- package/codeyam-cli/src/utils/editorScenarios.js +261 -0
- package/codeyam-cli/src/utils/editorScenarios.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.js +4 -2
- package/codeyam-cli/src/utils/entityChangeStatus.js.map +1 -1
- package/codeyam-cli/src/utils/entityChangeStatus.server.js +34 -0
- package/codeyam-cli/src/utils/entityChangeStatus.server.js.map +1 -1
- package/codeyam-cli/src/utils/parseRegisterArg.js.map +1 -1
- package/codeyam-cli/src/utils/progress.js +2 -2
- package/codeyam-cli/src/utils/progress.js.map +1 -1
- package/codeyam-cli/src/utils/scenarioCoverage.js +75 -0
- package/codeyam-cli/src/utils/scenarioCoverage.js.map +1 -0
- package/codeyam-cli/src/utils/scenariosManifest.js +204 -75
- package/codeyam-cli/src/utils/scenariosManifest.js.map +1 -1
- package/codeyam-cli/src/utils/setupClaudeCodeSettings.js +1 -0
- package/codeyam-cli/src/utils/setupClaudeCodeSettings.js.map +1 -1
- package/codeyam-cli/src/utils/simulationGateMiddleware.js +8 -1
- package/codeyam-cli/src/utils/simulationGateMiddleware.js.map +1 -1
- package/codeyam-cli/src/utils/slugUtils.js +25 -0
- package/codeyam-cli/src/utils/slugUtils.js.map +1 -0
- package/codeyam-cli/src/utils/syncMocksMiddleware.js +2 -2
- package/codeyam-cli/src/utils/syncMocksMiddleware.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js +40 -0
- package/codeyam-cli/src/webserver/__tests__/clientErrors.test.js.map +1 -0
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js +157 -0
- package/codeyam-cli/src/webserver/__tests__/editorProxy.test.js.map +1 -1
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js +146 -0
- package/codeyam-cli/src/webserver/__tests__/idleDetector.test.js.map +1 -0
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js +65 -0
- package/codeyam-cli/src/webserver/app/lib/clientErrors.js.map +1 -0
- package/codeyam-cli/src/webserver/app/lib/git.js +3 -2
- package/codeyam-cli/src/webserver/app/lib/git.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-0DY_NKil.js → ScenarioViewer-TSD3C211.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-scenario-coverage-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/api.editor-session-l0sNRNKZ.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-Csi0_PMl.js → dev.empty-Ii3inc0_.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor-16o0AIFV.js +15 -0
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-7Uga8I59.js +41 -0
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-BF4oLwaE.js → entity._sha._-DwCV5__E.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.dev-C7YX6r3H.js → entity._sha.scenarios._scenarioId.dev-BwKcai0j.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-CHMiAog3.js +6 -0
- package/codeyam-cli/src/webserver/build/client/assets/globals-CQPR0pFR.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/manifest-76e7b62c.js +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/memory-9gnxSZlb.js +101 -0
- package/codeyam-cli/src/webserver/build/client/assets/{root-ClvYBUSA.js → root-DBjt6o04.js} +3 -3
- package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-C-_hOl_g.js +1 -0
- package/codeyam-cli/src/webserver/build/client/sound-test.html +98 -0
- package/codeyam-cli/src/webserver/build/server/assets/index-DsZjKspK.js +1 -0
- package/codeyam-cli/src/webserver/build/server/assets/init-DdqKD2p4.js +10 -0
- package/codeyam-cli/src/webserver/build/server/assets/server-build-CKKeWtVK.js +444 -0
- package/codeyam-cli/src/webserver/build/server/index.js +1 -1
- package/codeyam-cli/src/webserver/build-info.json +5 -5
- package/codeyam-cli/src/webserver/editorProxy.js +99 -7
- package/codeyam-cli/src/webserver/editorProxy.js.map +1 -1
- package/codeyam-cli/src/webserver/idleDetector.js +73 -0
- package/codeyam-cli/src/webserver/idleDetector.js.map +1 -0
- package/codeyam-cli/src/webserver/public/sound-test.html +98 -0
- package/codeyam-cli/src/webserver/server.js +46 -4
- package/codeyam-cli/src/webserver/server.js.map +1 -1
- package/codeyam-cli/src/webserver/terminalServer.js +68 -29
- package/codeyam-cli/src/webserver/terminalServer.js.map +1 -1
- package/codeyam-cli/templates/chrome-extension-react/README.md +46 -0
- package/codeyam-cli/templates/chrome-extension-react/package.json +1 -0
- package/codeyam-cli/templates/codeyam-editor-claude.md +84 -5
- package/codeyam-cli/templates/editor-step-hook.py +14 -8
- package/codeyam-cli/templates/expo-react-native/README.md +41 -0
- package/codeyam-cli/templates/expo-react-native/package.json +1 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/DATABASE.md +14 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/README.md +53 -0
- package/codeyam-cli/templates/nextjs-prisma-sqlite/package.json +1 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/README.md +52 -0
- package/codeyam-cli/templates/nextjs-prisma-supabase/package.json +1 -0
- package/codeyam-cli/templates/skills/codeyam-dev-mode/SKILL.md +1 -1
- package/codeyam-cli/templates/skills/codeyam-editor/SKILL.md +14 -10
- package/package.json +1 -1
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js +56 -0
- package/packages/database/src/lib/kysely/tables/editorScenariosTable.js.map +1 -1
- package/packages/database/src/lib/loadEntities.js +0 -6
- package/packages/database/src/lib/loadEntities.js.map +1 -1
- package/packages/database/src/lib/updateCommitMetadata.js +0 -25
- package/packages/database/src/lib/updateCommitMetadata.js.map +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/editor-DgN1LTTt.js +0 -10
- package/codeyam-cli/src/webserver/build/client/assets/editorPreview-BLQMSKZa.js +0 -41
- package/codeyam-cli/src/webserver/build/client/assets/entity._sha.scenarios._scenarioId.fullscreen-CF164ouH.js +0 -6
- package/codeyam-cli/src/webserver/build/client/assets/globals-BkWJ_UNc.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/manifest-c26eb85b.js +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/memory-Bl2rpw8u.js +0 -96
- package/codeyam-cli/src/webserver/build/client/assets/useCustomSizes-CrAK28Bc.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/index-DflIr5SD.js +0 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-OhKy839M.js +0 -416
|
@@ -1,246 +1,454 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as os from 'os';
|
|
4
|
-
import {
|
|
4
|
+
import { scanScenarioFiles, writeScenarioMetadata, deleteScenarioFiles, syncScenarioFilesToDatabase, backfillScenarioMetadata, migrateScenarioFormats, } from "../scenariosManifest.js";
|
|
5
5
|
function makeTmpDir() {
|
|
6
6
|
return fs.mkdtempSync(path.join(os.tmpdir(), 'manifest-test-'));
|
|
7
7
|
}
|
|
8
|
-
function
|
|
8
|
+
function makeMetadata(overrides = {}) {
|
|
9
9
|
return {
|
|
10
|
-
id: 'test-id-1',
|
|
11
10
|
name: 'Test Scenario',
|
|
12
11
|
description: null,
|
|
13
12
|
componentName: null,
|
|
14
13
|
componentPath: null,
|
|
15
14
|
url: '/',
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
type: 'application',
|
|
16
|
+
screenshotPath: null,
|
|
17
|
+
viewportWidth: null,
|
|
18
|
+
viewportHeight: null,
|
|
18
19
|
createdAt: '2024-01-01T00:00:00.000Z',
|
|
19
20
|
updatedAt: '2024-01-01T00:00:00.000Z',
|
|
20
21
|
...overrides,
|
|
21
22
|
};
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
});
|
|
24
|
+
function writeScenarioFile(tmp, id, data, metadata) {
|
|
25
|
+
const dir = path.join(tmp, '.codeyam', 'editor-scenarios');
|
|
26
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
27
|
+
const content = metadata ? { _metadata: metadata, ...data } : data;
|
|
28
|
+
fs.writeFileSync(path.join(dir, `${id}.json`), JSON.stringify(content, null, 2));
|
|
29
|
+
}
|
|
30
|
+
// ─── scanScenarioFiles ────────────────────────────────────────────
|
|
31
|
+
describe('scanScenarioFiles', () => {
|
|
32
|
+
it('should find scenario files with _metadata', () => {
|
|
33
|
+
const tmp = makeTmpDir();
|
|
34
|
+
writeScenarioFile(tmp, 'abc-123', { seed: {} }, makeMetadata({ name: 'Home Page' }));
|
|
35
|
+
writeScenarioFile(tmp, 'def-456', { seed: {} }, makeMetadata({ name: 'About Page' }));
|
|
36
|
+
const results = scanScenarioFiles(tmp);
|
|
37
|
+
expect(results).toHaveLength(2);
|
|
38
|
+
const names = results.map((r) => r.metadata.name).sort();
|
|
39
|
+
expect(names).toEqual(['About Page', 'Home Page']);
|
|
40
40
|
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
expect(manifest.scenarios).toHaveLength(1);
|
|
49
|
-
expect(manifest.scenarios[0].id).toBe('test-id-1');
|
|
50
|
-
});
|
|
51
|
-
it('should append to existing manifest', () => {
|
|
52
|
-
const tmp = makeTmpDir();
|
|
53
|
-
addScenarioToManifest(tmp, makeEntry({ id: 'a', name: 'A' }));
|
|
54
|
-
addScenarioToManifest(tmp, makeEntry({ id: 'b', name: 'B' }));
|
|
55
|
-
const manifest = readManifest(tmp);
|
|
56
|
-
expect(manifest.scenarios).toHaveLength(2);
|
|
57
|
-
});
|
|
58
|
-
it('should deduplicate by id (update existing)', () => {
|
|
59
|
-
const tmp = makeTmpDir();
|
|
60
|
-
addScenarioToManifest(tmp, makeEntry({ id: 'a', name: 'Original' }));
|
|
61
|
-
addScenarioToManifest(tmp, makeEntry({ id: 'a', name: 'Updated' }));
|
|
62
|
-
const manifest = readManifest(tmp);
|
|
63
|
-
expect(manifest.scenarios).toHaveLength(1);
|
|
64
|
-
expect(manifest.scenarios[0].name).toBe('Updated');
|
|
65
|
-
});
|
|
41
|
+
it('should skip files without _metadata', () => {
|
|
42
|
+
const tmp = makeTmpDir();
|
|
43
|
+
writeScenarioFile(tmp, 'with-meta', { seed: {} }, makeMetadata());
|
|
44
|
+
writeScenarioFile(tmp, 'no-meta', { seed: {} }); // no metadata
|
|
45
|
+
const results = scanScenarioFiles(tmp);
|
|
46
|
+
expect(results).toHaveLength(1);
|
|
47
|
+
expect(results[0].id).toBe('with-meta');
|
|
66
48
|
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
it('should handle removing from nonexistent manifest', () => {
|
|
78
|
-
const tmp = makeTmpDir();
|
|
79
|
-
// Should not throw
|
|
80
|
-
removeScenarioFromManifest(tmp, 'nonexistent');
|
|
81
|
-
});
|
|
82
|
-
it('should handle removing nonexistent id', () => {
|
|
83
|
-
const tmp = makeTmpDir();
|
|
84
|
-
addScenarioToManifest(tmp, makeEntry({ id: 'a', name: 'A' }));
|
|
85
|
-
removeScenarioFromManifest(tmp, 'nonexistent');
|
|
86
|
-
const manifest = readManifest(tmp);
|
|
87
|
-
expect(manifest.scenarios).toHaveLength(1);
|
|
88
|
-
});
|
|
49
|
+
it('should skip .seed.json files', () => {
|
|
50
|
+
const tmp = makeTmpDir();
|
|
51
|
+
const dir = path.join(tmp, '.codeyam', 'editor-scenarios');
|
|
52
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
53
|
+
writeScenarioFile(tmp, 'abc', { seed: {} }, makeMetadata());
|
|
54
|
+
// Write a .seed.json file that has _metadata (shouldn't happen but be safe)
|
|
55
|
+
fs.writeFileSync(path.join(dir, 'abc.seed.json'), JSON.stringify({ _metadata: makeMetadata() }));
|
|
56
|
+
const results = scanScenarioFiles(tmp);
|
|
57
|
+
expect(results).toHaveLength(1);
|
|
58
|
+
expect(results[0].id).toBe('abc');
|
|
89
59
|
});
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
component_path: 'app/components/Button.tsx',
|
|
99
|
-
url: '/isolated-components/Button?s=Default',
|
|
100
|
-
screenshot_path: 'screenshots/row-1.png',
|
|
101
|
-
created_at: '2024-01-01 00:00:00',
|
|
102
|
-
updated_at: '2024-01-02 00:00:00',
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
id: 'row-2',
|
|
106
|
-
name: 'Scenario 2',
|
|
107
|
-
description: null,
|
|
108
|
-
component_name: null,
|
|
109
|
-
component_path: null,
|
|
110
|
-
url: '/',
|
|
111
|
-
screenshot_path: null,
|
|
112
|
-
created_at: '2024-01-03 00:00:00',
|
|
113
|
-
updated_at: '2024-01-03 00:00:00',
|
|
114
|
-
},
|
|
115
|
-
];
|
|
116
|
-
const manifest = buildManifestFromRows(rows);
|
|
117
|
-
expect(manifest.version).toBe(1);
|
|
118
|
-
expect(manifest.scenarios).toHaveLength(2);
|
|
119
|
-
expect(manifest.scenarios[0]).toEqual({
|
|
120
|
-
id: 'row-1',
|
|
121
|
-
name: 'Scenario 1',
|
|
122
|
-
description: 'A scenario',
|
|
123
|
-
componentName: 'Button',
|
|
124
|
-
componentPath: 'app/components/Button.tsx',
|
|
125
|
-
url: '/isolated-components/Button?s=Default',
|
|
126
|
-
mockDataFile: 'editor-scenarios/row-1.json',
|
|
127
|
-
screenshotFile: 'editor-scenarios/screenshots/row-1.png',
|
|
128
|
-
createdAt: '2024-01-01 00:00:00',
|
|
129
|
-
updatedAt: '2024-01-02 00:00:00',
|
|
130
|
-
});
|
|
131
|
-
expect(manifest.scenarios[1].screenshotFile).toBeNull();
|
|
132
|
-
});
|
|
133
|
-
it('should handle empty rows', () => {
|
|
134
|
-
const manifest = buildManifestFromRows([]);
|
|
135
|
-
expect(manifest.scenarios).toEqual([]);
|
|
136
|
-
});
|
|
60
|
+
it('should skip client-errors.json', () => {
|
|
61
|
+
const tmp = makeTmpDir();
|
|
62
|
+
const dir = path.join(tmp, '.codeyam', 'editor-scenarios');
|
|
63
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
64
|
+
writeScenarioFile(tmp, 'abc', { seed: {} }, makeMetadata());
|
|
65
|
+
fs.writeFileSync(path.join(dir, 'client-errors.json'), JSON.stringify({ _metadata: makeMetadata() }));
|
|
66
|
+
const results = scanScenarioFiles(tmp);
|
|
67
|
+
expect(results).toHaveLength(1);
|
|
137
68
|
});
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
expect(updatedRows[0].name).toBe('Updated Name');
|
|
69
|
+
it('should return empty array for missing directory', () => {
|
|
70
|
+
const tmp = makeTmpDir();
|
|
71
|
+
expect(scanScenarioFiles(tmp)).toEqual([]);
|
|
72
|
+
});
|
|
73
|
+
it('should extract id from filename', () => {
|
|
74
|
+
const tmp = makeTmpDir();
|
|
75
|
+
writeScenarioFile(tmp, 'my-uuid-here', { seed: {} }, makeMetadata());
|
|
76
|
+
const results = scanScenarioFiles(tmp);
|
|
77
|
+
expect(results[0].id).toBe('my-uuid-here');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
// ─── writeScenarioMetadata ────────────────────────────────────────
|
|
81
|
+
describe('writeScenarioMetadata', () => {
|
|
82
|
+
it('should add _metadata to existing scenario file', () => {
|
|
83
|
+
const tmp = makeTmpDir();
|
|
84
|
+
writeScenarioFile(tmp, 'abc', { type: 'application', seed: { users: [] } });
|
|
85
|
+
writeScenarioMetadata(tmp, 'abc', makeMetadata({ name: 'Updated' }));
|
|
86
|
+
const filePath = path.join(tmp, '.codeyam', 'editor-scenarios', 'abc.json');
|
|
87
|
+
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
88
|
+
expect(content._metadata.name).toBe('Updated');
|
|
89
|
+
expect(content.type).toBe('application'); // original data preserved
|
|
90
|
+
expect(content.seed).toEqual({ users: [] }); // original data preserved
|
|
91
|
+
});
|
|
92
|
+
it('should update existing _metadata', () => {
|
|
93
|
+
const tmp = makeTmpDir();
|
|
94
|
+
writeScenarioFile(tmp, 'abc', { seed: {} }, makeMetadata({ name: 'Old' }));
|
|
95
|
+
writeScenarioMetadata(tmp, 'abc', makeMetadata({ name: 'New' }));
|
|
96
|
+
const filePath = path.join(tmp, '.codeyam', 'editor-scenarios', 'abc.json');
|
|
97
|
+
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
98
|
+
expect(content._metadata.name).toBe('New');
|
|
99
|
+
});
|
|
100
|
+
it('should not create file if it does not exist', () => {
|
|
101
|
+
const tmp = makeTmpDir();
|
|
102
|
+
writeScenarioMetadata(tmp, 'nonexistent', makeMetadata());
|
|
103
|
+
const filePath = path.join(tmp, '.codeyam', 'editor-scenarios', 'nonexistent.json');
|
|
104
|
+
expect(fs.existsSync(filePath)).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
// ─── deleteScenarioFiles ──────────────────────────────────────────
|
|
108
|
+
describe('deleteScenarioFiles', () => {
|
|
109
|
+
it('should delete both .json and .seed.json', () => {
|
|
110
|
+
const tmp = makeTmpDir();
|
|
111
|
+
const dir = path.join(tmp, '.codeyam', 'editor-scenarios');
|
|
112
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
113
|
+
fs.writeFileSync(path.join(dir, 'abc.json'), '{}');
|
|
114
|
+
fs.writeFileSync(path.join(dir, 'abc.seed.json'), '{}');
|
|
115
|
+
deleteScenarioFiles(tmp, 'abc');
|
|
116
|
+
expect(fs.existsSync(path.join(dir, 'abc.json'))).toBe(false);
|
|
117
|
+
expect(fs.existsSync(path.join(dir, 'abc.seed.json'))).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
it('should not throw for missing files', () => {
|
|
120
|
+
const tmp = makeTmpDir();
|
|
121
|
+
expect(() => deleteScenarioFiles(tmp, 'nonexistent')).not.toThrow();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
// ─── backfillScenarioMetadata ─────────────────────────────────────
|
|
125
|
+
describe('backfillScenarioMetadata', () => {
|
|
126
|
+
it('should add _metadata to files that lack it', () => {
|
|
127
|
+
const tmp = makeTmpDir();
|
|
128
|
+
// Write a file WITHOUT _metadata (legacy)
|
|
129
|
+
writeScenarioFile(tmp, 'abc-123', {
|
|
130
|
+
type: 'application',
|
|
131
|
+
seed: { users: [] },
|
|
202
132
|
});
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
133
|
+
const updated = backfillScenarioMetadata(tmp, [
|
|
134
|
+
{
|
|
135
|
+
id: 'abc-123',
|
|
136
|
+
name: 'Home Page',
|
|
137
|
+
description: 'Default view',
|
|
138
|
+
component_name: null,
|
|
139
|
+
component_path: null,
|
|
140
|
+
url: '/',
|
|
141
|
+
type: 'application',
|
|
142
|
+
created_at: '2024-01-01T00:00:00.000Z',
|
|
143
|
+
updated_at: '2024-01-02T00:00:00.000Z',
|
|
144
|
+
},
|
|
145
|
+
]);
|
|
146
|
+
expect(updated).toBe(1);
|
|
147
|
+
// Verify _metadata was written
|
|
148
|
+
const filePath = path.join(tmp, '.codeyam', 'editor-scenarios', 'abc-123.json');
|
|
149
|
+
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
150
|
+
expect(content._metadata.name).toBe('Home Page');
|
|
151
|
+
expect(content._metadata.url).toBe('/');
|
|
152
|
+
// Original data preserved
|
|
153
|
+
expect(content.type).toBe('application');
|
|
154
|
+
expect(content.seed).toEqual({ users: [] });
|
|
155
|
+
});
|
|
156
|
+
it('should skip files that already have _metadata', () => {
|
|
157
|
+
const tmp = makeTmpDir();
|
|
158
|
+
writeScenarioFile(tmp, 'abc-123', { seed: {} }, makeMetadata({ name: 'Existing' }));
|
|
159
|
+
const updated = backfillScenarioMetadata(tmp, [
|
|
160
|
+
{
|
|
161
|
+
id: 'abc-123',
|
|
162
|
+
name: 'From DB',
|
|
163
|
+
description: null,
|
|
164
|
+
component_name: null,
|
|
165
|
+
component_path: null,
|
|
166
|
+
url: '/',
|
|
167
|
+
type: 'application',
|
|
168
|
+
created_at: '2024-01-01T00:00:00.000Z',
|
|
169
|
+
updated_at: '2024-01-01T00:00:00.000Z',
|
|
170
|
+
},
|
|
171
|
+
]);
|
|
172
|
+
expect(updated).toBe(0);
|
|
173
|
+
// Verify original _metadata preserved
|
|
174
|
+
const filePath = path.join(tmp, '.codeyam', 'editor-scenarios', 'abc-123.json');
|
|
175
|
+
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
176
|
+
expect(content._metadata.name).toBe('Existing');
|
|
177
|
+
});
|
|
178
|
+
it('should skip DB rows with no matching file', () => {
|
|
179
|
+
const tmp = makeTmpDir();
|
|
180
|
+
const dir = path.join(tmp, '.codeyam', 'editor-scenarios');
|
|
181
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
182
|
+
const updated = backfillScenarioMetadata(tmp, [
|
|
183
|
+
{
|
|
184
|
+
id: 'nonexistent',
|
|
185
|
+
name: 'Ghost',
|
|
186
|
+
description: null,
|
|
187
|
+
component_name: null,
|
|
188
|
+
component_path: null,
|
|
189
|
+
url: '/',
|
|
190
|
+
type: null,
|
|
191
|
+
created_at: '2024-01-01T00:00:00.000Z',
|
|
192
|
+
updated_at: '2024-01-01T00:00:00.000Z',
|
|
193
|
+
},
|
|
194
|
+
]);
|
|
195
|
+
expect(updated).toBe(0);
|
|
196
|
+
});
|
|
197
|
+
it('should handle mix of files with and without _metadata', () => {
|
|
198
|
+
const tmp = makeTmpDir();
|
|
199
|
+
writeScenarioFile(tmp, 'has-meta', { seed: {} }, makeMetadata());
|
|
200
|
+
writeScenarioFile(tmp, 'no-meta', { type: 'application', seed: {} });
|
|
201
|
+
const updated = backfillScenarioMetadata(tmp, [
|
|
202
|
+
{
|
|
203
|
+
id: 'has-meta',
|
|
204
|
+
name: 'A',
|
|
205
|
+
description: null,
|
|
206
|
+
component_name: null,
|
|
207
|
+
component_path: null,
|
|
208
|
+
url: '/',
|
|
209
|
+
type: null,
|
|
210
|
+
created_at: '2024-01-01T00:00:00.000Z',
|
|
211
|
+
updated_at: '2024-01-01T00:00:00.000Z',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: 'no-meta',
|
|
215
|
+
name: 'B',
|
|
216
|
+
description: null,
|
|
217
|
+
component_name: null,
|
|
218
|
+
component_path: null,
|
|
219
|
+
url: '/about',
|
|
220
|
+
type: 'application',
|
|
221
|
+
created_at: '2024-01-01T00:00:00.000Z',
|
|
222
|
+
updated_at: '2024-01-01T00:00:00.000Z',
|
|
223
|
+
},
|
|
224
|
+
]);
|
|
225
|
+
expect(updated).toBe(1); // Only no-meta was updated
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
// ─── syncScenarioFilesToDatabase ──────────────────────────────────
|
|
229
|
+
describe('syncScenarioFilesToDatabase', () => {
|
|
230
|
+
it('should insert scenarios from _metadata files', async () => {
|
|
231
|
+
const tmp = makeTmpDir();
|
|
232
|
+
writeScenarioFile(tmp, 'new-1', { seed: {} }, makeMetadata({ name: 'New Scenario' }));
|
|
233
|
+
const insertedRows = [];
|
|
234
|
+
const result = await syncScenarioFilesToDatabase(tmp, 'project-1', [], (row) => {
|
|
235
|
+
insertedRows.push(row);
|
|
236
|
+
}, () => { });
|
|
237
|
+
expect(result.inserted).toBe(1);
|
|
238
|
+
expect(insertedRows[0].id).toBe('new-1');
|
|
239
|
+
expect(insertedRows[0].name).toBe('New Scenario');
|
|
240
|
+
expect(insertedRows[0].project_id).toBe('project-1');
|
|
241
|
+
expect(insertedRows[0].type).toBe('application');
|
|
242
|
+
});
|
|
243
|
+
it('should sync type, screenshot_path, and viewport from _metadata', async () => {
|
|
244
|
+
const tmp = makeTmpDir();
|
|
245
|
+
writeScenarioFile(tmp, 'full-1', { seed: {} }, makeMetadata({
|
|
246
|
+
name: 'Full Scenario',
|
|
247
|
+
type: 'user',
|
|
248
|
+
screenshotPath: 'screenshots/full-1.png',
|
|
249
|
+
viewportWidth: 1280,
|
|
250
|
+
viewportHeight: 720,
|
|
251
|
+
}));
|
|
252
|
+
const insertedRows = [];
|
|
253
|
+
await syncScenarioFilesToDatabase(tmp, 'project-1', [], (row) => {
|
|
254
|
+
insertedRows.push(row);
|
|
255
|
+
}, () => { });
|
|
256
|
+
expect(insertedRows[0].type).toBe('user');
|
|
257
|
+
expect(insertedRows[0].screenshot_path).toBe('screenshots/full-1.png');
|
|
258
|
+
expect(insertedRows[0].viewport_width).toBe(1280);
|
|
259
|
+
expect(insertedRows[0].viewport_height).toBe(720);
|
|
260
|
+
});
|
|
261
|
+
it('should skip scenarios already in DB with same updatedAt', async () => {
|
|
262
|
+
const tmp = makeTmpDir();
|
|
263
|
+
writeScenarioFile(tmp, 'existing-1', { seed: {} }, makeMetadata({ updatedAt: '2024-01-01T00:00:00.000Z' }));
|
|
264
|
+
const result = await syncScenarioFilesToDatabase(tmp, 'project-1', [{ id: 'existing-1', updated_at: '2024-01-01T00:00:00.000Z' }], () => { }, () => { });
|
|
265
|
+
expect(result.skipped).toBe(1);
|
|
266
|
+
expect(result.inserted).toBe(0);
|
|
267
|
+
expect(result.updated).toBe(0);
|
|
268
|
+
});
|
|
269
|
+
it('should update scenarios newer than DB row', async () => {
|
|
270
|
+
const tmp = makeTmpDir();
|
|
271
|
+
writeScenarioFile(tmp, 'existing-1', { seed: {} }, makeMetadata({ name: 'Updated', updatedAt: '2024-02-01T00:00:00.000Z' }));
|
|
272
|
+
const updatedRows = [];
|
|
273
|
+
const result = await syncScenarioFilesToDatabase(tmp, 'project-1', [{ id: 'existing-1', updated_at: '2024-01-01T00:00:00.000Z' }], () => { }, (id, row) => {
|
|
274
|
+
updatedRows.push({ id, ...row });
|
|
210
275
|
});
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
276
|
+
expect(result.updated).toBe(1);
|
|
277
|
+
expect(updatedRows[0].name).toBe('Updated');
|
|
278
|
+
});
|
|
279
|
+
it('should handle empty directory', async () => {
|
|
280
|
+
const tmp = makeTmpDir();
|
|
281
|
+
const dir = path.join(tmp, '.codeyam', 'editor-scenarios');
|
|
282
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
283
|
+
const result = await syncScenarioFilesToDatabase(tmp, 'project-1', [], () => { }, () => { });
|
|
284
|
+
expect(result.inserted).toBe(0);
|
|
285
|
+
});
|
|
286
|
+
it('should handle mixed insert/update/skip', async () => {
|
|
287
|
+
const tmp = makeTmpDir();
|
|
288
|
+
writeScenarioFile(tmp, 'new-1', { seed: {} }, makeMetadata({ name: 'New', updatedAt: '2024-01-01T00:00:00.000Z' }));
|
|
289
|
+
writeScenarioFile(tmp, 'updated-1', { seed: {} }, makeMetadata({ name: 'Updated', updatedAt: '2024-02-01T00:00:00.000Z' }));
|
|
290
|
+
writeScenarioFile(tmp, 'current-1', { seed: {} }, makeMetadata({ name: 'Current', updatedAt: '2024-01-01T00:00:00.000Z' }));
|
|
291
|
+
const result = await syncScenarioFilesToDatabase(tmp, 'project-1', [
|
|
292
|
+
{ id: 'updated-1', updated_at: '2024-01-01T00:00:00.000Z' },
|
|
293
|
+
{ id: 'current-1', updated_at: '2024-01-01T00:00:00.000Z' },
|
|
294
|
+
], () => { }, () => { });
|
|
295
|
+
expect(result.inserted).toBe(1);
|
|
296
|
+
expect(result.updated).toBe(1);
|
|
297
|
+
expect(result.skipped).toBe(1);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
// ─── migrateScenarioFormats ──────────────────────────────────────
|
|
301
|
+
describe('migrateScenarioFormats', () => {
|
|
302
|
+
function writeConfigWithSizes(tmp, screenSizes, defaultScreenSize) {
|
|
303
|
+
const dir = path.join(tmp, '.codeyam');
|
|
304
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
305
|
+
fs.writeFileSync(path.join(dir, 'config.json'), JSON.stringify({
|
|
306
|
+
...(defaultScreenSize ? { defaultScreenSize } : {}),
|
|
307
|
+
...(Object.keys(screenSizes).length > 0 ? { screenSizes } : {}),
|
|
308
|
+
}));
|
|
309
|
+
}
|
|
310
|
+
it('should resolve null viewport using project defaultScreenSize', () => {
|
|
311
|
+
const tmp = makeTmpDir();
|
|
312
|
+
writeConfigWithSizes(tmp, { Desktop: { width: 1440, height: 900 } }, { name: 'Desktop', width: 1440, height: 900 });
|
|
313
|
+
writeScenarioFile(tmp, 'null-vp', { seed: {} }, makeMetadata({
|
|
314
|
+
name: 'Home Page',
|
|
315
|
+
viewportWidth: null,
|
|
316
|
+
viewportHeight: null,
|
|
317
|
+
}));
|
|
318
|
+
const result = migrateScenarioFormats(tmp);
|
|
319
|
+
expect(result.fixed).toBe(1);
|
|
320
|
+
const filePath = path.join(tmp, '.codeyam', 'editor-scenarios', 'null-vp.json');
|
|
321
|
+
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
322
|
+
expect(content._metadata.viewportWidth).toBe(1440);
|
|
323
|
+
expect(content._metadata.viewportHeight).toBe(900);
|
|
324
|
+
});
|
|
325
|
+
it('should wrap single dimension into dimensions array', () => {
|
|
326
|
+
const tmp = makeTmpDir();
|
|
327
|
+
writeConfigWithSizes(tmp, { Mobile: { width: 375, height: 667 } });
|
|
328
|
+
writeScenarioFile(tmp, 'single-dim', { seed: {} }, makeMetadata({
|
|
329
|
+
name: 'Mobile View',
|
|
330
|
+
dimension: 'Mobile',
|
|
331
|
+
viewportWidth: 375,
|
|
332
|
+
viewportHeight: 667,
|
|
333
|
+
}));
|
|
334
|
+
const result = migrateScenarioFormats(tmp);
|
|
335
|
+
expect(result.fixed).toBe(1);
|
|
336
|
+
const filePath = path.join(tmp, '.codeyam', 'editor-scenarios', 'single-dim.json');
|
|
337
|
+
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
338
|
+
expect(content._metadata.dimensions).toEqual(['Mobile']);
|
|
339
|
+
});
|
|
340
|
+
it('should build screenshotPaths from single screenshotPath + dimension', () => {
|
|
341
|
+
const tmp = makeTmpDir();
|
|
342
|
+
writeConfigWithSizes(tmp, { Desktop: { width: 1440, height: 900 } });
|
|
343
|
+
writeScenarioFile(tmp, 'single-ss', { seed: {} }, makeMetadata({
|
|
344
|
+
name: 'Dashboard',
|
|
345
|
+
dimension: 'Desktop',
|
|
346
|
+
screenshotPath: 'screenshots/single-ss.png',
|
|
347
|
+
viewportWidth: 1440,
|
|
348
|
+
viewportHeight: 900,
|
|
349
|
+
}));
|
|
350
|
+
const result = migrateScenarioFormats(tmp);
|
|
351
|
+
expect(result.fixed).toBe(1);
|
|
352
|
+
const filePath = path.join(tmp, '.codeyam', 'editor-scenarios', 'single-ss.json');
|
|
353
|
+
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
354
|
+
expect(content._metadata.screenshotPaths).toEqual({
|
|
355
|
+
Desktop: 'screenshots/single-ss.png',
|
|
217
356
|
});
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
357
|
+
});
|
|
358
|
+
it('should skip files that are already in the correct format', () => {
|
|
359
|
+
const tmp = makeTmpDir();
|
|
360
|
+
writeConfigWithSizes(tmp, { Desktop: { width: 1440, height: 900 } });
|
|
361
|
+
writeScenarioFile(tmp, 'good-fmt', { seed: {} }, makeMetadata({
|
|
362
|
+
name: 'Good Scenario',
|
|
363
|
+
viewportWidth: 1440,
|
|
364
|
+
viewportHeight: 900,
|
|
365
|
+
dimension: 'Desktop',
|
|
366
|
+
dimensions: ['Desktop'],
|
|
367
|
+
screenshotPaths: { Desktop: 'screenshots/good-fmt.png' },
|
|
368
|
+
screenshotPath: 'screenshots/good-fmt.png',
|
|
369
|
+
}));
|
|
370
|
+
const result = migrateScenarioFormats(tmp);
|
|
371
|
+
expect(result.fixed).toBe(0);
|
|
372
|
+
expect(result.scanned).toBe(1);
|
|
373
|
+
});
|
|
374
|
+
it('should fix multiple issues in a single file', () => {
|
|
375
|
+
const tmp = makeTmpDir();
|
|
376
|
+
writeConfigWithSizes(tmp, { Tablet: { width: 768, height: 1024 } }, { name: 'Tablet', width: 768, height: 1024 });
|
|
377
|
+
// File has null viewport, dimension set but no dimensions array, screenshotPath but no map
|
|
378
|
+
writeScenarioFile(tmp, 'multi-fix', { seed: {} }, makeMetadata({
|
|
379
|
+
name: 'Tablet View',
|
|
380
|
+
dimension: 'Tablet',
|
|
381
|
+
viewportWidth: null,
|
|
382
|
+
viewportHeight: null,
|
|
383
|
+
screenshotPath: 'screenshots/multi-fix.png',
|
|
384
|
+
}));
|
|
385
|
+
const result = migrateScenarioFormats(tmp);
|
|
386
|
+
expect(result.fixed).toBe(1);
|
|
387
|
+
const filePath = path.join(tmp, '.codeyam', 'editor-scenarios', 'multi-fix.json');
|
|
388
|
+
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
389
|
+
// Null viewport resolved via dimension name lookup
|
|
390
|
+
expect(content._metadata.viewportWidth).toBe(768);
|
|
391
|
+
expect(content._metadata.viewportHeight).toBe(1024);
|
|
392
|
+
// dimensions array created from single dimension
|
|
393
|
+
expect(content._metadata.dimensions).toEqual(['Tablet']);
|
|
394
|
+
// screenshotPaths map created from single screenshotPath
|
|
395
|
+
expect(content._metadata.screenshotPaths).toEqual({
|
|
396
|
+
Tablet: 'screenshots/multi-fix.png',
|
|
243
397
|
});
|
|
244
398
|
});
|
|
399
|
+
it('should use hardcoded fallback (1280x720) when no project config exists', () => {
|
|
400
|
+
const tmp = makeTmpDir();
|
|
401
|
+
// No config.json at all
|
|
402
|
+
writeScenarioFile(tmp, 'no-config', { seed: {} }, makeMetadata({
|
|
403
|
+
name: 'Bare Scenario',
|
|
404
|
+
viewportWidth: null,
|
|
405
|
+
viewportHeight: null,
|
|
406
|
+
}));
|
|
407
|
+
const result = migrateScenarioFormats(tmp);
|
|
408
|
+
expect(result.fixed).toBe(1);
|
|
409
|
+
const filePath = path.join(tmp, '.codeyam', 'editor-scenarios', 'no-config.json');
|
|
410
|
+
const content = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
411
|
+
expect(content._metadata.viewportWidth).toBe(1280);
|
|
412
|
+
expect(content._metadata.viewportHeight).toBe(720);
|
|
413
|
+
});
|
|
414
|
+
it('should handle empty scenarios directory', () => {
|
|
415
|
+
const tmp = makeTmpDir();
|
|
416
|
+
const dir = path.join(tmp, '.codeyam', 'editor-scenarios');
|
|
417
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
418
|
+
const result = migrateScenarioFormats(tmp);
|
|
419
|
+
expect(result.scanned).toBe(0);
|
|
420
|
+
expect(result.fixed).toBe(0);
|
|
421
|
+
});
|
|
422
|
+
it('should handle missing scenarios directory', () => {
|
|
423
|
+
const tmp = makeTmpDir();
|
|
424
|
+
const result = migrateScenarioFormats(tmp);
|
|
425
|
+
expect(result.scanned).toBe(0);
|
|
426
|
+
expect(result.fixed).toBe(0);
|
|
427
|
+
});
|
|
428
|
+
it('should preserve all non-metadata data in the file', () => {
|
|
429
|
+
const tmp = makeTmpDir();
|
|
430
|
+
writeConfigWithSizes(tmp, {}, { name: 'Desktop', width: 1440, height: 900 });
|
|
431
|
+
// Write file with extra data alongside _metadata
|
|
432
|
+
const dir = path.join(tmp, '.codeyam', 'editor-scenarios');
|
|
433
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
434
|
+
const fileContent = {
|
|
435
|
+
_metadata: makeMetadata({
|
|
436
|
+
name: 'With Data',
|
|
437
|
+
viewportWidth: null,
|
|
438
|
+
viewportHeight: null,
|
|
439
|
+
}),
|
|
440
|
+
type: 'application',
|
|
441
|
+
seed: { users: [{ id: 1, name: 'Alice' }] },
|
|
442
|
+
session: { cookieValue: 'abc123' },
|
|
443
|
+
};
|
|
444
|
+
fs.writeFileSync(path.join(dir, 'with-data.json'), JSON.stringify(fileContent, null, 2));
|
|
445
|
+
migrateScenarioFormats(tmp);
|
|
446
|
+
const content = JSON.parse(fs.readFileSync(path.join(dir, 'with-data.json'), 'utf8'));
|
|
447
|
+
expect(content.type).toBe('application');
|
|
448
|
+
expect(content.seed).toEqual({ users: [{ id: 1, name: 'Alice' }] });
|
|
449
|
+
expect(content.session).toEqual({ cookieValue: 'abc123' });
|
|
450
|
+
// And metadata was fixed
|
|
451
|
+
expect(content._metadata.viewportWidth).toBe(1440);
|
|
452
|
+
});
|
|
245
453
|
});
|
|
246
454
|
//# sourceMappingURL=scenariosManifest.test.js.map
|