├── .editorconfig ├── .github └── workflows │ ├── deploy.yml │ └── test.yml ├── .gitignore ├── .nycrc.json ├── LICENSE ├── LICENSES └── Apache-2.0.txt ├── README.md ├── REUSE.toml ├── eslint.config.mjs ├── package.json ├── step-by-step.md ├── tsconfig.json ├── ui5-coverage.yaml ├── ui5-dist.yaml ├── ui5.yaml └── webapp ├── Component.ts ├── controller └── App.controller.ts ├── index.html ├── manifest.json ├── test ├── Test.qunit.html ├── integration │ ├── HelloJourney.ts │ ├── opaTests.qunit.ts │ └── pages │ │ └── AppPage.ts ├── testsuite.qunit.html ├── testsuite.qunit.ts └── unit │ ├── controller │ └── App.qunit.ts │ └── unitTests.qunit.ts └── view └── App.view.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | # We recommend you to keep these unchanged 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | # Change these settings to your own preference 15 | indent_style = tab 16 | 17 | [*.{yaml,yml,md,json,xml,properties}] 18 | indent_size = 2 19 | indent_style = space 20 | 21 | [*.md] 22 | trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy TypeScript Application to Pages 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | concurrency: 14 | group: "pages" 15 | cancel-in-progress: false 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | - name: Setup Node 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: 22 27 | - name: Setup Pages 28 | uses: actions/configure-pages@v5 29 | - name: Install dependencies 30 | run: npm i 31 | - name: Build 32 | run: npm run build:opt 33 | - name: Upload artifact 34 | uses: actions/upload-pages-artifact@v3 35 | with: 36 | path: ./dist 37 | 38 | deploy: 39 | environment: 40 | name: github-pages 41 | url: ${{ steps.deployment.outputs.page_url }} 42 | runs-on: ubuntu-latest 43 | needs: build 44 | steps: 45 | - name: Deploy to GitHub Pages 46 | id: deployment 47 | uses: actions/deploy-pages@v4 48 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test and Checks 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | # No permissions are required for this workflow 10 | permissions: {} 11 | 12 | jobs: 13 | test: 14 | name: General checks, tests and build 15 | runs-on: ubuntu-latest 16 | steps: 17 | 18 | - uses: actions/checkout@v4 19 | 20 | - name: Setup Node 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: 22 24 | 25 | - name: Install dependencies 26 | run: npm i 27 | 28 | - name: Perform checks and tests 29 | run: npm test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # do not add dependency lock files to this repo because it should remain independent from specific dependency managers 9 | package-lock.json 10 | yarn.lock 11 | pnpm-lock.yaml 12 | 13 | # Mac system files 14 | .DS_Store 15 | 16 | # VSCode files 17 | .vscode/diff 18 | .vscode/settings.json 19 | 20 | # do not add typescript buildinfo 21 | tsconfig.tsbuildinfo 22 | 23 | # Dependency directories 24 | node_modules/ 25 | 26 | # build/test results 27 | dist 28 | coverage 29 | report 30 | .nyc_output 31 | -------------------------------------------------------------------------------- /.nycrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "all": true, 3 | "sourceMap": false, 4 | "exclude": [ 5 | "**/test/**/*.ts" 6 | ] 7 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /LICENSES/Apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Small TypeScript UI5 Example App 2 | 3 | [![REUSE status](https://api.reuse.software/badge/github.com/SAP-samples/ui5-typescript-helloworld)](https://api.reuse.software/info/github.com/SAP-samples/ui5-typescript-helloworld) 4 | 5 | **The main resource in this repository is [the detailed step-by-step guide](step-by-step.md), which explains how the TypeScript setup is created from scratch and how all the bits and pieces fit together.** 6 | 7 | ## Description 8 | 9 | This app demonstrates the TypeScript setup for developing UI5 applications, including testing. The focus is on *understanding the setup* [step by step](step-by-step.md). 10 | 11 | If you are *not* here for understanding the setup, then: 12 | - The *fastest* way to get started with an app is using the yeoman-based [Easy-UI5 template "ts-app"](https://github.com/ui5-community/generator-ui5-ts-app). 13 | - In the *[custom-controls](https://github.com/SAP-samples/ui5-typescript-helloworld/tree/custom-controls)* branch, there is an example how custom controls can be developed in TypeScript within applications. 14 | - In the [ui5-2.0](https://github.com/SAP-samples/ui5-typescript-helloworld/tree/ui5-2.0) branch, this repository demonstrates how an application can be tested against the type definitions (and runtime) of the upcoming *UI5 2.x version*. 15 | - A more complete *real-life-like* application is in the [TypeScript branch of the "UI5 CAP Event App"](https://github.com/SAP-samples/ui5-cap-event-app/tree/typescript). It comes with an [explanation](https://github.com/SAP-samples/ui5-cap-event-app/blob/typescript/docs/typescript.md) of what UI5 TypeScript code usually looks like and what to consider. 16 | - All *general information* about UI5 application development in TypeScript and links to tutorials, videos etc. can be found at https://sap.github.io/ui5-typescript. 17 | 18 | 19 | ## Overview of TypeScript-related Entities 20 | 21 | - The UI5 type definitions (`*.d.ts` files) are loaded as dev dependency from [npm](https://www.npmjs.com/package/@types/openui5). 22 | - The file [tsconfig.json](tsconfig.json) contains the configuration for the TypeScript compilation, including a reference to the UI5 `*.d.ts` files. 23 | - The TypeScript-to-JavaScript transpilation is done by [`ui5-tooling-transpile`](https://www.npmjs.com/package/ui5-tooling-transpile), which acts as both a build plugin (build results are stored in the `dist` folder) and middleware (the UI5 dev server transpiles the TypeScript files from `webapp` before sending it to the browser). Under the hood it uses the [Babel](https://babeljs.io/) transpiler. 24 | - In addition to the TypeScript compilation, there is also a conversion from the ES6 module and class syntax used in the source files to the classic UI5 module loading and class definition syntax (`sap.ui.require(...)`/`sap.ui.define(...)` and `SuperClass.extend(...)`). This conversion is also done by `ui5-tooling-transpile`, using the [babel-plugin-transform-modules-ui5](https://github.com/ui5-community/babel-plugin-transform-modules-ui5) project from the UI5 Community (initially developed by Ryan Murphy). 25 | 26 | ## A Note on Testing 27 | 28 | The main differences to tests written in JavaScript relate to a) writing OPA tests and b) configuring code coverage instrumentation: 29 | 30 | The *structural* code differences to writing tests in *JavaScript* are: 31 | 1. The **OPA Pages are simply classes extending `Opa5`, having the actions and assertions as class methods**. 32 | 2. The **OPA Journeys do not use the `Given`/`When`/`Then` objects, but call actions and assertions directly on the Page objects**. 33 | 34 | This simplifies the test code a lot and at the same time avoids type complications in TypeScript caused by OPA APIs not fitting a typed language. 35 | All other testing code is converted from JavaScript in the same way as regular application code is (i.e. using real ECMAScript classes and modules). 36 | 37 | The *setup* difference is in the configuration to instrument code for coverage reporting: the `istanbul` library needs to be added to the Babel config of `ui5-tooling-transpile-middleware` in `ui5.yaml`. 38 | 39 | Details can be found in the later sections of [step-by-step.md](step-by-step.md). 40 | 41 | > Note: the test setup is now using the [`ui5-test-runner`](https://github.com/ArnaudBuchholz/ui5-test-runner) instead of deprecated `karma`. 42 | 43 | 44 | ## Requirements 45 | 46 | Either [npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/), or [pnpm](https://pnpm.io/) for dependency management. 47 | 48 | ## Setup 49 | 50 | 1. Clone the project: 51 | 52 | ```sh 53 | git clone https://github.com/SAP-samples/ui5-typescript-helloworld.git 54 | cd ui5-typescript-helloworld 55 | ``` 56 | 57 | (or download from https://github.com/SAP-samples/ui5-typescript-helloworld/archive/main.zip) 58 | 59 | 2. Use npm (or any other package manager) to install the dependencies: 60 | 61 | ```sh 62 | npm install 63 | ``` 64 | 65 | ## Run the App 66 | 67 | Execute the following command to run the app locally for development in watch mode (the browser reloads the app automatically when there are changes in the source code): 68 | 69 | ```sh 70 | npm start 71 | ``` 72 | 73 | As shown in the terminal after executing this command, the app is then running on http://localhost:8080/index.html. A browser window with this URL should automatically open. 74 | 75 | ## Debug the App 76 | 77 | In the browser, you can directly debug the original TypeScript code, which is supplied via sourcemaps (need to be enabled in the browser's developer console if it does not work straight away). If the browser doesn't automatically jump to the TypeScript code when setting breakpoints, use e.g. `Ctrl`/`Cmd` + `P` in Chrome to open the `*.ts` file you want to debug. 78 | 79 | ## Run the Tests 80 | 81 | The tests can be executed either manually or in an automated way using [`ui5-test-runner`](https://github.com/ArnaudBuchholz/ui5-test-runner): 82 | 83 | 1. *Manual execution*: use `npm start` and then execute the tests by opening the [testsuite](http://localhost:8080/test/testsuite.qunit.html) at [http://localhost:8080/test/testsuite.qunit.html](http://localhost:8080/test/testsuite.qunit.html) in your browser. You can also directly launch the [QUnit tests](http://localhost:8080/test/Test.qunit.html?testsuite=test-resources/ui5/typescript/helloworld/testsuite.qunit&test=unit/unitTests) or the [Integration tests](http://localhost:8080/test/Test.qunit.html?testsuite=test-resources/ui5/typescript/helloworld/testsuite.qunit&test=integration/opaTests) individually. 84 | 85 | 2. *Headless testing*: launch test-runner either *without* coverage reporting using `npm run test-runner` or *with* coverage using `npm run test-runner-coverage`. 86 | While the tests are running, their status can be monitored at http://localhost:8081/_/progress.html 87 | 88 | > Note: when the application to test is passed using the `--url` argument (as we do it in this sample), then there is [no "watch" mode of the ui5-test-runner so far](https://github.com/ArnaudBuchholz/ui5-test-runner/issues/119), which automatically re-runs the tests when a resource changes. 89 | 90 | ## Build the App 91 | 92 | ### Unoptimized (but quick) 93 | 94 | Execute the following command to build the project and get an app that can be deployed: 95 | 96 | ```sh 97 | npm run build 98 | ``` 99 | 100 | The result is placed into the `dist` folder. To start the generated package, just run 101 | 102 | ```sh 103 | npm run start:dist 104 | ``` 105 | 106 | Note that `index.html` still loads the UI5 framework from the relative URL `resources/...`, which does not physically exist, but is only provided dynamically by the UI5 tooling. So for an actual deployment you should change this URL to either [the CDN](https://sdk.openui5.org/#/topic/2d3eb2f322ea4a82983c1c62a33ec4ae) or your local deployment of UI5. 107 | 108 | ### Optimized 109 | 110 | For an optimized self-contained build (takes longer because the UI5 resources are built, too), do: 111 | 112 | ```sh 113 | npm run build:opt 114 | ``` 115 | 116 | To start the generated package, again just run 117 | 118 | ```sh 119 | npm run start:dist 120 | ``` 121 | 122 | In this case, all UI5 framework resources are also available within the `dist` folder, so the folder can be deployed as-is to any static web server, without changing the bootstrap URL.
123 | With the self-contained build, the bootstrap URL in `index.html` has already been modified to load the newly created `sap-ui-custom.js` for bootstrapping, which contains all app resources as well as all needed UI5 JavaScript resources. Most UI5 resources inside the `dist` folder are for this reason actually **not** needed to run the app. Only the non-JS-files, like translation texts and CSS files, are used and must also be deployed. (Only when for some reason JS files are missing from the optimized self-contained bundle, they are also loaded separately.) 124 | 125 | ## Check the Code 126 | 127 | Do the following to run a TypeScript check: 128 | 129 | ```sh 130 | npm run ts-typecheck 131 | ``` 132 | 133 | This checks the application code for any type errors (but will also complain in case of fundamental syntax issues which break the parsing).
134 | 135 | To lint the TypeScript code, do: 136 | 137 | ```sh 138 | npm run lint 139 | ``` 140 | 141 | ## Limitations 142 | 143 | - At this time, the used eslint rules are not verified to be optimal or to be in sync with UI5 recommendations. 144 | - In the future there might be further improvements to how tests are written and configured. 145 | 146 | ## Known Issues 147 | 148 | None. 149 | 150 | ## How to Obtain Support 151 | 152 | The sample code is provided **as-is**. No support is provided. 153 | 154 | [Create an issue](https://github.com/SAP-samples/ui5-typescript-helloworld/issues) in this repository if you find a bug in the sample app code or documentation. 155 | 156 | For issues in the UI5 type definitions which are caused by the dts-generator please open [issues in the dts-generator's repository](https://github.com/SAP/ui5-typescript/issues).
157 | 158 | Issues in the UI5 type definitions which are also present in the [API documentation](https://ui5.sap.com/#/api) originate from the JSDoc comments in the original OpenUI5/SAPUI5 code, so please directly open an [OpenUI5](https://github.com/SAP/openui5/issues)/SAPUI5 ticket for those. 159 | 160 | Questions can be [asked in SAP Community](https://answers.sap.com/questions/ask.html). 161 | 162 | ## License 163 | 164 | Copyright (c) 2023-2025 SAP SE or an SAP affiliate company. All rights reserved. 165 | This project is licensed under the Apache Software License, version 2.0 except as noted otherwise in the [LICENSE](LICENSE) file. 166 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | SPDX-PackageName = "ui5-typescript-helloworld" 3 | SPDX-PackageSupplier = "SAP OpenUI5 " 4 | SPDX-PackageDownloadLocation = "https://github.com/SAP-samples/ui5-typescript-helloworld" 5 | SPDX-PackageComment = "The code in this project may include calls to APIs (“API Calls”) of\n SAP or third-party products or services developed outside of this project\n (“External Products”).\n “APIs” means application programming interfaces, as well as their respective\n specifications and implementing code that allows software to communicate with\n other software.\n API Calls to External Products are not licensed under the open source license\n that governs this project. The use of such API Calls and related External\n Products are subject to applicable additional agreements with the relevant\n provider of the External Products. In no event shall the open source license\n that governs this project grant any rights in or to any External Products,or\n alter, expand or supersede any terms of the applicable additional agreements.\n If you have a valid license agreement with SAP for the use of a particular SAP\n External Product, then you may make use of any API Calls included in this\n project’s code for that SAP External Product, subject to the terms of such\n license agreement. If you do not have a valid license agreement for the use of\n a particular SAP External Product, then you may only make use of any API Calls\n in this project for that SAP External Product for your internal, non-productive\n and non-commercial test and evaluation of such API Calls. Nothing herein grants\n you any rights to use or access any SAP External Product, or provide any third\n parties the right to use of access any SAP External Product, through API Calls." 6 | 7 | [[annotations]] 8 | path = "**" 9 | precedence = "aggregate" 10 | SPDX-FileCopyrightText = "2021 SAP SE or an SAP affiliate company and ui5-typescript-helloworld contributors" 11 | SPDX-License-Identifier = "Apache-2.0" 12 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import eslint from "@eslint/js"; 2 | import globals from "globals"; 3 | import tseslint from "typescript-eslint"; 4 | 5 | export default tseslint.config( 6 | eslint.configs.recommended, 7 | ...tseslint.configs.recommended, 8 | ...tseslint.configs.recommendedTypeChecked, 9 | { 10 | languageOptions: { 11 | globals: { 12 | ...globals.browser, 13 | sap: "readonly" 14 | }, 15 | ecmaVersion: 2023, 16 | parserOptions: { 17 | project: true, 18 | tsconfigRootDir: import.meta.dirname 19 | } 20 | } 21 | }, 22 | { 23 | ignores: ["eslint.config.mjs"] 24 | } 25 | ); 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui5-typescript-helloworld", 3 | "version": "1.0.0", 4 | "description": "Showcase of a TypeScript setup for developing UI5 applications", 5 | "author": "SAP SE", 6 | "license": "Apache-2.0", 7 | "scripts": { 8 | "build": "ui5 build --clean-dest", 9 | "build:opt": "ui5 build self-contained --clean-dest --all", 10 | "start": "ui5 serve --port 8080 -o index.html", 11 | "start-coverage": "ui5 serve --port 8080 --config ui5-coverage.yaml", 12 | "start:dist": "ui5 serve --port 8080 -o index.html --config ui5-dist.yaml", 13 | "ts-typecheck": "tsc --noEmit", 14 | "lint": "eslint webapp", 15 | "test-runner": "ui5-test-runner --url http://localhost:8080/test/testsuite.qunit.html", 16 | "test-runner-coverage": "ui5-test-runner --url http://localhost:8080/test/testsuite.qunit.html --coverage -ccb 60 -ccf 100 -ccl 80 -ccs 80", 17 | "test-ui5": "ui5-test-runner --start start-coverage --url http://localhost:8080/test/testsuite.qunit.html --coverage -ccb 60 -ccf 100 -ccl 80 -ccs 80", 18 | "test": "npm run lint && npm run test-ui5", 19 | "ui5lint": "ui5lint" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/SAP-samples/ui5-typescript-helloworld.git" 24 | }, 25 | "devDependencies": { 26 | "@types/openui5": "1.131.0", 27 | "@ui5/cli": "^4", 28 | "@ui5/linter": "^1.5.0", 29 | "babel-plugin-istanbul": "^7.0.0", 30 | "eslint": "^9.19.0", 31 | "globals": "^15.14.0", 32 | "typescript": "^5.7.3", 33 | "typescript-eslint": "^8.18.1", 34 | "ui5-middleware-livereload": "^3", 35 | "ui5-middleware-simpleproxy": "^3", 36 | "ui5-test-runner": "^5.4.3", 37 | "ui5-tooling-transpile": "^3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /step-by-step.md: -------------------------------------------------------------------------------- 1 | # A Detailed Guide to Creating a UI5 TypeScript App From Scratch in Five to Fifteen Steps 2 | 3 | This guide explains step-by-step and command-by-command how you achieve a complete UI5 TypeScript setup from scratch. 4 | 5 | While you can get started faster by using the [Easy-UI5 "ts-app" template](https://github.com/ui5-community/generator-ui5-ts-app) or just copying and modifying the entire Hello World app, this step-by-step guide will help you *understand* every bit and piece of the setup and how the pieces fit together. 6 | 7 | It consists of 15 steps, but in fact only steps 2, 3, 4 and 6 are really related to the basic TypeScript setup and steps 11, 12 and 14 to testing in TypeScript. The remaining steps are about adding the UI5 tools to the project and about wrapping everything up nicely, so those steps apply more or less to any UI5 application project regardless of the used language. 8 | 9 | ## Table of Contents 10 | 11 | 1. [Initialize an Empty Project](#1-initialize-an-empty-project) 12 | 1. [Create an Initial TypeScript Resource](#2-create-an-initial-typescript-resource) 13 | 1. [Set Up the TypeScript Compilation](#3-set-up-the-typescript-compilation) 14 | 1. [Set Up a Lint Check](#4-set-up-a-lint-check) 15 | 1. [Set Up the UI5 CLI Tooling](#5-set-up-the-ui5-cli-tooling) 16 | 1. [Using a UI5 Tooling Extension for Code Transformation](#6-using-a-ui5-tooling-extension-for-code-transformation) 17 | 1. [Complete the App Code](#7-complete-the-app-code) 18 | 1. [Set Up Live Reload for Easier Development (Optional)](#8-set-up-live-reload-for-easier-development-optional) 19 | 1. [Add an Optimized UI5 Build (Optional)](#9-add-an-optimized-ui5-build-optional) 20 | 1. [Add Scripts for Building/Running/Checking to `package.json`](#10-add-scripts-for-buildingrunningchecking-to-packagejson) 21 | 1. [The Test Code](#11-the-test-code) 22 | 1. [Enable TypeScript support for the Test Code](#12-enable-typescript-support-for-the-test-code) 23 | 1. [Automated QUnit/OPA Testing using `ui5-test-runner`](#13-automated-qunitopa-testing-using-ui5-test-runner) 24 | 1. [Enable Code Coverage Reporting](#14-enable-code-coverage-reporting) 25 | 1. [Add Scripts for Testing to `package.json`](#15-add-scripts-for-testing-to-packagejson) 26 | 27 | ## 1. Initialize an Empty Project 28 | 29 | Type the following on the command line to create the project directory and go inside: 30 | 31 | ```sh 32 | mkdir ui5-typescript-from-scratch 33 | cd ui5-typescript-from-scratch 34 | ``` 35 | 36 | Initialize an [npm](https://www.npmjs.com)-based project - this creates the `package.json` file where also the dependencies will be added: 37 | 38 | ```sh 39 | npm init -y 40 | ``` 41 | 42 | The `-y` parameter uses default settings for all options without asking - you can adapt them in package.json if needed. 43 | 44 | ## 2. Create an Initial TypeScript Resource 45 | 46 | Inside your project, create a `webapp` folder: 47 | 48 | ```sh 49 | mkdir webapp 50 | ``` 51 | 52 | Inside this folder (`cd webapp`), create a file with TypeScript code. In order to test the use of UI5 types and the code transformation, name it `Component.ts` (note: the file ending is `.ts`, not `.js`!) and add the following code inside. Of course, this is so far just a dummy component and not yet a complete app: 53 | 54 | ```ts 55 | import UIComponent from "sap/ui/core/UIComponent"; 56 | 57 | /** 58 | * @namespace ui5.typescript.helloworld 59 | */ 60 | export default class Component extends UIComponent { 61 | 62 | public multiply(x : number, y : number) : number { 63 | return x * y; 64 | } 65 | } 66 | ``` 67 | 68 | Note that the scope of this tutorial is the TypeScript setup of a project, not the application code itself. Hence the content of the *.ts files will not be explained further. It is plain regular UI5 application code, with two exceptions: 69 | 70 | 1. It is **TypeScript** code, which means while mostly being plain JavaScript, it also contains type declarations for variables, parameters and function return values, as seen in the definition of the "multiply" method. In a moment, you will be able to see how these will be stripped away by the TypeScript compilation. 71 | 72 | 1. It is **modern JavaScript (/TypeScript)** code with [modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) and [classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes), which will be transformed to classic UI5 code (with `sap.ui.require(...)` and `BaseClass.extend(...)`) in a further step of the build process. This is not really related to TypeScript, but it's the way how we recommend to write modern UI5 apps when a build step is anyway needed. 73 | 74 | ## 3. Set Up the TypeScript Compilation 75 | 76 | Now, let's get the TypeScript compiler and the UI5 type definitions as dev dependencies: 77 | 78 | ```sh 79 | npm install --save-dev typescript @types/openui5 80 | ``` 81 | 82 | When you are developing a SAPUI5 application (i.e. also using control libraries which are not available in OpenUI5), use the `@sapui5/types` types instead of the `@types/openui5` ones. 83 | 84 | > **Remark:** There are also `@openui5/types` available - how do they differ from the `@types/openui5` ones?
85 | The content is basically the same, one difference is in versioning: while the types in the `@openui5` namespace are exactly in sync with the respective OpenUI5 patch release, the ones in the `@types` namespace follow the [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) versioning and are only released *once* per minor release of OpenUI5 ([more details here](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/openui5#versioning)), not for every patch. In practice, it shouldn't make a noticeable difference which you use, but note that in the `@types` namespace there is usually only the `*.*.0` patch release available.
86 | > The SAPUI5 types are not available in the `@types` namespace. 87 | 88 | To trigger the first TypeScript transpilation in this project, now execute 89 | 90 | ```sh 91 | npx tsc webapp/Component.ts 92 | ``` 93 | 94 | (The `npx` command runs the subsequently written npm module from within the `node_modules` folder, so it does not need to be installed globally.) 95 | 96 | The TypeScript compiler tries to compile the component file, but it complains because it finds some unknown JavaScript classes (`Iterator`, `Generator`) in the UI5 type definitions. This is because TypeScript by default works with a pretty old language level of JavaScript (ES3) and we need to tell it to accept a newer language level (ES2022). 97 | 98 | Actually, there is some Component.**js** file created inside the `webapp` folder, but the content is really weird and bloated because the transpiler tried to re-build some newer JavaScript features with extra code.
99 | **Please delete this file to avoid downstream issues!** 100 | 101 | To configure the transpiler properly, we need to add a `tsconfig.json` configuration file. Add a file with this name and the following content to the *root* of the project, *outside* the `webapp` folder: 102 | 103 | ```json 104 | { 105 | "compilerOptions": { 106 | "target": "es2023", 107 | "module": "es2022", 108 | "moduleResolution": "node", 109 | "skipLibCheck": true, 110 | "allowJs": true, 111 | "strict": true, 112 | "strictPropertyInitialization": false, 113 | "rootDir": "./webapp", 114 | "paths": { 115 | "ui5/typescript/helloworld/*": ["./webapp/*"] 116 | }, 117 | "composite": true 118 | }, 119 | "include": ["./webapp/**/*"] 120 | } 121 | ``` 122 | 123 | > **Note:** when you use the `@sapui5/types` or `@openui5/types` types instead, you need to add the following section to tsconfig.json: 124 | > 125 | > ```json 126 | > "types": [ 127 | > "@sapui5/types" 128 | > ], 129 | >``` 130 | > (or `@openui5/types`, respectively) 131 | > 132 | > Why? TypeScript automatically finds all type definition files in a dependency starting with `@types/...` (i.e. all `.d.ts` files in `node_modules/@types/...`). The SAPUI5 types are only available in a package starting with `@sapui5/...`, hence TypeScript must be explicitly pointed to these types. Note that this disables the automatic loading of other types from `node_modules/@types/...`, so any additional types also need to be added to this list. 133 | 134 | There are additional settings in this file, e.g. telling the compiler which files to compile (all matching `./webapp/**/*`) and how the modules should be resolved (`"moduleResolution": "node"`). And a couple of compiler options which are not so important right now. They determine how exactly the compiler should behave. The "paths" section informs TypeScript about the mapping of namespaces used in the app. 135 | 136 | Now you can do the following **in the root directory** of your project. TypeScript will pick up all the settings and as result you will find a compiled JavaScript file in the automatically created `dist` folder: 137 | 138 | ```sh 139 | npx tsc --outDir ./dist 140 | ``` 141 | 142 | Yay! Your first successfully compiled TypeScript code! When inspecting the `dist/Component.js` file, you will see that all TypeScript specifics are gone. Specifically, the type information is stripped from the line defining the `multiply` method: 143 | 144 | ```js 145 | multiply(x, y) { 146 | return x * y; 147 | } 148 | ``` 149 | 150 | Again, remember to delete the previously generated `webapp/Component.js` file if you tried transpiling before creating the `tsconfig.json` file. 151 | 152 | In case there is a type error, the compilation will let you know. E.g. when you change the return type of the `multiply` function to `string` in `Component.ts`, then there will be an error: 153 | 154 | ```ts 155 | public multiply(x : number, y : number) : string { 156 | ``` 157 | 158 | will result in: 159 | 160 | ```sh 161 | webapp/Component.ts:9:3 - error TS2322: Type 'number' is not assignable to type 'string'. 162 | 9 return x * y; 163 | ``` 164 | 165 | You can also invoke this check without creating the compiled output files when you add the `-noEmit` flag to the compiler call: 166 | 167 | ```sh 168 | npx tsc -noEmit 169 | ``` 170 | 171 | Note that the transpilation result still contains ES modules and classes, not the UI5-specific module loading and class definition. This will be taken care of later. 172 | 173 | ## 4. Set Up a Lint Check 174 | 175 | While not required, it makes sense to have your code checked with a linter. The popular [ESLint](https://eslint.org/) tool also understands TypeScript when some plug-ins are added. It is the recommended tool to lint TypeScript code. So let's add ESLint and these plug-ins as dev dependencies! 176 | 177 | ```sh 178 | npm install --save-dev eslint typescript-eslint 179 | ``` 180 | 181 | ESLint needs to be told which plug-ins to use and which JavaScript language level the code should have, so create a `eslint.config.mjs` file in the project root with these settings: 182 | 183 | ```js 184 | import eslint from "@eslint/js"; 185 | import globals from "globals"; 186 | import tseslint from "typescript-eslint"; 187 | 188 | export default tseslint.config( 189 | eslint.configs.recommended, 190 | ...tseslint.configs.recommended, 191 | ...tseslint.configs.recommendedTypeChecked, 192 | { 193 | languageOptions: { 194 | globals: { 195 | ...globals.browser, 196 | sap: "readonly" 197 | }, 198 | ecmaVersion: 2023, 199 | parserOptions: { 200 | project: true, 201 | tsconfigRootDir: import.meta.dirname 202 | } 203 | } 204 | }, 205 | { 206 | ignores: ["eslint.config.mjs"] 207 | } 208 | ); 209 | ``` 210 | 211 | After adding the configuration, you can now execute ESLint with the following command: 212 | 213 | ```sh 214 | npx eslint webapp 215 | ``` 216 | 217 | Your TypeScript code can now be checked for syntax and style problems. 218 | 219 | There should not be any output (this means: no error), but if you introduce a syntax error to Component.ts, the check will complain with an error and if e.g. the return type of the `multiply` function is missing, it will show a warning. 220 | 221 | If you get an error straight away which says something about the file "webapp\Component.**js**", then this might be a left-over compilation result from step 3 above. Delete this file and re-try. 222 | 223 | In the configuration file all kinds of details regarding the ESLint rules can be configured. But for this guide (and because the UI5 team does not currently give a set of actual recommendations) let's stick with the recommended TypeScript defaults, which the above configuration extends. 224 | 225 | ## 5. Set Up the UI5 CLI Tooling 226 | 227 | To benefit from an improved development experience and the possibility to build and optimize your application before productive use, it is recommended to use the [UI5 CLI Tooling](https://sap.github.io/ui5-tooling/pages/CLI/). You can benefit from an ecosystem of tooling extensions (e.g., livereload, proxies, ...) to simplify your development. It also nicely integrates the TypeScript transpilation (see the next chapter), so you do not have to invoke the transpiler directly. 228 | 229 | You can install the UI5 CLI Tooling with the following command: 230 | 231 | ```sh 232 | npm install --save-dev @ui5/cli 233 | ``` 234 | 235 | Afterwards, you can use the UI5 Tooling to initialize the project and create the UI5 Tooling specific configuration file - the `ui5.yaml`. To do so, just execute the following command: 236 | 237 | ```sh 238 | npx ui5 init 239 | ``` 240 | 241 | By default the configuration file includes just the following basic metadata (the `specVersion` of the `ui5.yaml` file, the project `name` and the project `type`): 242 | 243 | ```yaml 244 | specVersion: "4.0" 245 | metadata: 246 | name: ui5-typescript-from-scratch 247 | type: application 248 | ``` 249 | 250 | For running UI5 applications with the UI5 Tooling we also need some additional `framework` information (like framework `name` and `version`) and the required libraries and theme libraries. Now put the following content in your `ui5.yaml` (please change the generated `metadata > name` to `ui5.typescript.helloworld` and use a current OpenUI5 version): 251 | 252 | ```yaml 253 | specVersion: "4.0" 254 | metadata: 255 | name: ui5.typescript.helloworld 256 | type: application 257 | framework: 258 | name: OpenUI5 259 | version: "1.131.1" 260 | libraries: 261 | - name: sap.m 262 | - name: sap.ui.core 263 | - name: sap.ui.unified 264 | - name: themelib_sap_horizon 265 | ``` 266 | 267 | As seen in the initial content of the `ui5.yaml` after running `ui5 init` only the first four lines are strictly required to use the UI5 Tooling, the rest is still useful: the `framework` section downloads the UI5 framework along with needed libraries and provides it at the virtual path `/resources` when `ui5 serve` is called. This path is from where the index.html file loads UI5. More information about the UI5 Tooling can be found here: [https://sap.github.io/ui5-tooling](https://sap.github.io/ui5-tooling). 268 | 269 | The UI5 Tooling commands require at least a [`manifest.json`](https://ui5.sap.com/#/topic/be0cf40f61184b358b5faedaec98b2da.html) file in the `webapp` folder. A very simple and lightweight `manifest.json` providing the `id`, `type`, `title` and `version` of your application: 270 | 271 | ```json 272 | { 273 | "_version": "1.68.0", 274 | "sap.app": { 275 | "id": "ui5.typescript.helloworld", 276 | "type": "application", 277 | "title": "UI5 TypeScript Hello World", 278 | "applicationVersion": { 279 | "version": "1.0.0" 280 | } 281 | } 282 | } 283 | ``` 284 | 285 | What you can do now: create a simple `test.html` file in the `webapp` folder and run it from there using the UI5 Tooling: 286 | 287 | ```html 288 | 289 | 290 | 291 | 292 | UI5 made easy! 293 | 301 | 313 | 314 | 315 |
316 | 317 | 318 | ``` 319 | 320 | After creating this file you can start the `test.html` running inside the development server of the UI5 CLI Tooling (but note that this is just to test the UI5 tooling setup and not related to TypeScript!): 321 | 322 | ```sh 323 | npx ui5 serve -o test.html 324 | ``` 325 | 326 | That's it! A web server with the test page is started and it is automatically opened inside your default browser! 327 | 328 | ## 6. Using a UI5 Tooling Extension for Code Transformation 329 | 330 | The code transpiled by `tsc` still uses ES modules and classes, which would still need to be transformed to classic UI5 code to work properly. To apply this transformation, we will use the [`ui5-tooling-transpile`](https://www.npmjs.com/package/ui5-tooling-transpile) tooling extension instead of calling `tsc`. This tooling extension uses the [Babel](https://babeljs.io/) transpiler behind the scenes and configures Babel to 1. transpile the code to JavaScript and 2. transform the code to proper UI5 code (especially UI5-style imports and classes). The TypeScript compiler will no longer be called for the transpilation from now on, only for the type checking. 331 | 332 | Add the dependency to `ui5-tooling-transpile` to your project first: 333 | 334 | ```sh 335 | npm install --save-dev ui5-tooling-transpile 336 | ``` 337 | 338 | Then add the following configuration at the end of your `ui5.yaml`. Make sure the indentation levels are correct, with "builder" and "server" on the same level as "framework": 339 | 340 | ```yaml 341 | builder: 342 | customTasks: 343 | - name: ui5-tooling-transpile-task 344 | afterTask: replaceVersion 345 | server: 346 | customMiddleware: 347 | - name: ui5-tooling-transpile-middleware 348 | afterMiddleware: compression 349 | ``` 350 | 351 | The `ui5-tooling-transpile` tooling extension is by default configuration free. It derives the programming language being TypeScript or JavaScript by the existence of the `tsconfig.json` file in the project root. 352 | 353 | > `ui5-tooling-transpile` uses a default Babel configuration. As an *optional* step - when you need to customize the code transformation of Babel - you can create a [Babel configuration](https://babeljs.io/docs/configuration) file (i.e. `.babelrc.json`) in the root of the project, with the following content: 354 | > 355 | > ```json 356 | > { 357 | > "ignore": [ 358 | > "**/*.d.ts" 359 | > ], 360 | > "presets": [ 361 | > ["@babel/preset-env", { // applied 3rd 362 | > "targets": "defaults" 363 | > }], 364 | > "transform-ui5", // applied 2nd 365 | > "@babel/preset-typescript" // applied 1st 366 | > ], 367 | > "sourceMaps": true 368 | > } 369 | > ``` 370 | > 371 | > The default configuration used internally by the tooling extension is similar to this. If you decide for a custom Babel configuration we recommend for the `@babel/preset-env` to use the targets [`defaults`](https://browsersl.ist/#q=defaults). 372 | 373 | Now you are ready to transform your TypeScript code into proper UI5 JavaScript code. Just execute the build with the UI5 Tooling with the following command: 374 | 375 | ```sh 376 | npx ui5 build --clean-dest 377 | ``` 378 | 379 | The result is a `dist` folder with (among others) a `Component-dbg.js` file which is converted from TypeScript AND also converted to classic UI5 code! 380 | 381 | Open this file to see: the module imports are replaced with the classic `sap.ui.define(...)` and the `Component` class is now defined by calling `UIComponent.extend(...)`: 382 | 383 | ```js 384 | ... 385 | sap.ui.define(["sap/ui/core/UIComponent"], function (UIComponent) { 386 | ... 387 | const Component = UIComponent.extend("ui5.typescript.helloworld.Component", { 388 | ``` 389 | 390 | This means the complete TypeScript build setup is now done! 391 | 392 | ## 7. Complete the App Code 393 | 394 | To extend the now-complete TypeScript setup into a complete app development setup in the rest of this tutorial, we need a complete and runnable app. 395 | 396 | Please copy the entire content of this repository's [webapp](webapp) directory (you can [download the entire repository from here as zip file](../../archive/refs/heads/main.zip)) into your local project's `webapp` directory. Make sure to also replace the dummy `Component.ts` file we have used so far and delete the `webapp/test` folder for the time being! The tests will be explained further down. 397 | 398 | Alternatively, you could of course also develop your own UI5 app in TypeScript within the `webapp` folder. 399 | 400 | ## 8. Set Up Live Reload for Easier Development (Optional) 401 | 402 | To make the browser reload the app automatically when you modify the sources, you only need to add the [`livereload`](https://www.npmjs.com/package/ui5-middleware-livereload) middleware. This middleware checks for any changes in the `webapp` folder and causes the browser to reload when such a change is detected. 403 | 404 | The `livereload` middleware is added as follows. First, add it as another dependency: 405 | 406 | ```sh 407 | npm install --save-dev ui5-middleware-livereload 408 | ``` 409 | 410 | Second, register the middleware at the end of the `server > customMiddleware` section in your `ui5.yaml` file. The `livreload` middleware is configuration-free, you just need to register it: 411 | 412 | ```yaml 413 | - name: ui5-middleware-livereload 414 | afterMiddleware: compression 415 | ``` 416 | 417 | Make sure to get the indentation right (like the ui5-tooling-transpile-middleware lines) because it is significant in yaml files. 418 | 419 | As result, you can now run the development server with the following command: 420 | 421 | ```sh 422 | npx ui5 serve -o index.html 423 | ``` 424 | 425 | The app in the automatically opened browser window reloads whenever a source file in `webapp` folder was changed and saved. 426 | 427 | ## 9. Add an Optimized UI5 Build (Optional) 428 | 429 | This step is again not at all related to TypeScript, but as the UI5 Tooling are already set up, you can as well use them for building an optimized **self-contained** app: it picks only the needed UI5 framework modules and controls and bundles them with all application resources into one single file. 430 | 431 | ```sh 432 | npx ui5 build self-contained --clean-dest --all 433 | ``` 434 | 435 | The `self-contained` command takes care of bundling all resources into one single file. This means: app code AND UI5 code! The `--all` switch takes care of building and copying all UI5 framework resources to the `dist` folder as well. The JavaScript resources should not be needed there (because all needed ones should already be in the bundle). But library CSS files etc. are not in the bundle, they are just put aside. 436 | 437 | This takes a while, maybe a minute or two, as it also needs to process all UI5 resources and creates not only the optimized bundle, but also all the other UI5 resources. But this is anyway a step which is usually only done once before releasing the app, not for every development roundtrip. 438 | 439 | Alternatively, if you don't need the fully optimized one-file bundle and want to load UI5 from CDN or elsewhere, you can also just do a regular build: 440 | 441 | ```sh 442 | npx ui5 build --clean-dest 443 | ``` 444 | 445 | Either way, the result in `dist` can either be put on a static web server or it can be served with the UI5 Tooling. To set up the latter, a slightly different UI5 tools configuration is needed because it now needs to serve from the `dist` folder. This configuration goes into a new file named `ui5-dist.yaml` in the project root: 446 | 447 | ```yaml 448 | specVersion: "4.0" 449 | metadata: 450 | name: ui5.typescript.helloworld 451 | type: application 452 | resources: 453 | configuration: 454 | paths: 455 | webapp: dist 456 | framework: 457 | name: OpenUI5 458 | version: "1.131.1" 459 | libraries: 460 | - name: sap.m 461 | - name: sap.ui.core 462 | - name: sap.ui.unified 463 | - name: themelib_sap_horizon 464 | ``` 465 | 466 | One difference from the other yaml file is the removal of the `builder` and `middleware` configuration sections, as they are not needed anymore in a productive build, and the other one is the addition of the `resources` section which tells the UI5 Tooling to serve from the `dist` directory. 467 | 468 | To run the build result from `dist`, `ui5 serve` can then be executed as before, but additionally using this new configuration file: 469 | 470 | ```sh 471 | npx ui5 serve -o index.html --config ui5-dist.yaml 472 | ``` 473 | 474 | ## 10. Add Scripts for Building/Running/Checking to `package.json` 475 | 476 | Now it's time to write down the various commands used so far as scripts in `package.json`, so you don't need to recall and type them every time they are used. 477 | 478 | While we are at it, we can add one more command: the `ui5-linter`, which complements eslint with code checks that make sure the code adheres to the best practices and is ready for UI5 2.x: 479 | 480 | ```sh 481 | npm install --save-dev @ui5/linter 482 | ``` 483 | 484 | It can be invoked using `npx ui5lint`. 485 | 486 | Change the `"scripts"` section in the `package.json` file to have the following content. All scripts have already been used and explained earlier, so there is nothing new here, it's just for convenience. 487 | 488 | ```json 489 | { 490 | "build": "ui5 build --clean-dest", 491 | "build:opt": "ui5 build self-contained --clean-dest --all", 492 | "start": "ui5 serve --port 8080 -o index.html", 493 | "start:dist": "ui5 serve --port 8080 -o index.html --config ui5-dist.yaml", 494 | "ts-typecheck": "tsc --noEmit", 495 | "lint": "eslint webapp", 496 | "ui5lint": "ui5lint" 497 | } 498 | ``` 499 | 500 | Calling `npx` is not needed here, as the commands are automatically found within the `node_modules` folder when run as npm script. 501 | 502 | An important topic has been skipped so far - let's take a look into testing! 503 | 504 | ## 11. The Test Code 505 | 506 | ### Overview 507 | 508 | The location for the QUnit tests for UI5 applications is the `webapp/test` folder. So far we have ignored its content. Now let's step through all parts of it. Below you see the overall structure. 509 | 510 | It is [exactly the same structure and files as for JavaScript projects](https://github.com/ui5-community/generator-ui5-app/tree/main/generators/app/templates/webapp/test), so this section does not go into all the details, but focuses on the TypeScript-specific parts. 511 | 512 | ```text 513 | webapp/test 514 | ├── integration // the OPA integration tests 515 | | ├── pages // - test pages 516 | | | └── AppPage.ts 517 | | ├── HelloJourney.ts // - journey 518 | | └── opaTests.qunit.ts // - the testsuite 519 | ├── unit // the unit tests 520 | | ├── controller // - user-defined QUnit tests folder 521 | | | └── App.qunit.ts // - QUnit test for the controller 522 | | └── unitTests.qunit.ts // - the testsuite 523 | ├── Test.qunit.html // the page inside which the tests are run 524 | ├── testsuite.qunit.ts // the general testsuite setup 525 | └── testsuite.qunit.html // the general testsuite html page 526 | ``` 527 | 528 | Starting from the last item of the above tree: 529 | - `testsuite.qunit.html` is the main entry point and makes the UI5 test starter build the overall testsuite according to the configuration in `testsuite.qunit.ts`. 530 | - `testsuite.qunit.ts` is the overall test configuration as defined and required by the [UI5 test starter](https://ui5.sap.com/sdk/#/topic/22f50c0f0b104bf3ba84620880793d3f). 531 | - `Test.qunit.html` is the generic test page in which the tests are run. It will be called with the test suite and test name in order to run a test. 532 | - `unitTests.qunit.ts` is where all QUnit test pages are registered by simply importing their modules: `import "unit/controller/App.qunit";` 533 | - `App.qunit.ts` is an example of a very basic unit test written in TypeScript. 534 | - `opaTests.qunit.ts` is like for the unit tests the central place where you register your journeys by importing them: `import "integration/HelloJourney";`. 535 | - `HelloJourney.ts` and `AppPage.ts` are the well-known journeys and pages for OPA tests, but they come with a twist in TypeScript, or rather: a simplification. See the main README.md file for details. 536 | 537 | 538 | ### Unit Tests (QUnit) 539 | 540 | Writing [Unit tests (QUnit)](./webapp/test/unit/) in TypeScript is straightforward, just as you know it from JavaScript. 541 | 542 | In the (very basic) tests in [`webapp/test/unit/controller/App.qunit.ts`](webapp/test/unit/controller/App.qunit.ts) there is nothing surprising from TypeScript perspective. There isn't any TypeScript-specific syntax required, but of course ES6-style imports are used just like in TypeScript application code. 543 | 544 | > Note: `QUnit` is globally defined and its types are automatically required by the UI5 types. So there is no setup needed to use it. However, in order to allow clean code that does not access any globals, starting with UI5 1.112, QUnit should be explicitly imported like this: `import QUnit from "sap/ui/thirdparty/qunit-2";` 545 | 546 | ### Integration Tests (OPA) 547 | 548 | OPA tests are written in a simplified and slightly different way compared to JavaScript, so make sure to carefully read this section! 549 | 550 | #### The "Hello" Journey 551 | 552 | The test journey [`webapp/test/integration/HelloJourney.ts`](webapp/test/integration/HelloJourney.ts) is overall pretty straightforward, but it comes with one significant difference to JavaScript: the `Given`/`When`/`Then` objects normally given to the `opaTest(...)` callback are **not used at all!**
553 | Instead, the actions and assertions are called directly on the OPA test Page (in this case the `AppPage`). The same goes for setup and teardown functions like `iStartMyUIComponent()` and `iTeardownMyApp()`, which are also available on the Page, as it inherits from `Opa5`. 554 | 555 | You are free to make the difference between actions and assertions clear with comments, but there is no need to carry different entities around through the code, especially as those entities are hard to fit into the TypeScript world. 556 | 557 | #### The "App" Page 558 | 559 | This is where the biggest changes are done compared to non-TypeScript OPA tests: the OPA Pages are simply classes extending `Opa5`, having the actions and assertions as class methods. 560 | 561 | Apart from this, the implementation of the actions and assertions is done just like in JavaScript. 562 | 563 | 564 | 565 | ## 12. Enable TypeScript support for the Test Code 566 | 567 | To make TypeScript aware of the additional module paths for the `unit` and the `integration` test code, we need to extend the `paths` information of the `tsconfig.json` with the following entries: 568 | 569 | ```json 570 | "paths": { 571 | "ui5/typescript/helloworld/*": ["./webapp/*"], 572 | "unit/*": ["./webapp/test/unit/*"], 573 | "integration/*": ["./webapp/test/integration/*"] 574 | }, 575 | ``` 576 | 577 | Now you should be able to get proper code completion support for your QUnit and OPA tests. 578 | 579 | ## 13. Automated QUnit/OPA Testing using ui5-test-runner 580 | 581 | To automate the execution of the QUnit/OPA tests, we are using [`ui5-test-runner`](https://arnaudbuchholz.github.io/ui5-test-runner/). To add the required dependency you need to run the following command: 582 | 583 | ```sh 584 | npm install --save-dev ui5-test-runner 585 | ``` 586 | 587 | While `ui5-test-runner` can launch the app in its legacy mode, it requires the app to be available at a given URL in normal mode. Either start the app first (if not running) in a different terminal, then afterwards the test-runner: 588 | 589 | ```sh 590 | npm start 591 | # in different terminal: 592 | npx ui5-test-runner --url http://localhost:8080/test/testsuite.qunit.html 593 | ``` 594 | 595 | Or, if you do not want to launch the app separately, you can make the test runner start it: 596 | 597 | ```sh 598 | npx ui5-test-runner --start start --url http://localhost:8080/test/testsuite.qunit.html 599 | ``` 600 | 601 | > Note: if you want to monitor the progress of the tests, you can add the `--port 8081` parameter. Then the progress can be seen at http://localhost:8081/_/progress.html as long as the tests are running. 602 | 603 | After running the tests, a `report` folder will be created, which contains all kinds of information about the test run, including screenshots. 604 | 605 | Add this `report` folder to your `.gitignore` file, as it is not meant to be checked in. 606 | 607 | 608 | ## 14. Enable Code Coverage Reporting 609 | 610 | To measure code coverage, the code needs to be instrumented. In contrast to JavaScript, the code instrumentation for coverage reporting in TypeScript does not work with `@ui5/middleware-code-coverage` at the moment. Instead, the Babel plugin `istanbul` needs to be set up in the Babel configuration of the `ui5-tooling-transpile` middleware. This is also documented by the [`ui5-test-runner`](https://github.com/ArnaudBuchholz/ui5-test-runner/blob/main/docs/coverage.md#typescript-ui5cli-projects). 611 | 612 | 613 | ### a) Configuration in `ui5-coverage.yaml` 614 | 615 | Code instrumentation is not always needed, only for coverage tests. Hence, create an additional configuration file `ui5-coverage.yaml` as copy of `ui5.yaml` with the `ui5-tooling-transpile-middleware` section extended like this. The respective npm script in `package.json` will then reference this file. 616 | 617 | ```yaml 618 | - name: ui5-tooling-transpile-middleware 619 | afterMiddleware: compression 620 | configuration: 621 | debug: true 622 | babelConfig: 623 | sourceMaps: true 624 | ignore: 625 | - "**/*.d.ts" 626 | presets: 627 | - - "@babel/preset-env" 628 | - targets: defaults 629 | - - transform-ui5 630 | - "@babel/preset-typescript" 631 | plugins: 632 | - istanbul 633 | ``` 634 | 635 | ### b) Add `babel-plugin-istanbul` Dependency 636 | 637 | Add the `babel-plugin-istanbul` referenced in the last line above to the project's dev dependencies: 638 | 639 | ```sh 640 | npm install --save-dev babel-plugin-istanbul 641 | ``` 642 | 643 | ### c) Use `.nycrc.json` to Exclude Test Files from Coverage 644 | 645 | To exclude the test files from coverage reporting, create a `.nycrc.json` file with the following content: 646 | 647 | ```json 648 | { 649 | "all": true, 650 | "sourceMap": false, 651 | "exclude": [ 652 | "**/test/**/*.ts" 653 | ] 654 | } 655 | ``` 656 | 657 | ### Running Tests with Code Coverage 658 | 659 | Now, you are ready to run your first test execution with coverage reporting. Again, there are two ways to do it: 660 | 661 | Either run the following commands in different terminals. The first one runs the UI5 dev server with the new yaml including coverage configuration (stop the regular `npm start` if still running to free port 8080), and the second one launches the actual tests witch certain coverage thresholds: 662 | 663 | ```sh 664 | npx ui5 serve --port 8080 --config ui5-coverage.yaml 665 | # in different terminal: 666 | npx ui5-test-runner --url http://localhost:8080/test/testsuite.qunit.html --coverage -ccb 60 -ccf 100 -ccl 80 -ccs 80 667 | ``` 668 | 669 | Or do it within one command (this will look simpler once we create a `package.json` script for the application start): 670 | 671 | ```sh 672 | npx ui5-test-runner --start "npx ui5 serve --port 8080 --config ui5-coverage.yaml" --url http://localhost:8080/test/testsuite.qunit.html --coverage -ccb 60 -ccf 100 -ccl 80 -ccs 80 673 | ``` 674 | 675 | After the execution finished, you should see a **Coverage summary** in your console and you can find the results of the test coverage run in your `coverage` folder. The following resources are being created for the different report formats: 676 | 677 | * `lcovonly`: `coverage/lcov.info` 678 | * `cobertura`: `cobertura-coverage.xml` 679 | * `html`: `coverage/lcov-report/index.html` 680 | 681 | Add the `coverage` and `.nyc_output` folders which are created during the tests to your `.gitignore` file. 682 | 683 | 684 | ## 15. Add Scripts for Testing to `package.json` 685 | 686 | Now it's time to write down the testing commands used so far to the `"scripts"` section in `package.json`, so you don't need to type the full commands every time they are used: 687 | 688 | ```json 689 | { 690 | [...], 691 | "start-coverage": "ui5 serve --port 8080 --config ui5-coverage.yaml", 692 | "test-runner": "ui5-test-runner --url http://localhost:8080/test/testsuite.qunit.html", 693 | "test-runner-coverage": "ui5-test-runner --url http://localhost:8080/test/testsuite.qunit.html --coverage -ccb 60 -ccf 100 -ccl 80 -ccs 80", 694 | "test-ui5": "ui5-test-runner --start start-coverage --url http://localhost:8080/test/testsuite.qunit.html --coverage -ccb 60 -ccf 100 -ccl 80 -ccs 80", 695 | "test": "npm run lint && npm run test-ui5", 696 | } 697 | ``` 698 | 699 | The `test-ui5` script is suited well for CI scenarios, as it includes the instruction to start the server. The `test-runner...` scripts, on the other hand, expect the server to already run. You can of course adapt as needed. 700 | 701 | For the general `test` script, we recommend to execute `lint` and `test-ui5` to validate that static code checks and the functional and integration tests are executed when testing your app. 702 | 703 | 704 | This is the end! You got a comprehensive and complete overview on UI5 application development with TypeScript from the very basics. All setup details, which might be skipped in other tutorials because the setup is already prepared, should be explained now. 705 | 706 | ## Done! 707 | 708 | You now have not only a fully functional TypeScript app development setup with all the features and npm scripts – but hopefully also an understanding of the different tools and configurations used in this setup! 709 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2023", 4 | "module": "es2022", 5 | "moduleResolution": "node", 6 | "skipLibCheck": true, 7 | "allowJs": true, 8 | "strict": true, 9 | "strictPropertyInitialization": false, 10 | "rootDir": "./webapp", 11 | "baseUrl": "./", 12 | "paths": { 13 | "ui5/typescript/helloworld/*": ["./webapp/*"], 14 | "unit/*": ["./webapp/test/unit/*"], 15 | "integration/*": ["./webapp/test/integration/*"] 16 | }, 17 | "composite": true 18 | }, 19 | "include": ["./webapp/**/*"] 20 | } 21 | -------------------------------------------------------------------------------- /ui5-coverage.yaml: -------------------------------------------------------------------------------- 1 | specVersion: "4.0" 2 | metadata: 3 | name: ui5.typescript.helloworld 4 | type: application 5 | framework: 6 | name: OpenUI5 7 | version: "1.132.1" 8 | libraries: 9 | - name: sap.m 10 | - name: sap.ui.core 11 | - name: sap.ui.unified 12 | - name: themelib_sap_horizon 13 | builder: 14 | customTasks: 15 | - name: ui5-tooling-transpile-task 16 | afterTask: replaceVersion 17 | server: 18 | customMiddleware: 19 | - name: ui5-tooling-transpile-middleware 20 | afterMiddleware: compression 21 | configuration: 22 | debug: true 23 | babelConfig: 24 | sourceMaps: true 25 | ignore: 26 | - "**/*.d.ts" 27 | presets: 28 | - - "@babel/preset-env" 29 | - targets: defaults 30 | - - transform-ui5 31 | - "@babel/preset-typescript" 32 | plugins: 33 | - istanbul 34 | - name: ui5-middleware-livereload 35 | afterMiddleware: compression 36 | -------------------------------------------------------------------------------- /ui5-dist.yaml: -------------------------------------------------------------------------------- 1 | specVersion: "4.0" 2 | metadata: 3 | name: ui5.typescript.helloworld 4 | type: application 5 | resources: 6 | configuration: 7 | paths: 8 | webapp: dist 9 | framework: 10 | name: OpenUI5 11 | version: "1.132.1" 12 | libraries: 13 | - name: sap.m 14 | - name: sap.ui.core 15 | - name: sap.ui.unified 16 | - name: themelib_sap_horizon 17 | -------------------------------------------------------------------------------- /ui5.yaml: -------------------------------------------------------------------------------- 1 | specVersion: "4.0" 2 | metadata: 3 | name: ui5.typescript.helloworld 4 | type: application 5 | framework: 6 | name: OpenUI5 7 | version: "1.131.1" 8 | libraries: 9 | - name: sap.m 10 | - name: sap.ui.core 11 | - name: sap.ui.unified 12 | - name: themelib_sap_horizon 13 | builder: 14 | customTasks: 15 | - name: ui5-tooling-transpile-task 16 | afterTask: replaceVersion 17 | server: 18 | customMiddleware: 19 | - name: ui5-tooling-transpile-middleware 20 | afterMiddleware: compression 21 | - name: ui5-middleware-livereload 22 | afterMiddleware: compression 23 | -------------------------------------------------------------------------------- /webapp/Component.ts: -------------------------------------------------------------------------------- 1 | import UIComponent from "sap/ui/core/UIComponent"; 2 | import Device from "sap/ui/Device"; 3 | 4 | 5 | /** 6 | * @namespace ui5.typescript.helloworld 7 | */ 8 | export default class Component extends UIComponent { 9 | 10 | public static metadata = { 11 | interfaces: ["sap.ui.core.IAsyncContentCreation"], 12 | manifest: "json" 13 | }; 14 | 15 | private contentDensityClass : string; 16 | 17 | public init() : void { 18 | // call the base component's init function 19 | super.init(); 20 | } 21 | 22 | /** 23 | * This method can be called to determine whether the sapUiSizeCompact or sapUiSizeCozy 24 | * design mode class should be set, which influences the size appearance of some controls. 25 | * 26 | * @public 27 | * @return {string} css class, either 'sapUiSizeCompact' or 'sapUiSizeCozy' - or an empty string if no css class should be set 28 | */ 29 | public getContentDensityClass() : string { 30 | if (this.contentDensityClass === undefined) { 31 | // check whether FLP has already set the content density class; do nothing in this case 32 | if (document.body.classList.contains("sapUiSizeCozy") || document.body.classList.contains("sapUiSizeCompact")) { 33 | this.contentDensityClass = ""; 34 | } else if (!Device.support.touch) { // apply "compact" mode if touch is not supported 35 | this.contentDensityClass = "sapUiSizeCompact"; 36 | } else { 37 | // "cozy" in case of touch support; default for most sap.m controls, but needed for desktop-first controls like sap.ui.table.Table 38 | this.contentDensityClass = "sapUiSizeCozy"; 39 | } 40 | } 41 | return this.contentDensityClass; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /webapp/controller/App.controller.ts: -------------------------------------------------------------------------------- 1 | import MessageBox from "sap/m/MessageBox"; 2 | import Controller from "sap/ui/core/mvc/Controller"; 3 | import AppComponent from "../Component"; 4 | 5 | /** 6 | * @namespace ui5.typescript.helloworld.controller 7 | */ 8 | export default class App extends Controller { 9 | 10 | public onInit() : void { 11 | // apply content density mode to root view 12 | const view = this.getView() 13 | if (view) { 14 | view.addStyleClass((this.getOwnerComponent() as AppComponent).getContentDensityClass()); 15 | } 16 | } 17 | 18 | public sayHello() : void { 19 | MessageBox.show("Hello World!"); 20 | } 21 | } -------------------------------------------------------------------------------- /webapp/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UI5 TypeScript Hello World App 6 | 18 | 19 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /webapp/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "_version": "1.68.0", 3 | 4 | "sap.app": { 5 | "id": "ui5.typescript.helloworld", 6 | "type": "application", 7 | "i18n": "i18n/i18n.properties", 8 | "title": "UI5 TypeScript Hello World", 9 | "description": "UI5 TypeScript Hello World", 10 | "applicationVersion": { 11 | "version": "1.0.0" 12 | }, 13 | "ach": "set-ach" 14 | }, 15 | 16 | "sap.fiori": { 17 | 18 | }, 19 | 20 | "sap.ui": { 21 | "technology": "UI5", 22 | "icons": { 23 | 24 | }, 25 | "deviceTypes": { 26 | "desktop": true, 27 | "tablet": true, 28 | "phone": true 29 | } 30 | }, 31 | 32 | "sap.ui5": { 33 | "rootView": { 34 | "viewName": "ui5.typescript.helloworld.view.App", 35 | "type": "XML", 36 | "id": "app" 37 | }, 38 | 39 | "dependencies": { 40 | "minUI5Version": "1.104.0", 41 | "libs": { 42 | "sap.ui.core": {}, 43 | "sap.ui.layout": {}, 44 | "sap.ui.unified": {}, 45 | "sap.m": {} 46 | } 47 | }, 48 | 49 | "handleValidation": true, 50 | 51 | "contentDensities": { 52 | "compact": true, 53 | "cozy": true 54 | }, 55 | 56 | "models": { 57 | 58 | }, 59 | 60 | "routing": { 61 | 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /webapp/test/Test.qunit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 13 |
14 |
15 | 16 | -------------------------------------------------------------------------------- /webapp/test/integration/HelloJourney.ts: -------------------------------------------------------------------------------- 1 | import opaTest from "sap/ui/test/opaQunit"; 2 | import AppPage from "./pages/AppPage"; 3 | import QUnit from "sap/ui/thirdparty/qunit-2"; 4 | 5 | const onTheAppPage = new AppPage(); 6 | 7 | QUnit.module("Hello"); 8 | 9 | opaTest("Should open the Hello dialog", function () { 10 | // Arrangements 11 | onTheAppPage.iStartMyUIComponent({ 12 | componentConfig: { 13 | name: "ui5.typescript.helloworld" 14 | } 15 | }); 16 | 17 | // Action 18 | onTheAppPage.iPressTheSayHelloWithDialogButton(); 19 | 20 | // Assertion 21 | onTheAppPage.iShouldSeeTheHelloDialog(); 22 | 23 | // Action 24 | onTheAppPage.iPressTheOkButtonInTheDialog(); 25 | 26 | // Assertion 27 | onTheAppPage.iShouldNotSeeTheHelloDialog(); 28 | 29 | // Cleanup 30 | onTheAppPage.iTeardownMyApp(); 31 | }); 32 | 33 | opaTest("Should close the Hello dialog", function () { 34 | // Arrangements 35 | onTheAppPage.iStartMyUIComponent({ 36 | componentConfig: { 37 | name: "ui5.typescript.helloworld" 38 | } 39 | }); 40 | 41 | // Actions 42 | onTheAppPage.iPressTheSayHelloWithDialogButton() 43 | .and.iPressTheOkButtonInTheDialog(); 44 | 45 | // Assertions 46 | onTheAppPage.iShouldNotSeeTheHelloDialog(); 47 | 48 | // Cleanup 49 | onTheAppPage.iTeardownMyApp(); 50 | }); 51 | -------------------------------------------------------------------------------- /webapp/test/integration/opaTests.qunit.ts: -------------------------------------------------------------------------------- 1 | // import all your OPA tests here 2 | import "integration/HelloJourney"; 3 | -------------------------------------------------------------------------------- /webapp/test/integration/pages/AppPage.ts: -------------------------------------------------------------------------------- 1 | import Opa5 from "sap/ui/test/Opa5"; 2 | import Press from "sap/ui/test/actions/Press"; 3 | 4 | const viewName = "ui5.typescript.helloworld.view.App"; 5 | 6 | export default class AppPage extends Opa5 { 7 | 8 | // Actions 9 | iPressTheSayHelloWithDialogButton() { 10 | return this.waitFor({ 11 | id: "helloButton", 12 | viewName, 13 | actions: new Press(), 14 | errorMessage: "Did not find the 'Say Hello With Dialog' button on the App view" 15 | }); 16 | } 17 | 18 | iPressTheOkButtonInTheDialog() { 19 | return this.waitFor({ 20 | controlType: "sap.m.Button", 21 | searchOpenDialogs: true, 22 | viewName, 23 | actions: new Press(), 24 | errorMessage: "Did not find the 'OK' button in the Dialog" 25 | }); 26 | } 27 | 28 | // Assertions 29 | iShouldSeeTheHelloDialog() { 30 | return this.waitFor({ 31 | controlType: "sap.m.Dialog", 32 | success: function () { 33 | // we set the view busy, so we need to query the parent of the app 34 | Opa5.assert.ok(true, "The dialog is open"); 35 | }, 36 | errorMessage: "Did not find the dialog control" 37 | }); 38 | } 39 | 40 | iShouldNotSeeTheHelloDialog() { 41 | return this.waitFor({ 42 | controlType: "sap.m.App", // dummy, I just want a check function, where I can search the DOM. Probably there is a better way for a NEGATIVE test (NO dialog). 43 | check: function() { 44 | return document.querySelectorAll(".sapMDialog").length === 0; 45 | }, 46 | success: function() { 47 | Opa5.assert.ok(true, "No dialog is open"); 48 | } 49 | }); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /webapp/test/testsuite.qunit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /webapp/test/testsuite.qunit.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "Testsuite for the TypesScript Hello World app", 3 | defaults: { 4 | page: "ui5://test-resources/ui5/typescript/helloworld/Test.qunit.html?testsuite={suite}&test={name}", 5 | qunit: { 6 | version: 2 7 | }, 8 | sinon: { 9 | version: 4 10 | }, 11 | ui5: { 12 | theme: "sap_horizon" 13 | }, 14 | loader: { 15 | paths: { 16 | "ui5/typescript/helloworld": "../", 17 | "integration": "./integration", 18 | "unit": "./unit" 19 | } 20 | } 21 | }, 22 | tests: { 23 | "unit/unitTests": { 24 | title: "QUnit test suite for the TypesScript Hello World app" 25 | }, 26 | "integration/opaTests": { 27 | title: "Integration tests for the TypeScript Hello World app" 28 | }, 29 | } 30 | }; -------------------------------------------------------------------------------- /webapp/test/unit/controller/App.qunit.ts: -------------------------------------------------------------------------------- 1 | import AppController from "ui5/typescript/helloworld/controller/App.controller"; 2 | import QUnit from "sap/ui/thirdparty/qunit-2"; 3 | 4 | QUnit.module("Sample App controller test"); 5 | 6 | QUnit.test("The AppController class has a sayHello method", function (assert) { 7 | // as a very basic test example just check the presence of the "sayHello" method 8 | assert.strictEqual(typeof AppController.prototype.sayHello, "function"); 9 | }); 10 | -------------------------------------------------------------------------------- /webapp/test/unit/unitTests.qunit.ts: -------------------------------------------------------------------------------- 1 | // import all your QUnit tests here 2 | import "unit/controller/App.qunit"; 3 | -------------------------------------------------------------------------------- /webapp/view/App.view.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 11 | 12 | 17 | 18 |