@@ -43,6 +43,7 @@ import {
4343 toSortItems ,
4444 SPEC_TO_BUILDER_OP ,
4545 BUILDER_TO_SPEC_OP ,
46+ ROW_HEIGHT_OPTIONS ,
4647} from '../utils/view-config-utils' ;
4748
4849import { buildViewConfigSchema } from '../utils/view-config-schema' ;
@@ -370,79 +371,116 @@ describe('buildViewConfigSchema', () => {
370371 // ── Page Config Section ─────────────────────────────────────────────
371372
372373 describe ( 'pageConfig section' , ( ) => {
373- it ( 'contains expected field keys' , ( ) => {
374+ it ( 'contains expected field keys in spec order' , ( ) => {
375+ const schema = buildSchema ( ) ;
376+ const section = schema . sections . find ( s => s . key === 'pageConfig' ) ! ;
377+ const fieldKeys = section . fields . map ( f => f . key ) ;
378+ // Spec order: label, type, showSearch, showSort, showFilters, showHideFields, showGroup, showColor, showDensity,
379+ // allowExport(_export), navigation, selection, addRecord, showRecordCount, allowPrinting
380+ // description is UI extension (after label)
381+ expect ( fieldKeys ) . toEqual ( [
382+ 'label' , 'description' , 'type' ,
383+ 'showSearch' , 'showSort' , 'showFilters' , 'showHideFields' , 'showGroup' , 'showColor' , 'showDensity' ,
384+ '_export' ,
385+ '_navigationMode' , '_navigationWidth' , '_navigationOpenNewTab' ,
386+ '_selectionType' ,
387+ '_addRecord' ,
388+ 'showRecordCount' , 'allowPrinting' ,
389+ ] ) ;
390+ } ) ;
391+
392+ it ( 'showSort comes before showFilters per spec' , ( ) => {
393+ const schema = buildSchema ( ) ;
394+ const section = schema . sections . find ( s => s . key === 'pageConfig' ) ! ;
395+ const fieldKeys = section . fields . map ( f => f . key ) ;
396+ expect ( fieldKeys . indexOf ( 'showSort' ) ) . toBeLessThan ( fieldKeys . indexOf ( 'showFilters' ) ) ;
397+ } ) ;
398+
399+ it ( '_export comes before _navigationMode per spec' , ( ) => {
374400 const schema = buildSchema ( ) ;
375401 const section = schema . sections . find ( s => s . key === 'pageConfig' ) ! ;
376402 const fieldKeys = section . fields . map ( f => f . key ) ;
377- expect ( fieldKeys ) . toContain ( 'label' ) ;
378- expect ( fieldKeys ) . toContain ( 'description' ) ;
379- expect ( fieldKeys ) . toContain ( 'type' ) ;
380- expect ( fieldKeys ) . toContain ( 'showSearch' ) ;
381- expect ( fieldKeys ) . toContain ( 'showFilters' ) ;
382- expect ( fieldKeys ) . toContain ( 'showSort' ) ;
383- expect ( fieldKeys ) . toContain ( '_navigationMode' ) ;
384- expect ( fieldKeys ) . toContain ( '_selectionType' ) ;
385- expect ( fieldKeys ) . toContain ( '_addRecord' ) ;
386- expect ( fieldKeys ) . toContain ( '_export' ) ;
403+ expect ( fieldKeys . indexOf ( '_export' ) ) . toBeLessThan ( fieldKeys . indexOf ( '_navigationMode' ) ) ;
387404 } ) ;
388405 } ) ;
389406
390407 // ── Data Section ────────────────────────────────────────────────────
391408
392409 describe ( 'data section' , ( ) => {
393- it ( 'contains expected field keys' , ( ) => {
410+ it ( 'contains expected field keys in spec order' , ( ) => {
411+ const schema = buildSchema ( ) ;
412+ const section = schema . sections . find ( s => s . key === 'data' ) ! ;
413+ const fieldKeys = section . fields . map ( f => f . key ) ;
414+ // Spec order: columns, filter, sort, prefixField, pagination, searchableFields, filterableFields,
415+ // hiddenFields, quickFilters, virtualScroll
416+ // _source is UI extension (first), _groupBy is UI extension (after prefixField), _typeOptions is UI extension (last)
417+ expect ( fieldKeys ) . toEqual ( [
418+ '_source' ,
419+ '_columns' , '_filterBy' , '_sortBy' ,
420+ 'prefixField' , '_groupBy' ,
421+ '_pageSize' , '_pageSizeOptions' ,
422+ '_searchableFields' , '_filterableFields' , '_hiddenFields' ,
423+ '_quickFilters' ,
424+ 'virtualScroll' ,
425+ '_typeOptions' ,
426+ ] ) ;
427+ } ) ;
428+
429+ it ( '_columns comes before _filterBy and _sortBy per spec' , ( ) => {
394430 const schema = buildSchema ( ) ;
395431 const section = schema . sections . find ( s => s . key === 'data' ) ! ;
396432 const fieldKeys = section . fields . map ( f => f . key ) ;
397- expect ( fieldKeys ) . toContain ( '_source' ) ;
398- expect ( fieldKeys ) . toContain ( '_sortBy' ) ;
399- expect ( fieldKeys ) . toContain ( '_groupBy' ) ;
400- expect ( fieldKeys ) . toContain ( 'prefixField' ) ;
401- expect ( fieldKeys ) . toContain ( '_columns' ) ;
402- expect ( fieldKeys ) . toContain ( '_filterBy' ) ;
403- expect ( fieldKeys ) . toContain ( '_pageSize' ) ;
404- expect ( fieldKeys ) . toContain ( '_pageSizeOptions' ) ;
405- expect ( fieldKeys ) . toContain ( '_searchableFields' ) ;
406- expect ( fieldKeys ) . toContain ( '_filterableFields' ) ;
407- expect ( fieldKeys ) . toContain ( '_hiddenFields' ) ;
408- expect ( fieldKeys ) . toContain ( '_quickFilters' ) ;
409- expect ( fieldKeys ) . toContain ( 'virtualScroll' ) ;
410- expect ( fieldKeys ) . toContain ( '_typeOptions' ) ;
433+ expect ( fieldKeys . indexOf ( '_columns' ) ) . toBeLessThan ( fieldKeys . indexOf ( '_filterBy' ) ) ;
434+ expect ( fieldKeys . indexOf ( '_filterBy' ) ) . toBeLessThan ( fieldKeys . indexOf ( '_sortBy' ) ) ;
411435 } ) ;
412436 } ) ;
413437
414438 // ── Appearance Section ──────────────────────────────────────────────
415439
416440 describe ( 'appearance section' , ( ) => {
417- it ( 'contains expected field keys' , ( ) => {
441+ it ( 'contains expected field keys in spec order' , ( ) => {
442+ const schema = buildSchema ( ) ;
443+ const section = schema . sections . find ( s => s . key === 'appearance' ) ! ;
444+ const fieldKeys = section . fields . map ( f => f . key ) ;
445+ // Spec order: striped, bordered, color, wrapHeaders, collapseAllByDefault, fieldTextColor,
446+ // showDescription, resizable, densityMode, rowHeight, conditionalFormatting, emptyState
447+ expect ( fieldKeys ) . toEqual ( [
448+ 'striped' , 'bordered' , 'color' ,
449+ 'wrapHeaders' , 'collapseAllByDefault' ,
450+ 'fieldTextColor' , 'showDescription' ,
451+ 'resizable' , 'densityMode' , 'rowHeight' ,
452+ '_conditionalFormatting' , '_emptyState' ,
453+ ] ) ;
454+ } ) ;
455+
456+ it ( 'striped and bordered come before color per spec' , ( ) => {
418457 const schema = buildSchema ( ) ;
419458 const section = schema . sections . find ( s => s . key === 'appearance' ) ! ;
420459 const fieldKeys = section . fields . map ( f => f . key ) ;
421- expect ( fieldKeys ) . toContain ( 'color' ) ;
422- expect ( fieldKeys ) . toContain ( 'fieldTextColor' ) ;
423- expect ( fieldKeys ) . toContain ( 'rowHeight' ) ;
424- expect ( fieldKeys ) . toContain ( 'wrapHeaders' ) ;
425- expect ( fieldKeys ) . toContain ( 'showDescription' ) ;
426- expect ( fieldKeys ) . toContain ( 'striped' ) ;
427- expect ( fieldKeys ) . toContain ( 'bordered' ) ;
428- expect ( fieldKeys ) . toContain ( 'resizable' ) ;
429- expect ( fieldKeys ) . toContain ( 'densityMode' ) ;
430- expect ( fieldKeys ) . toContain ( '_conditionalFormatting' ) ;
431- expect ( fieldKeys ) . toContain ( '_emptyState' ) ;
460+ expect ( fieldKeys . indexOf ( 'striped' ) ) . toBeLessThan ( fieldKeys . indexOf ( 'color' ) ) ;
461+ expect ( fieldKeys . indexOf ( 'bordered' ) ) . toBeLessThan ( fieldKeys . indexOf ( 'color' ) ) ;
432462 } ) ;
433463 } ) ;
434464
435465 // ── User Actions Section ────────────────────────────────────────────
436466
437467 describe ( 'userActions section' , ( ) => {
438- it ( 'contains expected field keys' , ( ) => {
468+ it ( 'contains expected field keys in spec order' , ( ) => {
469+ const schema = buildSchema ( ) ;
470+ const section = schema . sections . find ( s => s . key === 'userActions' ) ! ;
471+ const fieldKeys = section . fields . map ( f => f . key ) ;
472+ // Spec order: inlineEdit, clickIntoRecordDetails, addDeleteRecordsInline, rowActions, bulkActions
473+ expect ( fieldKeys ) . toEqual ( [
474+ 'inlineEdit' , 'clickIntoRecordDetails' , 'addDeleteRecordsInline' ,
475+ '_rowActions' , '_bulkActions' ,
476+ ] ) ;
477+ } ) ;
478+
479+ it ( 'inlineEdit comes before clickIntoRecordDetails per spec' , ( ) => {
439480 const schema = buildSchema ( ) ;
440481 const section = schema . sections . find ( s => s . key === 'userActions' ) ! ;
441482 const fieldKeys = section . fields . map ( f => f . key ) ;
442- expect ( fieldKeys ) . toContain ( 'inlineEdit' ) ;
443- expect ( fieldKeys ) . toContain ( 'addDeleteRecordsInline' ) ;
444- expect ( fieldKeys ) . toContain ( '_rowActions' ) ;
445- expect ( fieldKeys ) . toContain ( '_bulkActions' ) ;
483+ expect ( fieldKeys . indexOf ( 'inlineEdit' ) ) . toBeLessThan ( fieldKeys . indexOf ( 'clickIntoRecordDetails' ) ) ;
446484 } ) ;
447485 } ) ;
448486
@@ -525,3 +563,137 @@ describe('buildViewConfigSchema', () => {
525563 }
526564 } ) ;
527565} ) ;
566+
567+ // ═══════════════════════════════════════════════════════════════════════════
568+ // 3. Spec-alignment validation
569+ // ═══════════════════════════════════════════════════════════════════════════
570+
571+ describe ( 'spec alignment' , ( ) => {
572+ // ── ROW_HEIGHT_OPTIONS matches spec RowHeight enum ───────────────────
573+ describe ( 'ROW_HEIGHT_OPTIONS' , ( ) => {
574+ it ( 'contains all 5 spec RowHeight values' , ( ) => {
575+ const values = ROW_HEIGHT_OPTIONS . map ( o => o . value ) ;
576+ expect ( values ) . toEqual ( [ 'compact' , 'short' , 'medium' , 'tall' , 'extra_tall' ] ) ;
577+ } ) ;
578+
579+ it ( 'each option has a gapClass' , ( ) => {
580+ for ( const opt of ROW_HEIGHT_OPTIONS ) {
581+ expect ( opt . gapClass ) . toBeDefined ( ) ;
582+ expect ( typeof opt . gapClass ) . toBe ( 'string' ) ;
583+ }
584+ } ) ;
585+ } ) ;
586+
587+ // ── NamedListView field coverage ────────────────────────────────────
588+ describe ( 'NamedListView spec field coverage' , ( ) => {
589+ function buildSchema ( ) {
590+ return buildViewConfigSchema ( {
591+ t : mockT ,
592+ fieldOptions : mockFieldOptions ,
593+ objectDef : mockObjectDef ,
594+ updateField : mockUpdateField ,
595+ filterGroupValue : mockFilterGroup ,
596+ sortItemsValue : mockSortItems ,
597+ } ) ;
598+ }
599+
600+ function allFieldKeys ( ) {
601+ const schema = buildSchema ( ) ;
602+ return schema . sections . flatMap ( s => s . fields . map ( f => f . key ) ) ;
603+ }
604+
605+ // Comprehensive: every NamedListView spec property must map to a UI field
606+ it ( 'covers ALL NamedListView spec properties' , ( ) => {
607+ const keys = allFieldKeys ( ) ;
608+ // NamedListView properties → UI field keys mapping
609+ const specPropertyToFieldKey : Record < string , string > = {
610+ label : 'label' ,
611+ type : 'type' ,
612+ columns : '_columns' ,
613+ filter : '_filterBy' ,
614+ sort : '_sortBy' ,
615+ showSearch : 'showSearch' ,
616+ showSort : 'showSort' ,
617+ showFilters : 'showFilters' ,
618+ showHideFields : 'showHideFields' ,
619+ showGroup : 'showGroup' ,
620+ showColor : 'showColor' ,
621+ showDensity : 'showDensity' ,
622+ allowExport : '_export' ,
623+ striped : 'striped' ,
624+ bordered : 'bordered' ,
625+ color : 'color' ,
626+ inlineEdit : 'inlineEdit' ,
627+ wrapHeaders : 'wrapHeaders' ,
628+ clickIntoRecordDetails : 'clickIntoRecordDetails' ,
629+ addRecordViaForm : '_addRecord' , // compound field
630+ addDeleteRecordsInline : 'addDeleteRecordsInline' ,
631+ collapseAllByDefault : 'collapseAllByDefault' ,
632+ fieldTextColor : 'fieldTextColor' ,
633+ prefixField : 'prefixField' ,
634+ showDescription : 'showDescription' ,
635+ navigation : '_navigationMode' , // compound: mode/width/openNewTab
636+ selection : '_selectionType' ,
637+ pagination : '_pageSize' , // compound: pageSize/pageSizeOptions
638+ searchableFields : '_searchableFields' ,
639+ filterableFields : '_filterableFields' ,
640+ resizable : 'resizable' ,
641+ densityMode : 'densityMode' ,
642+ rowHeight : 'rowHeight' ,
643+ hiddenFields : '_hiddenFields' ,
644+ exportOptions : '_export' , // compound with allowExport
645+ rowActions : '_rowActions' ,
646+ bulkActions : '_bulkActions' ,
647+ sharing : '_sharingEnabled' , // compound: enabled/visibility
648+ addRecord : '_addRecord' , // compound with addRecordViaForm
649+ conditionalFormatting : '_conditionalFormatting' ,
650+ quickFilters : '_quickFilters' ,
651+ showRecordCount : 'showRecordCount' ,
652+ allowPrinting : 'allowPrinting' ,
653+ virtualScroll : 'virtualScroll' ,
654+ emptyState : '_emptyState' ,
655+ aria : '_ariaLabel' , // compound: label/describedBy/live
656+ } ;
657+ for ( const [ specProp , fieldKey ] of Object . entries ( specPropertyToFieldKey ) ) {
658+ expect ( keys ) . toContain ( fieldKey ) ;
659+ }
660+ } ) ;
661+
662+ it ( 'covers all NamedListView toolbar toggles in order' , ( ) => {
663+ const schema = buildSchema ( ) ;
664+ const section = schema . sections . find ( s => s . key === 'pageConfig' ) ! ;
665+ const keys = section . fields . map ( f => f . key ) ;
666+ const toolbarFields = [
667+ 'showSearch' , 'showSort' , 'showFilters' ,
668+ 'showHideFields' , 'showGroup' , 'showColor' , 'showDensity' ,
669+ ] ;
670+ // All present
671+ for ( const field of toolbarFields ) {
672+ expect ( keys ) . toContain ( field ) ;
673+ }
674+ // Order matches spec
675+ for ( let i = 0 ; i < toolbarFields . length - 1 ; i ++ ) {
676+ expect ( keys . indexOf ( toolbarFields [ i ] ) ) . toBeLessThan ( keys . indexOf ( toolbarFields [ i + 1 ] ) ) ;
677+ }
678+ } ) ;
679+
680+ it ( 'covers all NamedListView boolean toggles in userActions in spec order' , ( ) => {
681+ const schema = buildSchema ( ) ;
682+ const section = schema . sections . find ( s => s . key === 'userActions' ) ! ;
683+ const keys = section . fields . map ( f => f . key ) ;
684+ // Spec order: inlineEdit → clickIntoRecordDetails → addDeleteRecordsInline
685+ expect ( keys . indexOf ( 'inlineEdit' ) ) . toBeLessThan ( keys . indexOf ( 'clickIntoRecordDetails' ) ) ;
686+ expect ( keys . indexOf ( 'clickIntoRecordDetails' ) ) . toBeLessThan ( keys . indexOf ( 'addDeleteRecordsInline' ) ) ;
687+ } ) ;
688+
689+ // Protocol suggestions: UI fields not in NamedListView spec
690+ it ( 'documents UI extension fields not in NamedListView spec' , ( ) => {
691+ const keys = allFieldKeys ( ) ;
692+ // These fields are UI extensions — documented as protocol suggestions
693+ const uiExtensions = [ 'description' , '_source' , '_groupBy' , '_typeOptions' ] ;
694+ for ( const ext of uiExtensions ) {
695+ expect ( keys ) . toContain ( ext ) ;
696+ }
697+ } ) ;
698+ } ) ;
699+ } ) ;
0 commit comments