@@ -6,6 +6,8 @@ import { fileURLToPath } from 'node:url';
66
77import {
88 ANALYSED_PACK_OBJECT_IMPORTERS ,
9+ DESIGNER_NAMESPACE_HOMES ,
10+ DESIGNER_NAMESPACE_SPECIFIER ,
911 DESIGNER_TABLE ,
1012 collectDesignerKeys ,
1113 collectIndirectTemplateHeads ,
@@ -792,6 +794,62 @@ describe('collectDesignerKeys() — the optional second argument', () => {
792794 } ) ;
793795} ) ;
794796
797+ /**
798+ * objectstack#25922 — the table's second home, the `studio` namespace. Its
799+ * catalogues are read as members of the table they belong to, but only when the
800+ * table module imports the namespace: the edge the runtime follows.
801+ */
802+ describe ( 'collectDesignerKeys() — the namespace homes' , ( ) => {
803+ const [ EN_HOME , ZH_HOME ] = DESIGNER_NAMESPACE_HOMES ;
804+ const IMPORTING_TABLE = `import { studioString } from '${ DESIGNER_NAMESPACE_SPECIFIER } ';\n${ DESIGNER_TABLE_FIXTURE } ` ;
805+ const homes = ( en : string , zh : string ) => ( {
806+ [ EN_HOME . file ] : `const ${ EN_HOME . decl } : Record<string, string> = { ${ en } };\nexport default ${ EN_HOME . decl } ;\n` ,
807+ [ ZH_HOME . file ] : `const ${ ZH_HOME . decl } : Record<string, string> = { ${ zh } };\nexport default ${ ZH_HOME . decl } ;\n` ,
808+ } ) ;
809+ const MOVED = homes ( "'designer.fx.moved': 'Moved'" , "'designer.fx.moved': '已迁移'" ) ;
810+
811+ it ( 'merges a home into its table when the table imports the namespace' , ( ) => {
812+ const { tables, values } = collectDesignerKeys ( designerRoot ( { [ DESIGNER_TABLE ] : IMPORTING_TABLE , ...MOVED } ) , {
813+ withValues : true ,
814+ } ) ;
815+ expect ( tables . get ( 'ENGINE_STRINGS_EN' ) ! . size ) . toBe ( 10 ) ;
816+ expect ( tables . get ( 'ENGINE_STRINGS_ZH' ) ! . has ( 'designer.fx.moved' ) ) . toBe ( true ) ;
817+ expect ( values ! . get ( 'ENGINE_STRINGS_ZH' ) ! . get ( 'designer.fx.moved' ) ) . toBe ( '已迁移' ) ;
818+ } ) ;
819+
820+ it ( 'reads no home when the table does not import the namespace, even if the files exist' , ( ) => {
821+ const { tables } = collectDesignerKeys ( designerRoot ( MOVED ) ) ;
822+ expect ( tables . get ( 'ENGINE_STRINGS_EN' ) ! . has ( 'designer.fx.moved' ) ) . toBe ( false ) ;
823+ } ) ;
824+
825+ it ( 'throws when the table imports the namespace and a home cannot be read' , ( ) => {
826+ expect ( ( ) => collectDesignerKeys ( designerRoot ( { [ DESIGNER_TABLE ] : IMPORTING_TABLE } ) ) ) . toThrow ( / c a n n o t b e r e a d / ) ;
827+ } ) ;
828+
829+ it ( 'throws on a key in both homes' , ( ) => {
830+ const root = designerRoot ( {
831+ [ DESIGNER_TABLE ] : IMPORTING_TABLE ,
832+ ...homes ( "'engine.fx.dead': 'Copy'" , "'engine.fx.dead': '副本'" ) ,
833+ } ) ;
834+ expect ( ( ) => collectDesignerKeys ( root ) ) . toThrow ( / e x a c t l y o n e h o m e / ) ;
835+ } ) ;
836+
837+ it ( 'a caller that passes `source` reads homes through `readHome`, or throws' , ( ) => {
838+ const root = designerRoot ( MOVED ) ;
839+ expect ( ( ) => collectDesignerKeys ( root , { source : IMPORTING_TABLE } ) ) . toThrow ( / r e a d H o m e / ) ;
840+ const read = collectDesignerKeys ( root , {
841+ source : IMPORTING_TABLE ,
842+ readHome : ( file : string ) => fs . readFileSync ( path . join ( root , file ) , 'utf8' ) ,
843+ } ) ;
844+ expect ( read . corpus . has ( 'designer.fx.moved' ) ) . toBe ( true ) ;
845+ } ) ;
846+
847+ it ( "the dead-keys sweep does not read a home's own literal as a reader of its keys" , ( ) => {
848+ const result = sweepDesignerTable ( designerRoot ( { [ DESIGNER_TABLE ] : IMPORTING_TABLE , ...MOVED } ) ) ;
849+ expect ( keysOf ( result . confirmed ) ) . toContain ( 'designer.fx.moved' ) ;
850+ } ) ;
851+ } ) ;
852+
795853describe ( 'sweepDesignerTable()' , ( ) => {
796854 it ( 'excludes a key spelled as a literal at a call site' , ( ) => {
797855 const result = sweepDesignerTable ( designerRoot ( ) ) ;
0 commit comments