Change getCycleDay signature and process LocalDate

This commit is contained in:
Julia Friesel
2018-06-09 12:00:06 +02:00
parent 1430734874
commit 5b406848cb
10 changed files with 129 additions and 223 deletions
+5 -2
View File
@@ -10,7 +10,10 @@ import styles from './styles'
import { saveBleeding } from './db' import { saveBleeding } from './db'
import { formatDateForViewHeader } from './format' import { formatDateForViewHeader } from './format'
import { bleeding as labels } from './labels' 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 { export default class Bleeding extends Component {
constructor(props) { constructor(props) {
@@ -40,7 +43,7 @@ export default class Bleeding extends Component {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.welcome}>{formatDateForViewHeader(day.date)}</Text> <Text style={styles.welcome}>{formatDateForViewHeader(day.date)}</Text>
<Text>Cycle day {getCycleDay()}</Text> <Text>Cycle day {getCycleDay(day.date)}</Text>
<Text>Bleeding</Text> <Text>Bleeding</Text>
<RadioForm <RadioForm
radio_props={bleedingRadioProps} radio_props={bleedingRadioProps}
+5 -2
View File
@@ -7,7 +7,10 @@ import {
import styles from './styles' import styles from './styles'
import { formatDateForViewHeader } from './format' import { formatDateForViewHeader } from './format'
import { bleeding as labels} from './labels' 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 DayView extends Component { export default class DayView extends Component {
constructor(props) { constructor(props) {
@@ -31,7 +34,7 @@ export default class DayView extends Component {
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.welcome}>{formatDateForViewHeader(day.date)}</Text> <Text style={styles.welcome}>{formatDateForViewHeader(day.date)}</Text>
<Text>Cycle day {getCycleDay()}</Text> <Text>Cycle day {getCycleDay(day.date)}</Text>
<Text style={styles.welcome}>{bleedingLabel}</Text> <Text style={styles.welcome}>{bleedingLabel}</Text>
<Button <Button
onPress={() => navigate('bleeding', { cycleDay: day })} onPress={() => navigate('bleeding', { cycleDay: day })}
+17 -15
View File
@@ -1,8 +1,4 @@
import realm from 'realm' import Realm from 'realm'
let db
let cycleDaysSortedbyTempValueView = []
let bleedingDaysSortedByDate = []
const TemperatureSchema = { const TemperatureSchema = {
name: 'Temperature', name: 'Temperature',
@@ -36,8 +32,7 @@ const CycleDaySchema = {
} }
} }
async function openDatabase() { const db = new Realm({
db = await realm.open({
schema: [ schema: [
CycleDaySchema, CycleDaySchema,
TemperatureSchema, TemperatureSchema,
@@ -45,12 +40,10 @@ async function openDatabase() {
], ],
// we only want this in dev mode // we only want this in dev mode
deleteRealmIfMigrationNeeded: true deleteRealmIfMigrationNeeded: true
}) })
// just for testing purposes, the highest temperature will be topmost
// because I was too layz to make a scroll view const cycleDaysSortedbyTempValueView = db.objects('CycleDay').filtered('temperature != null').sorted('temperature.value', true)
cycleDaysSortedbyTempValueView = db.objects('CycleDay').filtered('temperature != null').sorted('temperature.value', true) const bleedingDaysSortedByDate = db.objects('CycleDay').filtered('bleeding != null').sorted('date', true)
bleedingDaysSortedByDate = db.objects('CycleDay').filtered('bleeding != null').sorted('date', true)
}
function saveTemperature(date, temperature) { function saveTemperature(date, temperature) {
db.write(() => { db.write(() => {
@@ -62,6 +55,8 @@ function saveTemperature(date, temperature) {
}) })
} }
const getCycleDaysSortedByDateView = () => db.objects('CycleDay').sorted('date', true)
function saveBleeding(cycleDay, bleeding) { function saveBleeding(cycleDay, bleeding) {
db.write(() => { db.write(() => {
cycleDay.bleeding = bleeding cycleDay.bleeding = bleeding
@@ -80,11 +75,18 @@ function getOrCreateCycleDay(localDate) {
return result return result
} }
function deleteAll() {
db.write(() => {
db.deleteAll()
})
}
export { export {
cycleDaysSortedbyTempValueView, cycleDaysSortedbyTempValueView,
openDatabase,
saveTemperature, saveTemperature,
saveBleeding, saveBleeding,
getOrCreateCycleDay, getOrCreateCycleDay,
bleedingDaysSortedByDate bleedingDaysSortedByDate,
getCycleDaysSortedByDateView,
deleteAll
} }
+18 -29
View File
@@ -1,54 +1,43 @@
import moment from 'moment' import * as joda from 'js-joda'
export default function config(opts) { const LocalDate = joda.LocalDate
export default function config(bleedingDaysSortedByDateView, opts) {
opts = opts || { opts = opts || {
// at the very minimum, a cycle can be a bleeding day // at the very minimum, a cycle can be a bleeding day
// followed by a non-bleeding day, thus a length of 2 // followed by a non-bleeding day, thus a length of 2
minCycleLengthInDays: 2 maxBreakInBleeding: 1
} }
return function getCycleDayNumber(unsorted, targetDate) { return function getCycleDayNumber(targetDateString) {
// sort the cycle days in descending order so we travel into // sort the cycle days in descending order so we travel into
// the past as we iterate over the array // the past as we iterate over the array
// also, to retrieve the number, we only need the cycle days before the target day // 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 targetDate = LocalDate.parse(targetDateString)
targetDate = moment(targetDate) const withWrappedDates = bleedingDaysSortedByDateView
const sorted = unsorted .filter(day => !day.bleeding.exclude)
.map(day => { .map(day => {
day.date = moment(day.date) day.wrappedDate = LocalDate.parse(day.date)
return day return day
}) })
.sort((a, b) => b.date.isAfter(a.date)) // TODO write test for what if there is no first day before?? aka no firstbleedingdaybeforeindex
const firstDayBeforeTargetDayIndex = sorted.findIndex(day => day.date.isBefore(targetDate)) const firstBleedingDayBeforeTargetDayIndex = withWrappedDates.findIndex(day => day.wrappedDate.isBefore(targetDate))
const cycleDays = sorted.slice(firstDayBeforeTargetDayIndex) const cycleDays = withWrappedDates.slice(firstBleedingDayBeforeTargetDayIndex)
const lastPeriodStart = cycleDays.find((day, i) => { const lastPeriodStart = cycleDays.find((day, i) => {
if ( return thereIsNoPreviousBleedingDayWithinTheThreshold(day, cycleDays.slice(i + 1), opts.maxBreakInBleeding)
isBleedingDay(day) &&
thereIsNoPreviousBleedingDayWithinTheThreshold(day, cycleDays.slice(i + 1), opts.minCycleLengthInDays)) {
return true
}
}) })
if (!lastPeriodStart) return null if (!lastPeriodStart) return null
// by default, moment.diff rounds down, so we get the decimal number const diffInDays = lastPeriodStart.wrappedDate.until(targetDate, joda.ChronoUnit.DAYS)
// and round it ourselves
const diffInDays = Math.round(targetDate.diff(lastPeriodStart.date, 'days', true))
// cycle starts at day 1 // cycle starts at day 1
return diffInDays + 1 return diffInDays + 1
} }
} }
function thereIsNoPreviousBleedingDayWithinTheThreshold(bleedingDay, earlierCycleDays, threshold) { function thereIsNoPreviousBleedingDayWithinTheThreshold(bleedingDay, earlierCycleDays, allowedBleedingBreak) {
// the bleeding day itself is included in the threshold, thus we subtract 1 const periodThreshold = bleedingDay.wrappedDate.minusDays(allowedBleedingBreak + 1)
const minCycleThresholdDate = moment(bleedingDay.date).subtract(threshold - 1, 'days') return !earlierCycleDays.some(({ wrappedDate }) => wrappedDate.equals(periodThreshold) || wrappedDate.isAfter(periodThreshold))
return !earlierCycleDays.some(day => {
return day.date.isSameOrAfter(minCycleThresholdDate, 'day') && isBleedingDay(day)
})
}
function isBleedingDay(day) {
return day.bleeding && day.bleeding.value && !day.bleeding.exclude
} }
-1
View File
@@ -1 +0,0 @@
export default () => 6
+18 -2
View File
@@ -5,18 +5,30 @@ import {
Text Text
} from 'react-native' } from 'react-native'
import styles from './styles' import styles from './styles'
import getCycleDay from './get-cycle-day' import cycleDayModule from './get-cycle-day-number'
import { getCycleDaysSortedByDateView, 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`
export default class Home extends Component { export default class Home extends Component {
constructor(props) { constructor(props) {
super(props) super(props)
this.state = {
welcomeText: cycleDayNumber ? welcomeTextWithCycleDay : welcomeText
}
} }
render() { render() {
const navigate = this.props.navigation.navigate const navigate = this.props.navigation.navigate
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text style={styles.welcome}>Welcome! Today is day {getCycleDay()} of your current cycle</Text> <Text style={styles.welcome}>{this.state.welcomeText}</Text>
<Button <Button
onPress={() => navigate('temperatureList')} onPress={() => navigate('temperatureList')}
title="Edit symptoms for today"> title="Edit symptoms for today">
@@ -25,6 +37,10 @@ export default class Home extends Component {
onPress={() => navigate('calendar')} onPress={() => navigate('calendar')}
title="Go to calendar"> title="Go to calendar">
</Button> </Button>
<Button
onPress={() => deleteAll()}
title="delete everything">
</Button>
</View> </View>
) )
} }
+2 -7
View File
@@ -1,9 +1,4 @@
import { AppRegistry } from 'react-native' import { AppRegistry } from 'react-native'
import Home from './app' import App from './app'
import { openDatabase } from './db'
// TODO error handling AppRegistry.registerComponent('home', () => App)
openDatabase()
.then(() => {
AppRegistry.registerComponent('home', () => Home)
})
+5
View File
@@ -4693,6 +4693,11 @@
"merge-stream": "^1.0.1" "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": { "js-tokens": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+1
View File
@@ -14,6 +14,7 @@
"lint": "eslint app test" "lint": "eslint app test"
}, },
"dependencies": { "dependencies": {
"js-joda": "^1.8.2",
"moment": "^2.22.1", "moment": "^2.22.1",
"react": "16.3.1", "react": "16.3.1",
"react-native": "0.55.4", "react-native": "0.55.4",
+50 -157
View File
@@ -7,233 +7,126 @@ chai.use(dirtyChai)
import getCycleDayNumberModule from '../get-cycle-day-number' import getCycleDayNumberModule from '../get-cycle-day-number'
describe('getCycleDay', () => { describe('getCycleDay', () => {
const getCycleDayNumber = getCycleDayNumberModule() it('works for a simple example', function () {
it('works if the last data entered is a bleeding day', function () { const bleedingDays = [{
const cycleDays = [{ date: '2018-05-10',
date: new Date(2018, 5, 2)
}, {
date: new Date(2018, 5, 3),
bleeding: { bleeding: {
value: 2 value: 2
} }
}, { }, {
date: new Date(2018, 5, 4) date: '2018-05-09',
}, {
date: new Date(2018, 5, 9),
bleeding: { bleeding: {
value: 2 value: 2
} }
}, { }, {
date: new Date(2018, 5, 10), date: '2018-05-03',
bleeding: { bleeding: {
value: 2 value: 2
} }
}] }]
const targetDate = new Date(2018, 5, 17) const getCycleDayNumber = getCycleDayNumberModule(bleedingDays)
const result = getCycleDayNumber(cycleDays, targetDate) const targetDate = '2018-05-17'
expect(result).to.eql(9) const result = getCycleDayNumber(targetDate)
})
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)
expect(result).to.eql(9) expect(result).to.eql(9)
}) })
it('works if some bleedings are exluded', function () { it('works if some bleedings are exluded', function () {
const cycleDays = [{ const bleedingDays = [{
date: new Date(2018, 5, 2) 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: { bleeding: {
value: 2 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 targetDate = '2018-05-17'
const result = getCycleDayNumber(cycleDays, targetDate) const getCycleDayNumber = getCycleDayNumberModule(bleedingDays)
const result = getCycleDayNumber(targetDate)
expect(result).to.eql(15) 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 cycleDays = [{ const bleedingDays = [{
date: new Date(2018, 5, 14), date: '2018-05-13',
}, {
date: new Date(2018, 5, 13),
bleeding: { bleeding: {
value: 2 value: 2
} }
}, { }, {
date: new Date(2018, 4, 12), date: '2018-04-11',
}, {
date: new Date(2018, 4, 11),
bleeding: { bleeding: {
value: 2 value: 2
} }
}, { }, {
date: new Date(2018, 4, 10), date: '2018-04-10',
bleeding: { bleeding: {
value: 2 value: 2
} }
}, {
date: new Date(2018, 4, 9),
}] }]
const targetDate = new Date(2018, 4, 27) const targetDate = '2018-04-27'
const result = getCycleDayNumber(cycleDays, targetDate) const getCycleDayNumber = getCycleDayNumberModule(bleedingDays)
const result = getCycleDayNumber(targetDate)
expect(result).to.eql(18) expect(result).to.eql(18)
}) })
}) })
describe('getCycleDay returns null', () => { describe('getCycleDay returns null', () => {
const getCycleDayNumber = getCycleDayNumberModule()
it('if there are no bleeding days', function () { it('if there are no bleeding days', function () {
const cycleDays = [{ const bleedingDays = []
date: new Date(2018, 5, 2) const targetDate = '2018-05-17'
}, { const getCycleDayNumber = getCycleDayNumberModule(bleedingDays)
date: new Date(2018, 5, 4) const result = getCycleDayNumber(targetDate)
}, {
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)
expect(result).to.be.null() expect(result).to.be.null()
}) })
}) })
describe('getCycleDay with cycle thresholds', () => { describe('getCycleDay with cycle thresholds', () => {
const getCycleDayNumber = getCycleDayNumberModule({ const opts = { maxBreakInBleeding: 3 }
minCycleLengthInDays: 5
})
it('disregards bleeding breaks shorter than the min cycle threshold in a bleeding period', () => { it('disregards bleeding breaks shorter than max allowed bleeding break in a bleeding period', () => {
const cycleDays = [{ const bleedingDays = [{
date: new Date(2018, 5, 10), date: '2018-05-14',
bleeding: { bleeding: {
value: 2 value: 2
} }
}, { }, {
date: new Date(2018, 5, 14), date: '2018-05-10',
bleeding: { bleeding: {
value: 2 value: 2
} }
}] }]
const targetDate = new Date(2018, 5, 17) const targetDate = '2018-05-17'
const result = getCycleDayNumber(cycleDays, targetDate) const getCycleDayNumber = getCycleDayNumberModule(bleedingDays, opts)
const result = getCycleDayNumber(targetDate)
expect(result).to.eql(8) expect(result).to.eql(8)
}) })
it('counts bleeding breaks longer than the min cycle threshold in a bleeding period', () => { it('counts bleeding breaks longer than maxAllowedBleedingBreak in a bleeding period', () => {
const cycleDays = [{ const bleedingDays = [{
date: new Date(2018, 5, 9), date: '2018-05-14',
bleeding: { bleeding: {
value: 2 value: 2
} }
}, { }, {
date: new Date(2018, 5, 14), date: '2018-05-09',
bleeding: { bleeding: {
value: 2 value: 2
} }
}] }]
const targetDate = new Date(2018, 5, 17) const targetDate = '2018-05-17'
const result = getCycleDayNumber(cycleDays, targetDate) const getCycleDayNumber = getCycleDayNumberModule(bleedingDays, opts)
const result = getCycleDayNumber(targetDate)
expect(result).to.eql(4) expect(result).to.eql(4)
}) })
}) })