diff --git a/test/sympto/index.spec.js b/test/sympto/index.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..88ada8698285628f3c86607a21fbe42f7b14bd24 --- /dev/null +++ b/test/sympto/index.spec.js @@ -0,0 +1,660 @@ +import chai from 'chai' +import getSensiplanStatus from '../../lib/sympto' +import { AssertionError } from 'assert' +import { + cycleWithoutFhm, + longAndComplicatedCycle, + cycleWithTempAndNoMucusShift, + cycleWithFhm, + cycleWithoutAnyShifts, + fiveDayCycle, + cycleWithEarlyMucus, + cycleWithMucusOnFirstDay, + mucusPeakAndFhmOnSameDay, + fhmTwoDaysBeforeMucusPeak, + fhm5DaysAfterMucusPeak, + mucusPeak5DaysAfterFhm, + mucusPeakTwoDaysBeforeFhm, + fhmOnDay12, + fhmOnDay15, + mucusPeakSlightlyBeforeTempShift, + tempAndCervixEvalEndOnSameDay +} from './fixtures' + +const expect = chai.expect + +describe('sympto', () => { + describe('with no previous higher measurement', () => { + it('with no shifts detects only peri-ovulatory', function () { + const status = getSensiplanStatus({ + cycle: cycleWithoutAnyShifts, + previousCycle: cycleWithoutFhm + }) + + expect(status).to.eql({ + + phases: { + periOvulatory: { + start: { date: '2018-06-01' }, + cycleDays: cycleWithoutAnyShifts + } + }, + }) + }) + + it('with shifts detects only peri-ovulatory and post-ovulatory', () => { + const status = getSensiplanStatus({ + cycle: longAndComplicatedCycle, + previousCycle: cycleWithoutFhm + }) + + expect(status.temperatureShift).to.be.an('object') + expect(status.mucusShift).to.be.an('object') + + expect(Object.keys(status.phases).length).to.eql(2) + expect(status.phases.periOvulatory).to.eql({ + start: { date: '2018-06-01' }, + end: { date: '2018-06-21', time: '18:00' }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => date <= '2018-06-21') + }) + expect(status.phases.postOvulatory).to.eql({ + start: { + date: '2018-06-21', + time: '18:00' + }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => date >= '2018-06-21') + }) + }) + }) + describe('with previous higher measurement', () => { + describe('with no shifts detects pre-ovulatory phase', function () { + it('according to 5-day-rule', function () { + const status = getSensiplanStatus({ + cycle: fiveDayCycle, + previousCycle: cycleWithFhm + }) + + expect(Object.keys(status.phases).length).to.eql(1) + + expect(status.phases.preOvulatory).to.eql({ + cycleDays: fiveDayCycle, + start: { date: '2018-06-01' }, + end: { date: '2018-06-05' } + }) + }) + + }) + describe('with no shifts detects pre- and peri-ovulatory phase', () => { + it('according to 5-day-rule', function () { + const status = getSensiplanStatus({ + cycle: cycleWithTempAndNoMucusShift, + previousCycle: cycleWithFhm + }) + + expect(Object.keys(status.phases).length).to.eql(2) + + expect(status.phases.preOvulatory).to.eql({ + cycleDays: cycleWithTempAndNoMucusShift + .filter(({date}) => date <= '2018-06-05'), + start: { date: '2018-06-01' }, + end: { date: '2018-06-05' } + }) + expect(status.phases.periOvulatory).to.eql({ + cycleDays: cycleWithTempAndNoMucusShift + .filter(({date}) => date > '2018-06-05'), + start: { date: '2018-06-06' } + }) + }) + it('according to 5-day-rule with shortened pre-phase', function () { + const status = getSensiplanStatus({ + cycle: cycleWithEarlyMucus, + previousCycle: cycleWithFhm + }) + + expect(Object.keys(status.phases).length).to.eql(2) + + expect(status.phases.preOvulatory).to.eql({ + cycleDays: [cycleWithEarlyMucus[0]], + start: { date: '2018-06-01' }, + end: { date: '2018-06-01' } + }) + expect(status.phases.periOvulatory).to.eql({ + cycleDays: cycleWithEarlyMucus.slice(1), + start: { date: '2018-06-02' } + }) + }) + }) + describe('with shifts detects pre- and peri-ovulatory phase', function () { + it('according to 5-day-rule', function () { + const status = getSensiplanStatus({ + cycle: longAndComplicatedCycle, + previousCycle: cycleWithFhm + }) + + expect(Object.keys(status.phases).length).to.eql(3) + + expect(status.phases.preOvulatory).to.eql({ + cycleDays: longAndComplicatedCycle + .filter(({date}) => date <= '2018-06-05'), + start: { date: '2018-06-01' }, + end: { date: '2018-06-05' } + }) + expect(status.phases.periOvulatory).to.eql({ + cycleDays: longAndComplicatedCycle + .filter(({date}) => date > '2018-06-05' && date <= '2018-06-21'), + start: { date: '2018-06-06' }, + end: { date: '2018-06-21', time: '18:00'} + }) + expect(status.phases.postOvulatory).to.eql({ + cycleDays: longAndComplicatedCycle + .filter(({date}) => date >= '2018-06-21'), + start: { date: '2018-06-21', time: '18:00'} + }) + }) + + }) + }) + + describe('combining first higher measurment and mucus peak', () => { + it('with fhM + mucus peak on same day finds start of postovu phase', () => { + const status = getSensiplanStatus({ + cycle: mucusPeakAndFhmOnSameDay, + previousCycle: cycleWithFhm + }) + + expect(status.temperatureShift).to.be.an('object') + expect(status.mucusShift).to.be.an('object') + + expect(Object.keys(status.phases).length).to.eql(3) + expect(status.phases.preOvulatory).to.eql({ + start: { date: '2018-06-01' }, + end: { date: '2018-06-05' }, + cycleDays: mucusPeakAndFhmOnSameDay + .filter(({date}) => date <= '2018-06-05') + }) + expect(status.phases.periOvulatory).to.eql({ + start: { date: '2018-06-06' }, + end: { date: '2018-06-21', time: '18:00' }, + cycleDays: mucusPeakAndFhmOnSameDay + .filter(({date}) => { + return date > '2018-06-05' && date <= '2018-06-21' + }) + }) + expect(status.phases.postOvulatory).to.eql({ + start: { + date: '2018-06-21', + time: '18:00' + }, + cycleDays: mucusPeakAndFhmOnSameDay + .filter(({date}) => date >= '2018-06-21') + }) + }) + + it('with fhM 2 days before mucus peak waits for end of mucus eval', () => { + const status = getSensiplanStatus({ + cycle: fhmTwoDaysBeforeMucusPeak, + previousCycle: cycleWithFhm + }) + + expect(status.temperatureShift).to.be.an('object') + expect(status.mucusShift).to.be.an('object') + + expect(Object.keys(status.phases).length).to.eql(3) + expect(status.phases.preOvulatory).to.eql({ + start: { date: '2018-06-01' }, + end: { date: '2018-06-05' }, + cycleDays: fhmTwoDaysBeforeMucusPeak + .filter(({date}) => date <= '2018-06-05') + }) + expect(status.phases.periOvulatory).to.eql({ + start: { date: '2018-06-06' }, + end: { date: '2018-06-26', time: '18:00' }, + cycleDays: fhmTwoDaysBeforeMucusPeak + .filter(({date}) => { + return date > '2018-06-05' && date <= '2018-06-26' + }) + }) + expect(status.phases.postOvulatory).to.eql({ + start: { + date: '2018-06-26', + time: '18:00' + }, + cycleDays: fhmTwoDaysBeforeMucusPeak + .filter(({date}) => date >= '2018-06-26') + }) + }) + + it('another example for mucus peak before temp shift', () => { + const status = getSensiplanStatus({ + cycle: mucusPeakSlightlyBeforeTempShift, + previousCycle: cycleWithFhm + }) + + expect(status.temperatureShift).to.be.an('object') + expect(status.mucusShift).to.be.an('object') + + expect(Object.keys(status.phases).length).to.eql(3) + expect(status.phases.preOvulatory).to.eql({ + start: { date: '2018-06-01' }, + end: { date: '2018-06-05' }, + cycleDays: mucusPeakSlightlyBeforeTempShift + .filter(({date}) => date <= '2018-06-05') + }) + expect(status.phases.periOvulatory).to.eql({ + start: { date: '2018-06-06' }, + end: { date: '2018-06-17', time: '18:00' }, + cycleDays: mucusPeakSlightlyBeforeTempShift + .filter(({date}) => { + return date > '2018-06-05' && date <= '2018-06-17' + }) + }) + expect(status.phases.postOvulatory).to.eql({ + start: { + date: '2018-06-17', + time: '18:00' + }, + cycleDays: mucusPeakSlightlyBeforeTempShift + .filter(({date}) => date >= '2018-06-17') + }) + }) + + it('with another mucus peak 5 days after fHM ignores it', () => { + const status = getSensiplanStatus({ + cycle: mucusPeak5DaysAfterFhm, + previousCycle: cycleWithFhm + }) + + expect(status.temperatureShift).to.be.an('object') + expect(status.mucusShift).to.be.an('object') + + expect(Object.keys(status.phases).length).to.eql(3) + expect(status.phases.preOvulatory).to.eql({ + start: { date: '2018-06-01' }, + end: { date: '2018-06-01' }, + cycleDays: mucusPeak5DaysAfterFhm + .filter(({date}) => date <= '2018-06-01') + }) + expect(status.phases.periOvulatory).to.eql({ + start: { date: '2018-06-02' }, + end: { date: '2018-06-22', time: '18:00' }, + cycleDays: mucusPeak5DaysAfterFhm + .filter(({date}) => { + return date > '2018-06-01' && date <= '2018-06-22' + }) + }) + expect(status.phases.postOvulatory).to.eql({ + start: { + date: '2018-06-22', + time: '18:00' + }, + cycleDays: mucusPeak5DaysAfterFhm + .filter(({date}) => date >= '2018-06-22') + }) + }) + + it('with mucus peak 2 days before fhM waits for end of temp eval', () => { + const status = getSensiplanStatus({ + cycle: mucusPeakTwoDaysBeforeFhm, + previousCycle: cycleWithFhm + }) + + expect(status.temperatureShift).to.be.an('object') + expect(status.mucusShift).to.be.an('object') + + expect(Object.keys(status.phases).length).to.eql(3) + expect(status.phases.preOvulatory).to.eql({ + start: { date: '2018-06-01' }, + end: { date: '2018-06-04' }, + cycleDays: mucusPeakTwoDaysBeforeFhm + .filter(({date}) => date <= '2018-06-04') + }) + expect(status.phases.periOvulatory).to.eql({ + start: { date: '2018-06-05' }, + end: { date: '2018-07-03', time: '18:00' }, + cycleDays: mucusPeakTwoDaysBeforeFhm + .filter(({date}) => { + return date > '2018-06-04' && date <= '2018-07-03' + }) + }) + expect(status.phases.postOvulatory).to.eql({ + start: { + date: '2018-07-03', + time: '18:00' + }, + cycleDays: mucusPeakTwoDaysBeforeFhm + .filter(({date}) => date >= '2018-07-03') + }) + }) + + it('with mucus peak 5 days before fhM waits for end of temp eval', () => { + const status = getSensiplanStatus({ + cycle: fhm5DaysAfterMucusPeak, + previousCycle: cycleWithFhm + }) + + expect(status.temperatureShift).to.be.an('object') + expect(status.mucusShift).to.be.an('object') + + expect(Object.keys(status.phases).length).to.eql(3) + expect(status.phases.preOvulatory).to.eql({ + start: { date: '2018-06-01' }, + end: { date: '2018-06-05' }, + cycleDays: fhm5DaysAfterMucusPeak + .filter(({date}) => date <= '2018-06-05') + }) + expect(status.phases.periOvulatory).to.eql({ + start: { date: '2018-06-06' }, + end: { date: '2018-06-21', time: '18:00' }, + cycleDays: fhm5DaysAfterMucusPeak + .filter(({date}) => { + return date > '2018-06-05' && date <= '2018-06-21' + }) + }) + expect(status.phases.postOvulatory).to.eql({ + start: { + date: '2018-06-21', + time: '18:00' + }, + cycleDays: fhm5DaysAfterMucusPeak + .filter(({date}) => date >= '2018-06-21') + }) + }) + }) + + describe('combining temperature and cervix measurment', () => { + it('with evaluation of temperature and cervix end on same day', () => { + const status = getSensiplanStatus({ + cycle: tempAndCervixEvalEndOnSameDay, + previousCycle: cycleWithFhm, + secondarySymptom: 'cervix' + }) + expect(Object.keys(status.phases).length).to.eql(3) + expect(status.phases.preOvulatory).to.eql({ + start: { date: '2018-06-01' }, + end: { date: '2018-06-05' }, + cycleDays: tempAndCervixEvalEndOnSameDay + .filter(({date}) => date <= '2018-06-05') + }) + expect(status.phases.periOvulatory).to.eql({ + start: { date: '2018-06-06' }, + end: { date: '2018-06-17', time: '18:00' }, + cycleDays: tempAndCervixEvalEndOnSameDay + .filter(({date}) => { + return date > '2018-06-05' && date <= '2018-06-17' + }) + }) + expect(status.phases.postOvulatory).to.eql({ + start: { date: '2018-06-17', time: '18:00' }, + cycleDays: tempAndCervixEvalEndOnSameDay + .filter(({date}) => date >= '2018-06-17') + }) + expect(status.cervixShift.detected).to.be.true() + expect(status.cervixShift.cervixPeak.date).to.eql('2018-06-14') + expect(status.cervixShift.evaluationCompleteDay.date).to.eql('2018-06-17') + + }) + }) + + describe('applying the minus-8 rule', () => { + it('shortens the pre-ovu phase if there is a previous <13 fhm', () => { + const status = getSensiplanStatus({ + cycle: longAndComplicatedCycle, + previousCycle: fhmOnDay15, + earlierCycles: [fhmOnDay12, ...Array(10).fill(fhmOnDay15)] + }) + + expect(status.temperatureShift).to.be.an('object') + expect(status.mucusShift).to.be.an('object') + + expect(Object.keys(status.phases).length).to.eql(3) + expect(status.phases.preOvulatory).to.eql({ + start: { date: '2018-06-01' }, + end: { date: '2018-06-04' }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => date <= '2018-06-04') + }) + expect(status.phases.periOvulatory).to.eql({ + start: { date: '2018-06-05' }, + end: { date: '2018-06-21', time: '18:00' }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => { + return date > '2018-06-04' && date <= '2018-06-21' + }) + }) + expect(status.phases.postOvulatory).to.eql({ + start: { + date: '2018-06-21', + time: '18:00' + }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => date >= '2018-06-21') + }) + }) + it('shortens pre-ovu phase with prev <13 fhm even with <12 cycles', () => { + const status = getSensiplanStatus({ + cycle: longAndComplicatedCycle, + previousCycle: fhmOnDay12, + earlierCycles: Array(10).fill(fhmOnDay12) + }) + + expect(status.temperatureShift).to.be.an('object') + expect(status.mucusShift).to.be.an('object') + + expect(Object.keys(status.phases).length).to.eql(3) + expect(status.phases.preOvulatory).to.eql({ + start: { date: '2018-06-01' }, + end: { date: '2018-06-04' }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => date <= '2018-06-04') + }) + expect(status.phases.periOvulatory).to.eql({ + start: { date: '2018-06-05' }, + end: { date: '2018-06-21', time: '18:00' }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => { + return date > '2018-06-04' && date <= '2018-06-21' + }) + }) + expect(status.phases.postOvulatory).to.eql({ + start: { + date: '2018-06-21', + time: '18:00' + }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => date >= '2018-06-21') + }) + }) + it('shortens the pre-ovu phase if mucus occurs', () => { + const status = getSensiplanStatus({ + cycle: cycleWithEarlyMucus, + previousCycle: fhmOnDay12, + earlierCycles: Array(10).fill(fhmOnDay12) + }) + + + expect(Object.keys(status.phases).length).to.eql(2) + expect(status.phases.preOvulatory).to.eql({ + start: { date: '2018-06-01' }, + end: { date: '2018-06-01' }, + cycleDays: cycleWithEarlyMucus + .filter(({date}) => date <= '2018-06-01') + }) + expect(status.phases.periOvulatory).to.eql({ + start: { date: '2018-06-02' }, + cycleDays: cycleWithEarlyMucus + .filter(({date}) => { + return date > '2018-06-01' + }) + }) + }) + + it('shortens the pre-ovu phase if mucus occurs even on the first day', () => { + const status = getSensiplanStatus({ + cycle: cycleWithMucusOnFirstDay, + previousCycle: fhmOnDay12, + earlierCycles: Array(10).fill(fhmOnDay12) + }) + + + expect(Object.keys(status.phases).length).to.eql(1) + + expect(status.phases.periOvulatory).to.eql({ + start: { date: '2018-06-01' }, + cycleDays: cycleWithMucusOnFirstDay + }) + }) + + it('lengthens the pre-ovu phase if >= 12 cycles with fhm > 13', () => { + const status = getSensiplanStatus({ + cycle: longAndComplicatedCycle, + previousCycle: fhmOnDay15, + earlierCycles: Array(11).fill(fhmOnDay15) + }) + + + expect(Object.keys(status.phases).length).to.eql(3) + expect(status.phases.preOvulatory).to.eql({ + start: { date: '2018-06-01' }, + end: { date: '2018-06-07' }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => date <= '2018-06-07') + }) + expect(status.phases.periOvulatory).to.eql({ + start: { date: '2018-06-08' }, + end: { date: '2018-06-21', time: '18:00' }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => { + return date > '2018-06-07' && date <= '2018-06-21' + }) + }) + expect(status.phases.postOvulatory).to.eql({ + start: { + date: '2018-06-21', + time: '18:00' + }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => date >= '2018-06-21') + }) + }) + + it('does not lengthen the pre-ovu phase if < 12 cycles', () => { + const status = getSensiplanStatus({ + cycle: longAndComplicatedCycle, + previousCycle: fhmOnDay15, + earlierCycles: Array(10).fill(fhmOnDay15) + }) + + + expect(Object.keys(status.phases).length).to.eql(3) + expect(status.phases.preOvulatory).to.eql({ + start: { date: '2018-06-01' }, + end: { date: '2018-06-05' }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => date <= '2018-06-05') + }) + expect(status.phases.periOvulatory).to.eql({ + start: { date: '2018-06-06' }, + end: { date: '2018-06-21', time: '18:00' }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => { + return date > '2018-06-05' && date <= '2018-06-21' + }) + }) + expect(status.phases.postOvulatory).to.eql({ + start: { + date: '2018-06-21', + time: '18:00' + }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => date >= '2018-06-21') + }) + }) + + it('does not detect any pre-ovu phase if prev cycle had no fhm', () => { + const status = getSensiplanStatus({ + cycle: longAndComplicatedCycle, + previousCycle: cycleWithoutFhm, + earlierCycles: [...Array(12).fill(fhmOnDay15)] + }) + + + expect(Object.keys(status.phases).length).to.eql(2) + expect(status.phases.periOvulatory).to.eql({ + start: { date: '2018-06-01' }, + end: { date: '2018-06-21', time: '18:00' }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => { + return date >= '2018-06-01' && date <= '2018-06-21' + }) + }) + expect(status.phases.postOvulatory).to.eql({ + start: { + date: '2018-06-21', + time: '18:00' + }, + cycleDays: longAndComplicatedCycle + .filter(({date}) => date >= '2018-06-21') + }) + }) + }) + + describe('when args are wrong', () => { + it('throws when arg object is not in right format', () => { + const wrongObject = { hello: 'world' } + expect(() => getSensiplanStatus(wrongObject)).to.throw(AssertionError) + }) + it('throws if cycle array is empty', () => { + expect(() => getSensiplanStatus({cycle: []})).to.throw(AssertionError) + }) + it('throws if cycle days are not in right format', () => { + expect(() => getSensiplanStatus({ + cycle: [{ + hello: 'world', + bleeding: { value: 0 } + }], + earlierCycles: [[{ + date: '1992-09-09', + bleeding: { value: 0 } + }]] + })).to.throw(AssertionError) + expect(() => getSensiplanStatus({ + cycle: [{ + date: '2018-04-13', + temperature: {value: '35'}, + bleeding: { value: 0 } + }], + earlierCycles: [[{ + date: '1992-09-09', + bleeding: { value: 0 } + }]] + })).to.throw(AssertionError) + expect(() => getSensiplanStatus({ + cycle: [{ + date: '09-14-2017', + bleeding: { value: 0 } + }], + earlierCycles: [[{ + date: '1992-09-09', + bleeding: { value: 0 } + }]] + })).to.throw(AssertionError) + }) + it('throws if first cycle day does not have bleeding value', () => { + expect(() => getSensiplanStatus({ + cycle: [{ + date: '2017-01-01', + bleeding: { + value: 'medium' + } + }], + earlierCycles: [[ + { + date: '2017-09-23', + } + ]] + })).to.throw(AssertionError) + }) + }) +}) \ No newline at end of file diff --git a/test/sympto/mucus-temp-fixtures.js b/test/sympto/mucus-temp-fixtures.js index c313629c3e8af2a5454921d3e2ee5b0a575f990a..705e6dc55c7e32286459d9a52c9e7acb1aa08e7f 100644 --- a/test/sympto/mucus-temp-fixtures.js +++ b/test/sympto/mucus-temp-fixtures.js @@ -4,6 +4,14 @@ function convertToSymptoFormat(val) { if (val.temperature) sympto.temperature = { value: val.temperature } if (val.mucus) sympto.mucus = { value: val.mucus } if (val.bleeding) sympto.bleeding = { value: val.bleeding } + if (val.cervix) { + sympto.cervix = {} + if (val.cervix === 'firm & closed') { + sympto.cervix.firmAndClosed = true + } else { + sympto.cervix.firmAndClosed = false + } + } return sympto }