claw-dashboard 2.1.1 → 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 (89) hide show
  1. package/CHANGELOG.md +29 -1
  2. package/docs/API.md +1 -2
  3. package/index.js +31 -41
  4. package/package.json +22 -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/.c8rc.json +0 -38
  14. package/.dockerignore +0 -68
  15. package/.github/dependabot.yml +0 -45
  16. package/.github/pull_request_template.md +0 -39
  17. package/.github/workflows/ci.yml +0 -147
  18. package/.github/workflows/release.yml +0 -40
  19. package/.github/workflows/security.yml +0 -84
  20. package/.husky/pre-commit +0 -1
  21. package/.planning/codebase/ARCHITECTURE.md +0 -206
  22. package/.planning/codebase/INTEGRATIONS.md +0 -150
  23. package/.planning/codebase/STACK.md +0 -122
  24. package/.planning/codebase/STRUCTURE.md +0 -201
  25. package/CONTRIBUTING.md +0 -378
  26. package/Dockerfile +0 -54
  27. package/FEATURES.md +0 -307
  28. package/TODO.md +0 -28
  29. package/ai.openclaw.dashboard.plist +0 -35
  30. package/build-cjs.js +0 -127
  31. package/cjs-shim.js +0 -13
  32. package/dist/clawdash +0 -1729
  33. package/dist/clawdash.meta.json +0 -2236
  34. package/dist/widgets.cjs +0 -6511
  35. package/docker-compose.yml +0 -67
  36. package/esbuild.config.js +0 -158
  37. package/eslint.config.js +0 -56
  38. package/examples/plugins/README.md +0 -122
  39. package/examples/plugins/api-status/index.js +0 -294
  40. package/examples/plugins/api-status/plugin.json +0 -19
  41. package/examples/plugins/hello-world/index.js +0 -104
  42. package/examples/plugins/hello-world/plugin.json +0 -15
  43. package/examples/plugins/system-metrics-chart/index.js +0 -339
  44. package/examples/plugins/system-metrics-chart/plugin.json +0 -18
  45. package/examples/plugins/weather-widget/index.js +0 -136
  46. package/examples/plugins/weather-widget/plugin.json +0 -19
  47. package/index.cjs +0 -23005
  48. package/jest.config.js +0 -11
  49. package/scripts/release.js +0 -595
  50. package/src/database.js +0 -734
  51. package/tests/alerts.test.js +0 -437
  52. package/tests/auto-save.test.js +0 -529
  53. package/tests/cache.test.js +0 -317
  54. package/tests/cli.test.js +0 -351
  55. package/tests/command-palette.test.js +0 -320
  56. package/tests/config-processor.test.js +0 -452
  57. package/tests/config-validator.test.js +0 -710
  58. package/tests/config-watcher.test.js +0 -594
  59. package/tests/config.test.js +0 -755
  60. package/tests/database.test.js +0 -438
  61. package/tests/errors.test.js +0 -189
  62. package/tests/example-plugins.test.js +0 -624
  63. package/tests/integration.test.js +0 -1321
  64. package/tests/loading-states.test.js +0 -300
  65. package/tests/manifest-validation-on-load.test.js +0 -671
  66. package/tests/performance-monitor.test.js +0 -190
  67. package/tests/plugin-api-rate-limit.test.js +0 -302
  68. package/tests/plugin-errors.test.js +0 -311
  69. package/tests/plugin-lifecycle-e2e.test.js +0 -1036
  70. package/tests/plugin-reload.test.js +0 -764
  71. package/tests/rate-limiter.test.js +0 -539
  72. package/tests/removed-dependencies.test.js +0 -74
  73. package/tests/retry.test.js +0 -308
  74. package/tests/security.test.js +0 -411
  75. package/tests/settings-modal.test.js +0 -226
  76. package/tests/theme-selector.test.js +0 -57
  77. package/tests/utils.js +0 -242
  78. package/tests/utils.test.js +0 -317
  79. package/tests/validate-plugin-cli.test.js +0 -359
  80. package/tests/validation.test.js +0 -837
  81. package/tests/web-server.test.js +0 -646
  82. package/tests/widget-arrange-mode.test.js +0 -183
  83. package/tests/widget-config-hot-reload.test.js +0 -465
  84. package/tests/widget-dependency.test.js +0 -591
  85. package/tests/widget-error-boundary.test.js +0 -749
  86. package/tests/widget-error-isolation.test.js +0 -469
  87. package/tests/widget-integration.test.js +0 -1147
  88. package/tests/widget-refresh-intervals.test.js +0 -284
  89. 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
- });