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' }], +    }); +  }); + + +});  | 
