Skip to content
This repository was archived by the owner on May 5, 2025. It is now read-only.

Commit f560390

Browse files
Merge pull request #5 from fptqnk17/feature/testing
update: setup testing
2 parents ca0d7f1 + 5591e3f commit f560390

27 files changed

+1566
-120
lines changed

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# 🎁 Gift Idea Tracker
2+
3+
A mobile application built with **React Native + Redux + Supabase**, designed to help users organize and track gift ideas for their friends, family, and special occasions — all in one place.
4+
5+
> 📆 Plan ahead. 🎁 Stay thoughtful. 💡 Never forget a gift again.
6+
7+
---
8+
9+
## 💡 AI-Powered Development Workflow
10+
11+
We leveraged **cutting-edge AI tools** throughout the entire software development lifecycle to accelerate productivity, improve quality, and stay creative:
12+
13+
| Phase | Tools Used |
14+
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
15+
| 📋 Requirement Gathering | [ChatGPT](https://chat.openai.com) – Assist in defining user stories and features |
16+
| 🎨 UI/UX Design | [Uizard](https://uizard.io) + Autodesigner 1.5 for AI wireframes and flows<br>[Figma](https://figma.com) + Codia AI plugin for auto-generating UI components |
17+
| 💻 Development | [a0.dev](https://a0.dev) to generate boilerplate code from designs<br>[VSCode](https://code.visualstudio.com) with [GitHub Copilot](https://github.com/features/copilot) for live coding, bug fixes, and code suggestions |
18+
| 🧪 Testing | Combination of **GitHub Copilot** and **ChatGPT** for writing test cases and debugging<br>Manual & automated testing via Jest + React Native Testing Library |
19+
20+
<br>
21+
22+
## ✨ Features
23+
24+
- 🧠 AI-assisted wireframes for fast UI prototyping
25+
- 📋 Manage gift ideas with title, image, notes, and tags
26+
- 👥 Add & manage recipients and event dates
27+
- 💰 Track budgets for each recipient and overall spending
28+
- 📊 Visual charts for budget analysis
29+
- ⏰ Reminder & calendar sync for upcoming events
30+
- 🔔 Push notifications (optional)
31+
- ☁️ Data stored securely using Supabase
32+
33+
34+
<br>
35+
36+
## 🛠️ Tech Stack
37+
38+
| Layer | Tools/Tech |
39+
| ------------ | ----------------------------------------- |
40+
| Frontend | React Native, Redux Toolkit, TypeScript |
41+
| Backend | Supabase (PostgreSQL, Auth, Storage) |
42+
| Design | Uizard, Figma (with Codia AI) |
43+
| AI Assistant | ChatGPT, GitHub Copilot, a0.dev |
44+
| Testing | Jest, React Native Testing Library, Detox |
45+
| Build/Deploy | Expo, EAS Build, Google Play, TestFlight |

bun.lock

Lines changed: 242 additions & 60 deletions
Large diffs are not rendered by default.

jest.config.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module.exports = {
2+
preset: 'react-native',
3+
transform: {
4+
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
5+
},
6+
transformIgnorePatterns: [
7+
'node_modules/(?!(jest-)?react-native|@react-native|@react-navigation|react-native-reanimated|react-native-gesture-handler)',
8+
],
9+
10+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
11+
setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'],
12+
moduleNameMapper: {
13+
'^@/(.*)$': '<rootDir>/src/$1', // chỉnh lại nếu bạn dùng alias khác
14+
},
15+
};

jest.setup.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Jest setup file
2+
3+
// Mock global functions or modules if needed
4+
jest.mock('react-native', () => {
5+
const ReactNative = jest.requireActual('react-native');
6+
return {
7+
...ReactNative,
8+
ActionSheetIOS: {
9+
showActionSheetWithOptions: jest.fn(),
10+
},
11+
};
12+
});
13+
14+
// Add any other global mocks or configurations here

package.json

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,19 @@
55
"scripts": {
66
"start": "expo start",
77
"android": "expo start --android",
8-
"ios": "expo start --ios"
8+
"ios": "expo start --ios",
9+
"test": "jest"
910
},
1011
"dependencies": {
1112
"@expo/config-plugins": "~9.0.0",
1213
"@expo/vector-icons": "14.1.0",
1314
"@react-native-community/datetimepicker": "8.2.0",
1415
"@reduxjs/toolkit": "^2.7.0",
1516
"@supabase/supabase-js": "^2.49.4",
17+
"@testing-library/jest-native": "^5.4.3",
18+
"@testing-library/react-hooks": "^8.0.1",
19+
"@testing-library/react-native": "13.2.0",
20+
"@types/redux-mock-store": "^1.5.0",
1621
"expo": "52.0.46",
1722
"expo-dev-client": "~5.0.20",
1823
"expo-fast-image": "1.1.3",
@@ -22,6 +27,7 @@
2227
"expo-router": "4.0.20",
2328
"expo-splash-screen": "0.29.24",
2429
"expo-status-bar": "2.0.1",
30+
"jest": "^29.7.0",
2531
"metro-react-native-babel-preset": "^0.77.0",
2632
"react": "18.3.1",
2733
"react-dom": "18.3.1",
@@ -31,14 +37,17 @@
3137
"react-native-reanimated": "3.16.1",
3238
"react-native-safe-area-context": "4.12.0",
3339
"react-native-screens": "4.4.0",
34-
"react-redux": "^9.2.0"
40+
"react-redux": "^9.2.0",
41+
"redux-mock-store": "^1.5.5",
42+
"ts-jest": "^29.3.2"
3543
},
3644
"devDependencies": {
3745
"@babel/core": "7.26.10",
3846
"@trivago/prettier-plugin-sort-imports": "5.2.2",
3947
"@types/react": "18.3.12",
4048
"eas-cli": "16.3.3",
4149
"prettier": "3.5.3",
50+
"react-test-renderer": "18.3.1",
4251
"typescript": "5.8.3"
4352
},
4453
"private": true
Lines changed: 62 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,38 @@
11
import React from 'react';
2-
import { View, Text, StyleSheet, Image, Switch } from 'react-native';
2+
import { Button, Image, StyleSheet, Switch, Text, View } from 'react-native';
33

4-
const CalendarIntegration = () => {
5-
const [googleEnabled, setGoogleEnabled] = React.useState(false);
6-
const [appleEnabled, setAppleEnabled] = React.useState(false);
4+
interface CalendarIntegrationProps {
5+
onIntegrate?: () => void;
6+
}
77

8-
return (
9-
<View style={styles.container}>
10-
<Text style={styles.sectionTitle}>Calendar Integration</Text>
11-
<View style={styles.integrationItem}>
12-
<Image
13-
source={{ uri: 'https://www.gstatic.com/calendar/images/dynamiclogo_2020q4/calendar_31_2x.png' }}
14-
style={styles.calendarIcon}
15-
/>
16-
<Text style={styles.integrationText}>Connect with Google Calendar</Text>
17-
<Switch
18-
value={googleEnabled}
19-
onValueChange={setGoogleEnabled}
20-
trackColor={{ false: '#767577', true: '#4285F4' }}
21-
/>
22-
</View>
8+
const CalendarIntegration: React.FC<CalendarIntegrationProps> = ({
9+
onIntegrate,
10+
}) => {
11+
const [googleEnabled, setGoogleEnabled] = React.useState(false);
12+
const [appleEnabled, setAppleEnabled] = React.useState(false);
13+
14+
return (
15+
<View style={styles.container}>
16+
<Text style={styles.sectionTitle}>Calendar Integration</Text>
17+
<View style={styles.integrationItem}>
18+
<Image
19+
source={{
20+
uri: 'https://www.gstatic.com/calendar/images/dynamiclogo_2020q4/calendar_31_2x.png',
21+
}}
22+
style={styles.calendarIcon}
23+
/>
24+
<Text style={styles.integrationText}>Connect with Google Calendar</Text>
25+
<Switch
26+
value={googleEnabled}
27+
onValueChange={setGoogleEnabled}
28+
trackColor={{ false: '#767577', true: '#4285F4' }}
29+
/>
30+
</View>
2331
<View style={styles.integrationItem}>
24-
<Image
25-
source={{ uri: 'https://help.apple.com/assets/65D689DF13D1B1E17703916F/65D689E0D302CF88600FDD25/en_US/941b3852f089696217cabe420c7a459f.png' }}
32+
<Image
33+
source={{
34+
uri: 'https://help.apple.com/assets/65D689DF13D1B1E17703916F/65D689E0D302CF88600FDD25/en_US/941b3852f089696217cabe420c7a459f.png',
35+
}}
2636
style={styles.calendarIcon}
2737
/>
2838
<Text style={styles.integrationText}>Connect with Apple Calendar</Text>
@@ -32,37 +42,40 @@ const CalendarIntegration = () => {
3242
trackColor={{ false: '#767577', true: '#007AFF' }}
3343
/>
3444
</View>
35-
</View>
36-
);
45+
{onIntegrate && (
46+
<Button title="Integrate Calendar" onPress={onIntegrate} />
47+
)}
48+
</View>
49+
);
3750
};
3851

3952
const styles = StyleSheet.create({
40-
container: {
41-
backgroundColor: '#F8F9FA',
42-
borderRadius: 12,
43-
marginHorizontal: 16,
44-
},
45-
sectionTitle: {
46-
fontSize: 16,
47-
fontWeight: '600',
48-
marginBottom: 8,
49-
color: '#333',
50-
},
51-
integrationItem: {
52-
flexDirection: 'row',
53-
alignItems: 'center',
54-
marginBottom: 8,
55-
},
56-
calendarIcon: {
57-
width: 28,
58-
height: 28,
59-
marginRight: 12,
60-
},
61-
integrationText: {
62-
flex: 1,
63-
fontSize: 15,
64-
color: '#333',
65-
},
53+
container: {
54+
backgroundColor: '#F8F9FA',
55+
borderRadius: 12,
56+
marginHorizontal: 16,
57+
},
58+
sectionTitle: {
59+
fontSize: 16,
60+
fontWeight: '600',
61+
marginBottom: 8,
62+
color: '#333',
63+
},
64+
integrationItem: {
65+
flexDirection: 'row',
66+
alignItems: 'center',
67+
marginBottom: 8,
68+
},
69+
calendarIcon: {
70+
width: 28,
71+
height: 28,
72+
marginRight: 12,
73+
},
74+
integrationText: {
75+
flex: 1,
76+
fontSize: 15,
77+
color: '#333',
78+
},
6679
});
6780

6881
export default CalendarIntegration;

src/components/settings/ReminderSettings.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
33
import { MaterialIcons } from '@expo/vector-icons';
44
import Modal from 'react-native-modal';
55

6-
const ReminderSettings = () => {
6+
interface ReminderSettingsProps {
7+
onToggle?: () => void;
8+
}
9+
10+
const ReminderSettings: React.FC<ReminderSettingsProps> = ({ onToggle }) => {
711
const [leadTime, setLeadTime] = useState('1 day before');
812
const [occasions, setOccasions] = useState('All occasions');
913
const [isLeadTimeModalVisible, setIsLeadTimeModalVisible] = useState(false);
@@ -14,10 +18,12 @@ const ReminderSettings = () => {
1418

1519
const toggleLeadTimeModal = () => {
1620
setIsLeadTimeModalVisible(!isLeadTimeModalVisible);
21+
if (onToggle) onToggle();
1722
};
1823

1924
const toggleOccasionsModal = () => {
2025
setIsOccasionsModalVisible(!isOccasionsModalVisible);
26+
if (onToggle) onToggle();
2127
};
2228

2329
return (

src/components/settings/SyncedEvents.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@ import React from 'react';
22
import { View, Text, StyleSheet } from 'react-native';
33
import { MaterialIcons } from '@expo/vector-icons';
44

5-
const SyncedEvents = () => {
6-
const events = [
7-
{ id: 1, name: 'Event name 1', date: '1 May 2025', synced: true },
8-
{ id: 2, name: 'Event name 2', date: '12 Jan 2025', synced: false },
9-
{ id: 3, name: 'Event name 3', date: '12 Dec 2024', synced: true },
10-
];
5+
interface Event {
6+
id: number;
7+
name: string;
8+
date: string;
9+
synced: boolean;
10+
}
1111

12+
interface SyncedEventsProps {
13+
events: Event[];
14+
}
15+
16+
const SyncedEvents: React.FC<SyncedEventsProps> = ({ events }) => {
1217
return (
1318
<View style={styles.container}>
1419
<Text style={styles.sectionTitle}>Synced Events</Text>

src/features/gifts/giftService.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ export const addGift = async (gift: CreateGiftDTO): Promise<GiftIdea> => {
1313
return data as GiftIdea;
1414
};
1515

16-
export const deleteGift = async (id: string): Promise<void> => {
16+
export const deleteGift = async (id: string): Promise<boolean> => {
1717
const { error } = await supabase.from('gifts').delete().eq('id', id);
1818
if (error) throw error;
19+
return true;
1920
};
2021

2122
export const updateGift = async (
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { fireEvent, render } from '@testing-library/react-native';
2+
import React from 'react';
3+
4+
import CalendarIntegration from '@/components/settings/CalendarIntegration';
5+
6+
describe('CalendarIntegration Component', () => {
7+
it('should render the calendar integration button', () => {
8+
const mockFn = jest.fn();
9+
const { getByText } = render(<CalendarIntegration onIntegrate={mockFn} />);
10+
expect(getByText('Integrate Calendar')).toBeTruthy();
11+
});
12+
13+
it('should trigger calendar integration on button press', () => {
14+
const mockIntegrateCalendar = jest.fn();
15+
const { getByText } = render(
16+
<CalendarIntegration onIntegrate={mockIntegrateCalendar} />,
17+
);
18+
fireEvent.press(getByText('Integrate Calendar'));
19+
expect(mockIntegrateCalendar).toHaveBeenCalled();
20+
});
21+
});

0 commit comments

Comments
 (0)