Repository: FHIR/sushi (ts)

Issue:
Error Populating Slices on Observation.component
The CancerGeneticVariant profile needs to slice Observation.component. SUSHI seems to be OK with populating the slices in-line, but the IG Publisher crashes with this message: 

```
Caused by: org.hl7.fhir.exceptions.DefinitionException: Attempt to a slice an element that does not repeat: Observation.component.code/Observation.component.code from http://hl7.org/fhir/us/core/StructureDefinition/us-core-observation-lab in http://hl7.org/fhir/us/mcode/StructureDefinition/CancerGeneticVariant
```

The offending lines are currently commented out so we can build the IG, but look for these lines in _CancerGenomics.fsh_:
```
* component[GeneStudied] ^short = "Gene studied [ID]"
* component[GeneStudied].code = LNC#48018-6
* component[GeneStudied].value[x] only CodeableConcept
* component[GeneStudied].valueCodeableConcept 1..1
* component[GeneStudied].valueCodeableConcept from http://www.genenames.org/geneId (extensible)
* component[VariationCode] ^short = "Discrete genetic variant"
* component[VariationCode].code = LNC#81252-9
* component[VariationCode].value[x] only CodeableConcept
etc.
```


Pull request description:
Fix addElement to put element in proper place
In cases where the element id is a substring of another element id (but the same number of parts), it was possible for the element to be placed in the wrong location (after the element with the id its own id is a substring of).  This fixes that.

Fixes #122

Patch:
diff --git a/src/fhirtypes/StructureDefinition.ts b/src/fhirtypes/StructureDefinition.ts
index 4745527..31eac01 100644
--- a/src/fhirtypes/StructureDefinition.ts
+++ b/src/fhirtypes/StructureDefinition.ts
@@ -119,7 +119,10 @@ export class StructureDefinition {
       const currentId = this.elements[i].id;
       if (element.id.startsWith(`${currentId}.`) || element.id.startsWith(`${currentId}:`)) {
         lastMatchId = currentId;
-      } else if (!currentId.startsWith(lastMatchId)) {
+      } else if (
+        !currentId.startsWith(`${lastMatchId}.`) &&
+        !currentId.startsWith(`${lastMatchId}:`)
+      ) {
         break;
       }
     }


Test patch:
diff --git a/test/fhirtypes/ElementDefinition.test.ts b/test/fhirtypes/ElementDefinition.test.ts
index 97108ca..1c7e485 100644
--- a/test/fhirtypes/ElementDefinition.test.ts
+++ b/test/fhirtypes/ElementDefinition.test.ts
@@ -363,6 +363,112 @@ describe('ElementDefinition', () => {
       );
     });
 
+    it('should add children in the right location when one slicename is a substring of another', () => {
+      // Tests bug reported here: https://github.com/FHIR/sushi/issues/122
+      const numOriginalElements = observation.elements.length;
+      const component = observation.elements.find(e => e.path === 'Observation.component');
+      component.slicing = {
+        ordered: false,
+        rules: 'open',
+        discriminator: [{ type: 'value', path: 'code' }]
+      };
+      // Set up the slices first
+      const fooSlice = component.addSlice('FooSlice');
+      const fooSliceBar = component.addSlice('FooSliceBar');
+      // Then unfold them
+      const newFooSliceElements = fooSlice.unfold(fisher);
+      expect(newFooSliceElements).toHaveLength(8);
+      expect(newFooSliceElements[0].id).toBe('Observation.component:FooSlice.id');
+      expect(newFooSliceElements[1].id).toBe('Observation.component:FooSlice.extension');
+      expect(newFooSliceElements[2].id).toBe('Observation.component:FooSlice.modifierExtension');
+      expect(newFooSliceElements[3].id).toBe('Observation.component:FooSlice.code');
+      expect(newFooSliceElements[4].id).toBe('Observation.component:FooSlice.value[x]');
+      expect(newFooSliceElements[5].id).toBe('Observation.component:FooSlice.dataAbsentReason');
+      expect(newFooSliceElements[6].id).toBe('Observation.component:FooSlice.interpretation');
+      expect(newFooSliceElements[7].id).toBe('Observation.component:FooSlice.referenceRange');
+      const newFooSliceBarElements = fooSliceBar.unfold(fisher);
+      expect(newFooSliceBarElements).toHaveLength(8);
+      expect(newFooSliceBarElements[0].id).toBe('Observation.component:FooSliceBar.id');
+      expect(newFooSliceBarElements[1].id).toBe('Observation.component:FooSliceBar.extension');
+      expect(newFooSliceBarElements[2].id).toBe(
+        'Observation.component:FooSliceBar.modifierExtension'
+      );
+      expect(newFooSliceBarElements[3].id).toBe('Observation.component:FooSliceBar.code');
+      expect(newFooSliceBarElements[4].id).toBe('Observation.component:FooSliceBar.value[x]');
+      expect(newFooSliceBarElements[5].id).toBe(
+        'Observation.component:FooSliceBar.dataAbsentReason'
+      );
+      expect(newFooSliceBarElements[6].id).toBe('Observation.component:FooSliceBar.interpretation');
+      expect(newFooSliceBarElements[7].id).toBe('Observation.component:FooSliceBar.referenceRange');
+      expect(observation.elements).toHaveLength(numOriginalElements + 18);
+      // Then check they are in right order, starting with plain old Observation.component
+      const componentIdx = observation.elements.findIndex(e => e.id === 'Observation.component');
+      expect(observation.elements[componentIdx + 1].id).toBe('Observation.component.id');
+      expect(observation.elements[componentIdx + 2].id).toBe('Observation.component.extension');
+      expect(observation.elements[componentIdx + 3].id).toBe(
+        'Observation.component.modifierExtension'
+      );
+      expect(observation.elements[componentIdx + 4].id).toBe('Observation.component.code');
+      expect(observation.elements[componentIdx + 5].id).toBe('Observation.component.value[x]');
+      expect(observation.elements[componentIdx + 6].id).toBe(
+        'Observation.component.dataAbsentReason'
+      );
+      expect(observation.elements[componentIdx + 7].id).toBe(
+        'Observation.component.interpretation'
+      );
+      expect(observation.elements[componentIdx + 8].id).toBe(
+        'Observation.component.referenceRange'
+      );
+      expect(observation.elements[componentIdx + 9].id).toBe('Observation.component:FooSlice');
+      expect(observation.elements[componentIdx + 10].id).toBe('Observation.component:FooSlice.id');
+      expect(observation.elements[componentIdx + 11].id).toBe(
+        'Observation.component:FooSlice.extension'
+      );
+      expect(observation.elements[componentIdx + 12].id).toBe(
+        'Observation.component:FooSlice.modifierExtension'
+      );
+      expect(observation.elements[componentIdx + 13].id).toBe(
+        'Observation.component:FooSlice.code'
+      );
+      expect(observation.elements[componentIdx + 14].id).toBe(
+        'Observation.component:FooSlice.value[x]'
+      );
+      expect(observation.elements[componentIdx + 15].id).toBe(
+        'Observation.component:FooSlice.dataAbsentReason'
+      );
+      expect(observation.elements[componentIdx + 16].id).toBe(
+        'Observation.component:FooSlice.interpretation'
+      );
+      expect(observation.elements[componentIdx + 17].id).toBe(
+        'Observation.component:FooSlice.referenceRange'
+      );
+      expect(observation.elements[componentIdx + 18].id).toBe('Observation.component:FooSliceBar');
+      expect(observation.elements[componentIdx + 19].id).toBe(
+        'Observation.component:FooSliceBar.id'
+      );
+      expect(observation.elements[componentIdx + 20].id).toBe(
+        'Observation.component:FooSliceBar.extension'
+      );
+      expect(observation.elements[componentIdx + 21].id).toBe(
+        'Observation.component:FooSliceBar.modifierExtension'
+      );
+      expect(observation.elements[componentIdx + 22].id).toBe(
+        'Observation.component:FooSliceBar.code'
+      );
+      expect(observation.elements[componentIdx + 23].id).toBe(
+        'Observation.component:FooSliceBar.value[x]'
+      );
+      expect(observation.elements[componentIdx + 24].id).toBe(
+        'Observation.component:FooSliceBar.dataAbsentReason'
+      );
+      expect(observation.elements[componentIdx + 25].id).toBe(
+        'Observation.component:FooSliceBar.interpretation'
+      );
+      expect(observation.elements[componentIdx + 26].id).toBe(
+        'Observation.component:FooSliceBar.referenceRange'
+      );
+    });
+
     it('should not add any children when an element has multiple types', () => {
       const numOriginalElements = observation.elements.length;
       const valueIdx = observation.elements.findIndex(e => e.path === 'Observation.value[x]');
diff --git a/test/fhirtypes/StructureDefinition.test.ts b/test/fhirtypes/StructureDefinition.test.ts
index 57deeeb..905a683 100644
--- a/test/fhirtypes/StructureDefinition.test.ts
+++ b/test/fhirtypes/StructureDefinition.test.ts
@@ -139,6 +139,27 @@ describe('StructureDefinition', () => {
       expect(observation.elements[3].id).toBe('Observation.meta.id');
     });
 
+    it('should add an element in the right place even with substrings involved', () => {
+      // Tests bug reported here: https://github.com/FHIR/sushi/issues/122
+      observation.addElement(new ElementDefinition('Observation.component:FooBefore'));
+      observation.addElement(new ElementDefinition('Observation.component:Foo'));
+      observation.addElement(new ElementDefinition('Observation.component:FooAfter'));
+      observation.addElement(new ElementDefinition('Observation.component:Foo.id'));
+      observation.addElement(new ElementDefinition('Observation.component:FooAfter.id'));
+      observation.addElement(new ElementDefinition('Observation.component:FooBefore.id'));
+      expect(observation.elements).toHaveLength(56);
+      const getIdx = (id: string) => {
+        return observation.elements.findIndex(e => e.id === id);
+      };
+      expect(getIdx('Observation.component')).toBe(41);
+      expect(getIdx('Observation.component:FooBefore')).toBe(50);
+      expect(getIdx('Observation.component:FooBefore.id')).toBe(51);
+      expect(getIdx('Observation.component:Foo')).toBe(52);
+      expect(getIdx('Observation.component:Foo.id')).toBe(53);
+      expect(getIdx('Observation.component:FooAfter')).toBe(54);
+      expect(getIdx('Observation.component:FooAfter.id')).toBe(55);
+    });
+
     it('should add explicit choice element in the right place', () => {
       observation.addElement(new ElementDefinition('Observation.value[x]:valueQuantity'));
       expect(observation.elements).toHaveLength(51);