Skip to content

Commit

Permalink
explorer: Add a reset method to data stores
Browse files Browse the repository at this point in the history
- the `dataStore.getData` method now starts a new call and ignore the previous one
- the `pollingDataStore.start` method now starts a new poll and ignore the previous one

Resolves #1732
  • Loading branch information
ascartabelli committed May 20, 2024
1 parent ec0621c commit b56aeed
Show file tree
Hide file tree
Showing 5 changed files with 438 additions and 91 deletions.
267 changes: 222 additions & 45 deletions explorer/src/lib/dusk/svelte-stores/__tests__/createDataStore.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import {
} from "vitest";
import { get } from "svelte/store";

import { rejectAfter, resolveAfter } from "$lib/dusk/promise";

import { createDataStore } from "..";

describe("createDataStore", () => {
const data = {};
const data1 = { a: 1 };
const data2 = { a: 2 };
const error = new Error("some error message");
const args = [1, "a", new Date()];
const dataRetriever = vi.fn().mockResolvedValue(data);
const dataRetriever = vi.fn().mockResolvedValue(data1);

/** @type {DataStore} */
let dataStore;
Expand All @@ -34,9 +37,10 @@ describe("createDataStore", () => {
vi.useRealTimers();
});

it("should create a readable data store and expose a `getData` service method", () => {
it("should create a readable data store and expose `getData` and `reset` as service method", () => {
expect(dataRetriever).not.toHaveBeenCalled();
expect(dataStore).toHaveProperty("getData", expect.any(Function));
expect(dataStore).toHaveProperty("reset", expect.any(Function));
expect(dataStore).toHaveProperty("subscribe", expect.any(Function));
expect(dataStore).not.toHaveProperty("set");
expect(get(dataStore)).toStrictEqual({
Expand All @@ -47,6 +51,11 @@ describe("createDataStore", () => {
});

it("should set the loading property to `true` when `getData` is called and then fill the data and set loading to `false` if the promise resolves", async () => {
const expectedState = {
data: data1,
error: null,
isLoading: false,
};
const dataPromise = dataStore.getData(...args);

expect(dataRetriever).toHaveBeenCalledTimes(1);
Expand All @@ -59,17 +68,18 @@ describe("createDataStore", () => {

await vi.advanceTimersToNextTimerAsync();

expect(dataPromise).resolves.toStrictEqual({
data,
error: null,
isLoading: false,
});
expect(dataPromise).resolves.toStrictEqual(get(dataStore));
expect(await dataPromise).toStrictEqual(expectedState);
expect(get(dataStore)).toStrictEqual(expectedState);
});

it("should set the loading property to `true` when `getData` is called and then set the error and the loading to `false` if the promise rejects", async () => {
dataRetriever.mockRejectedValueOnce(error);

const expectedState = {
data: null,
error,
isLoading: false,
};
const dataPromise = dataStore.getData(...args);

expect(dataRetriever).toHaveBeenCalledTimes(1);
Expand All @@ -82,89 +92,256 @@ describe("createDataStore", () => {

await vi.advanceTimersToNextTimerAsync();

expect(dataPromise).resolves.toStrictEqual({
expect(await dataPromise).toStrictEqual(expectedState);
expect(get(dataStore)).toStrictEqual(expectedState);
});

it("should ignore the previous call if `getData` is called while the promise is still pending", async () => {
dataRetriever
.mockImplementationOnce(() => resolveAfter(1000, data1))
.mockResolvedValueOnce(data2);

/** @type {DataStoreContent} */
let expectedState = {
data: data2,
error: null,
isLoading: false,
};

dataStore.getData(...args);

expect(dataRetriever).toHaveBeenCalledTimes(1);
expect(dataRetriever).toHaveBeenCalledWith(...args);

let dataPromise = dataStore.getData(...args);

expect(dataRetriever).toHaveBeenCalledTimes(2);
expect(dataRetriever).toHaveBeenNthCalledWith(2, ...args);
expect(await dataPromise).toStrictEqual(expectedState);
expect(get(dataStore)).toStrictEqual(expectedState);

// waiting for the first promise to resolve
await vi.advanceTimersToNextTimerAsync();

expect(get(dataStore)).toStrictEqual(expectedState);

dataRetriever
.mockImplementationOnce(() => rejectAfter(1000, error))
.mockResolvedValueOnce(data2);

dataStore.getData(...args);

expect(dataRetriever).toHaveBeenCalledTimes(3);
expect(dataRetriever).toHaveBeenNthCalledWith(3, ...args);

expectedState = {
data: data2,
error: null,
isLoading: false,
};
dataPromise = dataStore.getData(...args);

expect(dataRetriever).toHaveBeenCalledTimes(4);
expect(dataRetriever).toHaveBeenNthCalledWith(4, ...args);
expect(await dataPromise).toStrictEqual(expectedState);
expect(get(dataStore)).toStrictEqual(expectedState);

// waiting for the first promise to resolve
await vi.advanceTimersToNextTimerAsync();

expect(get(dataStore)).toStrictEqual(expectedState);

dataRetriever
.mockImplementationOnce(() => resolveAfter(1000, data1))
.mockRejectedValueOnce(error);

dataStore.getData(...args);

expect(dataRetriever).toHaveBeenCalledTimes(5);
expect(dataRetriever).toHaveBeenNthCalledWith(5, ...args);

expectedState = {
data: null,
error,
isLoading: false,
});
expect(dataPromise).resolves.toStrictEqual(get(dataStore));
};
dataPromise = dataStore.getData(...args);

expect(dataRetriever).toHaveBeenCalledTimes(6);
expect(dataRetriever).toHaveBeenNthCalledWith(6, ...args);
expect(await dataPromise).toStrictEqual(expectedState);
expect(get(dataStore)).toStrictEqual(expectedState);

// waiting for the first promise to resolve
await vi.advanceTimersToNextTimerAsync();

expect(get(dataStore)).toStrictEqual(expectedState);
});

it("should not call the data retriever when `getData` is called and the promise is still pending", async () => {
const dataPromise = dataStore.getData(1);
it("should clear the error and leave the existing data while the promise is pending and clear the data when it ends with a failure", async () => {
dataRetriever.mockRejectedValueOnce(error);

/** @type {DataStoreContent} */
let expectedState = {
data: null,
error,
isLoading: false,
};
let dataPromise = dataStore.getData();

expect(get(dataStore)).toStrictEqual({
data: null,
error: null,
isLoading: true,
});

const dataPromise2 = dataStore.getData(2);
const dataPromise3 = dataStore.getData(3);

expect(dataPromise2).toBe(dataPromise);
expect(dataPromise3).toBe(dataPromise);

await vi.advanceTimersToNextTimerAsync();

expect(dataRetriever).toHaveBeenCalledTimes(1);
expect(dataRetriever).toHaveBeenCalledWith(1);
expect(dataPromise).resolves.toStrictEqual({
data,
expect(await dataPromise).toStrictEqual(expectedState);
expect(get(dataStore)).toStrictEqual(expectedState);

expectedState = {
data: data1,
error: null,
isLoading: false,
};
dataPromise = dataStore.getData();

expect(get(dataStore)).toStrictEqual({
data: null,
error: null,
isLoading: true,
});
expect(dataPromise).resolves.toStrictEqual(get(dataStore));
});

it("should clear the error and leave the existing data while the promise is pending and clear the data when it ends with a failure", async () => {
dataRetriever.mockRejectedValueOnce(error);
await vi.advanceTimersToNextTimerAsync();

let dataPromise = dataStore.getData();
expect(await dataPromise).toStrictEqual(expectedState);
expect(get(dataStore)).toStrictEqual(expectedState);

await vi.advanceTimersToNextTimerAsync();
dataRetriever.mockRejectedValueOnce(error);

expect(dataPromise).resolves.toStrictEqual({
expectedState = {
data: null,
error,
isLoading: false,
});
expect(dataPromise).resolves.toStrictEqual(get(dataStore));

};
dataPromise = dataStore.getData();

expect(get(dataStore)).toStrictEqual({
data: null,
data: data1,
error: null,
isLoading: true,
});

await vi.advanceTimersToNextTimerAsync();

expect(dataPromise).resolves.toStrictEqual({
data,
expect(await dataPromise).toStrictEqual(expectedState);
expect(get(dataStore)).toStrictEqual(expectedState);
});

it("should expose a `reset` method to reset the data to its initial state", async () => {
expect(get(dataStore)).toStrictEqual({
data: null,
error: null,
isLoading: false,
});
expect(dataPromise).resolves.toStrictEqual(get(dataStore));

dataRetriever.mockRejectedValueOnce(error);
await dataStore.getData(...args);

dataPromise = dataStore.getData();
expect(get(dataStore)).toStrictEqual({
data: data1,
error: null,
isLoading: false,
});

dataStore.reset();

expect(get(dataStore)).toStrictEqual({
data,
data: null,
error: null,
isLoading: true,
isLoading: false,
});

await vi.advanceTimersToNextTimerAsync();
dataRetriever.mockRejectedValueOnce(error);

await dataStore.getData(...args);

expect(dataPromise).resolves.toStrictEqual({
expect(get(dataStore)).toStrictEqual({
data: null,
error,
isLoading: false,
});
expect(dataPromise).resolves.toStrictEqual(get(dataStore));

dataStore.reset();

expect(get(dataStore)).toStrictEqual({
data: null,
error: null,
isLoading: false,
});
});

it("should ignore the pending promise when `reset` is called and have `getData` return the reset store", async () => {
const expectedInitialState = {
data: null,
error: null,
isLoading: false,
};

await dataStore.getData(...args);

let dataPromise = dataStore.getData(...args);

expect(get(dataStore)).toStrictEqual({
data: data1,
error: null,
isLoading: true,
});

dataStore.reset();

expect(await dataPromise).toStrictEqual(expectedInitialState);
expect(get(dataStore)).toStrictEqual(expectedInitialState);

await dataStore.getData(...args);

dataRetriever.mockRejectedValueOnce(error);
dataPromise = dataStore.getData(...args);

expect(get(dataStore)).toStrictEqual({
data: data1,
error: null,
isLoading: true,
});

dataStore.reset();

expect(await dataPromise).toStrictEqual(expectedInitialState);
expect(get(dataStore)).toStrictEqual(expectedInitialState);
});

it("should ignore the pending promise when `reset` is called and a `getData` is called immediately afterwards and return the new result", async () => {
const expectedState = {
data: data2,
error: null,
isLoading: false,
};

dataRetriever
.mockImplementationOnce(() => resolveAfter(1000, data1))
.mockResolvedValueOnce(data2);

dataStore.getData(...args);
dataStore.reset();

const dataPromise = dataStore.getData(...args);

expect(await dataPromise).toStrictEqual(expectedState);
expect(get(dataStore)).toStrictEqual(expectedState);

// waiting for the first promise to resolve
await vi.advanceTimersToNextTimerAsync();

expect(get(dataStore)).toStrictEqual(expectedState);
});
});
Loading

0 comments on commit b56aeed

Please sign in to comment.