feat: added tests

ref: N25B-399
This commit is contained in:
JobvAlewijk
2026-01-02 21:06:41 +01:00
parent d80ced547c
commit 7b05c7344c
2 changed files with 120 additions and 27 deletions

View File

@@ -2,8 +2,9 @@ import React from "react";
import styles from "./SimpleProgram.module.css"; import styles from "./SimpleProgram.module.css";
import useProgramStore from "../../utils/programStore.ts"; import useProgramStore from "../../utils/programStore.ts";
/* ---------- Reusable UI ---------- */ /**
* Generic container box with a header and content area.
*/
type BoxProps = { type BoxProps = {
title: string; title: string;
children: React.ReactNode; children: React.ReactNode;
@@ -16,8 +17,10 @@ const Box: React.FC<BoxProps> = ({ title, children }) => (
</div> </div>
); );
/* ---------- Lists ---------- */ /**
* Renders a list of goals for a phase.
* Expects goal-like objects from the program store.
*/
const GoalList: React.FC<{ goals: unknown[] }> = ({ goals }) => { const GoalList: React.FC<{ goals: unknown[] }> = ({ goals }) => {
if (!goals.length) { if (!goals.length) {
return <p className={styles.empty}>No goals defined.</p>; return <p className={styles.empty}>No goals defined.</p>;
@@ -49,6 +52,9 @@ const GoalList: React.FC<{ goals: unknown[] }> = ({ goals }) => {
); );
}; };
/**
* Renders a list of triggers for a phase.
*/
const TriggerList: React.FC<{ triggers: unknown[] }> = ({ triggers }) => { const TriggerList: React.FC<{ triggers: unknown[] }> = ({ triggers }) => {
if (!triggers.length) { if (!triggers.length) {
return <p className={styles.empty}>No triggers defined.</p>; return <p className={styles.empty}>No triggers defined.</p>;
@@ -73,6 +79,9 @@ const TriggerList: React.FC<{ triggers: unknown[] }> = ({ triggers }) => {
); );
}; };
/**
* Renders a list of norms for a phase.
*/
const NormList: React.FC<{ norms: unknown[] }> = ({ norms }) => { const NormList: React.FC<{ norms: unknown[] }> = ({ norms }) => {
if (!norms.length) { if (!norms.length) {
return <p className={styles.empty}>No norms defined.</p>; return <p className={styles.empty}>No norms defined.</p>;
@@ -92,8 +101,9 @@ const NormList: React.FC<{ norms: unknown[] }> = ({ norms }) => {
); );
}; };
/* ---------- Phase Grid ---------- */ /**
* Displays all phase-related information in a grid layout.
*/
type PhaseGridProps = { type PhaseGridProps = {
norms: unknown[]; norms: unknown[];
goals: unknown[]; goals: unknown[];
@@ -104,31 +114,31 @@ const PhaseGrid: React.FC<PhaseGridProps> = ({
norms, norms,
goals, goals,
triggers, triggers,
}) => { }) => (
return ( <div className={styles.phaseGrid}>
<div className={styles.phaseGrid}> <Box title="Norms">
<Box title="Norms"> <NormList norms={norms} />
<NormList norms={norms} /> </Box>
</Box>
<Box title="Triggers"> <Box title="Triggers">
<TriggerList triggers={triggers} /> <TriggerList triggers={triggers} />
</Box> </Box>
<Box title="Goals"> <Box title="Goals">
<GoalList goals={goals} /> <GoalList goals={goals} />
</Box> </Box>
<Box title="Conditional Norms"> <Box title="Conditional Norms">
<p className={styles.empty}>No conditional norms defined.</p> <p className={styles.empty}>No conditional norms defined.</p>
</Box> </Box>
{/* Let er dus op dat deze erbij moeten */} </div>
</div> );
);
};
/* ---------- Main Component ---------- */
/**
* Main program viewer.
* Reads all data from the program store and allows
* navigating between phases.
*/
const SimpleProgram: React.FC = () => { const SimpleProgram: React.FC = () => {
const getPhaseIds = useProgramStore((s) => s.getPhaseIds); const getPhaseIds = useProgramStore((s) => s.getPhaseIds);
const getNormsInPhase = useProgramStore((s) => s.getNormsInPhase); const getNormsInPhase = useProgramStore((s) => s.getNormsInPhase);

View File

@@ -0,0 +1,83 @@
import { render, screen, fireEvent } from "@testing-library/react";
import SimpleProgram from "../../../src/pages/SimpleProgram/SimpleProgram";
import useProgramStore from "../../../src/utils/programStore";
/**
* Helper to preload the program store before rendering.
*/
function loadProgram(phases: Record<string, unknown>[]) {
useProgramStore.getState().setProgramState({ phases });
}
describe("SimpleProgram", () => {
beforeEach(() => {
loadProgram([]);
});
test("shows empty state when no program is loaded", () => {
render(<SimpleProgram />);
expect(screen.getByText("No program loaded.")).toBeInTheDocument();
});
test("renders first phase content", () => {
loadProgram([
{
id: "phase-1",
norms: [{ id: "n1", norm: "Be polite" }],
goals: [{ id: "g1", description: "Finish task", achieved: true }],
triggers: [{ id: "t1", label: "Keyword trigger" }],
},
]);
render(<SimpleProgram />);
expect(screen.getByText("Phase 1 / 1")).toBeInTheDocument();
expect(screen.getByText("Be polite")).toBeInTheDocument();
expect(screen.getByText("Finish task")).toBeInTheDocument();
expect(screen.getByText("Keyword trigger")).toBeInTheDocument();
});
test("allows navigating between phases", () => {
loadProgram([
{
id: "phase-1",
norms: [],
goals: [],
triggers: [],
},
{
id: "phase-2",
norms: [{ id: "n2", norm: "Be careful" }],
goals: [],
triggers: [],
},
]);
render(<SimpleProgram />);
expect(screen.getByText("Phase 1 / 2")).toBeInTheDocument();
fireEvent.click(screen.getByText("Next ▶"));
expect(screen.getByText("Phase 2 / 2")).toBeInTheDocument();
expect(screen.getByText("Be careful")).toBeInTheDocument();
});
test("prev button is disabled on first phase", () => {
loadProgram([
{ id: "phase-1", norms: [], goals: [], triggers: [] },
]);
render(<SimpleProgram />);
expect(screen.getByText("◀ Prev")).toBeDisabled();
});
test("next button is disabled on last phase", () => {
loadProgram([
{ id: "phase-1", norms: [], goals: [], triggers: [] },
]);
render(<SimpleProgram />);
expect(screen.getByText("Next ▶")).toBeDisabled();
});
});