diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 18d1143db..bb2441701 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -528,7 +528,7 @@ jobs: report_name: Coverage Backend type: lcov result_path: ./backend/coverage/lcov.info - min_coverage: 54 + min_coverage: 66 token: ${{ github.token }} ########################################################################## diff --git a/.gitignore b/.gitignore index b02b9d6ec..08ccd2b30 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.dbeaver +.project *.log /node_modules/* messages.pot @@ -11,3 +13,9 @@ package-lock.json /deployment/bare_metal/nginx/update-page/updating.html /deployment/bare_metal/log /deployment/bare_metal/backup + +# Node Version Manager configuration file +.nvmrc + +# Apple macOS folder attribute file +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c64df990..48eeff9a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,184 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [1.8.3](https://github.com/gradido/gradido/compare/1.8.2...1.8.3) + +- Checkbox [`#1894`](https://github.com/gradido/gradido/pull/1894) +- fix: Count Deprecated Links as Well [`#1892`](https://github.com/gradido/gradido/pull/1892) + +#### [1.8.2](https://github.com/gradido/gradido/compare/1.8.1...1.8.2) + +> 12 May 2022 + +- Release 1.8.2 [`#1890`](https://github.com/gradido/gradido/pull/1890) +- Update README.md [`#1878`](https://github.com/gradido/gradido/pull/1878) +- fix: Unique Previous Column in Transactions Table [`#1879`](https://github.com/gradido/gradido/pull/1879) +- fix: Up and Down Migrations for Older SQL Versions [`#1861`](https://github.com/gradido/gradido/pull/1861) +- 🍰 Refactor THX Page – 1. Step [`#1856`](https://github.com/gradido/gradido/pull/1856) +- Create LICENSE [`#1803`](https://github.com/gradido/gradido/pull/1803) +- docu: Update Deployment Documentation [`#1864`](https://github.com/gradido/gradido/pull/1864) +- fix: Loading Transaction Links after Reopening Link List [`#1863`](https://github.com/gradido/gradido/pull/1863) +- 🍰 Add NVM Config Files To '.gitignore' [`#1846`](https://github.com/gradido/gradido/pull/1846) + +#### [1.8.1](https://github.com/gradido/gradido/compare/1.8.0...1.8.1) + +> 28 April 2022 + +- v1.8.1 [`#1855`](https://github.com/gradido/gradido/pull/1855) +- 1851 integrate and test the behaviour of clipboard polyfill [`#1853`](https://github.com/gradido/gradido/pull/1853) +- fix: Deprecated Warning from Faker on Seeding [`#1854`](https://github.com/gradido/gradido/pull/1854) +- feat: Test Admin Resolver [`#1848`](https://github.com/gradido/gradido/pull/1848) +- devops: Disable DB Reset on Stage 1 [`#1852`](https://github.com/gradido/gradido/pull/1852) +- 🍰 Refactor notActivated and isDeleted [`#1791`](https://github.com/gradido/gradido/pull/1791) +- devops: Disable Send Email on Seeding [`#1849`](https://github.com/gradido/gradido/pull/1849) +- fix: Confirm Creation with Decimal [`#1838`](https://github.com/gradido/gradido/pull/1838) +- error message by no navigator.clipbord function [`#1841`](https://github.com/gradido/gradido/pull/1841) + +#### [1.8.0](https://github.com/gradido/gradido/compare/1.7.1...1.8.0) + +> 25 April 2022 + +- v1.8.0 [`#1836`](https://github.com/gradido/gradido/pull/1836) +- Fix: database version requirement for backend corrected [`#1835`](https://github.com/gradido/gradido/pull/1835) +- feat: More User Resolver Tests [`#1827`](https://github.com/gradido/gradido/pull/1827) +- fix: Round Decay with Tranasction Links [`#1834`](https://github.com/gradido/gradido/pull/1834) +- Fix: config value for the redeem URL was missing [`#1828`](https://github.com/gradido/gradido/pull/1828) +- Refactor: Database admin pending creations use decimal [`#1748`](https://github.com/gradido/gradido/pull/1748) +- refactor: Drop Server User Table [`#1808`](https://github.com/gradido/gradido/pull/1808) +- 1816 expired link are not highlighted [`#1821`](https://github.com/gradido/gradido/pull/1821) +- 1812 put qr code into popup on generate [`#1820`](https://github.com/gradido/gradido/pull/1820) +- Docu: Federation image [`#1817`](https://github.com/gradido/gradido/pull/1817) +- 1813 qr code popup [`#1819`](https://github.com/gradido/gradido/pull/1819) +- Fix: cross-env for windows [`#1822`](https://github.com/gradido/gradido/pull/1822) +- fix: Double Load Transaction Links [`#1818`](https://github.com/gradido/gradido/pull/1818) +- Generated link in backend should also give back the base url [`#1745`](https://github.com/gradido/gradido/pull/1745) +- 1731 style startDecayStartblock, style Adapted across pages [`#1809`](https://github.com/gradido/gradido/pull/1809) +- Refactor: Frontend bake in community info [`#1750`](https://github.com/gradido/gradido/pull/1750) +- fix: Load Transaction Link Details on Click [`#1806`](https://github.com/gradido/gradido/pull/1806) +- devops: Deploy Seed in Backend [`#1790`](https://github.com/gradido/gradido/pull/1790) +- refactor: Balance Model and Decay Rounding [`#1780`](https://github.com/gradido/gradido/pull/1780) +- change config DECAY_START_TIME in UTC 0000 [`#1807`](https://github.com/gradido/gradido/pull/1807) +- 1751 make gdt visible only if explicitly clicked [`#1752`](https://github.com/gradido/gradido/pull/1752) +- add Tab system from bootstrap in SearchUserTable Userdata [`#1744`](https://github.com/gradido/gradido/pull/1744) +- Fix: Certbot renewal [`#1789`](https://github.com/gradido/gradido/pull/1789) +- 🍰 Add Wallet Link To Mails [`#1765`](https://github.com/gradido/gradido/pull/1765) +- 1633 display qr code on link in transaction list [`#1661`](https://github.com/gradido/gradido/pull/1661) +- 1755 insert additional text when redeeming [`#1756`](https://github.com/gradido/gradido/pull/1756) +- refactor: Define Context Interface [`#1762`](https://github.com/gradido/gradido/pull/1762) +- fix: Elopage Status [`#1742`](https://github.com/gradido/gradido/pull/1742) +- Refactor: Frontend decay start block as static config value [`#1749`](https://github.com/gradido/gradido/pull/1749) +- better date format for reddem valid date [`#1758`](https://github.com/gradido/gradido/pull/1758) +- add insert shadow in summary links transaction type [`#1754`](https://github.com/gradido/gradido/pull/1754) +- Feature: JWT duration is now 30min by default [`#1747`](https://github.com/gradido/gradido/pull/1747) +- Docu: Scope of Gradido [`#1746`](https://github.com/gradido/gradido/pull/1746) +- fix: Check That Recipient User Has Activated Account to Receive Coins [`#1743`](https://github.com/gradido/gradido/pull/1743) +- Fix: Fixed config dist version to properly reflect new password reset url [`#1737`](https://github.com/gradido/gradido/pull/1737) +- 503 transaction list pagination pages clickable [`#1677`](https://github.com/gradido/gradido/pull/1677) +- if no recipientEmail else form.email [`#1722`](https://github.com/gradido/gradido/pull/1722) +- 1727 change button text and observe spelling [`#1728`](https://github.com/gradido/gradido/pull/1728) +- 1729 load spinner if pending balance [`#1730`](https://github.com/gradido/gradido/pull/1730) +- transaction type remains when jumping from the verification back [`#1724`](https://github.com/gradido/gradido/pull/1724) +- text for toast expand link copied [`#1726`](https://github.com/gradido/gradido/pull/1726) + +#### [1.7.1](https://github.com/gradido/gradido/compare/1.7.0...1.7.1) + +> 1 April 2022 + +- v1.7.1 [`#1721`](https://github.com/gradido/gradido/pull/1721) +- fix: Localize Dates on Redeem Transaction Link Page [`#1720`](https://github.com/gradido/gradido/pull/1720) +- fix: Round Virtual Transaction Link Transaction [`#1718`](https://github.com/gradido/gradido/pull/1718) +- larger icon and deacy information if center [`#1719`](https://github.com/gradido/gradido/pull/1719) +- Fix: restore script load correct .env [`#1717`](https://github.com/gradido/gradido/pull/1717) +- fix-disbled-button-if-totalBalance [`#1716`](https://github.com/gradido/gradido/pull/1716) +- icon droplet-halflarger and correctly positioned [`#1713`](https://github.com/gradido/gradido/pull/1713) +- fix: Clean up Registration Flow [`#1709`](https://github.com/gradido/gradido/pull/1709) +- 1703 submit button disabled when total amount to submit is minus [`#1705`](https://github.com/gradido/gradido/pull/1705) +- add extra disabled variable for send emit, disabled send by emit [`#1704`](https://github.com/gradido/gradido/pull/1704) +- Fix: Correct calculation of decay [`#1699`](https://github.com/gradido/gradido/pull/1699) +- Fix: Allow sending of more then half of my wealth via link [`#1700`](https://github.com/gradido/gradido/pull/1700) +- feat: Seed Creations Months Ago From Now [`#1702`](https://github.com/gradido/gradido/pull/1702) +- Fix: Frontend show proper error message on failed send [`#1701`](https://github.com/gradido/gradido/pull/1701) + +#### [1.7.0](https://github.com/gradido/gradido/compare/1.6.6...1.7.0) + +> 30 March 2022 + +- v1.7.0 [`#1698`](https://github.com/gradido/gradido/pull/1698) +- folder for new style images [`#1694`](https://github.com/gradido/gradido/pull/1694) +- fix: No Email Exposed on Forgot Password [`#1696`](https://github.com/gradido/gradido/pull/1696) +- fix: No Decay Calculation in Frontend [`#1692`](https://github.com/gradido/gradido/pull/1692) +- fix: Wrong Balance on Decay Transaction [`#1691`](https://github.com/gradido/gradido/pull/1691) +- fix: No Plus Before Zero Decay [`#1689`](https://github.com/gradido/gradido/pull/1689) +- fix: Update Deployment env.dist [`#1688`](https://github.com/gradido/gradido/pull/1688) +- 1684 when generating a link form does not reset [`#1687`](https://github.com/gradido/gradido/pull/1687) +- Refactor: Multicreation - do not show unactivated emails [`#1679`](https://github.com/gradido/gradido/pull/1679) +- feat: Show Link Duration in Emails [`#1663`](https://github.com/gradido/gradido/pull/1663) +- refactor: Balance Resolver [`#1665`](https://github.com/gradido/gradido/pull/1665) +- refactor: Set Email Optin Valid Time to 24 hours [`#1662`](https://github.com/gradido/gradido/pull/1662) +- Fix: Fixes found on Stage1 [`#1683`](https://github.com/gradido/gradido/pull/1683) +- 1555 admin see user generated link [`#1656`](https://github.com/gradido/gradido/pull/1656) +- 1594 show transaction was created by link [`#1680`](https://github.com/gradido/gradido/pull/1680) +- refactor: Memo Text Length to 255 Characters [`#1675`](https://github.com/gradido/gradido/pull/1675) +- adminarea: fetchPolicy on searchUser deleted User [`#1678`](https://github.com/gradido/gradido/pull/1678) +- 1223 community communication concept [`#1313`](https://github.com/gradido/gradido/pull/1313) +- clear form.email if click send per link, tests if clicked [`#1660`](https://github.com/gradido/gradido/pull/1660) +- feat: User in Transaction Clickable to Send Directly [`#1658`](https://github.com/gradido/gradido/pull/1658) +- feat: Add Sender Email to Transaction Received Mail [`#1664`](https://github.com/gradido/gradido/pull/1664) +- Feature: Enforce config versions [`#1627`](https://github.com/gradido/gradido/pull/1627) +- 1559 frontend transport redeem link through register [`#1647`](https://github.com/gradido/gradido/pull/1647) +- update-balance if link succesfully generated [`#1655`](https://github.com/gradido/gradido/pull/1655) +- feat: Add Referrer ID to Users [`#1654`](https://github.com/gradido/gradido/pull/1654) +- 1558 - show tranaction link information page [`#1625`](https://github.com/gradido/gradido/pull/1625) +- refactor: No Float Ids [`#1624`](https://github.com/gradido/gradido/pull/1624) +- Change the text if the account is not activated yet and changed the b… [`#1336`](https://github.com/gradido/gradido/pull/1336) +- Refactor: Corrected name of transaction link summary [`#1628`](https://github.com/gradido/gradido/pull/1628) +- fix: Query for Only Creations Transaction List [`#1623`](https://github.com/gradido/gradido/pull/1623) +- Fix: build for development and production links external modules properly [`#1626`](https://github.com/gradido/gradido/pull/1626) +- feat: Seed Transaction Links [`#1622`](https://github.com/gradido/gradido/pull/1622) +- 1588 frontend expendable paginated link list [`#1620`](https://github.com/gradido/gradido/pull/1620) +- feat: Seed Creation Transactions in Backend [`#1621`](https://github.com/gradido/gradido/pull/1621) +- Feature: Eslint i18n validation [`#1618`](https://github.com/gradido/gradido/pull/1618) +- refactor: Seed in Backend [`#1619`](https://github.com/gradido/gradido/pull/1619) +- 1554 frontend transaction link summary [`#1613`](https://github.com/gradido/gradido/pull/1613) +- Frontend generate link for send gdd [`#1579`](https://github.com/gradido/gradido/pull/1579) +- feat: Test Logout in User Resolver [`#1617`](https://github.com/gradido/gradido/pull/1617) +- Fix: Logrotate & Log Dates & Save Update Log & Correct tag Checkout [`#1612`](https://github.com/gradido/gradido/pull/1612) +- refactor: No Reset DB in Backend Unit Tests [`#1616`](https://github.com/gradido/gradido/pull/1616) +- Test: Require 53% backend coverage [`#1611`](https://github.com/gradido/gradido/pull/1611) +- 1599 components for transactionlist types [`#1600`](https://github.com/gradido/gradido/pull/1600) +- feat: Link Transaction to Transaction Link on Redeem [`#1610`](https://github.com/gradido/gradido/pull/1610) +- feat: Redeem Transaction Link Mutation [`#1607`](https://github.com/gradido/gradido/pull/1607) +- feat: List Transaction Links Query [`#1606`](https://github.com/gradido/gradido/pull/1606) +- feat: Virtual Transaction for Transaction Links [`#1603`](https://github.com/gradido/gradido/pull/1603) +- refactor: Transaction Link Query [`#1605`](https://github.com/gradido/gradido/pull/1605) +- 1216 seo vorschau links [`#1426`](https://github.com/gradido/gradido/pull/1426) +- Feature: Eslint style rules & Stylelint for SCSS [`#1598`](https://github.com/gradido/gradido/pull/1598) +- refactor: Remove showEmail from Transaction Links [`#1602`](https://github.com/gradido/gradido/pull/1602) +- feat: Delete Transaction Link Mutation [`#1597`](https://github.com/gradido/gradido/pull/1597) +- Query-transaction-link [`#1586`](https://github.com/gradido/gradido/pull/1586) +- feat: Create Transaction Link Mutation [`#1585`](https://github.com/gradido/gradido/pull/1585) +- feat: Model Transaction Link [`#1584`](https://github.com/gradido/gradido/pull/1584) +- feat: Test Login in User Resolver [`#1538`](https://github.com/gradido/gradido/pull/1538) +- add style in App.vue, set class .pointer on transaction-list-item [`#1583`](https://github.com/gradido/gradido/pull/1583) +- feat: Use Vue Filter to Display Gradido Amounts [`#1576`](https://github.com/gradido/gradido/pull/1576) +- refactor: Resolve Relative Paths in Backend [`#1572`](https://github.com/gradido/gradido/pull/1572) +- refactor: Frontend Directory Structure and Routes [`#1571`](https://github.com/gradido/gradido/pull/1571) +- community name in creation transaction is displayed cleanly [`#1578`](https://github.com/gradido/gradido/pull/1578) +- Planning: send new users gradido [`#1567`](https://github.com/gradido/gradido/pull/1567) +- Refactor arithmetic merge [`#1548`](https://github.com/gradido/gradido/pull/1548) +- Adminarea creation transactionlist show [`#1550`](https://github.com/gradido/gradido/pull/1550) +- Fix: Validate password on UpdateUserInfos [`#1568`](https://github.com/gradido/gradido/pull/1568) +- Fix: Allow sending to user without transactions [`#1549`](https://github.com/gradido/gradido/pull/1549) +- Fix: Balance type [`#1569`](https://github.com/gradido/gradido/pull/1569) +- Refactor: arithmetic and data types [`#1539`](https://github.com/gradido/gradido/pull/1539) +- refactor: Bootstrap Vue Toast in Admin interface [`#1547`](https://github.com/gradido/gradido/pull/1547) +- Refactor: Combine transaction tables and restructure transaction design [`#1531`](https://github.com/gradido/gradido/pull/1531) + #### [1.6.6](https://github.com/gradido/gradido/compare/1.6.5...1.6.6) +> 28 February 2022 + +- v1.6.6 [`#1541`](https://github.com/gradido/gradido/pull/1541) - Fix: Upper case email on register breaks account [`#1542`](https://github.com/gradido/gradido/pull/1542) - 1106 first transaction cannot be expanded [`#1432`](https://github.com/gradido/gradido/pull/1432) - added missing bootstrap scss. bootstrap/scss/bootstrap, plus more mis… [`#1540`](https://github.com/gradido/gradido/pull/1540) diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index e97055a78..2911c13d3 100644 --- a/README.md +++ b/README.md @@ -93,3 +93,15 @@ Note: The Changelog will be regenerated with all tags on release on the external ## Useful Links - [Gradido.net](https://gradido.net/) + + +## Attributions + +![browserstack_logo-freelogovectors net_](https://user-images.githubusercontent.com/1324583/167782608-0e4db0d4-3d34-45fb-ab06-344aa5e5ef4b.png) + +Browser compatibility testing with [BrowserStack](https://www.browserstack.com/). + + +## License +See the [LICENSE](LICENSE.md) file for license rights and limitations (Apache-2.0 license). + diff --git a/admin/.gitignore b/admin/.gitignore index 6bb62f667..a67d270bc 100644 --- a/admin/.gitignore +++ b/admin/.gitignore @@ -9,4 +9,4 @@ dist/ coverage/ # emacs -*~ \ No newline at end of file +*~ diff --git a/admin/.prettierrc.js b/admin/.prettierrc.js index e88113754..bc1d767d7 100644 --- a/admin/.prettierrc.js +++ b/admin/.prettierrc.js @@ -4,5 +4,6 @@ module.exports = { singleQuote: true, trailingComma: "all", tabWidth: 2, - bracketSpacing: true + bracketSpacing: true, + endOfLine: "auto", }; diff --git a/admin/package.json b/admin/package.json index 2b4be2851..57711b8be 100644 --- a/admin/package.json +++ b/admin/package.json @@ -3,7 +3,7 @@ "description": "Administraion Interface for Gradido", "main": "index.js", "author": "Moriz Wahl", - "version": "1.6.6", + "version": "1.8.3", "license": "MIT", "private": false, "scripts": { @@ -14,7 +14,7 @@ "analyse-bundle": "yarn build && webpack-bundle-analyzer dist/webpack.stats.json", "lint": "eslint --max-warnings=0 --ext .js,.vue,.json .", "stylelint": "stylelint --max-warnings=0 '**/*.{scss,vue}'", - "test": "TZ=UTC jest --coverage", + "test": "cross-env TZ=UTC jest --coverage", "locales": "scripts/sort.sh" }, "dependencies": { @@ -57,6 +57,7 @@ "@vue/cli-service": "~4.5.0", "babel-eslint": "^10.1.0", "babel-plugin-transform-require-context": "^0.1.1", + "cross-env": "^7.0.3", "eslint": "7.25.0", "eslint-config-prettier": "^8.3.0", "eslint-config-standard": "^16.0.3", diff --git a/admin/src/components/CreationFormular.spec.js b/admin/src/components/CreationFormular.spec.js index 083b7ca67..08ec71bdc 100644 --- a/admin/src/components/CreationFormular.spec.js +++ b/admin/src/components/CreationFormular.spec.js @@ -24,12 +24,6 @@ const mocks = { }, $store: { commit: stateCommitMock, - state: { - moderator: { - id: 0, - name: 'test moderator', - }, - }, }, } @@ -122,7 +116,6 @@ describe('CreationFormular', () => { creationDate: getCreationDate(2), amount: 90, memo: 'Test create coins', - moderator: 0, }, }), ) @@ -370,14 +363,12 @@ describe('CreationFormular', () => { creationDate: getCreationDate(1), amount: 200, memo: 'Test mass create coins', - moderator: 0, }, { email: 'bibi@bloxberg.de', creationDate: getCreationDate(1), amount: 200, memo: 'Test mass create coins', - moderator: 0, }, ], }, diff --git a/admin/src/components/CreationFormular.vue b/admin/src/components/CreationFormular.vue index cd4de5fd6..cdcd6ef1d 100644 --- a/admin/src/components/CreationFormular.vue +++ b/admin/src/components/CreationFormular.vue @@ -154,7 +154,6 @@ export default { creationDate: this.selected.date, amount: Number(this.value), memo: this.text, - moderator: Number(this.$store.state.moderator.id), }) }) this.$apollo @@ -188,7 +187,6 @@ export default { creationDate: this.selected.date, amount: Number(this.value), memo: this.text, - moderator: Number(this.$store.state.moderator.id), } this.$apollo .mutate({ diff --git a/admin/src/components/CreationTransactionListFormular.spec.js b/admin/src/components/CreationTransactionList.spec.js similarity index 89% rename from admin/src/components/CreationTransactionListFormular.spec.js rename to admin/src/components/CreationTransactionList.spec.js index fb137e516..3e2d5893e 100644 --- a/admin/src/components/CreationTransactionListFormular.spec.js +++ b/admin/src/components/CreationTransactionList.spec.js @@ -1,5 +1,5 @@ import { mount } from '@vue/test-utils' -import CreationTransactionListFormular from './CreationTransactionListFormular.vue' +import CreationTransactionList from './CreationTransactionList.vue' import { toastErrorSpy } from '../../test/testSetup' const localVue = global.localVue @@ -46,11 +46,11 @@ const propsData = { fields: ['date', 'balance', 'name', 'memo', 'decay'], } -describe('CreationTransactionListFormular', () => { +describe('CreationTransactionList', () => { let wrapper const Wrapper = () => { - return mount(CreationTransactionListFormular, { localVue, mocks, propsData }) + return mount(CreationTransactionList, { localVue, mocks, propsData }) } describe('mount', () => { diff --git a/admin/src/components/CreationTransactionListFormular.vue b/admin/src/components/CreationTransactionList.vue similarity index 96% rename from admin/src/components/CreationTransactionListFormular.vue rename to admin/src/components/CreationTransactionList.vue index ce2b136a4..ec5c12aa4 100644 --- a/admin/src/components/CreationTransactionListFormular.vue +++ b/admin/src/components/CreationTransactionList.vue @@ -1,6 +1,6 @@ diff --git a/admin/src/components/EditCreationFormular.spec.js b/admin/src/components/EditCreationFormular.spec.js index f5c7fb0fe..f39edad52 100644 --- a/admin/src/components/EditCreationFormular.spec.js +++ b/admin/src/components/EditCreationFormular.spec.js @@ -11,7 +11,6 @@ const apolloMutateMock = jest.fn().mockResolvedValue({ amount: 500, date: new Date(), memo: 'Test Schöpfung 2', - moderator: 0, }, }, }) @@ -28,12 +27,6 @@ const mocks = { mutate: apolloMutateMock, }, $store: { - state: { - moderator: { - id: 0, - name: 'test moderator', - }, - }, commit: stateCommitMock, }, } @@ -104,7 +97,6 @@ describe('EditCreationFormular', () => { creationDate: getCreationDate(0), amount: 500, memo: 'Test Schöpfung 2', - moderator: 0, }, }), ) @@ -129,7 +121,6 @@ describe('EditCreationFormular', () => { amount: 500, date: expect.any(Date), memo: 'Test Schöpfung 2', - moderator: 0, row: expect.any(Object), }, ], diff --git a/admin/src/components/EditCreationFormular.vue b/admin/src/components/EditCreationFormular.vue index 82b444154..fb30f2b77 100644 --- a/admin/src/components/EditCreationFormular.vue +++ b/admin/src/components/EditCreationFormular.vue @@ -120,7 +120,6 @@ export default { creationDate: this.selected.date, amount: Number(this.value), memo: this.text, - moderator: Number(this.$store.state.moderator.id), }, }) .then((result) => { @@ -129,7 +128,6 @@ export default { amount: Number(result.data.updatePendingCreation.amount), date: result.data.updatePendingCreation.date, memo: result.data.updatePendingCreation.memo, - moderator: Number(result.data.updatePendingCreation.moderator), row: this.row, }) this.toastSuccess( diff --git a/admin/src/components/Tables/OpenCreationsTable.spec.js b/admin/src/components/Tables/OpenCreationsTable.spec.js new file mode 100644 index 000000000..9ff348562 --- /dev/null +++ b/admin/src/components/Tables/OpenCreationsTable.spec.js @@ -0,0 +1,129 @@ +import { mount } from '@vue/test-utils' +import OpenCreationsTable from './OpenCreationsTable.vue' + +const localVue = global.localVue + +const apolloMutateMock = jest.fn().mockResolvedValue({}) +const apolloQueryMock = jest.fn().mockResolvedValue({}) + +const propsData = { + items: [ + { + id: 4, + firstName: 'Bob', + lastName: 'der Baumeister', + email: 'bob@baumeister.de', + amount: 300, + memo: 'Aktives Grundeinkommen für Januar 2022', + date: '2022-01-01T00:00:00.000Z', + moderator: 1, + creation: [700, 1000, 1000], + __typename: 'PendingCreation', + }, + { + id: 5, + firstName: 'Räuber', + lastName: 'Hotzenplotz', + email: 'raeuber@hotzenplotz.de', + amount: 210, + memo: 'Aktives Grundeinkommen für Januar 2022', + date: '2022-01-01T00:00:00.000Z', + moderator: 1, + creation: [790, 1000, 1000], + __typename: 'PendingCreation', + }, + { + id: 6, + firstName: 'Stephen', + lastName: 'Hawking', + email: 'stephen@hawking.uk', + amount: 330, + memo: 'Aktives Grundeinkommen für Januar 2022', + date: '2022-01-01T00:00:00.000Z', + moderator: 1, + creation: [670, 1000, 1000], + __typename: 'PendingCreation', + }, + ], + fields: [ + { key: 'bookmark', label: 'delete' }, + { key: 'email', label: 'e_mail' }, + { key: 'firstName', label: 'firstname' }, + { key: 'lastName', label: 'lastname' }, + { + key: 'amount', + label: 'creation', + formatter: (value) => { + return value + ' GDD' + }, + }, + { key: 'memo', label: 'text' }, + { + key: 'date', + label: 'date', + formatter: (value) => { + return value + }, + }, + { key: 'moderator', label: 'moderator' }, + { key: 'edit_creation', label: 'edit' }, + { key: 'confirm', label: 'save' }, + ], +} + +const mocks = { + $t: jest.fn((t) => t), + $d: jest.fn((d) => d), + $apollo: { + mutate: apolloMutateMock, + query: apolloQueryMock, + }, + $store: { + state: { + moderator: { + id: 0, + name: 'test moderator', + }, + }, + }, +} + +describe('OpenCreationsTable', () => { + let wrapper + + const Wrapper = () => { + return mount(OpenCreationsTable, { localVue, mocks, propsData }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('has a DIV element with the class .open-creations-table', () => { + expect(wrapper.find('div.open-creations-table').exists()).toBeTruthy() + }) + + it('has a table with three rows', () => { + expect(wrapper.findAll('tbody > tr')).toHaveLength(3) + }) + + it('find first button.bi-pencil-square for open EditCreationFormular ', () => { + expect(wrapper.findAll('tr').at(1).find('.bi-pencil-square').exists()).toBeTruthy() + }) + + describe('show edit details', () => { + beforeEach(async () => { + await wrapper.findAll('tr').at(1).find('.bi-pencil-square').trigger('click') + }) + + it.skip('has a component element with name EditCreationFormular', () => { + expect(wrapper.findComponent({ name: 'EditCreationFormular' }).exists()).toBe(true) + }) + + it.skip('renders the component component-edit-creation-formular', () => { + expect(wrapper.find('div.component-edit-creation-formular').exists()).toBeTruthy() + }) + }) + }) +}) diff --git a/admin/src/components/Tables/OpenCreationsTable.vue b/admin/src/components/Tables/OpenCreationsTable.vue index c3e908868..d2e9669e6 100644 --- a/admin/src/components/Tables/OpenCreationsTable.vue +++ b/admin/src/components/Tables/OpenCreationsTable.vue @@ -1,5 +1,5 @@ @@ -38,7 +44,8 @@ @@ -53,21 +60,26 @@ - + align="center" + > +
- {{ $t('transaction.nullTransactions') }} + +
{{ $t('transaction.nullTransactions') }}
+ diff --git a/frontend/src/components/Status.spec.js b/frontend/src/components/Status.spec.js index ba7b03fa4..64a15deb1 100644 --- a/frontend/src/components/Status.spec.js +++ b/frontend/src/components/Status.spec.js @@ -26,8 +26,8 @@ describe('Status', () => { }) describe('balance is pending', () => { - it('it displays an en-dash', () => { - expect(wrapper.find('div.gdd-status-div').text()).toEqual('em-dash GDD') + it('displays an animation icon test-pending-icon', () => { + expect(wrapper.find('.test-pending-icon').exists()).toBe(true) }) }) @@ -38,6 +38,10 @@ describe('Status', () => { }) }) + it('does not display an animation icon test-pending-icon', () => { + expect(wrapper.find('.test-pending-icon').exists()).toBe(false) + }) + it('it displays the ammount of GDD', () => { expect(wrapper.find('div.gdd-status-div').text()).toEqual('1234 GDD') }) diff --git a/frontend/src/components/Status.vue b/frontend/src/components/Status.vue index 9ebe48e09..14d470fb3 100644 --- a/frontend/src/components/Status.vue +++ b/frontend/src/components/Status.vue @@ -1,7 +1,10 @@ diff --git a/frontend/src/components/Transaction.vue b/frontend/src/components/Transaction.vue index ea651c663..e53e1520c 100644 --- a/frontend/src/components/Transaction.vue +++ b/frontend/src/components/Transaction.vue @@ -3,12 +3,12 @@
-
+
-
+
-
+
{{ getLinesByType.headline }} diff --git a/frontend/src/components/TransactionLinks/TransactionLink.spec.js b/frontend/src/components/TransactionLinks/TransactionLink.spec.js index f019a0ee1..ad9e4860e 100644 --- a/frontend/src/components/TransactionLinks/TransactionLink.spec.js +++ b/frontend/src/components/TransactionLinks/TransactionLink.spec.js @@ -13,6 +13,7 @@ const mocks = { locale: 'en', }, $t: jest.fn((t) => t), + $d: jest.fn((d) => d), $tc: jest.fn((tc) => tc), $apollo: { mutate: mockAPIcall, @@ -21,10 +22,10 @@ const mocks = { const propsData = { amount: '75', - code: 'c00000000c000000c0000', + link: 'http://localhost/redeem/c00000000c000000c0000', holdAvailableAmount: '5.13109484759482747111', id: 12, - memo: 'Wie schön hier etwas Quatsch zu lesen!', + memo: 'Katzenauge, Eulenschrei, was verschwunden komm herbei!', validUntil: '2022-03-30T14:22:40.000Z', } @@ -43,101 +44,140 @@ describe('TransactionLink', () => { expect(wrapper.find('div.transaction-link').exists()).toBeTruthy() }) - describe('Copy link to Clipboard', () => { - const navigatorClipboard = navigator.clipboard - beforeAll(() => { - delete navigator.clipboard - navigator.clipboard = { writeText: navigatorClipboardMock } - }) - afterAll(() => { - navigator.clipboard = navigatorClipboard + describe('Link validUntil Date is not valid', () => { + it('has no copy link button', () => { + expect(wrapper.find('.test-copy-link').exists()).toBe(false) }) - describe('copy with success', () => { - beforeEach(async () => { - navigatorClipboardMock.mockResolvedValue() - await wrapper.findAll('button').at(0).trigger('click') - }) - - it('toasts success message', () => { - expect(toastSuccessSpy).toBeCalledWith('gdd_per_link.link-copied') - }) + it('has no Qr-Code Button ', () => { + expect(wrapper.find('.test-qr-code').exists()).toBe(false) }) - describe('copy with error', () => { - beforeEach(async () => { - navigatorClipboardMock.mockRejectedValue() - await wrapper.findAll('button').at(0).trigger('click') - }) - - it('toasts error message', () => { - expect(toastErrorSpy).toBeCalledWith('gdd_per_link.not-copied') - }) + it('has delete link button ', () => { + expect(wrapper.find('.test-delete-link').exists()).toBe(true) }) }) - describe('delete link', () => { - let spy - - beforeEach(() => { + describe('Link validUntil Date is valid ', () => { + beforeEach(async () => { + const now = new Date() jest.clearAllMocks() - }) - - describe('with success', () => { - beforeEach(async () => { - spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') - spy.mockImplementation(() => Promise.resolve('some value')) - mockAPIcall.mockResolvedValue() - await wrapper.findAll('button').at(1).trigger('click') - }) - - it('test Modal if confirm true', () => { - expect(spy).toBeCalled() - }) - - it('calls the API', () => { - expect(mockAPIcall).toBeCalledWith( - expect.objectContaining({ - mutation: deleteTransactionLink, - variables: { - id: 12, - }, - }), - ) - }) - - it('toasts a success message', () => { - expect(toastSuccessSpy).toBeCalledWith('gdd_per_link.deleted') - }) - - it('emits reset transaction link list', () => { - expect(wrapper.emitted('reset-transaction-link-list')).toBeTruthy() + await wrapper.setProps({ + validUntil: `${new Date(now.getFullYear(), now.getMonth(), now.getDate() + 2)}`, }) }) - describe('with error', () => { - beforeEach(async () => { - spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') - spy.mockImplementation(() => Promise.resolve('some value')) - mockAPIcall.mockRejectedValue({ message: 'Something went wrong :(' }) - await wrapper.findAll('button').at(1).trigger('click') + describe('Copy link to Clipboard', () => { + const navigatorClipboard = navigator.clipboard + beforeAll(() => { + delete navigator.clipboard + navigator.clipboard = { writeText: navigatorClipboardMock } + }) + afterAll(() => { + navigator.clipboard = navigatorClipboard }) - it('toasts an error message', () => { - expect(toastErrorSpy).toBeCalledWith('Something went wrong :(') + describe('copy with success', () => { + beforeEach(async () => { + navigatorClipboardMock.mockResolvedValue() + await wrapper.find('.test-copy-link .dropdown-item').trigger('click') + }) + + it('should call clipboard.writeText', () => { + expect(navigator.clipboard.writeText).toHaveBeenCalledWith( + 'http://localhost/redeem/c00000000c000000c0000', + ) + }) + it('toasts success message', () => { + expect(toastSuccessSpy).toBeCalledWith('gdd_per_link.link-copied') + }) }) }) - describe('cancel delete', () => { + describe('qr code modal', () => { + let spy + beforeEach(async () => { - spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') - spy.mockImplementation(() => Promise.resolve(false)) - mockAPIcall.mockResolvedValue() - await wrapper.findAll('button').at(1).trigger('click') + jest.clearAllMocks() }) - it('does not call the API', () => { - expect(mockAPIcall).not.toBeCalled() + describe('with success', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'show') + // spy.mockImplementation(() => Promise.resolve('some value')) + // mockAPIcall.mockResolvedValue() + await wrapper.find('.test-qr-code .dropdown-item').trigger('click') + }) + + it('opens the qr-code Modal', () => { + expect(spy).toBeCalled() + }) + }) + }) + + describe('delete link', () => { + let spy + + beforeEach(async () => { + jest.clearAllMocks() + }) + + describe('with success', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve('some value')) + mockAPIcall.mockResolvedValue() + await wrapper.find('.test-delete-link .dropdown-item').trigger('click') + }) + + it('opens the modal ', () => { + expect(spy).toBeCalled() + }) + + it('calls the API', () => { + expect(mockAPIcall).toBeCalledWith( + expect.objectContaining({ + mutation: deleteTransactionLink, + variables: { + id: 12, + }, + }), + ) + }) + + it('toasts a success message', () => { + expect(toastSuccessSpy).toBeCalledWith('gdd_per_link.deleted') + }) + + it('emits reset transaction link list', () => { + expect(wrapper.emitted('reset-transaction-link-list')).toBeTruthy() + }) + }) + + describe('with error', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve('some value')) + mockAPIcall.mockRejectedValue({ message: 'Something went wrong :(' }) + await wrapper.find('.test-delete-link .dropdown-item').trigger('click') + }) + + it('toasts an error message', () => { + expect(toastErrorSpy).toBeCalledWith('Something went wrong :(') + }) + }) + + describe('cancel delete', () => { + beforeEach(async () => { + spy = jest.spyOn(wrapper.vm.$bvModal, 'msgBoxConfirm') + spy.mockImplementation(() => Promise.resolve(false)) + mockAPIcall.mockResolvedValue() + await wrapper.find('.test-delete-link .dropdown-item').trigger('click') + }) + + it('does not call the API', () => { + expect(mockAPIcall).not.toBeCalled() + }) }) }) }) diff --git a/frontend/src/components/TransactionLinks/TransactionLink.vue b/frontend/src/components/TransactionLinks/TransactionLink.vue index 9cbfdfb93..c7b7682ec 100644 --- a/frontend/src/components/TransactionLinks/TransactionLink.vue +++ b/frontend/src/components/TransactionLinks/TransactionLink.vue @@ -1,38 +1,63 @@ + diff --git a/frontend/src/components/TransactionRows/AmountAndNameRow.spec.js b/frontend/src/components/TransactionRows/AmountAndNameRow.spec.js new file mode 100644 index 000000000..172f5f401 --- /dev/null +++ b/frontend/src/components/TransactionRows/AmountAndNameRow.spec.js @@ -0,0 +1,75 @@ +import { mount } from '@vue/test-utils' +import AmountAndNameRow from './AmountAndNameRow' + +const localVue = global.localVue + +const mocks = { + $router: { + push: jest.fn(), + }, +} + +const propsData = { + amount: '19.99', + text: 'Some text', +} + +describe('AmountAndNameRow', () => { + let wrapper + + const Wrapper = () => { + return mount(AmountAndNameRow, { localVue, mocks, propsData }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + + it('renders the component', () => { + expect(wrapper.find('div.amount-and-name-row').exists()).toBe(true) + }) + + describe('without linked user', () => { + it('has a span with the text', () => { + expect(wrapper.find('div.gdd-transaction-list-item-name').text()).toBe('Some text') + }) + + it('has no link', () => { + expect(wrapper.find('div.gdd-transaction-list-item-name').find('a').exists()).toBe(false) + }) + }) + + describe('with linked user', () => { + beforeEach(async () => { + await wrapper.setProps({ + linkedUser: { firstName: 'Bibi', lastName: 'Bloxberg', email: 'bibi@bloxberg.de' }, + }) + }) + + it('has a link with first and last name', () => { + expect(wrapper.find('div.gdd-transaction-list-item-name').text()).toBe('Bibi Bloxberg') + }) + + it('has a link', () => { + expect(wrapper.find('div.gdd-transaction-list-item-name').find('a').exists()).toBe(true) + }) + + describe('click link', () => { + beforeEach(async () => { + await wrapper.find('div.gdd-transaction-list-item-name').find('a').trigger('click') + }) + + it('emits set tunneled email', () => { + expect(wrapper.emitted('set-tunneled-email')).toEqual([['bibi@bloxberg.de']]) + }) + + it('pushes the route with query for email', () => { + expect(mocks.$router.push).toBeCalledWith({ + path: '/send', + }) + }) + }) + }) + }) +}) diff --git a/frontend/src/components/TransactionRows/AmountAndNameRow.vue b/frontend/src/components/TransactionRows/AmountAndNameRow.vue index fd9be6bf8..322ad7dfa 100644 --- a/frontend/src/components/TransactionRows/AmountAndNameRow.vue +++ b/frontend/src/components/TransactionRows/AmountAndNameRow.vue @@ -10,7 +10,21 @@
- {{ itemText }} +
+ + {{ itemText }} + + + {{ $t('via_link') }} + + +
+ {{ itemText }}
@@ -32,6 +46,17 @@ export default { type: String, required: false, }, + transactionLinkId: { + type: Number, + required: false, + default: null, + }, + }, + methods: { + tunnelEmail() { + this.$emit('set-tunneled-email', this.linkedUser.email) + this.$router.push({ path: '/send' }) + }, }, computed: { itemText() { diff --git a/frontend/src/components/TransactionRows/DateRow.vue b/frontend/src/components/TransactionRows/DateRow.vue index 0c72907fe..5998be134 100644 --- a/frontend/src/components/TransactionRows/DateRow.vue +++ b/frontend/src/components/TransactionRows/DateRow.vue @@ -2,11 +2,13 @@
-
{{ diffNow ? $t('gdd_per_link.expired') : $t('form.date') }}
+
+ {{ text }} +
- {{ dateString }} + {{ $d(new Date(this.date), 'long') }}
@@ -25,12 +27,19 @@ export default { required: false, default: false, }, + validLink: { + type: Boolean, + required: false, + default: false, + }, }, computed: { - dateString() { - return this.diffNow - ? this.$moment(this.date).locale(this.$i18n.locale).fromNow() - : this.$d(new Date(this.date), 'long') + text() { + if (this.diffNow) + return this.validLink + ? this.$t('gdd_per_link.validUntil') + : this.$t('gdd_per_link.expiredOn') + return this.$t('form.date') }, }, } diff --git a/frontend/src/components/TransactionRows/DurationRow.vue b/frontend/src/components/TransactionRows/DurationRow.vue new file mode 100644 index 000000000..d40d9e35b --- /dev/null +++ b/frontend/src/components/TransactionRows/DurationRow.vue @@ -0,0 +1,44 @@ + + diff --git a/frontend/src/components/Transactions/TransactionCreation.spec.js b/frontend/src/components/Transactions/TransactionCreation.spec.js index e1ea9e0b0..be0713ecf 100644 --- a/frontend/src/components/Transactions/TransactionCreation.spec.js +++ b/frontend/src/components/Transactions/TransactionCreation.spec.js @@ -31,6 +31,7 @@ const propsData = { memo: 'sadasd asdasdasdasdadadd da dad aad', typeId: 'DECAY', decayStartBlock: new Date('2021-05-13T17:46:31.000Z'), + previousBookedBalance: '43.56', } describe('TransactionCreation', () => { diff --git a/frontend/src/components/Transactions/TransactionCreation.vue b/frontend/src/components/Transactions/TransactionCreation.vue index bb131d39a..694d907ed 100644 --- a/frontend/src/components/Transactions/TransactionCreation.vue +++ b/frontend/src/components/Transactions/TransactionCreation.vue @@ -12,7 +12,7 @@ - + @@ -27,12 +27,7 @@
- +
@@ -47,7 +42,7 @@ import DecayRow from '../TransactionRows/DecayRow' import DecayInformation from '../DecayInformations/DecayInformation' export default { - name: 'slot-creation', + name: 'TransactionCreation', components: { CollapseIcon, TypeIcon, @@ -82,8 +77,8 @@ export default { type: String, required: true, }, - decayStartBlock: { - type: Date, + previousBookedBalance: { + type: String, required: true, }, }, diff --git a/frontend/src/components/Transactions/TransactionDecay.spec.js b/frontend/src/components/Transactions/TransactionDecay.spec.js index 232e7f85a..8c5236b6e 100644 --- a/frontend/src/components/Transactions/TransactionDecay.spec.js +++ b/frontend/src/components/Transactions/TransactionDecay.spec.js @@ -31,6 +31,7 @@ const propsData = { memo: 'sadasd asdasdasdasdadadd da dad aad', typeId: 'DECAY', decayStartBlock: new Date('2021-05-13T17:46:31.000Z'), + previousBookedBalance: '43.56', } describe('TransactionDecay', () => { diff --git a/frontend/src/components/Transactions/TransactionDecay.vue b/frontend/src/components/Transactions/TransactionDecay.vue index 2f235502a..7505ac7c1 100644 --- a/frontend/src/components/Transactions/TransactionDecay.vue +++ b/frontend/src/components/Transactions/TransactionDecay.vue @@ -20,8 +20,12 @@
- - + +
@@ -33,7 +37,7 @@ import AmountAndNameRow from '../TransactionRows/AmountAndNameRow' import DecayInformationDecay from '../DecayInformations/DecayInformation-Decay' export default { - name: 'slot-decay', + name: 'TransactionDecay', components: { CollapseIcon, TypeIcon, @@ -53,6 +57,10 @@ export default { type: Object, required: true, }, + previousBookedBalance: { + type: String, + required: true, + }, }, data() { return { diff --git a/frontend/src/components/Transactions/TransactionLinkSummary.spec.js b/frontend/src/components/Transactions/TransactionLinkSummary.spec.js index 1f2a4388c..ba07f7aff 100644 --- a/frontend/src/components/Transactions/TransactionLinkSummary.spec.js +++ b/frontend/src/components/Transactions/TransactionLinkSummary.spec.js @@ -12,6 +12,7 @@ const mocks = { locale: 'en', }, $t: jest.fn((t) => t), + $d: jest.fn((d) => d), $tc: jest.fn((tc) => tc), $apollo: { query: apolloQueryMock, @@ -43,7 +44,7 @@ describe('TransactionLinkSummary', () => { listTransactionLinks: [ { amount: '75', - code: 'ce28664b5308c17f931c0367', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', createdAt: '2022-03-16T14:22:40.000Z', holdAvailableAmount: '5.13109484759482747111', id: 86, @@ -54,7 +55,7 @@ describe('TransactionLinkSummary', () => { }, { amount: '85', - code: 'ce28664b5308c17f931c0367', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', createdAt: '2022-03-16T14:22:40.000Z', holdAvailableAmount: '5.13109484759482747111', id: 107, @@ -64,7 +65,7 @@ describe('TransactionLinkSummary', () => { }, { amount: '95', - code: 'ce28664b5308c17f931c0367', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', createdAt: '2022-03-16T14:22:40.000Z', holdAvailableAmount: '5.13109484759482747111', id: 92, @@ -75,7 +76,7 @@ describe('TransactionLinkSummary', () => { }, { amount: '150', - code: 'ce28664b5308c17f931c0367', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', createdAt: '2022-03-16T14:22:40.000Z', holdAvailableAmount: '5.13109484759482747111', id: 16, @@ -99,18 +100,164 @@ describe('TransactionLinkSummary', () => { expect(wrapper.findComponent({ name: 'CollapseLinksList' }).exists()).toBe(true) }) - it('calls the API to get the list transaction links', () => { - expect(apolloQueryMock).toBeCalledWith({ - query: listTransactionLinks, - variables: { - currentPage: 1, - }, - fetchPolicy: 'network-only', + describe('click on transaction links', () => { + beforeEach(() => { + wrapper.find('div.transaction-link-details').trigger('click') }) - }) - it('has four transactionLinks', () => { - expect(wrapper.vm.transactionLinks).toHaveLength(4) + it('calls the API to get the list transaction links', () => { + expect(apolloQueryMock).toBeCalledWith({ + query: listTransactionLinks, + variables: { + currentPage: 1, + }, + fetchPolicy: 'network-only', + }) + }) + + it('has four transactionLinks', () => { + expect(wrapper.vm.transactionLinks).toHaveLength(4) + }) + + describe('close transaction link details', () => { + beforeEach(() => { + jest.clearAllMocks() + wrapper.find('div.transaction-link-details').trigger('click') + }) + + it('does not call the API', () => { + expect(apolloQueryMock).not.toBeCalled() + }) + + it('has no component CollapseLinksList', () => { + expect(wrapper.findComponent({ name: 'CollapseLinksList' }).isVisible()).toBe(false) + }) + + describe('reopen transaction link details', () => { + beforeEach(() => { + jest.clearAllMocks() + wrapper.find('div.transaction-link-details').trigger('click') + }) + + it('calls the API to get the list transaction links', () => { + expect(apolloQueryMock).toBeCalledWith({ + query: listTransactionLinks, + variables: { + currentPage: 1, + }, + fetchPolicy: 'network-only', + }) + }) + + it('has four transactionLinks', () => { + expect(wrapper.vm.transactionLinks).toHaveLength(4) + }) + }) + }) + + describe('load more transaction links', () => { + beforeEach(async () => { + jest.clearAllMocks() + apolloQueryMock.mockResolvedValue({ + data: { + listTransactionLinks: [ + { + amount: '76', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', + createdAt: '2022-03-16T14:22:40.000Z', + holdAvailableAmount: '5.13109484759482747111', + id: 87, + memo: + 'Hat jemand die Nummer von der Hexe aus Schneewittchen? Ich bräuchte mal ein paar Äpfel.', + redeemedAt: null, + validUntil: '2022-03-30T14:22:40.000Z', + }, + { + amount: '86', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', + createdAt: '2022-03-16T14:22:40.000Z', + holdAvailableAmount: '5.13109484759482747111', + id: 108, + memo: + 'Die Windfahn´ krächzt am Dach, Der Uhu im Geklüfte; Was wispert wie ein Ach Verhallend in die Lüfte?', + redeemedAt: null, + validUntil: '2022-03-30T14:22:40.000Z', + }, + { + amount: '96', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', + createdAt: '2022-03-16T14:22:40.000Z', + holdAvailableAmount: '5.13109484759482747111', + id: 93, + memo: + 'Verschlafen kräht der Hahn, Ein Blitz noch, und ein trüber, Umwölbter Tag bricht an – Walpurgisnacht vorüber!', + redeemedAt: null, + validUntil: '2022-03-30T14:22:40.000Z', + }, + { + amount: '150', + link: 'http://localhost/redeem/ce28664b5308c17f931c0367', + createdAt: '2022-03-16T14:22:40.000Z', + holdAvailableAmount: '5.13109484759482747111', + id: 17, + memo: 'Eene meene Flaschenschrank, fertig ist der Hexentrank!', + redeemedAt: null, + validUntil: '2022-03-30T14:22:40.000Z', + }, + ], + }, + }) + await wrapper.setData({ + currentPage: 2, + pending: false, + pageSize: 5, + }) + }) + + it('has eight transactionLinks', () => { + expect(wrapper.vm.transactionLinks).toHaveLength(8) + }) + + it('loads more transaction links', () => { + expect(apolloQueryMock).toBeCalledWith({ + query: listTransactionLinks, + variables: { + currentPage: 2, + }, + fetchPolicy: 'network-only', + }) + }) + + describe('close transaction link list', () => { + beforeEach(async () => { + wrapper.find('div.transaction-link-details').trigger('click') + }) + describe('reopen transaction link list', () => { + beforeEach(async () => { + jest.clearAllMocks() + wrapper.find('div.transaction-link-details').trigger('click') + }) + + it('calls the API once', () => { + expect(apolloQueryMock).toBeCalledTimes(1) + }) + + it('calls the API with current page one', () => { + expect(apolloQueryMock).toBeCalledWith({ + query: listTransactionLinks, + variables: { + currentPage: 1, + }, + fetchPolicy: 'network-only', + }) + }) + + it('has four transactionLinks', () => { + expect(wrapper.vm.transactionLinks).toHaveLength(4) + }) + }) + }) + }) }) describe('reset transaction links', () => { @@ -142,84 +289,10 @@ describe('TransactionLinkSummary', () => { }) }) - describe('load more transaction links', () => { - beforeEach(async () => { - jest.clearAllMocks() - apolloQueryMock.mockResolvedValue({ - data: { - listTransactionLinks: [ - { - amount: '76', - code: 'ce28664b5308c17f931c0367', - createdAt: '2022-03-16T14:22:40.000Z', - holdAvailableAmount: '5.13109484759482747111', - id: 87, - memo: - 'Hat jemand die Nummer von der Hexe aus Schneewittchen? Ich bräuchte mal ein paar Äpfel.', - redeemedAt: null, - validUntil: '2022-03-30T14:22:40.000Z', - }, - { - amount: '86', - code: 'ce28664b5308c17f931c0367', - createdAt: '2022-03-16T14:22:40.000Z', - holdAvailableAmount: '5.13109484759482747111', - id: 108, - memo: - 'Die Windfahn´ krächzt am Dach, Der Uhu im Geklüfte; Was wispert wie ein Ach Verhallend in die Lüfte?', - redeemedAt: null, - validUntil: '2022-03-30T14:22:40.000Z', - }, - { - amount: '96', - code: 'ce28664b5308c17f931c0367', - createdAt: '2022-03-16T14:22:40.000Z', - holdAvailableAmount: '5.13109484759482747111', - id: 93, - memo: - 'Verschlafen kräht der Hahn, Ein Blitz noch, und ein trüber, Umwölbter Tag bricht an – Walpurgisnacht vorüber!', - redeemedAt: null, - validUntil: '2022-03-30T14:22:40.000Z', - }, - { - amount: '150', - code: 'ce28664b5308c17f931c0367', - createdAt: '2022-03-16T14:22:40.000Z', - holdAvailableAmount: '5.13109484759482747111', - id: 17, - memo: 'Eene meene Flaschenschrank, fertig ist der Hexentrank!', - redeemedAt: null, - validUntil: '2022-03-30T14:22:40.000Z', - }, - ], - }, - }) - await wrapper.setData({ - currentPage: 2, - pending: false, - pageSize: 5, - }) - }) - - it('has eight transactionLinks', () => { - expect(wrapper.vm.transactionLinks).toHaveLength(8) - }) - - it('loads more transaction links', () => { - expect(apolloQueryMock).toBeCalledWith({ - query: listTransactionLinks, - variables: { - currentPage: 2, - }, - fetchPolicy: 'network-only', - }) - }) - }) - describe('loads transaction links with error', () => { beforeEach(() => { apolloQueryMock.mockRejectedValue({ message: 'OUCH!' }) - wrapper = Wrapper() + wrapper.find('div.transaction-link-details').trigger('click') }) it('toasts an error message', () => { diff --git a/frontend/src/components/Transactions/TransactionLinkSummary.vue b/frontend/src/components/Transactions/TransactionLinkSummary.vue index 3f80bfd18..cbce57029 100644 --- a/frontend/src/components/Transactions/TransactionLinkSummary.vue +++ b/frontend/src/components/Transactions/TransactionLinkSummary.vue @@ -1,7 +1,7 @@ diff --git a/frontend/src/config/index.js b/frontend/src/config/index.js index 3f62012ad..917a25d70 100644 --- a/frontend/src/config/index.js +++ b/frontend/src/config/index.js @@ -5,9 +5,10 @@ const pkg = require('../../package') const constants = { + DECAY_START_TIME: new Date('2021-05-13 17:46:31-0000'), // GMT+0 CONFIG_VERSION: { DEFAULT: 'DEFAULT', - EXPECTED: 'v1.2022-03-18', + EXPECTED: 'v2.2022-04-07', CURRENT: '', }, } @@ -27,6 +28,19 @@ const environment = { PORT: process.env.PORT || 3000, } +const endpoints = { + GRAPHQL_URI: process.env.GRAPHQL_URI || 'http://localhost/graphql', + ADMIN_AUTH_URL: process.env.ADMIN_AUTH_URL || 'http://localhost/admin/authenticate?token={token}', +} + +const community = { + COMMUNITY_NAME: process.env.COMMUNITY_NAME || 'Gradido Entwicklung', + COMMUNITY_URL: process.env.COMMUNITY_URL || 'http://localhost/', + COMMUNITY_REGISTER_URL: process.env.COMMUNITY_REGISTER_URL || 'http://localhost/register', + COMMUNITY_DESCRIPTION: + process.env.COMMUNITY_DESCRIPTION || 'Die lokale Entwicklungsumgebung von Gradido.', +} + const meta = { META_URL: process.env.META_URL || 'http://localhost', META_TITLE_DE: process.env.META_TITLE_DE || 'Gradido – Dein Dankbarkeitskonto', @@ -46,11 +60,6 @@ const meta = { META_AUTHOR: process.env.META_AUTHOR || 'Bernd Hückstädt - Gradido-Akademie', } -const endpoints = { - GRAPHQL_URI: process.env.GRAPHQL_URI || 'http://localhost/graphql', - ADMIN_AUTH_URL: process.env.ADMIN_AUTH_URL || 'http://localhost/admin/authenticate?token={token}', -} - // Check config version constants.CONFIG_VERSION.CURRENT = process.env.CONFIG_VERSION || constants.CONFIG_VERSION.DEFAULT if ( @@ -68,6 +77,7 @@ const CONFIG = { ...version, ...environment, ...endpoints, + ...community, ...meta, } diff --git a/frontend/src/config/index.spec.js b/frontend/src/config/index.spec.js new file mode 100644 index 000000000..3c4c7865e --- /dev/null +++ b/frontend/src/config/index.spec.js @@ -0,0 +1,9 @@ +import CONFIG from './index' + +describe('config/index', () => { + describe('decay start block', () => { + it('has the correct date set', () => { + expect(CONFIG.DECAY_START_TIME).toEqual(new Date('2021-05-13 17:46:31')) + }) + }) +}) diff --git a/frontend/src/filters/amount.js b/frontend/src/filters/amount.js index ded1cceb7..97340117d 100644 --- a/frontend/src/filters/amount.js +++ b/frontend/src/filters/amount.js @@ -13,6 +13,6 @@ const amount = (value) => { const GDD = (value) => { value = amount(value) if (value === '') return '' - if (!value.match(/^− /)) value = '+ ' + value + if (!value.match(/^− /) && !value.match(/^0[.,]00$/)) value = '+ ' + value return value + ' GDD' } diff --git a/frontend/src/graphql/mutations.js b/frontend/src/graphql/mutations.js index 95c811f8d..672af5f04 100644 --- a/frontend/src/graphql/mutations.js +++ b/frontend/src/graphql/mutations.js @@ -18,6 +18,12 @@ export const setPassword = gql` } ` +export const forgotPassword = gql` + mutation($email: String!) { + forgotPassword(email: $email) + } +` + export const updateUserInfos = gql` mutation( $firstName: String @@ -69,7 +75,7 @@ export const sendCoins = gql` export const createTransactionLink = gql` mutation($amount: Decimal!, $memo: String!) { createTransactionLink(amount: $amount, memo: $memo) { - code + link } } ` diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index d330d84f3..2bd905e5e 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -45,11 +45,12 @@ export const logout = gql` export const transactionsQuery = gql` query($currentPage: Int = 1, $pageSize: Int = 25, $order: Order = DESC) { transactionList(currentPage: $currentPage, pageSize: $pageSize, order: $order) { - balanceGDT - count - linkCount - balance - decayStartBlock + balance { + balance + balanceGDT + count + linkCount + } transactions { id typeId @@ -67,17 +68,15 @@ export const transactionsQuery = gql` end duration } + linkedUser { + email + } + transactionLinkId } } } ` -export const sendResetPasswordEmail = gql` - query($email: String!) { - sendResetPasswordEmail(email: $email) - } -` - export const listGDTEntriesQuery = gql` query($currentPage: Int!, $pageSize: Int!) { listGDTEntries(currentPage: $currentPage, pageSize: $pageSize) { @@ -96,17 +95,6 @@ export const listGDTEntriesQuery = gql` } ` -export const communityInfo = gql` - query { - getCommunityInfo { - name - description - registerUrl - url - } - } -` - export const communities = gql` query { communities { @@ -151,7 +139,7 @@ export const listTransactionLinks = gql` amount holdAvailableAmount memo - code + link createdAt validUntil redeemedAt diff --git a/frontend/src/layouts/AuthLayout.spec.js b/frontend/src/layouts/AuthLayout.spec.js new file mode 100644 index 000000000..dd2d2cab3 --- /dev/null +++ b/frontend/src/layouts/AuthLayout.spec.js @@ -0,0 +1,79 @@ +import { mount, RouterLinkStub } from '@vue/test-utils' +import AuthLayout from './AuthLayout' + +const localVue = global.localVue + +describe('AuthLayout', () => { + let wrapper + + const mocks = { + $i18n: { + locale: 'en', + }, + $t: jest.fn((t) => t), + $store: { + state: {}, + commit: jest.fn(), + }, + $route: { + meta: { + requiresAuth: false, + }, + }, + } + + const stubs = { + RouterLink: RouterLinkStub, + RouterView: true, + } + + const Wrapper = () => { + return mount(AuthLayout, { localVue, mocks, stubs }) + } + + describe('mount', () => { + beforeEach(() => { + wrapper = Wrapper() + }) + describe('Mobile Version Start', () => { + beforeEach(() => { + wrapper.vm.mobileStart = true + }) + + it('has Component AuthMobileStart', () => { + expect(wrapper.findComponent({ name: 'AuthMobileStart' }).exists()).toBe(true) + }) + }) + + describe('Desktop Version Start', () => { + beforeEach(() => { + wrapper.vm.mobileStart = false + }) + + it('has Component AuthNavbar', () => { + expect(wrapper.findComponent({ name: 'AuthNavbar' }).exists()).toBe(true) + }) + + it('has Component AuthCarousel', () => { + expect(wrapper.findComponent({ name: 'AuthCarousel' }).exists()).toBe(true) + }) + + it('has Component AuthFooter', () => { + expect(wrapper.findComponent({ name: 'AuthFooter' }).exists()).toBe(true) + }) + + it('has no sidebar', () => { + expect(wrapper.find('nav#sidenav-main').exists()).not.toBeTruthy() + }) + + it('has LanguageSwitch', () => { + expect(wrapper.findComponent({ name: 'LanguageSwitch' }).exists()).toBeTruthy() + }) + + test('test size in setTextSize ', () => { + wrapper.vm.setTextSize('85') + expect(wrapper.vm.$refs.pageFontSize.style.fontSize).toBe('85rem') + }) + }) + }) +}) diff --git a/frontend/src/layouts/AuthLayout.vue b/frontend/src/layouts/AuthLayout.vue new file mode 100644 index 000000000..1cc4923d1 --- /dev/null +++ b/frontend/src/layouts/AuthLayout.vue @@ -0,0 +1,178 @@ + + + + + diff --git a/frontend/src/layouts/AuthLayout_gdd.spec.js b/frontend/src/layouts/AuthLayout_gdd.spec.js deleted file mode 100644 index 691cb3d0e..000000000 --- a/frontend/src/layouts/AuthLayout_gdd.spec.js +++ /dev/null @@ -1,67 +0,0 @@ -import { mount } from '@vue/test-utils' -import AuthLayoutGdd from './AuthLayout_gdd' - -const localVue = global.localVue - -describe('AuthLayoutGdd', () => { - let wrapper - - const mocks = { - $i18n: { - locale: 'en', - }, - $t: jest.fn((t) => t), - $route: { - meta: { - hideFooter: false, - }, - path: '/', - }, - $store: { - state: {}, - commit: jest.fn(), - }, - } - - const stubs = { - // RouterLink: RouterLinkStub, - RouterView: true, - } - - const Wrapper = () => { - return mount(AuthLayoutGdd, { localVue, mocks, stubs }) - } - - describe('mount', () => { - beforeEach(() => { - wrapper = Wrapper() - }) - - it('has no sidebar', () => { - expect(wrapper.find('nav#sidenav-main').exists()).not.toBeTruthy() - }) - - it('has a main content div', () => { - expect(wrapper.find('div.main-content').exists()).toBeTruthy() - }) - - it('has a footer inside the main content', () => { - expect(wrapper.find('div.main-content').find('footer.footer').exists()).toBeTruthy() - }) - - it('has LanguageSwitch', () => { - expect(wrapper.findComponent({ name: 'LanguageSwitch' }).exists()).toBeTruthy() - }) - - describe('check LanguageSwitch on register page', () => { - beforeEach(() => { - mocks.$route.path = '/register' - wrapper = Wrapper() - }) - - it('has not LanguageSwitch', () => { - expect(wrapper.findComponent({ name: 'LanguageSwitch' }).exists()).toBeFalsy() - }) - }) - }) -}) diff --git a/frontend/src/layouts/AuthLayout_gdd.vue b/frontend/src/layouts/AuthLayout_gdd.vue deleted file mode 100644 index e87ebe06a..000000000 --- a/frontend/src/layouts/AuthLayout_gdd.vue +++ /dev/null @@ -1,19 +0,0 @@ - - diff --git a/frontend/src/layouts/DashboardLayout_gdd.spec.js b/frontend/src/layouts/DashboardLayout.spec.js similarity index 91% rename from frontend/src/layouts/DashboardLayout_gdd.spec.js rename to frontend/src/layouts/DashboardLayout.spec.js index fd45b9f05..398724201 100644 --- a/frontend/src/layouts/DashboardLayout_gdd.spec.js +++ b/frontend/src/layouts/DashboardLayout.spec.js @@ -1,6 +1,6 @@ import { mount, RouterLinkStub } from '@vue/test-utils' import flushPromises from 'flush-promises' -import DashboardLayoutGdd from './DashboardLayout_gdd' +import DashboardLayout from './DashboardLayout' import { toastErrorSpy } from '@test/testSetup' @@ -19,7 +19,7 @@ const apolloMock = jest.fn().mockResolvedValue({ }, }) -describe('DashboardLayoutGdd', () => { +describe('DashboardLayout', () => { let wrapper const mocks = { @@ -61,7 +61,7 @@ describe('DashboardLayoutGdd', () => { } const Wrapper = () => { - return mount(DashboardLayoutGdd, { localVue, mocks, stubs }) + return mount(DashboardLayout, { localVue, mocks, stubs }) } describe('mount', () => { @@ -145,11 +145,13 @@ describe('DashboardLayoutGdd', () => { apolloMock.mockResolvedValue({ data: { transactionList: { - balanceGDT: 100, - count: 4, - linkCount: 8, - balance: 1450, - decay: 1250, + balance: { + balanceGDT: 100, + count: 4, + linkCount: 8, + balance: 1450, + decay: 1250, + }, transactions: ['transaction', 'transaction', 'transaction', 'transaction'], }, }, @@ -276,5 +278,14 @@ describe('DashboardLayoutGdd', () => { }) }) }) + + describe('set tunneled email', () => { + it('updates tunneled email', async () => { + await wrapper + .findComponent({ ref: 'router-view' }) + .vm.$emit('set-tunneled-email', 'bibi@bloxberg.de') + expect(wrapper.vm.tunneledEmail).toBe('bibi@bloxberg.de') + }) + }) }) }) diff --git a/frontend/src/layouts/DashboardLayout_gdd.vue b/frontend/src/layouts/DashboardLayout.vue similarity index 85% rename from frontend/src/layouts/DashboardLayout_gdd.vue rename to frontend/src/layouts/DashboardLayout.vue index a2a76d88c..cd8794c40 100755 --- a/frontend/src/layouts/DashboardLayout_gdd.vue +++ b/frontend/src/layouts/DashboardLayout.vue @@ -15,7 +15,7 @@
-
+
@@ -45,6 +45,7 @@ import { FadeTransition } from 'vue2-transitions' import CONFIG from '@/config' export default { + name: 'DashboardLayout', components: { Navbar, Sidebar, @@ -53,16 +54,19 @@ export default { }, data() { return { - logo: 'img/brand/green.png', balance: 0, GdtBalance: 0, transactions: [], - bookedBalance: 0, transactionCount: 0, transactionLinkCount: 0, pending: true, visible: false, - decayStartBlock: new Date(), + tunneledEmail: null, + } + }, + provide() { + return { + getTunneledEmail: () => this.tunneledEmail, } }, methods: { @@ -96,12 +100,13 @@ export default { data: { transactionList }, } = result this.GdtBalance = - transactionList.balanceGDT === null ? null : Number(transactionList.balanceGDT) + transactionList.balance.balanceGDT === null + ? null + : Number(transactionList.balance.balanceGDT) this.transactions = transactionList.transactions - this.balance = Number(transactionList.balance) - this.transactionCount = transactionList.count - this.transactionLinkCount = transactionList.linkCount - this.decayStartBlock = new Date(transactionList.decayStartBlock) + this.balance = Number(transactionList.balance.balance) + this.transactionCount = transactionList.balance.count + this.transactionLinkCount = transactionList.balance.linkCount this.pending = false }) .catch((error) => { @@ -118,6 +123,9 @@ export default { setVisible(bool) { this.visible = bool }, + setTunneledEmail(email) { + this.tunneledEmail = email + }, }, computed: { elopageUri() { diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 35155ca79..3b4912a2f 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -1,5 +1,22 @@ { + "100": "100%", + "1000thanks": "1000 Dank, weil du bei uns bist!", + "125": "125%", + "85": "85%", "advanced-calculation": "Vorausberechnung", + "auth": { + "left": { + "gratitude": "Dankbarkeit", + "hasAccount": "Du hast schon einen Account?", + "hereLogin": "Hier Anmelden", + "learnMore": "Erfahre mehr …", + "newCurrency": "Die neue Währung", + "oneAnotherNature": "FÜR EINANDER, FÜR ALLE, FÜR DIE NATUR" + }, + "navbar": { + "aboutGradido": "Über Gradido" + } + }, "back": "Zurück", "community": { "choose-another-community": "Eine andere Gemeinschaft auswählen", @@ -30,12 +47,17 @@ "delete": "Löschen", "em-dash": "—", "error": { + "email-already-sent": "Wir haben dir bereits eine E-Mail vor weniger als 10 Minuten geschickt.", "empty-transactionlist": "Es gab einen Fehler mit der Übermittlung der Anzahl deiner Transaktionen.", "error": "Fehler!", - "no-account": "Leider konnten wir keinen Account mit diesen Daten finden.", - "no-transactionlist": "Es gab leider einen Fehler. Es wurden keine Transaktionen vom Server übermittelt", - "session-expired": "Die Sitzung wurde aus Sicherheitsgründen beendet." + "no-account": "Leider konnten wir keinen (aktivierten) Account mit diesen Daten finden.", + "no-transactionlist": "Es gab leider einen Fehler. Es wurden keine Transaktionen vom Server übermittelt.", + "no-user": "Kein Benutzer mit diesen Anmeldedaten.", + "session-expired": "Die Sitzung wurde aus Sicherheitsgründen beendet.", + "unknown-error": "Unbekanter Fehler: ", + "user-already-exists": "Ein Benutzer mit diesen Daten existiert bereits." }, + "followUs": "folge uns:", "footer": { "app_version": "App version {version}", "copyright": { @@ -51,6 +73,7 @@ "amount": "Betrag", "at": "am", "cancel": "Abbrechen", + "check_now": "Jetzt prüfen", "close": "Schließen", "current_balance": "Aktueller Kontostand", "date": "Datum", @@ -59,7 +82,7 @@ "email": "E-Mail", "firstname": "Vorname", "from": "Von", - "generate_now": "jetzt generieren", + "generate_now": "Jetzt generieren", "lastname": "Nachname", "memo": "Nachricht", "message": "Nachricht", @@ -94,42 +117,46 @@ }, "GDD": "GDD", "gdd_per_link": { - "choose-amount": "Wähle einen Betrag aus, welchen du per Link versenden möchtest. Du kannst auch noch eine Nachricht eintragen. Beim Klick „jetzt generieren“ wird ein Link erstellt, den du versenden kannst.", + "choose-amount": "Wähle einen Betrag aus, welchen du per Link versenden möchtest. Du kannst auch noch eine Nachricht eintragen. Beim Klick „Jetzt generieren“ wird ein Link erstellt, den du versenden kannst.", "copy": "kopieren", "created": "Der Link wurde erstellt!", "decay-14-day": "Vergänglichkeit für 14 Tage", "delete-the-link": "Den Link löschen?", "deleted": "Der Link wurde gelöscht!", - "expired": "Abgelaufen", - "has-account": "Du besitzt bereits ein Gradido Konto", + "expiredOn": "Abgelaufen am", + "has-account": "Du besitzt bereits ein Gradido Konto?", "header": "Gradidos versenden per Link", - "link-copied": "Link wurde in die Zwischenablage kopiert", + "isFree": "Gradido ist weltweit kostenfrei.", + "link-copied": "Link wurde in die Zwischenablage kopiert. Du kannst ihn jetzt in eine E-Mail oder Nachricht einfügen.", "link-deleted": "Der Link wurde am {date} gelöscht.", "link-expired": "Der Link ist nicht mehr gültig. Die Gültigkeit ist am {date} abgelaufen.", "link-overview": "Linkübersicht", "links_count": "Aktive Links", - "links_sum": "Summe deiner versendeten Gradidos", - "no-account": "Du besitzt noch kein Gradido Konto", + "links_sum": "Offene Links und QR-Codes", + "no-account": "Du besitzt noch kein Gradido Konto?", "no-redeem": "Du darfst deinen eigenen Link nicht einlösen!", - "not-copied": "Konnte den Link nicht kopieren: {err}", + "not-copied": "Dein Gerät lässt das Kopieren leider nicht zu! Bitte kopiere den Link von Hand!", "redeem": "Einlösen", "redeem-text": "Willst du den Betrag jetzt einlösen?", "redeemed": "Erfolgreich eingelöst! Deinem Konto wurden {n} GDD gutgeschrieben.", "redeemed-at": "Der Link wurde bereits am {date} eingelöst.", + "redeemed-title": "eingelöst", "to-login": "Log dich ein", - "to-register": "Registriere ein neues Konto" + "to-register": "Registriere ein neues Konto.", + "validUntil": "Gültig bis" }, "gdt": { - "calculation": "Berechnung der GradidoTransform", + "calculation": "Berechnung der Gradido Transform", "contribution": "Beitrag", "conversion": "Umrechnung", - "conversion-gdt-euro": "Umrechnung Euro / GradidoTransform (GDT)", + "conversion-gdt-euro": "Umrechnung Euro / Gradido Transform (GDT)", "credit": "Gutschrift", "factor": "Faktor", "formula": "Berechnungsformel", "funding": "Zu den Förderbeiträgen", - "gdt-received": "GradidoTransform (GDT) erhalten", - "no-transactions": "Du hast noch keine GradidoTransform (GDT).", + "gdt": "Gradido Transform", + "gdt-received": "Gradido Transform (GDT) erhalten", + "no-transactions": "Du hast noch keine Gradido Transform (GDT).", "not-reachable": "Der GDT Server ist nicht erreichbar.", "publisher": "Dein geworbenes Mitglied hat einen Beitrag bezahlt", "raise": "Erhöhung", @@ -140,9 +167,9 @@ "login": "Anmeldung", "math": { "aprox": "~", - "div": "/", "equal": "=", "exclaim": "!", + "minus": "−", "pipe": "|" }, "navigation": { @@ -155,10 +182,7 @@ "support": "Support", "transactions": "Transaktionen" }, - "publisher": { - "infoText": "Wenn dir dein Empfehlungsgeber seine Publisher-Id gegeben hat, trage sie hier ein, sonst lass das Feld bitte unverändert!", - "publisherId": "Publisher-Id:" - }, + "qrCode": "QR Code", "send_gdd": "GDD versenden", "send_per_link": "GDD versenden per Link", "settings": { @@ -198,24 +222,28 @@ "subtitle": "Wenn du dein Passwort vergessen hast, kannst du es hier zurücksetzen." } }, + "signin": "Anmelden", "signup": "Registrieren", "site": { + "forgotPassword": { + "heading": "Bitte gib deine E-Mail an mit der du bei Gradido angemeldet bist." + }, "login": { - "community": "Tausend Dank, weil du bei uns bist!", - "heading": "Gradido", - "new_wallet": "Neues Konto erstellen", - "signin": "Anmelden" + "heading": "Melde dich mit deinen Zugangsdaten an. Bewahre sie stets sicher auf!", + "saveLogin": "Anmeldung speichern" + }, + "resetPassword": { + "heading": "Trage bitte dein Passwort ein und wiederhole es." }, "signup": { "agree": "Ich stimme der Datenschutzerklärung zu.", "dont_match": "Die Passwörter stimmen nicht überein.", + "heading": "Registriere dich indem du alle Daten vollständig und in die richtigen Felder eingibst.", "lowercase": "Ein Kleinbuchstabe erforderlich.", "minimum": "Mindestens 8 Zeichen.", "no-whitespace": "Keine Leerzeichen und Tabulatoren", "one_number": "Zahl erforderlich.", "special-char": "Sonderzeichen erforderlich (z.B. _ oder ä)", - "subtitle": "Werde Teil der Gemeinschaft!", - "title": "Erstelle dein Gradido-Konto", "uppercase": "Großbuchstabe erforderlich." }, "thx": { @@ -227,7 +255,8 @@ "register": "Du bist jetzt registriert, bitte überprüfe deine Emails und klicke auf den Aktivierungslink.", "reset": "Dein Passwort wurde geändert.", "resetPassword": "Den Code den Du genutzt hast ist zu alt bitte fordere ein neuen über die Passwort Reset Seite an.", - "title": "Danke!" + "title": "Danke!", + "unsetPassword": "Dein Passwort wurde noch nicht gesetzt. Bitte setze es neu." } }, "success": "Erfolg", @@ -249,5 +278,8 @@ }, "transaction-link": { "send_you": "sendet dir" - } + }, + "via_link": "über einen Link", + "welcome": "Willkommen", + "WelcomeBy": "bei {name}" } diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index af54f3b50..63f2510a3 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -1,5 +1,22 @@ { + "100": "100%", + "1000thanks": "1000 thanks for being with us!", + "125": "125%", + "85": "85%", "advanced-calculation": "Advanced calculation", + "auth": { + "left": { + "gratitude": "Gratitude", + "hasAccount": "You already have an account?", + "hereLogin": "Log in here", + "learnMore": "Learn more …", + "newCurrency": "The new currency", + "oneAnotherNature": "FOR ONE ANOTHER, FOR ALL, FOR NATURE" + }, + "navbar": { + "aboutGradido": "About Gradido" + } + }, "back": "Back", "community": { "choose-another-community": "Choose another community", @@ -30,12 +47,17 @@ "delete": "Delete", "em-dash": "—", "error": { + "email-already-sent": "We already sent you an email less than 10 minutes ago.", "empty-transactionlist": "There was an error with the transmission of the number of your transactions.", "error": "Error!", - "no-account": "Unfortunately we could not find an account to the given data!", + "no-account": "Unfortunately we could not find an (activated) account to the given data.", "no-transactionlist": "Unfortunately, there was an error. No transactions have been sent from the server.", - "session-expired": "The session was closed for security reasons." + "no-user": "No user with this credentials.", + "session-expired": "The session was closed for security reasons.", + "unknown-error": "Unknown error: ", + "user-already-exists": "A user with this data already exists." }, + "followUs": "follow us:", "footer": { "app_version": "App version {version}", "copyright": { @@ -51,6 +73,7 @@ "amount": "Amount", "at": "at", "cancel": "Cancel", + "check_now": "Check now", "close": "Close", "current_balance": "Current Balance", "date": "Date", @@ -100,36 +123,40 @@ "decay-14-day": "Decay for 14 days", "delete-the-link": "Delete the link?", "deleted": "The link was deleted!", - "expired": "Expired", - "has-account": "You already have a Gradido account", + "expiredOn": "Expired on", + "has-account": "You already have a Gradido account?", "header": "Send Gradidos via link", - "link-copied": "Link copied to clipboard", + "isFree": "Gradido is free of charge worldwide.", + "link-copied": "Link has been copied to the clipboard. You can now paste it into an email or message.", "link-deleted": "The link was deleted on {date}.", "link-expired": "The link is no longer valid. The validity expired on {date}.", "link-overview": "Link overview", "links_count": "Active links", - "links_sum": "Total of your sent Gradidos", - "no-account": "You don't have a Gradido account yet", + "links_sum": "Open links and QR codes", + "no-account": "You don't have a Gradido account yet?", "no-redeem": "You not allowed to redeem your own link!", - "not-copied": "Could not copy link: {err}", + "not-copied": "Unfortunately, your device does not allow copying! Please copy the link by hand!", "redeem": "Redeem", "redeem-text": "Do you want to redeem the amount now?", "redeemed": "Successfully redeemed! Your account has been credited with {n} GDD.", "redeemed-at": "The link was already redeemed on {date}.", + "redeemed-title": "redeemed", "to-login": "Log in", - "to-register": "Register a new account" + "to-register": "Register a new account.", + "validUntil": "Valid until" }, "gdt": { - "calculation": "Calculation of GradidoTransform", + "calculation": "Calculation of Gradido Transform", "contribution": "Contribution", "conversion": "Conversion", - "conversion-gdt-euro": "Conversion Euro / GradidoTransform (GDT)", + "conversion-gdt-euro": "Conversion Euro / Gradido Transform (GDT)", "credit": "Credit", "factor": "Factor", "formula": "Calculation formula", "funding": "To the funding contributions", - "gdt-received": "GradidoTransform (GDT) received", - "no-transactions": "You do not have GradidoTransform (GDT) yet.", + "gdt": "Gradido Transform", + "gdt-received": "Gradido Transform (GDT) received", + "no-transactions": "You do not have Gradido Transform (GDT) yet.", "not-reachable": "The GDT Server is not reachable.", "publisher": "A member you referred has paid a contribution", "raise": "Increase", @@ -140,9 +167,9 @@ "login": "Login", "math": { "aprox": "~", - "div": "/", "equal": "=", "exclaim": "!", + "minus": "−", "pipe": "|" }, "navigation": { @@ -155,10 +182,7 @@ "support": "Support", "transactions": "Transactions" }, - "publisher": { - "infoText": "If your referrer has given you his publisher id, enter it here, otherwise leave the field unchanged!", - "publisherId": "PublisherID:" - }, + "qrCode": "QR Code", "send_gdd": "GDD send", "send_per_link": "GDD send via link", "settings": { @@ -198,28 +222,32 @@ "subtitle": "If you have forgotten your password, you can reset it here." } }, + "signin": "Sign in", "signup": "Sign up", "site": { + "forgotPassword": { + "heading": "Please enter the email address by which you're registered here." + }, "login": { - "community": "A thousand thanks for being with us!", - "heading": "Gradido", - "new_wallet": "Create new account", - "signin": "Sign in" + "heading": "Log in with your access data. Keep them safe!", + "saveLogin": "Save login" + }, + "resetPassword": { + "heading": "Please enter your password and repeat it." }, "signup": { "agree": "I agree to the privacy policy.", "dont_match": "Passwords don't match.", + "heading": "Register by entering all data completely and in the correct fields.", "lowercase": "One lowercase letter required.", "minimum": "8 characters minimum.", "no-whitespace": "No white spaces and tabs", "one_number": "One number required.", "special-char": "One special character required (e.g. _ or ä)", - "subtitle": "Become a part of the community!", - "title": "Create your Gradido account", "uppercase": "One uppercase letter required." }, "thx": { - "activateEmail": "Your account has not been activated yet, please check your emails and click the activation link or order a new activation link over the password reset page.", + "activateEmail": "Your account has not been activated yet. Please check your emails and click the activation link or order a new activation link over the password reset page.", "checkEmail": "Your email has been successfully verified. You can sign in now.", "email": "We have sent you an email.", "emailActivated": "Thank you your email has been activated.", @@ -227,7 +255,8 @@ "register": "You are registered now, please check your emails and click the activation link.", "reset": "Your password has been changed.", "resetPassword": "The code you used was to old please order a new on over the password reset page.", - "title": "Thank you!" + "title": "Thank you!", + "unsetPassword": "Your password has not been set yet. Please set it again." } }, "success": "Success", @@ -249,5 +278,8 @@ }, "transaction-link": { "send_you": "wants to send you" - } + }, + "via_link": "via Link", + "welcome": "Welcome", + "WelcomeBy": "by {name}" } diff --git a/frontend/src/main.js b/frontend/src/main.js index 585b603cb..0edcb7211 100755 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -16,6 +16,8 @@ import router from './routes/router' import { apolloProvider } from './plugins/apolloProvider' +import 'clipboard-polyfill/overwrite-globals' + // plugin setup Vue.use(DashboardPlugin) Vue.config.productionTip = false diff --git a/frontend/src/mixins/getCommunityInfo.js b/frontend/src/mixins/getCommunityInfo.js deleted file mode 100644 index c6c021f9c..000000000 --- a/frontend/src/mixins/getCommunityInfo.js +++ /dev/null @@ -1,24 +0,0 @@ -import { communityInfo } from '../graphql/queries' - -export const getCommunityInfoMixin = { - methods: { - getCommunityInfo() { - if (this.$store.state.community.name === '') { - this.$apollo - .query({ - query: communityInfo, - }) - .then((result) => { - this.$store.commit('community', result.data.getCommunityInfo) - return result.data.getCommunityInfo - }) - .catch((error) => { - this.toastError(error.message) - }) - } - }, - }, - created() { - this.getCommunityInfo() - }, -} diff --git a/frontend/src/pages/ForgotPassword.spec.js b/frontend/src/pages/ForgotPassword.spec.js index a547f9cc1..ecfe5a1a9 100644 --- a/frontend/src/pages/ForgotPassword.spec.js +++ b/frontend/src/pages/ForgotPassword.spec.js @@ -1,5 +1,6 @@ import { mount, RouterLinkStub } from '@vue/test-utils' import flushPromises from 'flush-promises' +import { toastErrorSpy } from '@test/testSetup' import ForgotPassword from './ForgotPassword' const mockAPIcall = jest.fn() @@ -21,7 +22,7 @@ const createMockObject = (comingFrom) => { push: mockRouterPush, }, $apollo: { - query: mockAPIcall, + mutate: mockAPIcall, }, $route: { params: { @@ -46,15 +47,7 @@ describe('ForgotPassword', () => { }) it('renders the component', () => { - expect(wrapper.find('div.forgot-password').exists()).toBeTruthy() - }) - - it('has a title', () => { - expect(wrapper.find('h1').text()).toEqual('settings.password.reset') - }) - - it('has a subtitle', () => { - expect(wrapper.find('p.text-lead').text()).toEqual('settings.password.subtitle') + expect(wrapper.find('div.forgot-password').exists()).toBe(true) }) describe('back button', () => { @@ -83,7 +76,7 @@ describe('ForgotPassword', () => { }) it('has a submit button', () => { - expect(form.find('button[type="submit"]').exists()).toBeTruthy() + expect(form.find('button[type="submit"]').exists()).toBe(true) }) describe('invalid Email', () => { @@ -116,15 +109,23 @@ describe('ForgotPassword', () => { await flushPromises() }) - it('pushes to "/thx/forgotPassword"', () => { - expect(mockAPIcall).toBeCalledWith( - expect.objectContaining({ - variables: { - email: 'user@example.org', - }, - }), - ) - expect(mockRouterPush).toHaveBeenCalledWith('/thx/forgotPassword') + it('shows error title, subtitle, login button', () => { + expect(wrapper.vm.showPageMessage).toBe(true) + expect(wrapper.find('.test-message-headline').text()).toBe('site.thx.errorTitle') + expect(wrapper.find('.test-message-subtitle').text()).toBe('error.email-already-sent') + expect(wrapper.find('.test-message-button').text()).toBe('login') + }) + + it('button link directs to "/login"', () => { + expect(wrapper.find('.test-message-button').attributes('href')).toBe('/login') + }) + + it.skip('click redirects to "/login"', async () => { + expect(mockRouterPush).toBeCalledWith('/login') + }) + + it('toasts a standard error message', () => { + expect(toastErrorSpy).toBeCalledWith('error.email-already-sent') }) }) @@ -141,29 +142,19 @@ describe('ForgotPassword', () => { await flushPromises() }) - it('pushes to "/thx/forgotPassword"', () => { - expect(mockAPIcall).toBeCalledWith( - expect.objectContaining({ - variables: { - email: 'user@example.org', - }, - }), - ) - expect(mockRouterPush).toHaveBeenCalledWith('/thx/forgotPassword') + it('shows success title, subtitle, login button', () => { + expect(wrapper.vm.showPageMessage).toBe(true) + expect(wrapper.find('.test-message-headline').text()).toBe('site.thx.title') + expect(wrapper.find('.test-message-subtitle').text()).toBe('site.thx.email') + expect(wrapper.find('.test-message-button').text()).toBe('login') + }) + + it('button link redirects to "/login"', () => { + expect(wrapper.find('.test-message-button').attributes('href')).toBe('/login') }) }) }) }) }) - - describe('comingFrom login', () => { - beforeEach(() => { - wrapper = Wrapper(createMockObject('resetPassword')) - }) - - it('has another subtitle', () => { - expect(wrapper.find('p.text-lead').text()).toEqual('settings.password.resend_subtitle') - }) - }) }) }) diff --git a/frontend/src/pages/ForgotPassword.vue b/frontend/src/pages/ForgotPassword.vue index 81121fbe7..74e5161f1 100644 --- a/frontend/src/pages/ForgotPassword.vue +++ b/frontend/src/pages/ForgotPassword.vue @@ -1,21 +1,9 @@ + diff --git a/frontend/src/pages/Login.spec.js b/frontend/src/pages/Login.spec.js index e26888d49..9158aaf42 100644 --- a/frontend/src/pages/Login.spec.js +++ b/frontend/src/pages/Login.spec.js @@ -1,22 +1,11 @@ import { RouterLinkStub, mount } from '@vue/test-utils' import flushPromises from 'flush-promises' -import Login from './Login' - import { toastErrorSpy } from '@test/testSetup' +import Login from './Login' const localVue = global.localVue -const apolloQueryMock = jest.fn().mockResolvedValue({ - data: { - getCommunityInfo: { - name: 'test12', - description: 'test community 12', - url: 'http://test12.test12/', - registerUrl: 'http://test12.test12/register', - }, - }, -}) - +const apolloQueryMock = jest.fn() const mockStoreDispach = jest.fn() const mockStoreCommit = jest.fn() const mockRouterPush = jest.fn() @@ -39,10 +28,6 @@ describe('Login', () => { dispatch: mockStoreDispach, commit: mockStoreCommit, state: { - community: { - name: '', - description: '', - }, publisherId: 12345, }, }, @@ -73,63 +58,8 @@ describe('Login', () => { wrapper = Wrapper() }) - it('commits the community info to the store', () => { - expect(mockStoreCommit).toBeCalledWith('community', { - name: 'test12', - description: 'test community 12', - url: 'http://test12.test12/', - registerUrl: 'http://test12.test12/register', - }) - }) - it('renders the Login form', () => { - expect(wrapper.find('div.login-form').exists()).toBeTruthy() - }) - - describe('communities gives back error', () => { - beforeEach(() => { - apolloQueryMock.mockRejectedValue({ - message: 'Failed to get communities', - }) - wrapper = Wrapper() - }) - - it('toasts an error message', () => { - expect(toastErrorSpy).toBeCalledWith('Failed to get communities') - }) - }) - - describe('Login header', () => { - it('has a welcome message', () => { - expect(wrapper.find('div.header').text()).toBe('site.login.heading site.login.community') - }) - }) - - describe('Community data already loaded', () => { - beforeEach(() => { - jest.clearAllMocks() - mocks.$store.state.community = { - name: 'Gradido Entwicklung', - url: 'http://localhost/', - registerUrl: 'http://localhost/register', - description: 'Die lokale Entwicklungsumgebung von Gradido.', - } - wrapper = Wrapper() - }) - - it('has a Community name', () => { - expect(wrapper.find('.test-communitydata b').text()).toBe('Gradido Entwicklung') - }) - - it('has a Community description', () => { - expect(wrapper.find('.test-communitydata p').text()).toBe( - 'Die lokale Entwicklungsumgebung von Gradido.', - ) - }) - - it('does not call community data update', () => { - expect(apolloQueryMock).not.toBeCalled() - }) + expect(wrapper.find('div.login-form').exists()).toBe(true) }) describe('links', () => { @@ -138,37 +68,23 @@ describe('Login', () => { 'settings.password.forgot_pwd', ) }) - - it('links to /forgot-password when clicking "Forgot Password"', () => { - expect(wrapper.findAllComponents(RouterLinkStub).at(0).props().to).toBe('/forgot-password') - }) - - it('has a link "Create new account"', () => { - expect(wrapper.findAllComponents(RouterLinkStub).at(1).text()).toEqual( - 'site.login.new_wallet', - ) - }) - - it('links to /register when clicking "Create new account"', () => { - expect(wrapper.findAllComponents(RouterLinkStub).at(1).props().to).toBe('/register') - }) }) describe('Login form', () => { it('has a login form', () => { - expect(wrapper.find('form').exists()).toBeTruthy() + expect(wrapper.find('form').exists()).toBe(true) }) it('has an Email input field', () => { - expect(wrapper.find('input[placeholder="Email"]').exists()).toBeTruthy() + expect(wrapper.find('input[placeholder="Email"]').exists()).toBe(true) }) it('has an Password input field', () => { - expect(wrapper.find('input[placeholder="form.password"]').exists()).toBeTruthy() + expect(wrapper.find('input[placeholder="form.password"]').exists()).toBe(true) }) it('has a Submit button', () => { - expect(wrapper.find('button[type="submit"]').exists()).toBeTruthy() + expect(wrapper.find('button[type="submit"]').exists()).toBe(true) }) }) @@ -197,13 +113,13 @@ describe('Login', () => { await wrapper.find('input[placeholder="Email"]').setValue('user@example.org') await wrapper.find('input[placeholder="form.password"]').setValue('1234') await flushPromises() - await wrapper.find('form').trigger('submit') - await flushPromises() apolloQueryMock.mockResolvedValue({ data: { login: 'token', }, }) + await wrapper.find('form').trigger('submit') + await flushPromises() }) it('calls the API with the given data', () => { @@ -255,58 +171,114 @@ describe('Login', () => { }) }) }) + }) - describe('login fails', () => { - beforeEach(() => { - apolloQueryMock.mockRejectedValue({ - message: '..No user with this credentials', - }) + describe('login fails', () => { + const createError = async (errorMessage) => { + apolloQueryMock.mockRejectedValue({ + message: errorMessage, + }) + wrapper = Wrapper() + jest.clearAllMocks() + await wrapper.find('input[placeholder="Email"]').setValue('user@example.org') + await wrapper.find('input[placeholder="form.password"]').setValue('1234') + await flushPromises() + await wrapper.find('form').trigger('submit') + await flushPromises() + } + + describe('login fails with "User email not validated"', () => { + beforeEach(async () => { + await createError('GraphQL error: User email not validated.') }) it('hides the spinner', () => { expect(spinnerHideMock).toBeCalled() }) - it('toasts an error message', () => { + it('shows error title, subtitle, login button', () => { + expect(wrapper.vm.showPageMessage).toBe(true) + expect(wrapper.find('.test-message-headline').text()).toBe('site.thx.errorTitle') + expect(wrapper.find('.test-message-subtitle').text()).toBe('site.thx.activateEmail') + expect(wrapper.find('.test-message-button').text()).toBe('settings.password.reset') + }) + + it('button link directs to "/forgot-password"', () => { + expect(wrapper.find('.test-message-button').attributes('href')).toBe('/forgot-password') + }) + + it.skip('click redirects to "/forgot-password"', async () => { + // wrapper.find('.test-message-button').trigger('click') + // await flushPromises() + // await wrapper.vm.$nextTick() + // expect(mockRouterPush).toBeCalledWith('/forgot-password') + }) + + it('toasts the error message', () => { expect(toastErrorSpy).toBeCalledWith('error.no-account') }) + }) - describe('login fails with "User email not validated"', () => { - beforeEach(async () => { - apolloQueryMock.mockRejectedValue({ - message: 'User email not validated', - }) - wrapper = Wrapper() - jest.clearAllMocks() - await wrapper.find('input[placeholder="Email"]').setValue('user@example.org') - await wrapper.find('input[placeholder="form.password"]').setValue('1234') - await flushPromises() - await wrapper.find('form').trigger('submit') - await flushPromises() - }) - - it('redirects to /thx/login', () => { - expect(mockRouterPush).toBeCalledWith('/thx/login') - }) + describe('login fails with "User has no password set yet"', () => { + beforeEach(async () => { + await createError('GraphQL error: User has no password set yet.') }) - describe('login fails with "User has no password set yet"', () => { - beforeEach(async () => { - apolloQueryMock.mockRejectedValue({ - message: 'User has no password set yet', - }) - wrapper = Wrapper() - jest.clearAllMocks() - await wrapper.find('input[placeholder="Email"]').setValue('user@example.org') - await wrapper.find('input[placeholder="form.password"]').setValue('1234') - await flushPromises() - await wrapper.find('form').trigger('submit') - await flushPromises() - }) + it('shows error title, subtitle, login button', () => { + expect(wrapper.vm.showPageMessage).toBe(true) + expect(wrapper.find('.test-message-headline').text()).toBe('site.thx.errorTitle') + expect(wrapper.find('.test-message-subtitle').text()).toBe('site.thx.unsetPassword') + expect(wrapper.find('.test-message-button').text()).toBe('settings.password.reset') + }) - it('redirects to /reset-password/login', () => { - expect(mockRouterPush).toBeCalledWith('/reset-password/login') - }) + it('button link directs to "/reset-password/login"', () => { + expect(wrapper.find('.test-message-button').attributes('href')).toBe( + '/reset-password/login', + ) + }) + + it.skip('click redirects to "/reset-password/login"', () => { + // expect(mockRouterPush).toBeCalledWith('/reset-password/login') + }) + + it('toasts the error message', () => { + expect(toastErrorSpy).toBeCalledWith('error.no-account') + }) + }) + + describe('login fails with "No user with this credentials"', () => { + beforeEach(async () => { + await createError('GraphQL error: No user with this credentials.') + }) + + it('shows no error message on the page', () => { + // don't show any error on the page! against boots + expect(wrapper.vm.showPageMessage).toBe(false) + expect(wrapper.find('.test-message-headline').exists()).toBe(false) + expect(wrapper.find('.test-message-subtitle').exists()).toBe(false) + expect(wrapper.find('.test-message-button').exists()).toBe(false) + }) + + it('toasts the error message', () => { + expect(toastErrorSpy).toBeCalledWith('error.no-user') + }) + }) + + describe('login fails with an unknow error', () => { + beforeEach(async () => { + await createError(' – Unknow error') + }) + + it('shows no error message on the page', () => { + // don't show any error on the page! against boots + expect(wrapper.vm.showPageMessage).toBe(false) + expect(wrapper.find('.test-message-headline').exists()).toBe(false) + expect(wrapper.find('.test-message-subtitle').exists()).toBe(false) + expect(wrapper.find('.test-message-button').exists()).toBe(false) + }) + + it('toasts the error message', () => { + expect(toastErrorSpy).toBeCalledWith('error.unknown-error – Unknow error') }) }) }) diff --git a/frontend/src/pages/Login.vue b/frontend/src/pages/Login.vue index 79532cb21..208f7941f 100755 --- a/frontend/src/pages/Login.vue +++ b/frontend/src/pages/Login.vue @@ -1,77 +1,68 @@ + diff --git a/frontend/src/pages/Overview.spec.js b/frontend/src/pages/Overview.spec.js index af234801e..8f0dda01d 100644 --- a/frontend/src/pages/Overview.spec.js +++ b/frontend/src/pages/Overview.spec.js @@ -25,12 +25,6 @@ describe('Overview', () => { wrapper = Wrapper() }) - it('has a status gdd-status-gdd', () => { - expect(wrapper.find('div.gdd-status-gdd').exists()).toBeTruthy() - }) - it('has a status gdd-status-gdt', () => { - expect(wrapper.find('div.gdd-status-gdt').exists()).toBeTruthy() - }) it('has a transactions table', () => { expect(wrapper.find('div.gdd-transaction-list').exists()).toBeTruthy() }) diff --git a/frontend/src/pages/Overview.vue b/frontend/src/pages/Overview.vue index 93344b3ee..15e140058 100644 --- a/frontend/src/pages/Overview.vue +++ b/frontend/src/pages/Overview.vue @@ -1,41 +1,27 @@ diff --git a/frontend/src/pages/RegisterCommunity.spec.js b/frontend/src/pages/RegisterCommunity.spec.js index deb1d999d..05f9014f4 100644 --- a/frontend/src/pages/RegisterCommunity.spec.js +++ b/frontend/src/pages/RegisterCommunity.spec.js @@ -1,21 +1,8 @@ import { mount, RouterLinkStub } from '@vue/test-utils' import RegisterCommunity from './RegisterCommunity' -import { toastErrorSpy } from '@test/testSetup' - const localVue = global.localVue -const apolloQueryMock = jest.fn().mockResolvedValue({ - data: { - getCommunityInfo: { - name: 'test12', - description: 'test community 12', - url: 'http://test12.test12/', - registerUrl: 'http://test12.test12/register', - }, - }, -}) - const mockStoreCommit = jest.fn() describe('RegisterCommunity', () => { @@ -26,9 +13,6 @@ describe('RegisterCommunity', () => { locale: 'en', }, $t: jest.fn((t) => t), - $apollo: { - query: apolloQueryMock, - }, $store: { commit: mockStoreCommit, state: { @@ -53,32 +37,10 @@ describe('RegisterCommunity', () => { wrapper = Wrapper() }) - it('commits the community info to the store', () => { - expect(mockStoreCommit).toBeCalledWith('community', { - name: 'test12', - description: 'test community 12', - url: 'http://test12.test12/', - registerUrl: 'http://test12.test12/register', - }) - }) - it('renders the Div Element "#register-community"', () => { expect(wrapper.find('div#register-community').exists()).toBeTruthy() }) - describe('communities gives back error', () => { - beforeEach(() => { - apolloQueryMock.mockRejectedValue({ - message: 'Failed to get communities', - }) - wrapper = Wrapper() - }) - - it('toasts an error message', () => { - expect(toastErrorSpy).toBeCalledWith('Failed to get communities') - }) - }) - describe('Community data already loaded', () => { beforeEach(() => { jest.clearAllMocks() @@ -100,10 +62,6 @@ describe('RegisterCommunity', () => { 'Die lokale Entwicklungsumgebung von Gradido.', ) }) - - it('does not call community data update', () => { - expect(apolloQueryMock).not.toBeCalled() - }) }) describe('buttons and links', () => { diff --git a/frontend/src/pages/RegisterCommunity.vue b/frontend/src/pages/RegisterCommunity.vue index 736a4282f..1acc0827e 100644 --- a/frontend/src/pages/RegisterCommunity.vue +++ b/frontend/src/pages/RegisterCommunity.vue @@ -6,12 +6,12 @@
-

{{ $store.state.community.name }}

+

{{ CONFIG.COMMUNITY_NAME }}

- {{ $store.state.community.description }} + {{ CONFIG.COMMUNITY_DESCRIPTION }}

- {{ $store.state.community.url }} + {{ CONFIG.COMMUNITY_URL }}

@@ -49,10 +49,14 @@
diff --git a/frontend/src/pages/ResetPassword.spec.js b/frontend/src/pages/ResetPassword.spec.js index c43f71932..ac944e29a 100644 --- a/frontend/src/pages/ResetPassword.spec.js +++ b/frontend/src/pages/ResetPassword.spec.js @@ -79,36 +79,6 @@ describe('ResetPassword', () => { expect(wrapper.find('div.resetpwd-form').exists()).toBeTruthy() }) - describe('Register header', () => { - describe('from reset', () => { - beforeEach(() => { - mocks.$route.path.mock = 'reset-password' - wrapper = Wrapper() - }) - - it('has a welcome message', async () => { - expect(wrapper.find('div.header').text()).toContain('settings.password.reset') - expect(wrapper.find('div.header').text()).toContain( - 'settings.password.reset-password.text', - ) - }) - }) - - describe('from checkEmail', () => { - beforeEach(() => { - mocks.$route.path.mock = 'checkEmail' - wrapper = Wrapper() - }) - - it('has a welcome message', async () => { - expect(wrapper.find('div.header').text()).toContain('settings.password.set') - expect(wrapper.find('div.header').text()).toContain( - 'settings.password.set-password.text', - ) - }) - }) - }) - describe('links', () => { it('has a link "Back"', async () => { expect(wrapper.findAllComponents(RouterLinkStub).at(0).text()).toEqual('back') @@ -150,13 +120,18 @@ describe('ResetPassword', () => { describe('server response with error code > 10min', () => { beforeEach(async () => { - apolloMutationMock.mockRejectedValue({ message: '...Code is older than 10 minutes' }) + jest.clearAllMocks() + apolloMutationMock.mockRejectedValue({ + message: '...email was sent more than 23 hours and 10 minutes ago', + }) await wrapper.find('form').trigger('submit') await flushPromises() }) it('toasts an error message', () => { - expect(toastErrorSpy).toHaveBeenCalledWith('...Code is older than 10 minutes') + expect(toastErrorSpy).toHaveBeenCalledWith( + '...email was sent more than 23 hours and 10 minutes ago', + ) }) it('router pushes to /forgot-password/resetPassword', () => { diff --git a/frontend/src/pages/ResetPassword.vue b/frontend/src/pages/ResetPassword.vue index 7771be5f6..a8b21da65 100644 --- a/frontend/src/pages/ResetPassword.vue +++ b/frontend/src/pages/ResetPassword.vue @@ -1,51 +1,26 @@ +