feat: added tests
ref: N25B-399
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
83
test/pages/simpleProgram/SimpleProgram.tsx
Normal file
83
test/pages/simpleProgram/SimpleProgram.tsx
Normal 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user