Merge branch '11-make-getcycleday-module' into 'master'

Resolve "Make getCycleDay module"

Closes #11

See merge request bloodyhealth/drip!5
This commit is contained in:
Julia Friesel
2018-06-13 12:08:45 +00:00
13 changed files with 284 additions and 46 deletions
+4 -2
View File
@@ -10,7 +10,9 @@ 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'
const getCycleDayNumber = cycleDayModule()
export default class Bleeding extends Component {
constructor(props) {
@@ -39,7 +41,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 {getCycleDayNumber(day.date)}</Text>
<Text>Bleeding</Text>
<RadioForm
radio_props={bleedingRadioProps}
+1 -1
View File
@@ -13,7 +13,7 @@ export default class DatePickView extends Component {
}
componentWillUnmount() {
bleedingDaysSortedByDate.removeListener(setStateWithCalendarFormattedDays)
bleedingDaysSortedByDate.removeAllListeners()
}
passDateToDayView(result) {
+19 -4
View File
@@ -7,19 +7,28 @@ 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 { bleedingDaysSortedByDate } from './db'
const getCycleDayNumber = cycleDayModule()
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: getCycleDayNumber(this.cycleDay.date),
}
bleedingDaysSortedByDate.addListener(setStateWithCurrentCycleDayNumber.bind(this))
}
componentWillUnmount() {
bleedingDaysSortedByDate.removeAllListeners()
}
render() {
const navigate = this.props.navigation.navigate
const cycleDay = this.state.cycleDay
const cycleDay = this.cycleDay
const bleedingValue = cycleDay.bleeding && cycleDay.bleeding.value
let bleedingLabel
if (typeof bleedingValue === 'number') {
@@ -40,7 +49,7 @@ export default class DayView extends Component {
return (
<View style={styles.container}>
<Text style={styles.welcome}>{formatDateForViewHeader(cycleDay.date)}</Text>
<Text>Cycle day {getCycleDay()}</Text>
<Text>Cycle day {getCycleDayNumber(cycleDay.date)}</Text>
<Text style={styles.welcome}>{bleedingLabel}</Text>
<Text style={styles.welcome}>{temperatureLabel}</Text>
<Button
@@ -55,3 +64,9 @@ export default class DayView extends Component {
)
}
}
function setStateWithCurrentCycleDayNumber() {
this.setState({
cycleDayNumber: getCycleDayNumber(this.cycleDay.date)
})
}
+14 -13
View File
@@ -1,10 +1,6 @@
import realm from 'realm'
import Realm from 'realm'
import { LocalDate } from 'js-joda'
let db
let cycleDaysSortedbyDate = []
let bleedingDaysSortedByDate = []
let temperatureDaysSortedByDate
const TemperatureSchema = {
name: 'Temperature',
@@ -38,8 +34,7 @@ const CycleDaySchema = {
}
}
async function openDatabase() {
db = await realm.open({
const db = new Realm({
schema: [
CycleDaySchema,
TemperatureSchema,
@@ -49,10 +44,8 @@ async function openDatabase() {
deleteRealmIfMigrationNeeded: true
})
cycleDaysSortedbyDate = db.objects('CycleDay').sorted('date', true)
bleedingDaysSortedByDate = db.objects('CycleDay').filtered('bleeding != null').sorted('date', true)
temperatureDaysSortedByDate = db.objects('CycleDay').filtered('temperature != null').sorted('date', true)
}
const bleedingDaysSortedByDate = db.objects('CycleDay').filtered('bleeding != null').sorted('date', true)
const temperatureDaysSortedByDate = db.objects('CycleDay').filtered('temperature != null').sorted('date', true)
function saveTemperature(cycleDay, temperature) {
db.write(() => {
@@ -60,6 +53,8 @@ function saveTemperature(cycleDay, temperature) {
})
}
const getCycleDaysSortedByDateView = () => db.objects('CycleDay').sorted('date', true)
function saveBleeding(cycleDay, bleeding) {
db.write(() => {
cycleDay.bleeding = bleeding
@@ -78,6 +73,12 @@ function getOrCreateCycleDay(localDate) {
return result
}
function deleteAll() {
db.write(() => {
db.deleteAll()
})
}
function getPreviousTemperature(cycleDay) {
cycleDay.wrappedDate = LocalDate.parse(cycleDay.date)
const winner = temperatureDaysSortedByDate.find(day => {
@@ -89,11 +90,11 @@ function getPreviousTemperature(cycleDay) {
}
export {
cycleDaysSortedbyDate,
openDatabase,
saveTemperature,
saveBleeding,
getOrCreateCycleDay,
bleedingDaysSortedByDate,
getCycleDaysSortedByDateView,
deleteAll,
getPreviousTemperature
}
+48
View File
@@ -0,0 +1,48 @@
import * as joda from 'js-joda'
const LocalDate = joda.LocalDate
export default function config(opts = {}) {
let bleedingDaysSortedByDate
if (!opts.bleedingDaysSortedByDate) {
// we only want to require (and run) the db module when not running the tests
bleedingDaysSortedByDate = require('./db').bleedingDaysSortedByDate
} else {
bleedingDaysSortedByDate = opts.bleedingDaysSortedByDate
}
const maxBreakInBleeding = opts.maxBreakInBleeding || 1
return function getCycleDayNumber(targetDateString) {
const targetDate = LocalDate.parse(targetDateString)
const withWrappedDates = bleedingDaysSortedByDate
.filter(day => !day.bleeding.exclude)
.map(day => {
day.wrappedDate = LocalDate.parse(day.date)
return day
})
const firstBleedingDayBeforeTargetDayIndex = withWrappedDates.findIndex(day => {
return (
day.wrappedDate.isEqual(targetDate) ||
day.wrappedDate.isBefore(targetDate)
)
})
if (firstBleedingDayBeforeTargetDayIndex < 0) return null
const previousBleedingDays = withWrappedDates.slice(firstBleedingDayBeforeTargetDayIndex)
const lastPeriodStart = previousBleedingDays.find((day, i) => {
return thereIsNoPreviousBleedingDayWithinTheThreshold(day, previousBleedingDays.slice(i + 1), maxBreakInBleeding)
})
const diffInDays = lastPeriodStart.wrappedDate.until(targetDate, joda.ChronoUnit.DAYS)
// cycle starts at day 1
return diffInDays + 1
}
}
function thereIsNoPreviousBleedingDayWithinTheThreshold(bleedingDay, earlierCycleDays, allowedBleedingBreak) {
const periodThreshold = bleedingDay.wrappedDate.minusDays(allowedBleedingBreak + 1)
return !earlierCycleDays.some(({ wrappedDate }) => wrappedDate.equals(periodThreshold) || wrappedDate.isAfter(periodThreshold))
}
-1
View File
@@ -1 +0,0 @@
export default () => 6
+31 -3
View File
@@ -6,12 +6,26 @@ import {
} from 'react-native'
import { LocalDate } from 'js-joda'
import styles from './styles'
import getCycleDay from './get-cycle-day'
import { getOrCreateCycleDay } from './db'
import cycleDayModule from './get-cycle-day-number'
import { getOrCreateCycleDay, bleedingDaysSortedByDate, deleteAll } from './db'
const getCycleDayNumber = cycleDayModule()
export default class Home extends Component {
constructor(props) {
super(props)
this.todayDateString = LocalDate.now().toString()
const cycleDayNumber = getCycleDayNumber(this.todayDateString)
this.state = {
welcomeText: determineWelcomeText(cycleDayNumber)
}
bleedingDaysSortedByDate.addListener(setStateWithCurrentWelcomeText.bind(this))
}
componentWillUnmount() {
bleedingDaysSortedByDate.removeAllListeners()
}
passTodayToDayView() {
@@ -25,7 +39,7 @@ export default class Home extends Component {
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={() => this.passTodayToDayView()}
title="Edit symptoms for today">
@@ -34,7 +48,21 @@ export default class Home extends Component {
onPress={() => navigate('calendar')}
title="Go to calendar">
</Button>
<Button
onPress={() => deleteAll()}
title="delete everything">
</Button>
</View>
)
}
}
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)) })
}
+2 -7
View File
@@ -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)
+6
View File
@@ -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",
+1
View File
@@ -27,6 +27,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",
+4 -2
View File
@@ -10,7 +10,9 @@ import {
import styles from './styles'
import { saveTemperature, getPreviousTemperature } from './db'
import { formatDateForViewHeader } from './format'
import getCycleDay from './get-cycle-day'
import cycleDayModule from './get-cycle-day-number'
const getCycleDayNumber = cycleDayModule()
export default class Temp extends Component {
constructor(props) {
@@ -37,7 +39,7 @@ export default class Temp extends Component {
return (
<View style={styles.container}>
<Text style={styles.welcome}>{formatDateForViewHeader(cycleDay.date)}</Text>
<Text>Cycle day {getCycleDay()}</Text>
<Text>Cycle day {getCycleDayNumber()}</Text>
<Text>Temperature</Text>
<TextInput
placeholder="Enter temperature"
-5
View File
@@ -1,5 +0,0 @@
import { expect } from 'chai'
it('says hello world', function () {
expect(1 + 5).to.eql(6)
})
+146
View File
@@ -0,0 +1,146 @@
import chai from 'chai'
import dirtyChai from 'dirty-chai'
const expect = chai.expect
chai.use(dirtyChai)
import getCycleDayNumberModule from '../get-cycle-day-number'
describe('getCycleDay', () => {
it('works for a simple example', function () {
const bleedingDays = [{
date: '2018-05-10',
bleeding: {
value: 2
}
}, {
date: '2018-05-09',
bleeding: {
value: 2
}
}, {
date: '2018-05-03',
bleeding: {
value: 2
}
}]
const getCycleDayNumber = getCycleDayNumberModule({bleedingDaysSortedByDate: bleedingDays})
const targetDate = '2018-05-17'
const result = getCycleDayNumber(targetDate)
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 = getCycleDayNumberModule({bleedingDaysSortedByDate: 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 bleedingDays = [{
date: '2018-05-13',
bleeding: {
value: 2
}
}, {
date: '2018-04-11',
bleeding: {
value: 2
}
}, {
date: '2018-04-10',
bleeding: {
value: 2
}
}]
const targetDate = '2018-04-27'
const getCycleDayNumber = getCycleDayNumberModule({bleedingDaysSortedByDate: bleedingDays})
const result = getCycleDayNumber(targetDate)
expect(result).to.eql(18)
})
it('gets the correct number if the target day is the only bleeding day', () => {
const bleedingDays = [{
date: '2018-05-13',
bleeding: {
value: 2
}
}]
const targetDate = '2018-05-13'
const getCycleDayNumber = getCycleDayNumberModule({bleedingDaysSortedByDate: bleedingDays})
const result = getCycleDayNumber(targetDate)
expect(result).to.eql(1)
})
})
describe('getCycleDay returns null', () => {
it('if there are no bleeding days', function () {
const bleedingDays = []
const targetDate = '2018-05-17'
const getCycleDayNumber = getCycleDayNumberModule({bleedingDaysSortedByDate: bleedingDays})
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 = getCycleDayNumberModule({bleedingDaysSortedByDate: bleedingDays, maxBreakInBleeding })
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 = getCycleDayNumberModule({bleedingDaysSortedByDate: bleedingDays, maxBreakInBleeding })
const result = getCycleDayNumber(targetDate)
expect(result).to.eql(4)
})
})