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:
+4
-2
@@ -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
@@ -13,7 +13,7 @@ export default class DatePickView extends Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
bleedingDaysSortedByDate.removeListener(setStateWithCalendarFormattedDays)
|
||||
bleedingDaysSortedByDate.removeAllListeners()
|
||||
}
|
||||
|
||||
passDateToDayView(result) {
|
||||
|
||||
+19
-4
@@ -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
|
||||
@@ -54,4 +63,10 @@ export default class DayView extends Component {
|
||||
</View >
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function setStateWithCurrentCycleDayNumber() {
|
||||
this.setState({
|
||||
cycleDayNumber: getCycleDayNumber(this.cycleDay.date)
|
||||
})
|
||||
}
|
||||
@@ -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,21 +34,18 @@ const CycleDaySchema = {
|
||||
}
|
||||
}
|
||||
|
||||
async function openDatabase() {
|
||||
db = await realm.open({
|
||||
schema: [
|
||||
CycleDaySchema,
|
||||
TemperatureSchema,
|
||||
BleedingSchema
|
||||
],
|
||||
// we only want this in dev mode
|
||||
deleteRealmIfMigrationNeeded: true
|
||||
})
|
||||
const db = new Realm({
|
||||
schema: [
|
||||
CycleDaySchema,
|
||||
TemperatureSchema,
|
||||
BleedingSchema
|
||||
],
|
||||
// we only want this in dev mode
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
export default () => 6
|
||||
@@ -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)) })
|
||||
}
|
||||
|
||||
@@ -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
+6
@@ -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",
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
|
||||
it('says hello world', function () {
|
||||
expect(1 + 5).to.eql(6)
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user