From bffb232ae92241e235090ba01970d393b971e0b7 Mon Sep 17 00:00:00 2001 From: Goeme Nthomiwa Date: Mon, 15 Apr 2024 14:03:10 -0700 Subject: [PATCH 01/10] fix: geo 513 fix footer styles (#391) --- frontend/src/components/Footer.vue | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/Footer.vue b/frontend/src/components/Footer.vue index a52586baf..16a36b8f3 100644 --- a/frontend/src/components/Footer.vue +++ b/frontend/src/components/Footer.vue @@ -1,5 +1,5 @@ - + - \ No newline at end of file + diff --git a/frontend/src/components/Logout.vue b/frontend/src/components/Logout.vue index b304a0f48..4c512bf04 100644 --- a/frontend/src/components/Logout.vue +++ b/frontend/src/components/Logout.vue @@ -2,7 +2,7 @@ - + You have logged out. Login Again @@ -21,7 +21,7 @@ export default { data() { return { - authRoutesLogin: sanitizeUrl(AuthRoutes.LOGIN_BCEID) + authRoutesLogin: sanitizeUrl(AuthRoutes.LOGIN_BCEID), }; }, mounted() { @@ -31,20 +31,18 @@ export default { redirectToLogin() { authStore().setJwtToken(); window.location.href = this.authRoutesLogin; - } - } + }, + }, }; diff --git a/frontend/src/components/MsieBanner.vue b/frontend/src/components/MsieBanner.vue index f92a64842..257bf8219 100644 --- a/frontend/src/components/MsieBanner.vue +++ b/frontend/src/components/MsieBanner.vue @@ -1,5 +1,12 @@ diff --git a/maintenance/src/assets/css/styles.css b/maintenance/src/assets/css/styles.css index 6866138cc..93593364f 100644 --- a/maintenance/src/assets/css/styles.css +++ b/maintenance/src/assets/css/styles.css @@ -18,7 +18,12 @@ body { a { color: #1976d2; - text-decoration: none; + text-decoration: underline; + font-size: 14px; + + &.title { + text-decoration: none; + } } a:hover { @@ -134,6 +139,7 @@ footer .first-nation-acknowledgement { line-height: 21px; transition: none !important; border-top: 4px solid rgb(252, 186, 25) !important; + border-bottom: 4px solid rgb(252, 186, 25) !important; overflow: hidden; } footer .first-nation-acknowledgement div { @@ -143,7 +149,7 @@ footer .first-nation-acknowledgement div { footer .more-info { display: flex; flex-direction: column; - font-size: 12px; + font-size: 14px; align-items: center; justify-content: center; padding-top: 50px; @@ -166,7 +172,7 @@ a.contact-email { min-height: 40px !important; padding: 4px; color: #464341; - font-size: 12px !important; + font-size: 14px !important; } .footer-btn:hover { background-color: #eeeeee; diff --git a/maintenance/src/index.html b/maintenance/src/index.html index ad2e91862..ae3086d1c 100644 --- a/maintenance/src/index.html +++ b/maintenance/src/index.html @@ -15,10 +15,9 @@
- + - + alt="B.C. Government Logo">

Pay Transparency Reporting

From 482db35888bed907ac3eab3c7fa752821a373e6b Mon Sep 17 00:00:00 2001 From: Sukanya Rath <98050194+sukanya-rath@users.noreply.github.com> Date: Tue, 16 Apr 2024 16:31:04 -0700 Subject: [PATCH 06/10] feat: added environment (#397) Signed-off-by: Sukanya Rath --- .github/workflows/cd-to-test-on-workflow-dispatch.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/cd-to-test-on-workflow-dispatch.yml b/.github/workflows/cd-to-test-on-workflow-dispatch.yml index 2308674fd..2944efca9 100644 --- a/.github/workflows/cd-to-test-on-workflow-dispatch.yml +++ b/.github/workflows/cd-to-test-on-workflow-dispatch.yml @@ -56,6 +56,7 @@ jobs: secrets: inherit with: backend-external-url: https://pay-transparency-test-backend-external.apps.silver.devops.gov.bc.ca/api + environment: test zap: name: ZAP Scan needs: [deploys] From e66bc5011e4cd0839ecd6a1f98ddce06f47432fd Mon Sep 17 00:00:00 2001 From: Goeme Nthomiwa Date: Tue, 16 Apr 2024 17:01:36 -0700 Subject: [PATCH 07/10] fix: update footer to fix typo (#399) --- frontend/src/components/Footer.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/Footer.vue b/frontend/src/components/Footer.vue index 16a36b8f3..b1ed3c16e 100644 --- a/frontend/src/components/Footer.vue +++ b/frontend/src/components/Footer.vue @@ -60,7 +60,7 @@
- The B.C. public Service acknowledges the territories of First Nations + The B.C. Public Service acknowledges the territories of First Nations around B.C. and is grateful to carry out our work on these lands. We acknowledge the rights, interests, priorities, and concerns of all Indigenous Peoples - First Nations, Métis, and Inuit - respecting and From b13ff1af905d4bdfadde8c5752d757630a8a1ef7 Mon Sep 17 00:00:00 2001 From: Goeme Nthomiwa Date: Tue, 16 Apr 2024 17:31:24 -0700 Subject: [PATCH 08/10] feat: geo 454 automate update report (#390) --- frontend/e2e/pages/dashboard.ts | 70 ++++++- frontend/e2e/pages/generate-report.ts | 191 ++++++++++++++++-- frontend/e2e/pages/page.ts | 8 +- frontend/e2e/pages/report.ts | 27 ++- frontend/e2e/report-generation.spec.ts | 23 +++ frontend/e2e/reports.spec.ts | 91 --------- frontend/e2e/utils/check-dashboard-reports.ts | 26 +++ frontend/e2e/utils/edit-report.ts | 42 ++++ frontend/e2e/utils/generate-report.ts | 35 ++++ frontend/e2e/utils/report.ts | 75 +++++++ 10 files changed, 473 insertions(+), 115 deletions(-) create mode 100644 frontend/e2e/report-generation.spec.ts delete mode 100644 frontend/e2e/reports.spec.ts create mode 100644 frontend/e2e/utils/check-dashboard-reports.ts create mode 100644 frontend/e2e/utils/edit-report.ts create mode 100644 frontend/e2e/utils/generate-report.ts create mode 100644 frontend/e2e/utils/report.ts diff --git a/frontend/e2e/pages/dashboard.ts b/frontend/e2e/pages/dashboard.ts index 31f8ad5f4..c13b4ebad 100644 --- a/frontend/e2e/pages/dashboard.ts +++ b/frontend/e2e/pages/dashboard.ts @@ -1,6 +1,12 @@ -import { expect } from '@playwright/test'; +import { Locator, Page, expect } from '@playwright/test'; import { PTPage, User } from './page'; import { PagePaths } from '../utils'; +import { + IEmployeeCountRange, + INaicsCode, + IReportDetails, +} from './generate-report'; +import { waitForCodes } from '../utils/report'; export class DashboardPage extends PTPage { static path = PagePaths.DASHBOARD; @@ -14,12 +20,16 @@ export class DashboardPage extends PTPage { } async gotoGenerateReport() { - expect(this.generateReportButton).toBeVisible(); - await this.generateReportButton.click(); - await this.instance.waitForURL(PagePaths.GENERATE_REPORT); - await expect( - this.instance.getByText('Disclaimer: This tool relies on the employer supplying accurate and complete payroll data in order to calculate pay gaps.'), - ).toBeVisible(); + await waitForCodes(this.instance, async () => { + expect(this.generateReportButton).toBeVisible(); + await this.generateReportButton.click(); + await this.instance.waitForURL(PagePaths.GENERATE_REPORT); + await expect( + this.instance.getByText( + 'Disclaimer: This tool relies on the employer supplying accurate and complete payroll data in order to calculate pay gaps.', + ), + ).toBeVisible(); + }); } async gotoReport(id: string) { @@ -38,13 +48,46 @@ export class DashboardPage extends PTPage { await expect(viewReportButton).toBeVisible(); } - async gotoEditReport(id: string) { + async canEditReport(id: string) { const editReportButton = await this.instance.getByTestId( `edit-report-${id}`, ); - expect(editReportButton).toBeVisible(); - await editReportButton.click(); + await expect(editReportButton).toBeVisible(); + + return editReportButton; + } + + async gotoEditReport(reportId: string, button: Locator) { + const getReportDetailsRequest = this.instance.waitForResponse( + (res) => + res.url().includes(`/api/v1/report/${reportId}`) && + res.status() === 200, + ); + const getEmployeeCountRangesRequest = this.instance.waitForResponse( + (res) => + res.url().includes('/api/v1/codes/employee-count-ranges') && + res.status() === 200, + ); + const getNaicsCodesRequest = this.instance.waitForResponse( + (res) => + res.url().includes('/api/v1/codes/naics-codes') && res.status() === 200, + ); + + expect(button).toBeVisible(); + await button.click(); await this.instance.waitForURL(PagePaths.GENERATE_REPORT); + const getReportDetailsResponse = await getReportDetailsRequest; + const reportDetails: IReportDetails = await getReportDetailsResponse.json(); + const getEmployeeCountRangesResponse = await getEmployeeCountRangesRequest; + const employeeCountRanges: IEmployeeCountRange[] = + await getEmployeeCountRangesResponse.json(); + const getNaicsCodesResponse = await getNaicsCodesRequest; + const naicsCodes: INaicsCode[] = await getNaicsCodesResponse.json(); + + PTPage.employeeCountRanges = employeeCountRanges; + PTPage.naicsCodes = naicsCodes; + + return { reportDetails, naicsCodes, employeeCountRanges }; } async verifyUser(user: User): Promise { @@ -54,4 +97,11 @@ export class DashboardPage extends PTPage { await expect(welcome).toBeVisible(); await super.verifyUser(user); } + + static async visit(page: Page): Promise { + await page.goto(PagePaths.DASHBOARD); + const dashboard = new DashboardPage(page); + await dashboard.setup(); + return dashboard; + } } diff --git a/frontend/e2e/pages/generate-report.ts b/frontend/e2e/pages/generate-report.ts index 3463d6c7f..99e00884a 100644 --- a/frontend/e2e/pages/generate-report.ts +++ b/frontend/e2e/pages/generate-report.ts @@ -1,7 +1,36 @@ -import { Locator, expect } from '@playwright/test'; +import { Locator, Response, expect } from '@playwright/test'; import { PTPage, User } from './page'; import path from 'path'; import flatten from 'lodash/flatten'; +import { LocalDate } from '@js-joda/core'; +import { validateSubmitErrors, waitForApiResponses } from '../utils/report'; +import { PagePaths } from '../utils'; + +export interface IReportDetails { + report_id: string; + user_comment: string; + employee_count_range_id: string; + naics_code: string; + report_start_date: string; + report_end_date: string; + reporting_year: string; + report_status: string; + revision: string; + data_constraints: string; + is_unlocked: boolean; + create_date: string; + company_id: string; +} + +export interface INaicsCode { + naics_code: string; + naics_label: string; +} + +export interface IEmployeeCountRange { + employee_count_range_id: string; + employee_count_range: string; +} interface IFormValues { naicsCode: string; @@ -20,6 +49,14 @@ interface IUploadFileErrors { export class GenerateReportPage extends PTPage { public naicsInput: Locator; public employeeCountInput: Locator; + public reportingYearInput: Locator; + public startMonthInput: Locator; + public startYearInput: Locator; + public endMonthInput: Locator; + public endYearInput: Locator; + public commentsInput: Locator; + public dataConstraintsInput: Locator; + public submitButton: Locator; async setup() { await super.setup(); @@ -28,11 +65,23 @@ export class GenerateReportPage extends PTPage { this.employeeCountInput = await this.instance.locator( '#employeeCountRange', ); + this.reportingYearInput = await this.instance.locator('#reportYear'); + this.startMonthInput = await this.instance.locator('#startMonth'); + this.startYearInput = await this.instance.locator('#startYear'); + this.endMonthInput = await this.instance.locator('#endMonth'); + this.endYearInput = await this.instance.locator('#endYear'); + + this.commentsInput = await this.instance.locator('#comments'); + this.dataConstraintsInput = await this.instance.locator('#dataConstraints'); + this.submitButton = await this.instance.getByRole('button', { + name: 'Submit', + }); } async setNaicsCode(label: string) { await this.naicsInput.click(); - const code = await this.instance.getByRole('option', { name: label }); + await this.instance.waitForTimeout(1000); + const code = await this.instance.getByRole('option', {name: label}); expect(code).toBeVisible(); await code.click(); } @@ -85,22 +134,140 @@ export class GenerateReportPage extends PTPage { async fillOutForm(values: IFormValues) { // 2: Fill out the form in the generate report form page - await this.setup(); await this.setNaicsCode(values.naicsCode); await this.setEmployeeCount(values.employeeCountRange); - const comments = await this.instance.locator('#comments'); - await comments.fill(values.comments); - const dataConstraints = await this.instance.locator('#dataConstraints'); - await dataConstraints.fill(values.dataConstraints); + await this.commentsInput.fill(values.comments); + await this.dataConstraintsInput.fill(values.dataConstraints); await this.selectFile(values.fileName); - await this.instance.waitForSelector('i.fa-xmark') + await this.instance.waitForSelector('i.fa-xmark'); + } + + async checkDefaultFormValues(report: IReportDetails) { + const naicsCode = PTPage.naicsCodes.find( + (nc) => nc.naics_code === report.naics_code, + ); + const employeeCountRange = PTPage.employeeCountRanges.find( + (nc) => nc.employee_count_range_id === report.employee_count_range_id, + ); + await expect( + await this.instance.getByText( + `${naicsCode?.naics_code} - ${naicsCode?.naics_label}`, + ), + ).toBeVisible(); + + const radioButton = await this.instance.getByLabel( + employeeCountRange!.employee_count_range, + ); + await expect(await radioButton.isChecked()).toBeTruthy(); + + await expect(this.reportingYearInput).toBeVisible(); + await expect(this.reportingYearInput).toHaveValue(report.reporting_year); + await expect(this.reportingYearInput).toBeDisabled(); + + await this.checkDate( + report.report_start_date, + this.startMonthInput, + this.startYearInput, + ); + await this.checkDate( + report.report_end_date, + this.endMonthInput, + this.endYearInput, + ); + if (report.user_comment) { + await expect(this.commentsInput).toHaveValue(report.user_comment); + } else { + await expect(this.commentsInput).toBeEmpty(); + } + + if (report.data_constraints) { + await expect(this.dataConstraintsInput).toHaveValue( + report.data_constraints, + ); + } else { + await expect(this.dataConstraintsInput).toBeEmpty(); + } + } + + async checkDate(value: string, monthLocator: Locator, yearLocator: Locator) { + const date = LocalDate.parse(value); + await expect(monthLocator).toBeVisible(); + await expect(monthLocator).toHaveValue(`${date.monthValue()}`); + await expect(yearLocator).toBeVisible(); + await expect(yearLocator).toHaveValue(`${date.year()}`); + } + + async submitForm(responseChecker?: (res: Response) => boolean) { + await this.submitButton.scrollIntoViewIfNeeded(); + if (responseChecker) { + const { report } = await waitForApiResponses( + { + report: this.instance.waitForResponse(responseChecker), + }, + async () => { + await this.submitButton.click(); + }, + ); + return report; + } else { + await this.submitButton.click(); + } + } + + async submitInvalidFormAndValidateErrors() { + await this.submitForm(); + // Check form errors + await this.checkErrors(); + // Check API errors + await validateSubmitErrors(this); + } + + async submitValidFormAndGotoDraftPage() { + await this.naicsInput.scrollIntoViewIfNeeded(); + await this.fillOutForm({ + naicsCode: '11 - Agriculture, forestry, fishing and hunting', + employeeCountRange: '50-299', + comments: 'Example test comment', + dataConstraints: 'Example data constraint text', + fileName: 'CsvGood.csv', + }); + + const validUploadResponse = await this.submitForm( + (res) => + res.url().includes('/api/v1/file-upload') && res.status() === 200, + ); + + await this.instance.waitForURL(PagePaths.DRAFT_REPORT); + + return validUploadResponse; } - async submitForm() { - const button = await this.instance.getByRole('button', { name: 'Submit' }); - await button.scrollIntoViewIfNeeded(); - await button.click(); + async editReportAndSubmit(reportDetails: IReportDetails) { + // edit form and submit form + const naicsCode = PTPage.naicsCodes.find( + (n) => n.naics_code !== reportDetails.naics_code, + ); + await this.naicsInput.click(); + await this.setNaicsCode( + `${naicsCode!.naics_code} - ${naicsCode!.naics_label}`, + ); + const employeeCountRange = PTPage.employeeCountRanges.find( + (n) => + n.employee_count_range_id !== reportDetails.employee_count_range_id, + ); + + await this.setEmployeeCount(employeeCountRange!.employee_count_range); + const comment = 'new comment edit'; + await this.commentsInput.fill(comment); + const dataConstraint = 'new data constraint edit'; + await this.dataConstraintsInput.fill(dataConstraint); + + await this.selectFile('CsvGood.csv'); + return this.submitForm( + (res) => + res.url().includes('/api/v1/file-upload') && res.status() === 200, + ); } } diff --git a/frontend/e2e/pages/page.ts b/frontend/e2e/pages/page.ts index 8bfbc7059..49985505b 100644 --- a/frontend/e2e/pages/page.ts +++ b/frontend/e2e/pages/page.ts @@ -1,5 +1,6 @@ import { Locator, Page, expect } from '@playwright/test'; import { PagePaths } from '../utils'; +import { IEmployeeCountRange, INaicsCode } from './generate-report'; export type User = { displayName: string; @@ -14,12 +15,17 @@ export type User = { export class PTPage { public accountButton: Locator; - constructor(public readonly instance: Page) {} + public static naicsCodes: INaicsCode[] = []; + public static employeeCountRanges: IEmployeeCountRange[] = []; + constructor(public readonly instance: Page, public user = undefined) {} async setup() { this.accountButton = await this.instance.getByTestId( 'header-account-button', ); + if (this.user) { + await this.verifyUser(this.user); + } } async verifyUser(user: User) { diff --git a/frontend/e2e/pages/report.ts b/frontend/e2e/pages/report.ts index 109d5e4ed..6fe605de0 100644 --- a/frontend/e2e/pages/report.ts +++ b/frontend/e2e/pages/report.ts @@ -1,11 +1,19 @@ -import { Locator, expect } from '@playwright/test'; +import { Locator, expect, Page } from '@playwright/test'; import { PTPage } from './page'; import { PagePaths } from '../utils'; +import { GenerateReportPage, IReportDetails } from './generate-report'; export class BaseReportPage extends PTPage { public downloadPDFButton: Locator; public backButton: Locator; + constructor( + page: Page, + public user, + ) { + super(page); + } + async setup() { await super.setup(); this.downloadPDFButton = await this.instance.getByRole('button', { @@ -14,6 +22,8 @@ export class BaseReportPage extends PTPage { this.backButton = ( await this.instance.getByRole('link', { name: 'Back' }) ).first(); + + await super.verifyUser(this.user); } async verifyUser(user) { @@ -105,6 +115,21 @@ export class DraftReportPage extends BaseReportPage { await this.instance.waitForTimeout(5000); await this.instance.waitForURL(PagePaths.VIEW_REPORT); } + + async validateCanGoBack(generateReportPage: GenerateReportPage) { + await this.backButton.scrollIntoViewIfNeeded(); + await this.goBack(); + await generateReportPage.selectFile('CsvGood.csv'); + await generateReportPage.submitForm(); + await this.instance.waitForURL(PagePaths.DRAFT_REPORT); + } + + static async initialize(page: Page, user): Promise { + const draftPage = new DraftReportPage(page, user); + await draftPage.setup(); + + return draftPage; + } } export class PublishedReportPage extends BaseReportPage { diff --git a/frontend/e2e/report-generation.spec.ts b/frontend/e2e/report-generation.spec.ts new file mode 100644 index 000000000..23d7564b9 --- /dev/null +++ b/frontend/e2e/report-generation.spec.ts @@ -0,0 +1,23 @@ +import { expect, test } from '@playwright/test'; +import { DashboardPage } from './pages/dashboard'; +import { GenerateReportPage } from './pages/generate-report'; +import { PagePaths } from './utils'; +import { DraftReportPage, PublishedReportPage } from './pages/report'; +import { waitForApiResponses, waitForUserAndReports } from './utils/report'; +import { generateReport } from './utils/generate-report'; +import { checkDashboardReports } from './utils/check-dashboard-reports'; +import { editReport } from './utils/edit-report'; + +test.describe.serial('report generation', () => { + test('generate new report', async ({ page }) => { + await generateReport(page); + }); + + test('verify that reports in dashboard', async ({ page }) => { + await checkDashboardReports(page); + }); + + test('edit report', async ({ page }) => { + await editReport(page); + }); +}); diff --git a/frontend/e2e/reports.spec.ts b/frontend/e2e/reports.spec.ts deleted file mode 100644 index 146dc6c71..000000000 --- a/frontend/e2e/reports.spec.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { test } from '@playwright/test'; -import { DashboardPage } from './pages/dashboard'; -import { GenerateReportPage } from './pages/generate-report'; -import { PagePaths } from './utils'; -import { - DraftReportPage, - PublishedReportPage, -} from './pages/report'; - -test('generate new report', async ({ page }) => { - // 1: Click generate report button from the dashboard page - await page.goto(PagePaths.DASHBOARD); - const dashboard = new DashboardPage(page); - await dashboard.setup(); - - const getUserResponse = page.waitForResponse( - (res) => res.url().includes('/api/user') && res.status() === 200, - ); - await dashboard.gotoGenerateReport(); - const response = await getUserResponse; - const user = await response.json(); - - // verify employer details - const generateReportPage = new GenerateReportPage(dashboard.instance); - await generateReportPage.setup(); - await generateReportPage.verifyUser(user); - - /***************** TEST FORM VALIDATION *********************/ - await generateReportPage.submitForm(); - await generateReportPage.checkErrors(); - - /*************************************************************/ - - /***************** FILL OUT FORM / BAD CSV ******************/ - await generateReportPage.naicsInput.scrollIntoViewIfNeeded(); - await generateReportPage.fillOutForm({ - naicsCode: '11 - Agriculture, forestry, fishing and hunting', - employeeCountRange: '50-299', - comments: 'Example test comment', - dataConstraints: 'Example data constraint text', - fileName: 'CsvWithErrors.csv', - }); - - const badUploadFileResponse = generateReportPage.instance.waitForResponse( - (res) => res.url().includes('/api/v1/file-upload') && res.status() === 400, - ); - await generateReportPage.submitForm(); - const errorResponse = await badUploadFileResponse; - let errors = await errorResponse.json(); - await generateReportPage.validateUploadRowValues(errors.error); - - const validUploadFileResponse = generateReportPage.instance.waitForResponse( - (res) => res.url().includes('/api/v1/file-upload') && res.status() === 200, - ); - await generateReportPage.selectFile('CsvGood.csv'); - await generateReportPage.submitForm(); - const validResponse = await validUploadFileResponse; - let validUploadResponse = await validResponse.json(); - await page.waitForURL(PagePaths.DRAFT_REPORT); - /*************************************************************/ - - /*********************DRAFT REPORT PREVIEW******************************/ - const draftReportPage = new DraftReportPage(page); - await draftReportPage.setup(); - await draftReportPage.verifyUser(user); - await draftReportPage.verifyEmployeerDetails(user, validUploadResponse); - - // Go Back - await draftReportPage.backButton.scrollIntoViewIfNeeded(); - await draftReportPage.goBack(); - await generateReportPage.selectFile('CsvGood.csv'); - await generateReportPage.submitForm(); - await page.waitForURL(PagePaths.DRAFT_REPORT); - /***********************************************************************/ - - /***********************FINALIZE REPORT*********************************/ - await draftReportPage.finalizedReport(validUploadResponse.report_id); - const publishedReportPage = new PublishedReportPage(page); - await publishedReportPage.setup(); - await publishedReportPage.verifyUser(user); - await publishedReportPage.verifyEmployeerDetails(user, validUploadResponse); - /***********************************************************************/ - const getReportsRequest = generateReportPage.instance.waitForResponse( - (res) => res.url().includes('/api/v1/report') && res.status() === 200, - ); - await page.goto(PagePaths.DASHBOARD); - const reportsResponse = await getReportsRequest - await reportsResponse.json() - // Check if report is visible in the dashboard - await dashboard.checkReport(validUploadResponse.report_id); -}); diff --git a/frontend/e2e/utils/check-dashboard-reports.ts b/frontend/e2e/utils/check-dashboard-reports.ts new file mode 100644 index 000000000..75f142ea0 --- /dev/null +++ b/frontend/e2e/utils/check-dashboard-reports.ts @@ -0,0 +1,26 @@ +import { Page, expect } from "@playwright/test"; +import { PagePaths } from "."; +import { DashboardPage } from "../pages/dashboard"; +import { waitForUserAndReports } from "./report"; + +export const checkDashboardReports = async (page: Page) => { + const dashboard = new DashboardPage(page); + const { user, reports } = await waitForUserAndReports( + dashboard.instance, + async () => { + await dashboard.instance.goto(PagePaths.DASHBOARD); + await dashboard.setup(); + }, + ); + + await dashboard.verifyUser(user); + const { report_id: reportId, reporting_year: year } = reports.find( + (r) => r.is_unlocked, + ); + await expect( + await dashboard.instance.getByTestId(`reporting_year-${reportId}`), + ).toHaveText(year); + expect(reportId).toBeDefined(); + await dashboard.checkReport(reportId); + await dashboard.canEditReport(reportId); +} \ No newline at end of file diff --git a/frontend/e2e/utils/edit-report.ts b/frontend/e2e/utils/edit-report.ts new file mode 100644 index 000000000..498bcaf0d --- /dev/null +++ b/frontend/e2e/utils/edit-report.ts @@ -0,0 +1,42 @@ +import { Page } from '@playwright/test'; +import { PagePaths } from '.'; +import { DashboardPage } from '../pages/dashboard'; +import { GenerateReportPage } from '../pages/generate-report'; +import { DraftReportPage, PublishedReportPage } from '../pages/report'; +import { waitForUserAndReports } from './report'; + +export const editReport = async (page: Page) => { + const dashboard = new DashboardPage(page); + const { user, reports } = await waitForUserAndReports( + dashboard.instance, + async () => { + await dashboard.instance.goto(PagePaths.DASHBOARD); + await dashboard.setup(); + }, + ); + await dashboard.verifyUser(user); + const { report_id: reportId } = reports.find((r) => r.is_unlocked); + const editReportButton = await dashboard.canEditReport(reportId); + + const { reportDetails } = await dashboard.gotoEditReport( + reportId, + editReportButton, + ); + + const formPage = new GenerateReportPage(dashboard.instance); + await formPage.setup(); + await formPage.checkDefaultFormValues(reportDetails); + + // edit form and submit form + const report = await formPage.editReportAndSubmit(reportDetails); + + const draftReportPage = new DraftReportPage(formPage.instance, user); + await draftReportPage.setup(); + await draftReportPage.verifyEmployeerDetails(user, null); + await draftReportPage.finalizedReport(report.report_id); + + const publishedReportPage = new PublishedReportPage(page, user); + await publishedReportPage.setup(); + await publishedReportPage.verifyUser(user); + await publishedReportPage.verifyEmployeerDetails(user, report); +}; diff --git a/frontend/e2e/utils/generate-report.ts b/frontend/e2e/utils/generate-report.ts new file mode 100644 index 000000000..2284263b1 --- /dev/null +++ b/frontend/e2e/utils/generate-report.ts @@ -0,0 +1,35 @@ +import { Page } from "@playwright/test"; +import { DashboardPage } from "../pages/dashboard"; +import { GenerateReportPage } from "../pages/generate-report"; +import { DraftReportPage, PublishedReportPage } from "../pages/report"; +import { waitForApiResponses } from "./report"; + +export const generateReport = async (page: Page) => { + const dashboard = await DashboardPage.visit(page); + + const { user } = await waitForApiResponses( + { + user: page.waitForResponse( + (res) => res.url().includes('/api/user') && res.status() === 200, + ), + }, + async () => await dashboard.gotoGenerateReport(), + ); + + // verify employer details + const generateReportPage = new GenerateReportPage(dashboard.instance, user); + await generateReportPage.setup(); + + await generateReportPage.submitInvalidFormAndValidateErrors(); + const reportDetails = + await generateReportPage.submitValidFormAndGotoDraftPage(); + + const draftReportPage = await DraftReportPage.initialize(page, user); + await draftReportPage.verifyEmployeerDetails(user, reportDetails); + await draftReportPage.validateCanGoBack(generateReportPage); + + await draftReportPage.finalizedReport(reportDetails.report_id); + const publishedReportPage = new PublishedReportPage(page, user); + await publishedReportPage.setup(); + await publishedReportPage.verifyEmployeerDetails(user, reportDetails); +} \ No newline at end of file diff --git a/frontend/e2e/utils/report.ts b/frontend/e2e/utils/report.ts new file mode 100644 index 000000000..6d211564d --- /dev/null +++ b/frontend/e2e/utils/report.ts @@ -0,0 +1,75 @@ +import { GenerateReportPage } from '../pages/generate-report'; +import { Response, Page } from '@playwright/test'; + +export const validateSubmitErrors = async (page: GenerateReportPage) => { + await page.naicsInput.scrollIntoViewIfNeeded(); + await page.fillOutForm({ + naicsCode: '11 - Agriculture, forestry, fishing and hunting', + employeeCountRange: '50-299', + comments: 'Example test comment', + dataConstraints: 'Example data constraint text', + fileName: 'CsvWithErrors.csv', + }); + + const errors = await page.submitForm( + (res) => res.url().includes('/api/v1/file-upload') && res.status() === 400, + ); + + await page.validateUploadRowValues(errors.error); +}; + +export const waitForApiResponses = async ( + apiCalls: { + [key: string]: Promise; + }, + action: () => Promise, +) => { + await action(); + const responses = await Promise.all( + Object.keys(apiCalls).map(async (callKey) => { + const response = await apiCalls[callKey]; + return { [callKey]: await response.json() }; + }), + ); + + return responses.reduce((acc, current) => { + return { ...acc, ...current }; + }, {}); +}; + +export const waitForUserAndReports = async ( + page: Page, + action: () => Promise, +) => { + return waitForApiResponses( + { + user: page.waitForResponse( + (res) => res.url().includes('/api/user') && res.status() === 200, + ), + reports: page.waitForResponse( + (res) => res.url().includes('/api/v1/report') && res.status() === 200, + ), + }, + action, + ); +}; + +export const waitForCodes = async (page: Page, action: () => Promise) => { + const employeeCountRanges = page.waitForResponse( + (res) => + res.url().includes('/api/v1/codes/employee-count-ranges') && + res.status() === 200, + ); + const naicsCodes = page.waitForResponse( + (res) => + res.url().includes('/api/v1/codes/naics-codes') && res.status() === 200, + ); + + return waitForApiResponses( + { + employeeCountRanges, + naicsCodes, + }, + action, + ); +}; From 6a138164ebf2d417e161ffa58b7fd57ee080a1b3 Mon Sep 17 00:00:00 2001 From: Brock Anderson Date: Wed, 17 Apr 2024 11:03:19 -0700 Subject: [PATCH 09/10] feat: reporting-year-options (#401) --- frontend/src/components/InputForm.vue | 12 ++++-- .../components/__tests__/InputForm.spec.ts | 43 +++++++++++++++++-- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/InputForm.vue b/frontend/src/components/InputForm.vue index 21785a87e..13411fb5b 100644 --- a/frontend/src/components/InputForm.vue +++ b/frontend/src/components/InputForm.vue @@ -755,10 +755,14 @@ export default { .format(dateFormatter); }, reportYearList() { - const list = [ - LocalDate.now().minusYears(1).year(), - LocalDate.now().year(), - ]; + const list = [] as number[]; + const yearNow = LocalDate.now().year(); + if (yearNow >= 2025) { + //only include last year if the current year is at least 2025 + list.push(LocalDate.now().minusYears(1).year()); + } + //always include the current year + list.push(LocalDate.now().year()); return list; }, startMonthList() { diff --git a/frontend/src/components/__tests__/InputForm.spec.ts b/frontend/src/components/__tests__/InputForm.spec.ts index c2aaab157..3b6a5b9ae 100644 --- a/frontend/src/components/__tests__/InputForm.spec.ts +++ b/frontend/src/components/__tests__/InputForm.spec.ts @@ -1,8 +1,13 @@ -import { DateTimeFormatter, LocalDate, TemporalAdjusters } from '@js-joda/core'; +import { + DateTimeFormatter, + LocalDate, + TemporalAdjusters, + convert, +} from '@js-joda/core'; import { Locale } from '@js-joda/locale_en'; import { createTestingPinia } from '@pinia/testing'; import { mount } from '@vue/test-utils'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { nextTick } from 'vue'; import { createVuetify } from 'vuetify'; import * as components from 'vuetify/components'; @@ -53,7 +58,17 @@ describe('InputForm', () => { let wrapper; let pinia; - beforeEach(() => { + const initWrapper = (options: any = {}) => { + const defaultOptions = { date: null }; + options = { ...defaultOptions, ...options }; + + if (options.date) { + vi.useFakeTimers(); + vi.setSystemTime(options.date); + } else { + vi.useRealTimers(); + } + //create an instances of vuetify and pinia so we can inject them //into the mounted component, allowing it to behave as it would //in a browser @@ -73,12 +88,17 @@ describe('InputForm', () => { plugins: [vuetify, pinia], }, }); + }; + + beforeEach(() => { + initWrapper(); }); afterEach(() => { if (wrapper) { wrapper.unmount(); } + vi.useRealTimers(); }); describe('submit', () => { @@ -267,6 +287,23 @@ describe('InputForm', () => { ).toBeFalsy(); } }); + + describe('reportYearList', () => { + describe('when the current year is before 2025', () => { + it('the current year is the only reporting year option', () => { + initWrapper({ date: convert(LocalDate.of(2024, 12, 31)).toDate() }); + const list = wrapper.vm.reportYearList; + expect(list).toStrictEqual([2024]); + }); + }); + describe('when the current year is after 2024', () => { + it('there are two reporting year options: [previous year, current year]', () => { + initWrapper({ date: convert(LocalDate.of(2025, 1, 1)).toDate() }); + const list = wrapper.vm.reportYearList; + expect(list).toStrictEqual([2024, 2025]); + }); + }); + }); }); describe('InputForm Edit Mode', () => { From 0e1ed0a5db726f51b5e9ed0e95f05329af6f0bfa Mon Sep 17 00:00:00 2001 From: jer3k <99355997+jer3k@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:17:14 -0700 Subject: [PATCH 10/10] feat: Add label to remove file icon (#403) --- frontend/src/components/InputForm.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/InputForm.vue b/frontend/src/components/InputForm.vue index 13411fb5b..eb6dc593a 100644 --- a/frontend/src/components/InputForm.vue +++ b/frontend/src/components/InputForm.vue @@ -544,6 +544,7 @@