Skip to content

Commit 279087e

Browse files
authored
Merge pull request #1352 from Real-Dev-Squad/develop
Dev to Main Sync
2 parents ca152ce + 4b74ba2 commit 279087e

File tree

13 files changed

+897
-113
lines changed

13 files changed

+897
-113
lines changed

__mocks__/db/extensionRequests.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { ExtensionRequest } from '@/components/ExtensionRequest/ExtensionStatusModal';
2+
3+
export const now = Date.now();
4+
5+
export const mockExtensionRequests: ExtensionRequest[] = [
6+
{
7+
id: '1',
8+
reason: 'Need more time',
9+
newEndsOn: now + 86400000,
10+
title: 'Fix bugs',
11+
taskId: '123',
12+
oldEndsOn: now,
13+
status: 'APPROVED',
14+
requestNumber: 1,
15+
timestamp: now,
16+
assignee: 'john',
17+
assigneeId: 'user-1',
18+
reviewedBy: 'admin',
19+
reviewedAt: Math.floor(now / 1000),
20+
},
21+
{
22+
id: '2',
23+
reason: 'Additional requirements',
24+
newEndsOn: now + 172800000,
25+
title: 'Add features',
26+
taskId: '123',
27+
oldEndsOn: now - 86400000,
28+
status: 'PENDING',
29+
requestNumber: 2,
30+
timestamp: now - 43200000,
31+
assignee: 'john',
32+
assigneeId: 'user-1',
33+
},
34+
{
35+
id: '3',
36+
reason: 'Need more time',
37+
newEndsOn: now + 86400000,
38+
title: 'Fix bugs',
39+
taskId: '123',
40+
oldEndsOn: now,
41+
status: 'APPROVED',
42+
requestNumber: 3,
43+
timestamp: now,
44+
assignee: 'john',
45+
assigneeId: 'user-1',
46+
reviewedBy: 'admin',
47+
reviewedAt: 1619090400,
48+
},
49+
{
50+
id: '4',
51+
reason: 'Additional requirements',
52+
newEndsOn: now + 172800000,
53+
title: 'Add features',
54+
taskId: '123',
55+
oldEndsOn: now - 86400000,
56+
status: 'DENIED',
57+
requestNumber: 4,
58+
timestamp: now - 43200000,
59+
assignee: 'john',
60+
assigneeId: 'user-1',
61+
reviewedBy: 'manager',
62+
reviewedAt: 1714611746,
63+
},
64+
{
65+
id: '5',
66+
reason: 'Task complexity increased',
67+
newEndsOn: now + 172800000,
68+
title: 'Implement new API',
69+
taskId: '123',
70+
oldEndsOn: now - 86400000,
71+
status: 'PENDING',
72+
requestNumber: 5,
73+
timestamp: now - 43200000,
74+
assignee: 'john',
75+
assigneeId: 'user-1',
76+
},
77+
];
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import { ExtensionRequestDetails } from '@/components/ExtensionRequest/ExtensionRequestDetails';
4+
import { mockExtensionRequests } from '../../../../__mocks__/db/extensionRequests';
5+
6+
jest.mock('@/components/ExtensionRequest/ExtensionStatusModal', () => ({
7+
formatToRelativeTime: jest.fn((timestamp) => {
8+
if (Math.floor(timestamp) === 1619090400) return 'a month ago';
9+
if (Math.floor(timestamp) === 1714611746) return '5 minutes ago';
10+
return 'some time ago';
11+
}),
12+
}));
13+
14+
describe('ExtensionRequestDetails Component', () => {
15+
const mockStyles = {
16+
extensionNoRequests: 'extensionNoRequests',
17+
extensionExtensionRequest: 'extensionExtensionRequest',
18+
extensionDetailRow: 'extensionDetailRow',
19+
extensionLabel: 'extensionLabel',
20+
extensionValue: 'extensionValue',
21+
extensionApproved: 'extensionApproved',
22+
extensionDenied: 'extensionDenied',
23+
extensionPending: 'extensionPending',
24+
extensionApprovalInfo: 'extensionApprovalInfo',
25+
extensionRejectionInfo: 'extensionRejectionInfo',
26+
};
27+
28+
const getExtensionRequestDetails = jest.fn((request, styles) => [
29+
{
30+
label: 'Request :',
31+
value: `#${request.requestNumber}`,
32+
testId: 'request-number',
33+
},
34+
{
35+
label: 'Reason :',
36+
value: request.reason,
37+
testId: 'request-reason',
38+
},
39+
{
40+
label: 'Status :',
41+
value: request.status,
42+
className:
43+
request.status === 'APPROVED'
44+
? styles.extensionApproved
45+
: request.status === 'DENIED'
46+
? styles.extensionDenied
47+
: styles.extensionPending,
48+
testId: 'request-status',
49+
},
50+
]);
51+
52+
test('should render no requests message when there are no extension requests', () => {
53+
render(
54+
<ExtensionRequestDetails
55+
extensionRequests={[]}
56+
styles={mockStyles}
57+
getExtensionRequestDetails={getExtensionRequestDetails}
58+
/>
59+
);
60+
expect(screen.getByTestId('no-requests-message')).toBeInTheDocument();
61+
expect(
62+
screen.getByTestId('first-extension-request-message')
63+
).toBeInTheDocument();
64+
});
65+
66+
test('should render extension request details correctly', () => {
67+
render(
68+
<ExtensionRequestDetails
69+
extensionRequests={[mockExtensionRequests[2]]}
70+
styles={mockStyles}
71+
getExtensionRequestDetails={getExtensionRequestDetails}
72+
/>
73+
);
74+
expect(screen.getByTestId('extension-request-3')).toBeInTheDocument();
75+
expect(screen.getByTestId('value-request-number')).toHaveTextContent(
76+
'#3'
77+
);
78+
expect(screen.getByTestId('value-request-reason')).toHaveTextContent(
79+
'Need more time'
80+
);
81+
expect(screen.getByTestId('value-request-status')).toHaveTextContent(
82+
'APPROVED'
83+
);
84+
expect(getExtensionRequestDetails).toHaveBeenCalledWith(
85+
mockExtensionRequests[2],
86+
mockStyles
87+
);
88+
});
89+
90+
test('should render approval info for approved requests', () => {
91+
render(
92+
<ExtensionRequestDetails
93+
extensionRequests={[mockExtensionRequests[2]]}
94+
styles={mockStyles}
95+
getExtensionRequestDetails={getExtensionRequestDetails}
96+
/>
97+
);
98+
const approvalInfo = screen.getByTestId('approval-info');
99+
expect(approvalInfo).toBeInTheDocument();
100+
expect(approvalInfo).toHaveTextContent(
101+
'Your request was approved by admin'
102+
);
103+
expect(approvalInfo).toHaveClass('extensionApprovalInfo');
104+
});
105+
106+
test('should render denial info for denied requests', () => {
107+
render(
108+
<ExtensionRequestDetails
109+
extensionRequests={[mockExtensionRequests[3]]}
110+
styles={mockStyles}
111+
getExtensionRequestDetails={getExtensionRequestDetails}
112+
/>
113+
);
114+
const denialInfo = screen.getByTestId('denied-info');
115+
expect(denialInfo).toBeInTheDocument();
116+
expect(denialInfo.textContent).toContain(
117+
'Your request was denied by manager'
118+
);
119+
expect(denialInfo).toHaveClass('extensionRejectionInfo');
120+
});
121+
122+
test('should not render review info for pending requests', () => {
123+
render(
124+
<ExtensionRequestDetails
125+
extensionRequests={[mockExtensionRequests[4]]}
126+
styles={mockStyles}
127+
getExtensionRequestDetails={getExtensionRequestDetails}
128+
/>
129+
);
130+
expect(screen.queryByTestId('approval-info')).not.toBeInTheDocument();
131+
expect(screen.queryByTestId('denied-info')).not.toBeInTheDocument();
132+
});
133+
134+
test('should apply correct class names to detail rows', () => {
135+
render(
136+
<ExtensionRequestDetails
137+
extensionRequests={[mockExtensionRequests[2]]}
138+
styles={mockStyles}
139+
getExtensionRequestDetails={getExtensionRequestDetails}
140+
/>
141+
);
142+
expect(screen.getAllByTestId(/detail-row-/)[0]).toHaveClass(
143+
'extensionDetailRow'
144+
);
145+
expect(screen.getByTestId('label-request-number')).toHaveClass(
146+
'extensionLabel'
147+
);
148+
expect(screen.getByTestId('value-request-number')).toHaveClass(
149+
'extensionValue'
150+
);
151+
});
152+
153+
test('should apply custom class names from getExtensionRequestDetails', () => {
154+
render(
155+
<ExtensionRequestDetails
156+
extensionRequests={[mockExtensionRequests[2]]}
157+
styles={mockStyles}
158+
getExtensionRequestDetails={getExtensionRequestDetails}
159+
/>
160+
);
161+
expect(screen.getByTestId('value-request-status')).toHaveClass(
162+
'extensionApproved'
163+
);
164+
});
165+
});
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import React from 'react';
2+
import { render, screen, fireEvent } from '@testing-library/react';
3+
import {
4+
ExtensionStatusModal,
5+
formatToRelativeTime,
6+
} from '@/components/ExtensionRequest/ExtensionStatusModal';
7+
import { useGetSelfExtensionRequestsQuery } from '@/app/services/tasksApi';
8+
import { mockExtensionRequests } from '../../../../__mocks__/db/extensionRequests';
9+
10+
jest.mock(
11+
'@/app/services/tasksApi',
12+
() => ({
13+
useGetSelfExtensionRequestsQuery: jest.fn(),
14+
}),
15+
{ virtual: true }
16+
);
17+
18+
const mockQuery = useGetSelfExtensionRequestsQuery as jest.Mock;
19+
const defaultProps = {
20+
isOpen: true,
21+
onClose: jest.fn(),
22+
taskId: '123',
23+
dev: true,
24+
assignee: 'john',
25+
};
26+
27+
const setupTest = (queryReturnValue: any) => {
28+
mockQuery.mockReturnValue(queryReturnValue);
29+
return render(<ExtensionStatusModal {...defaultProps} />);
30+
};
31+
32+
describe('ExtensionStatusModal Component', () => {
33+
test('should render loading state correctly', () => {
34+
setupTest({ isLoading: true, data: null });
35+
expect(screen.getByTestId('modal-title')).toBeInTheDocument();
36+
expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
37+
});
38+
39+
test('should hide request extension button when pending request exists', () => {
40+
setupTest({
41+
isLoading: false,
42+
data: { allExtensionRequests: [{ id: 1, status: 'PENDING' }] },
43+
});
44+
expect(
45+
screen.queryByTestId('request-extension-button')
46+
).not.toBeInTheDocument();
47+
});
48+
49+
test('should handle modal interactions correctly', () => {
50+
setupTest({ isLoading: false, data: { allExtensionRequests: [] } });
51+
52+
fireEvent.click(screen.getByTestId('close-button'));
53+
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
54+
55+
jest.clearAllMocks();
56+
fireEvent.click(screen.getByTestId('extension-modal-overlay'));
57+
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
58+
59+
jest.clearAllMocks();
60+
fireEvent.click(screen.getByTestId('extension-modal-content'));
61+
expect(defaultProps.onClose).not.toHaveBeenCalled();
62+
63+
fireEvent.keyDown(document, { key: 'Escape' });
64+
expect(defaultProps.onClose).toHaveBeenCalledTimes(1);
65+
66+
jest.clearAllMocks();
67+
fireEvent.keyDown(document, { key: 'Enter' });
68+
expect(defaultProps.onClose).not.toHaveBeenCalled();
69+
});
70+
test('should render close button correctly', () => {
71+
setupTest({ isLoading: false, data: { allExtensionRequests: [] } });
72+
73+
const closeButton = screen.getByTestId('close-button');
74+
expect(closeButton).toBeInTheDocument();
75+
expect(closeButton).toBeVisible();
76+
expect(closeButton).toHaveTextContent('Close');
77+
});
78+
79+
test('should show request extension button when no pending requests', () => {
80+
setupTest({
81+
isLoading: false,
82+
data: {
83+
allExtensionRequests: [
84+
{ id: 1, status: 'APPROVED', reviewedBy: 'Admin' },
85+
],
86+
},
87+
});
88+
expect(
89+
screen.getByTestId('request-extension-button')
90+
).toBeInTheDocument();
91+
});
92+
93+
test('should render modal correctly for approved request with large timestamp', () => {
94+
const approvedRequest = mockExtensionRequests.find(
95+
(req) => req.status === 'APPROVED'
96+
);
97+
98+
setupTest({
99+
isLoading: false,
100+
data: {
101+
allExtensionRequests: [approvedRequest],
102+
},
103+
});
104+
expect(screen.getByTestId('modal-title')).toBeInTheDocument();
105+
});
106+
107+
test('should render modal for denied extension requests', () => {
108+
const deniedRequest = mockExtensionRequests.find(
109+
(req) => req.status === 'DENIED'
110+
);
111+
112+
setupTest({
113+
isLoading: false,
114+
data: {
115+
allExtensionRequests: [deniedRequest],
116+
},
117+
});
118+
expect(screen.getByTestId('modal-title')).toBeInTheDocument();
119+
});
120+
121+
test('should test formatToRelativeTime function', () => {
122+
const timestamp = 1640995200;
123+
const result = formatToRelativeTime(timestamp);
124+
expect(result).toBe('3 years ago');
125+
});
126+
});

0 commit comments

Comments
 (0)