diff --git a/src/cryptometrics/components/button/Button.js b/src/cryptometrics/components/button/Button.js index 882cc5c4019f09da72169abd407750ea76f9cd2a..dea06077e09cff975ec0d4979fbab4683de11f7c 100644 --- a/src/cryptometrics/components/button/Button.js +++ b/src/cryptometrics/components/button/Button.js @@ -13,7 +13,7 @@ import classNames from "classnames"; * </Button> * ) */ -function Button({ children, className, onClick, type, disabled }) { +function Button({ children, className, onClick, type, disabled, id }) { return ( <button className={classNames("py-2 px-5 rounded-xl", className, { @@ -21,6 +21,7 @@ function Button({ children, className, onClick, type, disabled }) { })} onClick={disabled ? undefined : onClick} type={type ? type : "button"} + id={id ? id : undefined} > {children} </button> diff --git a/src/cryptometrics/components/button/FilterButton.js b/src/cryptometrics/components/button/FilterButton.js index e00728230ccd4ce5984d569744fd6de6def34070..5885e3356fa8edd280d73b82e5f8d93f8b05f6b1 100644 --- a/src/cryptometrics/components/button/FilterButton.js +++ b/src/cryptometrics/components/button/FilterButton.js @@ -6,6 +6,7 @@ export default function FilterButton({ icon, onClick }) { <Button className="mx-1 mt-2 font-sans font-normal bg-dark-600 rounded-lg dark:text-neutral-200 dark:hover:text-indigo-600 inline-flex h-10 w-10 justify-center items-center whitespace-nowrap" onClick={onClick} + id="filter-button" > <span className="text-white">{icon}</span> </Button> diff --git a/src/cryptometrics/components/cards/CryptoChartCard.js b/src/cryptometrics/components/cards/CryptoChartCard.js index 239b6046379be759f38a32a4c15315528cea2995..791bfe01ed568cc58fc2cc4f1c18cbc0fb8b6b78 100644 --- a/src/cryptometrics/components/cards/CryptoChartCard.js +++ b/src/cryptometrics/components/cards/CryptoChartCard.js @@ -62,12 +62,18 @@ function CryptoChartCard({ ></Image> </div> <div> - <p className="text-3xl font-poppins font-bold text-gray-700 dark:text-gray-100"> + <p + className="text-3xl font-poppins font-bold text-gray-700 dark:text-gray-100" + id="currency-name" + > {currencyName || ( <PlaceholderSkeleton className="h-9 w-[60%]" /> )} </p> - <p className="text-lg font-poppins font-medium text-gray-700 dark:text-gray-100"> + <p + className="text-lg font-poppins font-medium text-gray-700 dark:text-gray-100" + id="currency-symbol" + > {symbol} </p> </div> @@ -83,7 +89,10 @@ function CryptoChartCard({ <div className="flex flex-col h-[9.5rem]"> <div className="mt-auto"> <div className="flex justify-end"> - <p className="text-4xl font-poppins font-bold text-black dark:text-white"> + <p + className="text-4xl font-poppins font-bold text-black dark:text-white" + id="currency-price" + > {info || ( <PlaceholderSkeleton className="h-9 w-[200px]" /> )} @@ -95,6 +104,7 @@ function CryptoChartCard({ "text-lg font-poppins font-bold", detailColor )} + id="currency-price-change-percentage" > {detail || <PlaceholderSkeleton className="h-4 w-20" />} </p> diff --git a/src/cryptometrics/components/dropdown/FilterDropdownItem.js b/src/cryptometrics/components/dropdown/FilterDropdownItem.js index d9c4730355e5c08e1238bac7566dc9d7861c2a9d..a01f01c295e1c082a6fe22737c45a368c9aff59e 100644 --- a/src/cryptometrics/components/dropdown/FilterDropdownItem.js +++ b/src/cryptometrics/components/dropdown/FilterDropdownItem.js @@ -21,6 +21,7 @@ function FilterDropdownItem({ } )} onClick={disabled || !onClick ? undefined : () => onClick(id)} + id={`${id}-option`} > <div className="flex flex-row items-center"> {children} diff --git a/src/cryptometrics/components/inputs/Input.js b/src/cryptometrics/components/inputs/Input.js index a0794712f69530acd726b49dcc8c7e912b9b51ef..029202bdc931ae0cba7a81489e59c83a245c6fb9 100644 --- a/src/cryptometrics/components/inputs/Input.js +++ b/src/cryptometrics/components/inputs/Input.js @@ -9,6 +9,7 @@ function Input({ initialValue, symbolLeft, symbolRight, + id, }) { return ( <span className="relative flex w-full flex-wrap items-stretch mb-3"> @@ -34,6 +35,7 @@ function Input({ type={type ? type : "text"} onChange={onChange} defaultValue={initialValue || ""} + id={id ? id : undefined} ></input> {symbolRight && ( <span className="absolute right-3 w-5 text-center items-center justify-center h-full z-10 py-3"> diff --git a/src/cryptometrics/components/radio/RadioForm.js b/src/cryptometrics/components/radio/RadioForm.js index a6ab2043ccee0df40dbcf8ff65bc84e6611aee50..580bb198600b1e39bfde2d68c9b92427b74861f5 100644 --- a/src/cryptometrics/components/radio/RadioForm.js +++ b/src/cryptometrics/components/radio/RadioForm.js @@ -35,6 +35,7 @@ function RadioInputForm({ symbolLeft={inputLeftSymbol} symbolRight={inputRightSymbol} type={inputType} + id="radio-form-input" /> )} </div> diff --git a/src/cryptometrics/cypress/integration/home/filter.spec.js b/src/cryptometrics/cypress/integration/home/filter.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..b1740b1d432fb31b2c806219a38471c015f3f95a --- /dev/null +++ b/src/cryptometrics/cypress/integration/home/filter.spec.js @@ -0,0 +1,58 @@ +/// <reference types="cypress" /> + +// FT-HP-4 +describe("filter on home page", () => { + beforeEach(() => { + cy.visit("localhost:3000"); + }); + + it("filter price less than value", () => { + const filterButton = "#filter-button"; + const priceOption = "#current_price-option"; + cy.get(filterButton).should("be.visible"); + cy.get(filterButton).click(); + cy.get(priceOption).click(); + cy.get("span").contains("is less than").click(); + cy.get("#radio-form-input").type(100); + cy.get("button").contains("Filter").click(); + + cy.get("p#currency-price").each((price) => { + cy.wrap(price) + .invoke("text") + .then((price) => { + return price.replace("$", "").replace(",", ""); + }) + .then(parseFloat) + .should("be.lessThan", 100); + }); + }); + + it("filter price greater than value", () => { + const filterButton = "#filter-button"; + const priceOption = "#current_price-option"; + cy.get(filterButton).should("be.visible"); + cy.get(filterButton).click(); + cy.get(priceOption).click(); + cy.get("span").contains("is greater than").click(); + cy.get("#radio-form-input").type(100); + cy.get("button").contains("Filter").click(); + + cy.get("p#currency-price").each((price) => { + cy.wrap(price) + .invoke("text") + .then((price) => { + return price.replace("$", "").replace(",", ""); + }) + .then(parseFloat) + .should("be.greaterThan", 100); + }); + }); + + // it("search for invalid cryptocurrencies", () => { + // const searchInput = "[placeholder='Search']"; + // const cryptocurrencyName = "abcd123"; + // cy.get(searchInput).should("be.visible"); + // cy.get(searchInput).type(`${cryptocurrencyName}`); + // cy.get("body").should("contain", "No matching cryptocurrencies found."); + // }); +}); diff --git a/src/cryptometrics/cypress/integration/home/search.spec.js b/src/cryptometrics/cypress/integration/home/search.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..ae69e620f0c3ce1e512337cf783879e1e7ab5196 --- /dev/null +++ b/src/cryptometrics/cypress/integration/home/search.spec.js @@ -0,0 +1,44 @@ +/// <reference types="cypress" /> + +describe("search on home page", () => { + beforeEach(() => { + cy.visit("localhost:3000"); + }); + // FT-HP-2 + it("search for valid cryptocurrencies", () => { + const searchInput = "[placeholder='Search']"; + const cryptocurrencyName = "bitcoin"; + cy.get(searchInput).should("be.visible"); + cy.get(searchInput).type(`${cryptocurrencyName}`); + + // card-view + cy.get("button#card-view").click(); + cy.get("p#currency-name").each((name) => { + cy.wrap(name).should("contain", "Bitcoin"); + cy.wrap(name).should("not.contain", "Ethereum"); + }); + + // table-view + cy.get("button#list-view").click(); + cy.get("p#currency-name").each((name) => { + cy.wrap(name).should("contain", "Bitcoin"); + cy.wrap(name).should("not.contain", "Ethereum"); + }); + }); + + // FT-HP-3 + it("search for invalid cryptocurrencies", () => { + const searchInput = "[placeholder='Search']"; + const cryptocurrencyName = "abcd123"; + cy.get(searchInput).should("be.visible"); + cy.get(searchInput).type(`${cryptocurrencyName}`); + + // card-view + cy.get("button#card-view").click(); + cy.get("body").should("contain", "No matching cryptocurrencies found."); + + // table-view + cy.get("button#list-view").click(); + cy.get("body").should("contain", "No matching cryptocurrencies found."); + }); +}); diff --git a/src/cryptometrics/cypress/integration/home/table.spec.js b/src/cryptometrics/cypress/integration/home/table.spec.js index 635f99662ae294621df66e3fd3ce06b9bde7c3b0..9b72364b377afa66f26ba5866fc2f2abdc6fce0d 100644 --- a/src/cryptometrics/cypress/integration/home/table.spec.js +++ b/src/cryptometrics/cypress/integration/home/table.spec.js @@ -4,7 +4,7 @@ describe("table on home page", () => { beforeEach(() => { cy.visit("localhost:3000"); }); - + // FT-HP-1 it("displays table", () => { cy.get("button#list-view").should("be.visible"); cy.get("button#list-view").click(); diff --git a/src/cryptometrics/pages/index.js b/src/cryptometrics/pages/index.js index 94c9839849e423ca283df008c3fa327581ac1651..ebb795969a200dcb79b4ddf03fb89cf07954d325 100644 --- a/src/cryptometrics/pages/index.js +++ b/src/cryptometrics/pages/index.js @@ -126,7 +126,7 @@ export default function Home() { content={<CollectionIcon className="w-6 h-6 dark:text-white" />} > <div className="flex flex-wrap gap-x-10 gap-y-10 mt-3"> - {!listOfCoins.isLoading && + {!listOfCoins.isLoading && filteredCoins.length > 0 ? ( filteredCoins.map((coin) => { return ( <CryptoChartCard @@ -158,143 +158,166 @@ export default function Home() { type="area" /> ); - })} + }) + ) : ( + <div className="text-2xl dark:text-white"> + No matching cryptocurrencies found. + </div> + )} </div> </Tab> <Tab id="list-view" content={<TableIcon className="w-6 h-6 dark:text-white" />} > - <Table> - {/* Table Header */} - <TableHeader className="h-14 items-center sticky top-0 z-40"> - <TableCell className="w-6 h-10 text-center">Icon</TableCell> - <TableCell className="w-24 h-10"> - <p className="font-medium text-base text-center">Name</p> - </TableCell> - <TableCell className="w-24 h-10"> - <p className="font-medium text-base text-center">Price</p> - </TableCell> - <TableCell className="w-[10%] h-10"> - <p className="font-medium text-base text-center"> - Market Cap - </p> - </TableCell> - <TableCell className="w-40 h-10"> - <p className="font-medium text-base text-center"> - 24h change in % - </p> - </TableCell> - <TableCell className="w-40 h-10"> - <p className="font-medium text-base text-center"> - 24h change in $ - </p> - </TableCell> - <TableCell className="h-10"> - <div className="w-52"> + {filteredCoins?.length > 0 ? ( + <Table> + {/* Table Header */} + <TableHeader className="h-14 items-center sticky top-0 z-40"> + <TableCell className="w-6 h-10 text-center"> + Icon + </TableCell> + <TableCell className="w-24 h-10"> <p className="font-medium text-base text-center"> - Chart + Name </p> - </div> - </TableCell> - </TableHeader> - - {/* Table Content */} - {!listOfCoins.isLoading && - filteredCoins.map((coin, index) => { - return ( - <Link - key={"table_row_" + index} - href={`/coins/${coin.id}`} - passHref - > - <a> - <TableRow> - <TableCell className="w-6"> - <Image - src={coin.image} - width="32px" - height="32px" - alt={`${coin.id}_icon`} - layout="fixed" - ></Image> - </TableCell> - <TableCell className="w-24"> - <p className="font-medium text-base text-center"> - {coin.name} - </p> - <p className="font-light text-gray-300 text-sm mt-1"> - {coin.symbol.toUpperCase()} - </p> - </TableCell> - <TableCell className="w-24"> - <p className="font-light text-base text-blue-500 mt-1"> - $ - {numeral(coin.current_price).format( - "0,0.[0000000]" - )} - </p> - </TableCell> - <TableCell className="w-[10%]"> - <p className="font-light text-base text-pink-400 mt-1"> - $ - {numeral(coin.market_cap).format( - "0,0.[000]a" - )} - </p> - </TableCell> - <TableCell className="w-40"> - <p - className={classNames( - "font-light text-base mt-1", - { - "text-red-400": - coin.price_change_percentage_24h < 0, - "text-green-400": - coin.price_change_percentage_24h >= 0, - } - )} - > - {numeral( - coin.price_change_percentage_24h - ).format("+0.0[00]")} - % - </p> - </TableCell> - <TableCell className="w-40"> - <p - className={classNames( - "font-light text-base mt-1", - { - "text-red-400": coin.price_change_24h < 0, - "text-green-400": - coin.price_change_24h >= 0, - } - )} - > - {numeral(coin.price_change_24h).format( - "$0,0.[0000]" - )} - </p> - </TableCell> - <TableCell> - <div className="h-[50px] w-52"> - <CryptoRowLineChart - currencyId={coin.id} - color={ - coin.price_change_24h >= 0 - ? "#10B981" - : "#EF4444" - } - /> - </div> - </TableCell> - </TableRow> - </a> - </Link> - ); - })} - </Table> + </TableCell> + <TableCell className="w-24 h-10"> + <p className="font-medium text-base text-center"> + Price + </p> + </TableCell> + <TableCell className="w-[10%] h-10"> + <p className="font-medium text-base text-center"> + Market Cap + </p> + </TableCell> + <TableCell className="w-40 h-10"> + <p className="font-medium text-base text-center"> + 24h change in % + </p> + </TableCell> + <TableCell className="w-40 h-10"> + <p className="font-medium text-base text-center"> + 24h change in $ + </p> + </TableCell> + <TableCell className="h-10"> + <div className="w-52"> + <p className="font-medium text-base text-center"> + Chart + </p> + </div> + </TableCell> + </TableHeader> + {/* Table Content */} + {!listOfCoins.isLoading && + filteredCoins.map((coin, index) => { + return ( + <Link + key={"table_row_" + index} + href={`/coins/${coin.id}`} + passHref + > + <a> + <TableRow> + <TableCell className="w-6"> + <Image + src={coin.image} + width="32px" + height="32px" + alt={`${coin.id}_icon`} + layout="fixed" + ></Image> + </TableCell> + <TableCell className="w-24"> + <p + className="font-medium text-base text-center" + id="currency-name" + > + {coin.name} + </p> + <p + className="font-light text-gray-300 text-sm mt-1" + id="currency-symbol" + > + {coin.symbol.toUpperCase()} + </p> + </TableCell> + <TableCell className="w-24"> + <p className="font-light text-base text-blue-500 mt-1"> + $ + {numeral(coin.current_price).format( + "0,0.[0000000]" + )} + </p> + </TableCell> + <TableCell className="w-[10%]"> + <p className="font-light text-base text-pink-400 mt-1"> + $ + {numeral(coin.market_cap).format( + "0,0.[000]a" + )} + </p> + </TableCell> + <TableCell className="w-40"> + <p + className={classNames( + "font-light text-base mt-1", + { + "text-red-400": + coin.price_change_percentage_24h < 0, + "text-green-400": + coin.price_change_percentage_24h >= 0, + } + )} + > + {numeral( + coin.price_change_percentage_24h + ).format("+0.0[00]")} + % + </p> + </TableCell> + <TableCell className="w-40"> + <p + className={classNames( + "font-light text-base mt-1", + { + "text-red-400": + coin.price_change_24h < 0, + "text-green-400": + coin.price_change_24h >= 0, + } + )} + > + {numeral(coin.price_change_24h).format( + "$0,0.[0000]" + )} + </p> + </TableCell> + <TableCell> + <div className="h-[50px] w-52"> + <CryptoRowLineChart + currencyId={coin.id} + color={ + coin.price_change_24h >= 0 + ? "#10B981" + : "#EF4444" + } + /> + </div> + </TableCell> + </TableRow> + </a> + </Link> + ); + })} + </Table> + ) : ( + <div className="text-2xl mt-3 dark:text-white"> + No matching cryptocurrencies found. + </div> + )} </Tab> </Tabs> </Container>