gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-merchant-backoffice] branch master updated (93541e1 -> 0a1be29)


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated (93541e1 -> 0a1be29)
Date: Sat, 19 Feb 2022 10:53:43 +0100

This is an automated email from the git hooks/post-receive script.

ms pushed a change to branch master
in repository merchant-backoffice.

    from 93541e1  Show IBAN after login.
     new 56e0c02  serve public histories from tabs
     new 0a1be29  Implement public histories tabs.

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/bank/src/pages/home/index.tsx    |  67 +++++++------
 packages/bank/tests/__tests__/homepage.js | 152 ++++++++++++++++++------------
 2 files changed, 126 insertions(+), 93 deletions(-)

diff --git a/packages/bank/src/pages/home/index.tsx 
b/packages/bank/src/pages/home/index.tsx
index d56e1d2..32aed84 100644
--- a/packages/bank/src/pages/home/index.tsx
+++ b/packages/bank/src/pages/home/index.tsx
@@ -509,18 +509,8 @@ async function createWithdrawalCall(
   try {
     const { username, password } = backendState;
     let headers = prepareHeaders(username, password);
-    /**
-     * NOTE: tests show that when a same object is being
-     * POSTed, caching might prevent same requests from being
-     * made.  Hence, trying to POST twice the same amount might
-     * get silently ignored.
-     *
-     * headers.append("cache-control", "no-store");
-     * headers.append("cache-control", "no-cache");
-     * headers.append("pragma", "no-cache");
-     * */
 
-    // Backend URL must have been stored _with_ a final slash.
+    // Let bank generate withdraw URI:
     const url = new URL(
       `access-api/accounts/${backendState.username}/withdrawals`,
       backendState.url
@@ -562,8 +552,7 @@ async function loginCall(
   req: CredentialsRequestType,
   /**
    * FIXME: figure out if the two following
-   * functions can be retrieved somewhat from
-   * the state.
+   * functions can be retrieved from the state.
    */
   backendStateSetter: StateUpdater<BackendStateTypeOpt>,
   pageStateSetter: StateUpdater<PageStateType>
@@ -572,10 +561,7 @@ async function loginCall(
   /**
    * Optimistically setting the state as 'logged in', and
    * let the Account component request the balance to check
-   * whether the credentials are valid.  If not, then Account
-   * will switch the state back to 'logged out' (and the user
-   * will see again the login/register form).
-   */
+   * whether the credentials are valid.  */
   pageStateSetter((prevState) => ({ ...prevState, isLoggedIn: true }));
   let baseUrl = getRootPath();
   if (!baseUrl.endsWith('/')) {
@@ -635,7 +621,7 @@ async function registrationCall(
     return;
   }
   if (!res.ok) {
-    const errorRaw = JSON.stringify(await res.json());
+    const errorRaw = await res.text();
     console.log(`New registration gave response error (${res.status})`, 
errorRaw);
     pageStateSetter((prevState) => ({
       ...prevState,
@@ -717,7 +703,7 @@ function TalerWithdrawal(Props: any): VNode {
       submitAmount = submitAmount.replace(",", "."); // tolerating comma 
instead of point.
       const re = RegExp(amountRegex)
       if (!re.test(submitAmount)) {
-       console.log("Not withdrawing invalid amount", submitAmount);
+       console.log(`Not withdrawing invalid amount '${submitAmount}'.`);
         return;
       }
       console.log("Valid amount", submitAmount);
@@ -842,9 +828,11 @@ function Transactions(Props: any): VNode {
       }
     }
   }
-  if (!data) return <p>"Transactions page loading..."</p>;
-
-  console.log("History data", data);
+  if (!data) {
+    console.log(`History data of ${accountLabel} not arrived`);
+    return <p>"Transactions page loading..."</p>;
+  }
+  console.log(`History data of ${accountLabel}`, data);
   return <ul>{
     data.transactions.map(function(item: any) {
       const sign = item.direction == "DBIT" ? "-" : "";
@@ -934,6 +922,7 @@ function Account(Props: any): VNode {
    * the outcome.
    */
   if (talerWithdrawUri) {
+    console.log(`Showing withdraw URI: ${talerWithdrawUri}`);
     return (<Fragment>
       <p>Scan the following QR code, and then confirm!</p>
       <div>{QR({text: talerWithdrawUri})}</div>
@@ -1005,7 +994,8 @@ function SWRWithoutCredentials(Props: any): VNode {
 /**
  * Show histories of public accounts.
  */
-function PublicHistories(): VNode {
+function PublicHistories(Props: any): VNode {
+  const [showAccount, setShowAccount] = useState<string | undefined>();
   const { data, error } = useSWR("access-api/public-accounts")
   if (typeof error !== "undefined") {
     console.log("account error", error);
@@ -1019,17 +1009,31 @@ function PublicHistories(): VNode {
     }
   }
   if (!data) return <p>Waiting public accounts list...</p>
-  var txs = [];
+  var txs: any = {};
+  var accountsBar = [];
+
+  // Ask first story of all the public accounts.
   for (const account of data.publicAccounts) {
     console.log("Asking transactions for", account.accountLabel)
-    txs.push(
+    accountsBar.push(
+      <li><a onClick={() => 
setShowAccount(account.accountLabel)}>{account.accountLabel}</a></li>
+    );
+    txs[account.accountLabel] = 
       <div>{account.accountLabel} latest transactions:
         <Transactions accountLabel={account.accountLabel} pageNumber={0} />
       </div>
-    )
   }
+  /**
+   * Show the account specified in the props, or just one
+   * from the list if that's not given.
+   */
+  if (typeof showAccount === "undefined" && Object.keys(txs).length > 0)
+    setShowAccount(Object.keys(txs).pop());
+  console.log(`Public history tab: ${showAccount}`);
   return <Fragment>
-    {txs.length !== 0 ? txs : <p>No public transactions found.</p>}
+    <ul>{accountsBar}</ul>
+    {typeof showAccount !== "undefined" ? txs[showAccount] : <p>No public 
transactions found.</p>}
+    {Props.children}
   </Fragment>;
 }
 
@@ -1055,10 +1059,11 @@ export function BankHome(): VNode {
 
   if (pageState.showPublicHistories) {
     return (<SWRWithoutCredentials baseUrl={getRootPath()}>
-      <PublicHistories />
-      <a onClick={() => {
-        pageStateSetter((prevState: PageStateType) =>
-          ({...prevState, showPublicHistories: false}))}}>Go back</a>
+      <PublicHistories>
+        <a onClick={() => {
+          pageStateSetter((prevState: PageStateType) =>
+            ({...prevState, showPublicHistories: false}))}}>Go back</a>
+      </PublicHistories>
     </SWRWithoutCredentials>);
   }
 
diff --git a/packages/bank/tests/__tests__/homepage.js 
b/packages/bank/tests/__tests__/homepage.js
index 039bab1..9ea0ed4 100644
--- a/packages/bank/tests/__tests__/homepage.js
+++ b/packages/bank/tests/__tests__/homepage.js
@@ -26,35 +26,21 @@ beforeAll(() => {
   global.Storage.prototype.setItem = jest.fn((key, value) => {})
 })
 
-/**
- * Insert username and password into the registration
- * form and returns the submit button.  NOTE: the username
- * must be given always fresh, as it acts as a SWR key and
- * therefore might prevent calls from being made, because of
- * caching reasons.  That is not a problem per-se but can
- * disrupt ".toHaveLastBeenCalledWith()"-like asserts.
- *
- * Return the username and the submit button.
- */
 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 signupButton = screen.getByText("Sign up");
-  const signinButton = screen.getByText("Sign in");
+  const signinButton = screen.getByText("Login");
   return {
     username: username,
-    signupButton: signupButton,
     signinButton: signinButton
   };
 }
 fetchMock.enableMocks();
 
-function signUp(context) {
-  render(<BankHome />);
-  const { username, signupButton } = fillCredentialsForm();
+function mockSuccessLoginOrRegistration() {
   fetch.once("{}", {
     status: 200
   }).once(JSON.stringify({
@@ -64,13 +50,31 @@ function signUp(context) {
     },
     paytoUri: "payto://iban/123/ABC"
   }))
-  fireEvent.click(signupButton);
+}
+
+/**
+ * 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({}); // context unused
+    signUp({}, mockSuccessLoginOrRegistration); // context unused
   })
   test("Wire transfer success", async () => {
     const transferButton = screen.getByText("Create wire transfer");
@@ -98,14 +102,16 @@ describe("withdraw", () => {
     cleanup();
   })
 
+
+  let context = {};
   // Register and land on the profile page.
   beforeEach(() => {
-    signUp(context); 
+    context = signUp(context, mockSuccessLoginOrRegistration); 
   })
 
-  let context = {username: null};
-
   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");
@@ -114,6 +120,8 @@ describe("withdraw", () => {
   })
 
   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);
@@ -121,6 +129,8 @@ describe("withdraw", () => {
   })
 
   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",
@@ -143,7 +153,9 @@ describe("withdraw", () => {
   })
 
   test("Successful withdrawal creation and confirmation", async () => {
-    let withdrawButton = screen.getByText("Charge Taler wallet");
+    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"
@@ -151,12 +163,11 @@ describe("withdraw", () => {
     /**
      * 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.
-     */
+     * 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:5"})})
+      expect.objectContaining({body: JSON.stringify({amount: "EUR:10.0"})})
     )
     // assume wallet POSTed the payment details.
     const confirmButton = await screen.findByText("confirm withdrawal", 
{exact: false})
@@ -221,7 +232,11 @@ describe("home page", () => {
     cleanup();
   })
   test("public histories", async () => {
-    // Mock list of public accounts.
+    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",
@@ -281,53 +296,80 @@ describe("home page", () => {
        date: "2000-01-01"
       }]
     }))
-    render(<BankHome />);
+
+    // Navigate to dedicate public histories page.
+    const publicTxsPage = screen.getByText("transactions");
+    fireEvent.click(publicTxsPage);
+
     /**
-     * Check that transacions data appears on the page.
+     * Check that transactions data appears on the page.
      */
     await screen.findByText("reimbursement", {exact: false});
-    await screen.findByText("refund", {exact: false});
     await screen.findByText("bonus", {exact: false});
-    await screen.findByText("donation", {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/foo/transactions?page=0";
+      
"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/bar/transactions?page=0";
+      
"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 () => {
-    render(<BankHome />);
-    let { username, signupButton } = fillCredentialsForm();
-    fetch.mockResponseOnce("Not found", {status: 404})
-    fireEvent.click(signupButton);
+    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: username, password: "bar"}), method: 
"POST"},
+        {body: JSON.stringify({username: context.username, password: "bar"}), 
method: "POST"},
     ))
   })
 
   test("registration network failure", async () => {
-    render(<BankHome />);
-    const { username, signupButton } = fillCredentialsForm();
-    // Mocking network failure.
-    fetch.mockReject("API is down");
-    fireEvent.click(signupButton);
+    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: username, password: "bar"}), method: 
"POST"}
+        {body: JSON.stringify({username: context.username, password: "bar"}), 
method: "POST"}
       ))
   })
   
@@ -391,7 +433,7 @@ describe("home page", () => {
       `http://localhost/demobanks/default/access-api/accounts/${username}`,
       expect.anything()
     )
-    await screen.findByText("balance is EUR:10", {exact: false})
+    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})
@@ -402,26 +444,12 @@ describe("home page", () => {
   })
 
   test("registration success", async () => {
-    render(<BankHome />);
-    const { username, signupButton } = fillCredentialsForm();
-    /**
-     * Mock successful registration and balance request.
-     */
-    fetch.once("{}", {
-      status: 200
-    }).once(JSON.stringify({
-      balance: {
-        amount: "EUR:10",
-       credit_debit_indicator: "credit"
-      },
-      paytoUri: "payto://iban/123/ABC"
-    }))
-    fireEvent.click(signupButton);
+    let context = signUp({}, mockSuccessLoginOrRegistration);
     /**
      * Tests that a balance is shown after the successful
      * registration.
      */
-    await screen.findByText("balance is EUR:10", {exact: false})
+    await screen.findByText("balance is 10 EUR", {exact: false})
     /**
      * The expectation below tests whether the account
      * balance was requested after the successful registration.
@@ -431,7 +459,7 @@ describe("home page", () => {
       expect.anything() // no need to match auth headers.
     )
     expect(fetch).toHaveBeenCalledWith(
-      `http://localhost/demobanks/default/access-api/accounts/${username}`,
+      
`http://localhost/demobanks/default/access-api/accounts/${context.username}`,
       expect.anything() // no need to match auth headers.
     )
   })

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]