2929 */
3030import { describe , expect , it } from 'vitest' ;
3131import { assertEngineDeleteDispatch , assertEngineUpdateDispatch , assertEngineFindOnePredicate , isCodeArtifactBody } from '@objectstack/metadata-core' ;
32- import { expandViewContainer } from '@objectstack/spec/ui' ;
32+ import { expandViewContainer , ViewSchema } from '@objectstack/spec/ui' ;
3333import { ObjectStackProtocolImplementation } from './index.js' ;
3434
3535interface Row {
@@ -398,17 +398,20 @@ describe('#13407 org-scoped and environment-scoped runtime containers are served
398398} ) ;
399399
400400/**
401- * #21334 — a bare-`list` container on ANOTHER package's object must not take
402- * that package's `<object>. default` .
401+ * #21334 — a container on ANOTHER package's object must not take any of that
402+ * package's names, nor its default.
403403 *
404- * The spec names a bare `list` `<object>.default`. A container saved under any
405- * other name — in another package, or in none — for an object a code package
406- * ships used to expand there and REPLACE the packaged default on the object
407- * door (`GET /meta/view?object=`), wearing the shadowed artifact's `_packageId`,
404+ * The spec names every expanded view `<object>.<key>` (a bare `list` takes
405+ * `<object>.default`). A container saved under any other name — in another
406+ * package, or in none — for an object a code package ships used to expand
407+ * there and REPLACE the packaged views on the object door
408+ * (`GET /meta/view?object=`), wearing the shadowed artifact's `_packageId`,
408409 * while the by-name read kept the packaged item on an environment-scoped kernel
409410 * (and served the shadow too on an unscoped one, through the registry's bare
410411 * key). ADR-0005 keys an overlay by its own name and ADR-0126 rules out a silent
411- * override; the ruling takes the arm "expand under the container's own name".
412+ * override. Triage's ruling takes the arm "expand under the container's own
413+ * name"; the seat's answer extends it to every member of the container, and
414+ * rules that such a container never declares the object's default.
412415 *
413416 * The registry double here is the real `SchemaRegistry`'s shape where this card
414417 * turns on it: loader entries under `<package>:<name>`, a hydrated row under
@@ -419,7 +422,7 @@ describe('#13407 org-scoped and environment-scoped runtime containers are served
419422 * composition, through the REST doors, is
420423 * `view-container-cross-package-default.dogfood.test.ts`.
421424 */
422- describe ( '#21334 a bare-list container on another package\'s object never takes that package\'s <object>. default' , ( ) => {
425+ describe ( '#21334 a container on another package\'s object never takes that package\'s names or its default' , ( ) => {
423426 const SHOWCASE = 'com.example.showcase' ;
424427 const REPAIR = 'com.example.repairassets' ;
425428 const TASK = 'showcase_task' ;
@@ -428,13 +431,22 @@ describe('#21334 a bare-list container on another package\'s object never takes
428431 const data = { provider : 'object' , object : TASK } ;
429432 const PACKAGED_COLUMNS = [ 'title' , 'project' , 'assignee' , 'status' , 'priority' , 'due_date' , 'progress' ]
430433 . map ( ( field ) => ( { field } ) ) ;
431- /** What the showcase ships: a `defineView` container with a bare default list and one keyed view. */
434+ /**
435+ * What the showcase ships for `showcase_task`: a `defineView` container with
436+ * one member of every kind the expander knows, so every probe below aims at
437+ * a name the package really ships.
438+ */
432439 const packagedTaskViews = {
433440 list : { label : 'All Tasks' , type : 'grid' , data, columns : PACKAGED_COLUMNS } ,
434441 listViews : {
435442 in_progress : { label : 'In Progress' , type : 'grid' , data, columns : PACKAGED_COLUMNS . slice ( 0 , 4 ) } ,
436443 } ,
444+ form : { label : 'Task Form' , type : 'simple' , sections : [ { label : 'Main' , fields : [ 'title' ] } ] } ,
445+ formViews : {
446+ edit : { label : 'Edit Task' , type : 'simple' , sections : [ { label : 'Edit' , fields : [ 'title' , 'status' ] } ] } ,
447+ } ,
437448 } ;
449+ const PACKAGED = expandViewContainer ( TASK , packagedTaskViews ) . map ( ( vi ) => ( { ...( vi as any ) } ) ) ;
438450 /** The card's own probe body, verbatim. */
439451 const probe = ( name : string ) => ( { name, object : TASK , list : { type : 'grid' , columns : [ 'title' , 'status' ] } } ) ;
440452
@@ -522,29 +534,138 @@ describe('#21334 a bare-list container on another package\'s object never takes
522534 expect ( v ?. _packageId ) . toBe ( SHOWCASE ) ;
523535 expect ( v ?. isDefault ) . toBe ( true ) ;
524536 } ;
537+ /** (a) Every name the showcase ships answers the packaged view, once, on BOTH doors — the same row. */
538+ const expectEveryPackagedNameIntact = async ( protocol : Protocol , organizationId ?: string ) => {
539+ const served = await objectDoor ( protocol , organizationId ) ;
540+ for ( const shipped of PACKAGED ) {
541+ const listed = named ( served , shipped . name ) ;
542+ expect ( listed , `exactly one item answers ${ shipped . name } on the object door` ) . toHaveLength ( 1 ) ;
543+ expect ( { label : listed [ 0 ] . label , config : listed [ 0 ] . config , _packageId : listed [ 0 ] . _packageId } )
544+ . toEqual ( { label : shipped . label , config : shipped . config , _packageId : SHOWCASE } ) ;
545+ const read = await byNameDoor ( protocol , shipped . name , organizationId ) ;
546+ expect ( { label : read ?. label , config : read ?. config , _packageId : read ?. _packageId } )
547+ . toEqual ( { label : shipped . label , config : shipped . config , _packageId : SHOWCASE } ) ;
548+ }
549+ } ;
550+ /** (c) The object's only defaults are the ones its owning package declares. */
551+ const expectOnlyPackagedDefaults = ( served : any [ ] ) => {
552+ for ( const viewKind of [ 'list' , 'form' ] ) {
553+ expect (
554+ served . filter ( ( v ) => v . viewKind === viewKind && v . isDefault ) . map ( ( v ) => v . name ) ,
555+ `the ${ viewKind } default stays the owning package's` ,
556+ ) . toEqual ( PACKAGED . filter ( ( v ) => v . viewKind === viewKind && v . isDefault ) . map ( ( v ) => v . name ) ) ;
557+ }
558+ } ;
559+
560+ /**
561+ * Every member kind the spec's expander places, derived FROM the expander:
562+ * each top-level key of the container schema is offered a single view and a
563+ * record of views, and a key that yields an expanded item is a member kind.
564+ * A single-view member is enumerated twice — bare, and naming its own key.
565+ * A kind the spec adds later shows up here, and the enumeration below fails
566+ * until it is placed.
567+ */
568+ function memberKindsOfTheExpander ( ) : string [ ] {
569+ const slots = Object . keys ( ( ViewSchema as unknown as { shape ?: Record < string , unknown > } ) . shape ?? { } ) ;
570+ expect ( slots . length , 'the container schema\'s own keys are readable' ) . toBeGreaterThan ( 0 ) ;
571+ const view = { type : 'grid' , label : 'probe' } ;
572+ const kinds : string [ ] = [ ] ;
573+ for ( const slot of slots ) {
574+ if ( expandViewContainer ( 'o' , { [ slot ] : { ...view } } ) . length > 0 ) {
575+ kinds . push ( slot , `${ slot } #named` ) ;
576+ } else if ( expandViewContainer ( 'o' , { [ slot ] : { k : { ...view } } } ) . some ( ( vi ) => vi . name === 'o.k' ) ) {
577+ kinds . push ( `${ slot } .*` ) ;
578+ }
579+ }
580+ return kinds . sort ( ) ;
581+ }
582+
583+ const OWN = 'os_qa_probe' ;
584+ const listView = { type : 'grid' , columns : [ 'title' , 'status' ] } ;
585+ const formView = { label : 'Probe Form' , type : 'simple' , sections : [ { label : 'Probe' , fields : [ 'title' ] } ] } ;
586+ /**
587+ * One case per member kind: a container on `showcase_task` with ONLY that
588+ * member, whose key aims at a name the showcase ships, and the one name the
589+ * member must be served under instead.
590+ */
591+ const MEMBER_CASES : Record < string , { member : Record < string , unknown > ; authored : unknown ; shadows : string ; servedAs : string } > = {
592+ list : { member : { list : listView } , authored : listView , shadows : DEFAULT , servedAs : `${ TASK } .${ OWN } ` } ,
593+ 'list#named' : {
594+ member : { list : { ...listView , name : 'in_progress' } } , authored : { ...listView , name : 'in_progress' } ,
595+ shadows : `${ TASK } .in_progress` , servedAs : `${ TASK } .${ OWN } .in_progress` ,
596+ } ,
597+ 'listViews.*' : {
598+ member : { listViews : { in_progress : listView } } , authored : listView ,
599+ shadows : `${ TASK } .in_progress` , servedAs : `${ TASK } .${ OWN } .in_progress` ,
600+ } ,
601+ form : { member : { form : formView } , authored : formView , shadows : `${ TASK } .form` , servedAs : `${ TASK } .${ OWN } .form` } ,
602+ 'form#named' : {
603+ member : { form : { ...formView , name : 'edit' } } , authored : { ...formView , name : 'edit' } ,
604+ shadows : `${ TASK } .edit` , servedAs : `${ TASK } .${ OWN } .edit` ,
605+ } ,
606+ 'formViews.*' : {
607+ member : { formViews : { edit : formView } } , authored : formView ,
608+ shadows : `${ TASK } .edit` , servedAs : `${ TASK } .${ OWN } .edit` ,
609+ } ,
610+ } ;
611+
612+ it ( 'the enumeration covers every member kind the spec\'s expander places, and nothing else' , ( ) => {
613+ expect ( Object . keys ( MEMBER_CASES ) . sort ( ) ) . toEqual ( memberKindsOfTheExpander ( ) ) ;
614+ // Each probe really aims at a shipped name: without the fix it IS that name.
615+ for ( const [ kind , c ] of Object . entries ( MEMBER_CASES ) ) {
616+ const names = expandViewContainer ( TASK , c . member ) . map ( ( vi ) => vi . name ) ;
617+ expect ( names , kind ) . toEqual ( [ c . shadows ] ) ;
618+ expect ( PACKAGED . map ( ( v ) => v . name ) , kind ) . toContain ( c . shadows ) ;
619+ }
620+ } ) ;
525621
526622 const KERNELS = [
527623 [ 'an environment-scoped kernel (the standalone stack stamps env_local)' , 'env_local' ] ,
528624 [ 'an unscoped kernel (write-through hydrates the registry)' , undefined ] ,
529625 ] as const ;
530626 const CONTAINERS = [
531- { arm : 'package-scoped (saved into another writable package)' , name : 'os_qa_shadow_probe' , packageId : REPAIR , organizationId : undefined , ownPackage : REPAIR } ,
532- { arm : 'package-less, environment-wide' , name : 'os_qa_shadow_probe2' , packageId : undefined , organizationId : undefined , ownPackage : undefined } ,
533- { arm : 'package-less, organization-scoped' , name : 'os_qa_shadow_probe3' , packageId : undefined , organizationId : ORG , ownPackage : undefined } ,
627+ { arm : 'package-scoped (saved into another writable package)' , packageId : REPAIR , organizationId : undefined , ownPackage : REPAIR } ,
628+ { arm : 'package-less, environment-wide' , packageId : undefined , organizationId : undefined , ownPackage : undefined } ,
629+ { arm : 'package-less, organization-scoped' , packageId : undefined , organizationId : ORG , ownPackage : undefined } ,
534630 ] as const ;
631+ const save = ( protocol : Protocol , name : string , item : unknown , c : ( typeof CONTAINERS ) [ number ] ) =>
632+ protocol . saveMetaItem ( {
633+ type : 'view' , name, item,
634+ ...( c . packageId ? { packageId : c . packageId } : { } ) ,
635+ ...scoped ( c . organizationId ) ,
636+ } as any ) ;
535637
536638 for ( const [ kernel , environmentId ] of KERNELS ) {
537639 describe ( `on ${ kernel } ` , ( ) => {
538640 for ( const c of CONTAINERS ) {
539- it ( `${ c . arm } : the packaged default is unchanged on BOTH doors, and they answer the same row` , async ( ) => {
540- const { protocol } = showcaseHarness ( environmentId ) ;
541- expectPackagedDefault ( named ( await objectDoor ( protocol , c . organizationId ) , DEFAULT ) [ 0 ] ) ;
641+ for ( const [ kind , m ] of Object . entries ( MEMBER_CASES ) ) {
642+ it ( `${ c . arm } , member ${ kind } : no packaged name is replaced on either door, its own name is served with its own package, and it claims no default` , async ( ) => {
643+ const { protocol } = showcaseHarness ( environmentId ) ;
644+ await save ( protocol , OWN , { name : OWN , object : TASK , ...m . member } , c ) ;
645+
646+ // (a)
647+ await expectEveryPackagedNameIntact ( protocol , c . organizationId ) ;
648+ // (b)
649+ const served = await objectDoor ( protocol , c . organizationId ) ;
650+ const own = served . filter ( ( v ) => String ( v . name ) . startsWith ( `${ TASK } .${ OWN } ` ) ) ;
651+ expect ( own . map ( ( v ) => v . name ) ) . toEqual ( [ m . servedAs ] ) ;
652+ expect ( own [ 0 ] . object ) . toBe ( TASK ) ;
653+ expect ( own [ 0 ] . _packageId ) . toBe ( c . ownPackage ) ;
654+ expect ( own [ 0 ] . _provenance , 'never the packaged artifact\'s provenance' ) . not . toBe ( 'package' ) ;
655+ expect ( own [ 0 ] . _diagnostics ?. valid , 'a qualified ViewItem name the spec accepts' ) . toBe ( true ) ;
656+ expect ( own [ 0 ] . config , 'the member as authored, nothing lent left on it' ) . toEqual ( m . authored ) ;
657+ // (c)
658+ expect ( own [ 0 ] . isDefault ) . toBeUndefined ( ) ;
659+ expectOnlyPackagedDefaults ( served ) ;
660+ // The by-name door answers the container's own name with its row.
661+ const row = await byNameDoor ( protocol , OWN , c . organizationId ) ;
662+ expect ( row ?. object ) . toBe ( TASK ) ;
663+ } ) ;
664+ }
542665
543- await protocol . saveMetaItem ( {
544- type : 'view' , name : c . name , item : probe ( c . name ) ,
545- ...( c . packageId ? { packageId : c . packageId } : { } ) ,
546- ...scoped ( c . organizationId ) ,
547- } as any ) ;
666+ it ( `${ c . arm } : the card's probe — the packaged default unchanged on BOTH doors, and the object keeps ONE list default` , async ( ) => {
667+ const { protocol } = showcaseHarness ( environmentId ) ;
668+ await save ( protocol , 'os_qa_shadow_probe' , probe ( 'os_qa_shadow_probe' ) , c ) ;
548669
549670 const listed = named ( await objectDoor ( protocol , c . organizationId ) , DEFAULT ) ;
550671 expect ( listed , 'exactly one item answers <object>.default on the object door' ) . toHaveLength ( 1 ) ;
@@ -553,31 +674,13 @@ describe('#21334 a bare-list container on another package\'s object never takes
553674 expectPackagedDefault ( read ) ;
554675 expect ( { label : read . label , config : read . config , _packageId : read . _packageId } )
555676 . toEqual ( { label : listed [ 0 ] . label , config : listed [ 0 ] . config , _packageId : listed [ 0 ] . _packageId } ) ;
556- } ) ;
557-
558- it ( `${ c . arm } : the container's own view is served under its own name, carrying its own package` , async ( ) => {
559- const { protocol } = showcaseHarness ( environmentId ) ;
560- await protocol . saveMetaItem ( {
561- type : 'view' , name : c . name , item : probe ( c . name ) ,
562- ...( c . packageId ? { packageId : c . packageId } : { } ) ,
563- ...scoped ( c . organizationId ) ,
564- } as any ) ;
565-
566- const own = named ( await objectDoor ( protocol , c . organizationId ) , `${ TASK } .${ c . name } ` ) ;
567- expect ( own ) . toHaveLength ( 1 ) ;
568- expect ( own [ 0 ] . viewKind ) . toBe ( 'list' ) ;
569- expect ( own [ 0 ] . config , 'the list as authored, nothing lent left on it' ) . toEqual ( probe ( c . name ) . list ) ;
570- expect ( own [ 0 ] . _packageId ) . toBe ( c . ownPackage ) ;
571- expect ( own [ 0 ] . _provenance , 'never the packaged artifact\'s provenance' ) . not . toBe ( 'package' ) ;
572- expect ( own [ 0 ] . _diagnostics ?. valid , 'a qualified ViewItem name the spec accepts' ) . toBe ( true ) ;
573-
574- // The by-name door answers the container's own name with its row.
575- const row = await byNameDoor ( protocol , c . name , c . organizationId ) ;
576- expect ( row . list ) . toEqual ( probe ( c . name ) . list ) ;
677+ const served = await objectDoor ( protocol , c . organizationId ) ;
678+ expect ( served . filter ( ( v ) => v . viewKind === 'list' && v . isDefault ) . map ( ( v ) => v . name ) ) . toEqual ( [ DEFAULT ] ) ;
679+ expect ( named ( served , `${ TASK } .os_qa_shadow_probe` ) [ 0 ] ?. config ) . toEqual ( probe ( 'os_qa_shadow_probe' ) . list ) ;
577680 } ) ;
578681 }
579682
580- it ( 'CONTROL — a container of the object\'s OWN package still expands to <object>.default' , async ( ) => {
683+ it ( 'CONTROL — a container of the object\'s OWN package still expands to <object>.default, as its default ' , async ( ) => {
581684 const { protocol, rows } = showcaseHarness ( environmentId ) ;
582685 // A row bound to the package that owns the object (an installed
583686 // package's own stored view), written straight to the store.
@@ -590,6 +693,7 @@ describe('#21334 a bare-list container on another package\'s object never takes
590693 expect ( listed ) . toHaveLength ( 1 ) ;
591694 expect ( listed [ 0 ] . config ) . toEqual ( probe ( 'os_qa_same_pkg' ) . list ) ;
592695 expect ( listed [ 0 ] . _packageId ) . toBe ( SHOWCASE ) ;
696+ expect ( listed [ 0 ] . isDefault ) . toBe ( true ) ;
593697 expect ( named ( await objectDoor ( protocol ) , `${ TASK } .os_qa_same_pkg` ) ) . toEqual ( [ ] ) ;
594698 } ) ;
595699
@@ -604,7 +708,8 @@ describe('#21334 a bare-list container on another package\'s object never takes
604708 expect ( listed ) . toHaveLength ( 1 ) ;
605709 expect ( listed [ 0 ] . label ) . toBe ( 'Customized' ) ;
606710 expect ( listed [ 0 ] . _packageId ) . toBe ( SHOWCASE ) ;
607- expect ( named ( await objectDoor ( protocol ) , `${ TASK } .${ TASK } ` ) ) . toEqual ( [ ] ) ;
711+ expect ( listed [ 0 ] . isDefault ) . toBe ( true ) ;
712+ expect ( ( await objectDoor ( protocol ) ) . filter ( ( v ) => String ( v . name ) . startsWith ( `${ TASK } .${ TASK } ` ) ) ) . toEqual ( [ ] ) ;
608713 } ) ;
609714
610715 it ( 'CONTROL — the sanctioned override (a write to <object>.default by name) is served on both doors' , async ( ) => {
@@ -633,24 +738,31 @@ describe('#21334 a bare-list container on another package\'s object never takes
633738 await protocol . saveMetaItem ( { type : 'view' , name : 'probe_x' , item : container , packageId : REPAIR } as any ) ;
634739
635740 const served = await objectDoor ( protocol ) ;
636- expect ( named ( served , `${ TASK } .probe_x` ) [ 0 ] ?. label ) . toBe ( 'Keyed' ) ;
637- const bare = named ( served , `${ TASK } .probe_x_2` ) [ 0 ] ;
638- expect ( bare ?. config ) . toEqual ( container . list ) ;
639- expectPackagedDefault ( named ( served , DEFAULT ) [ 0 ] ) ;
640- expect ( named ( served , DEFAULT ) ) . toHaveLength ( 1 ) ;
741+ expect ( named ( served , `${ TASK } .probe_x.probe_x` ) [ 0 ] ?. label ) . toBe ( 'Keyed' ) ;
742+ expect ( named ( served , `${ TASK } .probe_x` ) [ 0 ] ?. config ) . toEqual ( container . list ) ;
743+ await expectEveryPackagedNameIntact ( protocol ) ;
744+ } ) ;
745+
746+ it ( 'a container named after a key the owning package ships keeps its bare list off that name' , async ( ) => {
747+ const { protocol } = showcaseHarness ( 'env_local' ) ;
748+ await protocol . saveMetaItem ( { type : 'view' , name : 'in_progress' , item : probe ( 'in_progress' ) , packageId : REPAIR } as any ) ;
749+
750+ const served = await objectDoor ( protocol ) ;
751+ expect ( named ( served , `${ TASK } .in_progress.in_progress` ) [ 0 ] ?. config ) . toEqual ( probe ( 'in_progress' ) . list ) ;
752+ expect ( named ( served , `${ TASK } .in_progress.in_progress` ) [ 0 ] ?. _packageId ) . toBe ( REPAIR ) ;
753+ await expectEveryPackagedNameIntact ( protocol ) ;
641754 } ) ;
642755
643- it ( 'a stored container with no name of its own does not expand its bare list under another package\'s name ' , async ( ) => {
756+ it ( 'a stored container with no name of its own expands nothing on another package\'s object ' , async ( ) => {
644757 const { protocol, rows } = showcaseHarness ( 'env_local' ) ;
645- const nameless = { object : TASK , list : { type : 'grid' , columns : [ 'title' ] } , listViews : { mine : { label : 'Mine' , type : 'grid' , columns : [ 'title' ] } } } ;
758+ const nameless = { object : TASK , list : { type : 'grid' , columns : [ 'title' ] } , listViews : { in_progress : { label : 'Mine' , type : 'grid' , columns : [ 'title' ] } } } ;
646759 rows . set ( 'nameless-row' , {
647760 id : 'r_nameless' , type : 'view' , name : 'os_qa_nameless' , organization_id : null ,
648761 package_id : REPAIR , state : 'active' , metadata : JSON . stringify ( nameless ) ,
649762 } ) ;
650763
651764 const served = await objectDoor ( protocol ) ;
652- expect ( named ( served , DEFAULT ) ) . toHaveLength ( 1 ) ;
653- expectPackagedDefault ( named ( served , DEFAULT ) [ 0 ] ) ;
654- expect ( named ( served , `${ TASK } .mine` ) [ 0 ] ?. _packageId ) . toBe ( REPAIR ) ;
765+ expect ( served . filter ( ( v ) => v . _packageId === REPAIR ) ) . toEqual ( [ ] ) ;
766+ await expectEveryPackagedNameIntact ( protocol ) ;
655767 } ) ;
656768} ) ;
0 commit comments