From 3b2beabb9e84c1ffd7d025de1deb3aacdbf1c9bf Mon Sep 17 00:00:00 2001 From: Julia Friesel Date: Mon, 4 Jun 2018 13:12:14 +0200 Subject: [PATCH 01/12] Get cycle day number for several happy paths --- get-cycle-day-number.js | 46 +++++++++++++ test/app.spec.js | 5 -- test/get-cycle-day.spec.js | 136 +++++++++++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 get-cycle-day-number.js delete mode 100644 test/app.spec.js create mode 100644 test/get-cycle-day.spec.js diff --git a/get-cycle-day-number.js b/get-cycle-day-number.js new file mode 100644 index 0000000..ff07b0c --- /dev/null +++ b/get-cycle-day-number.js @@ -0,0 +1,46 @@ +export default function (cycleDays, targetDay) { + const lastFirstBleedingday = findLastFirstBleedingDay(cycleDays) + + if (!lastFirstBleedingday) return + + const diffInDays = targetDay.diff(lastFirstBleedingday.date, 'days') + + // cycle starts at day 1 + return diffInDays + 1 +} + +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) + + let sawBleeding = false + + 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 + } + } +} + +function isBleedingDay(cycleDay) { + return cycleDay.bleeding + && cycleDay.bleeding.value + && !cycleDay.exclude +} \ No newline at end of file diff --git a/test/app.spec.js b/test/app.spec.js deleted file mode 100644 index 019e3fa..0000000 --- a/test/app.spec.js +++ /dev/null @@ -1,5 +0,0 @@ -import { expect } from 'chai' - -it('says hello world', function () { - expect(1 + 5).to.eql(6) -}) diff --git a/test/get-cycle-day.spec.js b/test/get-cycle-day.spec.js new file mode 100644 index 0000000..a5572fd --- /dev/null +++ b/test/get-cycle-day.spec.js @@ -0,0 +1,136 @@ +import { expect } from 'chai' +import moment from 'moment' + +import getCycleDayNumber from '../get-cycle-day-number' + +describe('getCycleDay returns the cycle day', () => { + it('if the last data entered is a bleeding day', function () { + const cycleDays = [{ + date: moment([2018, 5, 2]) + }, { + date: moment([2018, 5, 3]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 5, 4]) + }, { + date: moment([2018, 5, 9]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 5, 10]), + bleeding: { + value: 2 + } + }] + const targetDate = moment([2018, 5, 17]) + const result = getCycleDayNumber(cycleDays, targetDate) + expect(result).to.eql(9) + }) + + it('if the last data entered is a non-bleeding day', function () { + const cycleDays = [{ + date: moment([2018, 5, 2]) + }, { + date: moment([2018, 5, 3]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 5, 4]) + }, { + date: moment([2018, 5, 9]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 5, 10]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 5, 13]) + }, { + date: moment([2018, 5, 14]) + }] + + const targetDate = moment([2018, 5, 17]) + const result = getCycleDayNumber(cycleDays, targetDate) + expect(result).to.eql(9) + }) + + it('works if the cycle days are not sorted by date', function () { + const cycleDays = [{ + date: moment([2018, 5, 13]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 5, 9]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 5, 3]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 5, 4]) + }, { + date: moment([2018, 5, 10]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 5, 2]) + }] + + const targetDate = moment([2018, 5, 17]) + const result = getCycleDayNumber(cycleDays, targetDate) + expect(result).to.eql(9) + }) + + it('if there are only bleeding days', function () { + const cycleDays = [{ + date: moment([2018, 5, 9]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 5, 10]), + bleeding: { + value: 2 + } + }] + const targetDate = moment([2018, 5, 17]) + const result = getCycleDayNumber(cycleDays, targetDate) + expect(result).to.eql(9) + }) +}) + +describe('getCycleDay returns undefined', () => { + it('if there are no bleeding days', function () { + const cycleDays = [{ + date: moment([2018, 5, 2]) + }, { + date: moment([2018, 5, 4]) + }, { + date: moment([2018, 5, 9]), + }, { + date: moment([2018, 5, 10]), + }] + const targetDate = moment([2018, 5, 17]) + const result = getCycleDayNumber(cycleDays, targetDate) + expect(result).to.be.undefined + }) + + 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 + }) +}) \ No newline at end of file From 2b8569d6d079c5b9dc90948d8674dfdccff160c9 Mon Sep 17 00:00:00 2001 From: Julia Friesel Date: Mon, 4 Jun 2018 15:08:10 +0200 Subject: [PATCH 02/12] Exclude excluded bleedings --- get-cycle-day-number.js | 2 +- test/get-cycle-day.spec.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/get-cycle-day-number.js b/get-cycle-day-number.js index ff07b0c..30b56b8 100644 --- a/get-cycle-day-number.js +++ b/get-cycle-day-number.js @@ -42,5 +42,5 @@ function findLastFirstBleedingDay(cycleDays) { function isBleedingDay(cycleDay) { return cycleDay.bleeding && cycleDay.bleeding.value - && !cycleDay.exclude + && !cycleDay.bleeding.exclude } \ No newline at end of file diff --git a/test/get-cycle-day.spec.js b/test/get-cycle-day.spec.js index a5572fd..a7d3a1f 100644 --- a/test/get-cycle-day.spec.js +++ b/test/get-cycle-day.spec.js @@ -109,6 +109,34 @@ describe('getCycleDay returns the cycle day', () => { const result = getCycleDayNumber(cycleDays, targetDate) expect(result).to.eql(9) }) + + it('if some bleedings are exluded', function () { + const cycleDays = [{ + date: moment([2018, 5, 2]) + }, { + date: moment([2018, 5, 3]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 5, 4]) + }, { + date: moment([2018, 5, 9]), + bleeding: { + value: 2, + exclude: true + } + }, { + date: moment([2018, 5, 10]), + bleeding: { + value: 2, + exclude: true + } + }] + const targetDate = moment([2018, 5, 17]) + const result = getCycleDayNumber(cycleDays, targetDate) + expect(result).to.eql(15) + }) }) describe('getCycleDay returns undefined', () => { From 1cf408703945fa595c76cf5ade69a7644cc9d586 Mon Sep 17 00:00:00 2001 From: Julia Friesel Date: Tue, 5 Jun 2018 11:00:21 +0200 Subject: [PATCH 03/12] Add option for minimum cycle length --- get-cycle-day-number.js | 70 ++++++++++++++++++-------------------- package-lock.json | 6 ++++ package.json | 1 + test/get-cycle-day.spec.js | 69 +++++++++++++++++++++++++++++++------ 4 files changed, 99 insertions(+), 47 deletions(-) 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 From 3bc85d74fc9aee8917ffc337604b89269ab054d3 Mon Sep 17 00:00:00 2001 From: Julia Friesel Date: Thu, 7 Jun 2018 11:14:07 +0200 Subject: [PATCH 04/12] Get cycle day for day in any cycle, not just the current --- get-cycle-day-number.js | 6 +++++- test/get-cycle-day.spec.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/get-cycle-day-number.js b/get-cycle-day-number.js index 75014a8..0864b12 100644 --- a/get-cycle-day-number.js +++ b/get-cycle-day-number.js @@ -10,7 +10,11 @@ export default function config(opts) { 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)) + // also, to retrieve the number, we only need the cycle days before the target day + // TODO we can probably rely on the db to do the sorting for us + const sorted = [...unsorted].sort((a, b) => b.date.isAfter(a.date)) + const firstDayBeforeTargetDayIndex = sorted.findIndex(day => day.date.isBefore(targetDate)) + const cycleDays = sorted.slice(firstDayBeforeTargetDayIndex) const lastPeriodStart = cycleDays.find((day, i) => { if ( diff --git a/test/get-cycle-day.spec.js b/test/get-cycle-day.spec.js index 32eefd2..87ed42d 100644 --- a/test/get-cycle-day.spec.js +++ b/test/get-cycle-day.spec.js @@ -142,6 +142,35 @@ describe('getCycleDay', () => { const result = getCycleDayNumber(cycleDays, targetDate) expect(result).to.eql(15) }) + + it('gets the correct number if the target day is not in the current cycle', () => { + const cycleDays = [{ + date: moment([2018, 5, 14]), + }, { + date: moment([2018, 5, 13]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 4, 12]), + }, { + date: moment([2018, 4, 11]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 4, 10]), + bleeding: { + value: 2 + } + }, { + date: moment([2018, 4, 9]), + }] + + const targetDate = moment([2018, 4, 27]) + const result = getCycleDayNumber(cycleDays, targetDate) + expect(result).to.eql(18) + }) }) describe('getCycleDay returns null', () => { From 1430734874287dfc1ed147747c5aea77bc8b7083 Mon Sep 17 00:00:00 2001 From: Julia Friesel Date: Thu, 7 Jun 2018 11:22:29 +0200 Subject: [PATCH 05/12] Pass in dates as JS dates, not moment-wrapped --- get-cycle-day-number.js | 8 ++- test/get-cycle-day.spec.js | 99 +++++++++++++++++++------------------- 2 files changed, 56 insertions(+), 51 deletions(-) diff --git a/get-cycle-day-number.js b/get-cycle-day-number.js index 0864b12..d7890ca 100644 --- a/get-cycle-day-number.js +++ b/get-cycle-day-number.js @@ -12,7 +12,13 @@ export default function config(opts) { // the past as we iterate over the array // also, to retrieve the number, we only need the cycle days before the target day // TODO we can probably rely on the db to do the sorting for us - const sorted = [...unsorted].sort((a, b) => b.date.isAfter(a.date)) + targetDate = moment(targetDate) + const sorted = unsorted + .map(day => { + day.date = moment(day.date) + return day + }) + .sort((a, b) => b.date.isAfter(a.date)) const firstDayBeforeTargetDayIndex = sorted.findIndex(day => day.date.isBefore(targetDate)) const cycleDays = sorted.slice(firstDayBeforeTargetDayIndex) diff --git a/test/get-cycle-day.spec.js b/test/get-cycle-day.spec.js index 87ed42d..64ee0b9 100644 --- a/test/get-cycle-day.spec.js +++ b/test/get-cycle-day.spec.js @@ -1,6 +1,5 @@ import chai from 'chai' import dirtyChai from 'dirty-chai' -import moment from 'moment' const expect = chai.expect chai.use(dirtyChai) @@ -11,163 +10,163 @@ describe('getCycleDay', () => { const getCycleDayNumber = getCycleDayNumberModule() it('works if the last data entered is a bleeding day', function () { const cycleDays = [{ - date: moment([2018, 5, 2]) + date: new Date(2018, 5, 2) }, { - date: moment([2018, 5, 3]), + date: new Date(2018, 5, 3), bleeding: { value: 2 } }, { - date: moment([2018, 5, 4]) + date: new Date(2018, 5, 4) }, { - date: moment([2018, 5, 9]), + date: new Date(2018, 5, 9), bleeding: { value: 2 } }, { - date: moment([2018, 5, 10]), + date: new Date(2018, 5, 10), bleeding: { value: 2 } }] - const targetDate = moment([2018, 5, 17]) + const targetDate = new Date(2018, 5, 17) const result = getCycleDayNumber(cycleDays, targetDate) expect(result).to.eql(9) }) it('works if the last data entered is a non-bleeding day', function () { const cycleDays = [{ - date: moment([2018, 5, 2]) + date: new Date(2018, 5, 2) }, { - date: moment([2018, 5, 3]), + date: new Date(2018, 5, 3), bleeding: { value: 2 } }, { - date: moment([2018, 5, 4]) + date: new Date(2018, 5, 4) }, { - date: moment([2018, 5, 9]), + date: new Date(2018, 5, 9), bleeding: { value: 2 } }, { - date: moment([2018, 5, 10]), + date: new Date(2018, 5, 10), bleeding: { value: 2 } }, { - date: moment([2018, 5, 13]) + date: new Date(2018, 5, 13) }, { - date: moment([2018, 5, 14]) + date: new Date(2018, 5, 14) }] - const targetDate = moment([2018, 5, 17]) + const targetDate = new Date(2018, 5, 17) const result = getCycleDayNumber(cycleDays, targetDate) expect(result).to.eql(9) }) it('works if the cycle days are not sorted by date', function () { const cycleDays = [{ - date: moment([2018, 5, 13]), + date: new Date(2018, 5, 13), bleeding: { value: 2 } }, { - date: moment([2018, 5, 9]), + date: new Date(2018, 5, 9), bleeding: { value: 2 } }, { - date: moment([2018, 5, 3]), + date: new Date(2018, 5, 3), bleeding: { value: 2 } }, { - date: moment([2018, 5, 4]) + date: new Date(2018, 5, 4) }, { - date: moment([2018, 5, 10]), + date: new Date(2018, 5, 10), bleeding: { value: 2 } }, { - date: moment([2018, 5, 2]) + date: new Date(2018, 5, 2) }] - const targetDate = moment([2018, 5, 17]) + const targetDate = new Date(2018, 5, 17) const result = getCycleDayNumber(cycleDays, targetDate) expect(result).to.eql(5) }) it('works when there are only bleeding days', function () { const cycleDays = [{ - date: moment([2018, 5, 9]), + date: new Date(2018, 5, 9), bleeding: { value: 2 } }, { - date: moment([2018, 5, 10]), + date: new Date(2018, 5, 10), bleeding: { value: 2 } }] - const targetDate = moment([2018, 5, 17]) + const targetDate = new Date(2018, 5, 17) const result = getCycleDayNumber(cycleDays, targetDate) expect(result).to.eql(9) }) it('works if some bleedings are exluded', function () { const cycleDays = [{ - date: moment([2018, 5, 2]) + date: new Date(2018, 5, 2) }, { - date: moment([2018, 5, 3]), + date: new Date(2018, 5, 3), bleeding: { value: 2 } }, { - date: moment([2018, 5, 4]) + date: new Date(2018, 5, 4) }, { - date: moment([2018, 5, 9]), + date: new Date(2018, 5, 9), bleeding: { value: 2, exclude: true } }, { - date: moment([2018, 5, 10]), + date: new Date(2018, 5, 10), bleeding: { value: 2, exclude: true } }] - const targetDate = moment([2018, 5, 17]) + const targetDate = new Date(2018, 5, 17) const result = getCycleDayNumber(cycleDays, targetDate) expect(result).to.eql(15) }) it('gets the correct number if the target day is not in the current cycle', () => { const cycleDays = [{ - date: moment([2018, 5, 14]), + date: new Date(2018, 5, 14), }, { - date: moment([2018, 5, 13]), + date: new Date(2018, 5, 13), bleeding: { value: 2 } }, { - date: moment([2018, 4, 12]), + date: new Date(2018, 4, 12), }, { - date: moment([2018, 4, 11]), + date: new Date(2018, 4, 11), bleeding: { value: 2 } }, { - date: moment([2018, 4, 10]), + date: new Date(2018, 4, 10), bleeding: { value: 2 } }, { - date: moment([2018, 4, 9]), + date: new Date(2018, 4, 9), }] - const targetDate = moment([2018, 4, 27]) + const targetDate = new Date(2018, 4, 27) const result = getCycleDayNumber(cycleDays, targetDate) expect(result).to.eql(18) }) @@ -177,22 +176,22 @@ describe('getCycleDay returns null', () => { const getCycleDayNumber = getCycleDayNumberModule() it('if there are no bleeding days', function () { const cycleDays = [{ - date: moment([2018, 5, 2]) + date: new Date(2018, 5, 2) }, { - date: moment([2018, 5, 4]) + date: new Date(2018, 5, 4) }, { - date: moment([2018, 5, 9]), + date: new Date(2018, 5, 9), }, { - date: moment([2018, 5, 10]), + date: new Date(2018, 5, 10), }] - const targetDate = moment([2018, 5, 17]) + const targetDate = new Date(2018, 5, 17) const result = getCycleDayNumber(cycleDays, targetDate) expect(result).to.be.null() }) it('if there are no cycle days', function () { const cycleDays = [] - const targetDate = moment([2018, 5, 17]) + const targetDate = new Date(2018, 5, 17) const result = getCycleDayNumber(cycleDays, targetDate) expect(result).to.be.null() }) @@ -205,35 +204,35 @@ describe('getCycleDay with cycle thresholds', () => { it('disregards bleeding breaks shorter than the min cycle threshold in a bleeding period', () => { const cycleDays = [{ - date: moment([2018, 5, 10]), + date: new Date(2018, 5, 10), bleeding: { value: 2 } }, { - date: moment([2018, 5, 14]), + date: new Date(2018, 5, 14), bleeding: { value: 2 } }] - const targetDate = moment([2018, 5, 17]) + const targetDate = new Date(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]), + date: new Date(2018, 5, 9), bleeding: { value: 2 } }, { - date: moment([2018, 5, 14]), + date: new Date(2018, 5, 14), bleeding: { value: 2 } }] - const targetDate = moment([2018, 5, 17]) + const targetDate = new Date(2018, 5, 17) const result = getCycleDayNumber(cycleDays, targetDate) expect(result).to.eql(4) }) From 5b406848cb30b6e2b66a367faedee6a93bb6e81f Mon Sep 17 00:00:00 2001 From: Julia Friesel Date: Sat, 9 Jun 2018 12:00:06 +0200 Subject: [PATCH 06/12] Change getCycleDay signature and process LocalDate --- bleeding.js | 7 +- day-view.js | 7 +- db.js | 46 +++++---- get-cycle-day-number.js | 47 ++++----- get-cycle-day.js | 1 - home.js | 22 +++- index.js | 9 +- package-lock.json | 5 + package.json | 1 + test/get-cycle-day.spec.js | 207 +++++++++---------------------------- 10 files changed, 129 insertions(+), 223 deletions(-) delete mode 100644 get-cycle-day.js diff --git a/bleeding.js b/bleeding.js index 359a94f..f7e4e55 100644 --- a/bleeding.js +++ b/bleeding.js @@ -10,7 +10,10 @@ import styles from './styles' import { saveBleeding } from './db' import { formatDateForViewHeader } from './format' import { bleeding as labels } from './labels' -import getCycleDay from './get-cycle-day' +import cycleDayModule from './get-cycle-day-number' +import { getCycleDaysSortedByDateView } from './db' + +const getCycleDay = cycleDayModule(getCycleDaysSortedByDateView()) export default class Bleeding extends Component { constructor(props) { @@ -40,7 +43,7 @@ export default class Bleeding extends Component { return ( {formatDateForViewHeader(day.date)} - Cycle day {getCycleDay()} + Cycle day {getCycleDay(day.date)} Bleeding {formatDateForViewHeader(day.date)} - Cycle day {getCycleDay()} + Cycle day {getCycleDay(day.date)} {bleedingLabel} + ) } -} \ No newline at end of file +} diff --git a/index.js b/index.js index f1543b7..408a2f0 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,4 @@ import { AppRegistry } from 'react-native' -import Home from './app' -import { openDatabase } from './db' +import App from './app' -// TODO error handling -openDatabase() - .then(() => { - AppRegistry.registerComponent('home', () => Home) - }) \ No newline at end of file +AppRegistry.registerComponent('home', () => App) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c531614..21bda8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4693,6 +4693,11 @@ "merge-stream": "^1.0.1" } }, + "js-joda": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/js-joda/-/js-joda-1.8.2.tgz", + "integrity": "sha512-3w+3TnKqiplQyG/YQk31cBhJ/sg2Xb/fX7lneiK+z+nEjTzdfvHqJquJhtzyEA1NyLJKNpIeOQSBr3Q4nY+O8Q==" + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", diff --git a/package.json b/package.json index 28310a9..ff64b52 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "lint": "eslint app test" }, "dependencies": { + "js-joda": "^1.8.2", "moment": "^2.22.1", "react": "16.3.1", "react-native": "0.55.4", diff --git a/test/get-cycle-day.spec.js b/test/get-cycle-day.spec.js index 64ee0b9..60b4fa5 100644 --- a/test/get-cycle-day.spec.js +++ b/test/get-cycle-day.spec.js @@ -7,233 +7,126 @@ chai.use(dirtyChai) 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: new Date(2018, 5, 2) - }, { - date: new Date(2018, 5, 3), + it('works for a simple example', function () { + const bleedingDays = [{ + date: '2018-05-10', bleeding: { value: 2 } }, { - date: new Date(2018, 5, 4) - }, { - date: new Date(2018, 5, 9), + date: '2018-05-09', bleeding: { value: 2 } }, { - date: new Date(2018, 5, 10), + date: '2018-05-03', bleeding: { value: 2 } }] - const targetDate = new Date(2018, 5, 17) - const result = getCycleDayNumber(cycleDays, targetDate) - expect(result).to.eql(9) - }) - - it('works if the last data entered is a non-bleeding day', function () { - const cycleDays = [{ - date: new Date(2018, 5, 2) - }, { - date: new Date(2018, 5, 3), - bleeding: { - value: 2 - } - }, { - date: new Date(2018, 5, 4) - }, { - date: new Date(2018, 5, 9), - bleeding: { - value: 2 - } - }, { - date: new Date(2018, 5, 10), - bleeding: { - value: 2 - } - }, { - date: new Date(2018, 5, 13) - }, { - date: new Date(2018, 5, 14) - }] - - const targetDate = new Date(2018, 5, 17) - const result = getCycleDayNumber(cycleDays, targetDate) - expect(result).to.eql(9) - }) - - it('works if the cycle days are not sorted by date', function () { - const cycleDays = [{ - date: new Date(2018, 5, 13), - bleeding: { - value: 2 - } - }, { - date: new Date(2018, 5, 9), - bleeding: { - value: 2 - } - }, { - date: new Date(2018, 5, 3), - bleeding: { - value: 2 - } - }, { - date: new Date(2018, 5, 4) - }, { - date: new Date(2018, 5, 10), - bleeding: { - value: 2 - } - }, { - date: new Date(2018, 5, 2) - }] - - const targetDate = new Date(2018, 5, 17) - const result = getCycleDayNumber(cycleDays, targetDate) - expect(result).to.eql(5) - }) - - it('works when there are only bleeding days', function () { - const cycleDays = [{ - date: new Date(2018, 5, 9), - bleeding: { - value: 2 - } - }, { - date: new Date(2018, 5, 10), - bleeding: { - value: 2 - } - }] - const targetDate = new Date(2018, 5, 17) - const result = getCycleDayNumber(cycleDays, targetDate) + const getCycleDayNumber = getCycleDayNumberModule(bleedingDays) + const targetDate = '2018-05-17' + const result = getCycleDayNumber(targetDate) expect(result).to.eql(9) }) it('works if some bleedings are exluded', function () { - const cycleDays = [{ - date: new Date(2018, 5, 2) + const bleedingDays = [{ + date: '2018-05-10', + bleeding: { + value: 2, + exclude: true + } }, { - date: new Date(2018, 5, 3), + date: '2018-05-09', + bleeding: { + value: 2, + exclude: true + } + }, { + date: '2018-05-03', bleeding: { value: 2 } - }, { - date: new Date(2018, 5, 4) - }, { - date: new Date(2018, 5, 9), - bleeding: { - value: 2, - exclude: true - } - }, { - date: new Date(2018, 5, 10), - bleeding: { - value: 2, - exclude: true - } }] - const targetDate = new Date(2018, 5, 17) - const result = getCycleDayNumber(cycleDays, targetDate) + const targetDate = '2018-05-17' + const getCycleDayNumber = getCycleDayNumberModule(bleedingDays) + const result = getCycleDayNumber(targetDate) expect(result).to.eql(15) }) it('gets the correct number if the target day is not in the current cycle', () => { - const cycleDays = [{ - date: new Date(2018, 5, 14), - }, { - date: new Date(2018, 5, 13), + const bleedingDays = [{ + date: '2018-05-13', bleeding: { value: 2 } }, { - date: new Date(2018, 4, 12), - }, { - date: new Date(2018, 4, 11), + date: '2018-04-11', bleeding: { value: 2 } }, { - date: new Date(2018, 4, 10), + date: '2018-04-10', bleeding: { value: 2 } - }, { - date: new Date(2018, 4, 9), }] - const targetDate = new Date(2018, 4, 27) - const result = getCycleDayNumber(cycleDays, targetDate) + const targetDate = '2018-04-27' + const getCycleDayNumber = getCycleDayNumberModule(bleedingDays) + const result = getCycleDayNumber(targetDate) expect(result).to.eql(18) }) }) describe('getCycleDay returns null', () => { - const getCycleDayNumber = getCycleDayNumberModule() it('if there are no bleeding days', function () { - const cycleDays = [{ - date: new Date(2018, 5, 2) - }, { - date: new Date(2018, 5, 4) - }, { - date: new Date(2018, 5, 9), - }, { - date: new Date(2018, 5, 10), - }] - const targetDate = new Date(2018, 5, 17) - const result = getCycleDayNumber(cycleDays, targetDate) - expect(result).to.be.null() - }) - - it('if there are no cycle days', function () { - const cycleDays = [] - const targetDate = new Date(2018, 5, 17) - const result = getCycleDayNumber(cycleDays, targetDate) + const bleedingDays = [] + const targetDate = '2018-05-17' + const getCycleDayNumber = getCycleDayNumberModule(bleedingDays) + const result = getCycleDayNumber(targetDate) expect(result).to.be.null() }) }) describe('getCycleDay with cycle thresholds', () => { - const getCycleDayNumber = getCycleDayNumberModule({ - minCycleLengthInDays: 5 - }) + const opts = { maxBreakInBleeding: 3 } - it('disregards bleeding breaks shorter than the min cycle threshold in a bleeding period', () => { - const cycleDays = [{ - date: new Date(2018, 5, 10), + it('disregards bleeding breaks shorter than max allowed bleeding break in a bleeding period', () => { + const bleedingDays = [{ + date: '2018-05-14', bleeding: { value: 2 } }, { - date: new Date(2018, 5, 14), + date: '2018-05-10', bleeding: { value: 2 } }] - const targetDate = new Date(2018, 5, 17) - const result = getCycleDayNumber(cycleDays, targetDate) + const targetDate = '2018-05-17' + const getCycleDayNumber = getCycleDayNumberModule(bleedingDays, opts) + const result = getCycleDayNumber(targetDate) expect(result).to.eql(8) }) - it('counts bleeding breaks longer than the min cycle threshold in a bleeding period', () => { - const cycleDays = [{ - date: new Date(2018, 5, 9), + it('counts bleeding breaks longer than maxAllowedBleedingBreak in a bleeding period', () => { + const bleedingDays = [{ + date: '2018-05-14', bleeding: { value: 2 } }, { - date: new Date(2018, 5, 14), + date: '2018-05-09', bleeding: { value: 2 } }] - const targetDate = new Date(2018, 5, 17) - const result = getCycleDayNumber(cycleDays, targetDate) + const targetDate = '2018-05-17' + const getCycleDayNumber = getCycleDayNumberModule(bleedingDays, opts) + const result = getCycleDayNumber(targetDate) expect(result).to.eql(4) }) }) \ No newline at end of file From a56ee7df3800d3ff8b16b3ae9cdfb8662aaa86a2 Mon Sep 17 00:00:00 2001 From: Julia Friesel Date: Sat, 9 Jun 2018 12:47:57 +0200 Subject: [PATCH 07/12] Fix case for no previous bleeding day. Use new getCycleDay API --- bleeding.js | 4 ++-- day-view.js | 4 ++-- get-cycle-day-number.js | 16 +++++----------- home.js | 31 +++++++++++++++++++++++-------- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/bleeding.js b/bleeding.js index f7e4e55..d1e0452 100644 --- a/bleeding.js +++ b/bleeding.js @@ -11,9 +11,9 @@ import { saveBleeding } from './db' import { formatDateForViewHeader } from './format' import { bleeding as labels } from './labels' import cycleDayModule from './get-cycle-day-number' -import { getCycleDaysSortedByDateView } from './db' +import { bleedingDaysSortedByDate } from './db' -const getCycleDay = cycleDayModule(getCycleDaysSortedByDateView()) +const getCycleDay = cycleDayModule(bleedingDaysSortedByDate) export default class Bleeding extends Component { constructor(props) { diff --git a/day-view.js b/day-view.js index 799d815..b615ffd 100644 --- a/day-view.js +++ b/day-view.js @@ -8,9 +8,9 @@ import styles from './styles' import { formatDateForViewHeader } from './format' import { bleeding as labels} from './labels' import cycleDayModule from './get-cycle-day-number' -import { getCycleDaysSortedByDateView } from './db' +import { bleedingDaysSortedByDate } from './db' -const getCycleDay = cycleDayModule(getCycleDaysSortedByDateView()) +const getCycleDay = cycleDayModule(bleedingDaysSortedByDate) export default class DayView extends Component { constructor(props) { diff --git a/get-cycle-day-number.js b/get-cycle-day-number.js index 1c6d74f..a6c8598 100644 --- a/get-cycle-day-number.js +++ b/get-cycle-day-number.js @@ -4,15 +4,10 @@ const LocalDate = joda.LocalDate export default function config(bleedingDaysSortedByDateView, 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 maxBreakInBleeding: 1 } return function getCycleDayNumber(targetDateString) { - // sort the cycle days in descending order so we travel into - // the past as we iterate over the array - // also, to retrieve the number, we only need the cycle days before the target day const targetDate = LocalDate.parse(targetDateString) const withWrappedDates = bleedingDaysSortedByDateView .filter(day => !day.bleeding.exclude) @@ -20,16 +15,15 @@ export default function config(bleedingDaysSortedByDateView, opts) { day.wrappedDate = LocalDate.parse(day.date) return day }) - // TODO write test for what if there is no first day before?? aka no firstbleedingdaybeforeindex + const firstBleedingDayBeforeTargetDayIndex = withWrappedDates.findIndex(day => day.wrappedDate.isBefore(targetDate)) - const cycleDays = withWrappedDates.slice(firstBleedingDayBeforeTargetDayIndex) + if (firstBleedingDayBeforeTargetDayIndex < 0) return null + const previousBleedingDays = withWrappedDates.slice(firstBleedingDayBeforeTargetDayIndex) - const lastPeriodStart = cycleDays.find((day, i) => { - return thereIsNoPreviousBleedingDayWithinTheThreshold(day, cycleDays.slice(i + 1), opts.maxBreakInBleeding) + const lastPeriodStart = previousBleedingDays.find((day, i) => { + return thereIsNoPreviousBleedingDayWithinTheThreshold(day, previousBleedingDays.slice(i + 1), opts.maxBreakInBleeding) }) - if (!lastPeriodStart) return null - const diffInDays = lastPeriodStart.wrappedDate.until(targetDate, joda.ChronoUnit.DAYS) // cycle starts at day 1 diff --git a/home.js b/home.js index 101a7ea..c4749e7 100644 --- a/home.js +++ b/home.js @@ -6,22 +6,27 @@ import { } from 'react-native' import styles from './styles' import cycleDayModule from './get-cycle-day-number' -import { getCycleDaysSortedByDateView, deleteAll } from './db' +import { bleedingDaysSortedByDate, deleteAll } from './db' import { LocalDate } from 'js-joda' -const cycleDaysSortedByDateView = getCycleDaysSortedByDateView() -const getCycleDayNumber = cycleDayModule(cycleDaysSortedByDateView) -const now = new Date() -const cycleDayNumber = getCycleDayNumber(LocalDate.of(now.getFullYear(), now.getMonth() + 1, now.getDate())) -const welcomeTextWithCycleDay = `Welcome! Today is day ${cycleDayNumber} of your current cycle` -const welcomeText = `Welcome! We don't have enough information to know what your current cycle day is` +const getCycleDayNumber = cycleDayModule(bleedingDaysSortedByDate) export default class Home extends Component { constructor(props) { super(props) + const now = new Date() + this.todayDateString = LocalDate.of(now.getFullYear(), now.getMonth() + 1, now.getDate()).toString() + const cycleDayNumber = getCycleDayNumber(this.todayDateString) + this.state = { - welcomeText: cycleDayNumber ? welcomeTextWithCycleDay : welcomeText + welcomeText: determineWelcomeText(cycleDayNumber) } + + bleedingDaysSortedByDate.addListener(setStateWithCurrentWelcomeText.bind(this)) + } + + componentWillUnmount() { + bleedingDaysSortedByDate.removeListener(setStateWithCurrentWelcomeText) } render() { @@ -45,3 +50,13 @@ export default class Home extends Component { ) } } + +function determineWelcomeText(cycleDayNumber) { + const welcomeTextWithCycleDay = `Welcome! Today is day ${cycleDayNumber} of your current cycle` + const welcomeText = `Welcome! We don't have enough information to know what your current cycle day is` + return cycleDayNumber ? welcomeTextWithCycleDay : welcomeText +} + +function setStateWithCurrentWelcomeText() { + this.setState({ welcomeText: determineWelcomeText(getCycleDayNumber(this.todayDateString)) }) +} \ No newline at end of file From f98a60192b5cc0b25d8f13f6389c779b8cc61141 Mon Sep 17 00:00:00 2001 From: Julia Friesel Date: Sat, 9 Jun 2018 15:00:12 +0200 Subject: [PATCH 08/12] Remove listener on componentUnmount and clean up state --- day-view.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/day-view.js b/day-view.js index b615ffd..7ed9928 100644 --- a/day-view.js +++ b/day-view.js @@ -15,14 +15,20 @@ const getCycleDay = cycleDayModule(bleedingDaysSortedByDate) export default class DayView extends Component { constructor(props) { super(props) + this.cycleDay = props.navigation.state.params.cycleDay this.state = { - cycleDay: props.navigation.state.params.cycleDay + cycleDayNumber: getCycleDay(this.cycleDay.date), } + bleedingDaysSortedByDate.addListener(setStateWithCurrentCycleDayNumber.bind(this)) + } + + componentWillUnmount() { + bleedingDaysSortedByDate.removeListener(setStateWithCurrentCycleDayNumber) } render() { const navigate = this.props.navigation.navigate - const day = this.state.cycleDay + const day = this.cycleDay const bleedingValue = day.bleeding && day.bleeding.value let bleedingLabel if (typeof bleedingValue === 'number') { @@ -43,4 +49,10 @@ export default class DayView extends Component { ) } +} + +function setStateWithCurrentCycleDayNumber() { + this.setState({ + cycleDayNumber: getCycleDay(this.cycleDay.date) + }) } \ No newline at end of file From ab1ed96966b2c08cbcbba0da2700be7276f472a8 Mon Sep 17 00:00:00 2001 From: Julia Friesel Date: Mon, 11 Jun 2018 14:03:45 +0200 Subject: [PATCH 09/12] Change getCycleDayNumber signature and some PR improvements --- bleeding.js | 5 ++--- day-view.js | 8 ++++---- get-cycle-day-number.js | 12 ++++++------ home.js | 5 ++--- test/get-cycle-day.spec.js | 14 +++++++------- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/bleeding.js b/bleeding.js index d1e0452..6b2e956 100644 --- a/bleeding.js +++ b/bleeding.js @@ -11,9 +11,8 @@ import { saveBleeding } from './db' import { formatDateForViewHeader } from './format' import { bleeding as labels } from './labels' import cycleDayModule from './get-cycle-day-number' -import { bleedingDaysSortedByDate } from './db' -const getCycleDay = cycleDayModule(bleedingDaysSortedByDate) +const getCycleDayNumber = cycleDayModule() export default class Bleeding extends Component { constructor(props) { @@ -43,7 +42,7 @@ export default class Bleeding extends Component { return ( {formatDateForViewHeader(day.date)} - Cycle day {getCycleDay(day.date)} + Cycle day {getCycleDayNumber(day.date)} Bleeding {formatDateForViewHeader(day.date)} - Cycle day {getCycleDay(day.date)} + Cycle day {getCycleDayNumber(day.date)} {bleedingLabel}