1+ import fs from 'node:fs' ;
2+ import path from 'node:path' ;
3+
14import {
25 BODY_DOCUMENT_ID ,
36 DocumentDefinition ,
@@ -496,7 +499,7 @@ describe('DocumentService', () => {
496499 } ) ;
497500
498501 describe ( 'isCollectionField()' , ( ) => {
499- it ( '' , ( ) => {
502+ it ( 'should identify collection fields by maxOccurs ' , ( ) => {
500503 expect ( DocumentService . isCollectionField ( sourceDoc . fields [ 0 ] ) ) . toBeFalsy ( ) ;
501504 expect ( DocumentService . isCollectionField ( targetDoc . fields [ 0 ] ) ) . toBeFalsy ( ) ;
502505 expect ( DocumentService . isCollectionField ( sourceDoc . fields [ 0 ] . fields [ 0 ] ) ) . toBeFalsy ( ) ;
@@ -508,6 +511,300 @@ describe('DocumentService', () => {
508511 expect ( DocumentService . isCollectionField ( sourceDoc . fields [ 0 ] . fields [ 3 ] ) ) . toBeTruthy ( ) ;
509512 expect ( DocumentService . isCollectionField ( targetDoc . fields [ 0 ] . fields [ 3 ] ) ) . toBeTruthy ( ) ;
510513 } ) ;
514+
515+ it ( 'should identify members of collection choice as collection fields' , async ( ) => {
516+ const mockApi = {
517+ getResourceContent : jest . fn ( ) . mockResolvedValue ( `
518+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="test" xmlns:ns0="test">
519+ <xs:element name="TestDocument">
520+ <xs:complexType>
521+ <xs:sequence>
522+ <xs:element name="CollectionChoiceElement">
523+ <xs:complexType>
524+ <xs:choice maxOccurs="unbounded">
525+ <xs:element name="Email" type="xs:string"/>
526+ <xs:element name="Phone" type="xs:string"/>
527+ <xs:element name="Fax" type="xs:string"/>
528+ </xs:choice>
529+ </xs:complexType>
530+ </xs:element>
531+ </xs:sequence>
532+ </xs:complexType>
533+ </xs:element>
534+ </xs:schema>
535+ ` ) ,
536+ } ;
537+
538+ const result = await DocumentService . createDocument (
539+ mockApi as unknown as IMetadataApi ,
540+ DocumentType . SOURCE_BODY ,
541+ DocumentDefinitionType . XML_SCHEMA ,
542+ 'test' ,
543+ [ 'test.xsd' ] ,
544+ ) ;
545+
546+ expect ( result . validationStatus ) . toBe ( 'success' ) ;
547+ const doc = result . document ! ;
548+
549+ // Navigate to CollectionChoiceElement
550+ const collectionChoiceElement = doc . fields [ 0 ] . fields [ 0 ] ;
551+ expect ( collectionChoiceElement . name ) . toBe ( 'CollectionChoiceElement' ) ;
552+
553+ // The choice wrapper itself should be a collection
554+ const choiceWrapper = collectionChoiceElement . fields [ 0 ] ;
555+ expect ( choiceWrapper . wrapperKind ) . toBe ( 'choice' ) ;
556+ expect ( DocumentService . isCollectionField ( choiceWrapper ) ) . toBeTruthy ( ) ;
557+
558+ // Each member of the collection choice should inherit collection status
559+ const emailField = choiceWrapper . fields . find ( ( f ) => f . name === 'Email' ) ;
560+ const phoneField = choiceWrapper . fields . find ( ( f ) => f . name === 'Phone' ) ;
561+ const faxField = choiceWrapper . fields . find ( ( f ) => f . name === 'Fax' ) ;
562+
563+ expect ( emailField ) . toBeDefined ( ) ;
564+ expect ( phoneField ) . toBeDefined ( ) ;
565+ expect ( faxField ) . toBeDefined ( ) ;
566+
567+ // Members themselves have maxOccurs=1, but should be treated as collections
568+ // because they're inside a collection choice wrapper
569+ expect ( emailField ! . maxOccurs ) . toBe ( 1 ) ;
570+ expect ( DocumentService . isCollectionField ( emailField ! ) ) . toBeTruthy ( ) ;
571+
572+ expect ( phoneField ! . maxOccurs ) . toBe ( 1 ) ;
573+ expect ( DocumentService . isCollectionField ( phoneField ! ) ) . toBeTruthy ( ) ;
574+
575+ expect ( faxField ! . maxOccurs ) . toBe ( 1 ) ;
576+ expect ( DocumentService . isCollectionField ( faxField ! ) ) . toBeTruthy ( ) ;
577+ } ) ;
578+
579+ it ( 'should not treat members of non-collection choice as collection fields' , async ( ) => {
580+ const mockApi = {
581+ getResourceContent : jest . fn ( ) . mockResolvedValue ( `
582+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="test" xmlns:ns0="test">
583+ <xs:element name="TestDocument">
584+ <xs:complexType>
585+ <xs:sequence>
586+ <xs:element name="RegularChoiceElement">
587+ <xs:complexType>
588+ <xs:choice>
589+ <xs:element name="Choice1" type="xs:string"/>
590+ <xs:element name="Choice2" type="xs:string"/>
591+ </xs:choice>
592+ </xs:complexType>
593+ </xs:element>
594+ </xs:sequence>
595+ </xs:complexType>
596+ </xs:element>
597+ </xs:schema>
598+ ` ) ,
599+ } ;
600+
601+ const result = await DocumentService . createDocument (
602+ mockApi as unknown as IMetadataApi ,
603+ DocumentType . SOURCE_BODY ,
604+ DocumentDefinitionType . XML_SCHEMA ,
605+ 'test' ,
606+ [ 'test.xsd' ] ,
607+ ) ;
608+
609+ expect ( result . validationStatus ) . toBe ( 'success' ) ;
610+ const doc = result . document ! ;
611+
612+ // Navigate to RegularChoiceElement
613+ const regularChoiceElement = doc . fields [ 0 ] . fields [ 0 ] ;
614+ expect ( regularChoiceElement . name ) . toBe ( 'RegularChoiceElement' ) ;
615+
616+ // The choice wrapper itself should NOT be a collection (maxOccurs=1)
617+ const choiceWrapper = regularChoiceElement . fields [ 0 ] ;
618+ expect ( choiceWrapper . wrapperKind ) . toBe ( 'choice' ) ;
619+ expect ( DocumentService . isCollectionField ( choiceWrapper ) ) . toBeFalsy ( ) ;
620+
621+ // Members should NOT inherit collection status from non-collection choice
622+ const choice1Field = choiceWrapper . fields . find ( ( f ) => f . name === 'Choice1' ) ;
623+ const choice2Field = choiceWrapper . fields . find ( ( f ) => f . name === 'Choice2' ) ;
624+
625+ expect ( choice1Field ) . toBeDefined ( ) ;
626+ expect ( choice2Field ) . toBeDefined ( ) ;
627+
628+ expect ( DocumentService . isCollectionField ( choice1Field ! ) ) . toBeFalsy ( ) ;
629+ expect ( DocumentService . isCollectionField ( choice2Field ! ) ) . toBeFalsy ( ) ;
630+ } ) ;
631+ } ) ;
632+
633+ describe ( 'isFieldInsideCollectionChoiceWrapper()' , ( ) => {
634+ it ( 'should return true for fields inside collection choice wrapper' , async ( ) => {
635+ const mockApi = {
636+ getResourceContent : jest . fn ( ) . mockResolvedValue ( `
637+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="test" xmlns:ns0="test">
638+ <xs:element name="TestDocument">
639+ <xs:complexType>
640+ <xs:sequence>
641+ <xs:element name="CollectionChoiceElement">
642+ <xs:complexType>
643+ <xs:choice maxOccurs="unbounded">
644+ <xs:element name="Email" type="xs:string"/>
645+ <xs:element name="Phone" type="xs:string"/>
646+ </xs:choice>
647+ </xs:complexType>
648+ </xs:element>
649+ </xs:sequence>
650+ </xs:complexType>
651+ </xs:element>
652+ </xs:schema>
653+ ` ) ,
654+ } ;
655+
656+ const result = await DocumentService . createDocument (
657+ mockApi as unknown as IMetadataApi ,
658+ DocumentType . SOURCE_BODY ,
659+ DocumentDefinitionType . XML_SCHEMA ,
660+ 'test' ,
661+ [ 'test.xsd' ] ,
662+ ) ;
663+
664+ const doc = result . document ! ;
665+ const choiceWrapper = doc . fields [ 0 ] . fields [ 0 ] . fields [ 0 ] ;
666+ const emailField = choiceWrapper . fields . find ( ( f ) => f . name === 'Email' ) ! ;
667+
668+ expect ( DocumentService . isFieldInsideCollectionChoiceWrapper ( emailField ) ) . toBeTruthy ( ) ;
669+ } ) ;
670+
671+ it ( 'should return false for fields inside non-collection choice wrapper' , async ( ) => {
672+ const mockApi = {
673+ getResourceContent : jest . fn ( ) . mockResolvedValue ( `
674+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="test" xmlns:ns0="test">
675+ <xs:element name="TestDocument">
676+ <xs:complexType>
677+ <xs:sequence>
678+ <xs:element name="RegularChoiceElement">
679+ <xs:complexType>
680+ <xs:choice>
681+ <xs:element name="Choice1" type="xs:string"/>
682+ </xs:choice>
683+ </xs:complexType>
684+ </xs:element>
685+ </xs:sequence>
686+ </xs:complexType>
687+ </xs:element>
688+ </xs:schema>
689+ ` ) ,
690+ } ;
691+
692+ const result = await DocumentService . createDocument (
693+ mockApi as unknown as IMetadataApi ,
694+ DocumentType . SOURCE_BODY ,
695+ DocumentDefinitionType . XML_SCHEMA ,
696+ 'test' ,
697+ [ 'test.xsd' ] ,
698+ ) ;
699+
700+ const doc = result . document ! ;
701+ const choiceWrapper = doc . fields [ 0 ] . fields [ 0 ] . fields [ 0 ] ;
702+ const choice1Field = choiceWrapper . fields . find ( ( f ) => f . name === 'Choice1' ) ! ;
703+
704+ expect ( DocumentService . isFieldInsideCollectionChoiceWrapper ( choice1Field ) ) . toBeFalsy ( ) ;
705+ } ) ;
706+
707+ it ( 'should return false for fields not inside any choice wrapper' , ( ) => {
708+ const regularField = sourceDoc . fields [ 0 ] . fields [ 0 ] ;
709+ expect ( DocumentService . isFieldInsideCollectionChoiceWrapper ( regularField ) ) . toBeFalsy ( ) ;
710+ } ) ;
711+ } ) ;
712+
713+ describe ( 'isCollectionField() with TestDocument.xsd' , ( ) => {
714+ it ( 'should identify CollectionChoiceElement members as collection fields' , async ( ) => {
715+ const mockApi = {
716+ getResourceContent : jest . fn ( ) . mockImplementation ( ( filePath : string ) => {
717+ if ( filePath . includes ( 'TestDocument.xsd' ) ) {
718+ return Promise . resolve (
719+ fs . readFileSync ( path . resolve ( __dirname , '../../stubs/datamapper/xml/TestDocument.xsd' ) , 'utf-8' ) ,
720+ ) ;
721+ }
722+ return Promise . reject ( new Error ( 'File not found' ) ) ;
723+ } ) ,
724+ } ;
725+
726+ const result = await DocumentService . createDocument (
727+ mockApi as unknown as IMetadataApi ,
728+ DocumentType . SOURCE_BODY ,
729+ DocumentDefinitionType . XML_SCHEMA ,
730+ 'TestDocument' ,
731+ [ 'TestDocument.xsd' ] ,
732+ ) ;
733+
734+ expect ( result . validationStatus ) . toBe ( 'success' ) ;
735+ const doc = result . document ! ;
736+
737+ const testDocument = doc . fields [ 0 ] ;
738+ expect ( testDocument . name ) . toBe ( 'TestDocument' ) ;
739+
740+ const collectionChoiceElement = testDocument . fields . find ( ( f ) => f . name === 'CollectionChoiceElement' ) ;
741+ expect ( collectionChoiceElement ) . toBeDefined ( ) ;
742+
743+ const choiceWrapper = collectionChoiceElement ! . fields [ 0 ] ;
744+ expect ( choiceWrapper . wrapperKind ) . toBe ( 'choice' ) ;
745+ expect ( choiceWrapper . maxOccurs ) . toBe ( 'unbounded' ) ;
746+ expect ( DocumentService . isCollectionField ( choiceWrapper ) ) . toBeTruthy ( ) ;
747+
748+ const emailField = choiceWrapper . fields . find ( ( f ) => f . name === 'Email' ) ;
749+ const phoneField = choiceWrapper . fields . find ( ( f ) => f . name === 'Phone' ) ;
750+ const faxField = choiceWrapper . fields . find ( ( f ) => f . name === 'Fax' ) ;
751+
752+ expect ( emailField ) . toBeDefined ( ) ;
753+ expect ( phoneField ) . toBeDefined ( ) ;
754+ expect ( faxField ) . toBeDefined ( ) ;
755+
756+ expect ( DocumentService . isCollectionField ( emailField ! ) ) . toBeTruthy ( ) ;
757+ expect ( DocumentService . isCollectionField ( phoneField ! ) ) . toBeTruthy ( ) ;
758+ expect ( DocumentService . isCollectionField ( faxField ! ) ) . toBeTruthy ( ) ;
759+
760+ expect ( DocumentService . isFieldInsideCollectionChoiceWrapper ( emailField ! ) ) . toBeTruthy ( ) ;
761+ expect ( DocumentService . isFieldInsideCollectionChoiceWrapper ( phoneField ! ) ) . toBeTruthy ( ) ;
762+ expect ( DocumentService . isFieldInsideCollectionChoiceWrapper ( faxField ! ) ) . toBeTruthy ( ) ;
763+ } ) ;
764+
765+ it ( 'should not treat regular ChoiceElement members as collection fields' , async ( ) => {
766+ const mockApi = {
767+ getResourceContent : jest . fn ( ) . mockImplementation ( ( filePath : string ) => {
768+ if ( filePath . includes ( 'TestDocument.xsd' ) ) {
769+ return Promise . resolve (
770+ fs . readFileSync ( path . resolve ( __dirname , '../../stubs/datamapper/xml/TestDocument.xsd' ) , 'utf-8' ) ,
771+ ) ;
772+ }
773+ return Promise . reject ( new Error ( 'File not found' ) ) ;
774+ } ) ,
775+ } ;
776+
777+ const result = await DocumentService . createDocument (
778+ mockApi as unknown as IMetadataApi ,
779+ DocumentType . SOURCE_BODY ,
780+ DocumentDefinitionType . XML_SCHEMA ,
781+ 'TestDocument' ,
782+ [ 'TestDocument.xsd' ] ,
783+ ) ;
784+
785+ expect ( result . validationStatus ) . toBe ( 'success' ) ;
786+ const doc = result . document ! ;
787+
788+ const testDocument = doc . fields [ 0 ] ;
789+ const choiceElement = testDocument . fields . find ( ( f ) => f . name === 'ChoiceElement' ) ;
790+ expect ( choiceElement ) . toBeDefined ( ) ;
791+
792+ const choiceWrapper = choiceElement ! . fields [ 0 ] ;
793+ expect ( choiceWrapper . wrapperKind ) . toBe ( 'choice' ) ;
794+ expect ( choiceWrapper . maxOccurs ) . toBe ( 1 ) ;
795+ expect ( DocumentService . isCollectionField ( choiceWrapper ) ) . toBeFalsy ( ) ;
796+
797+ const choice1Field = choiceWrapper . fields . find ( ( f ) => f . name === 'Choice1' ) ;
798+ const choice2Field = choiceWrapper . fields . find ( ( f ) => f . name === 'Choice2' ) ;
799+
800+ expect ( choice1Field ) . toBeDefined ( ) ;
801+ expect ( choice2Field ) . toBeDefined ( ) ;
802+
803+ expect ( DocumentService . isCollectionField ( choice1Field ! ) ) . toBeFalsy ( ) ;
804+ expect ( DocumentService . isCollectionField ( choice2Field ! ) ) . toBeFalsy ( ) ;
805+ expect ( DocumentService . isFieldInsideCollectionChoiceWrapper ( choice1Field ! ) ) . toBeFalsy ( ) ;
806+ expect ( DocumentService . isFieldInsideCollectionChoiceWrapper ( choice2Field ! ) ) . toBeFalsy ( ) ;
807+ } ) ;
511808 } ) ;
512809
513810 describe ( 'createDocument() error handling' , ( ) => {
0 commit comments