Skip to content

Commit de97169

Browse files
committed
test(sidenav): add unit tests for useSidenavData hook with comprehensive scenarios for mail account status and storage usage
1 parent 9119594 commit de97169

4 files changed

Lines changed: 183 additions & 7 deletions

File tree

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import { renderHook } from '@testing-library/react';
2+
import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
3+
import { useSidenavData } from './useSidenavData';
4+
import { useGetStorageLimitQuery, useGetStorageUsageQuery } from '@/store/api/storage';
5+
import { useGetMailMeQuery } from '@/store/api/mail';
6+
import type { MailAccountResponse } from '@internxt/sdk/dist/mail/types';
7+
8+
vi.mock('@/store/api/storage', () => ({
9+
useGetStorageLimitQuery: vi.fn(),
10+
useGetStorageUsageQuery: vi.fn(),
11+
}));
12+
13+
vi.mock('@/store/api/mail', () => ({
14+
useGetMailMeQuery: vi.fn(),
15+
}));
16+
17+
const mockUseGetStorageLimitQuery = vi.mocked(useGetStorageLimitQuery);
18+
const mockUseGetStorageUsageQuery = vi.mocked(useGetStorageUsageQuery);
19+
const mockUseGetMailMeQuery = vi.mocked(useGetMailMeQuery);
20+
21+
const setupMocks = ({
22+
planLimit = 100,
23+
planUsage = 50,
24+
isLoadingPlanLimit = false,
25+
isLoadingPlanUsage = false,
26+
mailMe = undefined as MailAccountResponse | undefined,
27+
} = {}) => {
28+
mockUseGetStorageLimitQuery.mockReturnValue({
29+
data: planLimit,
30+
isLoading: isLoadingPlanLimit,
31+
} as unknown as ReturnType<typeof useGetStorageLimitQuery>);
32+
mockUseGetStorageUsageQuery.mockReturnValue({
33+
data: planUsage,
34+
isLoading: isLoadingPlanUsage,
35+
} as unknown as ReturnType<typeof useGetStorageUsageQuery>);
36+
mockUseGetMailMeQuery.mockReturnValue({ data: mailMe } as unknown as ReturnType<typeof useGetMailMeQuery>);
37+
};
38+
39+
describe('useSidenavData', () => {
40+
beforeEach(() => {
41+
vi.restoreAllMocks();
42+
});
43+
44+
describe('Mail account status', () => {
45+
test('When the mail account is active, then the mail features are reported as available', () => {
46+
setupMocks({ mailMe: { id: '1', defaultAddress: 'user@inxt.me', status: 'active' } });
47+
48+
const { result } = renderHook(() => useSidenavData());
49+
50+
expect(result.current.isMailDisabled).toBe(false);
51+
});
52+
53+
test('When the mail account is suspended, then the mail features are reported as disabled', () => {
54+
setupMocks({
55+
mailMe: {
56+
id: '1',
57+
defaultAddress: 'user@inxt.me',
58+
status: 'suspended',
59+
suspendedAt: '2026-05-01T00:00:00.000Z',
60+
deletionAt: '2026-06-01T00:00:00.000Z',
61+
},
62+
});
63+
64+
const { result } = renderHook(() => useSidenavData());
65+
66+
expect(result.current.isMailDisabled).toBe(true);
67+
});
68+
69+
test('When no mail account information is available, then the mail features are not reported as disabled', () => {
70+
setupMocks({ mailMe: undefined });
71+
72+
const { result } = renderHook(() => useSidenavData());
73+
74+
expect(result.current.isMailDisabled).toBe(false);
75+
});
76+
});
77+
78+
describe('Days until deletion', () => {
79+
beforeEach(() => {
80+
vi.useFakeTimers();
81+
vi.setSystemTime(new Date('2026-05-11T12:00:00.000Z'));
82+
});
83+
84+
afterEach(() => {
85+
vi.useRealTimers();
86+
});
87+
88+
test('When the account has a scheduled deletion date in the future, then the remaining days until deletion are reported', () => {
89+
setupMocks({
90+
mailMe: {
91+
id: '1',
92+
defaultAddress: 'user@inxt.me',
93+
status: 'suspended',
94+
deletionAt: '2026-05-16T12:00:00.000Z',
95+
},
96+
});
97+
98+
const { result } = renderHook(() => useSidenavData());
99+
100+
expect(result.current.daysUntilDeletion).toBe(5);
101+
});
102+
103+
test('When the account has no scheduled deletion date, then no remaining days are reported', () => {
104+
setupMocks({ mailMe: { id: '1', defaultAddress: 'user@inxt.me', status: 'active' } });
105+
106+
const { result } = renderHook(() => useSidenavData());
107+
108+
expect(result.current.daysUntilDeletion).toBeUndefined();
109+
});
110+
111+
test('When the scheduled deletion date has already passed, then no days are reported as remaining', () => {
112+
setupMocks({
113+
mailMe: {
114+
id: '1',
115+
defaultAddress: 'user@inxt.me',
116+
status: 'suspended',
117+
deletionAt: '2026-05-01T00:00:00.000Z',
118+
},
119+
});
120+
121+
const { result } = renderHook(() => useSidenavData());
122+
123+
expect(result.current.daysUntilDeletion).toBe(0);
124+
});
125+
});
126+
127+
describe('Storage percentage', () => {
128+
test('When the used storage is half of the available storage, then the reported usage is fifty percent', () => {
129+
setupMocks({ planUsage: 500, planLimit: 1000 });
130+
131+
const { result } = renderHook(() => useSidenavData());
132+
133+
expect(result.current.storagePercentage).toBe(50);
134+
});
135+
136+
test('When the used storage exceeds the available storage, then the reported usage is capped at one hundred percent', () => {
137+
setupMocks({ planUsage: 1500, planLimit: 1000 });
138+
139+
const { result } = renderHook(() => useSidenavData());
140+
141+
expect(result.current.storagePercentage).toBe(100);
142+
});
143+
144+
test('When the available storage is zero, then the reported usage is zero percent to avoid division by zero', () => {
145+
setupMocks({ planUsage: 100, planLimit: 0 });
146+
147+
const { result } = renderHook(() => useSidenavData());
148+
149+
expect(result.current.storagePercentage).toBe(0);
150+
});
151+
152+
test('When the storage information is still being fetched, then the loading state is reported as in progress', () => {
153+
setupMocks({ isLoadingPlanLimit: true, isLoadingPlanUsage: true });
154+
155+
const { result } = renderHook(() => useSidenavData());
156+
157+
expect(result.current.isLoadingPlanLimit).toBe(true);
158+
expect(result.current.isLoadingPlanUsage).toBe(true);
159+
});
160+
161+
test('When the storage information has finished loading, then the used and available storage values are returned', () => {
162+
setupMocks({ planUsage: 200, planLimit: 800 });
163+
164+
const { result } = renderHook(() => useSidenavData());
165+
166+
expect(result.current.planUsage).toBe(200);
167+
expect(result.current.planLimit).toBe(800);
168+
});
169+
});
170+
});

src/services/sdk/mail/mail.service.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ describe('Mail Service', () => {
3232
expect(mockMailClient.getMailAccount).toHaveBeenCalledOnce();
3333
});
3434

35-
test('When fetching the mail account and it is suspended, then suspendedAt and deletionAt should be present', async () => {
35+
test('When fetching the mail account and it is suspended, then its scheduled deletion date and suspension date should be present', async () => {
3636
const mockAccount: MailAccountResponse = {
3737
id: 'account-1',
3838
defaultAddress: 'jane@inxt.me',

src/store/api/mail/mail.api.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ describe('Mail API', () => {
8888
expect(result.data).toStrictEqual(mockAccount);
8989
});
9090

91-
test('When fetching the mail account fails, then a FetchMailMeError should be returned', async () => {
91+
test('When fetching the mail account fails, then an error indicating so is thrown', async () => {
9292
vi.spyOn(MailService.instance, 'getMe').mockRejectedValue(new Error('Network error'));
9393
const castErrorSpy = vi.spyOn(ErrorService.instance, 'castError');
9494
const store = createTestStore();

src/utils/days-until/daysUntil.test.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,39 @@ describe('Calculating the days remaining until a future date', () => {
1111
vi.useRealTimers();
1212
});
1313

14-
test('When no date is provided, then it should return undefined', () => {
14+
test('When no date is provided, then no value is returned', () => {
1515
const result = getDaysUntil(undefined);
1616

1717
expect(result).toBeUndefined();
1818
});
1919

20-
test('When the date is several days in the future, then it should return the rounded-up day count', () => {
20+
test('When the date is several days in the future, then the remaining whole days are returned', () => {
2121
const result = getDaysUntil('2026-05-16T12:00:00.000Z');
2222

2323
expect(result).toBe(5);
2424
});
2525

26-
test('When the date is less than a full day in the future, then it should round up to 1', () => {
26+
test('When the date is less than a full day away, then it is counted as one day remaining', () => {
2727
const result = getDaysUntil('2026-05-11T13:00:00.000Z');
2828

2929
expect(result).toBe(1);
3030
});
3131

32-
test('When the date is in the past, then it should clamp to 0', () => {
32+
test('When the date has already passed, then no days are reported as remaining', () => {
3333
const result = getDaysUntil('2026-05-01T00:00:00.000Z');
3434

3535
expect(result).toBe(0);
3636
});
3737

38-
test('When the date is exactly now, then it should return 0', () => {
38+
test('When the date is exactly the current moment, then no days are reported as remaining', () => {
3939
const result = getDaysUntil('2026-05-11T12:00:00.000Z');
4040

4141
expect(result).toBe(0);
4242
});
43+
44+
test('When the provided value is not a valid date, then no value is returned', () => {
45+
const result = getDaysUntil('not-a-date');
46+
47+
expect(result).toBeUndefined();
48+
});
4349
});

0 commit comments

Comments
 (0)