From 58194fb2cb6d57e2ac93bfd86d59e34f4dee780d Mon Sep 17 00:00:00 2001 From: Ogheneochuko <7204868+amochuko@users.noreply.github.com> Date: Sat, 6 Jul 2024 13:01:39 +0100 Subject: [PATCH 1/9] feat: added testing setup --- .github/workflows/playwright.yml | 27 +++++++++++ jest-setup.js | 1 + jest.config.js | 18 ++++++++ playwright.config.ts | 78 ++++++++++++++++++++++++++++++++ 4 files changed, 124 insertions(+) create mode 100644 .github/workflows/playwright.yml create mode 100644 jest-setup.js create mode 100644 jest.config.js create mode 100644 playwright.config.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml new file mode 100644 index 0000000..467190b --- /dev/null +++ b/.github/workflows/playwright.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/jest-setup.js b/jest-setup.js new file mode 100644 index 0000000..d0de870 --- /dev/null +++ b/jest-setup.js @@ -0,0 +1 @@ +import "@testing-library/jest-dom"; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..0949d32 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,18 @@ +const nextJest = require("next/jest"); + +const createJestConfig = nextJest({ dir: "./" }); + +/** @type {import('jest').Config}*/ +const customJestConfig = { + setupFilesAfterEnv: ["/jest-setup.js"], + moduleDirectories: ["node_modules", "/"], + testPathIgnorePatterns: ["/e2e"], // ignores e2e + testEnvironment: "jest-environment-jsdom", + collectCoverageFrom: [ + "**/*.{js,jsx}", + "!**/node_modules/**", + "!**/vendor/**", + ], +}; + +module.exports = createJestConfig(customJestConfig); diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..b5a4d55 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,78 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './e2e', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); From 62460d8901eb93708d0739cc124365f29078f1e2 Mon Sep 17 00:00:00 2001 From: Ogheneochuko <7204868+amochuko@users.noreply.github.com> Date: Sun, 7 Jul 2024 18:51:20 +0100 Subject: [PATCH 2/9] feat: added e2e test for dashboard --- e2e/export-dashboard.spec.ts | 50 ++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 e2e/export-dashboard.spec.ts diff --git a/e2e/export-dashboard.spec.ts b/e2e/export-dashboard.spec.ts new file mode 100644 index 0000000..6373c0c --- /dev/null +++ b/e2e/export-dashboard.spec.ts @@ -0,0 +1,50 @@ +import { expect, test } from "@playwright/test"; + +const devURL = "http://localhost:3000/"; + +test.describe("Dashboard", () => { + test("has export button", async ({ page }) => { + await page.goto(devURL); + + await page.getByRole("link", { name: /dashboard/i }).click(); + await expect( + page.getByRole("button", { name: "Export (PNG)" }) + ).toBeVisible(); + }); + + test("has name of Sprout pool plus amount of zec", async ({ page }) => { + await page.goto(devURL); + + await page.getByRole("link", { name: /dashboard/i }).click(); + + await page.getByRole("button", { name: "Sprout Pool" }).click(); + + await page.getByRole("button", { name: "Export (PNG)" }).click(); + + await expect(page.getByText(/ZEC in Sprout Pool/i)).toBeVisible(); + }); + + test("has name of Sapling pool plus amount of zec", async ({ page }) => { + await page.goto(devURL); + + await page.getByRole("link", { name: /dashboard/i }).click(); + + await page.getByRole("button", { name: "Sapling Pool" }).click(); + + await page.getByRole("button", { name: "Export (PNG)" }).click(); + + await expect(page.getByText(/ZEC in Sapling Pool/i)).toBeVisible(); + }); + + test("has name of Orchard pool plus amount of zec", async ({ page }) => { + await page.goto(devURL); + + await page.getByRole("link", { name: /dashboard/i }).click(); + + await page.getByRole("button", { name: "Orchard Pool" }).click(); + + await page.getByRole("button", { name: "Export (PNG)" }).click(); + + await expect(page.getByText(/ZEC in Orchard Pool/i)).toBeVisible(); + }); +}); From c09fe44e054587c9b3752e6746ce8d170aa83935 Mon Sep 17 00:00:00 2001 From: Ogheneochuko <7204868+amochuko@users.noreply.github.com> Date: Sun, 7 Jul 2024 19:23:05 +0100 Subject: [PATCH 3/9] feat: add test for useExportDashboardAsPNG --- .../useExportDashboardAsPNG.test.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/hooks/__tests__/useExportDashboardAsPNG.test.tsx diff --git a/src/hooks/__tests__/useExportDashboardAsPNG.test.tsx b/src/hooks/__tests__/useExportDashboardAsPNG.test.tsx new file mode 100644 index 0000000..003c19c --- /dev/null +++ b/src/hooks/__tests__/useExportDashboardAsPNG.test.tsx @@ -0,0 +1,29 @@ +import { act, renderHook } from "@testing-library/react-hooks"; +import useExportDashboardAsPNG, { PoolsType } from "../useExportDashboardAsPNG"; + +describe("useExportDashboardAsPNG", () => { + it("should handle saving the chart as PNG correctly", async () => { + const { result } = renderHook(() => useExportDashboardAsPNG()); + + // Mocking the divChartRef current value to simulate having a DOM node + Object.defineProperty(result.current.divChartRef, "current", { + value: { + appendChild: jest.fn(), + removeChild: jest.fn(), + }, + writable: true, + }); + + // Simulate calling handleSaveToPng with different pool types + await act(async () => { + await result.current.handleSaveToPng(PoolsType.sprout, { + sproutSupply: { timestamp: "2023-01-01T00:00:00Z", supply: 12345 }, + }); + }); + + // Check if the expected child was appended + expect( + result.current.divChartRef.current!.appendChild + ).toHaveBeenCalledWith(expect.anything()); + }); +}); From fadf50b88ec8217fcb883e72e542a7acff223515 Mon Sep 17 00:00:00 2001 From: Ogheneochuko <7204868+amochuko@users.noreply.github.com> Date: Sun, 7 Jul 2024 19:25:33 +0100 Subject: [PATCH 4/9] feat: added Export Dashboard --- src/hooks/useExportDashboardAsPNG.tsx | 66 +++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/hooks/useExportDashboardAsPNG.tsx diff --git a/src/hooks/useExportDashboardAsPNG.tsx b/src/hooks/useExportDashboardAsPNG.tsx new file mode 100644 index 0000000..6d2f8f3 --- /dev/null +++ b/src/hooks/useExportDashboardAsPNG.tsx @@ -0,0 +1,66 @@ +import html2canvas from "html2canvas"; +import { useRef } from "react"; + +export const PoolsType = { + default: "default", + sprout: "sprout", + sap: "sapling", + ord: "orchard", +}; + +/// This hook is use to handle the export of the dashboard chart +const useExportDashboardAsPNG = () => { + const divChartRef = useRef(null); + + const handleSaveToPng = async ( + poolType: string, + poolData: Record + ) => { + const poolQty = document.createTextNode(""); + + if (poolType == PoolsType.sprout) { + poolQty.textContent = `${poolData[ + "sproutSupply" + ]?.supply.toLocaleString()} ZEC in Sprout Pool`; + } else if (poolType == PoolsType.ord) { + poolQty.textContent = `${poolData[ + "orchardSupply" + ]?.supply.toLocaleString()} ZEC in Orchard Pool`; + } else if (poolType == PoolsType.sap) { + poolQty.textContent = `${poolData[ + "saplingSupply" + ]?.supply.toLocaleString()} ZEC in Sapling Pool`; + } else { + poolQty.textContent = ""; + } + + const span = document.createElement("span"); + span.style.color = "#eee"; + span.style.position = "absolute"; + span.style.top = "20px"; + span.style.paddingLeft = "24px"; + span.style.zIndex = "1000"; + span.appendChild(poolQty); + + if (divChartRef.current) { + divChartRef.current.appendChild(span); + } + + try { + const canvas = await html2canvas(divChartRef.current!); + const link = document.createElement("a"); + + link.href = canvas.toDataURL("image/png"); + link.download = `zcash-${poolType}-pool-chart.png`; + link.click(); + + divChartRef.current?.removeChild(span); + } catch (err) { + console.error("Error saving chart: ", err); + } + }; + + return { divChartRef, handleSaveToPng }; +}; + +export default useExportDashboardAsPNG; From b80b54d38298b0a2a3492b8ef9a248ac1eff99ae Mon Sep 17 00:00:00 2001 From: Ogheneochuko <7204868+amochuko@users.noreply.github.com> Date: Sun, 7 Jul 2024 19:26:27 +0100 Subject: [PATCH 5/9] fix: add styles props --- src/components/Button/Button.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 9732dc0..f0cbc70 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -5,19 +5,24 @@ interface ButtonProps { text: string; className?: string; onClick?: () => void; + styles?: React.CSSProperties } -const Button: React.FC = ({ href, text, className, onClick }) => { +const Button: React.FC = ({ href, text, className, onClick, styles }) => { if (href) { return ( - + {text} ); } return ( - ); From 66b0631e4a504d6e8a5103236028bab478efcbef Mon Sep 17 00:00:00 2001 From: Ogheneochuko <7204868+amochuko@users.noreply.github.com> Date: Sun, 7 Jul 2024 19:27:15 +0100 Subject: [PATCH 6/9] fix: added test for button --- src/components/__tests__/button.test.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/components/__tests__/button.test.tsx diff --git a/src/components/__tests__/button.test.tsx b/src/components/__tests__/button.test.tsx new file mode 100644 index 0000000..4cd6b22 --- /dev/null +++ b/src/components/__tests__/button.test.tsx @@ -0,0 +1,17 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import Button from "../Button/Button"; + +describe("Button", () => { + const user = userEvent.setup(); + + it("calls onClick prop when clicked", async () => { + const handleClick = jest.fn(); + + render(