aboutsummaryrefslogtreecommitdiff
path: root/packages/demobank-ui/tests
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2022-10-24 10:46:14 +0200
committerFlorian Dold <florian@dold.me>2022-10-24 10:46:14 +0200
commit3e060b80428943c6562250a6ff77eff10a0259b7 (patch)
treed08472bc5ca28621c62ac45b229207d8215a9ea7 /packages/demobank-ui/tests
parentfb52ced35ac872349b0e1062532313662552ff6c (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.ts21
-rw-r--r--packages/demobank-ui/tests/__mocks__/fileMocks.ts3
-rw-r--r--packages/demobank-ui/tests/__mocks__/setupTests.ts6
-rw-r--r--packages/demobank-ui/tests/__tests__/homepage.js466
-rw-r--r--packages/demobank-ui/tests/declarations.d.ts3
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" />