feature/IO-3181-Test-Framework-Selection - Skeletons complete

This commit is contained in:
Dave Richer
2025-03-26 16:52:59 -04:00
parent 67b6da7c31
commit 146bb6c5c0
25 changed files with 2988 additions and 324 deletions

1468
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -24,7 +24,6 @@
"@sentry/vite-plugin": "^3.2.2",
"@splitsoftware/splitio-react": "^2.0.1",
"@tanem/react-nprogress": "^5.0.53",
"@vitejs/plugin-react": "^4.3.4",
"antd": "^5.24.5",
"apollo-link-logger": "^2.0.1",
"apollo-link-sentry": "^4.2.0",
@@ -100,7 +99,10 @@
"build:production:imex": "env-cmd -f .env.production.imex npm run build",
"build:production:rome": "env-cmd -f .env.production.rome npm run build",
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular .",
"eulaize": "node src/utils/eulaize.js"
"eulaize": "node src/utils/eulaize.js",
"test:unit": "vitest run",
"test:watch": "vitest",
"test:e2e": "playwright test"
},
"browserslist": {
"production": [
@@ -128,7 +130,12 @@
"@emotion/babel-plugin": "^11.13.5",
"@emotion/react": "^11.14.0",
"@eslint/js": "^9.23.0",
"@playwright/test": "^1.51.1",
"@sentry/webpack-plugin": "^3.2.2",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.2.0",
"@vitejs/plugin-react": "^4.3.4",
"browserslist": "^4.24.4",
"browserslist-to-esbuild": "^2.1.1",
"chalk": "^5.4.1",
@@ -136,8 +143,10 @@
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-react": "^7.37.4",
"globals": "^15.15.0",
"jsdom": "^26.0.0",
"memfs": "^4.17.0",
"os-browserify": "^0.3.0",
"playwright": "^1.51.1",
"react-error-overlay": "^6.1.0",
"redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.3",
@@ -147,6 +156,7 @@
"vite-plugin-node-polyfills": "^0.23.0",
"vite-plugin-pwa": "^0.21.2",
"vite-plugin-style-import": "^2.0.0",
"vitest": "^3.0.9",
"workbox-window": "^7.3.0"
}
}

View File

@@ -0,0 +1,16 @@
import { defineConfig } from "@playwright/test";
export default defineConfig({
testDir: "./tests/e2e",
testMatch: "*.e2e.js",
use: {
baseURL: "https://localhost:3000",
browser: "chromium",
ignoreHTTPSErrors: true
},
webServer: {
command: "npm run start:imex",
port: 3000,
reuseExistingServer: !process.env.CI // Reuse server locally, not in CI
}
});

View File

@@ -1,3 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Alert component should render Alert component 1`] = `ShallowWrapper {}`;

View File

@@ -1,19 +0,0 @@
import { shallow } from "enzyme";
import React from "react";
import Alert from "./alert.component";
describe("Alert component", () => {
let wrapper;
beforeEach(() => {
const mockProps = {
type: "error",
message: "Test error message."
};
wrapper = shallow(<Alert {...mockProps} />);
});
it("should render Alert component", () => {
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,31 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import AlertComponent from "./alert.component";
describe("AlertComponent", () => {
it("renders with default props", () => {
render(<AlertComponent message="Default Alert" />);
expect(screen.getByText("Default Alert")).toBeInTheDocument();
expect(screen.getByRole("alert")).toHaveClass("ant-alert");
});
it("applies type prop correctly", () => {
render(<AlertComponent message="Success Alert" type="success" />);
const alert = screen.getByRole("alert");
expect(screen.getByText("Success Alert")).toBeInTheDocument();
expect(alert).toHaveClass("ant-alert-success");
});
it("displays description when provided", () => {
render(<AlertComponent message="Error Alert" description="Something went wrong" type="error" />);
expect(screen.getByText("Error Alert")).toBeInTheDocument();
expect(screen.getByText("Something went wrong")).toBeInTheDocument();
expect(screen.getByRole("alert")).toHaveClass("ant-alert-error");
});
it("is closable and shows icon when props are set", () => {
render(<AlertComponent message="Warning Alert" type="warning" showIcon closable />);
expect(screen.getByText("Warning Alert")).toBeInTheDocument();
expect(screen.getByRole("button", { name: /close/i })).toBeInTheDocument(); // Close button
});
});

View File

@@ -1,5 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AllocationsAssignmentComponent component should create an allocation on save 1`] = `ReactWrapper {}`;
exports[`AllocationsAssignmentComponent component should render AllocationsAssignmentComponent component 1`] = `ReactWrapper {}`;

View File

@@ -1,35 +0,0 @@
import { mount } from "enzyme";
import React from "react";
import { MockBodyshop } from "../../utils/TestingHelpers";
import { AllocationsAssignmentComponent } from "./allocations-assignment.component";
describe("AllocationsAssignmentComponent component", () => {
let wrapper;
beforeEach(() => {
const mockProps = {
bodyshop: MockBodyshop,
handleAssignment: jest.fn(),
assignment: {},
setAssignment: jest.fn(),
visibilityState: [false, jest.fn()],
maxHours: 4
};
wrapper = mount(<AllocationsAssignmentComponent {...mockProps} />);
});
it("should render AllocationsAssignmentComponent component", () => {
expect(wrapper).toMatchSnapshot();
});
it("should render a list of employees", () => {
const empList = wrapper.find("#employeeSelector");
expect(empList.children()).to.have.lengthOf(2);
});
it("should create an allocation on save", () => {
wrapper.find("Button").simulate("click");
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -1,4 +0,0 @@
import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() });

View File

@@ -3679,7 +3679,8 @@
"signinerror": {
"auth/user-disabled": "User account disabled. ",
"auth/user-not-found": "A user with this email does not exist.",
"auth/wrong-password": "The email and password combination you provided is incorrect."
"auth/wrong-password": "The email and password combination you provided is incorrect.",
"auth/invalid-email": "A user with this email does not exist."
}
}
},

View File

@@ -3679,7 +3679,8 @@
"signinerror": {
"auth/user-disabled": "",
"auth/user-not-found": "",
"auth/wrong-password": ""
"auth/wrong-password": "",
"auth/invalid-email": ""
}
}
},

View File

@@ -3679,7 +3679,8 @@
"signinerror": {
"auth/user-disabled": "",
"auth/user-not-found": "",
"auth/wrong-password": ""
"auth/wrong-password": "",
"auth/invalid-email": ""
}
}
},

View File

@@ -1,139 +0,0 @@
export const MockBodyshop = {
address1: "123 Fake St",
address2: "Unit #100",
city: "Vancouver",
country: "Canada",
created_at: "2019-12-10T20:03:06.420853+00:00",
email: "snaptsoft@gmail.com",
federal_tax_id: "GST10150492",
id: "52b7357c-0edd-4c95-85c3-dfdbcdfad9ac",
insurance_vendor_id: "F123456",
logo_img_path: "https://www.snapt.ca/assets/logo-placeholder.png",
md_ro_statuses: {
statuses: [
"Open",
"Scheduled",
"Arrived",
"Repair Plan",
"Parts",
"Body",
"Prep",
"Paint",
"Reassembly",
"Sublet",
"Detail",
"Completed",
"Delivered",
"Invoiced",
"Exported"
],
open_statuses: ["Open", "Scheduled", "Arrived", "Repair Plan", "Parts", "Body", "Prep", "Paint"],
default_arrived: "Arrived",
default_exported: "Exported",
default_imported: "Open",
default_invoiced: "Invoiced",
default_completed: "Completed",
default_delivered: "Delivered",
default_scheduled: "Scheduled"
},
md_order_statuses: {
statuses: ["Ordered", "Received", "Canceled", "Backordered"],
default_bo: "Backordered",
default_ordered: "Ordered",
default_canceled: "Canceled",
default_received: "Received"
},
shopname: "Testing Collision",
state: "BC",
state_tax_id: "PST1000-2991",
updated_at: "2020-03-23T22:06:03.509544+00:00",
zip_post: "V6B 1M9",
region_config: "CA_BC",
md_responsibility_centers: {
costs: [
"Aftermarket",
"ATS",
"Body",
"Detail",
"Daignostic",
"Electrical",
"Chrome",
"Frame",
"Mechanical",
"Refinish",
"Structural",
"Existing",
"Glass",
"LKQ",
"OEM",
"OEM Partial",
"Re-cored",
"Remanufactured",
"Other",
"Sublet",
"Towing"
],
profits: [
"Aftermarket",
"ATS",
"Body",
"Detail",
"Daignostic",
"Electrical",
"Chrome",
"Frame",
"Mechanical",
"Refinish",
"Structural",
"Existing",
"Glass",
"LKQ",
"OEM",
"OEM Partial",
"Re-cored",
"Remanufactured",
"Other",
"Sublet",
"Towing"
],
defaults: {
ATS: "ATS",
LAB: "Body",
LAD: "Diagnostic",
LAE: "Electrical",
LAF: "Frame",
LAG: "Glass",
LAM: "Mechanical",
LAR: "Refinish",
LAS: "Structural",
LAU: "Detail",
PAA: "Aftermarket",
PAC: "Chrome",
PAL: "LKQ",
PAM: "Remanufactured",
PAN: "OEM",
PAO: "Other",
PAP: "OEM Partial",
PAR: "16",
TOW: "Towing"
}
},
employees: [
{
id: "075b744c-8919-49ca-abb2-ccd51040326d",
first_name: "Patrick",
last_name: "BODY123",
employee_number: "101",
cost_center: "Body",
__typename: "employees"
},
{
id: "8cc787d3-1cfe-49d3-8a15-8469cd5c2e41",
first_name: "Patrick",
last_name: "Painter",
employee_number: "10211",
cost_center: "REFINISH",
__typename: "employees"
}
]
};

View File

@@ -0,0 +1,14 @@
// Button.test.jsx
import React from "react";
import { render, screen } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import { Button } from "antd";
import "antd/dist/reset.css"; // Optional: include if needed for styling reset
describe("AntD Button", () => {
it("renders with correct text", () => {
render(<Button>Click me</Button>);
const button = screen.getByRole("button", { name: /click me/i });
expect(button).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,6 @@
import { test, expect } from "@playwright/test";
test("homepage loads correctly", async ({ page }) => {
await page.goto("/");
await expect(page.locator("h1")).toContainText("ImEX Online");
});

View File

@@ -0,0 +1,28 @@
import { test, expect } from "@playwright/test";
import { login } from "./utils/login";
test.describe("SignInComponent", () => {
test("successfully logs in with valid credentials", async ({ page }) => {
const email = "dave@imex.dev";
const password = "dave123";
await login(page, { email, password });
// Additional assertions after login (optional)
await expect(page).toHaveURL(/\/manage\//);
});
test("displays error on invalid credentials", async ({ page }) => {
await page.goto("/"); // Adjust if login route differs
// Fill form with invalid credentials
await page.fill('input[placeholder="Username"]', "wronguser@example.com");
await page.fill('input[placeholder="Password"]', "wrongpassword");
await page.click("button.login-btn");
// Check for error alert
const alert = page.locator(".ant-alert-error");
await expect(alert).toBeVisible();
await expect(alert).toContainText("A user with this email does not exist.");
});
});

View File

@@ -0,0 +1,21 @@
import { expect } from "@playwright/test";
export async function login(page, { email, password }) {
// Navigate to the login page
await page.goto("/"); // Adjust if your login route differs (e.g., '/login')
// Fill email field
await page.fill('input[placeholder="Username"]', email); // Matches Ant Design Input placeholder
// Fill password field
await page.fill('input[placeholder="Password"]', password);
// Click login button
await page.click("button.login-btn");
// Wait for navigation or success indicator (e.g., redirect to /manage/)
await page.waitForURL(/\/manage\//, { timeout: 10000 }); // Adjust based on redirect
// Verify successful login (e.g., check for a dashboard element)
await expect(page.locator("text=Manage")).toBeVisible(); // Adjust to your apps post-login UI
}

5
client/tests/setup.js Normal file
View File

@@ -0,0 +1,5 @@
import { afterEach } from "vitest";
import { cleanup } from "@testing-library/react";
import "@testing-library/jest-dom";
afterEach(() => cleanup());

27
client/tests/setupI18n.js Normal file
View File

@@ -0,0 +1,27 @@
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import en_Translation from "../src/translations/en_us/common.json";
import es_Translation from "../src/translations/es/common.json";
import fr_Translation from "../src/translations/fr/common.json";
const resources = {
"en-US": en_Translation,
"fr-CA": fr_Translation,
"es-MX": es_Translation
};
i18n.use(initReactI18next).init({
resources,
lng: "en-US", // Default to en-US for tests (no LanguageDetector)
fallbackLng: "en-US",
debug: false, // Disable debug in tests
react: {
useSuspense: false // Disable Suspense for Vitest
},
interpolation: {
escapeValue: false, // React handles XSS
skipOnVariables: false
}
});
export default i18n;

19
client/vitest.config.js Normal file
View File

@@ -0,0 +1,19 @@
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: {
environment: "jsdom",
globals: true,
setupFiles: "./tests/setup.js",
testTimeout: 10000 // 10 seconds (in milliseconds)
},
css: {
preprocessorOptions: {
scss: {
silenceDeprecations: ["import"] // Suppress @import warnings
}
}
}
});

1406
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,9 @@
"setup": "rm -rf node_modules && npm i && cd client && rm -rf node_modules && npm i",
"setup:win": "rimraf node_modules && npm i && cd client && rimraf node_modules && npm i",
"start": "node server.js",
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\""
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\"",
"test:unit": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"@aws-sdk/client-cloudwatch-logs": "^3.772.0",
@@ -82,6 +84,8 @@
"globals": "^15.15.0",
"p-limit": "^3.1.0",
"prettier": "^3.5.3",
"source-map-explorer": "^2.5.2"
"source-map-explorer": "^2.5.2",
"supertest": "^7.1.0",
"vitest": "^3.0.9"
}
}

14
server/tests/api.test.js Normal file
View File

@@ -0,0 +1,14 @@
import { describe, it, expect } from "vitest";
import request from "supertest";
import express from "express";
const app = express();
app.get("/api/health", (req, res) => res.json({ status: "ok" }));
describe("API", () => {
it("returns health status", async () => {
const response = await request(app).get("/api/health");
expect(response.status).toBe(200);
expect(response.body).toEqual({ status: "ok" });
});
});

11
server/tests/math.test.js Normal file
View File

@@ -0,0 +1,11 @@
import { describe, it, expect } from "vitest";
function add(a, b) {
return a + b;
}
describe("Math", () => {
it("adds two numbers correctly", () => {
expect(add(2, 3)).toBe(5);
});
});

10
vitest.config.js Normal file
View File

@@ -0,0 +1,10 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "node",
globals: true,
include: ["./server/tests/**/*.{test,spec}.[jt]s"], // Only search /tests in root
exclude: ["**/client/**", "**/node_modules/**", "**/dist/**"] // Explicitly exclude /client
}
});