Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,22 @@ Here're some of the project's best features:
```

3. **Run Development Server**:

```bash
npm run dev
```

4. **Run Tests**:

```bash
npm test
```

5. **Run Coverage Report**:
```bash
npm run test:coverage
```

<h2>💻 Built with</h2>

Technologies used in the project:
Expand Down
25 changes: 25 additions & 0 deletions __tests__/convert/html-jsx/htmlToJsx.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { htmlToJsx } from "html-to-jsx-transform";

describe("htmlToJsx transformer", () => {
it("converts HTML attributes and nested content to JSX", () => {
const html =
'<section class="card"><h1>Title</h1><p id="summary">Summary</p></section>';
const expected =
'<section className="card"><h1>Title</h1><p id="summary">Summary</p></section>';

expect(htmlToJsx(html)).toBe(expected);
});

it("handles boolean attributes and self-closing tags", () => {
expect(htmlToJsx('<input type="checkbox" checked>')).toBe(
'<input type="checkbox" checked={true} />',
);
});

it("preserves attribute values with mixed quotes and whitespace", () => {
const html = '<button disabled class="btn primary">Click</button>';
expect(htmlToJsx(html)).toBe(
'<button disabled={true} className="btn primary">Click</button>',
);
});
});
83 changes: 83 additions & 0 deletions __tests__/convert/html-jsx/page.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import React from "react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import HTML_JSX from "@/app/convert/html-jsx/page";

jest.mock("@monaco-editor/react", () => ({
Editor: ({ language, value, onChange }) => (
<textarea
data-testid={language === "html" ? "html-editor" : "jsx-editor"}
aria-label={language === "html" ? "HTML editor" : "JSX editor"}
value={value}
onChange={(event) => onChange?.(event.target.value)}
/>
),
}));

jest.mock("@/components/navbar", () => ({
NavBar: ({ title, isDarkMode, toggleTheme }) => (
<div data-testid="mock-navbar">
<h1>{title}</h1>
<button aria-label="Toggle dark mode" onClick={toggleTheme}>
Toggle Theme
</button>
<span>{isDarkMode ? "dark" : "light"}</span>
</div>
),
}));

describe("HTML to JSX page", () => {
beforeEach(() => {
window.localStorage.clear();
});

it("renders the HTML and JSX panels", () => {
render(<HTML_JSX />);

expect(screen.getByText("HTML")).toBeInTheDocument();
expect(screen.getByText("JSX")).toBeInTheDocument();
expect(screen.getByLabelText("HTML editor")).toBeInTheDocument();
expect(screen.getByLabelText("JSX editor")).toBeInTheDocument();
});

it("converts HTML input into JSX output", async () => {
render(<HTML_JSX />);

const htmlEditor = screen.getByLabelText("HTML editor");
const jsxEditor = screen.getByLabelText("JSX editor");

fireEvent.change(htmlEditor, {
target: { value: '<div class="foo">Hello</div>' },
});

await waitFor(() => {
expect(jsxEditor).toHaveValue(
'function component() { return (<div className="foo">Hello</div>) }',
);
});
});

it("loads the saved theme from localStorage on mount", async () => {
window.localStorage.setItem("theme", JSON.stringify(true));

render(<HTML_JSX />);

await waitFor(() => {
expect(screen.getByText("dark")).toBeInTheDocument();
expect(screen.getByRole("main")).toHaveClass("bg-gray-900");
});
});

it("toggles dark mode and saves the preference", async () => {
render(<HTML_JSX />);

const toggleButton = screen.getByRole("button", {
name: /toggle dark mode/i,
});
fireEvent.click(toggleButton);

await waitFor(() => {
expect(screen.getByText("dark")).toBeInTheDocument();
expect(window.localStorage.getItem("theme")).toBe("true");
});
});
});
48 changes: 48 additions & 0 deletions __tests__/resume-builder/Preview.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import Preview from "@/app/resume-builder/Preview";
import { defaultResumeData } from "@/app/resume-builder/defaultResumeData";

describe("Preview", () => {
test("renders updated resume preview content from provided data", () => {
const sampleData = {
...defaultResumeData,
name: "Jane Doe",
email: "jane@example.com",
phone: "1234567890",
workExperience: [
{
title: "Software Engineer",
company: "Acme",
description: "Built apps.",
},
],
education: [
{
degree: "B.Sc.",
institution: "University",
description: "Computer Science",
},
],
links: {
linkedIn: "https://linkedin.com/janedoe",
website: "https://janedoe.dev",
github: "https://github.com/janedoe",
},
};

render(<Preview isDarkMode={false} data={sampleData} />);

expect(screen.getByText("Jane Doe")).toBeInTheDocument();
expect(screen.getAllByText("Software Engineer")[0]).toBeInTheDocument();
expect(screen.getByText("Acme")).toBeInTheDocument();
expect(screen.getByText("B.Sc.")).toBeInTheDocument();
expect(screen.getByText("University")).toBeInTheDocument();
expect(
screen.getByText((content) => content.includes("linkedin.com/janedoe")),
).toBeInTheDocument();
expect(
screen.getByText((content) => content.includes("https://janedoe.dev")),
).toBeInTheDocument();
});
});
80 changes: 80 additions & 0 deletions __tests__/resume-builder/ResumeForm.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import ResumeForm from "@/app/resume-builder/ResumeForm";
import { defaultResumeData } from "@/app/resume-builder/defaultResumeData";

describe("ResumeForm", () => {
const onFormChange = jest.fn();

beforeEach(() => {
onFormChange.mockClear();
});

test("renders core fields and updates parent state on text input change", () => {
render(
<ResumeForm
isDarkMode={false}
onFormChange={onFormChange}
initialData={defaultResumeData}
/>,
);

const nameInput = screen.getByPlaceholderText("John Doe");
fireEvent.change(nameInput, { target: { value: "Jane Doe" } });

expect(nameInput).toHaveValue("Jane Doe");
expect(onFormChange).toHaveBeenCalledWith(
expect.objectContaining({ name: "Jane Doe" }),
);
});

test("updates links nested state and notifies parent", () => {
render(
<ResumeForm
isDarkMode={false}
onFormChange={onFormChange}
initialData={defaultResumeData}
/>,
);

const linkedinInput = screen.getByPlaceholderText(
"https://www.linkedin.com/in/johndev/",
);
fireEvent.change(linkedinInput, {
target: { value: "https://linkedin.com/jane-doe" },
});

expect(linkedinInput).toHaveValue("https://linkedin.com/jane-doe");
expect(onFormChange).toHaveBeenCalledWith(
expect.objectContaining({
links: expect.objectContaining({
linkedIn: "https://linkedin.com/jane-doe",
}),
}),
);
});

test("adds a new work experience row and triggers parent update", () => {
render(
<ResumeForm
isDarkMode={false}
onFormChange={onFormChange}
initialData={defaultResumeData}
/>,
);

const addButton = screen.getByRole("button", {
name: /Add Work Experience/i,
});
fireEvent.click(addButton);

expect(onFormChange).toHaveBeenCalledWith(
expect.objectContaining({
workExperience: expect.arrayContaining([
expect.objectContaining({ title: "", company: "", description: "" }),
]),
}),
);
expect(onFormChange.mock.calls[0][0].workExperience).toHaveLength(2);
});
});
10 changes: 5 additions & 5 deletions src/app/convert/html-jsx/page.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"use client";

import { useState, useMemo, useEffect } from "react";
import { useState, useMemo, useEffect, useRef } from "react";
import { NavBar } from "@/components/navbar";
import CodeEditor from "./components/editor";
import { htmlToJsx } from "html-to-jsx-transform";

export default function HTML_JSX() {
const [isDarkMode, setIsDarkMode] = useState(false);
const [value, setValue] = useState("");
const [cache, setCache] = useState({});
const cacheRef = useRef({});

// Handle editor change
const handleChange = (val) => {
Expand All @@ -24,13 +24,13 @@ export default function HTML_JSX() {

// Generate JSX from HTML with caching
const generateJSX = () => {
if (cache[value]) {
return cache[value];
if (cacheRef.current[value]) {
return cacheRef.current[value];
}

const jsx = htmlToJsx(value);
const jsxCode = `function component() { return (${jsx}) }`;
setCache((prevCache) => ({ ...prevCache, [value]: jsxCode }));
cacheRef.current[value] = jsxCode;
return jsxCode;
};

Expand Down
Loading
Loading