Skip to content
This repository was archived by the owner on Feb 9, 2026. It is now read-only.

Commit 95df0ef

Browse files
authored
Merge pull request #476 from bounswe/customizable_visualizations
Mobile: 2-method waste input , filtering and additional wastes with chart fix
2 parents 848ecdc + 1d53071 commit 95df0ef

File tree

12 files changed

+2407
-71
lines changed

12 files changed

+2407
-71
lines changed

application/mobile/src/components/WasteFilterModal.tsx

Lines changed: 453 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import React from 'react';
2+
import { render, fireEvent, waitFor } from '@testing-library/react-native';
3+
import { WasteFilterModal, getDefaultFilters, WASTE_TYPE_OPTIONS, WasteFilters } from '../WasteFilterModal';
4+
5+
// Mock dependencies
6+
jest.mock('react-i18next', () => ({
7+
useTranslation: () => ({
8+
t: (key: string) => {
9+
const translations: Record<string, string> = {
10+
'filter.title': 'Filter Data',
11+
'filter.timeRange': 'Time Range',
12+
'filter.wasteTypes': 'Waste Types',
13+
'filter.apply': 'Apply Filters',
14+
'filter.clearAll': 'Clear All',
15+
'filter.selectAll': 'Select All',
16+
'filter.deselectAll': 'Deselect All',
17+
'filter.selected': 'selected',
18+
'filter.selectedFilters': 'Selected Filters',
19+
'filter.timeRanges.day': 'Today',
20+
'filter.timeRanges.week': 'Last 7 Days',
21+
'filter.timeRanges.month': 'Last 30 Days',
22+
'filter.timeRanges.year': 'Last Year',
23+
'filter.timeRanges.all': 'All Time',
24+
'common.cancel': 'Cancel',
25+
'home.wasteTypes.PLASTIC': 'Plastic',
26+
'home.wasteTypes.PAPER': 'Paper',
27+
'home.wasteTypes.GLASS': 'Glass',
28+
'home.wasteTypes.METAL': 'Metal',
29+
'home.wasteTypes.ELECTRONIC': 'Electronic',
30+
'home.wasteTypes.OIL&FATS': 'Oil',
31+
'home.wasteTypes.ORGANIC': 'Organic',
32+
};
33+
return translations[key] || key;
34+
},
35+
}),
36+
}));
37+
38+
jest.mock('../../context/ThemeContext', () => ({
39+
useTheme: () => ({
40+
colors: {
41+
primary: '#2E7D32',
42+
background: '#FFFFFF',
43+
backgroundSecondary: '#F5F5F5',
44+
textPrimary: '#212121',
45+
textSecondary: '#616161',
46+
textOnPrimary: '#FFFFFF',
47+
lightGray: '#E0E0E0',
48+
error: '#C62828',
49+
},
50+
isDarkMode: false,
51+
}),
52+
}));
53+
54+
jest.mock('../../hooks/useRTL', () => ({
55+
useRTL: () => ({
56+
isRTL: false,
57+
textStyle: {},
58+
rowStyle: {},
59+
}),
60+
}));
61+
62+
describe('WasteFilterModal', () => {
63+
const defaultProps = {
64+
visible: true,
65+
onClose: jest.fn(),
66+
onApply: jest.fn(),
67+
currentFilters: getDefaultFilters(),
68+
};
69+
70+
beforeEach(() => {
71+
jest.clearAllMocks();
72+
});
73+
74+
it('should render correctly when visible', () => {
75+
const { getByText } = render(<WasteFilterModal {...defaultProps} />);
76+
77+
expect(getByText('Filter Data')).toBeTruthy();
78+
expect(getByText('Time Range')).toBeTruthy();
79+
expect(getByText('Waste Types')).toBeTruthy();
80+
});
81+
82+
it('should not render when not visible', () => {
83+
const { queryByText } = render(
84+
<WasteFilterModal {...defaultProps} visible={false} />
85+
);
86+
87+
// Modal should not be visible (content may still be in tree but not displayed)
88+
// In React Native, we can check if the modal's visible prop is false
89+
});
90+
91+
it('should display all time range options', () => {
92+
const { getByText } = render(<WasteFilterModal {...defaultProps} />);
93+
94+
expect(getByText('Today')).toBeTruthy();
95+
expect(getByText('Last 7 Days')).toBeTruthy();
96+
expect(getByText('Last 30 Days')).toBeTruthy();
97+
expect(getByText('Last Year')).toBeTruthy();
98+
expect(getByText('All Time')).toBeTruthy();
99+
});
100+
101+
it('should display all waste type options', () => {
102+
const { getByText } = render(<WasteFilterModal {...defaultProps} />);
103+
104+
expect(getByText('Plastic')).toBeTruthy();
105+
expect(getByText('Paper')).toBeTruthy();
106+
expect(getByText('Glass')).toBeTruthy();
107+
expect(getByText('Metal')).toBeTruthy();
108+
expect(getByText('Electronic')).toBeTruthy();
109+
expect(getByText('Oil')).toBeTruthy();
110+
expect(getByText('Organic')).toBeTruthy();
111+
});
112+
113+
it('should call onClose when cancel button is pressed', () => {
114+
const { getByText } = render(<WasteFilterModal {...defaultProps} />);
115+
116+
fireEvent.press(getByText('Cancel'));
117+
118+
expect(defaultProps.onClose).toHaveBeenCalled();
119+
});
120+
121+
it('should call onApply with filters when apply button is pressed', () => {
122+
const { getByText } = render(<WasteFilterModal {...defaultProps} />);
123+
124+
fireEvent.press(getByText('Apply Filters'));
125+
126+
expect(defaultProps.onApply).toHaveBeenCalled();
127+
expect(defaultProps.onClose).toHaveBeenCalled();
128+
});
129+
130+
it('should update time range when option is selected', () => {
131+
const onApply = jest.fn();
132+
const { getByText } = render(
133+
<WasteFilterModal {...defaultProps} onApply={onApply} />
134+
);
135+
136+
// Select "Last 7 Days"
137+
fireEvent.press(getByText('Last 7 Days'));
138+
139+
// Apply filters
140+
fireEvent.press(getByText('Apply Filters'));
141+
142+
expect(onApply).toHaveBeenCalledWith(
143+
expect.objectContaining({
144+
timeRange: 'week',
145+
})
146+
);
147+
});
148+
149+
it('should toggle waste type selection', () => {
150+
const onApply = jest.fn();
151+
const { getByText } = render(
152+
<WasteFilterModal {...defaultProps} onApply={onApply} />
153+
);
154+
155+
// Deselect Plastic (should still work since we have more types selected)
156+
fireEvent.press(getByText('Plastic'));
157+
158+
// Apply filters
159+
fireEvent.press(getByText('Apply Filters'));
160+
161+
expect(onApply).toHaveBeenCalledWith(
162+
expect.objectContaining({
163+
wasteTypes: expect.not.arrayContaining(['PLASTIC']),
164+
})
165+
);
166+
});
167+
168+
it('should not allow deselecting all waste types', () => {
169+
const onApply = jest.fn();
170+
// Start with only one waste type selected
171+
const currentFilters: WasteFilters = {
172+
timeRange: 'all',
173+
wasteTypes: ['PLASTIC'],
174+
};
175+
176+
const { getByText } = render(
177+
<WasteFilterModal {...defaultProps} currentFilters={currentFilters} onApply={onApply} />
178+
);
179+
180+
// Try to deselect Plastic (should not work since it's the only one)
181+
fireEvent.press(getByText('Plastic'));
182+
183+
// Apply filters
184+
fireEvent.press(getByText('Apply Filters'));
185+
186+
// Should still have PLASTIC selected
187+
expect(onApply).toHaveBeenCalledWith(
188+
expect.objectContaining({
189+
wasteTypes: ['PLASTIC'],
190+
})
191+
);
192+
});
193+
194+
it('should display selected filters summary', () => {
195+
const { getByText } = render(<WasteFilterModal {...defaultProps} />);
196+
197+
expect(getByText('Selected Filters')).toBeTruthy();
198+
});
199+
});
200+
201+
describe('getDefaultFilters', () => {
202+
it('should return correct default values', () => {
203+
const defaults = getDefaultFilters();
204+
205+
expect(defaults.timeRange).toBe('all');
206+
expect(defaults.wasteTypes).toEqual([...WASTE_TYPE_OPTIONS]);
207+
expect(defaults.customStartDate).toBeUndefined();
208+
expect(defaults.customEndDate).toBeUndefined();
209+
});
210+
});
211+
212+
describe('WASTE_TYPE_OPTIONS', () => {
213+
it('should contain all expected waste types', () => {
214+
expect(WASTE_TYPE_OPTIONS).toContain('PLASTIC');
215+
expect(WASTE_TYPE_OPTIONS).toContain('PAPER');
216+
expect(WASTE_TYPE_OPTIONS).toContain('GLASS');
217+
expect(WASTE_TYPE_OPTIONS).toContain('METAL');
218+
expect(WASTE_TYPE_OPTIONS).toContain('ELECTRONIC');
219+
expect(WASTE_TYPE_OPTIONS).toContain('OIL&FATS');
220+
expect(WASTE_TYPE_OPTIONS).toContain('ORGANIC');
221+
expect(WASTE_TYPE_OPTIONS.length).toBe(7);
222+
});
223+
});
224+

0 commit comments

Comments
 (0)