Skip to content

Commit b88976c

Browse files
UI for applications (#635)
* application ui initial * intial API integration for applications * small css changes * implemented pagination for applications * filter UI * filter toggle logic * status filter logic * clear filter * UI for showing applications details * application details UI completed * application close from button * feedback modal initial * feedback input in application details * applicaton update completed * applicated update error handling * unauthorized case handling * test for application till clear interval * updated application test for clearing filter * updated test for pagination * tesrt for showing application details modal * test for application update * change before each * application details full width on smaller screens * no applications found logic * removed unnecessary change * base url change * load env script in page * made suggested changes * updated test cases * added api calls in try catch and added alt --------- Co-authored-by: Satyam Bajpai <[email protected]>
1 parent 3cae702 commit b88976c

File tree

7 files changed

+1425
-0
lines changed

7 files changed

+1425
-0
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
const puppeteer = require('puppeteer');
2+
3+
const {
4+
fetchedApplications,
5+
acceptedApplications,
6+
} = require('../../mock-data/applications');
7+
const { superUserForAudiLogs } = require('../../mock-data/users');
8+
9+
const SITE_URL = 'http://localhost:8000';
10+
// helper/loadEnv.js file causes API_BASE_URL to be stagin-api on local env url in taskRequest/index.html
11+
const API_BASE_URL = 'https://staging-api.realdevsquad.com';
12+
13+
describe('Applications page', () => {
14+
let browser;
15+
let page;
16+
17+
jest.setTimeout(60000);
18+
19+
beforeEach(async () => {
20+
browser = await puppeteer.launch({
21+
headless: 'new',
22+
ignoreHTTPSErrors: true,
23+
args: ['--incognito', '--disable-web-security'],
24+
});
25+
});
26+
beforeEach(async () => {
27+
page = await browser.newPage();
28+
29+
await page.setRequestInterception(true);
30+
31+
page.on('request', (request) => {
32+
if (
33+
request.url() === `${API_BASE_URL}/applications?size=5` ||
34+
request.url() ===
35+
`${API_BASE_URL}/applications?next=YwTi6zFNI3GlDsZVjD8C&size=5`
36+
) {
37+
request.respond({
38+
status: 200,
39+
contentType: 'application/json',
40+
body: JSON.stringify({
41+
applications: fetchedApplications,
42+
next: '/applications?next=YwTi6zFNI3GlDsZVjD8C&size=5',
43+
}),
44+
headers: {
45+
'Access-Control-Allow-Origin': '*',
46+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
47+
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
48+
},
49+
});
50+
} else if (
51+
request.url() === `${API_BASE_URL}/applications?size=5&status=accepted`
52+
) {
53+
request.respond({
54+
status: 200,
55+
contentType: 'application/json',
56+
body: JSON.stringify({ applications: acceptedApplications }),
57+
headers: {
58+
'Access-Control-Allow-Origin': '*',
59+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
60+
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
61+
},
62+
});
63+
} else if (request.url() === `${API_BASE_URL}/users/self`) {
64+
request.respond({
65+
status: 200,
66+
contentType: 'application/json',
67+
headers: {
68+
'Access-Control-Allow-Origin': '*',
69+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
70+
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
71+
},
72+
body: JSON.stringify(superUserForAudiLogs),
73+
});
74+
} else if (
75+
request.url() === `${API_BASE_URL}/applications/lavEduxsb2C5Bl4s289P`
76+
) {
77+
request.respond({
78+
status: 200,
79+
contentType: 'application/json',
80+
body: JSON.stringify({
81+
message: 'application updated successfully!',
82+
}),
83+
headers: {
84+
'Access-Control-Allow-Origin': '*',
85+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
86+
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
87+
},
88+
});
89+
} else {
90+
request.continue();
91+
}
92+
});
93+
await page.goto(`${SITE_URL}/applications`);
94+
await page.waitForNetworkIdle();
95+
});
96+
97+
afterEach(async () => {
98+
await page.close();
99+
});
100+
101+
afterAll(async () => {
102+
await browser.close();
103+
});
104+
105+
it('should render the initial UI elements', async function () {
106+
const title = await page.$('.header h1');
107+
const filterButton = await page.$('.filter-button');
108+
const applicationCards = await page.$$('.application-card');
109+
expect(title).toBeTruthy();
110+
expect(filterButton).toBeTruthy();
111+
expect(applicationCards).toBeTruthy();
112+
expect(applicationCards.length).toBe(5);
113+
});
114+
115+
it('should load and render the accepted application requests when accept is selected from filter, and after clearing the filter it should again show all the applications', async function () {
116+
await page.click('.filter-button');
117+
118+
await page.$eval('input[name="status"][value="accepted"]', (radio) =>
119+
radio.click(),
120+
);
121+
await page.click('.apply-filter-button');
122+
await page.waitForNetworkIdle();
123+
let applicationCards = await page.$$('.application-card');
124+
expect(applicationCards.length).toBe(4);
125+
126+
await page.click('.filter-button');
127+
await page.click('.clear-btn');
128+
129+
await page.waitForNetworkIdle();
130+
applicationCards = await page.$$('.application-card');
131+
expect(applicationCards.length).toBe(5);
132+
});
133+
134+
it('should load more applications on going to the bottom of the page', async function () {
135+
let applicationCards = await page.$$('.application-card');
136+
expect(applicationCards.length).toBe(5);
137+
await page.evaluate(() => {
138+
const element = document.querySelector('#page_bottom_element');
139+
if (element) {
140+
element.scrollIntoView({ behavior: 'auto' });
141+
}
142+
});
143+
await page.waitForNetworkIdle();
144+
applicationCards = await page.$$('.application-card');
145+
expect(applicationCards.length).toBe(10);
146+
});
147+
148+
it('should open application details modal for application, when user click on view details on any card', async function () {
149+
const applicationDetailsModal = await page.$('.application-details');
150+
expect(
151+
await applicationDetailsModal.evaluate((el) =>
152+
el.classList.contains('hidden'),
153+
),
154+
).toBe(true);
155+
await page.click('.view-details-button');
156+
expect(
157+
await applicationDetailsModal.evaluate((el) =>
158+
el.classList.contains('hidden'),
159+
),
160+
).toBe(false);
161+
});
162+
163+
it('should show toast message with application updated successfully', async function () {
164+
await page.click('.view-details-button');
165+
await page.click('.application-details-accept');
166+
const toast = await page.$('#toast');
167+
expect(await toast.evaluate((el) => el.classList.contains('hidden'))).toBe(
168+
false,
169+
);
170+
expect(await toast.evaluate((el) => el.innerText)).toBe(
171+
'application updated successfully!',
172+
);
173+
await page.waitForNetworkIdle();
174+
});
175+
});
Lines changed: 3 additions & 0 deletions
Loading

applications/index.html

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<link rel="stylesheet" href="/applications/style.css" />
7+
<script type="module" src="/applications/script.js" defer></script>
8+
<script src="/helpers/loadENV.js"></script>
9+
<title>Applications</title>
10+
</head>
11+
<body>
12+
<header class="header">
13+
<h1>RDS Join Applications</h1>
14+
</header>
15+
<div id="toast" class="hidden"></div>
16+
<div class="container">
17+
<div class="backdrop"></div>
18+
<button id="filter-button" class="filter-button hidden">
19+
<span class="filter-text">Filters</span>
20+
21+
<img
22+
class="funnel-icon"
23+
src="/taskRequests/assets/funnel.svg"
24+
alt="funnel icon"
25+
/>
26+
</button>
27+
<p class="no_applications_found hidden">No applications Found!</p>
28+
<div
29+
class="filter-modal hidden"
30+
role="dialog"
31+
aria-labelledby="filter-dialog-label"
32+
>
33+
<div class="filter-head">
34+
<h3 id="filter-dialog-label">Filters</h3>
35+
<button id="clear-button" class="clear-btn">Clear</button>
36+
</div>
37+
<div class="filters-container">
38+
<h2>Status</h2>
39+
<form class="modal-form" id="status-filter">
40+
<div>
41+
<input
42+
type="radio"
43+
id="rejected"
44+
name="status"
45+
value="rejected"
46+
/>
47+
<label for="rejected">Rejected</label>
48+
</div>
49+
<div>
50+
<input
51+
type="radio"
52+
id="accepted"
53+
name="status"
54+
value="accepted"
55+
/>
56+
<label for="accepted">Accepted</label>
57+
</div>
58+
59+
<div>
60+
<input type="radio" id="pending" name="status" value="pending" />
61+
<label for="pending">Pending</label>
62+
</div>
63+
</form>
64+
</div>
65+
<button id="apply-filter-button" class="apply-filter-button">
66+
Apply Filter
67+
</button>
68+
</div>
69+
<div class="backdrop-blur"></div>
70+
<div class="application-details hidden">
71+
<button class="application-close-button">
72+
<img
73+
src="./assets/closeButton.svg"
74+
class="close-button-icon"
75+
alt="close icon"
76+
/>
77+
</button>
78+
<div class="application-details-main"></div>
79+
<div class="application-details-actions">
80+
<button class="application-details-reject">Reject</button>
81+
<button class="application-details-accept">Accept</button>
82+
</div>
83+
</div>
84+
<main class="application-container"></main>
85+
<div class="loader hidden">Loading...</div>
86+
<div id="page_bottom_element"></div>
87+
</div>
88+
</body>
89+
</html>

0 commit comments

Comments
 (0)