Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/react-native-healthkit/src/healthkit.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
Workouts,
} from './modules'
import type { QuantityTypeIdentifier } from './types/QuantityTypeIdentifier'
import generateQuantityTypeSamples from './utils/generateQuantityTypeSamples'
import getMostRecentCategorySample from './utils/getMostRecentCategorySample'
import getMostRecentQuantitySample from './utils/getMostRecentQuantitySample'
import getMostRecentWorkout from './utils/getMostRecentWorkout'
Expand Down Expand Up @@ -49,6 +50,7 @@ export type AvailableQuantityTypesBeforeIOS17 = Exclude<
>

export {
generateQuantityTypeSamples,
getMostRecentCategorySample,
getMostRecentQuantitySample,
getMostRecentWorkout,
Expand Down Expand Up @@ -222,6 +224,7 @@ export default {
isProtectedDataAvailable,
queryStateOfMindSamples,
saveStateOfMindSample,
generateQuantityTypeSamples,

// hooks
useMostRecentCategorySample,
Expand Down
6 changes: 6 additions & 0 deletions packages/react-native-healthkit/src/healthkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,11 @@ export const getPreferredUnit = UnavailableFnFromModule(
Promise.resolve('count'),
) // Defaulting to 'count'

export const generateQuantityTypeSamples = UnavailableFnFromModule(
'generateQuantityTypeSamples',
Promise.resolve(false),
)

// Hooks (from original export list)
export function useMostRecentCategorySample<T extends CategoryTypeIdentifier>(
_categoryTypeIdentifier: T,
Expand Down Expand Up @@ -420,6 +425,7 @@ const HealthkitModule = {
isProtectedDataAvailable,
queryStateOfMindSamples,
saveStateOfMindSample,
generateQuantityTypeSamples,

// Hooks
useMostRecentCategorySample,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { afterEach, beforeEach, describe, expect, jest, test } from 'bun:test'

import { QuantityTypes } from '../modules'
import generateQuantityTypeSamples from './generateQuantityTypeSamples'

describe('generateQuantityTypeSamples', () => {
beforeEach(() => {
jest.clearAllMocks()
})

afterEach(() => {
jest.restoreAllMocks()
})

test('should generate samples with default date range', async () => {
const saveMock = jest
.spyOn(QuantityTypes, 'saveQuantitySample')
.mockResolvedValue(true)
const queryMock = jest
.spyOn(QuantityTypes, 'queryQuantitySamples')
.mockResolvedValue([])

const result = await generateQuantityTypeSamples(
'HKQuantityTypeIdentifierStepCount',
5,
)

expect(result).toBe(true)
expect(saveMock).toHaveBeenCalledTimes(5)
expect(queryMock).toHaveBeenCalledTimes(1)
})

test('should generate samples with custom date range', async () => {
const saveMock = jest
.spyOn(QuantityTypes, 'saveQuantitySample')
.mockResolvedValue(true)
const queryMock = jest
.spyOn(QuantityTypes, 'queryQuantitySamples')
.mockResolvedValue([])

const fromDate = new Date('2024-01-01')
const toDate = new Date('2024-01-07')

const result = await generateQuantityTypeSamples(
'HKQuantityTypeIdentifierHeartRate',
7,
{ fromDate, toDate },
)

expect(result).toBe(true)
expect(saveMock).toHaveBeenCalledTimes(7)
})

test('should return false if any sample fails to save', async () => {
jest
.spyOn(QuantityTypes, 'saveQuantitySample')
.mockResolvedValueOnce(true)
.mockResolvedValueOnce(false)
.mockResolvedValueOnce(true)
jest.spyOn(QuantityTypes, 'queryQuantitySamples').mockResolvedValue([])

const result = await generateQuantityTypeSamples(
'HKQuantityTypeIdentifierStepCount',
3,
)

expect(result).toBe(false)
})

test('should spread samples evenly across time range', async () => {
const saveMock = jest
.spyOn(QuantityTypes, 'saveQuantitySample')
.mockResolvedValue(true)
jest.spyOn(QuantityTypes, 'queryQuantitySamples').mockResolvedValue([])

const fromDate = new Date('2024-01-01T00:00:00Z')
const toDate = new Date('2024-01-04T00:00:00Z')

await generateQuantityTypeSamples('HKQuantityTypeIdentifierStepCount', 3, {
fromDate,
toDate,
})

const calls = saveMock.mock.calls
expect(calls.length).toBe(3)

// Check that dates are spread evenly (each day)
const dates = calls.map((call) => call[3] as Date)
const timeRange = toDate.getTime() - fromDate.getTime()
const interval = timeRange / 3

for (let i = 0; i < dates.length; i++) {
const expectedTime = fromDate.getTime() + interval * i
expect(dates[i]?.getTime()).toBe(expectedTime)
}
})

test('should use realistic values from the predefined ranges', async () => {
const saveMock = jest
.spyOn(QuantityTypes, 'saveQuantitySample')
.mockResolvedValue(true)
jest.spyOn(QuantityTypes, 'queryQuantitySamples').mockResolvedValue([])

await generateQuantityTypeSamples('HKQuantityTypeIdentifierHeartRate', 10)

const calls = saveMock.mock.calls
const values = calls.map((call) => call[2] as number)

// Heart rate should be between 60 and 100 based on our realistic ranges
for (const value of values) {
expect(value).toBeGreaterThanOrEqual(60)
expect(value).toBeLessThanOrEqual(100)
}
})

test('should handle errors gracefully', async () => {
jest
.spyOn(QuantityTypes, 'saveQuantitySample')
.mockRejectedValue(new Error('Save failed'))
jest.spyOn(QuantityTypes, 'queryQuantitySamples').mockResolvedValue([])

const consoleErrorSpy = jest
.spyOn(console, 'error')
.mockImplementation(() => {})

const result = await generateQuantityTypeSamples(
'HKQuantityTypeIdentifierStepCount',
2,
)

expect(result).toBe(false)
expect(consoleErrorSpy).toHaveBeenCalledTimes(2)

consoleErrorSpy.mockRestore()
})
})
Loading
Loading