diff --git a/components/stats.js b/components/stats.js index d192f04d5fe813e26797c4ccb395757762fb09ff..d9ec69c48079914283658189049e30ad0febf58a 100644 --- a/components/stats.js +++ b/components/stats.js @@ -9,17 +9,14 @@ import cycleModule from '../lib/cycle' import {getCycleLengthStats as getCycleInfo} from '../lib/cycle-length' import {stats as labels} from '../i18n/en/labels' import AppText from './app-text' -import { getCycleStartsSortedByDate } from '../db' export default class Stats extends Component { render() { - const allMensesStarts = getCycleStartsSortedByDate() - const atLeastOneCycle = allMensesStarts.length > 1 - let cycleLengths + const cycleLengths = cycleModule().getAllCycleLengths() + const atLeastOneCycle = cycleLengths.length > 1 let numberOfCycles let cycleInfo if (atLeastOneCycle) { - cycleLengths = cycleModule().getAllCycleLengths() numberOfCycles = cycleLengths.length if (numberOfCycles > 1) { cycleInfo = getCycleInfo(cycleLengths) diff --git a/lib/cycle.js b/lib/cycle.js index b675f52b9b221c2afc60aeb140b9f2b1bd517a00..b9f10fb02d123396236e2463c186346320fdf595 100644 --- a/lib/cycle.js +++ b/lib/cycle.js @@ -1,5 +1,5 @@ import * as joda from 'js-joda' -import {getCycleLengthStats} from './cycle-length' +import { getCycleLengthStats } from './cycle-length' const LocalDate = joda.LocalDate const DAYS = joda.ChronoUnit.DAYS @@ -40,7 +40,7 @@ export default function config(opts) { const lastMensesLocalDate = LocalDate.parse(lastMensesStart.date) const diffInDays = lastMensesLocalDate.until(targetDate, DAYS) // take maxCycleLength into account (we don't display cycle day numbers higher than 99 at the moment) - if (diffInDays >= 99) return null + if (diffInDays >= maxCycleLength) return null // cycle starts at day 1 return diffInDays + 1 } @@ -61,28 +61,38 @@ export default function config(opts) { if (startFromHere < 0) return null return cycleStartsSortedByDate .slice(startFromHere) - .map(getCycleForCycleStartDay) + .map(start => getCycleForCycleStartDay(start)) + // filter the ones exceeding maxCycleLength, those are null + .filter(cycle => cycle) } - function getCycleForCycleStartDay(startDay) { + function getCycleForCycleStartDay(startDay, todayDate) { + const todayAsLocalDate = todayDate ? LocalDate.parse(todayDate) : LocalDate.now() const cycleStartIndex = cycleDaysSortedByDate.indexOf(startDay) const i = cycleStartsSortedByDate.indexOf(startDay) + const startLocalDate = LocalDate.parse(startDay.date) const nextMensesStart = cycleStartsSortedByDate[i - 1] + let cycle + let cycleLength if (nextMensesStart) { - return cycleDaysSortedByDate.slice( + cycle = cycleDaysSortedByDate.slice( cycleDaysSortedByDate.indexOf(nextMensesStart) + 1, cycleStartIndex + 1, ) + const nextLocalDate = LocalDate.parse(nextMensesStart.date) + cycleLength = startLocalDate.until(nextLocalDate, DAYS) } else { - return cycleDaysSortedByDate.slice(0, cycleStartIndex + 1) + cycle = cycleDaysSortedByDate.slice(0, cycleStartIndex + 1) + cycleLength = startLocalDate.until(todayAsLocalDate, DAYS) } + return cycleLength > maxCycleLength ? null : cycle } - function getCycleForDay(dayOrDate) { + function getCycleForDay(dayOrDate, todayDate) { const dateString = typeof dayOrDate === 'string' ? dayOrDate : dayOrDate.date const cycleStart = getLastMensesStartForDay(dateString) if (!cycleStart) return null - return getCycleForCycleStartDay(cycleStart) + return getCycleForCycleStartDay(cycleStart, todayDate) } function isMensesStart(cycleDay) { @@ -138,24 +148,19 @@ export default function config(opts) { function getAllCycleLengths() { return cycleStartsSortedByDate .map(day => LocalDate.parse(day.date)) - .reduce((lengths, cycleStart, i, startsAsLocalDates) => { - if (i === startsAsLocalDates.length - 1) return lengths + .map((cycleStart, i, startsAsLocalDates) => { + if (i === cycleStartsSortedByDate.length - 1) return null const prevCycleStart = startsAsLocalDates[i + 1] - const cycleLength = prevCycleStart.until(cycleStart, DAYS) - if (cycleLength <= maxCycleLength) { lengths.push(cycleLength) } - return lengths - }, []) + return prevCycleStart.until(cycleStart, DAYS) + }) + .filter(length => length && length <= maxCycleLength) } function getPredictedMenses() { - const allMensesStarts = cycleStartsSortedByDate - const atLeastOneCycle = allMensesStarts.length > 1 - if (!atLeastOneCycle || - allMensesStarts.length < minCyclesForPrediction - ) { + const cycleLengths = getAllCycleLengths() + if (cycleLengths.length < minCyclesForPrediction) { return [] } - const cycleLengths = getAllCycleLengths() const cycleInfo = getCycleLengthStats(cycleLengths) const periodDistance = Math.round(cycleInfo.mean) let periodStartVariation @@ -169,6 +174,7 @@ export default function config(opts) { if (periodDistance - 5 < periodStartVariation) { // otherwise predictions overlap return [] } + const allMensesStarts = cycleStartsSortedByDate let lastStart = LocalDate.parse(allMensesStarts[0].date) const predictedMenses = [] for (let i = 0; i < 3; i++) { diff --git a/test/cycle.spec.js b/test/cycle.spec.js index 77bf379b7aaf98ae5071072347468557ae85be56..3a849e2d0af976f1ea13d34e92aebbed31196228 100644 --- a/test/cycle.spec.js +++ b/test/cycle.spec.js @@ -72,7 +72,26 @@ describe('getCycleDayNumber', () => { expect(result).to.be.null() }) - + it('returns null if the cycle is longer than the max', function () { + const cycleStarts = [{ + date: '2018-05-09', + isCycleStart: true, + bleeding: { + value: 2 + } + }, { + date: '2018-05-03', + isCycleStart: true, + bleeding: { value: 2 } + }] + // we use the default 99 days max length + const getCycleDayNumber = cycleModule({ + cycleStartsSortedByDate: cycleStarts + }).getCycleDayNumber + const targetDate = '2018-08-16' + const result = getCycleDayNumber(targetDate) + expect(result).to.be.null() + }) }) describe('getPreviousCycle', () => { @@ -246,6 +265,64 @@ describe('getPreviousCycle', () => { const result = getPreviousCycle('2018-04-18') expect(result).to.eql(null) }) + + it('returns null when the previous cycle > maxcyclelength', () => { + const cycleDaysSortedByDate = [ + { + date: '2018-07-05', + bleeding: { value: 2 } + }, + { + date: '2018-06-05', + bleeding: { value: 2 } + }, + { + date: '2018-05-05', + mucus: { value: 2 } + }, + { + date: '2018-05-04', + bleeding: { value: 2 } + }, + { + date: '2018-05-03', + bleeding: { value: 2 } + }, + { + date: '2018-04-05', + mucus: { value: 2 } + }, + { + date: '2018-04-04', + mucus: { value: 2 } + }, + { + date: '2018-04-03', + mucus: { value: 2 } + }, + { + date: '2018-04-02', + bleeding: { value: 2 } + }, + ] + + const cycleStarts = [ + '2018-07-05', + '2018-06-05', + '2018-05-03', + '2018-04-02' + ] + + const { getPreviousCycle } = cycleModule({ + cycleDaysSortedByDate, + cycleStartsSortedByDate: cycleDaysSortedByDate.filter(d => { + return cycleStarts.includes(d.date) + }), + maxCycleLength: 2 + }) + const result = getPreviousCycle('2018-06-08') + expect(result).to.eql(null) + }) }) describe('getCyclesBefore', () => { @@ -343,6 +420,68 @@ describe('getCyclesBefore', () => { ] ]) }) + + it('skips cycles that are longer than max', () => { + const cycleDaysSortedByDate = [ + { + date: '2018-07-05', + bleeding: { value: 2 } + }, + { + date: '2018-06-05', + bleeding: { value: 2 } + }, + { + date: '2018-05-05', + mucus: { value: 2 } + }, + { + date: '2018-05-04', + bleeding: { value: 2 } + }, + { + date: '2018-05-03', + bleeding: { value: 2 } + }, + { + date: '2018-04-05', + mucus: { value: 2 } + }, + { + date: '2018-04-04', + mucus: { value: 2 } + }, + { + date: '2018-04-03', + mucus: { value: 2 } + }, + { + date: '2018-04-02', + bleeding: { value: 2 } + }, + ] + + const cycleStarts = [ + '2018-07-05', + '2018-06-05', + '2018-05-03', + '2018-04-02' + ] + + const { getCyclesBefore } = cycleModule({ + cycleDaysSortedByDate, + cycleStartsSortedByDate: cycleDaysSortedByDate.filter(d => { + return cycleStarts.includes(d.date) + }), + maxCycleLength: 30 + }) + const result = getCyclesBefore(cycleDaysSortedByDate[0]) + expect(result.length).to.eql(1) + expect(result).to.eql([[{ + bleeding: { value: 2 }, + date: "2018-06-05" + }]]) + }) }) describe('getCycleForDay', () => { @@ -399,7 +538,7 @@ describe('getCycleForDay', () => { }) it('gets cycle that has only one day', () => { - const result = getCycleForDay('2018-07-05') + const result = getCycleForDay('2018-07-05', '2018-08-01') expect(result.length).to.eql(1) expect(result).to.eql([ { @@ -433,6 +572,18 @@ describe('getCycleForDay', () => { expect(result).to.eql(null) }) + it('returns null if the cycle is longer than the max', () => { + const { getCycleForDay } = cycleModule({ + cycleDaysSortedByDate, + cycleStartsSortedByDate: cycleDaysSortedByDate.filter(d => { + return cycleStarts.includes(d.date) + }), + maxCycleLength: 3 + }) + const result = getCycleForDay('2018-04-04') + expect(result).to.eql(null) + }) + it('gets cycle for day', () => { const result = getCycleForDay('2018-04-04') expect(result.length).to.eql(4) @@ -525,6 +676,47 @@ describe('getPredictedMenses', () => { const result = getPredictedMenses() expect(result).to.eql([]) }) + + it('if number of cycles is below minCyclesForPrediction because one of them is too long', () => { + const cycleDaysSortedByDate = [ + { + date: '2018-06-02', + bleeding: { value: 2 } + }, + { + date: '2018-06-01', + bleeding: { value: 2 } + }, + { + date: '2018-05-01', + bleeding: { value: 2 } + }, + { + date: '2018-04-03', + bleeding: { value: 2 } + }, + { + date: '2018-04-02', + bleeding: { value: 2 } + }, + { + date: '2018-04-01', + bleeding: { value: 2 } + }, + ] + const cycleStarts = ['2018-06-01', '2018-05-01', '2018-04-01'] + + const { getPredictedMenses } = cycleModule({ + cycleDaysSortedByDate, + bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding), + cycleStartsSortedByDate: cycleDaysSortedByDate.filter(d => { + return cycleStarts.includes(d.date) + }), + maxCycleLength: 2 + }) + const result = getPredictedMenses() + expect(result).to.eql([]) + }) }) describe('works', () => { it('for one completed cycle with minCyclesForPrediction = 1', () => { @@ -714,6 +906,62 @@ describe('getPredictedMenses', () => { ] expect(result).to.eql(expectedResult) }) + + it('does not count cycles longer than max', () => { + const cycleDaysSortedByDate = [ + { + date: '2018-08-01', + bleeding: { value: 2 } + }, + { + date: '2018-07-14', + bleeding: { value: 2 } + }, + { + date: '2018-07-04', + bleeding: { value: 2 } + }, + { + date: '2018-06-20', + bleeding: { value: 2 } + }, + { + date: '2018-04-20', + bleeding: { value: 2 } + }, + ] + + const { getPredictedMenses } = cycleModule({ + cycleDaysSortedByDate, + cycleStartsSortedByDate: cycleDaysSortedByDate, + maxCycleLength: 50 + }) + const result = getPredictedMenses() + const expectedResult = [ + [ + '2018-08-13', + '2018-08-14', + '2018-08-15', + '2018-08-16', + '2018-08-17', + ], + [ + '2018-08-27', + '2018-08-28', + '2018-08-29', + '2018-08-30', + '2018-08-31', + ], + [ + '2018-09-10', + '2018-09-11', + '2018-09-12', + '2018-09-13', + '2018-09-14', + ] + ] + expect(result).to.eql(expectedResult) + }) }) })