diff --git a/explorer/src/lib/containers/__tests__/StatisticsPanel.spec.js b/explorer/src/lib/containers/__tests__/StatisticsPanel.spec.js
new file mode 100644
index 0000000000..a87e5dc4bd
--- /dev/null
+++ b/explorer/src/lib/containers/__tests__/StatisticsPanel.spec.js
@@ -0,0 +1,75 @@
+import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
+import { cleanup, render } from "@testing-library/svelte";
+import { get } from "svelte/store";
+
+import { duskAPI } from "$lib/services";
+import { appStore } from "$lib/stores";
+import { apiMarketData, apiNodeLocations, apiStats } from "$lib/mock-data";
+
+import { StatisticsPanel } from "..";
+
+describe("StatisticsPanel", () => {
+ vi.useFakeTimers();
+
+ const STATS_FETCH_INTERVAL = 5000;
+ const { network } = get(appStore);
+ const getMarketDataSpy = vi
+ .spyOn(duskAPI, "getMarketData")
+ .mockResolvedValue({
+ currentPrice: apiMarketData.market_data.current_price,
+ marketCap: apiMarketData.market_data.market_cap,
+ });
+ const getNodeLocationsSpy = vi
+ .spyOn(duskAPI, "getNodeLocations")
+ .mockResolvedValue(apiNodeLocations.data);
+ const getStatsSpy = vi.spyOn(duskAPI, "getStats").mockResolvedValue(apiStats);
+
+ afterEach(() => {
+ cleanup();
+ getMarketDataSpy.mockClear();
+ getNodeLocationsSpy.mockClear();
+ getStatsSpy.mockClear();
+ });
+
+ afterAll(() => {
+ vi.useRealTimers();
+ getMarketDataSpy.mockRestore();
+ getNodeLocationsSpy.mockRestore();
+ getStatsSpy.mockRestore();
+ });
+
+ it("should render the StatisticsPanel, query for the necessary info, start polling for stats and stop the polling when unmounted", async () => {
+ const { container, unmount } = render(StatisticsPanel);
+
+ expect(container.firstChild).toMatchSnapshot();
+ expect(getMarketDataSpy).toHaveBeenCalledTimes(1);
+ expect(getMarketDataSpy).toHaveBeenNthCalledWith(1, network);
+ expect(getNodeLocationsSpy).toHaveBeenCalledTimes(1);
+ expect(getNodeLocationsSpy).toHaveBeenNthCalledWith(1, network);
+ expect(getStatsSpy).toHaveBeenCalledTimes(1);
+ expect(getStatsSpy).toHaveBeenNthCalledWith(1, network);
+
+ await vi.advanceTimersByTimeAsync(1);
+
+ // snapshot with received data from APIs
+ expect(container.firstChild).toMatchSnapshot();
+
+ await vi.advanceTimersByTimeAsync(STATS_FETCH_INTERVAL - 1);
+
+ expect(getStatsSpy).toHaveBeenCalledTimes(2);
+ expect(getStatsSpy).toHaveBeenNthCalledWith(2, network);
+
+ await vi.advanceTimersByTimeAsync(STATS_FETCH_INTERVAL);
+
+ expect(getStatsSpy).toHaveBeenCalledTimes(3);
+ expect(getStatsSpy).toHaveBeenNthCalledWith(3, network);
+
+ unmount();
+
+ await vi.advanceTimersByTimeAsync(STATS_FETCH_INTERVAL * 10);
+
+ expect(getStatsSpy).toHaveBeenCalledTimes(3);
+ expect(getNodeLocationsSpy).toHaveBeenCalledTimes(1);
+ expect(getMarketDataSpy).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/explorer/src/lib/containers/__tests__/__snapshots__/StatisticsPanel.spec.js.snap b/explorer/src/lib/containers/__tests__/__snapshots__/StatisticsPanel.spec.js.snap
new file mode 100644
index 0000000000..1190179973
--- /dev/null
+++ b/explorer/src/lib/containers/__tests__/__snapshots__/StatisticsPanel.spec.js.snap
@@ -0,0 +1,2290 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`StatisticsPanel > should render the StatisticsPanel, query for the necessary info, start polling for stats and stop the polling when unmounted 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ Dusk Price
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ Total Market Cap
+
+
+
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ Current Staked Amount
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ Next Epoch Staked Amount
+
+
+
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ Last Block
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ TX Last 100 Blocks
+
+
+
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ Provisioners
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ Next Epoch Provisioners
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`StatisticsPanel > should render the StatisticsPanel, query for the necessary info, start polling for stats and stop the polling when unmounted 2`] = `
+
+
+
+
+
+
+
+
+
+
+ 0.368
+
+
+
+
+
+ Dusk Price
+
+
+
+
+
+
+
+
+ 168M
+
+
+
+
+
+ Total Market Cap
+
+
+
+
+
+
+
+
+
+
+
+ 58.2M
+
+
+
+
+
+ Current Staked Amount
+
+
+
+
+
+
+
+
+ 2.6M
+
+
+
+
+
+ Next Epoch Staked Amount
+
+
+
+
+
+
+
+
+
+
+
+ 487,596
+
+
+
+
+
+ Last Block
+
+
+
+
+
+
+
+
+ 10
+
+
+
+
+
+ TX Last 100 Blocks
+
+
+
+
+
+
+
+
+
+
+
+ 945
+
+
+
+
+
+ Provisioners
+
+
+
+
+
+
+
+
+ 34
+
+
+
+
+
+ Next Epoch Provisioners
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/explorer/src/lib/containers/statistics-panel/StatisticsPanel.svelte b/explorer/src/lib/containers/statistics-panel/StatisticsPanel.svelte
index 8dc6a4441f..fa212eac88 100644
--- a/explorer/src/lib/containers/statistics-panel/StatisticsPanel.svelte
+++ b/explorer/src/lib/containers/statistics-panel/StatisticsPanel.svelte
@@ -7,6 +7,8 @@
mdiCurrencyUsd,
mdiSwapVertical,
} from "@mdi/js";
+ import { onDestroy, onMount } from "svelte";
+
import { createCurrencyFormatter, luxToDusk } from "$lib/dusk/currency";
import { createCompactFormatter } from "$lib/dusk/value";
import { duskIcon } from "$lib/dusk/icons";
@@ -20,7 +22,6 @@
} from "$lib/dusk/svelte-stores";
import { onNetworkChange } from "$lib/lifecyles";
import "./StatisticsPanel.css";
- import { onMount } from "svelte";
const valueFormatter = createCurrencyFormatter("en", "DUSK", 0);
const millionFormatter = createCompactFormatter("en");
@@ -53,6 +54,7 @@
onNetworkChange(pollingStatsDataStore.start);
onNetworkChange(getNodeLocations);
onNetworkChange(getMarketData);
+ onDestroy(pollingStatsDataStore.stop);
$: ({ data } = $nodeLocationsStore);
diff --git a/explorer/src/routes/+page.svelte b/explorer/src/routes/+page.svelte
index c9834ee1b6..fd9ea06f87 100644
--- a/explorer/src/routes/+page.svelte
+++ b/explorer/src/routes/+page.svelte
@@ -1,4 +1,6 @@
diff --git a/explorer/src/routes/__tests__/__snapshots__/page.spec.js.snap b/explorer/src/routes/__tests__/__snapshots__/page.spec.js.snap
new file mode 100644
index 0000000000..4ed04fc62e
--- /dev/null
+++ b/explorer/src/routes/__tests__/__snapshots__/page.spec.js.snap
@@ -0,0 +1,1093 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`home page > should render the home page, start polling for the latest chain info and stop the polling when the component is destroyed 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ Dusk Price
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ Total Market Cap
+
+
+
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ Current Staked Amount
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ Next Epoch Staked Amount
+
+
+
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ Last Block
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ TX Last 100 Blocks
+
+
+
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ Provisioners
+
+
+
+
+
+
+
+
+
+ - - -
+
+
+
+
+
+
+ Next Epoch Provisioners
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Blocks
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Transactions
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/explorer/src/routes/__tests__/page.spec.js b/explorer/src/routes/__tests__/page.spec.js
new file mode 100644
index 0000000000..c03f3e97c7
--- /dev/null
+++ b/explorer/src/routes/__tests__/page.spec.js
@@ -0,0 +1,57 @@
+import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
+import { cleanup, render } from "@testing-library/svelte";
+import { get } from "svelte/store";
+
+import { duskAPI } from "$lib/services";
+import { transformBlock, transformTransaction } from "$lib/chain-info";
+import { apiLatestChainInfo } from "$lib/mock-data";
+import { appStore } from "$lib/stores";
+
+import HomePage from "../+page.svelte";
+
+describe("home page", () => {
+ vi.useFakeTimers();
+
+ const { fetchInterval, network } = get(appStore);
+ const getLatestChainInfoSpy = vi
+ .spyOn(duskAPI, "getLatestChainInfo")
+ .mockResolvedValue({
+ blocks: apiLatestChainInfo.data.blocks.map(transformBlock),
+ transactions:
+ apiLatestChainInfo.data.transactions.map(transformTransaction),
+ });
+
+ afterEach(() => {
+ cleanup();
+ getLatestChainInfoSpy.mockClear();
+ });
+
+ afterAll(() => {
+ getLatestChainInfoSpy.mockRestore();
+ vi.useRealTimers();
+ });
+
+ it("should render the home page, start polling for the latest chain info and stop the polling when the component is destroyed", async () => {
+ const { container, unmount } = render(HomePage);
+
+ expect(container.firstChild).toMatchSnapshot();
+ expect(getLatestChainInfoSpy).toHaveBeenCalledTimes(1);
+ expect(getLatestChainInfoSpy).toHaveBeenNthCalledWith(1, network);
+
+ await vi.advanceTimersByTimeAsync(fetchInterval);
+
+ expect(getLatestChainInfoSpy).toHaveBeenCalledTimes(2);
+ expect(getLatestChainInfoSpy).toHaveBeenNthCalledWith(2, network);
+
+ await vi.advanceTimersByTimeAsync(fetchInterval);
+
+ expect(getLatestChainInfoSpy).toHaveBeenCalledTimes(3);
+ expect(getLatestChainInfoSpy).toHaveBeenNthCalledWith(3, network);
+
+ unmount();
+
+ await vi.advanceTimersByTimeAsync(fetchInterval * 10);
+
+ expect(getLatestChainInfoSpy).toHaveBeenCalledTimes(3);
+ });
+});