All files / platform/core/src/utils updateNewInstanceMetadata.test.js

0% Statements 0/55
100% Branches 0/0
0% Functions 0/15
0% Lines 0/55

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156                                                                                                                                                                                                                                                                                                                       
import { getCurrentDicomDateTime, updateNewInstanceMetadata } from './updateNewInstanceMetadata';
import { getLatestInstanceDateTime } from './latestInstanceDateTime';
 
describe('getCurrentDicomDateTime', () => {
  // Built with the local constructor, because a DICOM DA/TM pair with no
  // timezone is the local wall clock reading of the instant.
  test('formats a date as DICOM DA and TM values', () => {
    expect(getCurrentDicomDateTime(new Date(2026, 7, 9, 4, 5, 6, 70))).toEqual({
      date: '20260809',
      time: '040506.070000',
    });
  });
 
  test('reads the local wall clock whatever the zone the runner is in', () => {
    const now = new Date(2026, 7, 9, 23, 30, 0, 0);
 
    expect(getCurrentDicomDateTime(now)).toEqual({
      date: '20260809',
      time: '233000.000000',
    });
  });
 
  // DA and TM say nothing about the zone they were read in, so an object that
  // declares one has to be stamped in that zone rather than the local one.
  test('reads the timezone the object declares when it has one', () => {
    const noon = new Date(Date.UTC(2026, 7, 9, 12, 0, 0, 0));
 
    expect(getCurrentDicomDateTime(noon, '+0000')).toEqual({
      date: '20260809',
      time: '120000.000000',
    });
    expect(getCurrentDicomDateTime(noon, '-0500')).toEqual({
      date: '20260809',
      time: '070000.000000',
    });
    expect(getCurrentDicomDateTime(noon, '+0930')).toEqual({
      date: '20260809',
      time: '213000.000000',
    });
  });
 
  test('crosses the day boundary in the declared timezone', () => {
    const justAfterMidnightUTC = new Date(Date.UTC(2026, 7, 9, 0, 30, 0, 0));
 
    // Half past midnight in UTC is still the previous evening in New York.
    expect(getCurrentDicomDateTime(justAfterMidnightUTC, '-0500')).toEqual({
      date: '20260808',
      time: '193000.000000',
    });
  });
 
  test('falls back to the local zone for a malformed offset', () => {
    const now = new Date(2026, 7, 9, 4, 5, 6, 70);
 
    expect(getCurrentDicomDateTime(now, 'not an offset')).toEqual(getCurrentDicomDateTime(now));
    expect(getCurrentDicomDateTime(now, '')).toEqual(getCurrentDicomDateTime(now));
  });
});
 
describe('updateNewInstanceMetadata', () => {
  const series = { StudyInstanceUID: '1.2.3', SeriesInstanceUID: '1.2.3.4' };
 
  test('numbers the instance above every instance already in the series', () => {
    // The most recently created instance of a series is not necessarily the one
    // with the highest instance number, so `1 +` a single predecessor is not
    // enough to stay above the rest of the series.
    const priorInstances = [{ InstanceNumber: 1 }, { InstanceNumber: 7 }, { InstanceNumber: 3 }];
 
    expect(updateNewInstanceMetadata({ ...series }, priorInstances).InstanceNumber).toBe(8);
  });
 
  test('numbers the first instance of a new series 1', () => {
    expect(updateNewInstanceMetadata({ ...series }, []).InstanceNumber).toBe(1);
  });
 
  test('ignores instances with no usable instance number', () => {
    const priorInstances = [{ InstanceNumber: undefined }, { InstanceNumber: '2' }, null];
 
    expect(updateNewInstanceMetadata({ ...series }, priorInstances).InstanceNumber).toBe(3);
  });
 
  // The series date and time of an instance saved into an existing series
  // belong to that series, so the instance level date and time are the only
  // ones that place it as the most recent instance.
  test('stamps an instance date and time later than the series one', () => {
    const dataset = updateNewInstanceMetadata(
      { ...series, SeriesDate: '20200101', SeriesTime: '090000' },
      []
    );
 
    expect(dataset.ContentDate).toBe(dataset.InstanceCreationDate);
    expect(dataset.ContentTime).toBe(dataset.InstanceCreationTime);
    expect(getLatestInstanceDateTime(dataset)).toEqual({
      SeriesDate: dataset.ContentDate,
      SeriesTime: dataset.ContentTime,
    });
  });
 
  // The Segmentation and SR IODs both define ContentDate/ContentTime, as does
  // the General Image module an ordinary image series is built from, so the
  // default pair is the right one for all three.
  test.each([['SEG'], ['SR'], ['CT'], [undefined]])(
    'stamps the content date and time of a %s instance',
    Modality => {
      const dataset = updateNewInstanceMetadata({ ...series, Modality }, []);
 
      expect(dataset.ContentDate).toBe(dataset.InstanceCreationDate);
      expect(dataset.ContentTime).toBe(dataset.InstanceCreationTime);
      expect(dataset.StructureSetDate).toBeUndefined();
    }
  );
 
  // The RTSTRUCT IOD defines neither ContentDate nor ContentTime - the
  // Structure Set module carries StructureSetDate/StructureSetTime instead - so
  // stamping a content date/time writes an attribute a strict validator or
  // archive rejects the instance for.
  test('stamps the structure set date and time of an RTSTRUCT instance', () => {
    const dataset = updateNewInstanceMetadata({ ...series, Modality: 'RTSTRUCT' }, []);
 
    expect(dataset.StructureSetDate).toBe(dataset.InstanceCreationDate);
    expect(dataset.StructureSetTime).toBe(dataset.InstanceCreationTime);
    expect(dataset.ContentDate).toBeUndefined();
    expect(dataset.ContentTime).toBeUndefined();
    expect(getLatestInstanceDateTime(dataset)).toEqual({
      SeriesDate: dataset.StructureSetDate,
      SeriesTime: dataset.StructureSetTime,
    });
  });
 
  // The Presentation State Identification module is the same story, with
  // PresentationCreationDate/PresentationCreationTime.
  test('stamps the presentation creation date and time of a PR instance', () => {
    const dataset = updateNewInstanceMetadata({ ...series, Modality: 'PR' }, []);
 
    expect(dataset.PresentationCreationDate).toBe(dataset.InstanceCreationDate);
    expect(dataset.PresentationCreationTime).toBe(dataset.InstanceCreationTime);
    expect(dataset.ContentDate).toBeUndefined();
    expect(getLatestInstanceDateTime(dataset)).toEqual({
      SeriesDate: dataset.PresentationCreationDate,
      SeriesTime: dataset.PresentationCreationTime,
    });
  });
 
  // The stamp has to be on the same clock as the rest of the object, or the
  // date/time shown for it is off by the offset between the two zones.
  test('stamps in the timezone the dataset declares', () => {
    const now = new Date();
    const dataset = updateNewInstanceMetadata({ ...series, TimezoneOffsetFromUTC: '+0930' }, []);
 
    expect({ date: dataset.ContentDate, time: dataset.ContentTime.slice(0, 4) }).toEqual({
      date: getCurrentDicomDateTime(now, '+0930').date,
      time: getCurrentDicomDateTime(now, '+0930').time.slice(0, 4),
    });
  });
});