diff --git a/get-cycle-day-number.js b/get-cycle-day-number.js index 30b56b8..75014a8 100644 --- a/get-cycle-day-number.js +++ b/get-cycle-day-number.js @@ -1,46 +1,44 @@ -export default function (cycleDays, targetDay) { - const lastFirstBleedingday = findLastFirstBleedingDay(cycleDays) +import moment from 'moment' - if (!lastFirstBleedingday) return +export default function config(opts) { + opts = opts || { + // at the very minimum, a cycle can be a bleeding day + // followed by a non-bleeding day, thus a length of 2 + minCycleLengthInDays: 2 + } - const diffInDays = targetDay.diff(lastFirstBleedingday.date, 'days') + return function getCycleDayNumber(unsorted, targetDate) { + // sort the cycle days in descending order so we travel into + // the past as we iterate over the array + const cycleDays = [...unsorted].sort((a, b) => b.date.isAfter(a.date)) - // cycle starts at day 1 - return diffInDays + 1 -} + const lastPeriodStart = cycleDays.find((day, i) => { + if ( + isBleedingDay(day) && + thereIsNoPreviousBleedingDayWithinTheThreshold(day, cycleDays.slice(i + 1), opts.minCycleLengthInDays)) { + return true + } + }) -function findLastFirstBleedingDay(cycleDays) { - // sort the cycle days in descending order - // so we travel into the past as we iterate - // over the array - cycleDays.sort((a,b) => b.date - a.date) + if (!lastPeriodStart) return null - let sawBleeding = false + // by default, moment.diff rounds down, so we get the decimal number + // and round it ourselves + const diffInDays = Math.round(targetDate.diff(lastPeriodStart.date, 'days', true)) - for (let i = 0; i < cycleDays.length; i++) { - const cycleDay = cycleDays[i] - - // we have detected the day before the beginning - // of a bleeding period, so the previous cycle day in the array - // was the first day of bleeding - if (sawBleeding && (!isBleedingDay(cycleDay))) { - return cycleDays[i - 1] - } - - // if we get to the earliest cycle day and there was - // bleeding on that day, it's the first bleeding day for us - if (i === cycleDays.length - 1 && isBleedingDay(cycleDay)) { - return cycleDay - } - - if (isBleedingDay(cycleDay)) { - sawBleeding = true - } + // cycle starts at day 1 + return diffInDays + 1 } } -function isBleedingDay(cycleDay) { - return cycleDay.bleeding - && cycleDay.bleeding.value - && !cycleDay.bleeding.exclude +function thereIsNoPreviousBleedingDayWithinTheThreshold(bleedingDay, earlierCycleDays, threshold) { + // the bleeding day itself is included in the threshold, thus we subtract 1 + const minCycleThresholdDate = moment(bleedingDay.date).subtract(threshold - 1, 'days') + return !earlierCycleDays.some(day => { + return day.date.isSameOrAfter(minCycleThresholdDate, 'day') && isBleedingDay(day) + }) +} + +function isBleedingDay(day) { + return day.bleeding && day.bleeding.value && !day.bleeding.exclude } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9cedc1c..c531614 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2766,6 +2766,12 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, + "dirty-chai": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/dirty-chai/-/dirty-chai-2.0.1.tgz", + "integrity": "sha512-ys79pWKvDMowIDEPC6Fig8d5THiC0DJ2gmTeGzVAoEH18J8OzLud0Jh7I9IWg3NSk8x2UocznUuFmfHCXYZx9w==", + "dev": true + }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", diff --git a/package.json b/package.json index aa857af..28310a9 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "devDependencies": { "babel-preset-react-native": "4.0.0", "chai": "^4.1.2", + "dirty-chai": "^2.0.1", "eslint": "^4.19.1", "eslint-plugin-react": "^7.8.2", "mocha": "^5.2.0", diff --git a/test/get-cycle-day.spec.js b/test/get-cycle-day.spec.js index a7d3a1f..32eefd2 100644 --- a/test/get-cycle-day.spec.js +++ b/test/get-cycle-day.spec.js @@ -1,10 +1,15 @@ -import { expect } from 'chai' +import chai from 'chai' +import dirtyChai from 'dirty-chai' import moment from 'moment' -import getCycleDayNumber from '../get-cycle-day-number' +const expect = chai.expect +chai.use(dirtyChai) -describe('getCycleDay returns the cycle day', () => { - it('if the last data entered is a bleeding day', function () { +import getCycleDayNumberModule from '../get-cycle-day-number' + +describe('getCycleDay', () => { + const getCycleDayNumber = getCycleDayNumberModule() + it('works if the last data entered is a bleeding day', function () { const cycleDays = [{ date: moment([2018, 5, 2]) }, { @@ -30,7 +35,7 @@ describe('getCycleDay returns the cycle day', () => { expect(result).to.eql(9) }) - it('if the last data entered is a non-bleeding day', function () { + it('works if the last data entered is a non-bleeding day', function () { const cycleDays = [{ date: moment([2018, 5, 2]) }, { @@ -90,10 +95,10 @@ describe('getCycleDay returns the cycle day', () => { const targetDate = moment([2018, 5, 17]) const result = getCycleDayNumber(cycleDays, targetDate) - expect(result).to.eql(9) + expect(result).to.eql(5) }) - it('if there are only bleeding days', function () { + it('works when there are only bleeding days', function () { const cycleDays = [{ date: moment([2018, 5, 9]), bleeding: { @@ -110,7 +115,7 @@ describe('getCycleDay returns the cycle day', () => { expect(result).to.eql(9) }) - it('if some bleedings are exluded', function () { + it('works if some bleedings are exluded', function () { const cycleDays = [{ date: moment([2018, 5, 2]) }, { @@ -139,7 +144,8 @@ describe('getCycleDay returns the cycle day', () => { }) }) -describe('getCycleDay returns undefined', () => { +describe('getCycleDay returns null', () => { + const getCycleDayNumber = getCycleDayNumberModule() it('if there are no bleeding days', function () { const cycleDays = [{ date: moment([2018, 5, 2]) @@ -152,13 +158,54 @@ describe('getCycleDay returns undefined', () => { }] const targetDate = moment([2018, 5, 17]) const result = getCycleDayNumber(cycleDays, targetDate) - expect(result).to.be.undefined + expect(result).to.be.null() }) it('if there are no cycle days', function () { const cycleDays = [] const targetDate = moment([2018, 5, 17]) const result = getCycleDayNumber(cycleDays, targetDate) - expect(result).to.be.undefined + expect(result).to.be.null() + }) +}) + +describe('getCycleDay with cycle thresholds', () => { + const getCycleDayNumber = getCycleDayNumberModule({ + minCycleLengthInDays: 5 + }) + + it('disregards bleeding breaks shorter than the min cycle threshold in a bleeding period', () => { + const cycleDays = [{ + date: moment([2018, 5, 10]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 5, 14]), + bleeding: { + value: 2 + } + }] + + const targetDate = moment([2018, 5, 17]) + const result = getCycleDayNumber(cycleDays, targetDate) + expect(result).to.eql(8) + }) + + it('counts bleeding breaks longer than the min cycle threshold in a bleeding period', () => { + const cycleDays = [{ + date: moment([2018, 5, 9]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 5, 14]), + bleeding: { + value: 2 + } + }] + const targetDate = moment([2018, 5, 17]) + const result = getCycleDayNumber(cycleDays, targetDate) + expect(result).to.eql(4) }) }) \ No newline at end of file