edsger 0.46.0 → 0.48.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.
Files changed (121) hide show
  1. package/.claude/settings.local.json +3 -23
  2. package/dist/api/__tests__/app-store.test.d.ts +7 -0
  3. package/dist/api/__tests__/app-store.test.js +60 -0
  4. package/dist/api/__tests__/intelligence.test.d.ts +11 -0
  5. package/dist/api/__tests__/intelligence.test.js +315 -0
  6. package/dist/api/features/__tests__/feature-utils.test.d.ts +4 -0
  7. package/dist/api/features/__tests__/feature-utils.test.js +370 -0
  8. package/dist/api/features/__tests__/status-updater.test.d.ts +4 -0
  9. package/dist/api/features/__tests__/status-updater.test.js +88 -0
  10. package/dist/commands/build/__tests__/build.test.d.ts +5 -0
  11. package/dist/commands/build/__tests__/build.test.js +206 -0
  12. package/dist/commands/build/__tests__/detect-project.test.d.ts +6 -0
  13. package/dist/commands/build/__tests__/detect-project.test.js +160 -0
  14. package/dist/commands/build/__tests__/run-build.test.d.ts +6 -0
  15. package/dist/commands/build/__tests__/run-build.test.js +433 -0
  16. package/dist/commands/intelligence/__tests__/command.test.d.ts +4 -0
  17. package/dist/commands/intelligence/__tests__/command.test.js +48 -0
  18. package/dist/commands/run-sheet/index.js +6 -0
  19. package/dist/commands/workflow/core/__tests__/feature-filter.test.d.ts +5 -0
  20. package/dist/commands/workflow/core/__tests__/feature-filter.test.js +316 -0
  21. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.d.ts +4 -0
  22. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.js +397 -0
  23. package/dist/commands/workflow/core/__tests__/state-manager.test.d.ts +4 -0
  24. package/dist/commands/workflow/core/__tests__/state-manager.test.js +384 -0
  25. package/dist/config/__tests__/config.test.d.ts +4 -0
  26. package/dist/config/__tests__/config.test.js +286 -0
  27. package/dist/config/__tests__/feature-status.test.d.ts +4 -0
  28. package/dist/config/__tests__/feature-status.test.js +111 -0
  29. package/dist/errors/__tests__/index.test.d.ts +4 -0
  30. package/dist/errors/__tests__/index.test.js +349 -0
  31. package/dist/index.js +0 -0
  32. package/dist/phases/app-store-generation/__tests__/agent.test.d.ts +5 -0
  33. package/dist/phases/app-store-generation/__tests__/agent.test.js +142 -0
  34. package/dist/phases/app-store-generation/__tests__/context.test.d.ts +4 -0
  35. package/dist/phases/app-store-generation/__tests__/context.test.js +284 -0
  36. package/dist/phases/app-store-generation/__tests__/prompts.test.d.ts +4 -0
  37. package/dist/phases/app-store-generation/__tests__/prompts.test.js +122 -0
  38. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.d.ts +5 -0
  39. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +826 -0
  40. package/dist/phases/code-review/__tests__/diff-utils.test.d.ts +1 -0
  41. package/dist/phases/code-review/__tests__/diff-utils.test.js +101 -0
  42. package/dist/phases/intelligence-analysis/__tests__/context.test.d.ts +4 -0
  43. package/dist/phases/intelligence-analysis/__tests__/context.test.js +192 -0
  44. package/dist/phases/intelligence-analysis/__tests__/matching.test.d.ts +13 -0
  45. package/dist/phases/intelligence-analysis/__tests__/matching.test.js +154 -0
  46. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.d.ts +5 -0
  47. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +378 -0
  48. package/dist/phases/intelligence-analysis/__tests__/prompts.test.d.ts +4 -0
  49. package/dist/phases/intelligence-analysis/__tests__/prompts.test.js +33 -0
  50. package/dist/phases/pr-execution/__tests__/file-assigner.test.d.ts +1 -0
  51. package/dist/phases/pr-execution/__tests__/file-assigner.test.js +303 -0
  52. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +1 -0
  53. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +157 -0
  54. package/dist/phases/pr-resolve/__tests__/prompts.test.d.ts +1 -0
  55. package/dist/phases/pr-resolve/__tests__/prompts.test.js +116 -0
  56. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.d.ts +1 -0
  57. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +138 -0
  58. package/dist/phases/pr-resolve/__tests__/types.test.d.ts +1 -0
  59. package/dist/phases/pr-resolve/__tests__/types.test.js +43 -0
  60. package/dist/phases/pr-resolve/__tests__/workspace.test.d.ts +1 -0
  61. package/dist/phases/pr-resolve/__tests__/workspace.test.js +111 -0
  62. package/dist/phases/pr-review/__tests__/prompts.test.d.ts +1 -0
  63. package/dist/phases/pr-review/__tests__/prompts.test.js +49 -0
  64. package/dist/phases/pr-review/__tests__/review-comments.test.d.ts +1 -0
  65. package/dist/phases/pr-review/__tests__/review-comments.test.js +110 -0
  66. package/dist/phases/pr-shared/__tests__/agent-utils.test.d.ts +1 -0
  67. package/dist/phases/pr-shared/__tests__/agent-utils.test.js +91 -0
  68. package/dist/phases/pr-shared/__tests__/context.test.d.ts +1 -0
  69. package/dist/phases/pr-shared/__tests__/context.test.js +94 -0
  70. package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.d.ts +1 -0
  71. package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.js +331 -0
  72. package/dist/phases/release-sync/github.d.ts +12 -0
  73. package/dist/phases/release-sync/github.js +39 -0
  74. package/dist/phases/release-sync/snapshot.js +0 -1
  75. package/dist/phases/run-sheet/index.d.ts +15 -0
  76. package/dist/phases/run-sheet/index.js +154 -22
  77. package/dist/phases/run-sheet/render.d.ts +23 -5
  78. package/dist/phases/run-sheet/render.js +193 -31
  79. package/dist/phases/smoke-test/__tests__/agent.test.d.ts +4 -0
  80. package/dist/phases/smoke-test/__tests__/agent.test.js +84 -0
  81. package/dist/phases/smoke-test/__tests__/github.test.d.ts +9 -0
  82. package/dist/phases/smoke-test/__tests__/github.test.js +120 -0
  83. package/dist/phases/smoke-test/__tests__/snapshot.test.d.ts +8 -0
  84. package/dist/phases/smoke-test/__tests__/snapshot.test.js +93 -0
  85. package/dist/phases/smoke-test/github.d.ts +54 -0
  86. package/dist/phases/smoke-test/github.js +101 -0
  87. package/dist/phases/smoke-test/snapshot.d.ts +27 -0
  88. package/dist/phases/smoke-test/snapshot.js +157 -0
  89. package/dist/services/coaching/__tests__/coaching-agent.test.d.ts +1 -0
  90. package/dist/services/coaching/__tests__/coaching-agent.test.js +74 -0
  91. package/dist/services/coaching/__tests__/coaching-loop.test.d.ts +1 -0
  92. package/dist/services/coaching/__tests__/coaching-loop.test.js +59 -0
  93. package/dist/services/coaching/__tests__/self-rating.test.d.ts +1 -0
  94. package/dist/services/coaching/__tests__/self-rating.test.js +188 -0
  95. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +4 -0
  96. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +133 -0
  97. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +4 -0
  98. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +336 -0
  99. package/dist/services/lifecycle-agent/index.d.ts +24 -0
  100. package/dist/services/lifecycle-agent/index.js +25 -0
  101. package/dist/services/lifecycle-agent/phase-criteria.d.ts +57 -0
  102. package/dist/services/lifecycle-agent/phase-criteria.js +335 -0
  103. package/dist/services/lifecycle-agent/transition-rules.d.ts +60 -0
  104. package/dist/services/lifecycle-agent/transition-rules.js +184 -0
  105. package/dist/services/lifecycle-agent/types.d.ts +190 -0
  106. package/dist/services/lifecycle-agent/types.js +12 -0
  107. package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.d.ts +1 -0
  108. package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.js +122 -0
  109. package/dist/services/phase-hooks/__tests__/hook-executor.test.d.ts +1 -0
  110. package/dist/services/phase-hooks/__tests__/hook-executor.test.js +321 -0
  111. package/dist/services/phase-hooks/__tests__/hook-runner.test.d.ts +1 -0
  112. package/dist/services/phase-hooks/__tests__/hook-runner.test.js +261 -0
  113. package/dist/services/phase-hooks/__tests__/plugin-loader.test.d.ts +1 -0
  114. package/dist/services/phase-hooks/__tests__/plugin-loader.test.js +158 -0
  115. package/dist/services/video/__tests__/video-pipeline.test.d.ts +6 -0
  116. package/dist/services/video/__tests__/video-pipeline.test.js +249 -0
  117. package/dist/workspace/__tests__/workspace-manager.test.d.ts +7 -0
  118. package/dist/workspace/__tests__/workspace-manager.test.js +52 -0
  119. package/dist/workspace/workspace-manager.js +17 -4
  120. package/package.json +1 -1
  121. package/.env.local +0 -12
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Unit tests for app store generation context formatting.
3
+ */
4
+ import assert from 'node:assert';
5
+ import { describe, it } from 'node:test';
6
+ import { formatContextForPrompt } from '../context.js';
7
+ // ============================================================
8
+ // formatContextForPrompt - Full Product Data
9
+ // ============================================================
10
+ void describe('formatContextForPrompt with full product data', () => {
11
+ const fullContext = {
12
+ product: {
13
+ id: 'prod-001',
14
+ name: 'TaskMaster Pro',
15
+ description: 'A powerful task management app for teams.',
16
+ features: [
17
+ {
18
+ name: 'Kanban Board',
19
+ description: 'Drag-and-drop task management',
20
+ status: 'shipped',
21
+ },
22
+ {
23
+ name: 'Time Tracking',
24
+ description: 'Built-in time tracking for tasks',
25
+ status: 'in_development',
26
+ },
27
+ {
28
+ name: 'Team Chat',
29
+ description: 'Real-time messaging between team members',
30
+ status: 'backlog',
31
+ },
32
+ ],
33
+ },
34
+ existingConfigs: [],
35
+ };
36
+ void it('should contain the product name', () => {
37
+ const result = formatContextForPrompt(fullContext);
38
+ assert.ok(result.includes('TaskMaster Pro'), 'Output should contain the product name');
39
+ });
40
+ void it('should contain the product description', () => {
41
+ const result = formatContextForPrompt(fullContext);
42
+ assert.ok(result.includes('A powerful task management app for teams.'), 'Output should contain the product description');
43
+ });
44
+ void it('should contain each feature name in bold', () => {
45
+ const result = formatContextForPrompt(fullContext);
46
+ assert.ok(result.includes('**Kanban Board**'), 'Output should contain Kanban Board in bold');
47
+ assert.ok(result.includes('**Time Tracking**'), 'Output should contain Time Tracking in bold');
48
+ assert.ok(result.includes('**Team Chat**'), 'Output should contain Team Chat in bold');
49
+ });
50
+ void it('should contain feature descriptions', () => {
51
+ const result = formatContextForPrompt(fullContext);
52
+ assert.ok(result.includes('Drag-and-drop task management'), 'Output should contain Kanban Board description');
53
+ assert.ok(result.includes('Built-in time tracking for tasks'), 'Output should contain Time Tracking description');
54
+ assert.ok(result.includes('Real-time messaging between team members'), 'Output should contain Team Chat description');
55
+ });
56
+ void it('should contain the feature count', () => {
57
+ const result = formatContextForPrompt(fullContext);
58
+ assert.ok(result.includes('(3)'), 'Output should contain the feature count (3)');
59
+ });
60
+ void it('should contain the product ID', () => {
61
+ const result = formatContextForPrompt(fullContext);
62
+ assert.ok(result.includes('prod-001'), 'Output should contain the product ID');
63
+ });
64
+ void it('should contain feature statuses', () => {
65
+ const result = formatContextForPrompt(fullContext);
66
+ assert.ok(result.includes('shipped'), 'Output should contain shipped status');
67
+ assert.ok(result.includes('in_development'), 'Output should contain in_development status');
68
+ assert.ok(result.includes('backlog'), 'Output should contain backlog status');
69
+ });
70
+ });
71
+ // ============================================================
72
+ // formatContextForPrompt - Empty Features
73
+ // ============================================================
74
+ void describe('formatContextForPrompt with empty features', () => {
75
+ void it('should contain "No features listed." when features array is empty', () => {
76
+ const context = {
77
+ product: {
78
+ id: 'prod-002',
79
+ name: 'EmptyApp',
80
+ description: 'An app with no features yet.',
81
+ features: [],
82
+ },
83
+ existingConfigs: [],
84
+ };
85
+ const result = formatContextForPrompt(context);
86
+ assert.ok(result.includes('No features listed.'), 'Output should contain "No features listed." for empty features');
87
+ });
88
+ void it('should contain "No features listed." when features is undefined', () => {
89
+ const context = {
90
+ product: {
91
+ id: 'prod-003',
92
+ name: 'NoFeaturesApp',
93
+ description: 'No features property.',
94
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
+ },
96
+ existingConfigs: [],
97
+ };
98
+ const result = formatContextForPrompt(context);
99
+ assert.ok(result.includes('No features listed.'), 'Output should contain "No features listed." when features is undefined');
100
+ });
101
+ void it('should show feature count as 0', () => {
102
+ const context = {
103
+ product: {
104
+ id: 'prod-004',
105
+ name: 'ZeroFeatures',
106
+ description: 'Zero.',
107
+ features: [],
108
+ },
109
+ existingConfigs: [],
110
+ };
111
+ const result = formatContextForPrompt(context);
112
+ assert.ok(result.includes('(0)'), 'Output should contain feature count (0)');
113
+ });
114
+ });
115
+ // ============================================================
116
+ // formatContextForPrompt - With Existing Configs
117
+ // ============================================================
118
+ void describe('formatContextForPrompt with existing configs', () => {
119
+ // Use 'as AppStoreContext' since we only need the fields formatContextForPrompt accesses
120
+ const contextWithConfigs = {
121
+ product: {
122
+ id: 'prod-005',
123
+ name: 'ConfiguredApp',
124
+ description: 'App with existing store configs.',
125
+ features: [],
126
+ },
127
+ existingConfigs: [
128
+ {
129
+ id: 'config-1',
130
+ product_id: 'prod-005',
131
+ store_type: 'apple_app_store',
132
+ listings: {
133
+ 'en-US': { app_name: 'ConfiguredApp', description: 'Test' },
134
+ 'ja-JP': { app_name: 'ConfiguredApp JP', description: 'Test JP' },
135
+ },
136
+ screenshots: {
137
+ 'en-US': [
138
+ { device_type: 'iphone', local_path: '/tmp/1.png' },
139
+ { device_type: 'iphone', local_path: '/tmp/2.png' },
140
+ { device_type: 'ipad', local_path: '/tmp/3.png' },
141
+ ],
142
+ },
143
+ submission_status: 'draft',
144
+ },
145
+ ],
146
+ };
147
+ void it('should contain store_type', () => {
148
+ const result = formatContextForPrompt(contextWithConfigs);
149
+ assert.ok(result.includes('apple_app_store'), 'Output should contain the store_type');
150
+ });
151
+ void it('should contain locale count', () => {
152
+ const result = formatContextForPrompt(contextWithConfigs);
153
+ assert.ok(result.includes('2 locale(s)'), 'Output should contain the locale count');
154
+ });
155
+ void it('should contain screenshot count', () => {
156
+ const result = formatContextForPrompt(contextWithConfigs);
157
+ assert.ok(result.includes('3 screenshots'), 'Output should contain the screenshot count');
158
+ });
159
+ void it('should contain submission_status', () => {
160
+ const result = formatContextForPrompt(contextWithConfigs);
161
+ assert.ok(result.includes('draft'), 'Output should contain the submission status');
162
+ });
163
+ void it('should list locale names', () => {
164
+ const result = formatContextForPrompt(contextWithConfigs);
165
+ assert.ok(result.includes('en-US'), 'Output should contain en-US locale');
166
+ assert.ok(result.includes('ja-JP'), 'Output should contain ja-JP locale');
167
+ });
168
+ });
169
+ // ============================================================
170
+ // formatContextForPrompt - No Existing Configs
171
+ // ============================================================
172
+ void describe('formatContextForPrompt with no existing configs', () => {
173
+ void it('should contain "No existing store configurations."', () => {
174
+ const context = {
175
+ product: {
176
+ id: 'prod-006',
177
+ name: 'FreshApp',
178
+ description: 'Brand new app.',
179
+ features: [],
180
+ },
181
+ existingConfigs: [],
182
+ };
183
+ const result = formatContextForPrompt(context);
184
+ assert.ok(result.includes('No existing store configurations.'), 'Output should contain "No existing store configurations."');
185
+ });
186
+ });
187
+ // ============================================================
188
+ // formatContextForPrompt - Missing Optional Fields
189
+ // ============================================================
190
+ void describe('formatContextForPrompt with missing optional fields', () => {
191
+ void it('should contain "No description provided" when description is missing', () => {
192
+ const context = {
193
+ product: {
194
+ id: 'prod-007',
195
+ name: 'NoDescApp',
196
+ features: [],
197
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
198
+ },
199
+ existingConfigs: [],
200
+ };
201
+ const result = formatContextForPrompt(context);
202
+ assert.ok(result.includes('No description provided'), 'Output should contain "No description provided" when description is missing');
203
+ });
204
+ void it('should contain "No description provided" when description is empty string', () => {
205
+ const context = {
206
+ product: {
207
+ id: 'prod-008',
208
+ name: 'EmptyDescApp',
209
+ description: '',
210
+ features: [],
211
+ },
212
+ existingConfigs: [],
213
+ };
214
+ const result = formatContextForPrompt(context);
215
+ assert.ok(result.includes('No description provided'), 'Output should contain "No description provided" when description is empty');
216
+ });
217
+ void it('should show "No description" for features missing descriptions', () => {
218
+ const context = {
219
+ product: {
220
+ id: 'prod-009',
221
+ name: 'PartialApp',
222
+ description: 'Has features without descriptions.',
223
+ features: [
224
+ { name: 'Feature A', status: 'backlog' },
225
+ { name: 'Feature B', description: '', status: 'shipped' },
226
+ ],
227
+ },
228
+ existingConfigs: [],
229
+ };
230
+ const result = formatContextForPrompt(context);
231
+ assert.ok(result.includes('No description'), 'Output should handle features with missing descriptions');
232
+ });
233
+ void it('should show "unknown" for features missing status', () => {
234
+ const context = {
235
+ product: {
236
+ id: 'prod-010',
237
+ name: 'NoStatusApp',
238
+ description: 'Features without status.',
239
+ features: [{ name: 'Feature X', description: 'Something' }],
240
+ },
241
+ existingConfigs: [],
242
+ };
243
+ const result = formatContextForPrompt(context);
244
+ assert.ok(result.includes('unknown'), 'Output should show "unknown" for features missing status');
245
+ });
246
+ });
247
+ // ============================================================
248
+ // formatContextForPrompt - Multiple Configs
249
+ // ============================================================
250
+ void describe('formatContextForPrompt with multiple configs', () => {
251
+ void it('should list all configs', () => {
252
+ const context = {
253
+ product: {
254
+ id: 'prod-011',
255
+ name: 'MultiStore',
256
+ description: 'On all stores.',
257
+ features: [],
258
+ },
259
+ existingConfigs: [
260
+ {
261
+ id: 'cfg-1',
262
+ product_id: 'prod-011',
263
+ store_type: 'apple_app_store',
264
+ listings: { 'en-US': {} },
265
+ screenshots: {},
266
+ submission_status: 'submitted',
267
+ },
268
+ {
269
+ id: 'cfg-2',
270
+ product_id: 'prod-011',
271
+ store_type: 'google_play',
272
+ listings: { 'en-US': {}, 'es-ES': {} },
273
+ screenshots: { 'en-US': [{ device_type: 'phone' }] },
274
+ submission_status: 'approved',
275
+ },
276
+ ],
277
+ };
278
+ const result = formatContextForPrompt(context);
279
+ assert.ok(result.includes('apple_app_store'), 'Output should contain apple_app_store');
280
+ assert.ok(result.includes('google_play'), 'Output should contain google_play');
281
+ assert.ok(result.includes('submitted'), 'Output should contain submitted status');
282
+ assert.ok(result.includes('approved'), 'Output should contain approved status');
283
+ });
284
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Unit tests for app store generation prompts.
3
+ */
4
+ export {};
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Unit tests for app store generation prompts.
3
+ */
4
+ import assert from 'node:assert';
5
+ import { describe, it } from 'node:test';
6
+ import { createAppStorePromptWithContext, STORE_SCREENSHOT_SIZES, } from '../prompts.js';
7
+ // ============================================================
8
+ // STORE_SCREENSHOT_SIZES
9
+ // ============================================================
10
+ void describe('STORE_SCREENSHOT_SIZES', () => {
11
+ void it('should have both apple and google keys', () => {
12
+ assert.ok(STORE_SCREENSHOT_SIZES.apple, 'Missing apple key');
13
+ assert.ok(STORE_SCREENSHOT_SIZES.google, 'Missing google key');
14
+ });
15
+ void it('should have correct Apple iPhone 6.7" dimensions', () => {
16
+ const size = STORE_SCREENSHOT_SIZES.apple.iphone_6_7;
17
+ assert.strictEqual(size.width, 1290, 'iPhone 6.7" width should be 1290');
18
+ assert.strictEqual(size.height, 2796, 'iPhone 6.7" height should be 2796');
19
+ assert.ok(size.label, 'iPhone 6.7" should have a label');
20
+ });
21
+ void it('should have correct Apple iPhone 6.5" dimensions', () => {
22
+ const size = STORE_SCREENSHOT_SIZES.apple.iphone_6_5;
23
+ assert.strictEqual(size.width, 1284, 'iPhone 6.5" width should be 1284');
24
+ assert.strictEqual(size.height, 2778, 'iPhone 6.5" height should be 2778');
25
+ assert.ok(size.label, 'iPhone 6.5" should have a label');
26
+ });
27
+ void it('should have correct Apple iPad 12.9" dimensions', () => {
28
+ const size = STORE_SCREENSHOT_SIZES.apple.ipad_12_9;
29
+ assert.strictEqual(size.width, 2048, 'iPad 12.9" width should be 2048');
30
+ assert.strictEqual(size.height, 2732, 'iPad 12.9" height should be 2732');
31
+ assert.ok(size.label, 'iPad 12.9" should have a label');
32
+ });
33
+ void it('should have correct Google Phone dimensions', () => {
34
+ const size = STORE_SCREENSHOT_SIZES.google.phone;
35
+ assert.strictEqual(size.width, 1080, 'Phone width should be 1080');
36
+ assert.strictEqual(size.height, 1920, 'Phone height should be 1920');
37
+ assert.ok(size.label, 'Phone should have a label');
38
+ });
39
+ void it('should have correct Google 7" Tablet dimensions', () => {
40
+ const size = STORE_SCREENSHOT_SIZES.google.tablet_7;
41
+ assert.strictEqual(size.width, 1200, 'Tablet 7" width should be 1200');
42
+ assert.strictEqual(size.height, 1920, 'Tablet 7" height should be 1920');
43
+ assert.ok(size.label, 'Tablet 7" should have a label');
44
+ });
45
+ void it('should have correct Google 10" Tablet dimensions', () => {
46
+ const size = STORE_SCREENSHOT_SIZES.google.tablet_10;
47
+ assert.strictEqual(size.width, 1920, 'Tablet 10" width should be 1920');
48
+ assert.strictEqual(size.height, 1200, 'Tablet 10" height should be 1200');
49
+ assert.ok(size.label, 'Tablet 10" should have a label');
50
+ });
51
+ void it('should have width, height, and label on all sizes', () => {
52
+ for (const [store, devices] of Object.entries(STORE_SCREENSHOT_SIZES)) {
53
+ for (const [device, size] of Object.entries(devices)) {
54
+ assert.ok(typeof size.width === 'number' && size.width > 0, `${store}/${device} width should be a positive number`);
55
+ assert.ok(typeof size.height === 'number' && size.height > 0, `${store}/${device} height should be a positive number`);
56
+ assert.ok(typeof size.label === 'string' && size.label.length > 0, `${store}/${device} should have a non-empty label`);
57
+ }
58
+ }
59
+ });
60
+ void it('should have all dimensions as positive integers', () => {
61
+ for (const [store, devices] of Object.entries(STORE_SCREENSHOT_SIZES)) {
62
+ for (const [device, size] of Object.entries(devices)) {
63
+ assert.strictEqual(size.width, Math.floor(size.width), `${store}/${device} width should be an integer`);
64
+ assert.strictEqual(size.height, Math.floor(size.height), `${store}/${device} height should be an integer`);
65
+ }
66
+ }
67
+ });
68
+ });
69
+ // ============================================================
70
+ // createAppStorePromptWithContext
71
+ // ============================================================
72
+ void describe('createAppStorePromptWithContext', () => {
73
+ const testProductId = 'test-product-123';
74
+ const testContext = '## Product Context\nThis is a test product.';
75
+ const testLocale = 'en-US';
76
+ void it('should include productId in output', () => {
77
+ const result = createAppStorePromptWithContext(testProductId, testContext, 'all', testLocale);
78
+ assert.ok(result.includes(testProductId), 'Prompt should include the product ID');
79
+ });
80
+ void it('should include contextInfo in output', () => {
81
+ const result = createAppStorePromptWithContext(testProductId, testContext, 'all', testLocale);
82
+ assert.ok(result.includes(testContext), 'Prompt should include the context info');
83
+ });
84
+ void it('should mention "Apple App Store only" when targetStore is apple', () => {
85
+ const result = createAppStorePromptWithContext(testProductId, testContext, 'apple', testLocale);
86
+ assert.ok(result.includes('Apple App Store only'), 'Should mention "Apple App Store only" for apple target');
87
+ });
88
+ void it('should mention "Google Play Store only" when targetStore is google', () => {
89
+ const result = createAppStorePromptWithContext(testProductId, testContext, 'google', testLocale);
90
+ assert.ok(result.includes('Google Play Store only'), 'Should mention "Google Play Store only" for google target');
91
+ });
92
+ void it('should mention "Both" stores when targetStore is all', () => {
93
+ const result = createAppStorePromptWithContext(testProductId, testContext, 'all', testLocale);
94
+ assert.ok(result.includes('Both'), 'Should mention "Both" for all target stores');
95
+ });
96
+ void it('should include locale in instructions', () => {
97
+ const result = createAppStorePromptWithContext(testProductId, testContext, 'all', 'ja-JP');
98
+ assert.ok(result.includes('ja-JP'), 'Should include the specified locale');
99
+ });
100
+ void it('should include codebase exploration step when hasCodebase=true', () => {
101
+ const result = createAppStorePromptWithContext(testProductId, testContext, 'all', testLocale, true);
102
+ assert.ok(result.includes('Explore the codebase'), 'Should include codebase exploration step when hasCodebase=true');
103
+ });
104
+ void it('should NOT include codebase exploration step when hasCodebase=false', () => {
105
+ const result = createAppStorePromptWithContext(testProductId, testContext, 'all', testLocale, false);
106
+ assert.ok(!result.includes('Explore the codebase'), 'Should NOT include codebase exploration step when hasCodebase=false');
107
+ });
108
+ void it('should mention screenshot count requirement (5-8)', () => {
109
+ const result = createAppStorePromptWithContext(testProductId, testContext, 'all', testLocale);
110
+ assert.ok(result.includes('5-8'), 'Should mention 5-8 screenshot count requirement');
111
+ });
112
+ void it('should mention iPhone devices for apple target', () => {
113
+ const result = createAppStorePromptWithContext(testProductId, testContext, 'apple', testLocale);
114
+ assert.ok(result.includes('iPhone'), 'Apple target should mention iPhone devices');
115
+ assert.ok(result.includes('iPad'), 'Apple target should mention iPad devices');
116
+ });
117
+ void it('should mention Phone/Tablet devices for google target', () => {
118
+ const result = createAppStorePromptWithContext(testProductId, testContext, 'google', testLocale);
119
+ assert.ok(result.includes('Phone'), 'Google target should mention Phone devices');
120
+ assert.ok(result.includes('Tablet'), 'Google target should mention Tablet devices');
121
+ });
122
+ });
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Unit tests for screenshot composer pure functions.
3
+ * Non-exported functions are re-implemented inline for testing (project pattern).
4
+ */
5
+ export {};