diff options
Diffstat (limited to 'packages/merchant-backoffice-ui/tests')
19 files changed, 3604 insertions, 0 deletions
diff --git a/packages/merchant-backoffice-ui/tests/__mocks__/browserMocks.ts b/packages/merchant-backoffice-ui/tests/__mocks__/browserMocks.ts new file mode 100644 index 000000000..ee6bba505 --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/__mocks__/browserMocks.ts @@ -0,0 +1,42 @@ +/* + 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) + */ + +// Mock Browser API's which are not supported by JSDOM, e.g. ServiceWorker, LocalStorage +/** + * An example how to mock localStorage is given below 👇 + */ + +/* +// Mocks localStorage +const localStorageMock = (function() { + let store = {}; + + return { + getItem: (key) => store[key] || null, + setItem: (key, value) => store[key] = value.toString(), + clear: () => store = {} + }; + +})(); + +Object.defineProperty(window, 'localStorage', { + value: localStorageMock +}); */ diff --git a/packages/merchant-backoffice-ui/tests/__mocks__/fileMocks.ts b/packages/merchant-backoffice-ui/tests/__mocks__/fileMocks.ts new file mode 100644 index 000000000..0c045e9d1 --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/__mocks__/fileMocks.ts @@ -0,0 +1,24 @@ +/* + 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) + */ + +// This fixed an error related to the CSS and loading gif breaking my Jest test +// See https://facebook.github.io/jest/docs/en/webpack.html#handling-static-assets +export default 'test-file-stub'; diff --git a/packages/merchant-backoffice-ui/tests/__mocks__/fileTransformer.js b/packages/merchant-backoffice-ui/tests/__mocks__/fileTransformer.js new file mode 100644 index 000000000..e6193f8fd --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/__mocks__/fileTransformer.js @@ -0,0 +1,31 @@ +/* + 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) +*/ +// fileTransformer.js + +// eslint-disable-next-line @typescript-eslint/no-var-requires +const path = require('path'); + +module.exports = { + process(src, filename, config, options) { + return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; + }, +}; + diff --git a/packages/merchant-backoffice-ui/tests/__mocks__/setupTests.ts b/packages/merchant-backoffice-ui/tests/__mocks__/setupTests.ts new file mode 100644 index 000000000..b08eb7fe6 --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/__mocks__/setupTests.ts @@ -0,0 +1,28 @@ +/* + 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 "regenerator-runtime/runtime"; +// import { configure } from 'enzyme'; +// import Adapter from 'enzyme-adapter-preact-pure'; + +// configure({ +// adapter: new Adapter() +// }); diff --git a/packages/merchant-backoffice-ui/tests/axiosMock.ts b/packages/merchant-backoffice-ui/tests/axiosMock.ts new file mode 100644 index 000000000..13ddab598 --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/axiosMock.ts @@ -0,0 +1,445 @@ +/* + 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 * as axios from 'axios'; +import { MerchantBackend } from '../src/declaration'; +import { mockAxiosOnce, setAxiosRequestAsTestingEnvironment } from '../src/utils/switchableAxios'; +// import { mockAxiosOnce, setAxiosRequestAsTestingEnvironment } from "../src/hooks/backend"; + +export type Query<Req, Res> = (GetQuery | PostQuery | DeleteQuery | PatchQuery) & RequestResponse<Req, Res> + +interface RequestResponse<Req, Res> { + code?: number, +} +interface GetQuery { get: string } +interface PostQuery { post: string } +interface DeleteQuery { delete: string } +interface PatchQuery { patch: string } + + +const JEST_DEBUG_LOG = process.env['JEST_DEBUG_LOG'] !== undefined + +type ExpectationValues = { query: Query<any, any>; params?: { auth?: string, request?: any, qparam?: any, response?: any } } + +type TestValues = [axios.AxiosRequestConfig | undefined, ExpectationValues | undefined] + +const defaultCallback = (actualQuery?: axios.AxiosRequestConfig): axios.AxiosPromise<any> => { + if (JEST_DEBUG_LOG) { + console.log('UNEXPECTED QUERY', actualQuery) + } + throw Error('Default Axios mock callback is called, this mean that the test did a tried to use axios but there was no expectation in place, try using JEST_DEBUG_LOG env') +} + +setAxiosRequestAsTestingEnvironment( + defaultCallback +); + +export class AxiosMockEnvironment { + expectations: Array<{ + query: Query<any, any>, + auth?: string, + params?: { request?: any, qparam?: any, response?: any }, + result: { args: axios.AxiosRequestConfig | undefined } + } | undefined> = [] + // axiosMock: jest.MockedFunction<axios.AxiosStatic> + + addRequestExpectation<RequestType, ResponseType>(expectedQuery: Query<RequestType, ResponseType>, params: { auth?: string, request?: RequestType, qparam?: any, response?: ResponseType }): void { + const result = mockAxiosOnce(function (actualQuery?: axios.AxiosRequestConfig): axios.AxiosPromise { + + if (JEST_DEBUG_LOG) { + console.log('query to the backend is made', actualQuery) + } + if (!expectedQuery) { + return Promise.reject("a query was made but it was not expected") + } + if (JEST_DEBUG_LOG) { + console.log('expected query:', params?.request) + console.log('expected qparams:', params?.qparam) + console.log('sending response:', params?.response) + } + + const responseCode = expectedQuery.code || 200 + + //This response is what buildRequestOk is expecting in file hook/backend.ts + if (responseCode >= 200 && responseCode < 300) { + return Promise.resolve({ + data: params?.response, config: { + data: params?.response, + params: actualQuery?.params || {}, + }, request: { params: actualQuery?.params || {} } + } as any); + } + //This response is what buildRequestFailed is expecting in file hook/backend.ts + return Promise.reject({ + response: { + status: responseCode + }, + request: { + data: params?.response, + params: actualQuery?.params || {}, + } + }) + + } as any) + + this.expectations.push(expectedQuery ? { query: expectedQuery, params, result } : undefined) + } + + getLastTestValues(): TestValues { + const expectedQuery = this.expectations.shift() + + return [ + expectedQuery?.result.args, expectedQuery + ] + } + +} + +export function assertJustExpectedRequestWereMade(env: AxiosMockEnvironment): void { + let size = env.expectations.length + while (size-- > 0) { + assertNextRequest(env) + } + assertNoMoreRequestWereMade(env) +} + +export function assertNoMoreRequestWereMade(env: AxiosMockEnvironment): void { + const [actualQuery, expectedQuery] = env.getLastTestValues() + + expect(actualQuery).toBeUndefined(); + expect(expectedQuery).toBeUndefined(); +} + +export function assertNextRequest(env: AxiosMockEnvironment): void { + const [actualQuery, expectedQuery] = env.getLastTestValues() + + if (!actualQuery) { + //expected one query but the tested component didn't execute one + expect(actualQuery).toBe(expectedQuery); + return + } + + if (!expectedQuery) { + const errorMessage = 'a query was made to the backend but the test explicitly expected no query'; + if (JEST_DEBUG_LOG) { + console.log(errorMessage, actualQuery) + } + throw Error(errorMessage) + } + if ('get' in expectedQuery.query) { + expect(actualQuery.method).toBe('get'); + expect(actualQuery.url).toBe(expectedQuery.query.get); + } + if ('post' in expectedQuery.query) { + expect(actualQuery.method).toBe('post'); + expect(actualQuery.url).toBe(expectedQuery.query.post); + } + if ('delete' in expectedQuery.query) { + expect(actualQuery.method).toBe('delete'); + expect(actualQuery.url).toBe(expectedQuery.query.delete); + } + if ('patch' in expectedQuery.query) { + expect(actualQuery.method).toBe('patch'); + expect(actualQuery.url).toBe(expectedQuery.query.patch); + } + + if (expectedQuery.params?.request) { + expect(actualQuery.data).toMatchObject(expectedQuery.params.request) + } + if (expectedQuery.params?.qparam) { + expect(actualQuery.params).toMatchObject(expectedQuery.params.qparam) + } + + if (expectedQuery.params?.auth) { + expect(actualQuery.headers.Authorization).toBe(expectedQuery.params?.auth) + } + +} + +//////////////////// +// ORDER +//////////////////// + +export const API_CREATE_ORDER: Query< + MerchantBackend.Orders.PostOrderRequest, + MerchantBackend.Orders.PostOrderResponse +> = { + post: "http://backend/instances/default/private/orders", +}; + +export const API_GET_ORDER_BY_ID = ( + id: string +): Query< + unknown, + MerchantBackend.Orders.MerchantOrderStatusResponse +> => ({ + get: `http://backend/instances/default/private/orders/${id}`, +}); + +export const API_LIST_ORDERS: Query< + unknown, + MerchantBackend.Orders.OrderHistory +> = { + get: "http://backend/instances/default/private/orders", +}; + +export const API_REFUND_ORDER_BY_ID = ( + id: string +): Query< + MerchantBackend.Orders.RefundRequest, + MerchantBackend.Orders.MerchantRefundResponse +> => ({ + post: `http://backend/instances/default/private/orders/${id}/refund`, +}); + +export const API_FORGET_ORDER_BY_ID = ( + id: string +): Query< + MerchantBackend.Orders.ForgetRequest, + unknown +> => ({ + patch: `http://backend/instances/default/private/orders/${id}/forget`, +}); + +export const API_DELETE_ORDER = ( + id: string +): Query< + MerchantBackend.Orders.ForgetRequest, + unknown +> => ({ + delete: `http://backend/instances/default/private/orders/${id}`, +}); + +//////////////////// +// TRANSFER +//////////////////// + +export const API_LIST_TRANSFERS: Query< + unknown, + MerchantBackend.Transfers.TransferList +> = { + get: "http://backend/instances/default/private/transfers", +}; + +export const API_INFORM_TRANSFERS: Query< + MerchantBackend.Transfers.TransferInformation, + MerchantBackend.Transfers.MerchantTrackTransferResponse +> = { + post: "http://backend/instances/default/private/transfers", +}; + +//////////////////// +// PRODUCT +//////////////////// + +export const API_CREATE_PRODUCT: Query< + MerchantBackend.Products.ProductAddDetail, + unknown +> = { + post: "http://backend/instances/default/private/products", +}; + +export const API_LIST_PRODUCTS: Query< + unknown, + MerchantBackend.Products.InventorySummaryResponse +> = { + get: "http://backend/instances/default/private/products", +}; + +export const API_GET_PRODUCT_BY_ID = ( + id: string +): Query<unknown, MerchantBackend.Products.ProductDetail> => ({ + get: `http://backend/instances/default/private/products/${id}`, +}); + +export const API_UPDATE_PRODUCT_BY_ID = ( + id: string +): Query< + MerchantBackend.Products.ProductPatchDetail, + MerchantBackend.Products.InventorySummaryResponse +> => ({ + patch: `http://backend/instances/default/private/products/${id}`, +}); + +export const API_DELETE_PRODUCT = ( + id: string +): Query< + unknown, unknown +> => ({ + delete: `http://backend/instances/default/private/products/${id}`, +}); + +//////////////////// +// RESERVES +//////////////////// + +export const API_CREATE_RESERVE: Query< + MerchantBackend.Tips.ReserveCreateRequest, + MerchantBackend.Tips.ReserveCreateConfirmation +> = { + post: "http://backend/instances/default/private/reserves", +}; +export const API_LIST_RESERVES: Query< + unknown, + MerchantBackend.Tips.TippingReserveStatus +> = { + get: "http://backend/instances/default/private/reserves", +}; + +export const API_GET_RESERVE_BY_ID = ( + pub: string +): Query<unknown, MerchantBackend.Tips.ReserveDetail> => ({ + get: `http://backend/instances/default/private/reserves/${pub}`, +}); + +export const API_GET_TIP_BY_ID = ( + pub: string +): Query< + unknown, + MerchantBackend.Tips.TipDetails +> => ({ + get: `http://backend/instances/default/private/tips/${pub}`, +}); + +export const API_AUTHORIZE_TIP_FOR_RESERVE = ( + pub: string +): Query< + MerchantBackend.Tips.TipCreateRequest, + MerchantBackend.Tips.TipCreateConfirmation +> => ({ + post: `http://backend/instances/default/private/reserves/${pub}/authorize-tip`, +}); + +export const API_AUTHORIZE_TIP: Query< + MerchantBackend.Tips.TipCreateRequest, + MerchantBackend.Tips.TipCreateConfirmation +> = ({ + post: `http://backend/instances/default/private/tips`, +}); + + +export const API_DELETE_RESERVE = ( + id: string +): Query<unknown, unknown> => ({ + delete: `http://backend/instances/default/private/reserves/${id}`, +}); + + +//////////////////// +// INSTANCE ADMIN +//////////////////// + +export const API_CREATE_INSTANCE: Query< + MerchantBackend.Instances.InstanceConfigurationMessage, + unknown +> = { + post: "http://backend/management/instances", +}; + +export const API_GET_INSTANCE_BY_ID = ( + id: string +): Query< + unknown, + MerchantBackend.Instances.QueryInstancesResponse +> => ({ + get: `http://backend/management/instances/${id}`, +}); + +export const API_GET_INSTANCE_KYC_BY_ID = ( + id: string +): Query< + unknown, + MerchantBackend.Instances.AccountKycRedirects +> => ({ + get: `http://backend/management/instances/${id}/kyc`, +}); + +export const API_LIST_INSTANCES: Query< + unknown, + MerchantBackend.Instances.InstancesResponse +> = { + get: "http://backend/management/instances", +}; + +export const API_UPDATE_INSTANCE_BY_ID = ( + id: string +): Query< + MerchantBackend.Instances.InstanceReconfigurationMessage, + unknown +> => ({ + patch: `http://backend/management/instances/${id}`, +}); + +export const API_UPDATE_INSTANCE_AUTH_BY_ID = ( + id: string +): Query< + MerchantBackend.Instances.InstanceAuthConfigurationMessage, + unknown +> => ({ + post: `http://backend/management/instances/${id}/auth`, +}); + +export const API_DELETE_INSTANCE = ( + id: string +): Query<unknown, unknown> => ({ + delete: `http://backend/management/instances/${id}`, +}); + +//////////////////// +// INSTANCE +//////////////////// + +export const API_GET_CURRENT_INSTANCE: Query< + unknown, + MerchantBackend.Instances.QueryInstancesResponse +> = ({ + get: `http://backend/instances/default/private/`, +}); + +export const API_GET_CURRENT_INSTANCE_KYC: Query< + unknown, + MerchantBackend.Instances.AccountKycRedirects +> = + ({ + get: `http://backend/instances/default/private/kyc`, + }); + +export const API_UPDATE_CURRENT_INSTANCE: Query< + MerchantBackend.Instances.InstanceReconfigurationMessage, + unknown +> = { + patch: `http://backend/instances/default/private/`, +}; + +export const API_UPDATE_CURRENT_INSTANCE_AUTH: Query< + MerchantBackend.Instances.InstanceAuthConfigurationMessage, + unknown +> = { + post: `http://backend/instances/default/private/auth`, +}; + +export const API_DELETE_CURRENT_INSTANCE: Query< + unknown, + unknown +> = ({ + delete: `http://backend/instances/default/private`, +}); + + diff --git a/packages/merchant-backoffice-ui/tests/context/backend.test.tsx b/packages/merchant-backoffice-ui/tests/context/backend.test.tsx new file mode 100644 index 000000000..b7b50fd47 --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/context/backend.test.tsx @@ -0,0 +1,172 @@ +/* + 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 { ComponentChildren, h, VNode } from "preact"; +import { act } from "preact/test-utils"; +import { BackendContextProvider } from "../../src/context/backend"; +import { InstanceContextProvider } from "../../src/context/instance"; +import { MerchantBackend } from "../../src/declaration"; +import { + useAdminAPI, + useInstanceAPI, + useManagementAPI, +} from "../../src/hooks/instance"; +import { + API_CREATE_INSTANCE, + API_GET_CURRENT_INSTANCE, + API_UPDATE_CURRENT_INSTANCE_AUTH, + API_UPDATE_INSTANCE_AUTH_BY_ID, + assertJustExpectedRequestWereMade, + AxiosMockEnvironment, +} from "../axiosMock"; + +interface TestingContextProps { + children?: ComponentChildren; +} + +function TestingContext({ children }: TestingContextProps): VNode { + return ( + <BackendContextProvider defaultUrl="http://backend" initialToken="token"> + {children} + </BackendContextProvider> + ); +} +function AdminTestingContext({ children }: TestingContextProps): VNode { + return ( + <BackendContextProvider defaultUrl="http://backend" initialToken="token"> + <InstanceContextProvider + value={{ + token: "token", + id: "default", + admin: true, + changeToken: () => null, + }} + > + {children} + </InstanceContextProvider> + </BackendContextProvider> + ); +} + +describe("backend context api ", () => { + it("should use new token after updating the instance token in the settings as user", async () => { + const env = new AxiosMockEnvironment(); + + const { result, waitForNextUpdate } = renderHook( + () => { + const instance = useInstanceAPI(); + const management = useManagementAPI("default"); + const admin = useAdminAPI(); + + return { instance, management, admin }; + }, + { wrapper: TestingContext } + ); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + + env.addRequestExpectation(API_UPDATE_INSTANCE_AUTH_BY_ID("default"), { + request: { + method: "token", + token: "another_token", + }, + response: { + name: "instance_name", + } as MerchantBackend.Instances.QueryInstancesResponse, + }); + + await act(async () => { + await result.current?.management.setNewToken("another_token"); + }); + + // await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + env.addRequestExpectation(API_CREATE_INSTANCE, { + auth: "Bearer another_token", + request: { + id: "new_instance_id", + } as MerchantBackend.Instances.InstanceConfigurationMessage, + }); + + result.current.admin.createInstance({ + id: "new_instance_id", + } as MerchantBackend.Instances.InstanceConfigurationMessage); + + assertJustExpectedRequestWereMade(env); + }); + + it("should use new token after updating the instance token in the settings as admin", async () => { + const env = new AxiosMockEnvironment(); + + const { result, waitForNextUpdate } = renderHook( + () => { + const instance = useInstanceAPI(); + const management = useManagementAPI("default"); + const admin = useAdminAPI(); + + return { instance, management, admin }; + }, + { wrapper: AdminTestingContext } + ); + + if (!result.current) { + expect(result.current).toBeDefined(); + return; + } + + env.addRequestExpectation(API_UPDATE_CURRENT_INSTANCE_AUTH, { + request: { + method: "token", + token: "another_token", + }, + response: { + name: "instance_name", + } as MerchantBackend.Instances.QueryInstancesResponse, + }); + + await act(async () => { + await result.current?.instance.setNewToken("another_token"); + }); + + // await waitForNextUpdate({ timeout: 1 }); + + assertJustExpectedRequestWereMade(env); + + env.addRequestExpectation(API_CREATE_INSTANCE, { + auth: "Bearer another_token", + request: { + id: "new_instance_id", + } as MerchantBackend.Instances.InstanceConfigurationMessage, + }); + + result.current.admin.createInstance({ + id: "new_instance_id", + } as MerchantBackend.Instances.InstanceConfigurationMessage); + + assertJustExpectedRequestWereMade(env); + }); +}); diff --git a/packages/merchant-backoffice-ui/tests/declarations.d.ts b/packages/merchant-backoffice-ui/tests/declarations.d.ts new file mode 100644 index 000000000..61a53dc69 --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/declarations.d.ts @@ -0,0 +1,28 @@ +/* + 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) + */ + +declare global { + namespace jest { + interface Matchers<R> { + toBeWithinRange(a: number, b: number): R; + } + } +} diff --git a/packages/merchant-backoffice-ui/tests/functions/regex.test.ts b/packages/merchant-backoffice-ui/tests/functions/regex.test.ts new file mode 100644 index 000000000..fc8a6a42f --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/functions/regex.test.ts @@ -0,0 +1,87 @@ +/* + 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 { AMOUNT_REGEX, PAYTO_REGEX } from "../../src/utils/constants"; + +describe('payto uri format', () => { + const valids = [ + 'payto://iban/DE75512108001245126199?amount=EUR:200.0&message=hello', + 'payto://ach/122000661/1234', + 'payto://upi/alice@example.com?receiver-name=Alice&amount=INR:200', + 'payto://void/?amount=EUR:10.5', + 'payto://ilp/g.acme.bob' + ] + + test('should be valid', () => { + valids.forEach(v => expect(v).toMatch(PAYTO_REGEX)) + }); + + const invalids = [ + // has two question marks + 'payto://iban/DE75?512108001245126199?amount=EUR:200.0&message=hello', + // has a space + 'payto://ach /122000661/1234', + // has a space + 'payto://upi/alice@ example.com?receiver-name=Alice&amount=INR:200', + // invalid field name (mount instead of amount) + 'payto://void/?mount=EUR:10.5', + // payto:// is incomplete + 'payto: //ilp/g.acme.bob' + ] + + test('should not be valid', () => { + invalids.forEach(v => expect(v).not.toMatch(PAYTO_REGEX)) + }); +}) + +describe('amount format', () => { + const valids = [ + 'ARS:10', + 'COL:10.2', + 'UY:1,000.2', + 'ARS:10.123,123', + 'ARS:1,000,000', + 'ARSCOL:10', + 'THISISTHEMOTHERCOIN:1,000,000.123,123', + ] + + test('should be valid', () => { + valids.forEach(v => expect(v).toMatch(AMOUNT_REGEX)) + }); + + const invalids = [ + //no currency name + ':10', + //use . instead of , + 'ARS:1.000.000', + //currency name with numbers + '1ARS:10', + //currency name with numbers + 'AR5:10', + //missing value + 'USD:', + ] + + test('should not be valid', () => { + invalids.forEach(v => expect(v).not.toMatch(AMOUNT_REGEX)) + }); + +})
\ No newline at end of file diff --git a/packages/merchant-backoffice-ui/tests/header.test.tsx b/packages/merchant-backoffice-ui/tests/header.test.tsx new file mode 100644 index 000000000..f098b70d5 --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/header.test.tsx @@ -0,0 +1,63 @@ +/* + 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 { h } from "preact"; +import { ProductList } from "../src/components/product/ProductList"; +// See: https://github.com/preactjs/enzyme-adapter-preact-pure +// import { shallow } from 'enzyme'; +import * as backend from "../src/context/config"; +import { render, findAllByText } from "@testing-library/preact"; +import * as i18n from "../src/context/translation"; + +import * as jedLib from "jed"; +const handler = new jedLib.Jed("en"); + +describe("Initial Test of the Sidebar", () => { + beforeEach(() => { + jest + .spyOn(backend, "useConfigContext") + .mockImplementation(() => ({ version: "", currency: "" })); + jest.spyOn(i18n, "useTranslationContext").mockImplementation(() => ({ + changeLanguage: () => null, + handler, + lang: "en", + })); + }); + test("Product list renders a table", () => { + const context = render( + <ProductList + list={[ + { + description: "description of the product", + image: "asdasda", + price: "USD:10", + quantity: 1, + taxes: [{ name: "VAT", tax: "EUR:1" }], + unit: "book", + }, + ]} + /> + ); + + expect(context.findAllByText("description of the product")).toBeDefined(); + // expect(context.find('table tr td img').map(img => img.prop('src'))).toEqual(''); + }); +}); 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' }], + }); + }); + + +}); diff --git a/packages/merchant-backoffice-ui/tests/stories.test.tsx b/packages/merchant-backoffice-ui/tests/stories.test.tsx new file mode 100644 index 000000000..5fb3483d2 --- /dev/null +++ b/packages/merchant-backoffice-ui/tests/stories.test.tsx @@ -0,0 +1,89 @@ +/* + 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 { h, VNode } from "preact"; +import * as config from "../src/context/config"; +import * as i18n from "../src/context/translation"; +import { cleanup, render as originalRender } from "@testing-library/preact"; +import { SWRConfig } from "swr"; + +import fs from "fs"; + +function getFiles(dir: string, files_: string[] = []) { + const files = fs.readdirSync(dir); + for (const i in files) { + const name = dir + "/" + files[i]; + if (fs.statSync(name).isDirectory()) { + getFiles(name, files_); + } else { + files_.push(name); + } + } + return files_; +} + +const STORIES_NAME_REGEX = RegExp(".*.stories.tsx"); + +function render(vnode: VNode) { + return originalRender( + <SWRConfig + value={{ + provider: () => new Map(), + }} + > + {vnode} + </SWRConfig> + ); +} + +import * as jedLib from "jed"; +const handler = new jedLib.Jed("en"); + +describe("storybook testing", () => { + it("render every story", () => { + jest + .spyOn(config, "useConfigContext") + .mockImplementation(() => ({ version: "1.0.0", currency: "EUR" })); + jest.spyOn(i18n, "useTranslationContext").mockImplementation(() => ({ + changeLanguage: () => null, + handler, + lang: "en", + })); + + getFiles("./src") + .filter((f) => STORIES_NAME_REGEX.test(f)) + .map((f) => { + // const f = "./src/paths/instance/transfers/list/List.stories.tsx"; + // eslint-disable-next-line @typescript-eslint/no-var-requires + const s = require(`../${f}`); + + delete s.default; + Object.keys(s).forEach((k) => { + const Component = s[k]; + const vdom = <Component {...Component.args} />; + expect(() => { + const { unmount } = render(vdom); + unmount(); + }).not.toThrow(); //`problem rendering ${f} example ${k}` + cleanup(); + }); + }); + }); +}); |