Change getCycleDay signature and process LocalDate
This commit is contained in:
+5
-2
@@ -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 (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.welcome}>{formatDateForViewHeader(day.date)}</Text>
|
||||
<Text>Cycle day {getCycleDay()}</Text>
|
||||
<Text>Cycle day {getCycleDay(day.date)}</Text>
|
||||
<Text>Bleeding</Text>
|
||||
<RadioForm
|
||||
radio_props={bleedingRadioProps}
|
||||
|
||||
+5
-2
@@ -7,7 +7,10 @@ import {
|
||||
import styles from './styles'
|
||||
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 DayView extends Component {
|
||||
constructor(props) {
|
||||
@@ -31,7 +34,7 @@ export default class DayView extends Component {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<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>
|
||||
<Button
|
||||
onPress={() => navigate('bleeding', { cycleDay: day })}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import realm from 'realm'
|
||||
|
||||
let db
|
||||
let cycleDaysSortedbyTempValueView = []
|
||||
let bleedingDaysSortedByDate = []
|
||||
import Realm from 'realm'
|
||||
|
||||
const TemperatureSchema = {
|
||||
name: 'Temperature',
|
||||
@@ -36,21 +32,18 @@ const CycleDaySchema = {
|
||||
}
|
||||
}
|
||||
|
||||
async function openDatabase() {
|
||||
db = await realm.open({
|
||||
schema: [
|
||||
CycleDaySchema,
|
||||
TemperatureSchema,
|
||||
BleedingSchema
|
||||
],
|
||||
// we only want this in dev mode
|
||||
deleteRealmIfMigrationNeeded: true
|
||||
})
|
||||
// just for testing purposes, the highest temperature will be topmost
|
||||
// because I was too layz to make a scroll view
|
||||
cycleDaysSortedbyTempValueView = db.objects('CycleDay').filtered('temperature != null').sorted('temperature.value', true)
|
||||
bleedingDaysSortedByDate = db.objects('CycleDay').filtered('bleeding != null').sorted('date', true)
|
||||
}
|
||||
const db = new Realm({
|
||||
schema: [
|
||||
CycleDaySchema,
|
||||
TemperatureSchema,
|
||||
BleedingSchema
|
||||
],
|
||||
// we only want this in dev mode
|
||||
deleteRealmIfMigrationNeeded: true
|
||||
})
|
||||
|
||||
const cycleDaysSortedbyTempValueView = db.objects('CycleDay').filtered('temperature != null').sorted('temperature.value', true)
|
||||
const bleedingDaysSortedByDate = db.objects('CycleDay').filtered('bleeding != null').sorted('date', true)
|
||||
|
||||
function saveTemperature(date, temperature) {
|
||||
db.write(() => {
|
||||
@@ -62,6 +55,8 @@ function saveTemperature(date, temperature) {
|
||||
})
|
||||
}
|
||||
|
||||
const getCycleDaysSortedByDateView = () => db.objects('CycleDay').sorted('date', true)
|
||||
|
||||
function saveBleeding(cycleDay, bleeding) {
|
||||
db.write(() => {
|
||||
cycleDay.bleeding = bleeding
|
||||
@@ -80,11 +75,18 @@ function getOrCreateCycleDay(localDate) {
|
||||
return result
|
||||
}
|
||||
|
||||
function deleteAll() {
|
||||
db.write(() => {
|
||||
db.deleteAll()
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
cycleDaysSortedbyTempValueView,
|
||||
openDatabase,
|
||||
saveTemperature,
|
||||
saveBleeding,
|
||||
getOrCreateCycleDay,
|
||||
bleedingDaysSortedByDate
|
||||
bleedingDaysSortedByDate,
|
||||
getCycleDaysSortedByDateView,
|
||||
deleteAll
|
||||
}
|
||||
+18
-29
@@ -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 || {
|
||||
// at the very minimum, a cycle can be a bleeding day
|
||||
// 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
|
||||
// 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
|
||||
targetDate = moment(targetDate)
|
||||
const sorted = unsorted
|
||||
const targetDate = LocalDate.parse(targetDateString)
|
||||
const withWrappedDates = bleedingDaysSortedByDateView
|
||||
.filter(day => !day.bleeding.exclude)
|
||||
.map(day => {
|
||||
day.date = moment(day.date)
|
||||
day.wrappedDate = LocalDate.parse(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)
|
||||
// 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)
|
||||
|
||||
const lastPeriodStart = cycleDays.find((day, i) => {
|
||||
if (
|
||||
isBleedingDay(day) &&
|
||||
thereIsNoPreviousBleedingDayWithinTheThreshold(day, cycleDays.slice(i + 1), opts.minCycleLengthInDays)) {
|
||||
return true
|
||||
}
|
||||
return thereIsNoPreviousBleedingDayWithinTheThreshold(day, cycleDays.slice(i + 1), opts.maxBreakInBleeding)
|
||||
})
|
||||
|
||||
if (!lastPeriodStart) return null
|
||||
|
||||
// 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))
|
||||
const diffInDays = lastPeriodStart.wrappedDate.until(targetDate, joda.ChronoUnit.DAYS)
|
||||
|
||||
// cycle starts at day 1
|
||||
return diffInDays + 1
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
function thereIsNoPreviousBleedingDayWithinTheThreshold(bleedingDay, earlierCycleDays, allowedBleedingBreak) {
|
||||
const periodThreshold = bleedingDay.wrappedDate.minusDays(allowedBleedingBreak + 1)
|
||||
return !earlierCycleDays.some(({ wrappedDate }) => wrappedDate.equals(periodThreshold) || wrappedDate.isAfter(periodThreshold))
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export default () => 6
|
||||
@@ -5,18 +5,30 @@ import {
|
||||
Text
|
||||
} from 'react-native'
|
||||
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 {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
welcomeText: cycleDayNumber ? welcomeTextWithCycleDay : welcomeText
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const navigate = this.props.navigation.navigate
|
||||
return (
|
||||
<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
|
||||
onPress={() => navigate('temperatureList')}
|
||||
title="Edit symptoms for today">
|
||||
@@ -25,7 +37,11 @@ export default class Home extends Component {
|
||||
onPress={() => navigate('calendar')}
|
||||
title="Go to calendar">
|
||||
</Button>
|
||||
<Button
|
||||
onPress={() => deleteAll()}
|
||||
title="delete everything">
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
AppRegistry.registerComponent('home', () => App)
|
||||
Generated
+5
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
+50
-157
@@ -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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user