Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/needs page #79

Merged
merged 62 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
4f7fa90
refactor: generic types in getFromDb DatabaseManager.ts method
maxitect Dec 11, 2024
c0af107
refactor: make generic Section.tsx component from NeedsSection.tsx
maxitect Dec 11, 2024
631be41
refactor: make generic Display.tsx component from NeedsDisplay.tsx
maxitect Dec 11, 2024
ad4300b
refactor: use generic diplay component in needs page.tsx
maxitect Dec 11, 2024
171f170
feat: next actions page.tsx using generic display component to catego…
maxitect Dec 11, 2024
ef48b44
feat: add highlighting/filtering for needs and next actions based on …
maxitect Dec 11, 2024
93db53c
refactor: adapt needs page.tsx with new display component
maxitect Dec 11, 2024
ee1988d
refactor: adapt next actions page.tsx with new display component
maxitect Dec 11, 2024
83ec5b2
style: use highlighted key to conditionally style needs items
maxitect Dec 11, 2024
3ca2676
chore: merge with main
maxitect Dec 11, 2024
dc14e73
chore: merge with main pr #71, #73
maxitect Dec 11, 2024
323de34
refactor: supersede previous generic display component
maxitect Dec 12, 2024
0f14732
refactor: update generic display to enable needs extension
maxitect Dec 12, 2024
d5f0dbe
refactor: update needs modal to allow for props within needs display
maxitect Dec 12, 2024
1537ade
refactor: update needs display to extend generic needs component with…
maxitect Dec 12, 2024
4c4eebe
refactor: update section component to take multiple callback function…
maxitect Dec 12, 2024
a408b6d
refactor: update needs page.tsx to use needs display extended component
maxitect Dec 12, 2024
f610b0f
feat: combined needs display component updating database with mood an…
maxitect Dec 12, 2024
9378e27
refactor: remove comments in NeedsDisplay.tsx
maxitect Dec 12, 2024
9ecd2c1
refactor: remove any types from Display.tsx
maxitect Dec 12, 2024
a0b3a4f
refactor: delete unused NeedsSection.tsx
maxitect Dec 12, 2024
a9c7ba9
build: eslint plugin for react hooks
maxitect Dec 12, 2024
8ebd2ac
refactor: fix eslint errors in Display.tsx
maxitect Dec 12, 2024
1a75fba
refactor: fix eslint errors in NeedsDisplay.tsx
maxitect Dec 12, 2024
28dbd2f
refactor: remove toJSON method from db get method in DatabaseManager.ts
maxitect Dec 12, 2024
1eadf78
refactor: access data using toJSON in Display.tsx
maxitect Dec 12, 2024
251d168
chore: add chainEnd prop to generic Display.tsx component
maxitect Dec 12, 2024
36330cf
chore: ad chainEnd state and pass to display component in NeedsDispla…
maxitect Dec 12, 2024
95b3295
chore: add button to next actions in NeedsDisplay.tsx
maxitect Dec 12, 2024
56f07a0
refactor: move all page elements into NeedsDisplay.tsx
maxitect Dec 12, 2024
2845a66
chore: add missing chainEnd attribute in display component on next ac…
maxitect Dec 12, 2024
74cb437
chore: merge with main pr #72, #74
maxitect Dec 12, 2024
3c2eb7f
refactor: move header component out of NeedsDisplay.tsx
maxitect Dec 12, 2024
19d0ad7
refactor: move header component to needs page.tsx
maxitect Dec 12, 2024
c74106d
refactor: make priority key in needs schema an object containing orde…
maxitect Dec 12, 2024
1b8168f
refactor: add object type to update database for nested objects update
maxitect Dec 12, 2024
846b854
chore: update priority key when need is selected in NeedsDisplay.tsx
maxitect Dec 12, 2024
9241ddf
style: add disabled state to Button.tsx
maxitect Dec 13, 2024
e0373c7
refactor: remove button to next actions out of NeedsDisplay.tsx
maxitect Dec 13, 2024
48071f1
chore: move button to next actions into Display.tsx so that it can ac…
maxitect Dec 13, 2024
d32ef10
chore: merge with main pr #75
maxitect Dec 13, 2024
d1fe714
refactor: optimise state management in Display.tsx for performance
maxitect Dec 13, 2024
315da63
style: add glow shadow custom tailwind class
maxitect Dec 13, 2024
1ccf39c
style: apply glow shadow to next actions button when active
maxitect Dec 13, 2024
bb568d5
style: add custom tailwind mood colours
maxitect Dec 13, 2024
6f24ec4
feat: needs display rendering and updating database / split from gene…
maxitect Dec 13, 2024
c9e3391
style: condional colour highlighting for needs based on mood in Secti…
maxitect Dec 13, 2024
d31b8f3
feat: update selectedTimestamps key every time a need is selected
maxitect Dec 13, 2024
3cb73fa
feat: update selectedTimestamps key every time a need is selected
maxitect Dec 13, 2024
392ea98
fix: navigation to /next-actions route in NeedsDisplay.tsx
maxitect Dec 13, 2024
0ff484b
refactor: delete unused superseded generic display file
maxitect Dec 13, 2024
eb52706
refactor: update NeedsModal.tsx to contain aria-labels on buttons for…
maxitect Dec 13, 2024
1a26536
test: test file for NeedsDisplay.tsx
maxitect Dec 13, 2024
02e8ab7
test: test file for NeedsModal.tsx
maxitect Dec 13, 2024
f345f93
test: test file for Section.tsx
maxitect Dec 13, 2024
e5ca320
fix: insights display component database manager update fix
maxitect Dec 13, 2024
50ec1e9
fix: add tool tag component database manager update fix
maxitect Dec 13, 2024
6ddd413
fix: tool list component database manager update fix
maxitect Dec 13, 2024
ddad629
fix: tool display component database manager update fix
maxitect Dec 13, 2024
3133ace
fix: category bar component database manager update fix
maxitect Dec 13, 2024
7866043
style: change render position of generic Modal.tsx
maxitect Dec 13, 2024
9c0d3a5
chore: merge with main pr #76
maxitect Dec 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions __tests__/NeedsDisplay.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import NeedsDisplay from "@/app/needs/components/NeedsDisplay";
import { needs, needsCategories } from "@/lib/db/seed/needs";

jest.mock("@/context/DatabaseContext", () => ({
useDatabase: jest.fn(() => ({
getFromDb: jest.fn((table) => {
if (table === "needs_categories") {
return Promise.resolve(
needsCategories.map((category) => ({
...category,
toJSON: () => category,
}))
);
}
if (table === "needs") {
return Promise.resolve(
needs.map((need) => ({ ...need, toJSON: () => need }))
);
}
}),
updateDocument: jest.fn(),
})),
}));

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

describe("NeedsDisplay Component", () => {
const mockUpdateDocument = jest
.requireMock("@/context/DatabaseContext")
.useDatabase().updateDocument;

test("handles modal interactions for unselected needs", async () => {
render(<NeedsDisplay />);
const unselectedNeed = needs.find((need) => !need.selectedExpiry);
if (!unselectedNeed) return;

fireEvent.click(await screen.findByText(unselectedNeed.name));
expect(
await screen.findByText(`You have selected ~${unselectedNeed.name}~`)
).toBeInTheDocument();
});

test("updates the database correctly when deselecting a need", async () => {
render(<NeedsDisplay />);
const selectedNeed = needs.find(
(need) =>
need.selectedExpiry && new Date(need.selectedExpiry) > new Date()
);
if (!selectedNeed) return;

fireEvent.click(await screen.findByText(selectedNeed.name));
fireEvent.click(await screen.findByText("Yes"));

await waitFor(() => {
expect(mockUpdateDocument).toHaveBeenCalledWith(
"needs",
selectedNeed.id,
"selectedExpiry",
expect.any(String)
);
});
});
});
134 changes: 134 additions & 0 deletions __tests__/NeedsModal.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { render, screen, fireEvent } from "@testing-library/react";
import NeedsModal from "@/app/needs/components/NeedsModal";

describe("NeedsModal Component", () => {
const mockHandleCloseClick = jest.fn();
const mockHandleBackClick = jest.fn();
const mockHandlePositiveClick = jest.fn();
const mockHandleNegativeClick = jest.fn();

beforeEach(() => {
jest.clearAllMocks();
});

test("closes the modal on close icon click", () => {
render(
<NeedsModal
modalOpen={true}
title="Test Need"
needsStep={2}
urgent={0}
effortful={0}
worthDoing={0}
positiveLabel="Next"
negativeLabel="Skip"
handlePositiveClick={mockHandlePositiveClick}
handleNegativeClick={mockHandleNegativeClick}
handleBackClick={mockHandleBackClick}
handleCloseClick={mockHandleCloseClick}
/>
);

const closeButton = screen.getByRole("button", { name: "Close" });
fireEvent.click(closeButton);
expect(mockHandleCloseClick).toHaveBeenCalled();
});

test("renders modal content correctly", () => {
render(
<NeedsModal
modalOpen={true}
title="Test Need"
needsStep={1}
urgent={0}
effortful={0}
worthDoing={0}
positiveLabel="Next"
negativeLabel="Skip"
handlePositiveClick={mockHandlePositiveClick}
handleNegativeClick={mockHandleNegativeClick}
handleBackClick={mockHandleBackClick}
handleCloseClick={mockHandleCloseClick}
/>
);

expect(screen.getByText("Step 1 of 3")).toBeInTheDocument();
expect(screen.getByText("Test Need")).toBeInTheDocument();
expect(
screen.getByText(
"Select the button that best describes meeting this need right now."
)
).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Skip" })).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Next" })).toBeInTheDocument();
});

test("handles the back button click on step 2 or higher", () => {
render(
<NeedsModal
modalOpen={true}
title="Test Need"
needsStep={2}
urgent={0}
effortful={0}
worthDoing={0}
positiveLabel="Next"
negativeLabel="Skip"
handlePositiveClick={mockHandlePositiveClick}
handleNegativeClick={mockHandleNegativeClick}
handleBackClick={mockHandleBackClick}
handleCloseClick={mockHandleCloseClick}
/>
);

const backButton = screen.getByRole("button", { name: "Back" });
fireEvent.click(backButton);
expect(mockHandleBackClick).toHaveBeenCalled();
});

test("handles positive button click", () => {
render(
<NeedsModal
modalOpen={true}
title="Test Need"
needsStep={1}
urgent={0}
effortful={0}
worthDoing={0}
positiveLabel="Next"
negativeLabel="Skip"
handlePositiveClick={mockHandlePositiveClick}
handleNegativeClick={mockHandleNegativeClick}
handleBackClick={mockHandleBackClick}
handleCloseClick={mockHandleCloseClick}
/>
);

const positiveButton = screen.getByRole("button", { name: "Next" });
fireEvent.click(positiveButton);
expect(mockHandlePositiveClick).toHaveBeenCalled();
});

test("handles negative button click", () => {
render(
<NeedsModal
modalOpen={true}
title="Test Need"
needsStep={1}
urgent={0}
effortful={0}
worthDoing={0}
positiveLabel="Next"
negativeLabel="Skip"
handlePositiveClick={mockHandlePositiveClick}
handleNegativeClick={mockHandleNegativeClick}
handleBackClick={mockHandleBackClick}
handleCloseClick={mockHandleCloseClick}
/>
);

const negativeButton = screen.getByRole("button", { name: "Skip" });
fireEvent.click(negativeButton);
expect(mockHandleNegativeClick).toHaveBeenCalled();
});
});
56 changes: 56 additions & 0 deletions __tests__/Section.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import Section from "@/app/needs/components/Section";
import { render, screen, fireEvent } from "@testing-library/react";

describe("Section Component", () => {
const mockHandleOpen = jest.fn();
const mockCategoryData = {
key: "Category 1",
items: [
{
id: "1",
name: "Need 1",
highlighted: true,
label: "Need 1",
mood: "joy",
},
{
id: "2",
name: "Need 2",
highlighted: false,
label: "Need 2",
mood: "",
},
],
};

test("renders section with items", () => {
render(
<Section categoryData={mockCategoryData} handleOpen={mockHandleOpen} />
);

expect(screen.getByText("Category 1")).toBeInTheDocument();
expect(screen.getByText("Need 1")).toBeInTheDocument();
expect(screen.getByText("Need 2")).toBeInTheDocument();
});

test("applies correct styles for highlighted and non-highlighted items", () => {
render(
<Section categoryData={mockCategoryData} handleOpen={mockHandleOpen} />
);

const highlightedButton = screen.getByText("Need 1");
const nonHighlightedButton = screen.getByText("Need 2");

expect(highlightedButton).toHaveClass("bg-twd-mood-joy-yellow");
expect(nonHighlightedButton).toHaveClass("bg-gray-600");
});

test("calls handleOpen when item is clicked", () => {
render(
<Section categoryData={mockCategoryData} handleOpen={mockHandleOpen} />
);

fireEvent.click(screen.getByText("Need 1"));
expect(mockHandleOpen).toHaveBeenCalledWith(mockCategoryData.items[0]);
});
});
20 changes: 17 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@types/react-plotly.js": "^2.6.3",
"eslint": "^8",
"eslint-config-next": "15.0.3",
"eslint-plugin-react-hooks": "^5.1.0-rc-79ddf5b5-20241210",
"husky": "^9.1.7",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
Expand Down
16 changes: 9 additions & 7 deletions src/app/insights/components/InsightsDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
import { useDatabase } from "@/context/DatabaseContext";
import LineGraph from "./LineGraph";
import MoodAreaChart from "./AreaChart";
import retrieveDataObject from "@/lib/utils/retrieveDataObject";
import { useState, useEffect } from "react";

import Button from "@/ui/shared/Button";
import clsx from "clsx";
import MoodStreamGraph from "./StreamGraph";
import { RxDocument } from "rxdb";

export interface Insight {
neurotransmitters: {
Expand Down Expand Up @@ -71,19 +71,21 @@ export default function InsightsDisplay() {
}, [/* useNow, */ selectedButton, now]);

const getInsights = async () => {
const myInsights = await database.getFromDb("mood_records");
const insightsResponse = await database.getFromDb<RxDocument<Insight>>(
"mood_records"
);

if (!myInsights) {
if (!insightsResponse) {
console.log("No insights found.");
setInsights([]);
return;
}
const goodInsights = retrieveDataObject(myInsights);

setInsights(goodInsights);
const insightsData = insightsResponse.map((doc) => doc.toJSON() as Insight);
setInsights(insightsData);

if (goodInsights.length > 0) {
const latestInsight = goodInsights.reduce((acc, curr) => {
if (insightsData.length > 0) {
const latestInsight = insightsData.reduce((acc, curr) => {
return new Date(curr.timestamp) > new Date(acc.timestamp) ? curr : acc;
});
setNow(new Date(latestInsight.timestamp));
Expand Down
Loading
Loading