Skip to content

Commit 657db2b

Browse files
authored
Merge pull request #1 from dashed/claude/add-tests-ci-01KPT9Z5bTcEiVAtRs1LazMN
Add tests and GitHub Actions CI checks
2 parents 3a1e1ce + 5d639a5 commit 657db2b

File tree

7 files changed

+825
-5
lines changed

7 files changed

+825
-5
lines changed

.github/workflows/ci.yml

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main, claude/** ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
test:
11+
name: Test and Build
12+
runs-on: ubuntu-latest
13+
14+
strategy:
15+
matrix:
16+
node-version: [18.x, 20.x]
17+
18+
steps:
19+
- name: Checkout code
20+
uses: actions/checkout@v4
21+
22+
- name: Setup Node.js ${{ matrix.node-version }}
23+
uses: actions/setup-node@v4
24+
with:
25+
node-version: ${{ matrix.node-version }}
26+
cache: 'yarn'
27+
28+
- name: Install dependencies
29+
run: make install
30+
31+
- name: Run tests with coverage
32+
run: make test-coverage
33+
34+
- name: Build
35+
run: make build
36+
37+
- name: Upload coverage to Codecov
38+
if: matrix.node-version == '20.x'
39+
uses: codecov/codecov-action@v4
40+
with:
41+
fail_ci_if_error: false
42+
files: ./coverage/lcov.info
43+
continue-on-error: true
44+
45+
lint:
46+
name: Lint
47+
runs-on: ubuntu-latest
48+
49+
steps:
50+
- name: Checkout code
51+
uses: actions/checkout@v4
52+
53+
- name: Setup Node.js
54+
uses: actions/setup-node@v4
55+
with:
56+
node-version: 20.x
57+
cache: 'yarn'
58+
59+
- name: Install dependencies
60+
run: make install
61+
62+
- name: Check formatting with Prettier
63+
run: make lint

Makefile

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
.PHONY: help install test test-coverage build lint lint-fix clean ci start
2+
3+
# Default target
4+
help:
5+
@echo "Available targets:"
6+
@echo " make install - Install dependencies"
7+
@echo " make test - Run tests"
8+
@echo " make test-coverage - Run tests with coverage"
9+
@echo " make build - Build production bundle"
10+
@echo " make lint - Check code formatting"
11+
@echo " make lint-fix - Fix code formatting"
12+
@echo " make clean - Clean build artifacts"
13+
@echo " make ci - Run all CI checks (test + build + lint)"
14+
@echo " make start - Start development server"
15+
16+
# Install dependencies
17+
install:
18+
yarn install --frozen-lockfile
19+
20+
# Run tests without coverage
21+
test:
22+
yarn test --watchAll=false
23+
24+
# Run tests with coverage
25+
test-coverage:
26+
yarn test --watchAll=false --coverage
27+
28+
# Build production bundle
29+
build:
30+
yarn build
31+
32+
# Check code formatting with Prettier
33+
lint:
34+
yarn prettier --check "src/**/*.{js,jsx,ts,tsx,json,css,scss,md}"
35+
36+
# Fix code formatting with Prettier
37+
lint-fix:
38+
yarn prettier --write "src/**/*.{js,jsx,ts,tsx,json,css,scss,md}"
39+
40+
# Clean build artifacts
41+
clean:
42+
rm -rf build
43+
rm -rf coverage
44+
rm -rf node_modules
45+
46+
# Run all CI checks
47+
ci: test-coverage build lint
48+
49+
# Start development server
50+
start:
51+
yarn start

src/App.test.tsx

Lines changed: 128 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,132 @@
11
import React from "react";
2-
import { render, screen } from "@testing-library/react";
2+
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
33
import App from "./App";
44

5-
test("renders learn react link", () => {
6-
render(<App />);
7-
const linkElement = screen.getByText(/learn react/i);
8-
expect(linkElement).toBeInTheDocument();
5+
describe("App", () => {
6+
test("renders MathJojo heading", () => {
7+
render(<App />);
8+
const heading = screen.getByText(/MathJojo/i);
9+
expect(heading).toBeInTheDocument();
10+
});
11+
12+
test("renders with default LaTeX value", () => {
13+
render(<App />);
14+
const textarea = screen.getByRole("textbox") as HTMLTextAreaElement;
15+
expect(textarea.value).toBe(
16+
"\\zeta(s) = \\sum_{n=1}^\\infty \\frac{1}{n^s}"
17+
);
18+
});
19+
20+
test("updates textarea value on user input", () => {
21+
render(<App />);
22+
const textarea = screen.getByRole("textbox") as HTMLTextAreaElement;
23+
24+
fireEvent.change(textarea, { target: { value: "x^2 + y^2 = z^2" } });
25+
26+
expect(textarea.value).toBe("x^2 + y^2 = z^2");
27+
});
28+
29+
test("shows cheatsheet toggle", () => {
30+
render(<App />);
31+
const toggleLink = screen.getByText(/Hide Cheatsheet/i);
32+
expect(toggleLink).toBeInTheDocument();
33+
});
34+
35+
test("shows settings toggle", () => {
36+
render(<App />);
37+
const toggleLink = screen.getByText(/Show Settings/i);
38+
expect(toggleLink).toBeInTheDocument();
39+
});
40+
41+
test("cheatsheet can be toggled", () => {
42+
render(<App />);
43+
44+
// Initially shows "Hide Cheatsheet"
45+
let toggleLink = screen.getByText(/Hide Cheatsheet/i);
46+
expect(toggleLink).toBeInTheDocument();
47+
48+
// Click to hide
49+
fireEvent.click(toggleLink);
50+
51+
// Now shows "Show Cheatsheet"
52+
toggleLink = screen.getByText(/Show Cheatsheet/i);
53+
expect(toggleLink).toBeInTheDocument();
54+
55+
// Click to show again
56+
fireEvent.click(toggleLink);
57+
58+
// Back to "Hide Cheatsheet"
59+
toggleLink = screen.getByText(/Hide Cheatsheet/i);
60+
expect(toggleLink).toBeInTheDocument();
61+
});
62+
63+
test("settings can be toggled", () => {
64+
render(<App />);
65+
66+
// Initially shows "Show Settings"
67+
let toggleLink = screen.getByText(/Show Settings/i);
68+
expect(toggleLink).toBeInTheDocument();
69+
70+
// Click to show
71+
fireEvent.click(toggleLink);
72+
73+
// Now shows "Hide Settings"
74+
toggleLink = screen.getByText(/Hide Settings/i);
75+
expect(toggleLink).toBeInTheDocument();
76+
});
77+
});
78+
79+
describe("URL parameter handling", () => {
80+
const originalLocation = window.location;
81+
82+
beforeEach(() => {
83+
// Mock window.location
84+
delete (window as any).location;
85+
window.location = { ...originalLocation, search: "" } as any;
86+
});
87+
88+
afterEach(() => {
89+
window.location = originalLocation;
90+
});
91+
92+
test("loads value from URL parameter", () => {
93+
// Set up URL with compressed value
94+
window.location.search = "?v=NobAgB";
95+
96+
render(<App />);
97+
const textarea = screen.getByRole("textbox") as HTMLTextAreaElement;
98+
99+
// The compressed value should be decompressed
100+
expect(textarea.value).toBeTruthy();
101+
});
102+
103+
test("handles empty compressed value", () => {
104+
window.location.search = "?v=Q";
105+
106+
render(<App />);
107+
const textarea = screen.getByRole("textbox") as HTMLTextAreaElement;
108+
109+
// Should render empty value when v=Q
110+
expect(textarea.value).toBe("");
111+
});
112+
113+
test("loads displayMode from URL parameter", () => {
114+
window.location.search = "?displayMode=0";
115+
116+
render(<App />);
117+
118+
// Open settings to check display mode
119+
const toggleLink = screen.getByText(/Show Settings/i);
120+
fireEvent.click(toggleLink);
121+
122+
// The "No" option for display mode should be bold (active)
123+
const displayModeOptions = screen.getAllByText(/^(Yes|No)$/);
124+
const noOption = displayModeOptions.find(
125+
(el) =>
126+
el.textContent === "No" &&
127+
el.getAttribute("href") === "#disable-displaymode"
128+
);
129+
130+
expect(noOption).toHaveStyle({ fontWeight: "bold" });
131+
});
9132
});

0 commit comments

Comments
 (0)