Use new isCycleStartFunctions throughout cycle module
This commit is contained in:
+3
-2
@@ -9,16 +9,17 @@ import cycleModule from '../lib/cycle'
|
|||||||
import {getCycleLengthStats as getCycleInfo} from '../lib/cycle-length'
|
import {getCycleLengthStats as getCycleInfo} from '../lib/cycle-length'
|
||||||
import {stats as labels} from './labels'
|
import {stats as labels} from './labels'
|
||||||
import AppText from './app-text'
|
import AppText from './app-text'
|
||||||
|
import { getCycleStartsSortedByDate } from '../db'
|
||||||
|
|
||||||
export default class Stats extends Component {
|
export default class Stats extends Component {
|
||||||
render() {
|
render() {
|
||||||
const allMensesStarts = cycleModule().getAllMensesStarts()
|
const allMensesStarts = getCycleStartsSortedByDate()
|
||||||
const atLeastOneCycle = allMensesStarts.length > 1
|
const atLeastOneCycle = allMensesStarts.length > 1
|
||||||
let cycleLengths
|
let cycleLengths
|
||||||
let numberOfCycles
|
let numberOfCycles
|
||||||
let cycleInfo
|
let cycleInfo
|
||||||
if (atLeastOneCycle) {
|
if (atLeastOneCycle) {
|
||||||
cycleLengths = cycleModule().getCycleLength(allMensesStarts)
|
cycleLengths = cycleModule().getAllCycleLengths()
|
||||||
numberOfCycles = cycleLengths.length
|
numberOfCycles = cycleLengths.length
|
||||||
if (numberOfCycles > 1) {
|
if (numberOfCycles > 1) {
|
||||||
cycleInfo = getCycleInfo(cycleLengths)
|
cycleInfo = getCycleInfo(cycleLengths)
|
||||||
|
|||||||
@@ -51,6 +51,10 @@ export function getCycleDaysSortedByDate() {
|
|||||||
return db.objects('CycleDay').sorted('date', true)
|
return db.objects('CycleDay').sorted('date', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCycleStartsSortedByDate() {
|
||||||
|
return db.objects('CycleDay').filtered('isCycleStart = true').sorted('date', true)
|
||||||
|
}
|
||||||
|
|
||||||
export function saveSymptom(symptom, cycleDay, val) {
|
export function saveSymptom(symptom, cycleDay, val) {
|
||||||
db.write(() => {
|
db.write(() => {
|
||||||
if (symptom === 'bleeding') {
|
if (symptom === 'bleeding') {
|
||||||
|
|||||||
+45
-94
@@ -5,6 +5,7 @@ const DAYS = joda.ChronoUnit.DAYS
|
|||||||
|
|
||||||
export default function config(opts) {
|
export default function config(opts) {
|
||||||
let bleedingDaysSortedByDate
|
let bleedingDaysSortedByDate
|
||||||
|
let cycleStartsSortedByDate
|
||||||
let cycleDaysSortedByDate
|
let cycleDaysSortedByDate
|
||||||
let maxBreakInBleeding
|
let maxBreakInBleeding
|
||||||
let maxCycleLength
|
let maxCycleLength
|
||||||
@@ -14,57 +15,22 @@ export default function config(opts) {
|
|||||||
// we only want to require (and run) the db module
|
// we only want to require (and run) the db module
|
||||||
// when not running the tests
|
// when not running the tests
|
||||||
bleedingDaysSortedByDate = require('../db').getBleedingDaysSortedByDate()
|
bleedingDaysSortedByDate = require('../db').getBleedingDaysSortedByDate()
|
||||||
|
cycleStartsSortedByDate = require('../db').getCycleStartsSortedByDate()
|
||||||
cycleDaysSortedByDate = require('../db').getCycleDaysSortedByDate()
|
cycleDaysSortedByDate = require('../db').getCycleDaysSortedByDate()
|
||||||
maxBreakInBleeding = 1
|
maxBreakInBleeding = 1
|
||||||
maxCycleLength = 99
|
maxCycleLength = 99
|
||||||
minCyclesForPrediction = 3
|
minCyclesForPrediction = 3
|
||||||
} else {
|
} else {
|
||||||
bleedingDaysSortedByDate = opts.bleedingDaysSortedByDate || []
|
bleedingDaysSortedByDate = opts.bleedingDaysSortedByDate || []
|
||||||
|
cycleStartsSortedByDate = opts.cycleStartsSortedByDate || []
|
||||||
cycleDaysSortedByDate = opts.cycleDaysSortedByDate || []
|
cycleDaysSortedByDate = opts.cycleDaysSortedByDate || []
|
||||||
maxBreakInBleeding = opts.maxBreakInBleeding || 1
|
maxBreakInBleeding = opts.maxBreakInBleeding || 1
|
||||||
maxCycleLength = opts.maxCycleLength || 99
|
maxCycleLength = opts.maxCycleLength || 99
|
||||||
minCyclesForPrediction = opts.minCyclesForPrediction || 3
|
minCyclesForPrediction = opts.minCyclesForPrediction || 3
|
||||||
}
|
}
|
||||||
|
|
||||||
function findLatestMensesStart(bleedingDays) {
|
|
||||||
if (!bleedingDays.length) return null
|
|
||||||
|
|
||||||
// assumes bleeding days are ordered latest first, and
|
|
||||||
// excluded values already removed
|
|
||||||
const lastMensesStart = bleedingDays.find((day, i) => {
|
|
||||||
return noBleedingDayWithinThreshold(day, bleedingDays.slice(i + 1))
|
|
||||||
})
|
|
||||||
|
|
||||||
function noBleedingDayWithinThreshold(day, previousBleedingDays) {
|
|
||||||
const localDate = LocalDate.parse(day.date)
|
|
||||||
const threshold = localDate.minusDays(maxBreakInBleeding + 1).toString()
|
|
||||||
return !previousBleedingDays.some(({ date }) => date >= threshold)
|
|
||||||
}
|
|
||||||
|
|
||||||
return lastMensesStart
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLastMensesStartForDay(targetDateString) {
|
function getLastMensesStartForDay(targetDateString) {
|
||||||
// the index of the first bleeding day before the target day
|
return cycleStartsSortedByDate.find(start => start.date <= targetDateString)
|
||||||
const index = bleedingDaysSortedByDate.findIndex(day => {
|
|
||||||
return day.date <= targetDateString && !day.bleeding.exclude
|
|
||||||
})
|
|
||||||
|
|
||||||
if (index < 0) return null
|
|
||||||
|
|
||||||
const prevBleedingDays = bleedingDaysSortedByDate.slice(index)
|
|
||||||
return findLatestMensesStart(prevBleedingDays)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFollowingMensesStartForDay(targetDateString) {
|
|
||||||
const followingBleedingDays = bleedingDaysSortedByDate
|
|
||||||
.filter(day => !day.bleeding.exclude)
|
|
||||||
.reverse()
|
|
||||||
|
|
||||||
const firstBleedingDayAfterTargetDay = followingBleedingDays
|
|
||||||
.find(day => day.date > targetDateString)
|
|
||||||
|
|
||||||
return firstBleedingDayAfterTargetDay
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCycleDayNumber(targetDateString) {
|
function getCycleDayNumber(targetDateString) {
|
||||||
@@ -78,59 +44,44 @@ export default function config(opts) {
|
|||||||
return diffInDays + 1
|
return diffInDays + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCyclesBefore(targetCycleStartDay) {
|
|
||||||
return collectPreviousCycles([], targetCycleStartDay.date)
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectPreviousCycles(acc, startOfFollowingCycle) {
|
|
||||||
const cycle = getPreviousCycle(startOfFollowingCycle)
|
|
||||||
if (!cycle || !cycle.length) return acc
|
|
||||||
acc.push(cycle)
|
|
||||||
return collectPreviousCycles(acc, cycle[cycle.length - 1].date)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPreviousCycle(dateString) {
|
function getPreviousCycle(dateString) {
|
||||||
const startOfCycle = getLastMensesStartForDay(dateString)
|
|
||||||
if (!startOfCycle) return null
|
|
||||||
const dateBeforeStartOfCycle = LocalDate
|
|
||||||
.parse(startOfCycle.date)
|
|
||||||
.minusDays(1)
|
|
||||||
.toString()
|
|
||||||
|
|
||||||
return getCycleForDay(dateBeforeStartOfCycle)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCycleForDay(dayOrDate) {
|
|
||||||
const dateString = typeof dayOrDate === 'string' ? dayOrDate : dayOrDate.date
|
|
||||||
const cycleStart = getLastMensesStartForDay(dateString)
|
const cycleStart = getLastMensesStartForDay(dateString)
|
||||||
if (!cycleStart) return null
|
if (!cycleStart) return null
|
||||||
const cycleStartIndex = cycleDaysSortedByDate.indexOf(cycleStart)
|
const i = cycleDaysSortedByDate.indexOf(cycleStart)
|
||||||
const nextMensesStart = getFollowingMensesStartForDay(dateString)
|
const earlierCycleStart = cycleDaysSortedByDate[i - 1]
|
||||||
|
if (!earlierCycleStart) return null
|
||||||
|
return getCycleForStartDay(earlierCycleStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCyclesBefore(targetCycleStartDay) {
|
||||||
|
const startFromHere = cycleStartsSortedByDate.findIndex(start => {
|
||||||
|
return start.date < targetCycleStartDay.date
|
||||||
|
})
|
||||||
|
if (startFromHere < 0) return null
|
||||||
|
return cycleStartsSortedByDate
|
||||||
|
.slice(startFromHere)
|
||||||
|
.map(getCycleForStartDay)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCycleForStartDay(startDay) {
|
||||||
|
const cycleStartIndex = cycleDaysSortedByDate.indexOf(startDay)
|
||||||
|
const i = cycleStartsSortedByDate.indexOf(startDay)
|
||||||
|
const nextMensesStart = cycleStartsSortedByDate[i - 1]
|
||||||
if (nextMensesStart) {
|
if (nextMensesStart) {
|
||||||
return cycleDaysSortedByDate.slice(
|
return cycleDaysSortedByDate.slice(
|
||||||
cycleDaysSortedByDate.indexOf(nextMensesStart) + 1,
|
cycleDaysSortedByDate.indexOf(nextMensesStart) + 1,
|
||||||
cycleStartIndex + 1
|
cycleStartIndex + 1,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return cycleDaysSortedByDate.slice(0, cycleStartIndex + 1)
|
return cycleDaysSortedByDate.slice(0, cycleStartIndex + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllMensesStarts(initialBleedingDays = bleedingDaysSortedByDate) {
|
function getCycleForDay(dayOrDate) {
|
||||||
return recurse(initialBleedingDays.filter(d => !d.bleeding.exclude))
|
const dateString = typeof dayOrDate === 'string' ? dayOrDate : dayOrDate.date
|
||||||
|
const cycleStart = getLastMensesStartForDay(dateString)
|
||||||
function recurse(bleedingDays, collectedDates) {
|
if (!cycleStart) return null
|
||||||
collectedDates = collectedDates || []
|
return getCycleForStartDay(cycleStart)
|
||||||
const lastStart = findLatestMensesStart(bleedingDays)
|
|
||||||
if (!lastStart) {
|
|
||||||
return collectedDates
|
|
||||||
} else {
|
|
||||||
collectedDates.push(lastStart.date)
|
|
||||||
const index = bleedingDays.indexOf(lastStart)
|
|
||||||
const remainingDays = bleedingDays.slice(index + 1)
|
|
||||||
return recurse(remainingDays, collectedDates)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMensesStart(cycleDay) {
|
function isMensesStart(cycleDay) {
|
||||||
@@ -174,32 +125,33 @@ export default function config(opts) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCycleLength(cycleStartDates) {
|
function getAllCycleLengths() {
|
||||||
const cycleLengths = []
|
return cycleStartsSortedByDate
|
||||||
for (let i = 0; i < cycleStartDates.length - 1; i++) {
|
.map(day => LocalDate.parse(day.date))
|
||||||
const nextCycleStart = LocalDate.parse(cycleStartDates[i])
|
.reduce((lengths, cycleStart, i, startsAsLocalDates) => {
|
||||||
const cycleStart = LocalDate.parse(cycleStartDates[i + 1])
|
if (i === startsAsLocalDates.length - 1) return lengths
|
||||||
const cycleLength = cycleStart.until(nextCycleStart, DAYS)
|
const prevCycleStart = startsAsLocalDates[i + 1]
|
||||||
if (cycleLength <= maxCycleLength) { cycleLengths.push(cycleLength) }
|
const cycleLength = prevCycleStart.until(cycleStart, DAYS)
|
||||||
}
|
if (cycleLength <= maxCycleLength) { lengths.push(cycleLength) }
|
||||||
return cycleLengths
|
return lengths
|
||||||
|
}, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPredictedMenses() {
|
function getPredictedMenses() {
|
||||||
const allMensesStarts = getAllMensesStarts()
|
const allMensesStarts = cycleStartsSortedByDate
|
||||||
const atLeastOneCycle = allMensesStarts.length > 1
|
const atLeastOneCycle = allMensesStarts.length > 1
|
||||||
if (!atLeastOneCycle ||
|
if (!atLeastOneCycle ||
|
||||||
allMensesStarts.length < minCyclesForPrediction
|
allMensesStarts.length < minCyclesForPrediction
|
||||||
) {
|
) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
const cycleLengths = getCycleLength(allMensesStarts)
|
const cycleLengths = getAllCycleLengths()
|
||||||
const cycleInfo = getCycleLengthStats(cycleLengths)
|
const cycleInfo = getCycleLengthStats(cycleLengths)
|
||||||
const periodDistance = Math.round(cycleInfo.mean)
|
const periodDistance = Math.round(cycleInfo.mean)
|
||||||
let periodStartVariation
|
let periodStartVariation
|
||||||
if (cycleInfo.stdDeviation === null) {
|
if (cycleInfo.stdDeviation === null) {
|
||||||
periodStartVariation = 2
|
periodStartVariation = 2
|
||||||
} else if (cycleInfo.stdDeviation < 1.5) { // threshold is choosen a little arbitrarily
|
} else if (cycleInfo.stdDeviation < 1.5) { // threshold is chosen a little arbitrarily
|
||||||
periodStartVariation = 1
|
periodStartVariation = 1
|
||||||
} else {
|
} else {
|
||||||
periodStartVariation = 2
|
periodStartVariation = 2
|
||||||
@@ -207,7 +159,7 @@ export default function config(opts) {
|
|||||||
if (periodDistance - 5 < periodStartVariation) { // otherwise predictions overlap
|
if (periodDistance - 5 < periodStartVariation) { // otherwise predictions overlap
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
let lastStart = LocalDate.parse(allMensesStarts[0])
|
let lastStart = LocalDate.parse(allMensesStarts[0].date)
|
||||||
const predictedMenses = []
|
const predictedMenses = []
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
lastStart = lastStart.plusDays(periodDistance)
|
lastStart = lastStart.plusDays(periodDistance)
|
||||||
@@ -227,8 +179,7 @@ export default function config(opts) {
|
|||||||
getCycleForDay,
|
getCycleForDay,
|
||||||
getPreviousCycle,
|
getPreviousCycle,
|
||||||
getCyclesBefore,
|
getCyclesBefore,
|
||||||
getAllMensesStarts,
|
getAllCycleLengths,
|
||||||
getCycleLength,
|
|
||||||
getPredictedMenses,
|
getPredictedMenses,
|
||||||
isMensesStart,
|
isMensesStart,
|
||||||
getMensesDaysAfter
|
getMensesDaysAfter
|
||||||
|
|||||||
+166
-217
@@ -1,158 +1,80 @@
|
|||||||
import chai from 'chai'
|
import chai from 'chai'
|
||||||
import dirtyChai from 'dirty-chai'
|
import dirtyChai from 'dirty-chai'
|
||||||
import cycleModule from '../lib/cycle'
|
import cycleModule from '../lib/cycle'
|
||||||
import { LocalDate } from 'js-joda'
|
|
||||||
|
|
||||||
const expect = chai.expect
|
const expect = chai.expect
|
||||||
chai.use(dirtyChai)
|
chai.use(dirtyChai)
|
||||||
|
|
||||||
function useBleedingDays(days) {
|
// TODO getPreviousCycle
|
||||||
return cycleModule({ bleedingDaysSortedByDate: days }).getCycleDayNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('getCycleDay', () => {
|
describe('getCycleDayNumber', () => {
|
||||||
it('works for a simple example', () => {
|
it('works for a simple example', () => {
|
||||||
const bleedingDays = [{
|
const cycleStarts = [{
|
||||||
date: '2018-05-10',
|
|
||||||
bleeding: {
|
|
||||||
value: 2
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
date: '2018-05-09',
|
date: '2018-05-09',
|
||||||
|
isCycleStart: true,
|
||||||
bleeding: {
|
bleeding: {
|
||||||
value: 2
|
value: 2
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
date: '2018-05-03',
|
date: '2018-05-03',
|
||||||
bleeding: {
|
isCycleStart: true,
|
||||||
value: 2
|
bleeding: { value: 2 }
|
||||||
}
|
|
||||||
}]
|
}]
|
||||||
const getCycleDayNumber = useBleedingDays(bleedingDays)
|
const getCycleDayNumber = cycleModule({
|
||||||
|
cycleStartsSortedByDate: cycleStarts
|
||||||
|
}).getCycleDayNumber
|
||||||
const targetDate = '2018-05-17'
|
const targetDate = '2018-05-17'
|
||||||
const result = getCycleDayNumber(targetDate)
|
const result = getCycleDayNumber(targetDate)
|
||||||
expect(result).to.eql(9)
|
expect(result).to.eql(9)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('works if some bleedings are exluded', function () {
|
|
||||||
const bleedingDays = [{
|
|
||||||
date: '2018-05-10',
|
|
||||||
bleeding: {
|
|
||||||
value: 2,
|
|
||||||
exclude: true
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
date: '2018-05-09',
|
|
||||||
bleeding: {
|
|
||||||
value: 2,
|
|
||||||
exclude: true
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
date: '2018-05-03',
|
|
||||||
bleeding: {
|
|
||||||
value: 2
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
const targetDate = '2018-05-17'
|
|
||||||
const getCycleDayNumber = useBleedingDays(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', () => {
|
it('gets the correct number if the target day is not in the current cycle', () => {
|
||||||
const bleedingDays = [{
|
const cycleStarts = [{
|
||||||
date: '2018-05-13',
|
date: '2018-05-13',
|
||||||
bleeding: {
|
isCycleStart: true,
|
||||||
value: 2
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
date: '2018-04-11',
|
|
||||||
bleeding: {
|
bleeding: {
|
||||||
value: 2
|
value: 2
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
date: '2018-04-10',
|
date: '2018-04-10',
|
||||||
bleeding: {
|
isCycleStart: true,
|
||||||
value: 2
|
bleeding: { value: 2 }
|
||||||
}
|
|
||||||
}]
|
}]
|
||||||
|
|
||||||
const targetDate = '2018-04-27'
|
const targetDate = '2018-04-27'
|
||||||
const getCycleDayNumber = useBleedingDays(bleedingDays)
|
const getCycleDayNumber = cycleModule({
|
||||||
|
cycleStartsSortedByDate: cycleStarts
|
||||||
|
}).getCycleDayNumber
|
||||||
const result = getCycleDayNumber(targetDate)
|
const result = getCycleDayNumber(targetDate)
|
||||||
expect(result).to.eql(18)
|
expect(result).to.eql(18)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('gets the correct number if the target day is the only bleeding day', () => {
|
it('gets the correct number if the target day is the only bleeding day', () => {
|
||||||
const bleedingDays = [{
|
const cycleStarts = [{
|
||||||
date: '2018-05-13',
|
date: '2018-05-13',
|
||||||
bleeding: {
|
isCycleStart: true,
|
||||||
value: 2
|
bleeding: { value: 2 }
|
||||||
}
|
|
||||||
}]
|
}]
|
||||||
|
|
||||||
const targetDate = '2018-05-13'
|
const targetDate = '2018-05-13'
|
||||||
const getCycleDayNumber = useBleedingDays(bleedingDays)
|
const getCycleDayNumber = cycleModule({
|
||||||
|
cycleStartsSortedByDate: cycleStarts
|
||||||
|
}).getCycleDayNumber
|
||||||
const result = getCycleDayNumber(targetDate)
|
const result = getCycleDayNumber(targetDate)
|
||||||
expect(result).to.eql(1)
|
expect(result).to.eql(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getCycleDay returns null', () => {
|
it('returns null if there are no bleeding days', function () {
|
||||||
it('if there are no bleeding days', function () {
|
const cycleStarts = []
|
||||||
const bleedingDays = []
|
const targetDate = '2018-05-17'
|
||||||
const targetDate = '2018-05-17'
|
const getCycleDayNumber = cycleModule({
|
||||||
const getCycleDayNumber = useBleedingDays(bleedingDays)
|
cycleStartsSortedByDate: cycleStarts
|
||||||
const result = getCycleDayNumber(targetDate)
|
}).getCycleDayNumber
|
||||||
expect(result).to.be.null()
|
const result = getCycleDayNumber(targetDate)
|
||||||
})
|
expect(result).to.be.null()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getCycleDay with cycle thresholds', () => {
|
|
||||||
const maxBreakInBleeding = 3
|
|
||||||
|
|
||||||
it('disregards bleeding breaks shorter than max allowed bleeding break in a bleeding period', () => {
|
|
||||||
const bleedingDays = [{
|
|
||||||
date: '2018-05-14',
|
|
||||||
bleeding: {
|
|
||||||
value: 2
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
date: '2018-05-10',
|
|
||||||
bleeding: {
|
|
||||||
value: 2
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
const targetDate = '2018-05-17'
|
|
||||||
const getCycleDayNumber = cycleModule({
|
|
||||||
bleedingDaysSortedByDate: bleedingDays,
|
|
||||||
maxBreakInBleeding
|
|
||||||
}).getCycleDayNumber
|
|
||||||
const result = getCycleDayNumber(targetDate)
|
|
||||||
expect(result).to.eql(8)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('counts bleeding breaks longer than maxAllowedBleedingBreak in a bleeding period', () => {
|
|
||||||
const bleedingDays = [{
|
|
||||||
date: '2018-05-14',
|
|
||||||
bleeding: {
|
|
||||||
value: 2
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
date: '2018-05-09',
|
|
||||||
bleeding: {
|
|
||||||
value: 2
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
const targetDate = '2018-05-17'
|
|
||||||
const getCycleDayNumber = cycleModule({
|
|
||||||
bleedingDaysSortedByDate: bleedingDays,
|
|
||||||
maxBreakInBleeding
|
|
||||||
}).getCycleDayNumber
|
|
||||||
const result = getCycleDayNumber(targetDate)
|
|
||||||
expect(result).to.eql(4)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getCyclesBefore', () => {
|
describe('getCyclesBefore', () => {
|
||||||
@@ -196,9 +118,18 @@ describe('getCyclesBefore', () => {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const cycleStarts = [
|
||||||
|
'2018-07-05',
|
||||||
|
'2018-06-05',
|
||||||
|
'2018-05-03',
|
||||||
|
'2018-04-02'
|
||||||
|
]
|
||||||
|
|
||||||
const { getCyclesBefore } = cycleModule({
|
const { getCyclesBefore } = cycleModule({
|
||||||
cycleDaysSortedByDate,
|
cycleDaysSortedByDate,
|
||||||
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
|
cycleStartsSortedByDate: cycleDaysSortedByDate.filter(d => {
|
||||||
|
return cycleStarts.includes(d.date)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
const result = getCyclesBefore(cycleDaysSortedByDate[0])
|
const result = getCyclesBefore(cycleDaysSortedByDate[0])
|
||||||
expect(result.length).to.eql(3)
|
expect(result.length).to.eql(3)
|
||||||
@@ -282,9 +213,18 @@ describe('getCycleForDay', () => {
|
|||||||
bleeding: { value: 2 }
|
bleeding: { value: 2 }
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
const cycleStarts = [
|
||||||
|
'2018-07-05',
|
||||||
|
'2018-06-05',
|
||||||
|
'2018-05-03',
|
||||||
|
'2018-04-02'
|
||||||
|
]
|
||||||
|
|
||||||
const { getCycleForDay } = cycleModule({
|
const { getCycleForDay } = cycleModule({
|
||||||
cycleDaysSortedByDate,
|
cycleDaysSortedByDate,
|
||||||
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
|
cycleStartsSortedByDate: cycleDaysSortedByDate.filter(d => {
|
||||||
|
return cycleStarts.includes(d.date)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('gets cycle that has only one day', () => {
|
it('gets cycle that has only one day', () => {
|
||||||
@@ -350,16 +290,21 @@ describe('getPredictedMenses', () => {
|
|||||||
describe('cannot predict next menses', () => {
|
describe('cannot predict next menses', () => {
|
||||||
it('if no bleeding is documented', () => {
|
it('if no bleeding is documented', () => {
|
||||||
const cycleDaysSortedByDate = [ {} ]
|
const cycleDaysSortedByDate = [ {} ]
|
||||||
|
const cycleStarts = []
|
||||||
|
|
||||||
const { getPredictedMenses } = cycleModule({
|
const { getPredictedMenses } = cycleModule({
|
||||||
cycleDaysSortedByDate,
|
cycleDaysSortedByDate,
|
||||||
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding),
|
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding),
|
||||||
|
cycleStartsSortedByDate: cycleDaysSortedByDate.filter(d => {
|
||||||
|
return cycleStarts.includes(d.date)
|
||||||
|
}),
|
||||||
maxCycleLength: 99,
|
maxCycleLength: 99,
|
||||||
minCyclesForPrediction: 1
|
minCyclesForPrediction: 1
|
||||||
})
|
})
|
||||||
const result = getPredictedMenses()
|
const result = getPredictedMenses()
|
||||||
expect(result).to.eql([])
|
expect(result).to.eql([])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('if one bleeding is documented (no completed cycle)', () => {
|
it('if one bleeding is documented (no completed cycle)', () => {
|
||||||
const cycleDaysSortedByDate = [
|
const cycleDaysSortedByDate = [
|
||||||
{
|
{
|
||||||
@@ -367,16 +312,21 @@ describe('getPredictedMenses', () => {
|
|||||||
bleeding: { value: 2 }
|
bleeding: { value: 2 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
const cycleStarts = ['2018-06-02']
|
||||||
|
|
||||||
const { getPredictedMenses } = cycleModule({
|
const { getPredictedMenses } = cycleModule({
|
||||||
cycleDaysSortedByDate,
|
cycleDaysSortedByDate,
|
||||||
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding),
|
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding),
|
||||||
|
cycleStartsSortedByDate: cycleDaysSortedByDate.filter(d => {
|
||||||
|
return cycleStarts.includes(d.date)
|
||||||
|
}),
|
||||||
maxCycleLength: 99,
|
maxCycleLength: 99,
|
||||||
minCyclesForPrediction: 1
|
minCyclesForPrediction: 1
|
||||||
})
|
})
|
||||||
const result = getPredictedMenses()
|
const result = getPredictedMenses()
|
||||||
expect(result).to.eql([])
|
expect(result).to.eql([])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('if number of cycles is below minCyclesForPrediction', () => {
|
it('if number of cycles is below minCyclesForPrediction', () => {
|
||||||
const cycleDaysSortedByDate = [
|
const cycleDaysSortedByDate = [
|
||||||
{
|
{
|
||||||
@@ -392,10 +342,14 @@ describe('getPredictedMenses', () => {
|
|||||||
bleeding: { value: 2 }
|
bleeding: { value: 2 }
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
const cycleStarts = ['2018-06-01', '2018-05-01']
|
||||||
|
|
||||||
const { getPredictedMenses } = cycleModule({
|
const { getPredictedMenses } = cycleModule({
|
||||||
cycleDaysSortedByDate,
|
cycleDaysSortedByDate,
|
||||||
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
|
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding),
|
||||||
|
cycleStartsSortedByDate: cycleDaysSortedByDate.filter(d => {
|
||||||
|
return cycleStarts.includes(d.date)
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
const result = getPredictedMenses()
|
const result = getPredictedMenses()
|
||||||
expect(result).to.eql([])
|
expect(result).to.eql([])
|
||||||
@@ -413,10 +367,12 @@ describe('getPredictedMenses', () => {
|
|||||||
bleeding: { value: 2 }
|
bleeding: { value: 2 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
const cycleStarts = ['2018-07-15', '2018-07-01']
|
||||||
const { getPredictedMenses } = cycleModule({
|
const { getPredictedMenses } = cycleModule({
|
||||||
cycleDaysSortedByDate,
|
cycleDaysSortedByDate,
|
||||||
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding),
|
cycleStartsSortedByDate: cycleDaysSortedByDate.filter(d => {
|
||||||
|
return cycleStarts.includes(d.date)
|
||||||
|
}),
|
||||||
minCyclesForPrediction: 1
|
minCyclesForPrediction: 1
|
||||||
})
|
})
|
||||||
const result = getPredictedMenses()
|
const result = getPredictedMenses()
|
||||||
@@ -445,6 +401,7 @@ describe('getPredictedMenses', () => {
|
|||||||
]
|
]
|
||||||
expect(result).to.eql(expectedResult)
|
expect(result).to.eql(expectedResult)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('if number of cycles is above minCyclesForPrediction', () => {
|
it('if number of cycles is above minCyclesForPrediction', () => {
|
||||||
const cycleDaysSortedByDate = [
|
const cycleDaysSortedByDate = [
|
||||||
{
|
{
|
||||||
@@ -467,7 +424,8 @@ describe('getPredictedMenses', () => {
|
|||||||
|
|
||||||
const { getPredictedMenses } = cycleModule({
|
const { getPredictedMenses } = cycleModule({
|
||||||
cycleDaysSortedByDate,
|
cycleDaysSortedByDate,
|
||||||
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
|
cycleStartsSortedByDate: cycleDaysSortedByDate,
|
||||||
|
minCyclesForPrediction: 1
|
||||||
})
|
})
|
||||||
const result = getPredictedMenses()
|
const result = getPredictedMenses()
|
||||||
const expectedResult = [
|
const expectedResult = [
|
||||||
@@ -489,6 +447,7 @@ describe('getPredictedMenses', () => {
|
|||||||
]
|
]
|
||||||
expect(result).to.eql(expectedResult)
|
expect(result).to.eql(expectedResult)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('3 cycles with little standard deviation', () => {
|
it('3 cycles with little standard deviation', () => {
|
||||||
const cycleDaysSortedByDate = [
|
const cycleDaysSortedByDate = [
|
||||||
{
|
{
|
||||||
@@ -511,7 +470,7 @@ describe('getPredictedMenses', () => {
|
|||||||
|
|
||||||
const { getPredictedMenses } = cycleModule({
|
const { getPredictedMenses } = cycleModule({
|
||||||
cycleDaysSortedByDate,
|
cycleDaysSortedByDate,
|
||||||
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
|
cycleStartsSortedByDate: cycleDaysSortedByDate
|
||||||
})
|
})
|
||||||
const result = getPredictedMenses()
|
const result = getPredictedMenses()
|
||||||
const expectedResult = [
|
const expectedResult = [
|
||||||
@@ -533,6 +492,7 @@ describe('getPredictedMenses', () => {
|
|||||||
]
|
]
|
||||||
expect(result).to.eql(expectedResult)
|
expect(result).to.eql(expectedResult)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('3 cycles with bigger standard deviation', () => {
|
it('3 cycles with bigger standard deviation', () => {
|
||||||
const cycleDaysSortedByDate = [
|
const cycleDaysSortedByDate = [
|
||||||
{
|
{
|
||||||
@@ -555,7 +515,7 @@ describe('getPredictedMenses', () => {
|
|||||||
|
|
||||||
const { getPredictedMenses } = cycleModule({
|
const { getPredictedMenses } = cycleModule({
|
||||||
cycleDaysSortedByDate,
|
cycleDaysSortedByDate,
|
||||||
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
|
cycleStartsSortedByDate: cycleDaysSortedByDate
|
||||||
})
|
})
|
||||||
const result = getPredictedMenses()
|
const result = getPredictedMenses()
|
||||||
const expectedResult = [
|
const expectedResult = [
|
||||||
@@ -586,107 +546,6 @@ describe('getPredictedMenses', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getAllMensesStart', () => {
|
|
||||||
it('works for one cycle start', () => {
|
|
||||||
const cycleDaysSortedByDate = [
|
|
||||||
{
|
|
||||||
date: '2018-05-01',
|
|
||||||
bleeding: { value: 1 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const { getAllMensesStarts } = cycleModule({
|
|
||||||
cycleDaysSortedByDate,
|
|
||||||
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
|
|
||||||
})
|
|
||||||
const result = getAllMensesStarts()
|
|
||||||
expect(result.length).to.eql(1)
|
|
||||||
expect(result).to.eql(['2018-05-01'])
|
|
||||||
}),
|
|
||||||
it('works for two cycle starts', () => {
|
|
||||||
const cycleDaysSortedByDate = [
|
|
||||||
{
|
|
||||||
date: '2018-06-02',
|
|
||||||
bleeding: { value: 2 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: '2018-06-01',
|
|
||||||
bleeding: { value: 2 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: '2018-05-01',
|
|
||||||
bleeding: { value: 2 }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const { getAllMensesStarts } = cycleModule({
|
|
||||||
cycleDaysSortedByDate,
|
|
||||||
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
|
|
||||||
})
|
|
||||||
const result = getAllMensesStarts()
|
|
||||||
expect(result.length).to.eql(2)
|
|
||||||
expect(result).to.eql(['2018-06-01', '2018-05-01'])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('works for two cycle starts with excluded data', () => {
|
|
||||||
const cycleDaysSortedByDate = [
|
|
||||||
{
|
|
||||||
date: '2018-06-01',
|
|
||||||
bleeding: { value: 2 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: '2018-05-01',
|
|
||||||
bleeding: { value: 2 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
date: '2018-04-31',
|
|
||||||
bleeding: { value: 2 , exclude: true}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const { getAllMensesStarts } = cycleModule({
|
|
||||||
cycleDaysSortedByDate,
|
|
||||||
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
|
|
||||||
})
|
|
||||||
const result = getAllMensesStarts()
|
|
||||||
expect(result.length).to.eql(2)
|
|
||||||
expect(result).to.eql(['2018-06-01', '2018-05-01'])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an empty array if no bleeding days are given', () => {
|
|
||||||
const cycleDaysSortedByDate = [ {} ]
|
|
||||||
|
|
||||||
const { getAllMensesStarts } = cycleModule({
|
|
||||||
cycleDaysSortedByDate,
|
|
||||||
bleedingDaysSortedByDate: cycleDaysSortedByDate.filter(d => d.bleeding)
|
|
||||||
})
|
|
||||||
const result = getAllMensesStarts()
|
|
||||||
expect(result.length).to.eql(0)
|
|
||||||
expect(result).to.eql([])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('is not slow with 500 menses starts', () => {
|
|
||||||
const startDate = LocalDate.parse('2018-10-01')
|
|
||||||
const cycleDaysSortedByDate = Array(500)
|
|
||||||
.fill(null)
|
|
||||||
.map((_, i) => {
|
|
||||||
return {
|
|
||||||
date: startDate.minusMonths(i).toString(),
|
|
||||||
bleeding: { value: 2 }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const { getAllMensesStarts } = cycleModule({
|
|
||||||
cycleDaysSortedByDate,
|
|
||||||
bleedingDaysSortedByDate: cycleDaysSortedByDate
|
|
||||||
})
|
|
||||||
const start = Date.now()
|
|
||||||
const result = getAllMensesStarts()
|
|
||||||
const duration = Date.now() - start
|
|
||||||
expect(result.length).to.eql(500)
|
|
||||||
expect(duration).to.be.lessThan(100)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('isMensesStart', () => {
|
describe('isMensesStart', () => {
|
||||||
it('works for simple menses start', () => {
|
it('works for simple menses start', () => {
|
||||||
const cycleDaysSortedByDate = [
|
const cycleDaysSortedByDate = [
|
||||||
@@ -827,6 +686,51 @@ describe('isMensesStart', () => {
|
|||||||
const start = isMensesStart(cycleDaysSortedByDate[2])
|
const start = isMensesStart(cycleDaysSortedByDate[2])
|
||||||
expect(start).to.be.true()
|
expect(start).to.be.true()
|
||||||
})
|
})
|
||||||
|
describe('with cycle thresholds', () => {
|
||||||
|
const maxBreakInBleeding = 3
|
||||||
|
|
||||||
|
it('disregards bleeding breaks equal to max allowed bleeding break in a bleeding period', () => {
|
||||||
|
const bleedingDays = [{
|
||||||
|
date: '2018-05-14',
|
||||||
|
bleeding: {
|
||||||
|
value: 2
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
date: '2018-05-10',
|
||||||
|
bleeding: {
|
||||||
|
value: 2
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
const isMensesStart = cycleModule({
|
||||||
|
bleedingDaysSortedByDate: bleedingDays,
|
||||||
|
maxBreakInBleeding
|
||||||
|
}).isMensesStart
|
||||||
|
const result = isMensesStart(bleedingDays[0])
|
||||||
|
expect(result).to.be.false()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('counts bleeding breaks longer than maxAllowedBleedingBreak in a bleeding period', () => {
|
||||||
|
const bleedingDays = [{
|
||||||
|
date: '2018-05-14',
|
||||||
|
bleeding: {
|
||||||
|
value: 2
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
date: '2018-05-09',
|
||||||
|
bleeding: {
|
||||||
|
value: 2
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
const isMensesStart = cycleModule({
|
||||||
|
bleedingDaysSortedByDate: bleedingDays,
|
||||||
|
maxBreakInBleeding
|
||||||
|
}).isMensesStart
|
||||||
|
const result = isMensesStart(bleedingDays[0])
|
||||||
|
expect(result).to.be.true()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getMensesDaysAfter', () => {
|
describe('getMensesDaysAfter', () => {
|
||||||
@@ -999,4 +903,49 @@ describe('getMensesDaysAfter', () => {
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
describe('with cycle thresholds', () => {
|
||||||
|
const maxBreakInBleeding = 3
|
||||||
|
|
||||||
|
it('disregards bleeding breaks shorter than max allowed bleeding break in a bleeding period', () => {
|
||||||
|
const bleedingDays = [{
|
||||||
|
date: '2018-05-14',
|
||||||
|
bleeding: {
|
||||||
|
value: 2
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
date: '2018-05-10',
|
||||||
|
bleeding: {
|
||||||
|
value: 2
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
const getMensesDaysAfter = cycleModule({
|
||||||
|
bleedingDaysSortedByDate: bleedingDays,
|
||||||
|
maxBreakInBleeding
|
||||||
|
}).getMensesDaysAfter
|
||||||
|
const result = getMensesDaysAfter(bleedingDays[1])
|
||||||
|
expect(result).to.eql([bleedingDays[0]])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('counts bleeding breaks longer than maxAllowedBleedingBreak in a bleeding period', () => {
|
||||||
|
const bleedingDays = [{
|
||||||
|
date: '2018-05-14',
|
||||||
|
bleeding: {
|
||||||
|
value: 2
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
date: '2018-05-09',
|
||||||
|
bleeding: {
|
||||||
|
value: 2
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
const getMensesDaysAfter = cycleModule({
|
||||||
|
bleedingDaysSortedByDate: bleedingDays,
|
||||||
|
maxBreakInBleeding
|
||||||
|
}).getMensesDaysAfter
|
||||||
|
const result = getMensesDaysAfter(bleedingDays[1])
|
||||||
|
expect(result).to.eql([])
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
Reference in New Issue
Block a user