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();
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();
/**
* 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();
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();
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();
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.
)
})
})