mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-14 23:07:01 +10:00
test(security): cover url validation and form edge cases
Add and update tests for new security utilities and tightened UI behavior to prevent regressions in validation and error handling paths. Made-with: Cursor
This commit is contained in:
@@ -44,6 +44,25 @@ vi.mock("motion/react", () => ({
|
||||
}));
|
||||
|
||||
// Mock lingui
|
||||
vi.mock("@lingui/core/macro", () => ({
|
||||
t: (stringsOrDescriptor: TemplateStringsArray | { message: string }, ...values: unknown[]) => {
|
||||
if (
|
||||
typeof stringsOrDescriptor === "object" &&
|
||||
"message" in stringsOrDescriptor &&
|
||||
typeof stringsOrDescriptor.message === "string"
|
||||
) {
|
||||
return stringsOrDescriptor.message;
|
||||
}
|
||||
|
||||
const strings = stringsOrDescriptor as TemplateStringsArray;
|
||||
let result = strings[0];
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
result += String(values[i]) + strings[i + 1];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@lingui/react/macro", () => ({
|
||||
Trans: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
}));
|
||||
|
||||
@@ -5,7 +5,16 @@ afterEach(cleanup);
|
||||
|
||||
// Mock @lingui/core/macro
|
||||
vi.mock("@lingui/core/macro", () => ({
|
||||
t: (strings: TemplateStringsArray, ...values: unknown[]) => {
|
||||
t: (stringsOrDescriptor: TemplateStringsArray | { message: string }, ...values: unknown[]) => {
|
||||
if (
|
||||
typeof stringsOrDescriptor === "object" &&
|
||||
"message" in stringsOrDescriptor &&
|
||||
typeof stringsOrDescriptor.message === "string"
|
||||
) {
|
||||
return stringsOrDescriptor.message;
|
||||
}
|
||||
|
||||
const strings = stringsOrDescriptor as TemplateStringsArray;
|
||||
let result = strings[0];
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
result += String(values[i]) + strings[i + 1];
|
||||
@@ -127,7 +136,7 @@ describe("LevelDisplay", () => {
|
||||
describe("aria attributes", () => {
|
||||
it("has aria-label showing level out of 5", () => {
|
||||
render(<LevelDisplay icon="star" type="circle" level={3} />);
|
||||
const el = screen.getByRole("presentation");
|
||||
const el = screen.getByRole("img");
|
||||
expect(el.getAttribute("aria-label")).toContain("3");
|
||||
expect(el.getAttribute("aria-label")).toContain("5");
|
||||
});
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, it } from "vite-plus/test";
|
||||
|
||||
import { PageLink } from "./page-link";
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe("PageLink", () => {
|
||||
it("returns null when url is empty", () => {
|
||||
const { container } = render(<PageLink url="" />);
|
||||
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it("renders link with expected attributes and label", () => {
|
||||
render(<PageLink url="https://example.com" label="Visit site" className="custom-link" />);
|
||||
|
||||
const link = screen.getByRole("link", { name: "Visit site" });
|
||||
|
||||
expect(link.getAttribute("href")).toBe("https://example.com");
|
||||
expect(link.getAttribute("target")).toBe("_blank");
|
||||
expect(link.getAttribute("rel")).toBe("noopener");
|
||||
expect(link.className).toContain("inline-block");
|
||||
expect(link.className).toContain("custom-link");
|
||||
});
|
||||
|
||||
it("falls back to url text when label is missing", () => {
|
||||
render(<PageLink url="https://fallback.dev" label="" />);
|
||||
|
||||
const link = screen.getByRole("link", { name: "https://fallback.dev" });
|
||||
|
||||
expect(link.getAttribute("href")).toBe("https://fallback.dev");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, it } from "vite-plus/test";
|
||||
|
||||
import { BrandIcon } from "./brand-icon";
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
describe("BrandIcon", () => {
|
||||
it("renders both dark and light logo images by default", () => {
|
||||
render(<BrandIcon />);
|
||||
|
||||
const images = screen.getAllByRole("img", { name: "Reactive Resume" });
|
||||
|
||||
expect(images).toHaveLength(2);
|
||||
expect(images[0].getAttribute("src")).toBe("/logo/dark.svg");
|
||||
expect(images[0].className).toContain("hidden");
|
||||
expect(images[0].className).toContain("dark:block");
|
||||
expect(images[1].getAttribute("src")).toBe("/logo/light.svg");
|
||||
expect(images[1].className).toContain("block");
|
||||
expect(images[1].className).toContain("dark:hidden");
|
||||
});
|
||||
|
||||
it("uses icon variant and forwards shared img props", () => {
|
||||
render(<BrandIcon variant="icon" className="brand-class" loading="lazy" data-testid="brand" />);
|
||||
|
||||
const images = screen.getAllByTestId("brand");
|
||||
|
||||
expect(images).toHaveLength(2);
|
||||
expect(images[0].getAttribute("src")).toBe("/icon/dark.svg");
|
||||
expect(images[1].getAttribute("src")).toBe("/icon/light.svg");
|
||||
expect(images[0].getAttribute("loading")).toBe("lazy");
|
||||
expect(images[1].getAttribute("loading")).toBe("lazy");
|
||||
expect(images[0].className).toContain("brand-class");
|
||||
expect(images[1].className).toContain("brand-class");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user