diff options
author | Florian Dold <florian@dold.me> | 2022-10-24 10:46:14 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2022-10-24 10:46:14 +0200 |
commit | 3e060b80428943c6562250a6ff77eff10a0259b7 (patch) | |
tree | d08472bc5ca28621c62ac45b229207d8215a9ea7 /packages/demobank-ui/tests | |
parent | fb52ced35ac872349b0e1062532313662552ff6c (diff) |
repo: integrate packages from former merchant-backoffice.git
Diffstat (limited to 'packages/demobank-ui/tests')
-rw-r--r-- | packages/demobank-ui/tests/__mocks__/browserMocks.ts | 21 | ||||
-rw-r--r-- | packages/demobank-ui/tests/__mocks__/fileMocks.ts | 3 | ||||
-rw-r--r-- | packages/demobank-ui/tests/__mocks__/setupTests.ts | 6 | ||||
-rw-r--r-- | packages/demobank-ui/tests/__tests__/homepage.js | 466 | ||||
-rw-r--r-- | packages/demobank-ui/tests/declarations.d.ts | 3 |
5 files changed, 499 insertions, 0 deletions
diff --git a/packages/demobank-ui/tests/__mocks__/browserMocks.ts b/packages/demobank-ui/tests/__mocks__/browserMocks.ts new file mode 100644 index 000000000..5be8c3ce6 --- /dev/null +++ b/packages/demobank-ui/tests/__mocks__/browserMocks.ts @@ -0,0 +1,21 @@ +// 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/demobank-ui/tests/__mocks__/fileMocks.ts b/packages/demobank-ui/tests/__mocks__/fileMocks.ts new file mode 100644 index 000000000..87109e355 --- /dev/null +++ b/packages/demobank-ui/tests/__mocks__/fileMocks.ts @@ -0,0 +1,3 @@ +// 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/demobank-ui/tests/__mocks__/setupTests.ts b/packages/demobank-ui/tests/__mocks__/setupTests.ts new file mode 100644 index 000000000..b0bebb589 --- /dev/null +++ b/packages/demobank-ui/tests/__mocks__/setupTests.ts @@ -0,0 +1,6 @@ +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-preact-pure'; + +configure({ + adapter: new Adapter() as any +}); diff --git a/packages/demobank-ui/tests/__tests__/homepage.js b/packages/demobank-ui/tests/__tests__/homepage.js new file mode 100644 index 000000000..9ea0ed410 --- /dev/null +++ b/packages/demobank-ui/tests/__tests__/homepage.js @@ -0,0 +1,466 @@ +import "core-js/stable"; +import "regenerator-runtime/runtime"; +import "@testing-library/jest-dom"; +import { BankHome } from '../../src/pages/home'; +import { h } from 'preact'; +import { waitFor, cleanup, render, fireEvent, screen } from '@testing-library/preact'; +import expect from 'expect'; +import fetchMock from "jest-fetch-mock"; + +/** + * This mock makes the translator always return the + * english string. It didn't work within the 'beforeAll' + * function... + */ +jest.mock("../../src/i18n") +const i18n = require("../../src/i18n") +i18n.useTranslator.mockImplementation(() => function(arg) {return arg}) + +beforeAll(() => { + Object.defineProperty(window, 'location', { + value: { + origin: "http://localhost", + pathname: "/demobanks/default" + } + }) + global.Storage.prototype.setItem = jest.fn((key, value) => {}) +}) + +function fillCredentialsForm() { + const username = Math.random().toString().substring(2); + const u = screen.getByPlaceholderText("username"); + const p = screen.getByPlaceholderText("password"); + fireEvent.input(u, {target: {value: username}}) + fireEvent.input(p, {target: {value: "bar"}}) + const signinButton = screen.getByText("Login"); + return { + username: username, + signinButton: signinButton + }; +} +fetchMock.enableMocks(); + +function mockSuccessLoginOrRegistration() { + fetch.once("{}", { + status: 200 + }).once(JSON.stringify({ + balance: { + amount: "EUR:10", + credit_debit_indicator: "credit" + }, + paytoUri: "payto://iban/123/ABC" + })) +} + +/** + * Render homepage -> navigate to register page -> submit registration. + * 'webMock' is called before submission to mock the server response + */ +function signUp(context, webMock) { + render(<BankHome />); + const registerPage = screen.getByText("Register!"); + fireEvent.click(registerPage); + const username = Math.random().toString().substring(2); + const u = screen.getByPlaceholderText("username"); + const p = screen.getByPlaceholderText("password"); + fireEvent.input(u, {target: {value: username}}) + fireEvent.input(p, {target: {value: "bar"}}) + const registerButton = screen.getByText("Register"); + webMock(); + fireEvent.click(registerButton); + context.username = username; + return context; +} + +describe("wire transfer", () => { + beforeEach(() => { + signUp({}, mockSuccessLoginOrRegistration); // context unused + }) + test("Wire transfer success", async () => { + const transferButton = screen.getByText("Create wire transfer"); + const payto = screen.getByPlaceholderText("payto address"); + fireEvent.input(payto, {target: {value: "payto://only-checked-by-the-backend!"}}) + fetch.once("{}"); // 200 OK + fireEvent.click(transferButton); + await screen.findByText("wire transfer created", {exact: false}) + }) + test("Wire transfer fail", async () => { + const transferButton = screen.getByText("Create wire transfer"); + const payto = screen.getByPlaceholderText("payto address"); + fireEvent.input(payto, {target: {value: "payto://only-checked-by-the-backend!"}}) + fetch.once("{}", {status: 400}); + fireEvent.click(transferButton); + // assert this below does NOT appear. + await waitFor(() => expect( + screen.queryByText("wire transfer created", {exact: false})).not.toBeInTheDocument()); + }) +}) + +describe("withdraw", () => { + afterEach(() => { + fetch.resetMocks(); + cleanup(); + }) + + + let context = {}; + // Register and land on the profile page. + beforeEach(() => { + context = signUp(context, mockSuccessLoginOrRegistration); + }) + + test("network failure before withdrawal creation", async () => { + const a = screen.getAllByPlaceholderText("amount")[0]; + fireEvent.input(a, {target: {value: "10"}}); + let withdrawButton = screen.getByText("Charge Taler wallet"); + // mock network failure. + fetch.mockReject("API is down"); + fireEvent.click(withdrawButton); + await screen.findByText("could not create withdrawal operation", {exact: false}) + }) + + test("HTTP response error upon withdrawal creation", async () => { + const a = screen.getAllByPlaceholderText("amount")[0]; + fireEvent.input(a, {target: {value: "10,0"}}); + let withdrawButton = screen.getByText("Charge Taler wallet"); + fetch.once("{}", {status: 404}); + fireEvent.click(withdrawButton); + await screen.findByText("gave response error", {exact: false}) + }) + + test("Abort withdrawal", async () => { + const a = screen.getAllByPlaceholderText("amount")[0]; + fireEvent.input(a, {target: {value: "10,0"}}); + let withdrawButton = screen.getByText("Charge Taler wallet"); + fetch.once(JSON.stringify({ + taler_withdraw_uri: "taler://withdraw/foo", + withdrawal_id: "foo" + })); + /** + * After triggering a withdrawal, check if the taler://withdraw URI + * rendered, and confirm if so. Lastly, check that a success message + * appeared on the screen. + */ + fireEvent.click(withdrawButton); + const abortButton = await screen.findByText("abort withdrawal", {exact: false}) + fireEvent.click(abortButton); + expect(fetch).toHaveBeenLastCalledWith( + `http://localhost/demobanks/default/access-api/accounts/${context.username}/withdrawals/foo/abort`, + expect.anything() + ) + await waitFor(() => expect( + screen.queryByText("abort withdrawal", {exact: false})).not.toBeInTheDocument()); + }) + + test("Successful withdrawal creation and confirmation", async () => { + const a = screen.getAllByPlaceholderText("amount")[0]; + fireEvent.input(a, {target: {value: "10,0"}}); + let withdrawButton = await screen.findByText("Charge Taler wallet"); + fetch.once(JSON.stringify({ + taler_withdraw_uri: "taler://withdraw/foo", + withdrawal_id: "foo" + })); + /** + * After triggering a withdrawal, check if the taler://withdraw URI + * rendered, and confirm if so. Lastly, check that a success message + * appeared on the screen. */ + fireEvent.click(withdrawButton); + expect(fetch).toHaveBeenCalledWith( + `http://localhost/demobanks/default/access-api/accounts/${context.username}/withdrawals`, + expect.objectContaining({body: JSON.stringify({amount: "EUR:10.0"})}) + ) + // assume wallet POSTed the payment details. + const confirmButton = await screen.findByText("confirm withdrawal", {exact: false}) + /** + * Not expecting a new withdrawal possibility while one is being processed. + */ + await waitFor(() => expect( + screen.queryByText("charge taler wallet", {exact: false})).not.toBeInTheDocument()); + fetch.once("{}") + // Confirm currently processed withdrawal. + fireEvent.click(confirmButton); + /** + * After having confirmed above, wait that the + * pre-withdrawal elements disappears and a success + * message appears. + */ + await waitFor(() => expect( + screen.queryByText( + "confirm withdrawal", + {exact: false})).not.toBeInTheDocument() + ); + await waitFor(() => expect( + screen.queryByText( + "give this address to the taler wallet", + {exact: false})).not.toBeInTheDocument() + ); + expect(fetch).toHaveBeenLastCalledWith( + `http://localhost/demobanks/default/access-api/accounts/${context.username}/withdrawals/foo/confirm`, + expect.anything()) + // success message + await screen.findByText("withdrawal confirmed", {exact: false}) + + /** + * Click on a "return to homepage / close" button, and + * check that the withdrawal confirmation is gone, and + * the option to withdraw again reappeared. + */ + const closeButton = await screen.findByText("close", {exact: false}) + fireEvent.click(closeButton); + + /** + * After closing the operation, the confirmation message is not expected. + */ + await waitFor(() => expect( + screen.queryByText("withdrawal confirmed", {exact: false})).not.toBeInTheDocument() + ); + + /** + * After closing the operation, the possibility to withdraw again should be offered. + */ + await waitFor(() => expect( + screen.queryByText( + "charge taler wallet", + {exact: false})).toBeInTheDocument() + ); + }) +}) + +describe("home page", () => { + afterEach(() => { + fetch.resetMocks(); + cleanup(); + }) + test("public histories", async () => { + render(<BankHome />); + /** + * Mock list of public accounts. 'bar' is + * the shown account, since it occupies the last + * position (and SPA picks it via the 'pop()' method) */ + fetch.once(JSON.stringify({ + "publicAccounts" : [ { + "balance" : "EUR:1", + "iban" : "XXX", + "accountLabel" : "foo" + }, { + "balance" : "EUR:2", + "iban" : "YYY", + "accountLabel" : "bar" + }] + })).once(JSON.stringify({ + transactions: [{ + debtorIban: "XXX", + debtorBic: "YYY", + debtorName: "Foo", + creditorIban: "AAA", + creditorBic: "BBB", + creditorName: "Bar", + direction: "DBIT", + amount: "EUR:5", + subject: "Reimbursement", + date: "1970-01-01" + }, { + debtorIban: "XXX", + debtorBic: "YYY", + debtorName: "Foo", + creditorIban: "AAA", + creditorBic: "BBB", + creditorName: "Bar", + direction: "CRDT", + amount: "EUR:5", + subject: "Bonus", + date: "2000-01-01" + }] + })).once(JSON.stringify({ + transactions: [{ + debtorIban: "XXX", + debtorBic: "YYY", + debtorName: "Foo", + creditorIban: "AAA", + creditorBic: "BBB", + creditorName: "Bar", + direction: "DBIT", + amount: "EUR:5", + subject: "Donation", + date: "1970-01-01" + }, { + debtorIban: "XXX", + debtorBic: "YYY", + debtorName: "Foo", + creditorIban: "AAA", + creditorBic: "BBB", + creditorName: "Bar", + direction: "CRDT", + amount: "EUR:5", + subject: "Refund", + date: "2000-01-01" + }] + })) + + // Navigate to dedicate public histories page. + const publicTxsPage = screen.getByText("transactions"); + fireEvent.click(publicTxsPage); + + /** + * Check that transactions data appears on the page. + */ + await screen.findByText("reimbursement", {exact: false}); + await screen.findByText("bonus", {exact: false}); + /** + * The transactions below should not appear, because only + * one public account renders. + */ + await waitFor(() => expect( + screen.queryByText("refund", {exact: false})).not.toBeInTheDocument()); + await waitFor(() => expect( + screen.queryByText("donation", {exact: false})).not.toBeInTheDocument()); + /** + * First HTTP mock: + */ + await expect(fetch).toHaveBeenCalledWith( + "http://localhost/demobanks/default/access-api/public-accounts" + ) + /** + * Only expecting this request (second mock), as SWR doesn't let + * the unshown history request to the backend: + */ + await expect(fetch).toHaveBeenCalledWith( + "http://localhost/demobanks/default/access-api/accounts/bar/transactions?page=0" + ) + /** + * Switch tab: + */ + let fooTab = await screen.findByText("foo", {exact: false}); + fireEvent.click(fooTab); + /** + * Last two HTTP mocks should render now: + */ + await screen.findByText("refund", {exact: false}); + await screen.findByText("donation", {exact: false}); + + // Expect SWR to have requested 'foo' history + // (consuming the last HTTP mock): + await expect(fetch).toHaveBeenCalledWith( + "http://localhost/demobanks/default/access-api/accounts/foo/transactions?page=0" + ) + let backButton = await screen.findByText("Go back", {exact: false}); + fireEvent.click(backButton); + await waitFor(() => expect( + screen.queryByText("donation", {exact: false})).not.toBeInTheDocument()); + await screen.findByText("welcome to eufin bank", {exact: false}) + }) + + // check page informs about the current balance + // after a successful registration. + + test("new registration response error 404", async () => { + var context = signUp({}, () => fetch.mockResponseOnce("Not found", {status: 404})); + await screen.findByText("has a problem", {exact: false}); + expect(fetch).toHaveBeenCalledWith( + "http://localhost/demobanks/default/access-api/testing/register", + expect.objectContaining( + {body: JSON.stringify({username: context.username, password: "bar"}), method: "POST"}, + )) + }) + + test("registration network failure", async () => { + let context = signUp({}, ()=>fetch.mockReject("API is down")); + await screen.findByText("has a problem", {exact: false}); + expect(fetch).toHaveBeenCalledWith( + "http://localhost/demobanks/default/access-api/testing/register", + expect.objectContaining( + {body: JSON.stringify({username: context.username, password: "bar"}), method: "POST"} + )) + }) + + test("login non existent user", async () => { + render(<BankHome />); + const { username, signinButton } = fillCredentialsForm(); + fetch.once("{}", {status: 404}); + fireEvent.click(signinButton); + await screen.findByText("username or account label not found", {exact: false}) + }) + test("login wrong credentials", async () => { + render(<BankHome />); + const { username, signinButton } = fillCredentialsForm(); + fetch.once("{}", {status: 401}); + fireEvent.click(signinButton); + await screen.findByText("wrong credentials given", {exact: false}) + }) + + /** + * Test that balance and last transactions get shown + * after a successful login. + */ + test("login success", async () => { + render(<BankHome />); + const { username, signinButton } = fillCredentialsForm(); + + // Response to balance request. + fetch.once(JSON.stringify({ + balance: { + amount: "EUR:10", + credit_debit_indicator: "credit" + }, + paytoUri: "payto://iban/123/ABC" + })).once(JSON.stringify({ // Response to history request. + transactions: [{ + debtorIban: "XXX", + debtorBic: "YYY", + debtorName: "Foo", + creditorIban: "AAA", + creditorBic: "BBB", + creditorName: "Bar", + direction: "DBIT", + amount: "EUR:5", + subject: "Donation", + date: "01-01-1970" + }, { + debtorIban: "XXX", + debtorBic: "YYY", + debtorName: "Foo", + creditorIban: "AAA", + creditorBic: "BBB", + creditorName: "Bar", + direction: "CRDT", + amount: "EUR:5", + subject: "Refund", + date: "01-01-2000" + }] + })) + fireEvent.click(signinButton); + expect(fetch).toHaveBeenCalledWith( + `http://localhost/demobanks/default/access-api/accounts/${username}`, + expect.anything() + ) + await screen.findByText("balance is 10 EUR", {exact: false}) + // The two transactions in the history mocked above. + await screen.findByText("refund", {exact: false}) + await screen.findByText("donation", {exact: false}) + expect(fetch).toHaveBeenCalledWith( + `http://localhost/demobanks/default/access-api/accounts/${username}/transactions?page=0`, + expect.anything() + ) + }) + + test("registration success", async () => { + let context = signUp({}, mockSuccessLoginOrRegistration); + /** + * Tests that a balance is shown after the successful + * registration. + */ + await screen.findByText("balance is 10 EUR", {exact: false}) + /** + * The expectation below tests whether the account + * balance was requested after the successful registration. + */ + expect(fetch).toHaveBeenCalledWith( + "http://localhost/demobanks/default/access-api/testing/register", + expect.anything() // no need to match auth headers. + ) + expect(fetch).toHaveBeenCalledWith( + `http://localhost/demobanks/default/access-api/accounts/${context.username}`, + expect.anything() // no need to match auth headers. + ) + }) +}) diff --git a/packages/demobank-ui/tests/declarations.d.ts b/packages/demobank-ui/tests/declarations.d.ts new file mode 100644 index 000000000..67e940277 --- /dev/null +++ b/packages/demobank-ui/tests/declarations.d.ts @@ -0,0 +1,3 @@ +// Enable enzyme adapter's integration with TypeScript +// See: https://github.com/preactjs/enzyme-adapter-preact-pure#usage-with-typescript +/// <reference types="enzyme-adapter-preact-pure" /> |