claw-dashboard 2.1.0 → 2.2.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 (90) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/docs/API.md +1 -2
  3. package/index.js +32 -42
  4. package/package.json +23 -12
  5. package/src/auto-save.js +11 -5
  6. package/src/cli/export-snapshot.js +3 -1
  7. package/src/cli/import-snapshot.js +3 -1
  8. package/src/config.js +9 -15
  9. package/src/errors.js +0 -9
  10. package/src/security.js +6 -2
  11. package/src/snapshot.js +73 -26
  12. package/src/web-server.js +7 -10
  13. package/src/widgets/builtin-widgets.js +0 -94
  14. package/src/widgets/index.js +0 -1
  15. package/.c8rc.json +0 -38
  16. package/.dockerignore +0 -68
  17. package/.github/dependabot.yml +0 -45
  18. package/.github/pull_request_template.md +0 -39
  19. package/.github/workflows/ci.yml +0 -147
  20. package/.github/workflows/release.yml +0 -40
  21. package/.github/workflows/security.yml +0 -84
  22. package/.husky/pre-commit +0 -1
  23. package/.planning/codebase/ARCHITECTURE.md +0 -206
  24. package/.planning/codebase/INTEGRATIONS.md +0 -150
  25. package/.planning/codebase/STACK.md +0 -122
  26. package/.planning/codebase/STRUCTURE.md +0 -201
  27. package/CONTRIBUTING.md +0 -378
  28. package/Dockerfile +0 -54
  29. package/FEATURES.md +0 -307
  30. package/TODO.md +0 -28
  31. package/ai.openclaw.dashboard.plist +0 -35
  32. package/build-cjs.js +0 -127
  33. package/cjs-shim.js +0 -13
  34. package/dist/clawdash +0 -1729
  35. package/dist/clawdash.meta.json +0 -2236
  36. package/dist/widgets.cjs +0 -6511
  37. package/docker-compose.yml +0 -67
  38. package/esbuild.config.js +0 -158
  39. package/eslint.config.js +0 -56
  40. package/examples/plugins/README.md +0 -122
  41. package/examples/plugins/api-status/index.js +0 -294
  42. package/examples/plugins/api-status/plugin.json +0 -19
  43. package/examples/plugins/hello-world/index.js +0 -104
  44. package/examples/plugins/hello-world/plugin.json +0 -15
  45. package/examples/plugins/system-metrics-chart/index.js +0 -339
  46. package/examples/plugins/system-metrics-chart/plugin.json +0 -18
  47. package/examples/plugins/weather-widget/index.js +0 -136
  48. package/examples/plugins/weather-widget/plugin.json +0 -19
  49. package/index.cjs +0 -23005
  50. package/jest.config.js +0 -11
  51. package/scripts/release.js +0 -595
  52. package/src/database.js +0 -734
  53. package/tests/alerts.test.js +0 -437
  54. package/tests/auto-save.test.js +0 -529
  55. package/tests/cache.test.js +0 -317
  56. package/tests/cli.test.js +0 -351
  57. package/tests/command-palette.test.js +0 -320
  58. package/tests/config-processor.test.js +0 -452
  59. package/tests/config-validator.test.js +0 -710
  60. package/tests/config-watcher.test.js +0 -594
  61. package/tests/config.test.js +0 -755
  62. package/tests/database.test.js +0 -438
  63. package/tests/errors.test.js +0 -189
  64. package/tests/example-plugins.test.js +0 -624
  65. package/tests/integration.test.js +0 -1321
  66. package/tests/loading-states.test.js +0 -300
  67. package/tests/manifest-validation-on-load.test.js +0 -671
  68. package/tests/performance-monitor.test.js +0 -190
  69. package/tests/plugin-api-rate-limit.test.js +0 -302
  70. package/tests/plugin-errors.test.js +0 -311
  71. package/tests/plugin-lifecycle-e2e.test.js +0 -1036
  72. package/tests/plugin-reload.test.js +0 -764
  73. package/tests/rate-limiter.test.js +0 -539
  74. package/tests/retry.test.js +0 -308
  75. package/tests/security.test.js +0 -411
  76. package/tests/settings-modal.test.js +0 -226
  77. package/tests/theme-selector.test.js +0 -57
  78. package/tests/utils.js +0 -242
  79. package/tests/utils.test.js +0 -317
  80. package/tests/validate-plugin-cli.test.js +0 -359
  81. package/tests/validation.test.js +0 -837
  82. package/tests/web-server.test.js +0 -646
  83. package/tests/widget-arrange-mode.test.js +0 -183
  84. package/tests/widget-config-hot-reload.test.js +0 -465
  85. package/tests/widget-dependency.test.js +0 -591
  86. package/tests/widget-error-boundary.test.js +0 -749
  87. package/tests/widget-error-isolation.test.js +0 -469
  88. package/tests/widget-integration.test.js +0 -1147
  89. package/tests/widget-refresh-intervals.test.js +0 -284
  90. package/tests/worker-pool.test.js +0 -522
@@ -1,671 +0,0 @@
1
- /**
2
- * Manifest Validation on Load Tests
3
- * Tests that plugin manifests are validated against the schema when loaded
4
- */
5
-
6
- import { jest } from '@jest/globals';
7
- import { join, dirname } from 'path';
8
- import { fileURLToPath } from 'url';
9
- import { mkdtemp, rm, writeFile, mkdir } from 'fs/promises';
10
- import { tmpdir } from 'os';
11
-
12
- const __filename = fileURLToPath(import.meta.url);
13
- const __dirname = dirname(__filename);
14
-
15
- // Import modules under test
16
- import { WidgetLoader } from '../src/widgets/widget-loader.js';
17
-
18
- describe('Manifest Validation on Load', () => {
19
- let loader;
20
- let tempDir;
21
-
22
- beforeEach(async () => {
23
- tempDir = await mkdtemp(join(tmpdir(), 'manifest-validation-test-'));
24
- loader = new WidgetLoader({ pluginsDir: tempDir });
25
- });
26
-
27
- afterEach(async () => {
28
- if (loader) {
29
- await loader.clear();
30
- }
31
- if (tempDir) {
32
- await rm(tempDir, { recursive: true, force: true });
33
- }
34
- });
35
-
36
- const createPlugin = async (pluginId, manifest, indexJs = '') => {
37
- const pluginDir = join(tempDir, pluginId);
38
- await mkdir(pluginDir, { recursive: true });
39
- await writeFile(join(pluginDir, 'plugin.json'), JSON.stringify(manifest, null, 2));
40
- await writeFile(
41
- join(pluginDir, 'index.js'),
42
- indexJs || `
43
- export default class TestWidget {
44
- constructor(config) { this.config = config; this.name = '${manifest.name || pluginId}'; }
45
- async render() {}
46
- async getData() { return {}; }
47
- }
48
- `
49
- );
50
- return pluginDir;
51
- };
52
-
53
- describe('Valid Manifests', () => {
54
- test('should load plugin with valid manifest', async () => {
55
- const validManifest = {
56
- id: 'test-widget',
57
- name: 'Test Widget',
58
- version: '1.0.0',
59
- type: 'widget',
60
- category: 'custom',
61
- };
62
-
63
- await createPlugin('test-widget', validManifest);
64
- const id = await loader.loadPlugin(join(tempDir, 'test-widget'));
65
-
66
- expect(id).toBe('test-widget');
67
- expect(loader.widgetRegistry.has('test-widget')).toBe(true);
68
- });
69
-
70
- test('should discover plugin with valid manifest', async () => {
71
- const validManifest = {
72
- id: 'discover-test',
73
- name: 'Discover Test',
74
- version: '1.0.0',
75
- type: 'widget',
76
- category: 'system',
77
- };
78
-
79
- await createPlugin('discover-test', validManifest);
80
- const discovered = await loader.discoverPlugins();
81
-
82
- expect(discovered.some(p => p.id === 'discover-test')).toBe(true);
83
- });
84
-
85
- test('should accept manifest with all optional fields', async () => {
86
- const fullManifest = {
87
- id: 'full-widget',
88
- name: 'Full Widget',
89
- description: 'A complete widget with all fields',
90
- version: '2.1.0-beta.1',
91
- author: 'Test Author <test@example.com>',
92
- category: 'monitoring',
93
- type: 'widget',
94
- lazyLoad: true,
95
- priority: 50,
96
- config: {
97
- refreshInterval: 5000,
98
- maxDataPoints: 30,
99
- },
100
- permissions: ['network', 'system'],
101
- dependencies: ['base-widget'],
102
- __version: 1,
103
- };
104
-
105
- await createPlugin('full-widget', fullManifest);
106
- const id = await loader.loadPlugin(join(tempDir, 'full-widget'));
107
-
108
- expect(id).toBe('full-widget');
109
- });
110
-
111
- test('should accept valid categories', async () => {
112
- const categories = ['system', 'monitoring', 'custom', 'example'];
113
-
114
- for (const category of categories) {
115
- const loader2 = new WidgetLoader({ pluginsDir: tempDir });
116
- const manifest = {
117
- id: `cat-${category}`,
118
- name: `Category ${category}`,
119
- version: '1.0.0',
120
- type: 'widget',
121
- category,
122
- };
123
-
124
- await createPlugin(`cat-${category}`, manifest);
125
- const id = await loader2.loadPlugin(join(tempDir, `cat-${category}`));
126
- expect(id).toBe(`cat-${category}`);
127
- await loader2.clear();
128
- }
129
- });
130
-
131
- test('should accept valid semantic versions', async () => {
132
- const versions = ['1.0.0', '0.0.1', '10.20.30', '1.0.0-alpha', '2.0.0-beta.1+build123'];
133
-
134
- for (const version of versions) {
135
- const loader2 = new WidgetLoader({ pluginsDir: tempDir });
136
- const manifest = {
137
- id: `ver-${version.replace(/[^a-zA-Z0-9]/g, '-')}`,
138
- name: `Version Test`,
139
- version,
140
- type: 'widget',
141
- };
142
-
143
- await createPlugin(`ver-${version.replace(/[^a-zA-Z0-9]/g, '-')}`, manifest);
144
- const id = await loader2.loadPlugin(join(tempDir, `ver-${version.replace(/[^a-zA-Z0-9]/g, '-')}`));
145
- expect(id).toBeTruthy();
146
- await loader2.clear();
147
- }
148
- });
149
- });
150
-
151
- describe('Invalid Manifests - Required Fields', () => {
152
- test('should reject manifest missing id', async () => {
153
- const invalidManifest = {
154
- name: 'Missing ID',
155
- version: '1.0.0',
156
- type: 'widget',
157
- // missing id
158
- };
159
-
160
- await createPlugin('missing-id', invalidManifest);
161
- const id = await loader.loadPlugin(join(tempDir, 'missing-id'));
162
-
163
- expect(id).toBeNull();
164
- expect(loader.widgetRegistry.has('missing-id')).toBe(false);
165
- });
166
-
167
- test('should reject manifest missing name', async () => {
168
- const invalidManifest = {
169
- id: 'missing-name',
170
- version: '1.0.0',
171
- type: 'widget',
172
- // missing name
173
- };
174
-
175
- await createPlugin('missing-name', invalidManifest);
176
- const id = await loader.loadPlugin(join(tempDir, 'missing-name'));
177
-
178
- expect(id).toBeNull();
179
- });
180
-
181
- test('should reject manifest missing version', async () => {
182
- const invalidManifest = {
183
- id: 'missing-version',
184
- name: 'Missing Version',
185
- type: 'widget',
186
- // missing version
187
- };
188
-
189
- await createPlugin('missing-version', invalidManifest);
190
- const id = await loader.loadPlugin(join(tempDir, 'missing-version'));
191
-
192
- expect(id).toBeNull();
193
- });
194
-
195
- test('should reject manifest missing type', async () => {
196
- const invalidManifest = {
197
- id: 'missing-type',
198
- name: 'Missing Type',
199
- version: '1.0.0',
200
- // missing type
201
- };
202
-
203
- await createPlugin('missing-type', invalidManifest);
204
- const id = await loader.loadPlugin(join(tempDir, 'missing-type'));
205
-
206
- expect(id).toBeNull();
207
- });
208
- });
209
-
210
- describe('Invalid Manifests - Field Formats', () => {
211
- test('should reject invalid id format', async () => {
212
- const invalidManifest = {
213
- id: '_invalid-start',
214
- name: 'Invalid ID',
215
- version: '1.0.0',
216
- type: 'widget',
217
- };
218
-
219
- await createPlugin('_invalid-start', invalidManifest);
220
- const id = await loader.loadPlugin(join(tempDir, '_invalid-start'));
221
-
222
- expect(id).toBeNull();
223
- });
224
-
225
- test('should reject id ending with underscore', async () => {
226
- const invalidManifest = {
227
- id: 'invalid-end_',
228
- name: 'Invalid ID End',
229
- version: '1.0.0',
230
- type: 'widget',
231
- };
232
-
233
- await createPlugin('invalid-end_', invalidManifest);
234
- const id = await loader.loadPlugin(join(tempDir, 'invalid-end_'));
235
-
236
- expect(id).toBeNull();
237
- });
238
-
239
- test('should reject invalid semantic version', async () => {
240
- const invalidManifest = {
241
- id: 'bad-version',
242
- name: 'Bad Version',
243
- version: 'not-a-version',
244
- type: 'widget',
245
- };
246
-
247
- await createPlugin('bad-version', invalidManifest);
248
- const id = await loader.loadPlugin(join(tempDir, 'bad-version'));
249
-
250
- expect(id).toBeNull();
251
- });
252
-
253
- test('should reject invalid version format (missing patch)', async () => {
254
- const invalidManifest = {
255
- id: 'short-version',
256
- name: 'Short Version',
257
- version: '1.0', // missing patch
258
- type: 'widget',
259
- };
260
-
261
- await createPlugin('short-version', invalidManifest);
262
- const id = await loader.loadPlugin(join(tempDir, 'short-version'));
263
-
264
- expect(id).toBeNull();
265
- });
266
-
267
- test('should reject invalid category', async () => {
268
- const invalidManifest = {
269
- id: 'bad-category',
270
- name: 'Bad Category',
271
- version: '1.0.0',
272
- type: 'widget',
273
- category: 'invalid-category',
274
- };
275
-
276
- await createPlugin('bad-category', invalidManifest);
277
- const id = await loader.loadPlugin(join(tempDir, 'bad-category'));
278
-
279
- expect(id).toBeNull();
280
- });
281
-
282
- test('should reject invalid type', async () => {
283
- const invalidManifest = {
284
- id: 'bad-type',
285
- name: 'Bad Type',
286
- version: '1.0.0',
287
- type: 'invalid-type',
288
- };
289
-
290
- await createPlugin('bad-type', invalidManifest);
291
- const id = await loader.loadPlugin(join(tempDir, 'bad-type'));
292
-
293
- expect(id).toBeNull();
294
- });
295
- });
296
-
297
- describe('Invalid Manifests - Type Validation', () => {
298
- test('should reject non-string id', async () => {
299
- const invalidManifest = {
300
- id: 123,
301
- name: 'Non-string ID',
302
- version: '1.0.0',
303
- type: 'widget',
304
- };
305
-
306
- await createPlugin('123', invalidManifest);
307
- const id = await loader.loadPlugin(join(tempDir, '123'));
308
-
309
- expect(id).toBeNull();
310
- });
311
-
312
- test('should reject non-string version', async () => {
313
- const invalidManifest = {
314
- id: 'num-version',
315
- name: 'Number Version',
316
- version: 100, // number instead of string
317
- type: 'widget',
318
- };
319
-
320
- await createPlugin('num-version', invalidManifest);
321
- const id = await loader.loadPlugin(join(tempDir, 'num-version'));
322
-
323
- expect(id).toBeNull();
324
- });
325
-
326
- test('should reject non-boolean lazyLoad', async () => {
327
- const invalidManifest = {
328
- id: 'bad-lazyload',
329
- name: 'Bad LazyLoad',
330
- version: '1.0.0',
331
- type: 'widget',
332
- lazyLoad: 'true', // string instead of boolean
333
- };
334
-
335
- await createPlugin('bad-lazyload', invalidManifest);
336
- const id = await loader.loadPlugin(join(tempDir, 'bad-lazyload'));
337
-
338
- expect(id).toBeNull();
339
- });
340
-
341
- test('should reject priority out of range (negative)', async () => {
342
- const invalidManifest = {
343
- id: 'bad-priority-neg',
344
- name: 'Bad Priority Negative',
345
- version: '1.0.0',
346
- type: 'widget',
347
- priority: -1,
348
- };
349
-
350
- await createPlugin('bad-priority-neg', invalidManifest);
351
- const id = await loader.loadPlugin(join(tempDir, 'bad-priority-neg'));
352
-
353
- expect(id).toBeNull();
354
- });
355
-
356
- test('should reject priority out of range (too high)', async () => {
357
- const invalidManifest = {
358
- id: 'bad-priority-high',
359
- name: 'Bad Priority High',
360
- version: '1.0.0',
361
- type: 'widget',
362
- priority: 2000,
363
- };
364
-
365
- await createPlugin('bad-priority-high', invalidManifest);
366
- const id = await loader.loadPlugin(join(tempDir, 'bad-priority-high'));
367
-
368
- expect(id).toBeNull();
369
- });
370
- });
371
-
372
- describe('Invalid Manifests - Array Validation', () => {
373
- test('should reject duplicate permissions', async () => {
374
- const invalidManifest = {
375
- id: 'dup-perms',
376
- name: 'Duplicate Permissions',
377
- version: '1.0.0',
378
- type: 'widget',
379
- permissions: ['network', 'network', 'system'],
380
- };
381
-
382
- await createPlugin('dup-perms', invalidManifest);
383
- const id = await loader.loadPlugin(join(tempDir, 'dup-perms'));
384
-
385
- expect(id).toBeNull();
386
- });
387
-
388
- test('should reject invalid permission value', async () => {
389
- const invalidManifest = {
390
- id: 'bad-perm',
391
- name: 'Bad Permission',
392
- version: '1.0.0',
393
- type: 'widget',
394
- permissions: ['network', 'invalid-perm'],
395
- };
396
-
397
- await createPlugin('bad-perm', invalidManifest);
398
- const id = await loader.loadPlugin(join(tempDir, 'bad-perm'));
399
-
400
- expect(id).toBeNull();
401
- });
402
-
403
- test('should reject duplicate dependencies', async () => {
404
- const invalidManifest = {
405
- id: 'dup-deps',
406
- name: 'Duplicate Dependencies',
407
- version: '1.0.0',
408
- type: 'widget',
409
- dependencies: ['base-widget', 'base-widget'],
410
- };
411
-
412
- await createPlugin('dup-deps', invalidManifest);
413
- const id = await loader.loadPlugin(join(tempDir, 'dup-deps'));
414
-
415
- expect(id).toBeNull();
416
- });
417
-
418
- test('should reject invalid dependency id format', async () => {
419
- const invalidManifest = {
420
- id: 'bad-dep-id',
421
- name: 'Bad Dependency ID',
422
- version: '1.0.0',
423
- type: 'widget',
424
- dependencies: ['_invalid-dep'],
425
- };
426
-
427
- await createPlugin('bad-dep-id', invalidManifest);
428
- const id = await loader.loadPlugin(join(tempDir, 'bad-dep-id'));
429
-
430
- expect(id).toBeNull();
431
- });
432
- });
433
-
434
- describe('Validation with fallbackOnError', () => {
435
- test('should throw on invalid manifest when fallbackOnError is false', async () => {
436
- const invalidManifest = {
437
- id: 'no-fallback',
438
- name: 'No Fallback',
439
- version: 'invalid',
440
- type: 'widget',
441
- };
442
-
443
- await createPlugin('no-fallback', invalidManifest);
444
-
445
- await expect(
446
- loader.loadPlugin(join(tempDir, 'no-fallback'), { fallbackOnError: false })
447
- ).rejects.toThrow('Invalid plugin manifest');
448
- });
449
-
450
- test('should return null on invalid manifest when fallbackOnError is true', async () => {
451
- const invalidManifest = {
452
- id: 'with-fallback',
453
- name: 'With Fallback',
454
- version: 'invalid',
455
- type: 'widget',
456
- };
457
-
458
- await createPlugin('with-fallback', invalidManifest);
459
- const id = await loader.loadPlugin(join(tempDir, 'with-fallback'), { fallbackOnError: true });
460
-
461
- expect(id).toBeNull();
462
- });
463
- });
464
-
465
- describe('Discovery with Invalid Plugins', () => {
466
- test('should skip invalid plugins during discovery', async () => {
467
- const validManifest = {
468
- id: 'valid-plugin',
469
- name: 'Valid Plugin',
470
- version: '1.0.0',
471
- type: 'widget',
472
- };
473
-
474
- const invalidManifest = {
475
- id: 'invalid-plugin',
476
- name: 'Invalid Plugin',
477
- version: 'bad-version',
478
- type: 'widget',
479
- };
480
-
481
- await createPlugin('valid-plugin', validManifest);
482
- await createPlugin('invalid-plugin', invalidManifest);
483
-
484
- const discovered = await loader.discoverPlugins();
485
-
486
- expect(discovered.some(p => p.id === 'valid-plugin')).toBe(true);
487
- expect(discovered.some(p => p.id === 'invalid-plugin')).toBe(false);
488
- });
489
-
490
- test('should discover only valid plugins when mixed with invalid ones', async () => {
491
- const manifests = [
492
- { id: 'plugin-1', name: 'Plugin 1', version: '1.0.0', type: 'widget' },
493
- { id: 'plugin-2', name: 'Plugin 2', version: 'invalid', type: 'widget' },
494
- { id: 'plugin-3', name: 'Plugin 3', version: '2.0.0', type: 'widget' },
495
- { id: 'plugin-4', name: 'Plugin 4', type: 'widget' }, // missing version
496
- ];
497
-
498
- for (const manifest of manifests) {
499
- await createPlugin(manifest.id || 'unknown', manifest);
500
- }
501
-
502
- const discovered = await loader.discoverPlugins();
503
- const ids = discovered.map(p => p.id);
504
-
505
- expect(ids).toContain('plugin-1');
506
- expect(ids).not.toContain('plugin-2');
507
- expect(ids).toContain('plugin-3');
508
- expect(ids).not.toContain('plugin-4');
509
- });
510
- });
511
-
512
- describe('Registration with Validation', () => {
513
- test('should register plugin with valid manifest', async () => {
514
- const validManifest = {
515
- id: 'reg-test',
516
- name: 'Registration Test',
517
- version: '1.0.0',
518
- type: 'widget',
519
- };
520
-
521
- await createPlugin('reg-test', validManifest);
522
- const id = await loader.registerPlugin(join(tempDir, 'reg-test'));
523
-
524
- expect(id).toBe('reg-test');
525
- expect(loader.widgetRegistry.has('reg-test')).toBe(true);
526
- });
527
-
528
- test('should skip registration for invalid manifest', async () => {
529
- const invalidManifest = {
530
- id: 'reg-invalid',
531
- name: 'Registration Invalid',
532
- version: 'bad',
533
- type: 'widget',
534
- };
535
-
536
- await createPlugin('reg-invalid', invalidManifest);
537
- const id = await loader.registerPlugin(join(tempDir, 'reg-invalid'));
538
-
539
- expect(id).toBeNull();
540
- expect(loader.widgetRegistry.has('reg-invalid')).toBe(false);
541
- });
542
- });
543
-
544
- describe('Edge Cases', () => {
545
- test('should handle empty manifest object', async () => {
546
- await createPlugin('empty-manifest', {});
547
- const id = await loader.loadPlugin(join(tempDir, 'empty-manifest'));
548
-
549
- expect(id).toBeNull();
550
- });
551
-
552
- test('should handle null manifest values', async () => {
553
- const invalidManifest = {
554
- id: null,
555
- name: null,
556
- version: null,
557
- type: null,
558
- };
559
-
560
- await createPlugin('null-values', invalidManifest);
561
- const id = await loader.loadPlugin(join(tempDir, 'null-values'));
562
-
563
- expect(id).toBeNull();
564
- });
565
-
566
- test('should handle very long id', async () => {
567
- const invalidManifest = {
568
- id: 'a'.repeat(100),
569
- name: 'Long ID',
570
- version: '1.0.0',
571
- type: 'widget',
572
- };
573
-
574
- await createPlugin('long-id', invalidManifest);
575
- const id = await loader.loadPlugin(join(tempDir, 'long-id'));
576
-
577
- expect(id).toBeNull();
578
- });
579
-
580
- test('should handle empty string name', async () => {
581
- const invalidManifest = {
582
- id: 'empty-name',
583
- name: '',
584
- version: '1.0.0',
585
- type: 'widget',
586
- };
587
-
588
- await createPlugin('empty-name', invalidManifest);
589
- const id = await loader.loadPlugin(join(tempDir, 'empty-name'));
590
-
591
- expect(id).toBeNull();
592
- });
593
-
594
- test('should validate __version field', async () => {
595
- const invalidManifest = {
596
- id: 'bad-config-version',
597
- name: 'Bad Config Version',
598
- version: '1.0.0',
599
- type: 'widget',
600
- __version: 0, // below minimum
601
- };
602
-
603
- await createPlugin('bad-config-version', invalidManifest);
604
- const id = await loader.loadPlugin(join(tempDir, 'bad-config-version'));
605
-
606
- expect(id).toBeNull();
607
- });
608
- });
609
-
610
- describe('Description and Author Field Validation', () => {
611
- test('should accept manifest with long description', async () => {
612
- const manifest = {
613
- id: 'long-desc',
614
- name: 'Long Description',
615
- version: '1.0.0',
616
- type: 'widget',
617
- description: 'A'.repeat(500), // at max length
618
- };
619
-
620
- await createPlugin('long-desc', manifest);
621
- const id = await loader.loadPlugin(join(tempDir, 'long-desc'));
622
-
623
- expect(id).toBe('long-desc');
624
- });
625
-
626
- test('should reject description exceeding max length', async () => {
627
- const invalidManifest = {
628
- id: 'too-long-desc',
629
- name: 'Too Long Description',
630
- version: '1.0.0',
631
- type: 'widget',
632
- description: 'A'.repeat(501), // exceeds max
633
- };
634
-
635
- await createPlugin('too-long-desc', invalidManifest);
636
- const id = await loader.loadPlugin(join(tempDir, 'too-long-desc'));
637
-
638
- expect(id).toBeNull();
639
- });
640
-
641
- test('should accept manifest with long author', async () => {
642
- const manifest = {
643
- id: 'long-author',
644
- name: 'Long Author',
645
- version: '1.0.0',
646
- type: 'widget',
647
- author: 'A'.repeat(200), // at max length
648
- };
649
-
650
- await createPlugin('long-author', manifest);
651
- const id = await loader.loadPlugin(join(tempDir, 'long-author'));
652
-
653
- expect(id).toBe('long-author');
654
- });
655
-
656
- test('should reject author exceeding max length', async () => {
657
- const invalidManifest = {
658
- id: 'too-long-author',
659
- name: 'Too Long Author',
660
- version: '1.0.0',
661
- type: 'widget',
662
- author: 'A'.repeat(201), // exceeds max
663
- };
664
-
665
- await createPlugin('too-long-author', invalidManifest);
666
- const id = await loader.loadPlugin(join(tempDir, 'too-long-author'));
667
-
668
- expect(id).toBeNull();
669
- });
670
- });
671
- });