diff options
Diffstat (limited to 'packages/merchant-backoffice-ui/tests/hooks')
9 files changed, 2595 insertions, 0 deletions
diff --git a/packages/merchant-backoffice-ui/tests/hooks/async.test.ts b/packages/merchant-backoffice-ui/tests/hooks/async.test.ts new file mode 100644 index 000000000..a6d0cddfa --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/hooks/async.test.ts @@ -0,0 +1,158 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { renderHook } from "@testing-library/preact-hooks" +import { useAsync } from "../../src/hooks/async" + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ +test("async function is called", async () => { + jest.useFakeTimers() + + const timeout = 500 + + const asyncFunction = jest.fn(() => new Promise((res) => { + setTimeout(() => { + res({ the_answer: 'yes' }) + }, timeout); + })) + + const { result, waitForNextUpdate } = renderHook(() => { + return useAsync(asyncFunction) + }) + + expect(result.current?.isLoading).toBeFalsy() + + result.current?.request() + expect(asyncFunction).toBeCalled() + await waitForNextUpdate({ timeout: 1 }) + expect(result.current?.isLoading).toBeTruthy() + + jest.advanceTimersByTime(timeout + 1) + await waitForNextUpdate({ timeout: 1 }) + expect(result.current?.isLoading).toBeFalsy() + expect(result.current?.data).toMatchObject({ the_answer: 'yes' }) + expect(result.current?.error).toBeUndefined() + expect(result.current?.isSlow).toBeFalsy() +}) + +test("async function return error if rejected", async () => { + jest.useFakeTimers() + + const timeout = 500 + + const asyncFunction = jest.fn(() => new Promise((_, rej) => { + setTimeout(() => { + rej({ the_error: 'yes' }) + }, timeout); + })) + + const { result, waitForNextUpdate } = renderHook(() => { + return useAsync(asyncFunction) + }) + + expect(result.current?.isLoading).toBeFalsy() + + result.current?.request() + expect(asyncFunction).toBeCalled() + await waitForNextUpdate({ timeout: 1 }) + expect(result.current?.isLoading).toBeTruthy() + + jest.advanceTimersByTime(timeout + 1) + await waitForNextUpdate({ timeout: 1 }) + expect(result.current?.isLoading).toBeFalsy() + expect(result.current?.error).toMatchObject({ the_error: 'yes' }) + expect(result.current?.data).toBeUndefined() + expect(result.current?.isSlow).toBeFalsy() +}) + +test("async function is slow", async () => { + jest.useFakeTimers() + + const timeout = 2200 + + const asyncFunction = jest.fn(() => new Promise((res) => { + setTimeout(() => { + res({ the_answer: 'yes' }) + }, timeout); + })) + + const { result, waitForNextUpdate } = renderHook(() => { + return useAsync(asyncFunction) + }) + + expect(result.current?.isLoading).toBeFalsy() + + result.current?.request() + expect(asyncFunction).toBeCalled() + await waitForNextUpdate({ timeout: 1 }) + expect(result.current?.isLoading).toBeTruthy() + + jest.advanceTimersByTime(timeout / 2) + await waitForNextUpdate({ timeout: 1 }) + expect(result.current?.isLoading).toBeTruthy() + expect(result.current?.isSlow).toBeTruthy() + expect(result.current?.data).toBeUndefined() + expect(result.current?.error).toBeUndefined() + + jest.advanceTimersByTime(timeout / 2) + await waitForNextUpdate({ timeout: 1 }) + expect(result.current?.isLoading).toBeFalsy() + expect(result.current?.data).toMatchObject({ the_answer: 'yes' }) + expect(result.current?.error).toBeUndefined() + expect(result.current?.isSlow).toBeFalsy() + +}) + +test("async function is cancellable", async () => { + jest.useFakeTimers() + + const timeout = 2200 + + const asyncFunction = jest.fn(() => new Promise((res) => { + setTimeout(() => { + res({ the_answer: 'yes' }) + }, timeout); + })) + + const { result, waitForNextUpdate } = renderHook(() => { + return useAsync(asyncFunction) + }) + + expect(result.current?.isLoading).toBeFalsy() + + result.current?.request() + expect(asyncFunction).toBeCalled() + await waitForNextUpdate({ timeout: 1 }) + expect(result.current?.isLoading).toBeTruthy() + + jest.advanceTimersByTime(timeout / 2) + await waitForNextUpdate({ timeout: 1 }) + expect(result.current?.isLoading).toBeTruthy() + expect(result.current?.isSlow).toBeTruthy() + expect(result.current?.data).toBeUndefined() + expect(result.current?.error).toBeUndefined() + + result.current?.cancel() + await waitForNextUpdate({ timeout: 1 }) + expect(result.current?.isLoading).toBeFalsy() + expect(result.current?.data).toBeUndefined() + expect(result.current?.error).toBeUndefined() + expect(result.current?.isSlow).toBeFalsy() + +}) diff --git a/packages/merchant-backoffice-ui/tests/hooks/listener.test.ts b/packages/merchant-backoffice-ui/tests/hooks/listener.test.ts new file mode 100644 index 000000000..ae34c1339 --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/hooks/listener.test.ts @@ -0,0 +1,62 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { renderHook, act } from '@testing-library/preact-hooks'; +import { useListener } from '../../src/hooks/listener'; + +// jest.useFakeTimers() + +test('listener', async () => { + + + function createSomeString() { + return "hello" + } + async function addWorldToTheEnd(resultFromComponentB: string) { + return `${resultFromComponentB} world` + } + const expectedResult = "hello world" + + const { result } = renderHook(() => useListener(addWorldToTheEnd)) + + if (!result.current) { + expect(result.current).toBeDefined() + return; + } + + { + const [activator, subscriber] = result.current + expect(activator).toBeUndefined() + + act(() => { + subscriber(createSomeString) + }) + + } + + const [activator] = result.current + expect(activator).toBeDefined() + if (!activator) return; + + const response = await activator() + expect(response).toBe(expectedResult) + +}); diff --git a/packages/merchant-backoffice-ui/tests/hooks/notification.test.ts b/packages/merchant-backoffice-ui/tests/hooks/notification.test.ts new file mode 100644 index 000000000..6825a825a --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/hooks/notification.test.ts @@ -0,0 +1,51 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + + /** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { renderHook, act} from '@testing-library/preact-hooks'; +import { useNotifications } from '../../src/hooks/notifications'; + +jest.useFakeTimers() + +test('notification should disapear after timeout', () => { + jest.spyOn(global, 'setTimeout'); + + const timeout = 1000 + const { result, rerender } = renderHook(() => useNotifications(undefined, timeout)); + + expect(result.current?.notifications.length).toBe(0); + + act(() => { + result.current?.pushNotification({ + message: 'some_id', + type: 'INFO' + }); + }); + expect(result.current?.notifications.length).toBe(1); + + jest.advanceTimersByTime(timeout/2); + rerender() + expect(result.current?.notifications.length).toBe(1); + + jest.advanceTimersByTime(timeout); + rerender() + expect(result.current?.notifications.length).toBe(0); + +}); diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/index.tsx b/packages/merchant-backoffice-ui/tests/hooks/swr/index.tsx new file mode 100644 index 000000000..44514855d --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/hooks/swr/index.tsx @@ -0,0 +1,45 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { ComponentChildren, h, VNode } from "preact"; +import { SWRConfig } from "swr"; +import { BackendContextProvider } from "../../../src/context/backend"; +import { InstanceContextProvider } from "../../../src/context/instance"; + +interface TestingContextProps { + children?: ComponentChildren; +} +export function TestingContext({ children }: TestingContextProps): VNode { + return ( + <BackendContextProvider defaultUrl="http://backend" initialToken="token"> + <InstanceContextProvider + value={{ + token: "token", + id: "default", + admin: true, + changeToken: () => null, + }} + > + <SWRConfig value={{ provider: () => new Map() }}>{children}</SWRConfig> + </InstanceContextProvider> + </BackendContextProvider> + ); +} diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/instance.test.ts b/packages/merchant-backoffice-ui/tests/hooks/swr/instance.test.ts new file mode 100644 index 000000000..55d9fa6ee --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/hooks/swr/instance.test.ts @@ -0,0 +1,636 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { renderHook } from "@testing-library/preact-hooks"; +import { act } from "preact/test-utils"; +import { MerchantBackend } from "../../../src/declaration"; +import { useAdminAPI, useBackendInstances, useInstanceAPI, useInstanceDetails, useManagementAPI } from "../../../src/hooks/instance"; +import { + API_CREATE_INSTANCE, + API_DELETE_INSTANCE, + API_GET_CURRENT_INSTANCE, + API_LIST_INSTANCES, + API_UPDATE_CURRENT_INSTANCE, + API_UPDATE_CURRENT_INSTANCE_AUTH, + API_UPDATE_INSTANCE_AUTH_BY_ID, + API_UPDATE_INSTANCE_BY_ID, + assertJustExpectedRequestWereMade, + AxiosMockEnvironment +} from "../../axiosMock"; +import { TestingContext } from "./index"; + +describe("instance api interaction with details ", () => { + + it("should evict cache when updating an instance", async () => { + + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_GET_CURRENT_INSTANCE, { + response: { + name: 'instance_name' + } as MerchantBackend.Instances.QueryInstancesResponse, + }); + + const { result, waitForNextUpdate } = renderHook( + () => { + const api = useInstanceAPI(); + const query = useInstanceDetails(); + + return { query, api }; + }, + { wrapper: TestingContext } + ); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + name: 'instance_name' + }); + + env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE, { + request: { + name: 'other_name' + } as MerchantBackend.Instances.InstanceReconfigurationMessage, + }); + + act(async () => { + await result.current?.api.updateInstance({ + name: 'other_name' + } as MerchantBackend.Instances.InstanceReconfigurationMessage); + }); + + assertJustExpectedRequestWereMade(env); + + env.addRequestExpectation(API_GET_CURRENT_INSTANCE, { + response: { + name: 'other_name' + } as MerchantBackend.Instances.QueryInstancesResponse, + }); + + expect(result.current.query.loading).toBeFalsy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + + expect(result.current.query.data).toEqual({ + name: 'other_name' + }); + }); + + it("should evict cache when setting the instance's token", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_GET_CURRENT_INSTANCE, { + response: { + name: 'instance_name', + auth: { + method: 'token', + token: 'not-secret', + } + } as MerchantBackend.Instances.QueryInstancesResponse, + }); + + const { result, waitForNextUpdate } = renderHook( + () => { + const api = useInstanceAPI(); + const query = useInstanceDetails(); + + return { query, api }; + }, + { wrapper: TestingContext } + ); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + name: 'instance_name', + auth: { + method: 'token', + token: 'not-secret', + } + }); + + env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE_AUTH, { + request: { + method: 'token', + token: 'secret' + } as MerchantBackend.Instances.InstanceAuthConfigurationMessage, + }); + + act(async () => { + await result.current?.api.setNewToken('secret'); + }); + + assertJustExpectedRequestWereMade(env); + + env.addRequestExpectation(API_GET_CURRENT_INSTANCE, { + response: { + name: 'instance_name', + auth: { + method: 'token', + token: 'secret', + } + } as MerchantBackend.Instances.QueryInstancesResponse, + }); + + expect(result.current.query.loading).toBeFalsy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + + expect(result.current.query.data).toEqual({ + name: 'instance_name', + auth: { + method: 'token', + token: 'secret', + } + }); + }); + + it("should evict cache when clearing the instance's token", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_GET_CURRENT_INSTANCE, { + response: { + name: 'instance_name', + auth: { + method: 'token', + token: 'not-secret', + } + } as MerchantBackend.Instances.QueryInstancesResponse, + }); + + const { result, waitForNextUpdate } = renderHook( + () => { + const api = useInstanceAPI(); + const query = useInstanceDetails(); + + return { query, api }; + }, + { wrapper: TestingContext } + ); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + name: 'instance_name', + auth: { + method: 'token', + token: 'not-secret', + } + }); + + env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE_AUTH, { + request: { + method: 'external', + } as MerchantBackend.Instances.InstanceAuthConfigurationMessage, + }); + + act(async () => { + await result.current?.api.clearToken(); + }); + + assertJustExpectedRequestWereMade(env); + + env.addRequestExpectation(API_GET_CURRENT_INSTANCE, { + response: { + name: 'instance_name', + auth: { + method: 'external', + } + } as MerchantBackend.Instances.QueryInstancesResponse, + }); + + expect(result.current.query.loading).toBeFalsy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + + expect(result.current.query.data).toEqual({ + name: 'instance_name', + auth: { + method: 'external', + } + }); + }); +}); + +describe("instance admin api interaction with listing ", () => { + + it("should evict cache when creating a new instance", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_LIST_INSTANCES, { + response: { + instances: [{ + name: 'instance_name' + } as MerchantBackend.Instances.Instance] + }, + }); + + const { result, waitForNextUpdate } = renderHook( + () => { + const api = useAdminAPI(); + const query = useBackendInstances(); + + return { query, api }; + }, + { wrapper: TestingContext } + ); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + instances: [{ + name: 'instance_name' + }] + }); + + env.addRequestExpectation(API_CREATE_INSTANCE, { + request: { + name: 'other_name' + } as MerchantBackend.Instances.InstanceConfigurationMessage, + }); + + act(async () => { + await result.current?.api.createInstance({ + name: 'other_name' + } as MerchantBackend.Instances.InstanceConfigurationMessage); + }); + + assertJustExpectedRequestWereMade(env); + + env.addRequestExpectation(API_LIST_INSTANCES, { + response: { + instances: [{ + name: 'instance_name' + } as MerchantBackend.Instances.Instance, + { + name: 'other_name' + } as MerchantBackend.Instances.Instance] + }, + }); + + expect(result.current.query.loading).toBeFalsy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + + expect(result.current.query.data).toEqual({ + instances: [{ + name: 'instance_name' + }, { + name: 'other_name' + }] + }); + }); + + it("should evict cache when deleting an instance", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_LIST_INSTANCES, { + response: { + instances: [{ + id: 'default', + name: 'instance_name' + } as MerchantBackend.Instances.Instance, + { + id: 'the_id', + name: 'second_instance' + } as MerchantBackend.Instances.Instance] + }, + }); + + const { result, waitForNextUpdate } = renderHook( + () => { + const api = useAdminAPI(); + const query = useBackendInstances(); + + return { query, api }; + }, + { wrapper: TestingContext } + ); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + instances: [{ + id: 'default', + name: 'instance_name' + }, { + id: 'the_id', + name: 'second_instance' + }] + }); + + env.addRequestExpectation(API_DELETE_INSTANCE('the_id'), {}); + + act(async () => { + await result.current?.api.deleteInstance('the_id'); + }); + + assertJustExpectedRequestWereMade(env); + + env.addRequestExpectation(API_LIST_INSTANCES, { + response: { + instances: [{ + id: 'default', + name: 'instance_name' + } as MerchantBackend.Instances.Instance] + }, + }); + + expect(result.current.query.loading).toBeFalsy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + + expect(result.current.query.data).toEqual({ + instances: [{ + id: 'default', + name: 'instance_name' + }] + }); + }); + it("should evict cache when deleting (purge) an instance", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_LIST_INSTANCES, { + response: { + instances: [{ + id: 'default', + name: 'instance_name' + } as MerchantBackend.Instances.Instance, + { + id: 'the_id', + name: 'second_instance' + } as MerchantBackend.Instances.Instance] + }, + }); + + const { result, waitForNextUpdate } = renderHook( + () => { + const api = useAdminAPI(); + const query = useBackendInstances(); + + return { query, api }; + }, + { wrapper: TestingContext } + ); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + instances: [{ + id: 'default', + name: 'instance_name' + }, { + id: 'the_id', + name: 'second_instance' + }] + }); + + env.addRequestExpectation(API_DELETE_INSTANCE('the_id'), { + qparam: { + purge: 'YES' + } + }); + + act(async () => { + await result.current?.api.purgeInstance('the_id'); + }); + + assertJustExpectedRequestWereMade(env); + + env.addRequestExpectation(API_LIST_INSTANCES, { + response: { + instances: [{ + id: 'default', + name: 'instance_name' + } as MerchantBackend.Instances.Instance] + }, + }); + + expect(result.current.query.loading).toBeFalsy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + + expect(result.current.query.data).toEqual({ + instances: [{ + id: 'default', + name: 'instance_name' + }] + }); + }); +}); + +describe("instance management api interaction with listing ", () => { + + it("should evict cache when updating an instance", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_LIST_INSTANCES, { + response: { + instances: [{ + id: 'managed', + name: 'instance_name' + } as MerchantBackend.Instances.Instance] + }, + }); + + const { result, waitForNextUpdate } = renderHook( + () => { + const api = useManagementAPI('managed'); + const query = useBackendInstances(); + + return { query, api }; + }, + { wrapper: TestingContext } + ); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + instances: [{ + id: 'managed', + name: 'instance_name' + }] + }); + + env.addRequestExpectation(API_UPDATE_INSTANCE_BY_ID('managed'), { + request: { + name: 'other_name' + } as MerchantBackend.Instances.InstanceReconfigurationMessage, + }); + + act(async () => { + await result.current?.api.updateInstance({ + name: 'other_name' + } as MerchantBackend.Instances.InstanceConfigurationMessage); + }); + + assertJustExpectedRequestWereMade(env); + + env.addRequestExpectation(API_LIST_INSTANCES, { + response: { + instances: [ + { + id: 'managed', + name: 'other_name' + } as MerchantBackend.Instances.Instance] + }, + }); + + expect(result.current.query.loading).toBeFalsy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + + expect(result.current.query.data).toEqual({ + instances: [{ + id: 'managed', + name: 'other_name' + }] + }); + }); + +}); + diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/order.test.ts b/packages/merchant-backoffice-ui/tests/hooks/swr/order.test.ts new file mode 100644 index 000000000..e7f6c9334 --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/hooks/swr/order.test.ts @@ -0,0 +1,567 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { renderHook } from "@testing-library/preact-hooks"; +import { act } from "preact/test-utils"; +import { TestingContext } from "."; +import { MerchantBackend } from "../../../src/declaration"; +import { useInstanceOrders, useOrderAPI, useOrderDetails } from "../../../src/hooks/order"; +import { + API_CREATE_ORDER, + API_DELETE_ORDER, + API_FORGET_ORDER_BY_ID, + API_GET_ORDER_BY_ID, + API_LIST_ORDERS, API_REFUND_ORDER_BY_ID, assertJustExpectedRequestWereMade, assertNextRequest, assertNoMoreRequestWereMade, AxiosMockEnvironment +} from "../../axiosMock"; + +describe("order api interaction with listing", () => { + + it("should evict cache when creating an order", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: 0, paid: "yes" }, + response: { + orders: [{ order_id: "1" } as MerchantBackend.Orders.OrderHistoryEntry], + }, + }); + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: -20, paid: "yes" }, + response: { + orders: [{ order_id: "2" } as MerchantBackend.Orders.OrderHistoryEntry], + }, + }); + + + const { result, waitForNextUpdate } = renderHook(() => { + const newDate = (d: Date) => { + console.log("new date", d); + }; + const query = useInstanceOrders({ paid: "yes" }, newDate); + const api = useOrderAPI(); + + return { query, api }; + }, { wrapper: TestingContext }); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + + expect(result.current.query.loading).toBeTruthy(); + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + orders: [{ order_id: "1" }, { order_id: "2" }], + }); + + env.addRequestExpectation(API_CREATE_ORDER, { + request: { + order: { amount: "ARS:12", summary: "pay me" }, + }, + response: { order_id: "3" }, + }); + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: 0, paid: "yes" }, + response: { + orders: [{ order_id: "1" } as any], + }, + }); + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: -20, paid: "yes" }, + response: { + orders: [{ order_id: "2" } as any, { order_id: "3" } as any], + }, + }); + + act(async () => { + await result.current?.api.createOrder({ + order: { amount: "ARS:12", summary: "pay me" }, + } as any); + }); + + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + orders: [{ order_id: "1" }, { order_id: "2" }, { order_id: "3" }], + }); + }); + it("should evict cache when doing a refund", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: 0, paid: "yes" }, + response: { + orders: [{ order_id: "1", amount: 'EUR:12', refundable: true } as MerchantBackend.Orders.OrderHistoryEntry], + }, + }); + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: -20, paid: "yes" }, + response: { orders: [], }, + }); + + + const { result, waitForNextUpdate } = renderHook(() => { + const newDate = (d: Date) => { + console.log("new date", d); + }; + const query = useInstanceOrders({ paid: "yes" }, newDate); + const api = useOrderAPI(); + + return { query, api }; + }, { wrapper: TestingContext }); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + + expect(result.current.query.loading).toBeTruthy(); + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + orders: [{ + order_id: "1", + amount: 'EUR:12', + refundable: true, + }], + }); + + env.addRequestExpectation(API_REFUND_ORDER_BY_ID('1'), { + request: { + reason: 'double pay', + refund: 'EUR:1' + }, + }); + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: 0, paid: "yes" }, + response: { + orders: [{ order_id: "1", amount: 'EUR:12', refundable: false } as any], + }, + }); + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: -20, paid: "yes" }, + response: { orders: [], }, + }); + + act(async () => { + await result.current?.api.refundOrder('1', { + reason: 'double pay', + refund: 'EUR:1' + }); + }); + + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + orders: [{ + order_id: "1", + amount: 'EUR:12', + refundable: false, + }], + }); + }); + it("should evict cache when deleting an order", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: 0, paid: "yes" }, + response: { + orders: [{ order_id: "1" } as MerchantBackend.Orders.OrderHistoryEntry], + }, + }); + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: -20, paid: "yes" }, + response: { + orders: [{ order_id: "2" } as MerchantBackend.Orders.OrderHistoryEntry], + }, + }); + + + const { result, waitForNextUpdate } = renderHook(() => { + const newDate = (d: Date) => { + console.log("new date", d); + }; + const query = useInstanceOrders({ paid: "yes" }, newDate); + const api = useOrderAPI(); + + return { query, api }; + }, { wrapper: TestingContext }); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + + expect(result.current.query.loading).toBeTruthy(); + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + orders: [{ order_id: "1" }, { order_id: "2" }], + }); + + env.addRequestExpectation(API_DELETE_ORDER('1'), {}); + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: 0, paid: "yes" }, + response: { + orders: [], + }, + }); + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: -20, paid: "yes" }, + response: { + orders: [{ order_id: "2" } as any], + }, + }); + + act(async () => { + await result.current?.api.deleteOrder('1'); + }); + + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + orders: [{ order_id: "2" }], + }); + }); + +}); + +describe("order api interaction with details", () => { + + it("should evict cache when doing a refund", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), { + // qparam: { delta: 0, paid: "yes" }, + response: { + summary: 'description', + refund_amount: 'EUR:0', + } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse, + }); + + const { result, waitForNextUpdate } = renderHook(() => { + const query = useOrderDetails('1') + const api = useOrderAPI(); + + return { query, api }; + }, { wrapper: TestingContext }); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + + expect(result.current.query.loading).toBeTruthy(); + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + summary: 'description', + refund_amount: 'EUR:0', + }); + + env.addRequestExpectation(API_REFUND_ORDER_BY_ID('1'), { + request: { + reason: 'double pay', + refund: 'EUR:1' + }, + }); + + env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), { + response: { + summary: 'description', + refund_amount: 'EUR:1', + } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse, + }); + + act(async () => { + await result.current?.api.refundOrder('1', { + reason: 'double pay', + refund: 'EUR:1' + }); + }); + + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + summary: 'description', + refund_amount: 'EUR:1', + }); + }) + it("should evict cache when doing a forget", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), { + // qparam: { delta: 0, paid: "yes" }, + response: { + summary: 'description', + refund_amount: 'EUR:0', + } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse, + }); + + const { result, waitForNextUpdate } = renderHook(() => { + const query = useOrderDetails('1') + const api = useOrderAPI(); + + return { query, api }; + }, { wrapper: TestingContext }); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + + expect(result.current.query.loading).toBeTruthy(); + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + summary: 'description', + refund_amount: 'EUR:0', + }); + + env.addRequestExpectation(API_FORGET_ORDER_BY_ID('1'), { + request: { + fields: ['$.summary'] + }, + }); + + env.addRequestExpectation(API_GET_ORDER_BY_ID('1'), { + response: { + summary: undefined, + } as unknown as MerchantBackend.Orders.CheckPaymentPaidResponse, + }); + + act(async () => { + await result.current?.api.forgetOrder('1', { + fields: ['$.summary'] + }); + }); + + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + summary: undefined, + }); + }) +}) + +describe("order listing pagination", () => { + + it("should not load more if has reach the end", async () => { + const env = new AxiosMockEnvironment(); + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: 20, wired: "yes", date_ms: 12 }, + response: { + orders: [{ order_id: "1" } as any], + }, + }); + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: -20, wired: "yes", date_ms: 13 }, + response: { + orders: [{ order_id: "2" } as any], + }, + }); + + + const { result, waitForNextUpdate } = renderHook(() => { + const newDate = (d: Date) => { + console.log("new date", d); + }; + const date = new Date(12); + const query = useInstanceOrders({ wired: "yes", date }, newDate) + return { query } + }, { wrapper: TestingContext }); + + assertJustExpectedRequestWereMade(env); + + await waitForNextUpdate(); + + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + orders: [{ order_id: "1" }, { order_id: "2" }], + }); + + expect(result.current.query.isReachingEnd).toBeTruthy() + expect(result.current.query.isReachingStart).toBeTruthy() + + await act(() => { + if (!result.current?.query.ok) throw Error("not ok"); + result.current.query.loadMore(); + }); + assertNoMoreRequestWereMade(env); + + await act(() => { + if (!result.current?.query.ok) throw Error("not ok"); + result.current.query.loadMorePrev(); + }); + assertNoMoreRequestWereMade(env); + + expect(result.current.query.data).toEqual({ + orders: [ + { order_id: "1" }, + { order_id: "2" }, + ], + }); + }); + + it("should load more if result brings more that PAGE_SIZE", async () => { + const env = new AxiosMockEnvironment(); + + const ordersFrom0to20 = Array.from({ length: 20 }).map((e, i) => ({ order_id: String(i) })) + const ordersFrom20to40 = Array.from({ length: 20 }).map((e, i) => ({ order_id: String(i + 20) })) + const ordersFrom20to0 = [...ordersFrom0to20].reverse() + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: 20, wired: "yes", date_ms: 12 }, + response: { + orders: ordersFrom0to20, + }, + }); + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: -20, wired: "yes", date_ms: 13 }, + response: { + orders: ordersFrom20to40, + }, + }); + + const { result, waitForNextUpdate } = renderHook(() => { + const newDate = (d: Date) => { + console.log("new date", d); + }; + const date = new Date(12); + const query = useInstanceOrders({ wired: "yes", date }, newDate) + return { query } + }, { wrapper: TestingContext }); + + assertJustExpectedRequestWereMade(env); + + await waitForNextUpdate({ timeout: 1 }); + + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + orders: [...ordersFrom20to0, ...ordersFrom20to40], + }); + + expect(result.current.query.isReachingEnd).toBeFalsy() + expect(result.current.query.isReachingStart).toBeFalsy() + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: -40, wired: "yes", date_ms: 13 }, + response: { + orders: [...ordersFrom20to40, { order_id: '41' }], + }, + }); + + await act(() => { + if (!result.current?.query.ok) throw Error("not ok"); + result.current.query.loadMore(); + }); + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + env.addRequestExpectation(API_LIST_ORDERS, { + qparam: { delta: 40, wired: "yes", date_ms: 12 }, + response: { + orders: [...ordersFrom0to20, { order_id: '-1' }], + }, + }); + + await act(() => { + if (!result.current?.query.ok) throw Error("not ok"); + result.current.query.loadMorePrev(); + }); + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.data).toEqual({ + orders: [{ order_id: '-1' }, ...ordersFrom20to0, ...ordersFrom20to40, { order_id: '41' }], + }); + }); + + +}); diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/product.test.ts b/packages/merchant-backoffice-ui/tests/hooks/swr/product.test.ts new file mode 100644 index 000000000..5d39a7c47 --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/hooks/swr/product.test.ts @@ -0,0 +1,338 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { renderHook } from "@testing-library/preact-hooks"; +import { act } from "preact/test-utils"; +import { TestingContext } from "."; +import { MerchantBackend } from "../../../src/declaration"; +import { useInstanceProducts, useProductAPI, useProductDetails } from "../../../src/hooks/product"; +import { + API_CREATE_PRODUCT, + API_DELETE_PRODUCT, API_GET_PRODUCT_BY_ID, + API_LIST_PRODUCTS, + API_UPDATE_PRODUCT_BY_ID, + assertJustExpectedRequestWereMade, + assertNextRequest, + AxiosMockEnvironment +} from "../../axiosMock"; + +describe("product api interaction with listing ", () => { + it("should evict cache when creating a product", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_LIST_PRODUCTS, { + response: { + products: [{ product_id: "1234" }], + }, + }); + env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), { + response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail, + }); + + const { result, waitForNextUpdate } = renderHook( + () => { + const query = useInstanceProducts(); + const api = useProductAPI(); + return { api, query }; + }, + { wrapper: TestingContext } + ); // get products -> loading + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + await waitForNextUpdate({ timeout: 1 }); + + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual([ + { id: "1234", price: "ARS:12" }, + ]); + + env.addRequestExpectation(API_CREATE_PRODUCT, { + request: { price: "ARS:23" } as MerchantBackend.Products.ProductAddDetail, + }); + + env.addRequestExpectation(API_LIST_PRODUCTS, { + response: { + products: [{ product_id: "1234" }, { product_id: "2345" }], + }, + }); + env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), { + response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail, + }); + env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), { + response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail, + }); + env.addRequestExpectation(API_GET_PRODUCT_BY_ID("2345"), { + response: { price: "ARS:23" } as MerchantBackend.Products.ProductDetail, + }); + + act(async () => { + await result.current?.api.createProduct({ + price: "ARS:23", + } as any); + }); + + assertNextRequest(env); + await waitForNextUpdate({ timeout: 1 }); + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual([ + { + id: "1234", + price: "ARS:12", + }, + { + id: "2345", + price: "ARS:23", + }, + ]); + }); + + it("should evict cache when updating a product", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_LIST_PRODUCTS, { + response: { + products: [{ product_id: "1234" }], + }, + }); + env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), { + response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail, + }); + + const { result, waitForNextUpdate } = renderHook( + () => { + const query = useInstanceProducts(); + const api = useProductAPI(); + return { api, query }; + }, + { wrapper: TestingContext } + ); // get products -> loading + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + await waitForNextUpdate({ timeout: 1 }); + + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual([ + { id: "1234", price: "ARS:12" }, + ]); + + env.addRequestExpectation(API_UPDATE_PRODUCT_BY_ID("1234"), { + request: { price: "ARS:13" } as MerchantBackend.Products.ProductPatchDetail, + }); + + env.addRequestExpectation(API_LIST_PRODUCTS, { + response: { + products: [{ product_id: "1234" }], + }, + }); + env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), { + response: { price: "ARS:13" } as MerchantBackend.Products.ProductDetail, + }); + + act(async () => { + await result.current?.api.updateProduct("1234", { + price: "ARS:13", + } as any); + }); + + assertNextRequest(env); + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual([ + { + id: "1234", + price: "ARS:13", + }, + ]); + }); + + it("should evict cache when deleting a product", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_LIST_PRODUCTS, { + response: { + products: [{ product_id: "1234" }, { product_id: "2345" }], + }, + }); + env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), { + response: { price: "ARS:12" } as MerchantBackend.Products.ProductDetail, + }); + env.addRequestExpectation(API_GET_PRODUCT_BY_ID("2345"), { + response: { price: "ARS:23" } as MerchantBackend.Products.ProductDetail, + }); + + const { result, waitForNextUpdate } = renderHook( + () => { + const query = useInstanceProducts(); + const api = useProductAPI(); + return { api, query }; + }, + { wrapper: TestingContext } + ); // get products -> loading + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + await waitForNextUpdate({ timeout: 1 }); + + await waitForNextUpdate({ timeout: 1 }); + // await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual([ + { id: "1234", price: "ARS:12" }, + { id: "2345", price: "ARS:23" }, + ]); + + env.addRequestExpectation(API_DELETE_PRODUCT("2345"), {}); + + env.addRequestExpectation(API_LIST_PRODUCTS, { + response: { + products: [{ product_id: "1234" }], + }, + }); + env.addRequestExpectation(API_GET_PRODUCT_BY_ID("1234"), { + response: { price: "ARS:13" } as MerchantBackend.Products.ProductDetail, + }); + + act(async () => { + await result.current?.api.deleteProduct("2345"); + }); + + assertNextRequest(env); + await waitForNextUpdate({ timeout: 1 }); + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual([ + { + id: "1234", + price: "ARS:13", + }, + ]); + }); + +}); + +describe("product api interaction with details", () => { + it("should evict cache when updating a product", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_GET_PRODUCT_BY_ID("12"), { + response: { + description: "this is a description", + } as MerchantBackend.Products.ProductDetail, + }); + + const { result, waitForNextUpdate } = renderHook(() => { + const query = useProductDetails("12"); + const api = useProductAPI(); + return { query, api }; + }, { wrapper: TestingContext }); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + await waitForNextUpdate(); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + description: "this is a description", + }); + + env.addRequestExpectation(API_UPDATE_PRODUCT_BY_ID("12"), { + request: { description: "other description" } as MerchantBackend.Products.ProductPatchDetail, + }); + + env.addRequestExpectation(API_GET_PRODUCT_BY_ID("12"), { + response: { + description: "other description", + } as MerchantBackend.Products.ProductDetail, + }); + + act(async () => { + return await result.current?.api.updateProduct("12", { + description: "other description", + } as any); + }); + + assertNextRequest(env); + await waitForNextUpdate(); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + description: "other description", + }); + }) +})
\ No newline at end of file diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/reserve.test.ts b/packages/merchant-backoffice-ui/tests/hooks/swr/reserve.test.ts new file mode 100644 index 000000000..0361c54e8 --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/hooks/swr/reserve.test.ts @@ -0,0 +1,470 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { renderHook } from "@testing-library/preact-hooks"; +import { act } from "preact/test-utils"; +import { MerchantBackend } from "../../../src/declaration"; +import { + useInstanceReserves, + useReserveDetails, + useReservesAPI, + useTipDetails, +} from "../../../src/hooks/reserves"; +import { + API_AUTHORIZE_TIP, + API_AUTHORIZE_TIP_FOR_RESERVE, + API_CREATE_RESERVE, + API_DELETE_RESERVE, + API_GET_RESERVE_BY_ID, + API_GET_TIP_BY_ID, + API_LIST_RESERVES, + assertJustExpectedRequestWereMade, + AxiosMockEnvironment, +} from "../../axiosMock"; +import { TestingContext } from "./index"; + +describe("reserve api interaction with listing ", () => { + it("should evict cache when creating a reserve", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_LIST_RESERVES, { + response: { + reserves: [ + { + reserve_pub: "11", + } as MerchantBackend.Tips.ReserveStatusEntry, + ], + }, + }); + + const { result, waitForNextUpdate } = renderHook( + () => { + const api = useReservesAPI(); + const query = useInstanceReserves(); + + return { query, api }; + }, + { wrapper: TestingContext } + ); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + reserves: [{ reserve_pub: "11" }], + }); + + env.addRequestExpectation(API_CREATE_RESERVE, { + request: { + initial_balance: "ARS:3333", + exchange_url: "http://url", + wire_method: "iban", + }, + response: { + reserve_pub: "22", + payto_uri: "payto", + }, + }); + + act(async () => { + await result.current?.api.createReserve({ + initial_balance: "ARS:3333", + exchange_url: "http://url", + wire_method: "iban", + }); + return; + }); + + assertJustExpectedRequestWereMade(env); + + env.addRequestExpectation(API_LIST_RESERVES, { + response: { + reserves: [ + { + reserve_pub: "11", + } as MerchantBackend.Tips.ReserveStatusEntry, + { + reserve_pub: "22", + } as MerchantBackend.Tips.ReserveStatusEntry, + ], + }, + }); + + expect(result.current.query.loading).toBeFalsy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + + expect(result.current.query.data).toEqual({ + reserves: [ + { + reserve_pub: "11", + } as MerchantBackend.Tips.ReserveStatusEntry, + { + reserve_pub: "22", + } as MerchantBackend.Tips.ReserveStatusEntry, + ], + }); + }); + + it("should evict cache when deleting a reserve", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_LIST_RESERVES, { + response: { + reserves: [ + { + reserve_pub: "11", + } as MerchantBackend.Tips.ReserveStatusEntry, + { + reserve_pub: "22", + } as MerchantBackend.Tips.ReserveStatusEntry, + { + reserve_pub: "33", + } as MerchantBackend.Tips.ReserveStatusEntry, + ], + }, + }); + + const { result, waitForNextUpdate } = renderHook( + () => { + const api = useReservesAPI(); + const query = useInstanceReserves(); + + return { query, api }; + }, + { + wrapper: TestingContext, + } + ); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + reserves: [ + { reserve_pub: "11" }, + { reserve_pub: "22" }, + { reserve_pub: "33" }, + ], + }); + + env.addRequestExpectation(API_DELETE_RESERVE("11"), {}); + + act(async () => { + await result.current?.api.deleteReserve("11"); + return; + }); + + assertJustExpectedRequestWereMade(env); + + env.addRequestExpectation(API_LIST_RESERVES, { + response: { + reserves: [ + { + reserve_pub: "22", + } as MerchantBackend.Tips.ReserveStatusEntry, + { + reserve_pub: "33", + } as MerchantBackend.Tips.ReserveStatusEntry, + ], + }, + }); + + expect(result.current.query.loading).toBeFalsy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + + expect(result.current.query.data).toEqual({ + reserves: [ + { + reserve_pub: "22", + } as MerchantBackend.Tips.ReserveStatusEntry, + { + reserve_pub: "33", + } as MerchantBackend.Tips.ReserveStatusEntry, + ], + }); + }); +}); + +describe("reserve api interaction with details", () => { + it("should evict cache when adding a tip for a specific reserve", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), { + response: { + payto_uri: "payto://here", + tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }], + } as MerchantBackend.Tips.ReserveDetail, + }); + + const { result, waitForNextUpdate } = renderHook( + () => { + const api = useReservesAPI(); + const query = useReserveDetails("11"); + + return { query, api }; + }, + { + wrapper: TestingContext, + } + ); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + payto_uri: "payto://here", + tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }], + }); + + env.addRequestExpectation(API_AUTHORIZE_TIP_FOR_RESERVE("11"), { + request: { + amount: "USD:12", + justification: "not", + next_url: "http://taler.net", + }, + response: { + tip_id: "id2", + taler_tip_uri: "uri", + tip_expiration: { t_s: 1 }, + tip_status_url: "url", + }, + }); + + act(async () => { + await result.current?.api.authorizeTipReserve("11", { + amount: "USD:12", + justification: "not", + next_url: "http://taler.net", + }); + }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + + env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), { + response: { + payto_uri: "payto://here", + tips: [ + { reason: "why?", tip_id: "id1", total_amount: "USD:10" }, + { reason: "not", tip_id: "id2", total_amount: "USD:12" }, + ], + } as MerchantBackend.Tips.ReserveDetail, + }); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + + expect(result.current.query.data).toEqual({ + payto_uri: "payto://here", + tips: [ + { reason: "why?", tip_id: "id1", total_amount: "USD:10" }, + { reason: "not", tip_id: "id2", total_amount: "USD:12" }, + ], + }); + }); + + it("should evict cache when adding a tip for a random reserve", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), { + response: { + payto_uri: "payto://here", + tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }], + } as MerchantBackend.Tips.ReserveDetail, + }); + + const { result, waitForNextUpdate } = renderHook( + () => { + const api = useReservesAPI(); + const query = useReserveDetails("11"); + + return { query, api }; + }, + { + wrapper: TestingContext, + } + ); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + payto_uri: "payto://here", + tips: [{ reason: "why?", tip_id: "id1", total_amount: "USD:10" }], + }); + + env.addRequestExpectation(API_AUTHORIZE_TIP, { + request: { + amount: "USD:12", + justification: "not", + next_url: "http://taler.net", + }, + response: { + tip_id: "id2", + taler_tip_uri: "uri", + tip_expiration: { t_s: 1 }, + tip_status_url: "url", + }, + }); + + act(async () => { + await result.current?.api.authorizeTip({ + amount: "USD:12", + justification: "not", + next_url: "http://taler.net", + }); + }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + + env.addRequestExpectation(API_GET_RESERVE_BY_ID("11"), { + response: { + payto_uri: "payto://here", + tips: [ + { reason: "why?", tip_id: "id1", total_amount: "USD:10" }, + { reason: "not", tip_id: "id2", total_amount: "USD:12" }, + ], + } as MerchantBackend.Tips.ReserveDetail, + }); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + + expect(result.current.query.data).toEqual({ + payto_uri: "payto://here", + tips: [ + { reason: "why?", tip_id: "id1", total_amount: "USD:10" }, + { reason: "not", tip_id: "id2", total_amount: "USD:12" }, + ], + }); + }); +}); + +describe("reserve api interaction with tip details", () => { + it("should list tips", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_GET_TIP_BY_ID("11"), { + response: { + total_picked_up: "USD:12", + reason: "not", + } as MerchantBackend.Tips.TipDetails, + }); + + const { result, waitForNextUpdate } = renderHook( + () => { + // const api = useReservesAPI(); + const query = useTipDetails("11"); + + return { query }; + }, + { + wrapper: TestingContext, + } + ); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + expect(result.current.query.loading).toBeTruthy(); + + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + total_picked_up: "USD:12", + reason: "not", + }); + }); +}); diff --git a/packages/merchant-backoffice-ui/tests/hooks/swr/transfer.test.ts b/packages/merchant-backoffice-ui/tests/hooks/swr/transfer.test.ts new file mode 100644 index 000000000..612cf8842 --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/hooks/swr/transfer.test.ts @@ -0,0 +1,268 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { act, renderHook } from "@testing-library/preact-hooks"; +import { TestingContext } from "./index"; +import { useInstanceTransfers, useTransferAPI } from "../../../src/hooks/transfer"; +import { + API_INFORM_TRANSFERS, + API_LIST_TRANSFERS, + assertJustExpectedRequestWereMade, + assertNoMoreRequestWereMade, + AxiosMockEnvironment, +} from "../../axiosMock"; +import { MerchantBackend } from "../../../src/declaration"; + +describe("transfer api interaction with listing", () => { + + it("should evict cache when informing a transfer", async () => { + const env = new AxiosMockEnvironment(); + + env.addRequestExpectation(API_LIST_TRANSFERS, { + qparam: { limit: 0 }, + response: { + transfers: [{ wtid: "2" } as MerchantBackend.Transfers.TransferDetails], + }, + }); + // FIXME: is this query really needed? if the hook is rendered without + // position argument then then backend is returning the newest and no need + // to this second query + env.addRequestExpectation(API_LIST_TRANSFERS, { + qparam: { limit: -20 }, + response: { + transfers: [], + }, + }); + + const { result, waitForNextUpdate } = renderHook(() => { + const moveCursor = (d: string) => { + console.log("new position", d); + }; + const query = useInstanceTransfers({}, moveCursor); + const api = useTransferAPI(); + + return { query, api }; + }, { wrapper: TestingContext }); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + + expect(result.current.query.loading).toBeTruthy(); + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + if (!result.current.query.ok) return; + + expect(result.current.query.data).toEqual({ + transfers: [{ wtid: "2" }], + }); + + env.addRequestExpectation(API_INFORM_TRANSFERS, { + request: { + wtid: '3', + credit_amount: 'EUR:1', + exchange_url: 'exchange.url', + payto_uri: 'payto://' + }, + response: { total: '' } as any, + }); + + env.addRequestExpectation(API_LIST_TRANSFERS, { + qparam: { limit: 0 }, + response: { + transfers: [{ wtid: "2" } as any, { wtid: "3" } as any], + }, + }); + + env.addRequestExpectation(API_LIST_TRANSFERS, { + qparam: { limit: -20 }, + response: { + transfers: [], + }, + }); + + act(async () => { + await result.current?.api.informTransfer({ + wtid: '3', + credit_amount: 'EUR:1', + exchange_url: 'exchange.url', + payto_uri: 'payto://' + }); + }); + + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.loading).toBeFalsy(); + expect(result.current.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + transfers: [{ wtid: "3" }, { wtid: "2" }], + }); + }); + +}); + +describe("transfer listing pagination", () => { + + it("should not load more if has reach the end", async () => { + const env = new AxiosMockEnvironment(); + env.addRequestExpectation(API_LIST_TRANSFERS, { + qparam: { limit: 0, payto_uri: 'payto://' }, + response: { + transfers: [{ wtid: "2" } as any], + }, + }); + + env.addRequestExpectation(API_LIST_TRANSFERS, { + qparam: { limit: -20, payto_uri: 'payto://' }, + response: { + transfers: [{ wtid: "1" } as any], + }, + }); + + + const { result, waitForNextUpdate } = renderHook(() => { + const moveCursor = (d: string) => { + console.log("new position", d); + }; + const query = useInstanceTransfers({ payto_uri: 'payto://' }, moveCursor) + return { query } + }, { wrapper: TestingContext }); + + assertJustExpectedRequestWereMade(env); + + await waitForNextUpdate(); + + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + transfers: [{ wtid: "2" }, { wtid: "1" }], + }); + + expect(result.current.query.isReachingEnd).toBeTruthy() + expect(result.current.query.isReachingStart).toBeTruthy() + + await act(() => { + if (!result.current?.query.ok) throw Error("not ok"); + result.current.query.loadMore(); + }); + assertNoMoreRequestWereMade(env); + + await act(() => { + if (!result.current?.query.ok) throw Error("not ok"); + result.current.query.loadMorePrev(); + }); + assertNoMoreRequestWereMade(env); + + expect(result.current.query.data).toEqual({ + transfers: [ + { wtid: "2" }, + { wtid: "1" }, + ], + }); + }); + + it("should load more if result brings more that PAGE_SIZE", async () => { + const env = new AxiosMockEnvironment(); + + const transfersFrom0to20 = Array.from({ length: 20 }).map((e, i) => ({ wtid: String(i) })) + const transfersFrom20to40 = Array.from({ length: 20 }).map((e, i) => ({ wtid: String(i + 20) })) + const transfersFrom20to0 = [...transfersFrom0to20].reverse() + + env.addRequestExpectation(API_LIST_TRANSFERS, { + qparam: { limit: 20, payto_uri: 'payto://' }, + response: { + transfers: transfersFrom0to20, + }, + }); + + env.addRequestExpectation(API_LIST_TRANSFERS, { + qparam: { limit: -20, payto_uri: 'payto://' }, + response: { + transfers: transfersFrom20to40, + }, + }); + + const { result, waitForNextUpdate } = renderHook(() => { + const moveCursor = (d: string) => { + console.log("new position", d); + }; + const query = useInstanceTransfers({ payto_uri: 'payto://', position: '1' }, moveCursor) + return { query } + }, { wrapper: TestingContext }); + + assertJustExpectedRequestWereMade(env); + + await waitForNextUpdate({ timeout: 1 }); + + expect(result.current?.query.ok).toBeTruthy(); + if (!result.current?.query.ok) return; + + expect(result.current.query.data).toEqual({ + transfers: [...transfersFrom20to0, ...transfersFrom20to40], + }); + + expect(result.current.query.isReachingEnd).toBeFalsy() + expect(result.current.query.isReachingStart).toBeFalsy() + + env.addRequestExpectation(API_LIST_TRANSFERS, { + qparam: { limit: -40, payto_uri: 'payto://', offset: "1" }, + response: { + transfers: [...transfersFrom20to40, { wtid: '41' }], + }, + }); + + await act(() => { + if (!result.current?.query.ok) throw Error("not ok"); + result.current.query.loadMore(); + }); + await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + env.addRequestExpectation(API_LIST_TRANSFERS, { + qparam: { limit: 40, payto_uri: 'payto://', offset: "1" }, + response: { + transfers: [...transfersFrom0to20, { wtid: '-1' }], + }, + }); + + await act(() => { + if (!result.current?.query.ok) throw Error("not ok"); + result.current.query.loadMorePrev(); + }); + await waitForNextUpdate({ timeout: 1 }); + assertJustExpectedRequestWereMade(env); + + expect(result.current.query.data).toEqual({ + transfers: [{ wtid: '-1' }, ...transfersFrom20to0, ...transfersFrom20to40, { wtid: '41' }], + }); + }); + + +}); |