Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/fac30/things-we-do into fea…
Browse files Browse the repository at this point in the history
…ture/actions-view-sane-edition
  • Loading branch information
JasonWarrenUK committed Dec 17, 2024
2 parents 1d05a25 + 17aec56 commit 4da19c0
Show file tree
Hide file tree
Showing 18 changed files with 296 additions and 250 deletions.
129 changes: 44 additions & 85 deletions __tests__/toolkitAdd.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,12 @@ import {
screen,
fireEvent,
waitFor,
act,
} from "@testing-library/react";
import AddToolPage from "@/app/toolkit/add-tool/page";
import Inputs from "@/app/toolkit/add-tool/components/AddToolInputs"; // Adjust the path if needed
import { AddToolProvider } from "@/context/AddToolContext";
import { validateUrl } from "@/lib/utils/validateUrl";
import AddToolPage from "@/app/toolkit/add-tool/page";
import { useDatabase } from "@/context/DatabaseContext";

jest.mock("next/navigation", () => ({
useRouter: jest.fn(() => ({
push: jest.fn(),
back: jest.fn(),
forward: jest.fn(),
refresh: jest.fn(),
prefetch: jest.fn(),
})),
usePathname: jest.fn(() => "/addTool"),
}));
import { validateUrl } from "@/lib/utils/validateUrl";

jest.mock("@/lib/utils/validateUrl", () => ({
validateUrl: jest.fn(),
Expand All @@ -29,6 +18,46 @@ jest.mock("@/context/DatabaseContext", () => ({
useDatabase: jest.fn(),
}));

jest.mock("next/navigation", () => ({
useRouter: jest.fn(() => ({
push: jest.fn(),
})),
}));

describe("Inputs Component", () => {
const mockDatabase = {
addToDb: jest.fn(),
addCategories: jest.fn(),
getFromDb: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
(useDatabase as jest.Mock).mockReturnValue(mockDatabase);
(validateUrl as jest.Mock).mockImplementation(() => ({
isValid: true,
url: "https://test.com",
}));
});

it("renders all form components", () => {
render(
<AddToolProvider>
<Inputs />
</AddToolProvider>
);

expect(screen.getByText("Name")).toBeInTheDocument();
expect(screen.getByText("Tags")).toBeInTheDocument();
expect(screen.getByText("Description")).toBeInTheDocument();
expect(screen.getByText("Image URL")).toBeInTheDocument();
expect(screen.getByText("Link")).toBeInTheDocument();
expect(
screen.getByRole("button", { name: "Add Tool" })
).toBeInTheDocument();
});
});

describe("AddToolInputs Component", () => {
const mockDatabase = {
getFromDb: jest.fn(),
Expand Down Expand Up @@ -64,7 +93,7 @@ describe("AddToolInputs Component", () => {
).toBeInTheDocument();
});

describe("AddToolTags Component", () => {
describe("AddToolTags Component", () => {
it("renders existing categories", async () => {
mockDatabase.getFromDb.mockResolvedValue([
{ name: "Category 1" },
Expand Down Expand Up @@ -119,74 +148,4 @@ describe("AddToolInputs Component", () => {
fireEvent.change(nameInput, { target: { value: "Test Tool" } });
expect(nameInput.value).toBe("Test Tool");
});

it("validates URLs correctly", async () => {
mockDatabase.getFromDb.mockResolvedValue([{ name: "Category 1" }]);

(validateUrl as jest.Mock).mockImplementationOnce(() => ({
isValid: false,
error: "Invalid URL",
}));

render(
<AddToolProvider>
<AddToolPage />
</AddToolProvider>
);

await waitFor(() => {
expect(screen.getByText("Category 1")).toBeInTheDocument();
});
fireEvent.click(screen.getByText("Category 1"));

const infoUrlInput = screen.getByRole("textbox", { name: "Link" });
fireEvent.change(infoUrlInput, { target: { value: "invalid-url" } });

const submitButton = screen.getByRole("button", { name: "Add Tool" });
await act(async () => {
fireEvent.click(submitButton);
});

await waitFor(() => {
expect(screen.getByText("Invalid URL")).toBeInTheDocument();
});
});

it("inserts data into the database", async () => {
mockDatabase.getFromDb.mockResolvedValue([{ name: "Category 1" }]);

render(
<AddToolProvider>
<AddToolPage />
</AddToolProvider>
);

await waitFor(() => {
expect(screen.getByText("Category 1")).toBeInTheDocument();
});
fireEvent.click(screen.getByText("Category 1"));

const inputs = screen.getAllByRole("textbox");
const nameInput = inputs[0] as HTMLInputElement;
const infoUrlInput = screen.getByRole("textbox", { name: "Link" });

fireEvent.change(nameInput, { target: { value: "Test Tool" } });
fireEvent.change(infoUrlInput, { target: { value: "https://test.com" } });

const submitButton = screen.getByRole("button", { name: "Add Tool" });

await act(async () => {
fireEvent.click(submitButton);
});

await waitFor(() => {
expect(mockDatabase.addToDb).toHaveBeenCalledWith(
"toolkit_items",
expect.objectContaining({
name: "Test Tool",
infoUrl: "https://test.com",
})
);
});
});
});
Binary file modified public/images/decisionMaker.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/images/insights_screen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 6 additions & 2 deletions src/app/insights/components/AreaChart.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import PlotlyChart from "@/ui/shared/PlotlyChart";
import { Insight } from "./InsightsDisplay";
import moodColours from "./moodColours.json";

interface MoodAreaChartProps {
dataArray: Insight[];
Expand All @@ -14,6 +15,8 @@ export default function MoodAreaChart({
endOfRange,
selectedButton,
}: MoodAreaChartProps) {
const width = screen.width * 0.85;

if (!dataArray || dataArray.length === 0) {
return <div>No data available for the graph.</div>;
}
Expand Down Expand Up @@ -62,6 +65,7 @@ export default function MoodAreaChart({
fill: "tonexty",
name: mood,
stackgroup: "one",
fillcolor: moodColours[mood as keyof typeof moodColours] || "#FFFFFF",
line: { shape: "spline" },
};
});
Expand Down Expand Up @@ -93,8 +97,8 @@ export default function MoodAreaChart({
<PlotlyChart
data={traces as Plotly.Data[]}
layout={{
width: 350,
height: 350,
width: width,
height: width,
margin: {
l: 10,
r: 10,
Expand Down
47 changes: 39 additions & 8 deletions src/app/insights/components/LineGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export default function LineGraph({
endOfRange,
selectedButton,
}: LineGraphProps) {
const width = screen.width * 0.85;

if (!dataArray || dataArray.length === 0) {
return <div>No data available for the graph.</div>;
}
Expand Down Expand Up @@ -51,6 +53,28 @@ export default function LineGraph({
}));
};

const aggregateDataByDay = (
data: number[],
timestamps: string[]
): AggregatedData[] => {
const dailyData: { [key: string]: number[] } = {};

timestamps.forEach((timestamp, index) => {
const date = new Date(timestamp);
const dayKey = date.toISOString().split("T")[0];

if (!dailyData[dayKey]) {
dailyData[dayKey] = [];
}
dailyData[dayKey].push(data[index]);
});

return Object.entries(dailyData).map(([dayKey, values]) => ({
timestamp: new Date(dayKey).toISOString(),
value: values.reduce((sum, val) => sum + val, 0) / values.length,
}));
};

const sortedData = [...dataArray].sort(
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
);
Expand All @@ -66,9 +90,16 @@ export default function LineGraph({
aggregated.map((d) => d.timestamp),
aggregated.map((d) => d.value),
];
} else if (selectedButton === "week" || selectedButton === "month") {
const aggregated = aggregateDataByDay(values, xAxis);
return [
aggregated.map((d) => d.timestamp),
aggregated.map((d) => d.value),
];
}
return [xAxis, values];
};

const [dopamineX, dopamineY] = processData(
sortedData.map((entry) => entry.neurotransmitters.dopamine)
);
Expand Down Expand Up @@ -128,32 +159,32 @@ export default function LineGraph({
y: dopamineY,
type: "scatter",
mode: "lines",
marker: { color: "green" },
line: { shape: "spline", width: 3 },
marker: { color: "#893FFC" },
line: { shape: "linear", width: 3 },
name: "Urgent",
},
{
x: serotoninX,
y: serotoninY,
type: "scatter",
mode: "lines",
marker: { color: "blue" },
line: { shape: "spline", width: 3 },
marker: { color: "#D3A107" },
line: { shape: "linear", width: 3 },
name: "Effortful",
},
{
x: adrenalineX,
y: adrenalineY,
type: "scatter",
mode: "lines",
marker: { color: "red" },
line: { shape: "spline", width: 3 },
marker: { color: "#6FDC8C" },
line: { shape: "linear", width: 3 },
name: "Worthwile",
},
]}
layout={{
width: 350,
height: 350,
width: width,
height: width,
margin: {
l: 10,
r: 10,
Expand Down
10 changes: 7 additions & 3 deletions src/app/insights/components/StreamGraph.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import PlotlyChart from "@/ui/shared/PlotlyChart";
import { Insight } from "./InsightsDisplay";
import moodColours from "./moodColours.json";

interface MoodStreamGraphProps {
dataArray: Insight[];
Expand All @@ -14,6 +15,8 @@ export default function MoodStreamGraph({
endOfRange,
selectedButton,
}: MoodStreamGraphProps) {
const width = screen.width * 0.85;

if (!dataArray || dataArray.length === 0) {
return <div>No data available for the graph.</div>;
}
Expand Down Expand Up @@ -69,7 +72,7 @@ export default function MoodStreamGraph({
fill: "tonexty",
name: mood,
stackgroup: "one",
fillcolor: "auto",
fillcolor: moodColours[mood as keyof typeof moodColours] || "#FFFFFF",
orientation: "v",
stackgaps: "interpolate",
line: { shape: "spline" },
Expand Down Expand Up @@ -125,8 +128,9 @@ export default function MoodStreamGraph({
<PlotlyChart
data={traces as Plotly.Data[]}
layout={{
width: 350,
height: 350,
autosize: true,
width: width,
height: width,
margin: {
l: 10,
r: 10,
Expand Down
10 changes: 10 additions & 0 deletions src/app/insights/components/moodColours.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"interest": "#D3A107",
"guilt": "#4689FF",
"freeze": "#FF7EB5",
"fight/flight": "#FA4E56",
"joy": "#FF8833",
"content": "#09BDB9",
"relief": "#6FDC8C",
"distress": "#33B1FF"
}
2 changes: 1 addition & 1 deletion src/app/needs/info/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export default function NeedsInfoPage() {
</div>
<Link
className="text-twd-text-link"
href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC7404378/"
href="https://journals.sagepub.com/doi/10.1177/21582440221096139"
>
Learn More Here
</Link>
Expand Down
22 changes: 19 additions & 3 deletions src/app/toolkit/add-tool/components/AddToolInputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,18 @@ export default function Inputs() {
const [categoryErrorModal, setCategoryErrorModal] = useState(false);
const [infoUrlErrorModal, setInfoUrlErrorModal] = useState(false);
const [imageUrlErrorModal, setImageUrlErrorModal] = useState(false);
const [nameErrorModalOpen, setNameErrorModalOpen] = useState(false);
const [submitErrorModal, setSubmitErrorModal] = useState(false);
const [submitErrorMessage, setSubmitErrorMessage] = useState("");

function SubmitButton() {
const handleSubmit = async () => {
console.log(`Validating form with state: ${JSON.stringify(formState)}`);

if (!formState.name || formState.name.trim() === "") {
setNameErrorModalOpen(true);
return;
}

if (formState.categories.length === 0) {
setCategoryErrorModal(true);
Expand Down Expand Up @@ -133,11 +139,21 @@ export default function Inputs() {
}}
/>

{/* Modal for missing name */}
<Modal
title="Name is required"
modalOpen={nameErrorModalOpen}
forwardButton={{
label: "OK",
action: () => setNameErrorModalOpen(false),
}}
/>

<Modal
title="You created an unused category. What would you like to save?"
title="You created an unused tag. What would you like to save?"
modalOpen={unusedCategoryModalOpen}
forwardButton={{
label: "Tool & Category",
label: "Tool & Tag",
action: () => {
setSaveUnusedCategory(true);
setUnusedCategoryModalOpen(false);
Expand All @@ -153,7 +169,7 @@ export default function Inputs() {
/>

<Modal
title="Please select at least one category"
title="Please select at least one tag"
modalOpen={categoryErrorModal}
forwardButton={{
label: "OK",
Expand Down
Loading

0 comments on commit 4da19c0

Please sign in to comment.