From ca0f9ecd151fea3ca9d2257692356d48c2cee9a4 Mon Sep 17 00:00:00 2001 From: jhcp Date: Sun, 26 May 2024 21:37:12 -0700 Subject: [PATCH 1/5] Testing constrained text component --- frontend/cypress.config.ts | 1 + frontend/src/components/ConstrainedText.tsx | 7 +- .../components/tests/ConstrainedText.cy.tsx | 76 ++++++++++++++++++- 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/frontend/cypress.config.ts b/frontend/cypress.config.ts index 45e924e..56252ad 100644 --- a/frontend/cypress.config.ts +++ b/frontend/cypress.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from "cypress"; export default defineConfig({ + projectId: 'e5nkh3', e2e: { setupNodeEvents(on, config) { // implement node event listeners here diff --git a/frontend/src/components/ConstrainedText.tsx b/frontend/src/components/ConstrainedText.tsx index 8f213a5..c3a7798 100644 --- a/frontend/src/components/ConstrainedText.tsx +++ b/frontend/src/components/ConstrainedText.tsx @@ -19,10 +19,13 @@ const ConstrainedText = ({ }: Props) => { return ( - {prefix} + {text.length <= charLimit ? "" : prefix} {text.length <= charLimit ? text - : text.substring(0, Math.max(charLimit - postfix.length, 0))} + : text.substring( + 0, + Math.max(charLimit - postfix.length - prefix.length, 0) + )} {text.length <= charLimit ? "" : postfix} ); diff --git a/frontend/src/components/tests/ConstrainedText.cy.tsx b/frontend/src/components/tests/ConstrainedText.cy.tsx index 866031c..10b4887 100644 --- a/frontend/src/components/tests/ConstrainedText.cy.tsx +++ b/frontend/src/components/tests/ConstrainedText.cy.tsx @@ -1,9 +1,81 @@ import ConstrainedText from "../ConstrainedText"; describe("", () => { - it("renders", () => { + let charLimit = 10; + let string = "This is the constrained text."; + + it("renders correctly when provided text under limit", () => { + charLimit = 100; + cy.mount(); + cy.get("p.chakra-text", { + timeout: 500, + }).should("have.text", string.slice(0, charLimit)); + }); + + it("renders the correct amount of characters.", () => { + cy.mount(); + cy.get("p.chakra-text").contains(string.slice(0, charLimit), { + timeout: 500, + }); + }); + + it("correctly postfixes text", () => { + charLimit = 10; + cy.mount( + + ); + cy.get("p.chakra-text", { + timeout: 500, + }).should("have.text", string.slice(0, charLimit - 3) + "..."); + }); + + it("correctly prefixes text", () => { + cy.mount( + + ); + cy.get("p.chakra-text", { + timeout: 500, + }).should("have.text", "..." + string.slice(0, charLimit - 3)); + }); + + it("can prefix and postfix simultaneously", () => { + cy.mount( + + ); + cy.get("p.chakra-text", { + timeout: 500, + }).should("have.text", ".,." + string.slice(0, charLimit - 6) + ",.,"); + }); + + it("correctly renders postfix with a postfix longer than char limit", () => { + cy.mount( + + ); + cy.get("p.chakra-text", { + timeout: 500, + }).should("have.text", "This is a long postfix."); + }); + + //Visual test + it("uses provided styles", () => { cy.mount( - + ); }); }); From 0e0d9e0f21ed1eefaef208a8d57e3baab5e4b786 Mon Sep 17 00:00:00 2001 From: jhcp Date: Mon, 27 May 2024 09:23:39 -0700 Subject: [PATCH 2/5] Page selector tests --- frontend/src/components/PageNumberButton.tsx | 2 +- frontend/src/components/PageSelector.tsx | 78 ++++++---- .../src/components/tests/PageSelector.cy.tsx | 140 +++++++++++++++++- 3 files changed, 185 insertions(+), 35 deletions(-) diff --git a/frontend/src/components/PageNumberButton.tsx b/frontend/src/components/PageNumberButton.tsx index af4e1f5..787a188 100644 --- a/frontend/src/components/PageNumberButton.tsx +++ b/frontend/src/components/PageNumberButton.tsx @@ -26,7 +26,7 @@ const PageNumberButton = ({ borderColor="gray.500" borderStyle="solid none solid none" > - {displayValue === -1 ? "..." : displayValue} + {displayValue <= -1 ? "..." : displayValue} ); }; diff --git a/frontend/src/components/PageSelector.tsx b/frontend/src/components/PageSelector.tsx index 7a8fc49..b6b1b92 100644 --- a/frontend/src/components/PageSelector.tsx +++ b/frontend/src/components/PageSelector.tsx @@ -19,16 +19,14 @@ const PageSelector = ({ minimal = true, }: Props) => { if (limit < 5) { - throw RangeError("Limit must be at minimum 5."); + console.log( + "Error: limit for PageSelector must be at most 5. Supplied: ", + limit + ); + return error loading page selector.; } - const cells: JSX.Element[] = []; - - // Render up to limit cells. - // Start at first cell (1) or selected cell - 1 (always have at least one cell before selected cell) - - // If range > limit, display limit - 1 numbers - + const cells: JSX.Element[] = []; //Holds a list of elements to render // Push a left arrow cells.push( , + ); + if (range <= limit) { for (let i = 1; i <= range; i++) { cells.push( @@ -54,13 +53,29 @@ const PageSelector = ({ displayValue={i} onSelect={onSelect} key={"pageSelect" + i} - />, + /> ); } } else { - const start = Math.max(1, selected - Math.floor((limit - 2) / 2)); - const end = Math.min(range, selected + Math.floor((limit - 2) / 2)); - console.log("Starting at ", start, " and ending at ", end); + const borderRange = Math.floor((limit - 2) / 2); + let start = Math.max(1, selected - borderRange); + let end = Math.min(range, selected + borderRange); + + if (end <= limit - 1) { + // Implies that we should just display up to limit numbers + // (minus one for the end of the range) since we are very early in the list + end = limit - 1; // Like a "ceil" operation + if (start != 1) { + throw RangeError("Misalignment of range occured, lower bound"); + } + } else if (start >= range - limit + 1) { + // Inverse of above + start = range - limit + 2; // Range is inclusive, so add two (if limit is 5 & range 10, this displays 1...7, 8, 9, 10) + if (end != range) { + throw RangeError("Misalignment of range occured, upper bound"); + } + } + // Always push a 1 cells.push( , + /> ); + // Push the rest of range for (let i = start; i <= end; i++) { if (i === 1 || i === range) continue; - ; + cells.push( + + ); } // Always push the end of the range cells.push( @@ -87,22 +105,26 @@ const PageSelector = ({ displayValue={range} onSelect={onSelect} key={"pageSelect" + range} - />, + /> ); + // Insert ... buttons if necessary - if (selected - Math.floor((limit - 2) / 2) > 2) + if (selected - borderRange > 2) { cells.splice( - 1, + 2, 0, - , + ); - if (selected + Math.floor((limit - 1) / 2) < range - 2) + } + if (selected + borderRange < range - 2) { cells.splice( - range - 2, + start === 1 ? end + 1 : limit + 1, // Wild formula but just trust me on this bro 0, - , + ); + } } + // Push a right arrow cells.push( , + ); return ( diff --git a/frontend/src/components/tests/PageSelector.cy.tsx b/frontend/src/components/tests/PageSelector.cy.tsx index a8c4f0a..613ebe8 100644 --- a/frontend/src/components/tests/PageSelector.cy.tsx +++ b/frontend/src/components/tests/PageSelector.cy.tsx @@ -1,16 +1,144 @@ +import { useState } from "react"; import PageSelector from "../PageSelector"; describe("", () => { - it("freaking loads", () => { + it("loads a full range when range==limit", () => { + cy.mount( + {}} /> + ); + }); + + it("loads a full range when range < limit", () => { + cy.mount( + {}} /> + ); + }); + + it("rejects a limit < 5", () => { + cy.mount( + {}} /> + ); + cy.get(".css-0").should("have.text", "error loading page selector."); + }); + + it("renders only up to limit cells", () => { + cy.mount( + {}} /> + ); + cy.get(".chakra-button__group") + .children(":visible", { timeout: 500 }) + .should("have.length", 9); + + cy.get(".chakra-button__group > :nth-child(2)").should("have.text", "1"); + cy.get(".chakra-button__group > :nth-child(3)").should("have.text", "..."); + cy.get(".chakra-button__group > :nth-child(4)").should("have.text", "4"); + cy.get(".chakra-button__group > :nth-child(5)").should("have.text", "5"); + cy.get(".chakra-button__group > :nth-child(6)").should("have.text", "6"); + cy.get(".chakra-button__group > :nth-child(7)").should("have.text", "..."); + cy.get(".chakra-button__group > :nth-child(8)").should("have.text", "10"); + }); + + it("renders only right ... box when low selected value", () => { + cy.mount( + {}} /> + ); + cy.get(".chakra-button__group") + .children(":visible", { timeout: 500 }) + .should("have.length", 8); + + cy.get(".chakra-button__group > :nth-child(2)").should("have.text", "1"); + cy.get(".chakra-button__group > :nth-child(3)").should("have.text", "2"); + cy.get(".chakra-button__group > :nth-child(4)").should("have.text", "3"); + cy.get(".chakra-button__group > :nth-child(5)").should("have.text", "4"); + cy.get(".chakra-button__group > :nth-child(6)").should("have.text", "..."); + cy.get(".chakra-button__group > :nth-child(7)").should("have.text", "10"); + }); + + it("renders only left ... box when high selected values", () => { + cy.mount( + {}} /> + ); + cy.get(".chakra-button__group") + .children(":visible", { timeout: 500 }) + .should("have.length", 8); + + cy.get(".chakra-button__group > :nth-child(2)").should("have.text", "1"); + cy.get(".chakra-button__group > :nth-child(3)").should("have.text", "..."); + cy.get(".chakra-button__group > :nth-child(4)").should("have.text", "7"); + cy.get(".chakra-button__group > :nth-child(5)").should("have.text", "8"); + cy.get(".chakra-button__group > :nth-child(6)").should("have.text", "9"); + cy.get(".chakra-button__group > :nth-child(7)").should("have.text", "10"); + }); + + it("has more center values", () => { + cy.log("with high limits, and not more outer values"); + cy.mount( + {}} /> + ); + cy.get(".chakra-button__group") + .children(":visible", { timeout: 500 }) + .should("have.length", 11); + + cy.get(".chakra-button__group > :nth-child(2)").should("have.text", "1"); + cy.get(".chakra-button__group > :nth-child(3)").should("have.text", "..."); + cy.get(".chakra-button__group > :nth-child(4)").should("have.text", "3"); + cy.get(".chakra-button__group > :nth-child(5)").should("have.text", "4"); + cy.get(".chakra-button__group > :nth-child(6)").should("have.text", "5"); + cy.get(".chakra-button__group > :nth-child(7)").should("have.text", "6"); + cy.get(".chakra-button__group > :nth-child(8)").should("have.text", "7"); + cy.get(".chakra-button__group > :nth-child(9)").should("have.text", "..."); + cy.get(".chakra-button__group > :nth-child(10)").should("have.text", "10"); + }); + + it("properly renders page values for large ranges", () => { + cy.mount( + {}} /> + ); + cy.get(".chakra-button__group") + .children(":visible", { timeout: 500 }) + .should("have.length", 9); + + cy.get(".chakra-button__group > :nth-child(2)").should("have.text", "1"); + cy.get(".chakra-button__group > :nth-child(3)").should("have.text", "..."); + cy.get(".chakra-button__group > :nth-child(4)").should("have.text", "83"); + cy.get(".chakra-button__group > :nth-child(5)").should("have.text", "84"); + cy.get(".chakra-button__group > :nth-child(6)").should("have.text", "85"); + cy.get(".chakra-button__group > :nth-child(7)").should("have.text", "..."); + cy.get(".chakra-button__group > :nth-child(8)").should("have.text", "100"); + }); + + it("renders maximalSelector correctly", () => { cy.mount( { - console.log("Printin"); - }} + selected={5} + onSelect={() => {}} + minimal={false} /> ); + cy.get(".chakra-button__group") + .children(":visible", { timeout: 500 }) + .should("have.length", 9); + + cy.get(".chakra-button__group > :nth-child(1)").should("have.text", "Left"); + cy.get(".chakra-button__group > :nth-child(9)").should( + "have.text", + "Right" + ); + }); + + it("disables left button on select p1", () => { + cy.mount( + {}} /> + ); + cy.get(".chakra-button__group > :nth-child(1)").should("be.disabled"); + }); + + it("disables left button on select final page", () => { + cy.mount( + {}} /> + ); + cy.get(".chakra-button__group > :nth-child(8)").should("be.disabled"); }); }); From 92763324b4c7c79083798fc1ea21673467aabb75 Mon Sep 17 00:00:00 2001 From: jhcp Date: Mon, 27 May 2024 10:49:05 -0700 Subject: [PATCH 3/5] SkeletonGroup testing --- frontend/src/components/CompactGroup.tsx | 2 +- frontend/src/components/SkeletonGroup.tsx | 31 +++++------- .../src/components/tests/PageSelector.cy.tsx | 1 - .../src/components/tests/SkeletonGroup.cy.tsx | 48 +++++++++++++++++++ frontend/src/pages/MyGroupsPage.tsx | 27 ++++++++--- 5 files changed, 81 insertions(+), 28 deletions(-) create mode 100644 frontend/src/components/tests/SkeletonGroup.cy.tsx diff --git a/frontend/src/components/CompactGroup.tsx b/frontend/src/components/CompactGroup.tsx index 89c8fea..b855d79 100644 --- a/frontend/src/components/CompactGroup.tsx +++ b/frontend/src/components/CompactGroup.tsx @@ -7,7 +7,7 @@ interface Props { group: IGroup; width: string; height: string; - corners?: Array; + corners?: boolean[]; } const CompactGroupV1 = ({ diff --git a/frontend/src/components/SkeletonGroup.tsx b/frontend/src/components/SkeletonGroup.tsx index 963fa11..ad80716 100644 --- a/frontend/src/components/SkeletonGroup.tsx +++ b/frontend/src/components/SkeletonGroup.tsx @@ -1,25 +1,26 @@ -import { - Box, - Skeleton, - SkeletonCircle, - SkeletonText, - VStack, -} from "@chakra-ui/react"; +import { Box, Skeleton, SkeletonText, VStack } from "@chakra-ui/react"; import "../styles/CompactGroup.css"; interface Props { width: string; height: string; + corners?: boolean[]; // TL, TR, BR, BL } -const SkeletonGroup = ({ width, height }: Props) => { +const SkeletonGroup = ({ + width, + height, + corners = [false, false, false, false], +}: Props) => { return ( @@ -32,17 +33,7 @@ const SkeletonGroup = ({ width, height }: Props) => { spacing="4" skeletonHeight="2" /> - - - - + ); diff --git a/frontend/src/components/tests/PageSelector.cy.tsx b/frontend/src/components/tests/PageSelector.cy.tsx index 613ebe8..8b2596f 100644 --- a/frontend/src/components/tests/PageSelector.cy.tsx +++ b/frontend/src/components/tests/PageSelector.cy.tsx @@ -1,4 +1,3 @@ -import { useState } from "react"; import PageSelector from "../PageSelector"; describe("", () => { diff --git a/frontend/src/components/tests/SkeletonGroup.cy.tsx b/frontend/src/components/tests/SkeletonGroup.cy.tsx new file mode 100644 index 0000000..22aea24 --- /dev/null +++ b/frontend/src/components/tests/SkeletonGroup.cy.tsx @@ -0,0 +1,48 @@ +import { ChakraProvider } from "@chakra-ui/react"; +import SkeletonGroup from "../SkeletonGroup"; + +describe("", () => { + it("Renders with specific, equal w&h", () => { + cy.mount( + + + + ); + }); + + it("Renders with specific w>h ", () => { + cy.mount( + + + + ); + }); + + it("Renders with specific w { + cy.mount( + + + + ); + }); + + it("properly shrinks children for small w&h", () => { + cy.mount( + + + + ); + }); + + it("correctly computes corners", () => { + cy.mount( + + + + ); + }); +}); diff --git a/frontend/src/pages/MyGroupsPage.tsx b/frontend/src/pages/MyGroupsPage.tsx index 6030819..ea58a74 100644 --- a/frontend/src/pages/MyGroupsPage.tsx +++ b/frontend/src/pages/MyGroupsPage.tsx @@ -52,7 +52,7 @@ const GroupPage: React.FC = ({ const data = await res.json(); return data; } - }, + } ); const tempGroupList = await Promise.all(groupPromises); @@ -66,8 +66,8 @@ const GroupPage: React.FC = ({ const lowerQuery = input.toLowerCase(); setFilteredGroups( groupList.filter((group) => - group.groupName.toLowerCase().includes(lowerQuery), - ), + group.groupName.toLowerCase().includes(lowerQuery) + ) ); } }; @@ -170,10 +170,25 @@ const GroupPage: React.FC = ({ alignItems="center" > {loading ? ( - skelIds.map((id) => { + skelIds.map((id, ind) => { + const currentPage = Math.floor(ind / (gridDims[0] * gridDims[1])); + if (currentPage + 1 != selectedPage) return null; + const row = Math.floor( + (ind % (gridDims[1] * gridDims[0])) / gridDims[1] + ); + const col = ind % gridDims[1]; return ( - + ); }) @@ -182,7 +197,7 @@ const GroupPage: React.FC = ({ const currentPage = Math.floor(ind / (gridDims[0] * gridDims[1])); if (currentPage + 1 != selectedPage) return null; const row = Math.floor( - (ind % (gridDims[1] * gridDims[0])) / gridDims[1], + (ind % (gridDims[1] * gridDims[0])) / gridDims[1] ); const col = ind % gridDims[1]; return ( From 0b92a6f3d5d202349ee9285a03e68fc7ab8c710e Mon Sep 17 00:00:00 2001 From: jhcp Date: Mon, 27 May 2024 14:28:48 -0700 Subject: [PATCH 4/5] Search Bar testing --- frontend/src/components/SearchBar.tsx | 19 +++--- .../src/components/tests/SearchBar.cy.tsx | 58 +++++++++++++++++++ 2 files changed, 66 insertions(+), 11 deletions(-) create mode 100644 frontend/src/components/tests/SearchBar.cy.tsx diff --git a/frontend/src/components/SearchBar.tsx b/frontend/src/components/SearchBar.tsx index 5241563..6910b18 100644 --- a/frontend/src/components/SearchBar.tsx +++ b/frontend/src/components/SearchBar.tsx @@ -5,6 +5,7 @@ import { IoSearch } from "react-icons/io5"; interface Props { onSearch: (searchText: string) => void; placeholder: string; + searchAfterEvery?: boolean; style?: CSSProperties; width?: string; } @@ -12,27 +13,22 @@ interface Props { const SearchBar = ({ onSearch, placeholder, + searchAfterEvery = true, width = "auto", style = {}, }: Props) => { const ref = useRef(null); const [value, setValue] = useState(""); - // Debounce the search input - useEffect(() => { - const timer = setTimeout(() => { - onSearch(value); - }, 300); - - return () => clearTimeout(timer); - }, [value, onSearch]); - return ( - + setValue(e.target.value)} + onChange={(e) => { + setValue(e.target.value); + if (searchAfterEvery) onSearch(e.target.value); + }} placeholder={placeholder} _placeholder={{ fontStyle: "italic", @@ -53,6 +49,7 @@ const SearchBar = ({ as={IoSearch} color="var(--col-accent)" onClick={() => onSearch(ref.current ? ref.current.value : "")} + _hover={{ color: "var(--col-tertiary)" }} /> diff --git a/frontend/src/components/tests/SearchBar.cy.tsx b/frontend/src/components/tests/SearchBar.cy.tsx new file mode 100644 index 0000000..c0f6f9b --- /dev/null +++ b/frontend/src/components/tests/SearchBar.cy.tsx @@ -0,0 +1,58 @@ +import { ChakraProvider } from "@chakra-ui/react"; +import SearchBar from "../SearchBar"; + +describe("", () => { + //This is the intended behavior, since this is a filtering search bar, not one that retrieves data from an endpoint + it("Submits the search function after every keypress", () => { + const testdata = "This is input."; + cy.mount( + + console.log(inp)} /> + + ); + cy.get(".chakra-input").type(testdata); + }); + + //Visual test + it("uses provided styles", () => { + cy.mount( + + console.log(inp)} + style={{ + color: "rgba(255,0,0,1)", + }} + /> + + ); + }); + + it("Submits the search function only on icon", () => { + const testdata = "This is input."; + cy.mount( + + console.log(inp)} + searchAfterEvery={false} + /> + + ); + cy.get(".chakra-input").type(testdata); + cy.get(".chakra-input__right-element").click(); + }); + + it("correctly adjusts width", () => { + cy.mount( + + console.log(inp)} + searchAfterEvery={false} + width="50%" + /> + + ); + }); +}); From 51a621afa8a717f2a8d94f3c3e2d9b883a680e39 Mon Sep 17 00:00:00 2001 From: jhcp Date: Sun, 2 Jun 2024 19:00:26 -0700 Subject: [PATCH 5/5] removed unused useEffect --- frontend/src/components/SearchBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/SearchBar.tsx b/frontend/src/components/SearchBar.tsx index 6910b18..dd1bca7 100644 --- a/frontend/src/components/SearchBar.tsx +++ b/frontend/src/components/SearchBar.tsx @@ -1,5 +1,5 @@ import { Icon, Input, InputGroup, InputRightElement } from "@chakra-ui/react"; -import { CSSProperties, useRef, useState, useEffect } from "react"; +import { CSSProperties, useRef, useState } from "react"; import { IoSearch } from "react-icons/io5"; interface Props {