Add test coverage and Fastify entrypoint
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
coverage
|
||||||
|
|
||||||
3
app/api/health/route.ts
Normal file
3
app/api/health/route.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export async function GET() {
|
||||||
|
return Response.json({ status: "ok" });
|
||||||
|
}
|
||||||
6
app/api/version/route.ts
Normal file
6
app/api/version/route.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { getBuildInfo } from "../../../src/utils/buildInfo";
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const info = getBuildInfo();
|
||||||
|
return Response.json({ version: info.version, commit: info.commit });
|
||||||
|
}
|
||||||
19
components/EnvCard.tsx
Normal file
19
components/EnvCard.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { Environment } from "../src/types";
|
||||||
|
import { StatusPill } from "./StatusPill";
|
||||||
|
|
||||||
|
interface EnvCardProps {
|
||||||
|
env: Environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EnvCard({ env }: EnvCardProps) {
|
||||||
|
return (
|
||||||
|
<div className="env-card">
|
||||||
|
<div className="env-region" style={{ textTransform: "uppercase", fontSize: "0.85rem" }}>
|
||||||
|
{env.region}
|
||||||
|
</div>
|
||||||
|
<h2>{env.name}</h2>
|
||||||
|
<div>Env ID: {env.id}</div>
|
||||||
|
<StatusPill status={env.status} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
components/StatusPill.tsx
Normal file
16
components/StatusPill.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import type { EnvStatus } from "../src/types";
|
||||||
|
|
||||||
|
const statusConfig: Record<EnvStatus, { label: string; className: string }> = {
|
||||||
|
healthy: { label: "Healthy", className: "status-pill status-pill--healthy" },
|
||||||
|
degraded: { label: "Degraded", className: "status-pill status-pill--degraded" },
|
||||||
|
down: { label: "Down", className: "status-pill status-pill--down" }
|
||||||
|
};
|
||||||
|
|
||||||
|
interface StatusPillProps {
|
||||||
|
status: EnvStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StatusPill({ status }: StatusPillProps) {
|
||||||
|
const config = statusConfig[status];
|
||||||
|
return <span className={config.className}>{config.label}</span>;
|
||||||
|
}
|
||||||
24
lib/fetcher.ts
Normal file
24
lib/fetcher.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import type { Environment } from "../src/types";
|
||||||
|
|
||||||
|
const mockEnvironments: Environment[] = [
|
||||||
|
{ id: "env_1", name: "Development", region: "us-east-1", status: "healthy" },
|
||||||
|
{ id: "env_2", name: "Staging", region: "eu-west-1", status: "degraded" }
|
||||||
|
];
|
||||||
|
|
||||||
|
export async function getEnvironments(): Promise<Environment[]> {
|
||||||
|
return mockEnvironments;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getEnvById(id: string): Promise<Environment | undefined> {
|
||||||
|
return mockEnvironments.find((env) => env.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getHealth(): Promise<{ status: string; uptime: number }> {
|
||||||
|
return { status: "ok", uptime: process.uptime() };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getVersion(): Promise<{ version: string; commit: string }> {
|
||||||
|
const version = process.env.APP_VERSION || "1.0.0";
|
||||||
|
const commit = process.env.APP_COMMIT || "unknown";
|
||||||
|
return { version, commit };
|
||||||
|
}
|
||||||
5077
package-lock.json
generated
Normal file
5077
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
package.json
Normal file
37
package.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "blackroad-os",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "vitest",
|
||||||
|
"build": "tsc"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"dependencies": {
|
||||||
|
"bullmq": "^5.64.1",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"fastify": "^5.6.2",
|
||||||
|
"ioredis": "^5.8.2",
|
||||||
|
"node-cron": "^4.2.1",
|
||||||
|
"react": "^19.2.0",
|
||||||
|
"react-dom": "^19.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@testing-library/jest-dom": "^6.9.1",
|
||||||
|
"@testing-library/react": "^16.3.0",
|
||||||
|
"@types/node": "^24.10.1",
|
||||||
|
"@types/react": "^19.2.6",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"@types/supertest": "^6.0.3",
|
||||||
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
|
"jsdom": "^27.2.0",
|
||||||
|
"supertest": "^7.1.4",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"vitest": "^4.0.13"
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/app.ts
Normal file
9
src/app.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import express from "express";
|
||||||
|
import { createMetaRouter } from "./routes/meta";
|
||||||
|
|
||||||
|
export function createApp() {
|
||||||
|
const app = express();
|
||||||
|
app.use(express.json());
|
||||||
|
app.use("/internal", createMetaRouter());
|
||||||
|
return app;
|
||||||
|
}
|
||||||
27
src/heartbeat.ts
Normal file
27
src/heartbeat.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import cron from "node-cron";
|
||||||
|
import { Queue } from "bullmq";
|
||||||
|
|
||||||
|
export function buildHeartbeatQueue(connection = { host: "localhost", port: 6379 }) {
|
||||||
|
return new Queue("heartbeat", { connection });
|
||||||
|
}
|
||||||
|
|
||||||
|
let defaultQueue: Queue | null = null;
|
||||||
|
function getDefaultQueue() {
|
||||||
|
if (!defaultQueue) {
|
||||||
|
defaultQueue = buildHeartbeatQueue();
|
||||||
|
}
|
||||||
|
return defaultQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function enqueueHeartbeat(queue = getDefaultQueue()) {
|
||||||
|
const payload = { ts: Date.now() };
|
||||||
|
await queue.add("heartbeat", payload);
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function startHeartbeatScheduler(queue = getDefaultQueue()) {
|
||||||
|
const task = cron.schedule("*/5 * * * *", () => {
|
||||||
|
enqueueHeartbeat(queue);
|
||||||
|
});
|
||||||
|
return task;
|
||||||
|
}
|
||||||
28
src/index.ts
Normal file
28
src/index.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import Fastify from "fastify";
|
||||||
|
import { getBuildInfo } from "./utils/buildInfo";
|
||||||
|
|
||||||
|
export async function createServer() {
|
||||||
|
const server = Fastify({ logger: true });
|
||||||
|
|
||||||
|
server.get("/health", async () => ({ status: "ok" }));
|
||||||
|
|
||||||
|
server.get("/version", async () => {
|
||||||
|
const info = getBuildInfo();
|
||||||
|
return { version: info.version, commit: info.commit };
|
||||||
|
});
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
const port = Number(process.env.PORT || 3000);
|
||||||
|
createServer()
|
||||||
|
.then((server) => server.listen({ port, host: "0.0.0.0" }))
|
||||||
|
.then((address) => {
|
||||||
|
console.log(`Server listening at ${address}`);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
18
src/jobs/sample.job.ts
Normal file
18
src/jobs/sample.job.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Worker } from "bullmq";
|
||||||
|
|
||||||
|
export function registerSampleJobProcessor(connection = { host: "localhost", port: 6379 }) {
|
||||||
|
const worker = new Worker(
|
||||||
|
"sample",
|
||||||
|
async (job) => {
|
||||||
|
console.log(`Processing job ${job.id}`);
|
||||||
|
return job.data;
|
||||||
|
},
|
||||||
|
{ connection }
|
||||||
|
);
|
||||||
|
|
||||||
|
worker.on("failed", (job, err) => {
|
||||||
|
console.error(`Job ${job?.id} failed`, err);
|
||||||
|
});
|
||||||
|
|
||||||
|
return worker;
|
||||||
|
}
|
||||||
16
src/routes/meta.ts
Normal file
16
src/routes/meta.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import { getBuildInfo } from "../utils/buildInfo";
|
||||||
|
|
||||||
|
export function createMetaRouter() {
|
||||||
|
const router = Router();
|
||||||
|
router.get("/health", (_req, res) => {
|
||||||
|
res.json({ status: "ok" });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/version", (_req, res) => {
|
||||||
|
const info = getBuildInfo();
|
||||||
|
res.json({ version: info.version, commit: info.commit });
|
||||||
|
});
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
8
src/types.ts
Normal file
8
src/types.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export type EnvStatus = "healthy" | "degraded" | "down";
|
||||||
|
|
||||||
|
export interface Environment {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
region: string;
|
||||||
|
status: EnvStatus;
|
||||||
|
}
|
||||||
22
src/utils/buildInfo.ts
Normal file
22
src/utils/buildInfo.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import * as childProcess from "child_process";
|
||||||
|
|
||||||
|
export interface BuildInfo {
|
||||||
|
version: string;
|
||||||
|
commit: string;
|
||||||
|
buildTime: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readGitCommit(): string | undefined {
|
||||||
|
try {
|
||||||
|
return childProcess.execSync("git rev-parse HEAD", { stdio: "pipe" }).toString().trim();
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBuildInfo(gitReader: () => string | undefined = readGitCommit): BuildInfo {
|
||||||
|
const version = process.env.APP_VERSION || "1.0.0";
|
||||||
|
const commit = process.env.APP_COMMIT || gitReader() || "unknown";
|
||||||
|
const buildTime = new Date().toISOString();
|
||||||
|
return { version, commit, buildTime };
|
||||||
|
}
|
||||||
44
tests/apiRoutes.test.ts
Normal file
44
tests/apiRoutes.test.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import request from "supertest";
|
||||||
|
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||||
|
import { createApp } from "../src/app";
|
||||||
|
import { createServer } from "../src/index";
|
||||||
|
|
||||||
|
vi.mock("../src/utils/buildInfo", () => ({
|
||||||
|
getBuildInfo: () => ({ version: "test-version", commit: "test-commit", buildTime: "now" })
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("Express internal routes", () => {
|
||||||
|
const app = createApp();
|
||||||
|
|
||||||
|
it("returns health", async () => {
|
||||||
|
const response = await request(app).get("/internal/health");
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.body).toEqual({ status: "ok" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns version", async () => {
|
||||||
|
const response = await request(app).get("/internal/version");
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.body).toEqual({ version: "test-version", commit: "test-commit" });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Fastify public routes", () => {
|
||||||
|
let server: Awaited<ReturnType<typeof createServer>>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
server = await createServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns health", async () => {
|
||||||
|
const response = await server.inject({ method: "GET", url: "/health" });
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.json()).toEqual({ status: "ok" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns version", async () => {
|
||||||
|
const response = await server.inject({ method: "GET", url: "/version" });
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response.json()).toEqual({ version: "test-version", commit: "test-commit" });
|
||||||
|
});
|
||||||
|
});
|
||||||
27
tests/buildInfo.test.ts
Normal file
27
tests/buildInfo.test.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { describe, expect, it, vi, afterEach } from "vitest";
|
||||||
|
import { getBuildInfo } from "../src/utils/buildInfo";
|
||||||
|
|
||||||
|
const originalEnv = { ...process.env };
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env = { ...originalEnv };
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getBuildInfo", () => {
|
||||||
|
it("uses env vars when provided", () => {
|
||||||
|
process.env.APP_VERSION = "3.0.0";
|
||||||
|
process.env.APP_COMMIT = "xyz";
|
||||||
|
const info = getBuildInfo();
|
||||||
|
expect(info.version).toBe("3.0.0");
|
||||||
|
expect(info.commit).toBe("xyz");
|
||||||
|
expect(new Date(info.buildTime).toString()).not.toBe("Invalid Date");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to git when env missing", () => {
|
||||||
|
const gitReader = vi.fn().mockReturnValue("abcdef");
|
||||||
|
delete process.env.APP_COMMIT;
|
||||||
|
const info = getBuildInfo(gitReader);
|
||||||
|
expect(info.commit).toBe("abcdef");
|
||||||
|
});
|
||||||
|
});
|
||||||
20
tests/envCard.test.tsx
Normal file
20
tests/envCard.test.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import { EnvCard } from "../components/EnvCard";
|
||||||
|
import type { Environment } from "../src/types";
|
||||||
|
|
||||||
|
describe("EnvCard", () => {
|
||||||
|
const env: Environment = {
|
||||||
|
id: "env_123",
|
||||||
|
name: "Production",
|
||||||
|
region: "us-west-2",
|
||||||
|
status: "healthy"
|
||||||
|
};
|
||||||
|
|
||||||
|
it("renders name, region, and id", () => {
|
||||||
|
render(<EnvCard env={env} />);
|
||||||
|
expect(screen.getByText(env.region)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(env.name)).toBeInTheDocument();
|
||||||
|
expect(screen.getByText(`Env ID: ${env.id}`)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
37
tests/fetcher.test.ts
Normal file
37
tests/fetcher.test.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { getEnvironments, getEnvById, getHealth, getVersion } from "../lib/fetcher";
|
||||||
|
|
||||||
|
const originalEnv = { ...process.env };
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env = { ...originalEnv };
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("fetcher", () => {
|
||||||
|
it("returns mock environments", async () => {
|
||||||
|
const envs = await getEnvironments();
|
||||||
|
expect(envs).toHaveLength(2);
|
||||||
|
expect(envs[0]).toEqual(
|
||||||
|
expect.objectContaining({ id: "env_1", name: "Development", region: "us-east-1" })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns environment by id", async () => {
|
||||||
|
const env = await getEnvById("env_2");
|
||||||
|
expect(env?.name).toBe("Staging");
|
||||||
|
expect(await getEnvById("missing"))?.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns health with uptime", async () => {
|
||||||
|
vi.spyOn(process, "uptime").mockReturnValue(42);
|
||||||
|
const health = await getHealth();
|
||||||
|
expect(health).toEqual({ status: "ok", uptime: 42 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns version info", async () => {
|
||||||
|
process.env.APP_VERSION = "2.0.0";
|
||||||
|
process.env.APP_COMMIT = "abc123";
|
||||||
|
const info = await getVersion();
|
||||||
|
expect(info).toEqual({ version: "2.0.0", commit: "abc123" });
|
||||||
|
});
|
||||||
|
});
|
||||||
29
tests/heartbeat.test.ts
Normal file
29
tests/heartbeat.test.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { vi, describe, it, expect, beforeEach } from "vitest";
|
||||||
|
import cron from "node-cron";
|
||||||
|
import { startHeartbeatScheduler } from "../src/heartbeat";
|
||||||
|
|
||||||
|
vi.mock("node-cron", () => {
|
||||||
|
return {
|
||||||
|
default: {
|
||||||
|
schedule: vi.fn((expression, callback) => ({ fireOnTick: callback, expression }))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("startHeartbeatScheduler", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("schedules heartbeat every five minutes and enqueues payload", async () => {
|
||||||
|
const add = vi.fn();
|
||||||
|
const task = startHeartbeatScheduler({ add } as any);
|
||||||
|
|
||||||
|
expect(cron.schedule).toHaveBeenCalledWith("*/5 * * * *", expect.any(Function));
|
||||||
|
|
||||||
|
// fire the cron callback
|
||||||
|
(task as any).fireOnTick();
|
||||||
|
|
||||||
|
expect(add).toHaveBeenCalledWith("heartbeat", expect.objectContaining({ ts: expect.any(Number) }));
|
||||||
|
});
|
||||||
|
});
|
||||||
21
tests/nextRoutes.test.ts
Normal file
21
tests/nextRoutes.test.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { GET as health } from "../app/api/health/route";
|
||||||
|
import { GET as version } from "../app/api/version/route";
|
||||||
|
|
||||||
|
vi.mock("../src/utils/buildInfo", () => ({
|
||||||
|
getBuildInfo: () => ({ version: "api-version", commit: "api-commit", buildTime: "now" })
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("Next API routes", () => {
|
||||||
|
it("returns health response", async () => {
|
||||||
|
const res = await health();
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(await res.json()).toEqual({ status: "ok" });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns version response", async () => {
|
||||||
|
const res = await version();
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
expect(await res.json()).toEqual({ version: "api-version", commit: "api-commit" });
|
||||||
|
});
|
||||||
|
});
|
||||||
39
tests/sampleJob.test.ts
Normal file
39
tests/sampleJob.test.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import { registerSampleJobProcessor } from "../src/jobs/sample.job";
|
||||||
|
|
||||||
|
vi.mock("bullmq", () => {
|
||||||
|
class MockWorker {
|
||||||
|
public handlers: Record<string, Function[]> = {};
|
||||||
|
constructor(_name: string, processor: Function, _opts: any) {
|
||||||
|
this.processor = processor;
|
||||||
|
}
|
||||||
|
processor: Function;
|
||||||
|
on(event: string, handler: Function) {
|
||||||
|
this.handlers[event] = this.handlers[event] || [];
|
||||||
|
this.handlers[event].push(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { Worker: MockWorker };
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("registerSampleJobProcessor", () => {
|
||||||
|
it("registers worker and handlers", () => {
|
||||||
|
const consoleLog = vi.spyOn(console, "log").mockImplementation(() => {});
|
||||||
|
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
|
||||||
|
|
||||||
|
const worker = registerSampleJobProcessor({ host: "localhost", port: 6379 } as any) as any;
|
||||||
|
|
||||||
|
expect(worker.processor).toBeInstanceOf(Function);
|
||||||
|
expect(worker.handlers.failed).toHaveLength(1);
|
||||||
|
|
||||||
|
// simulate processing and failure
|
||||||
|
worker.processor({ id: 1, data: { hello: "world" } });
|
||||||
|
worker.handlers.failed[0]({ id: 1 }, new Error("boom"));
|
||||||
|
|
||||||
|
expect(consoleLog).toHaveBeenCalledWith("Processing job 1");
|
||||||
|
expect(consoleError).toHaveBeenCalled();
|
||||||
|
|
||||||
|
consoleLog.mockRestore();
|
||||||
|
consoleError.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
19
tests/statusPill.test.tsx
Normal file
19
tests/statusPill.test.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import { StatusPill } from "../components/StatusPill";
|
||||||
|
import type { EnvStatus } from "../src/types";
|
||||||
|
|
||||||
|
describe("StatusPill", () => {
|
||||||
|
const cases: [EnvStatus, string, string][] = [
|
||||||
|
["healthy", "Healthy", "status-pill--healthy"],
|
||||||
|
["degraded", "Degraded", "status-pill--degraded"],
|
||||||
|
["down", "Down", "status-pill--down"]
|
||||||
|
];
|
||||||
|
|
||||||
|
it.each(cases)("renders %s status", (status, label, className) => {
|
||||||
|
render(<StatusPill status={status} />);
|
||||||
|
const pill = screen.getByText(label);
|
||||||
|
expect(pill).toBeInTheDocument();
|
||||||
|
expect(pill.className).toContain(className);
|
||||||
|
});
|
||||||
|
});
|
||||||
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true,
|
||||||
|
"baseUrl": "./",
|
||||||
|
"types": ["node", "vitest/globals", "@testing-library/jest-dom"]
|
||||||
|
},
|
||||||
|
"include": ["src", "lib", "components", "tests", "app", "vitest.config.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
11
vitest.config.ts
Normal file
11
vitest.config.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
test: {
|
||||||
|
environment: "jsdom",
|
||||||
|
setupFiles: "./vitest.setup.ts",
|
||||||
|
globals: true
|
||||||
|
}
|
||||||
|
});
|
||||||
1
vitest.setup.ts
Normal file
1
vitest.setup.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import "@testing-library/jest-dom/vitest";
|
||||||
Reference in New Issue
Block a user