From 4748e271af544889308565ff4ae11819a36cc3bc Mon Sep 17 00:00:00 2001 From: Rishi <148757583+rishirishhh@users.noreply.github.com> Date: Wed, 16 Oct 2024 19:17:52 +0530 Subject: [PATCH 1/2] enhancement: added shimmer effect (#884) * enhancement: added shimmer effect * added e2e tests * removed comments from style.css * Update tests.yml This will be removed eventually * Update tests.yml * added uniform design to the card * fixed small error --- .../extension-requests.test.js | 100 +++++++ extension-requests/script.js | 272 ++++++++++++++---- extension-requests/style.css | 33 +++ 3 files changed, 349 insertions(+), 56 deletions(-) diff --git a/__tests__/extension-requests/extension-requests.test.js b/__tests__/extension-requests/extension-requests.test.js index b9ffc5a2..b8d50c5e 100644 --- a/__tests__/extension-requests/extension-requests.test.js +++ b/__tests__/extension-requests/extension-requests.test.js @@ -640,6 +640,106 @@ describe('Tests the Extension Requests Screen', () => { expect(cardCount === 3 || cardCount === 7).toBe(true); }); + it('checks whether the shimmer effect is visible under dev flag only for the assignee image element', async () => { + await page.goto(`${baseUrl}/?dev=true`); + const assignImageSelector = await page.$$( + '[data-testid="assignee-image skeleton"]', + ); + expect(assignImageSelector).toBeTruthy(); + + await page.waitForTimeout(5000); + const hasSkeletonClassAfter = await page.$eval('.assignee-image', (el) => + el.classList.contains('skeleton'), + ); + expect(hasSkeletonClassAfter).toBe(false); + }); + + it('checks whether the shimmer effect is visible under dev flag only for the assignee name element', async () => { + await page.goto(`${baseUrl}/?dev=true`); + const assignNameSelector = await page.$$( + '[data-testid="assignee-name skeleton-text"]', + ); + expect(assignNameSelector).toBeTruthy(); + await page.waitForTimeout(5000); + const hasSkeletonClassAfter = await page.$eval('.assignee-name', (el) => + el.classList.contains('skeleton-text'), + ); + expect(hasSkeletonClassAfter).toBe(false); + }); + + it('checks whether the shimmer effect is working for deadlineValue element under feature flag', async () => { + await page.goto(`${baseUrl}/?dev=true`); + const deadlineValueSelector = await page.$$( + '[data-testid="skeleton-span"]', + ); + expect(deadlineValueSelector).toBeTruthy(); + await page.waitForTimeout(5000); + const hasSkeletonClassAfter = await page.$eval('.tooltip-container', (el) => + el.classList.contains('skeleton-span'), + ); + expect(hasSkeletonClassAfter).toBe(false); + }); + + it('checks whether the shimmer effect is working for requestedValue element under feature flag', async () => { + await page.goto(`${baseUrl}/?dev=true`); + const requestedValueSelector = await page.$$( + '[data-testid="skeleton-text"]', + ); + expect(requestedValueSelector).toBeTruthy(); + await page.waitForTimeout(5000); + const hasSkeletonClassAfter = await page.$eval('.requested-day', (el) => + el.classList.contains('skeleton-text'), + ); + expect(hasSkeletonClassAfter).toBe(false); + }); + it('checks whether the shimmer effect is working for newDeadlineValue element under feature flag', async () => { + await page.goto(`${baseUrl}/?dev=true`); + const newDeadlineValueSelector = await page.$$( + '[data-testid="skeleton-span"]', + ); + expect(newDeadlineValueSelector).toBeTruthy(); + await page.waitForTimeout(5000); + const hasSkeletonClassAfter = await page.$eval('.requested-day', (el) => + el.classList.contains('skeleton-span'), + ); + expect(hasSkeletonClassAfter).toBe(false); + }); + + it('checks whether the shimmer effect is working for extensionRequestNumberValue element under feature flag', async () => { + await page.goto(`${baseUrl}/?dev=true`); + const extensionRequestNumberValueSelector = await page.$$( + '[data-testid="skeleton-span"]', + ); + expect(extensionRequestNumberValueSelector).toBeTruthy(); + await page.waitForTimeout(5000); + const hasSkeletonClassAfter = await page.$eval( + '.extension-request-number', + (el) => el.classList.contains('skeleton-span'), + ); + expect(hasSkeletonClassAfter).toBe(false); + }); + + it('checks whether the shimmer effect is visible under dev flag only for the statusSiteLink element', async () => { + await page.goto(`${baseUrl}/?dev=true`); + const statusSiteLinkSelector = await page.$$( + '[data-testid="external-link skeleton-link"]', + ); + expect(statusSiteLinkSelector).toBeTruthy(); + await page.waitForTimeout(5000); + const hasSkeletonClassAfter = await page.$eval('.external-link', (el) => + el.classList.contains('skeleton-link'), + ); + expect(hasSkeletonClassAfter).toBe(false); + }); + + it('checks whether the shimmer effect is visible under dev flag only for the taskStatusValue element', async () => { + await page.goto(`${baseUrl}/?dev=true`); + const taskStatusValueElement = await page.$$( + '[data-testid="skeleton-span"]', + ); + expect(taskStatusValueElement).toBeTruthy(); + }); + it('Checks whether the card is not removed from display when api call is unsuccessful', async () => { const extensionCards = await page.$$('.extension-card'); diff --git a/extension-requests/script.js b/extension-requests/script.js index c2b5f3a8..dfe1818b 100644 --- a/extension-requests/script.js +++ b/extension-requests/script.js @@ -240,7 +240,11 @@ async function populateExtensionRequests(query = {}, newLink) { return; } for (let data of allExtensionRequests) { - createExtensionCard(data); + if (query.dev) { + createExtensionCard(data, true); + } else { + createExtensionCard(data); + } } initializeAccordions(); } catch (error) { @@ -260,7 +264,11 @@ const intersectionObserver = new IntersectionObserver(async (entries) => { return; } if (entries[0].isIntersecting && !isDataLoading) { - await populateExtensionRequests({}, nextLink); + if (isDev) { + await populateExtensionRequests({ dev: true }, nextLink); + } else { + await populateExtensionRequests({}, nextLink); + } } }); @@ -462,7 +470,7 @@ const handleFormPropagation = async (event) => { event.preventDefault(); }; -async function createExtensionCard(data) { +async function createExtensionCard(data, dev) { renderLogRecord[data.id] = []; //Create card element const rootElement = createElement({ @@ -470,8 +478,12 @@ async function createExtensionCard(data) { attributes: { class: 'extension-card' }, }); extensionRequestsContainer.appendChild(rootElement); - const removeSpinner = addSpinner(rootElement); - rootElement.classList.add('disabled'); + let removeSpinner; + if (!dev) { + removeSpinner = addSpinner(rootElement); + rootElement.classList.add('disabled'); + } + //Api calls const userDataPromise = getUser(data.assignee); const taskDataPromise = getTaskDetails(data.taskId); @@ -569,12 +581,24 @@ async function createExtensionCard(data) { type: 'div', attributes: { class: 'details-container' }, }); - const statusSiteLink = createElement({ - type: 'a', - attributes: { - class: 'external-link', - }, - }); + + let statusSiteLink; + if (dev) { + statusSiteLink = createElement({ + type: 'a', + attributes: { + class: 'external-link skeleton-link', + 'data-testid': 'external-link skeleton-link', + }, + }); + } else { + statusSiteLink = createElement({ + type: 'a', + attributes: { + class: 'external-link', + }, + }); + } const taskTitle = createElement({ type: 'span', attributes: { class: 'task-title' }, @@ -598,15 +622,27 @@ async function createExtensionCard(data) { innerText: `Deadline${isDeadLineCrossed ? ' ' : ' in '}`, }); deadlineContainer.appendChild(deadlineText); - const deadlineValue = createElement({ - type: 'span', - innerText: `${deadlineDays}`, - attributes: { - class: `tooltip-container ${ - isDeadLineCrossed && isStatusPending ? 'red-text' : '' - }`, - }, - }); + + let deadlineValue; + if (dev) { + deadlineValue = createElement({ + type: 'span', + attributes: { + class: 'skeleton-span', + 'data-testid': 'skeleton-span', + }, + }); + } else { + deadlineValue = createElement({ + type: 'span', + innerText: `${deadlineDays}`, + attributes: { + class: `tooltip-container ${ + isDeadLineCrossed && isStatusPending ? 'red-text' : '' + }`, + }, + }); + } deadlineContainer.appendChild(deadlineValue); const deadlineTooltip = createElement({ type: 'span', @@ -625,13 +661,25 @@ async function createExtensionCard(data) { innerText: 'Requested ', }); requestedContainer.appendChild(requestedText); - const requestedValue = createElement({ - type: 'span', - attributes: { - class: `requested-day tooltip-container ${requestedDaysTextColor}`, - }, - innerText: ` ${requestedDaysAgo}`, - }); + + let requestedValue; + if (dev) { + requestedValue = createElement({ + type: 'span', + attributes: { + class: 'skeleton-text', + 'data-testid': 'skeleton-text', + }, + }); + } else { + requestedValue = createElement({ + type: 'span', + attributes: { + class: `requested-day tooltip-container ${requestedDaysTextColor}`, + }, + innerText: `${requestedDaysAgo}`, + }); + } const requestedToolTip = createElement({ type: 'span', attributes: { class: 'tooltip' }, @@ -647,9 +695,21 @@ async function createExtensionCard(data) { innerText: 'Task status ', }); taskStatusContainer.appendChild(taskStatusText); - const taskStatusValue = createElement({ - type: 'span', - }); + + let taskStatusValue; + if (dev) { + taskStatusValue = createElement({ + type: 'span', + attributes: { + class: 'skeleton-span', + 'data-testid': 'skeleton-span', + }, + }); + } else { + taskStatusValue = createElement({ + type: 'span', + }); + } taskStatusContainer.appendChild(taskStatusValue); const datesContainer = createElement({ type: 'div', @@ -683,11 +743,20 @@ async function createExtensionCard(data) { innerText: `New deadline${isNewDeadLineCrossed ? ' ' : ' in '}`, }); newDeadlineContainer.appendChild(newDeadlineText); - const newDeadlineValue = createElement({ - type: 'span', - attributes: { class: 'requested-day tooltip-container' }, - innerText: ` ${newDeadlineDays}`, - }); + + let newDeadlineValue; + if (dev) { + newDeadlineValue = createElement({ + type: 'span', + attributes: { class: 'skeleton-span', 'data-testid': 'skeleton-span' }, + }); + } else { + newDeadlineValue = createElement({ + type: 'span', + attributes: { class: 'requested-day tooltip-container' }, + innerText: ` ${newDeadlineDays}`, + }); + } const newDeadlineToolTip = createElement({ type: 'span', attributes: { class: 'tooltip' }, @@ -707,11 +776,19 @@ async function createExtensionCard(data) { }); extensionForContainer.appendChild(extensionForText); - const extensionForValue = createElement({ - type: 'span', - attributes: { class: 'tooltip-container' }, - innerText: ` +${extensionDays}`, - }); + let extensionForValue; + if (dev) { + extensionForValue = createElement({ + type: 'span', + attributes: { class: 'skeleton-span' }, + }); + } else { + extensionForValue = createElement({ + type: 'span', + attributes: { class: 'tooltip-container' }, + innerText: ` +${extensionDays}`, + }); + } const extensionToolTip = createElement({ type: 'span', attributes: { class: 'tooltip' }, @@ -744,11 +821,19 @@ async function createExtensionCard(data) { const requestNumber = data.requestNumber || 1; - const extensionRequestNumberValue = createElement({ - type: 'span', - attributes: { class: 'extension-request-number' }, - innerText: `#${requestNumber}`, - }); + let extensionRequestNumberValue; + if (dev) { + extensionRequestNumberValue = createElement({ + type: 'span', + attributes: { class: 'skeleton-span', 'data-testid': 'skeleton-span' }, + }); + } else { + extensionRequestNumberValue = createElement({ + type: 'span', + attributes: { class: 'extension-request-number' }, + innerText: `#${requestNumber}`, + }); + } extensionRequestNumberContainer.appendChild(extensionRequestNumberValue); const cardAssigneeButtonContainer = createElement({ type: 'div', @@ -765,16 +850,40 @@ async function createExtensionCard(data) { innerText: 'Assigned to', }); assigneeContainer.appendChild(assigneeText); - const assigneeImage = createElement({ - type: 'img', - attributes: { class: 'assignee-image' }, - }); + let assigneeImage; + if (dev) { + assigneeImage = createElement({ + type: 'img', + attributes: { + class: 'assignee-image skeleton', + 'data-testid': 'assignee-image skeleton', + }, + }); + } else { + assigneeImage = createElement({ + type: 'img', + attributes: { class: 'assignee-image' }, + }); + } assigneeContainer.appendChild(assigneeImage); - const assigneeNameElement = createElement({ - type: 'span', - attributes: { class: 'assignee-name' }, - }); + + let assigneeNameElement; + if (dev) { + assigneeNameElement = createElement({ + type: 'span', + attributes: { + class: 'assignee-name skeleton-text', + 'data-testid': 'assignee-name skeleton-text', + }, + }); + } else { + assigneeNameElement = createElement({ + type: 'span', + attributes: { class: 'assignee-name' }, + }); + } assigneeContainer.appendChild(assigneeNameElement); + const extensionCardButtons = createElement({ type: 'div', attributes: { class: 'extension-card-buttons' }, @@ -1198,10 +1307,22 @@ async function createExtensionCard(data) { userFirstName = userFirstName ?? ''; statusSiteLink.href = `${STATUS_BASE_URL}/tasks/${data.taskId}`; statusSiteLink.innerText = taskData.title; + if (dev) { + statusSiteLink.classList.remove('skeleton-link'); + } assigneeImage.src = userImage; + if (dev) { + assigneeImage.classList.remove('skeleton'); + } assigneeImage.alt = userFirstName; assigneeNameElement.innerText = userFirstName; + if (dev) { + assigneeNameElement.classList.remove('skeleton-text'); + } taskStatusValue.innerText = ` ${taskStatus}`; + if (dev) { + taskStatusValue.classList.remove('skeleton-span'); + } CommitedHourslabel.innerText = 'Commited Hours:'; if (comittedHours) { CommitedHoursContent.innerText = `${comittedHours / 4} hrs / week`; @@ -1209,10 +1330,49 @@ async function createExtensionCard(data) { CommitedHoursContent.innerText = 'Missing'; CommitedHoursContent.classList.add('label-content-missing'); } + if (dev) { + deadlineValue.classList.remove('skeleton-span'); + deadlineValue.innerText = `${deadlineDays}`; - removeSpinner(); - renderExtensionCreatedLog(); - rootElement.classList.remove('disabled'); + deadlineValue.classList.add('tooltip-container'); + if (isDeadLineCrossed && isStatusPending) { + deadlineValue.classList.add('red-text'); + } + } + + if (dev) { + requestedValue.classList.remove('skeleton-text'); + requestedValue.innerText = `${requestedDaysAgo}`; + + requestedValue.classList.add( + 'requested-day', + 'tooltip-container', + requestedDaysTextColor, + ); + } + + if (dev) { + newDeadlineValue.classList.remove('skeleton-span'); + newDeadlineValue.innerText = ` ${newDeadlineDays}`; + newDeadlineValue.classList.add('requested-day', 'tooltip-container'); + } + + if (dev) { + extensionForValue.classList.remove('skeleton-span'); + extensionForValue.innerText = ` +${extensionDays}`; + extensionForValue.classList.add('tooltip-container'); + } + + if (dev) { + extensionRequestNumberValue.classList.remove('skeleton-span'); + extensionRequestNumberValue.innerText = `#${requestNumber}`; + extensionRequestNumberValue.classList.add('extension-request-number'); + } + if (!dev) { + removeSpinner(); + renderExtensionCreatedLog(); + rootElement.classList.remove('disabled'); + } }); return rootElement; diff --git a/extension-requests/style.css b/extension-requests/style.css index 50d70d1b..bfcafebe 100644 --- a/extension-requests/style.css +++ b/extension-requests/style.css @@ -165,6 +165,39 @@ .approve-button:hover { background-color: var(--green500); } +.skeleton { + width: 1.5rem; + height: 1.5rem; + border-radius: 50%; + animation: skeleton-loading 1s linear infinite alternate; +} + +.skeleton-text, +.skeleton-link, +.skeleton-span { + width: 3rem; + height: 1rem; + background-color: hsl(200, 20%, 70%); + animation: skeleton-loading 1s linear infinite alternate; +} + +.skeleton-link, +.skeleton-span, +.skeleton-text { + width: 8rem; + display: inline-flex; + border-radius: 0.125rem; + line-height: 3rem; +} + +@keyframes skeleton-loading { + 0% { + background-color: hsl(200, 20%, 70%); + } + 100% { + background-color: hsl(200, 20%, 80%); + } +} .edit-button { background: none; From 74765818572c1ed7a43d9400d3a9522f250b6b13 Mon Sep 17 00:00:00 2001 From: shantanu-02 <126399165+shantanu-02@users.noreply.github.com> Date: Thu, 17 Oct 2024 01:55:32 +0530 Subject: [PATCH 2/2] Feature/repository links for webpages (#863) * fix: Added footer contaning repo link for every webpage * fix: Added footer contaning repo link for every webpage * Enhancement: Added tests for new URLs * Enhancement: Added tests for new URLs * Fix: Removed Live URLs which do not have footer from testing * Enhancement: Fixed position for footer * fix: removed commented content * Enhancement: Used adata-test-id instead of class/id * Fix: footer position styling * Fix: changes using data-test-id * Fix: Prettier changes * Fix: minor bug fixes * Enhancement: Made reusable footer component * Enhancement: Replaced footer of webpages with footer component * Fix: Minor fixes * Fix: Removed additional footers * Fix: Removed additional footers * Fix: Removed additional footers * Fix: Added styles in global stylesheet * Fix: Minor fix of stylesheet * Fix: Test timeout error * Fix: Using data-test-id instead of classes --------- Co-authored-by: Vinit khandal <111434418+vinit717@users.noreply.github.com> --- __tests__/applications/applications.test.js | 31 +++++++++ __tests__/groups/group.test.js | 31 +++++++++ __tests__/users/App.test.js | 9 ++- applications/index.html | 7 ++ footer/footerComponent.js | 19 ++++++ global.css | 7 ++ groups/index.html | 10 ++- users/discord/components/TabsSection.js | 6 +- .../discord/components/UserDetailsSection.js | 65 ++++++++++--------- users/discord/components/UsersSection.js | 9 ++- users/discord/index.html | 8 ++- 11 files changed, 166 insertions(+), 36 deletions(-) create mode 100644 footer/footerComponent.js diff --git a/__tests__/applications/applications.test.js b/__tests__/applications/applications.test.js index a8ead04f..e0081e76 100644 --- a/__tests__/applications/applications.test.js +++ b/__tests__/applications/applications.test.js @@ -295,4 +295,35 @@ describe('Applications page', () => { ); await page.waitForNetworkIdle(); }); + + it('should display the footer with the correct repo link', async () => { + const footer = await page.$('[data-test-id="footer"]'); + expect(footer).toBeTruthy(); + + const infoRepo = await footer.$('[data-test-id="info-repo"]'); + expect(infoRepo).toBeTruthy(); + + const repoLink = await infoRepo.$('[data-test-id="repo-link"]'); + expect(repoLink).toBeTruthy(); + + const repoLinkHref = await page.evaluate((el) => el.href, repoLink); + expect(repoLinkHref).toBe( + 'https://github.com/Real-Dev-Squad/website-dashboard', + ); + + const repoLinkTarget = await page.evaluate((el) => el.target, repoLink); + expect(repoLinkTarget).toBe('_blank'); + + const repoLinkRel = await page.evaluate((el) => el.rel, repoLink); + expect(repoLinkRel).toBe('noopener noreferrer'); + + const repoLinkText = await page.evaluate((el) => el.innerText, repoLink); + expect(repoLinkText).toBe('open sourced repo'); + + const repoLinkClass = await page.evaluate((el) => el.className, repoLink); + expect(repoLinkClass).toBe(''); + + const repoLinkStyle = await page.evaluate((el) => el.style, repoLink); + expect(repoLinkStyle).toBeTruthy(); + }); }); diff --git a/__tests__/groups/group.test.js b/__tests__/groups/group.test.js index 4400450f..a158810f 100644 --- a/__tests__/groups/group.test.js +++ b/__tests__/groups/group.test.js @@ -266,4 +266,35 @@ describe('Discord Groups Page', () => { expect(noGroupDiv).toBeTruthy(); }); + + it('should display the footer with the correct repo link', async () => { + const footer = await page.$('[data-test-id="footer"]'); + expect(footer).toBeTruthy(); + + const infoRepo = await footer.$('[data-test-id="info-repo"]'); + expect(infoRepo).toBeTruthy(); + + const repoLink = await infoRepo.$('[data-test-id="repo-link"]'); + expect(repoLink).toBeTruthy(); + + const repoLinkHref = await page.evaluate((el) => el.href, repoLink); + expect(repoLinkHref).toBe( + 'https://github.com/Real-Dev-Squad/website-dashboard', + ); + + const repoLinkTarget = await page.evaluate((el) => el.target, repoLink); + expect(repoLinkTarget).toBe('_blank'); + + const repoLinkRel = await page.evaluate((el) => el.rel, repoLink); + expect(repoLinkRel).toBe('noopener noreferrer'); + + const repoLinkText = await page.evaluate((el) => el.innerText, repoLink); + expect(repoLinkText).toBe('open sourced repo'); + + const repoLinkClass = await page.evaluate((el) => el.className, repoLink); + expect(repoLinkClass).toBe(''); + + const repoLinkStyle = await page.evaluate((el) => el.style, repoLink); + expect(repoLinkStyle).toBeTruthy(); + }); }); diff --git a/__tests__/users/App.test.js b/__tests__/users/App.test.js index b612408f..05b36b53 100644 --- a/__tests__/users/App.test.js +++ b/__tests__/users/App.test.js @@ -6,7 +6,7 @@ const API_BASE_URL = 'https://staging-api.realdevsquad.com'; describe('App Component', () => { let browser; let page; - jest.setTimeout(60000); + jest.setTimeout(90000); let config = { launchOptions: { headless: 'new', @@ -68,6 +68,11 @@ describe('App Component', () => { }); it('should render all sections', async () => { + await page.waitForSelector('.tabs_section'); + await page.waitForSelector('.users_section'); + await page.waitForSelector('.user_card'); + await page.waitForSelector('.user_details_section'); + let tabsSection = await page.$('.tabs_section'); let usersSection = await page.$('.users_section'); let firstUser = await page.$('.user_card'); @@ -82,6 +87,8 @@ describe('App Component', () => { }); it('should update the URL query string and re-render the app', async () => { + await page.waitForSelector('[data_key="verified"]'); + // Click on the "Linked Accounts" tab await page.click('[data_key="verified"]'); diff --git a/applications/index.html b/applications/index.html index 8ab396b0..b37d33da 100644 --- a/applications/index.html +++ b/applications/index.html @@ -4,6 +4,7 @@ +