|
8 | 8 |
|
9 | 9 | /** |
10 | 10 | * P1 SpecBridge Protocol Alignment Tests |
11 | | - * Tests for the enhanced bridge transformations: ListView, FormView, Dashboard, Page |
| 11 | + * Tests for the enhanced bridge transformations: ListView, FormView |
12 | 12 | */ |
13 | 13 | import { describe, it, expect } from 'vitest'; |
14 | 14 | import { SpecBridge } from '../SpecBridge'; |
@@ -336,322 +336,6 @@ describe('P1 SpecBridge Protocol Alignment', () => { |
336 | 336 | }); |
337 | 337 | }); |
338 | 338 |
|
339 | | - // ======================================================================== |
340 | | - // P1.3 Dashboard Bridge Enhancements |
341 | | - // ======================================================================== |
342 | | - describe('P1.3 Dashboard bridge enhancements', () => { |
343 | | - it('should map widget data binding properties', () => { |
344 | | - const bridge = new SpecBridge(); |
345 | | - const node = bridge.transformDashboard({ |
346 | | - name: 'sales', |
347 | | - widgets: [ |
348 | | - { |
349 | | - title: 'Revenue', |
350 | | - type: 'bar-chart', |
351 | | - object: 'Opportunity', |
352 | | - filter: [['stage', '=', 'Closed Won']], |
353 | | - categoryField: 'region', |
354 | | - valueField: 'amount', |
355 | | - aggregate: 'sum', |
356 | | - }, |
357 | | - ], |
358 | | - }); |
359 | | - |
360 | | - const widgetGrid = (node.body as any[]).find((b: any) => b.type === 'widget-grid'); |
361 | | - const widgets = widgetGrid.body as any[]; |
362 | | - expect(widgets[0].object).toBe('Opportunity'); |
363 | | - expect(widgets[0].categoryField).toBe('region'); |
364 | | - expect(widgets[0].valueField).toBe('amount'); |
365 | | - expect(widgets[0].aggregate).toBe('sum'); |
366 | | - }); |
367 | | - |
368 | | - it('should map widget color variants', () => { |
369 | | - const bridge = new SpecBridge(); |
370 | | - const node = bridge.transformDashboard({ |
371 | | - name: 'colored', |
372 | | - widgets: [ |
373 | | - { title: 'Success', type: 'metric', colorVariant: 'success' }, |
374 | | - { title: 'Danger', type: 'metric', colorVariant: 'danger' }, |
375 | | - ], |
376 | | - }); |
377 | | - |
378 | | - const widgetGrid = (node.body as any[]).find((b: any) => b.type === 'widget-grid'); |
379 | | - const widgets = widgetGrid.body as any[]; |
380 | | - expect(widgets[0].colorVariant).toBe('success'); |
381 | | - expect(widgets[1].colorVariant).toBe('danger'); |
382 | | - }); |
383 | | - |
384 | | - it('should map widget measures (pivot/matrix)', () => { |
385 | | - const bridge = new SpecBridge(); |
386 | | - const node = bridge.transformDashboard({ |
387 | | - name: 'pivot', |
388 | | - widgets: [ |
389 | | - { |
390 | | - title: 'Matrix', |
391 | | - type: 'pivot', |
392 | | - measures: [ |
393 | | - { valueField: 'amount', aggregate: 'sum', label: 'Total', format: '$0,0' }, |
394 | | - ], |
395 | | - }, |
396 | | - ], |
397 | | - }); |
398 | | - |
399 | | - const widgetGrid = (node.body as any[]).find((b: any) => b.type === 'widget-grid'); |
400 | | - const widgets = widgetGrid.body as any[]; |
401 | | - expect(widgets[0].measures).toHaveLength(1); |
402 | | - expect(widgets[0].measures[0].label).toBe('Total'); |
403 | | - }); |
404 | | - |
405 | | - it('should map globalFilters with optionsFrom', () => { |
406 | | - const bridge = new SpecBridge(); |
407 | | - const node = bridge.transformDashboard({ |
408 | | - name: 'filtered', |
409 | | - widgets: [], |
410 | | - globalFilters: [ |
411 | | - { |
412 | | - field: 'region', |
413 | | - label: 'Region', |
414 | | - type: 'select', |
415 | | - optionsFrom: { |
416 | | - object: 'Region', |
417 | | - valueField: 'id', |
418 | | - labelField: 'name', |
419 | | - }, |
420 | | - targetWidgets: ['widget-0'], |
421 | | - }, |
422 | | - ], |
423 | | - }); |
424 | | - |
425 | | - const filterBar = (node.body as any[]).find((b: any) => b.type === 'filter-bar'); |
426 | | - expect(filterBar).toBeDefined(); |
427 | | - expect(filterBar.filters[0].field).toBe('region'); |
428 | | - expect(filterBar.filters[0].optionsFrom.object).toBe('Region'); |
429 | | - expect(filterBar.filters[0].targetWidgets).toEqual(['widget-0']); |
430 | | - }); |
431 | | - |
432 | | - it('should map dateRange filter', () => { |
433 | | - const bridge = new SpecBridge(); |
434 | | - const node = bridge.transformDashboard({ |
435 | | - name: 'dated', |
436 | | - widgets: [], |
437 | | - dateRange: { |
438 | | - field: 'created_at', |
439 | | - defaultRange: 'last_30_days', |
440 | | - allowCustomRange: true, |
441 | | - }, |
442 | | - }); |
443 | | - |
444 | | - const dateFilter = (node.body as any[]).find((b: any) => b.type === 'date-range-filter'); |
445 | | - expect(dateFilter).toBeDefined(); |
446 | | - expect(dateFilter.field).toBe('created_at'); |
447 | | - expect(dateFilter.defaultRange).toBe('last_30_days'); |
448 | | - expect(dateFilter.allowCustomRange).toBe(true); |
449 | | - }); |
450 | | - |
451 | | - it('should map DashboardHeader with actions', () => { |
452 | | - const bridge = new SpecBridge(); |
453 | | - const node = bridge.transformDashboard({ |
454 | | - name: 'with_header', |
455 | | - widgets: [], |
456 | | - header: { |
457 | | - showTitle: true, |
458 | | - actions: [ |
459 | | - { label: 'Refresh', actionType: 'refresh', icon: 'RefreshCw' }, |
460 | | - ], |
461 | | - }, |
462 | | - }); |
463 | | - |
464 | | - const header = (node.body as any[]).find((b: any) => b.type === 'dashboard-header'); |
465 | | - expect(header).toBeDefined(); |
466 | | - expect(header.showTitle).toBe(true); |
467 | | - expect(header.actions).toHaveLength(1); |
468 | | - }); |
469 | | - |
470 | | - it('should pass through dashboard description and refreshInterval', () => { |
471 | | - const bridge = new SpecBridge(); |
472 | | - const node = bridge.transformDashboard({ |
473 | | - name: 'auto_refresh', |
474 | | - label: 'Live Dashboard', |
475 | | - description: 'Real-time sales data', |
476 | | - refreshInterval: 30, |
477 | | - widgets: [], |
478 | | - }); |
479 | | - |
480 | | - expect(node.description).toBe('Real-time sales data'); |
481 | | - expect(node.refreshInterval).toBe(30); |
482 | | - }); |
483 | | - |
484 | | - it('should pass through dashboard aria', () => { |
485 | | - const bridge = new SpecBridge(); |
486 | | - const node = bridge.transformDashboard({ |
487 | | - name: 'accessible', |
488 | | - widgets: [], |
489 | | - aria: { ariaLabel: 'Sales Dashboard', role: 'region' }, |
490 | | - }); |
491 | | - |
492 | | - expect(node.aria).toBeDefined(); |
493 | | - expect(node.aria.ariaLabel).toBe('Sales Dashboard'); |
494 | | - }); |
495 | | - |
496 | | - it('should map widget action properties', () => { |
497 | | - const bridge = new SpecBridge(); |
498 | | - const node = bridge.transformDashboard({ |
499 | | - name: 'clickable', |
500 | | - widgets: [ |
501 | | - { |
502 | | - title: 'Open Deals', |
503 | | - type: 'metric', |
504 | | - actionUrl: '/opportunities?status=open', |
505 | | - actionType: 'navigate', |
506 | | - actionIcon: 'ArrowRight', |
507 | | - }, |
508 | | - ], |
509 | | - }); |
510 | | - |
511 | | - const widgetGrid = (node.body as any[]).find((b: any) => b.type === 'widget-grid'); |
512 | | - const widgets = widgetGrid.body as any[]; |
513 | | - expect(widgets[0].actionUrl).toBe('/opportunities?status=open'); |
514 | | - expect(widgets[0].actionType).toBe('navigate'); |
515 | | - expect(widgets[0].actionIcon).toBe('ArrowRight'); |
516 | | - }); |
517 | | - }); |
518 | | - |
519 | | - // ======================================================================== |
520 | | - // P1.4 Page Bridge Enhancements |
521 | | - // ======================================================================== |
522 | | - describe('P1.4 Page bridge enhancements', () => { |
523 | | - it('should map expanded page types', () => { |
524 | | - const bridge = new SpecBridge(); |
525 | | - const pageTypes = [ |
526 | | - 'record', 'home', 'app', 'utility', |
527 | | - 'grid', 'list', 'gallery', 'kanban', 'calendar', 'timeline', |
528 | | - ]; |
529 | | - |
530 | | - pageTypes.forEach((pageType) => { |
531 | | - const node = bridge.transformPage({ |
532 | | - name: `${pageType}_page`, |
533 | | - type: pageType, |
534 | | - }); |
535 | | - expect(node.pageType).toBe(pageType); |
536 | | - }); |
537 | | - }); |
538 | | - |
539 | | - // (Removed "should map blank page layout" — the bridge no longer maps |
540 | | - // blankLayout; the `blank` page type has no renderer, dropped from |
541 | | - // @objectstack/spec (framework#2265).) |
542 | | - |
543 | | - it('should map page variables with source', () => { |
544 | | - const bridge = new SpecBridge(); |
545 | | - const node = bridge.transformPage({ |
546 | | - name: 'record_page', |
547 | | - type: 'record', |
548 | | - variables: [ |
549 | | - { name: 'recordId', type: 'record_id', source: 'url_param' }, |
550 | | - { name: 'tab', type: 'string', defaultValue: 'details' }, |
551 | | - ], |
552 | | - }); |
553 | | - |
554 | | - expect(node.variables).toHaveLength(2); |
555 | | - expect(node.variables[0].name).toBe('recordId'); |
556 | | - expect(node.variables[0].type).toBe('record_id'); |
557 | | - expect(node.variables[0].source).toBe('url_param'); |
558 | | - }); |
559 | | - |
560 | | - it('should map event handlers on page components', () => { |
561 | | - const bridge = new SpecBridge(); |
562 | | - const node = bridge.transformPage({ |
563 | | - name: 'interactive', |
564 | | - regions: [ |
565 | | - { |
566 | | - name: 'main', |
567 | | - components: [ |
568 | | - { |
569 | | - type: 'element:button', |
570 | | - id: 'save-btn', |
571 | | - label: 'Save', |
572 | | - events: { |
573 | | - onClick: '${actions.save()}', |
574 | | - onHover: '${actions.highlight()}', |
575 | | - }, |
576 | | - }, |
577 | | - ], |
578 | | - }, |
579 | | - ], |
580 | | - }); |
581 | | - |
582 | | - const region = (node.body as any[])[0]; |
583 | | - const component = region.body[0]; |
584 | | - expect(component.events).toBeDefined(); |
585 | | - expect(component.events.onClick).toBe('${actions.save()}'); |
586 | | - }); |
587 | | - |
588 | | - it('should map responsive config on page components', () => { |
589 | | - const bridge = new SpecBridge(); |
590 | | - const node = bridge.transformPage({ |
591 | | - name: 'responsive', |
592 | | - regions: [ |
593 | | - { |
594 | | - name: 'main', |
595 | | - components: [ |
596 | | - { |
597 | | - type: 'element:text', |
598 | | - id: 'heading', |
599 | | - responsive: { |
600 | | - sm: { hidden: true }, |
601 | | - md: { hidden: false }, |
602 | | - }, |
603 | | - }, |
604 | | - ], |
605 | | - }, |
606 | | - ], |
607 | | - }); |
608 | | - |
609 | | - const region = (node.body as any[])[0]; |
610 | | - const component = region.body[0]; |
611 | | - expect(component.responsive).toBeDefined(); |
612 | | - expect(component.responsive.sm.hidden).toBe(true); |
613 | | - }); |
614 | | - |
615 | | - it('should pass through page description and icon', () => { |
616 | | - const bridge = new SpecBridge(); |
617 | | - const node = bridge.transformPage({ |
618 | | - name: 'detailed', |
619 | | - label: 'Home', |
620 | | - description: 'Main landing page', |
621 | | - icon: 'Home', |
622 | | - }); |
623 | | - |
624 | | - expect(node.description).toBe('Main landing page'); |
625 | | - expect(node.icon).toBe('Home'); |
626 | | - }); |
627 | | - |
628 | | - it('should pass through page aria', () => { |
629 | | - const bridge = new SpecBridge(); |
630 | | - const node = bridge.transformPage({ |
631 | | - name: 'accessible', |
632 | | - aria: { ariaLabel: 'Home Page', role: 'main' }, |
633 | | - }); |
634 | | - |
635 | | - expect(node.aria).toBeDefined(); |
636 | | - expect(node.aria.ariaLabel).toBe('Home Page'); |
637 | | - }); |
638 | | - |
639 | | - it('should map region width', () => { |
640 | | - const bridge = new SpecBridge(); |
641 | | - const node = bridge.transformPage({ |
642 | | - name: 'sidebar_page', |
643 | | - regions: [ |
644 | | - { name: 'sidebar', width: 'small', components: [] }, |
645 | | - { name: 'main', width: 'large', components: [] }, |
646 | | - ], |
647 | | - }); |
648 | | - |
649 | | - const regions = node.body as any[]; |
650 | | - expect(regions[0].width).toBe('small'); |
651 | | - expect(regions[1].width).toBe('large'); |
652 | | - }); |
653 | | - }); |
654 | | - |
655 | 339 | // ======================================================================== |
656 | 340 | // P2 Sharing / ExportOptions / Pagination Protocol Alignment |
657 | 341 | // ======================================================================== |
|
0 commit comments