diff --git a/explorer/src/lib/components/__tests__/BlocksCard.spec.js b/explorer/src/lib/components/__tests__/BlocksCard.spec.js new file mode 100644 index 0000000000..e7ac63701c --- /dev/null +++ b/explorer/src/lib/components/__tests__/BlocksCard.spec.js @@ -0,0 +1,71 @@ +import { afterAll, afterEach, describe, expect, it, vi } from "vitest"; +import { cleanup, fireEvent, render } from "@testing-library/svelte"; +import { apiBlocks } from "$lib/mock-data"; +import { transformBlock } from "$lib/chain-info"; +import { BlocksCard } from ".."; +import { mapWith, slice } from "lamb"; + +const transformBlocks = mapWith(transformBlock); +const data = slice(transformBlocks(apiBlocks.data.blocks), 0, 10); + +describe("Blocks Card", () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(2024, 4, 20)); + const baseProps = { + blocks: data, + error: null, + loading: false, + }; + const baseOptions = { + props: baseProps, + target: document.body, + }; + + afterEach(cleanup); + + afterAll(() => { + vi.useRealTimers(); + }); + + it("should render the `BlocksCard` component", () => { + const { container } = render(BlocksCard, baseOptions); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it("should disable the `Show More` button is the card is in the loading state", () => { + const loading = true; + + const { container, getByRole } = render(BlocksCard, { + ...baseOptions, + props: { ...baseProps, loading }, + }); + + expect(getByRole("button")).toBeDisabled(); + + expect(container.firstChild).toMatchSnapshot(); + }); + + it("should disable the `Show More` button if there is no more data to display", async () => { + const loading = true; + + const { container, getByRole } = render(BlocksCard, { + ...baseOptions, + props: { ...baseProps, loading }, + }); + + const button = getByRole("button"); + + const showMoreIncrement = 15; + + const clicks = Math.ceil(data.length / showMoreIncrement) - 1; + + Array.from({ length: clicks }).forEach(async () => { + await fireEvent.click(button); + }); + + expect(button).toBeDisabled(); + + expect(container.firstChild).toMatchSnapshot(); + }); +}); diff --git a/explorer/src/lib/components/__tests__/DataCard.spec.js b/explorer/src/lib/components/__tests__/DataCard.spec.js index fc58764667..24957ef8ae 100644 --- a/explorer/src/lib/components/__tests__/DataCard.spec.js +++ b/explorer/src/lib/components/__tests__/DataCard.spec.js @@ -9,7 +9,7 @@ describe("DataCard", () => { const baseProps = { data: null, error: null, - headerButtonDetails: { action: () => {}, label: "Button" }, + headerButtonDetails: { action: () => {}, disabled: false, label: "Button" }, loading: false, title: "Title", }; @@ -73,7 +73,11 @@ describe("DataCard", () => { it("should pass the correct function to the button on click event", async () => { const onClickMock = vi.fn(); - const headerButtonDetails = { action: onClickMock, label: "Back" }; + const headerButtonDetails = { + action: onClickMock, + disabled: false, + label: "Back", + }; const { getByRole } = render(DataCard, { ...baseOptions, props: { ...baseProps, headerButtonDetails }, @@ -83,4 +87,19 @@ describe("DataCard", () => { expect(onClickMock).toHaveBeenCalledTimes(1); }); + + it("should render the `DataCard` with a disabled button", () => { + const headerButtonDetails = { + action: () => {}, + disabled: true, + label: "Back", + }; + const { container, getByRole } = render(DataCard, { + ...baseOptions, + props: { ...baseProps, headerButtonDetails }, + }); + + expect(getByRole("button")).toBeDisabled(); + expect(container.firstChild).toMatchSnapshot(); + }); }); diff --git a/explorer/src/lib/components/__tests__/__snapshots__/BlocksCard.spec.js.snap b/explorer/src/lib/components/__tests__/__snapshots__/BlocksCard.spec.js.snap new file mode 100644 index 0000000000..f946329c55 --- /dev/null +++ b/explorer/src/lib/components/__tests__/__snapshots__/BlocksCard.spec.js.snap @@ -0,0 +1,2407 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Blocks Card > should disable the \`Show More\` button if there is no more data to display 1`] = ` +
+
+

+ Blocks +

+ + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ # Block + + Fee + + Txn(s) + + Rewards +
+ + 487,491 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,490 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,489 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,488 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,487 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,486 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,485 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,484 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,483 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,482 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+
+ + + + + +
+`; + +exports[`Blocks Card > should disable the \`Show More\` button is the card is in the loading state 1`] = ` +
+
+

+ Blocks +

+ + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ # Block + + Fee + + Txn(s) + + Rewards +
+ + 487,491 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,490 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,489 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,488 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,487 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,486 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,485 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,484 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,483 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,482 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+
+ + + + + +
+`; + +exports[`Blocks Card > should render the \`BlocksCard\` component 1`] = ` +
+
+

+ Blocks +

+ + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ # Block + + Fee + + Txn(s) + + Rewards +
+ + 487,491 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,490 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,489 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,488 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,487 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,486 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,485 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,484 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,483 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+ + 487,482 + + + + + + last month + + + + AVG: + + + 0 +
+ + + TOTAL: + + + 0 +
+ 0 + + + 16 Dusk + + +
+
+ + + + + +
+`; diff --git a/explorer/src/lib/components/__tests__/__snapshots__/BlocksTable.spec.js.snap b/explorer/src/lib/components/__tests__/__snapshots__/BlocksTable.spec.js.snap index 3593a8fe51..8125f41345 100644 --- a/explorer/src/lib/components/__tests__/__snapshots__/BlocksTable.spec.js.snap +++ b/explorer/src/lib/components/__tests__/__snapshots__/BlocksTable.spec.js.snap @@ -2,7 +2,7 @@ exports[`Blocks Table > should render the \`BlocksTable\` component 1`] = `
should render the \`DataCard\` in the no data state 1`] = ` + +`; + +exports[`DataCard > should render the \`DataCard\` with a disabled button 1`] = ` +
+
+

+ Title +

+ + + +
+ + +
`; diff --git a/explorer/src/lib/components/__tests__/__snapshots__/TransactionsTable.spec.js.snap b/explorer/src/lib/components/__tests__/__snapshots__/TransactionsTable.spec.js.snap index 5cd704194c..3375b4f776 100644 --- a/explorer/src/lib/components/__tests__/__snapshots__/TransactionsTable.spec.js.snap +++ b/explorer/src/lib/components/__tests__/__snapshots__/TransactionsTable.spec.js.snap @@ -2,7 +2,7 @@ exports[`Transactions Table > should render the \`TransactionsTable\` component 1`] = `
history.back(), label: "Back" }} + headerButtonDetails={{ + action: () => history.back(), + disabled: false, + label: "Back", + }} >
diff --git a/explorer/src/lib/components/blocks-card/BlocksCard.css b/explorer/src/lib/components/blocks-card/BlocksCard.css new file mode 100644 index 0000000000..a97dce8cea --- /dev/null +++ b/explorer/src/lib/components/blocks-card/BlocksCard.css @@ -0,0 +1,5 @@ +.blocks-card__table, +.blocks-card__list { + max-height: 70dvh; + overflow-y: auto; +} diff --git a/explorer/src/lib/components/blocks-card/BlocksCard.svelte b/explorer/src/lib/components/blocks-card/BlocksCard.svelte index dd814ec18a..52cc38ae54 100644 --- a/explorer/src/lib/components/blocks-card/BlocksCard.svelte +++ b/explorer/src/lib/components/blocks-card/BlocksCard.svelte @@ -2,13 +2,10 @@ @@ -29,15 +51,20 @@ data={blocks} {error} {loading} - className={classes} title="Blocks" - headerButtonDetails={{ action: () => goto("/blocks"), label: "All Blocks" }} + headerButtonDetails={{ + action: () => loadMoreItems(), + disabled: isLoadMoreDisabled, + label: "Show More", + }} > {#if clientWidth > 768} - + {:else} - {#each blocks as block (block)} - - {/each} +
+ {#each displayedBlocks as block (block)} + + {/each} +
{/if} diff --git a/explorer/src/lib/components/blocks-table/BlocksTable.svelte b/explorer/src/lib/components/blocks-table/BlocksTable.svelte index b7fd2a7f07..304091bcb1 100644 --- a/explorer/src/lib/components/blocks-table/BlocksTable.svelte +++ b/explorer/src/lib/components/blocks-table/BlocksTable.svelte @@ -11,18 +11,23 @@ } from "$lib/components/table"; import { Badge } from "$lib/dusk/components"; import { luxToDusk } from "$lib/dusk/currency"; - import { getRelativeTimeString } from "$lib/dusk/string"; + import { getRelativeTimeString, makeClassName } from "$lib/dusk/string"; import { createValueFormatter } from "$lib/dusk/value"; import "./BlocksTable.css"; - /** @type {Block[]}*/ + /** @type {string | undefined} */ + export let className = undefined; + + /** @type {Block[]} */ export let data; const numberFormatter = createValueFormatter("en"); + + $: classes = makeClassName(["blocks-table", className]); -
+
# Block diff --git a/explorer/src/lib/components/data-card/DataCard.svelte b/explorer/src/lib/components/data-card/DataCard.svelte index aa01984942..d6e0664b7c 100644 --- a/explorer/src/lib/components/data-card/DataCard.svelte +++ b/explorer/src/lib/components/data-card/DataCard.svelte @@ -19,7 +19,7 @@ /** @type {String}*/ export let title; - /** @type {{action:(e: MouseEvent) => void, label: String}}*/ + /** @type {{action:(e: MouseEvent) => void, disabled:boolean, label: String}}*/ export let headerButtonDetails; /** @type {string | Undefined} */ @@ -37,6 +37,7 @@ on:click={headerButtonDetails.action} text={headerButtonDetails.label} variant="secondary" + disabled={headerButtonDetails.disabled} /> {#if loading && data === null} diff --git a/explorer/src/lib/components/index.js b/explorer/src/lib/components/index.js index 0a46fc3e92..a32ac63af4 100644 --- a/explorer/src/lib/components/index.js +++ b/explorer/src/lib/components/index.js @@ -9,12 +9,13 @@ export { default as DataCard } from "./data-card/DataCard.svelte"; export { default as DataGuard } from "./data-guard/DataGuard.svelte"; export { default as DetailList } from "./detail-list/DetailList.svelte"; export { default as Footer } from "./footer/Footer.svelte"; +export { default as LatestBlocksCard } from "./latest-blocks-card/LatestBlocksCard.svelte"; +export { default as LatestTransactionsCard } from "./latest-transactions-card/LatestTransactionsCard.svelte"; export { default as ListItem } from "./list-item/ListItem.svelte"; export { default as Navbar } from "./navbar/Navbar.svelte"; export { default as SearchNotification } from "./search-notification/SearchNotification.svelte"; export { default as TextboxAndButton } from "./textbox-and-button/TextboxAndButton.svelte"; export { default as TransactionDetails } from "./transaction-details/TransactionDetails.svelte"; -export { default as TransactionsCard } from "./transactions-card/TransactionsCard.svelte"; export { default as TransactionsList } from "./transactions-list/TransactionsList.svelte"; export { default as TransactionsTable } from "./transactions-table/TransactionsTable.svelte"; export { default as WorldMap } from "./world-map/WorldMap.svelte"; diff --git a/explorer/src/lib/components/latest-blocks-card/LatestBlocksCard.svelte b/explorer/src/lib/components/latest-blocks-card/LatestBlocksCard.svelte new file mode 100644 index 0000000000..dad50c377e --- /dev/null +++ b/explorer/src/lib/components/latest-blocks-card/LatestBlocksCard.svelte @@ -0,0 +1,47 @@ + + + + + + goto("/blocks"), + disabled: false, + label: "All Blocks", + }} +> + {#if clientWidth > 768} + + {:else} + {#each blocks as block (block)} + + {/each} + {/if} + diff --git a/explorer/src/lib/components/transactions-card/TransactionsCard.svelte b/explorer/src/lib/components/latest-transactions-card/LatestTransactionsCard.svelte similarity index 91% rename from explorer/src/lib/components/transactions-card/TransactionsCard.svelte rename to explorer/src/lib/components/latest-transactions-card/LatestTransactionsCard.svelte index dad040ba35..851c906c99 100644 --- a/explorer/src/lib/components/transactions-card/TransactionsCard.svelte +++ b/explorer/src/lib/components/latest-transactions-card/LatestTransactionsCard.svelte @@ -24,7 +24,7 @@ /** @type {number} */ let clientWidth; - $: classes = makeClassName(["transactions-card", className]); + $: classes = makeClassName(["latest-transactions-card", className]); @@ -37,6 +37,7 @@ title="Transactions" headerButtonDetails={{ action: () => goto("/transactions"), + disabled: false, label: "All Transactions", }} > diff --git a/explorer/src/lib/components/table/Table.svelte b/explorer/src/lib/components/table/Table.svelte index 16e8d1ed25..27ab2b942c 100644 --- a/explorer/src/lib/components/table/Table.svelte +++ b/explorer/src/lib/components/table/Table.svelte @@ -1,8 +1,14 @@ -
+
diff --git a/explorer/src/lib/components/transaction-details/TransactionDetails.svelte b/explorer/src/lib/components/transaction-details/TransactionDetails.svelte index c9309a678c..16c56c79da 100644 --- a/explorer/src/lib/components/transaction-details/TransactionDetails.svelte +++ b/explorer/src/lib/components/transaction-details/TransactionDetails.svelte @@ -62,7 +62,11 @@ {loading} className={classes} title="Transaction Details" - headerButtonDetails={{ action: () => history.back(), label: "Back" }} + headerButtonDetails={{ + action: () => history.back(), + disabled: false, + label: "Back", + }} >
diff --git a/explorer/src/lib/components/transactions-table/TransactionsTable.svelte b/explorer/src/lib/components/transactions-table/TransactionsTable.svelte index 0f0cdc0921..a3939101b7 100644 --- a/explorer/src/lib/components/transactions-table/TransactionsTable.svelte +++ b/explorer/src/lib/components/transactions-table/TransactionsTable.svelte @@ -12,18 +12,27 @@ import { Badge } from "$lib/dusk/components"; import { luxToDusk } from "$lib/dusk/currency"; import { createValueFormatter } from "$lib/dusk/value"; - import { getRelativeTimeString, middleEllipsis } from "$lib/dusk/string"; + import { + getRelativeTimeString, + makeClassName, + middleEllipsis, + } from "$lib/dusk/string"; import "./TransactionsTable.css"; + /** @type {string | Undefined} */ + export let className = undefined; + /** @type {Transaction[]}*/ export let data; const HASH_CHARS_LENGTH = 10; const numberFormatter = createValueFormatter("en"); + + $: classes = makeClassName(["transactions-table", className]); - +
Hash diff --git a/explorer/src/routes/+page.svelte b/explorer/src/routes/+page.svelte index 662df19723..c9834ee1b6 100644 --- a/explorer/src/routes/+page.svelte +++ b/explorer/src/routes/+page.svelte @@ -1,5 +1,5 @@ +
-
All Blocks
+
diff --git a/explorer/src/routes/blocks/__tests__/__snapshots__/page.spec.js.snap b/explorer/src/routes/blocks/__tests__/__snapshots__/page.spec.js.snap deleted file mode 100644 index 7f99a0126d..0000000000 --- a/explorer/src/routes/blocks/__tests__/__snapshots__/page.spec.js.snap +++ /dev/null @@ -1,14 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`Blocks > should render the Blocks page 1`] = ` -
-
-
- All Blocks -
-
- -
-`; diff --git a/explorer/src/routes/blocks/__tests__/page.spec.js b/explorer/src/routes/blocks/__tests__/page.spec.js deleted file mode 100644 index a3cee0b0ec..0000000000 --- a/explorer/src/routes/blocks/__tests__/page.spec.js +++ /dev/null @@ -1,11 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { render } from "@testing-library/svelte"; -import Blocks from "../+page.svelte"; - -describe("Blocks", () => { - it("should render the Blocks page", () => { - const { container } = render(Blocks, {}); - - expect(container.firstChild).toMatchSnapshot(); - }); -}); diff --git a/explorer/src/routes/blocks/block/+page.svelte b/explorer/src/routes/blocks/block/+page.svelte index 0def38bffb..f1a3b9751d 100644 --- a/explorer/src/routes/blocks/block/+page.svelte +++ b/explorer/src/routes/blocks/block/+page.svelte @@ -1,6 +1,6 @@