Skip to content

Commit 2a1cf6a

Browse files
authored
Merge pull request #851 from apedley/app-food-polish
App Food Management Polish + Bug Fixes
2 parents fecc0f8 + a035afc commit 2a1cf6a

64 files changed

Lines changed: 2547 additions & 1060 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

SparkyFitnessMobile/App.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import './global.css'
2-
import React, { useEffect, useState } from 'react';
2+
import React, { useEffect, useMemo, useState } from 'react';
33
import { StatusBar, Platform, type ImageSourcePropType } from 'react-native';
44
import { Ionicons } from '@expo/vector-icons';
55
import {
@@ -59,8 +59,7 @@ function AppContent() {
5959
// Determine if we're in dark mode based on current theme
6060
const isDarkMode = theme === 'dark' || theme === 'amoled';
6161

62-
// Create navigation theme that matches app colors
63-
const navigationTheme: Theme = {
62+
const navigationTheme = useMemo<Theme>(() => ({
6463
dark: isDarkMode,
6564
colors: {
6665
primary: primary,
@@ -76,7 +75,7 @@ function AppContent() {
7675
bold: { fontFamily: 'System', fontWeight: '600' },
7776
heavy: { fontFamily: 'System', fontWeight: '700' },
7877
},
79-
};
78+
}), [isDarkMode, primary, bgPrimary, chrome, textPrimary, chromeBorder]);
8079

8180
useEffect(() => {
8281
if (Platform.OS !== 'ios') {
Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,15 @@
11
import { render, screen } from '@testing-library/react-native';
2-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2+
import { QueryClientProvider } from '@tanstack/react-query';
33

44
import SyncScreen from '@/src/screens/SyncScreen';
55
import { NavigationContainer } from '@react-navigation/native';
66
import SettingsScreen from '@/src/screens/SettingsScreen';
77
import { createStackNavigator } from '@react-navigation/stack';
8+
import { createTestQueryClient } from './hooks/queryTestUtils';
89

910
const Stack = createStackNavigator();
1011

11-
const queryClient = new QueryClient({
12-
defaultOptions: {
13-
queries: {
14-
retry: false,
15-
},
16-
},
17-
});
12+
const queryClient = createTestQueryClient();
1813

1914
const AppNavigator = () => {
2015
return (
@@ -30,9 +25,12 @@ const AppNavigator = () => {
3025
};
3126

3227
describe('<SyncScreen />', () => {
28+
afterEach(() => {
29+
queryClient.clear();
30+
});
31+
3332
test('renders Sync Now button', async () => {
3433
render(<AppNavigator />);
35-
expect(screen.getByText('Sync Now')).toBeVisible();
36-
34+
expect(await screen.findByText('Sync Now')).toBeVisible();
3735
});
3836
});
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import React from 'react';
2+
import { act, fireEvent, render } from '@testing-library/react-native';
3+
import ChartTouchOverlay, {
4+
CHART_TOUCH_LONG_PRESS_DELAY_MS,
5+
buildChartTouchZones,
6+
type ChartTouchLayout,
7+
} from '../../src/components/ChartTouchOverlay';
8+
9+
const layout: ChartTouchLayout = {
10+
chartBounds: {
11+
left: 0,
12+
right: 60,
13+
top: 5,
14+
bottom: 45,
15+
},
16+
points: [
17+
{ x: 10, xValue: '2026-03-01', y: 30, yValue: 1000 },
18+
{ x: 30, xValue: '2026-03-02', y: 20, yValue: 2000 },
19+
{ x: 50, xValue: '2026-03-03', y: 10, yValue: 3000 },
20+
],
21+
};
22+
23+
const createTouchEvent = (locationX: number, locationY: number) => ({
24+
nativeEvent: {
25+
changedTouches: [{ locationX, locationY }],
26+
touches: [{ locationX, locationY }],
27+
locationX,
28+
locationY,
29+
},
30+
});
31+
32+
describe('ChartTouchOverlay', () => {
33+
beforeEach(() => {
34+
jest.useFakeTimers();
35+
});
36+
37+
afterEach(() => {
38+
jest.runOnlyPendingTimers();
39+
jest.useRealTimers();
40+
});
41+
42+
it('builds touch zones from the midpoints between plotted points', () => {
43+
expect(buildChartTouchZones(layout.points, layout.chartBounds)).toEqual([
44+
{ index: 0, left: 0, width: 20 },
45+
{ index: 1, left: 20, width: 20 },
46+
{ index: 2, left: 40, width: 20 },
47+
]);
48+
});
49+
50+
it('waits for the long press delay before selecting a zone', () => {
51+
const onSelect = jest.fn();
52+
const onClear = jest.fn();
53+
const screen = render(
54+
<ChartTouchOverlay
55+
layout={layout}
56+
onSelect={onSelect}
57+
onClear={onClear}
58+
testIDPrefix="touch-overlay"
59+
/>,
60+
);
61+
62+
fireEvent(
63+
screen.getByTestId('touch-overlay'),
64+
'touchStart',
65+
createTouchEvent(25, 20),
66+
);
67+
68+
expect(onSelect).not.toHaveBeenCalled();
69+
70+
act(() => {
71+
jest.advanceTimersByTime(CHART_TOUCH_LONG_PRESS_DELAY_MS);
72+
});
73+
74+
expect(onSelect).toHaveBeenCalledWith(1);
75+
76+
fireEvent(
77+
screen.getByTestId('touch-overlay'),
78+
'touchEnd',
79+
createTouchEvent(25, 20),
80+
);
81+
82+
expect(onClear).toHaveBeenCalledTimes(1);
83+
});
84+
85+
it('cancels activation when the touch turns into a drag before the delay', () => {
86+
const onSelect = jest.fn();
87+
const screen = render(
88+
<ChartTouchOverlay
89+
layout={layout}
90+
onSelect={onSelect}
91+
testIDPrefix="touch-overlay"
92+
/>,
93+
);
94+
95+
fireEvent(
96+
screen.getByTestId('touch-overlay'),
97+
'touchStart',
98+
createTouchEvent(25, 20),
99+
);
100+
fireEvent(
101+
screen.getByTestId('touch-overlay'),
102+
'touchMove',
103+
createTouchEvent(25, 35),
104+
);
105+
106+
act(() => {
107+
jest.advanceTimersByTime(CHART_TOUCH_LONG_PRESS_DELAY_MS);
108+
});
109+
110+
expect(onSelect).not.toHaveBeenCalled();
111+
});
112+
113+
it('updates the selected zone while dragging after activation', () => {
114+
const onSelect = jest.fn();
115+
const onClear = jest.fn();
116+
const screen = render(
117+
<ChartTouchOverlay
118+
layout={layout}
119+
onSelect={onSelect}
120+
onClear={onClear}
121+
testIDPrefix="touch-overlay"
122+
/>,
123+
);
124+
125+
fireEvent(
126+
screen.getByTestId('touch-overlay'),
127+
'touchStart',
128+
createTouchEvent(10, 20),
129+
);
130+
act(() => {
131+
jest.advanceTimersByTime(CHART_TOUCH_LONG_PRESS_DELAY_MS);
132+
});
133+
134+
fireEvent(
135+
screen.getByTestId('touch-overlay'),
136+
'touchMove',
137+
createTouchEvent(45, 20),
138+
);
139+
140+
expect(onSelect).toHaveBeenNthCalledWith(1, 0);
141+
expect(onSelect).toHaveBeenNthCalledWith(2, 2);
142+
});
143+
});

SparkyFitnessMobile/__tests__/hooks/queryTestUtils.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import React from 'react';
2-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
2+
import { act } from '@testing-library/react-native';
3+
import { QueryClient, QueryClientProvider, notifyManager } from '@tanstack/react-query';
34
import type { DefaultOptions } from '@tanstack/react-query';
45

56
export type { QueryClient } from '@tanstack/react-query';
67

8+
// Ensure React Query state updates are wrapped in act() to avoid warnings
9+
notifyManager.setNotifyFunction((callback) => {
10+
act(callback);
11+
});
12+
713
/**
814
* Creates a QueryClient configured for tests: no retries and instant staleness.
915
* Pass `options` to replace the default options entirely at the `defaultOptions` level.

0 commit comments

Comments
 (0)