├── .circleci └── config.yml ├── .gitignore ├── CODEOWNERS ├── LICENSE ├── README.md ├── _config.yml ├── assets └── server.key.enc ├── config ├── lts-config.json └── project-scratch-def.json ├── developer-workflow.md ├── doc-resources ├── lts_browser_debugging_example.png ├── lts_package_app_success.png ├── lts_sfdx_test_run_output.png ├── lts_test_suite_page_success.png └── metadata-visualization-and-runtime-flowchart.png ├── lightning-component-tests ├── main │ └── default │ │ ├── aura │ │ ├── egApplicationEvent │ │ │ ├── egApplicationEvent.auradoc │ │ │ ├── egApplicationEvent.evt │ │ │ └── egApplicationEvent.evt-meta.xml │ │ ├── egAttributeTypes │ │ │ ├── egAttributeTypes.auradoc │ │ │ ├── egAttributeTypes.cmp │ │ │ ├── egAttributeTypes.cmp-meta.xml │ │ │ └── egAttributeTypesController.js │ │ ├── egClientSideAction │ │ │ ├── egClientSideAction.auradoc │ │ │ ├── egClientSideAction.cmp │ │ │ ├── egClientSideAction.cmp-meta.xml │ │ │ ├── egClientSideActionController.js │ │ │ └── egClientSideActionHelper.js │ │ ├── egComponentEvent │ │ │ ├── egComponentEvent.auradoc │ │ │ ├── egComponentEvent.evt │ │ │ └── egComponentEvent.evt-meta.xml │ │ ├── egComponentMethod │ │ │ ├── egComponentMethod.auradoc │ │ │ ├── egComponentMethod.cmp │ │ │ ├── egComponentMethod.cmp-meta.xml │ │ │ └── egComponentMethodController.js │ │ ├── egConditionalUI │ │ │ ├── egConditionalUI.auradoc │ │ │ ├── egConditionalUI.cmp │ │ │ ├── egConditionalUI.cmp-meta.xml │ │ │ └── egConditionalUIController.js │ │ ├── egEventHandling │ │ │ ├── egEventHandling.auradoc │ │ │ ├── egEventHandling.cmp │ │ │ ├── egEventHandling.cmp-meta.xml │ │ │ └── egEventHandlingController.js │ │ ├── egFacet │ │ │ ├── egFacet.auradoc │ │ │ ├── egFacet.cmp │ │ │ └── egFacet.cmp-meta.xml │ │ ├── egGlobalValueProvider │ │ │ ├── egGlobalValueProvider.auradoc │ │ │ ├── egGlobalValueProvider.cmp │ │ │ └── egGlobalValueProvider.cmp-meta.xml │ │ ├── egLdsView │ │ │ ├── egLdsView.auradoc │ │ │ ├── egLdsView.cmp │ │ │ ├── egLdsView.cmp-meta.xml │ │ │ ├── egLdsViewController.js │ │ │ └── egLdsViewHelper.js │ │ ├── egRenderElement │ │ │ ├── egRenderElement.auradoc │ │ │ ├── egRenderElement.cmp │ │ │ └── egRenderElement.cmp-meta.xml │ │ └── egServerSideActionCallback │ │ │ ├── egServerSideActionCallback.auradoc │ │ │ ├── egServerSideActionCallback.cmp │ │ │ ├── egServerSideActionCallback.cmp-meta.xml │ │ │ ├── egServerSideActionCallbackController.js │ │ │ └── egServerSideActionCallbackHelper.js │ │ ├── classes │ │ ├── egServerSideActionController.cls │ │ └── egServerSideActionController.cls-meta.xml │ │ └── labels │ │ └── CustomLabels.labels-meta.xml └── test │ └── default │ ├── aura │ ├── jasmineTests │ │ ├── jasmineTests.app │ │ ├── jasmineTests.app-meta.xml │ │ └── jasmineTests.auradoc │ ├── lts_jasmineRunner │ │ ├── lts_jasmineRunner.auradoc │ │ ├── lts_jasmineRunner.cmp │ │ ├── lts_jasmineRunner.cmp-meta.xml │ │ └── lts_jasmineRunnerController.js │ ├── lts_mochaRunner │ │ ├── lts_mochaRunner.auradoc │ │ ├── lts_mochaRunner.cmp │ │ ├── lts_mochaRunner.cmp-meta.xml │ │ └── lts_mochaRunnerController.js │ └── mochaTests │ │ ├── mochaTests.app │ │ ├── mochaTests.app-meta.xml │ │ └── mochaTests.auradoc │ └── staticresources │ ├── jasmineExampleTests.resource │ ├── jasmineExampleTests.resource-meta.xml │ ├── jasmineHelloWorldTests.resource │ ├── jasmineHelloWorldTests.resource-meta.xml │ ├── jasmineLightningDataServiceTests.resource │ ├── jasmineLightningDataServiceTests.resource-meta.xml │ ├── lts_chai.resource │ ├── lts_chai.resource-meta.xml │ ├── lts_jasmine.resource │ ├── lts_jasmine.resource-meta.xml │ ├── lts_jasmineboot.resource │ ├── lts_jasmineboot.resource-meta.xml │ ├── lts_mochaBootJs.resource │ ├── lts_mochaBootJs.resource-meta.xml │ ├── lts_mochaCss.resource │ ├── lts_mochaCss.resource-meta.xml │ ├── lts_mochaJs.resource │ ├── lts_mochaJs.resource-meta.xml │ ├── lts_sinon.resource │ ├── lts_sinon.resource-meta.xml │ ├── lts_testutil.resource │ ├── lts_testutil.resource-meta.xml │ ├── mochaExampleTests.resource │ └── mochaExampleTests.resource-meta.xml └── sfdx-project.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/forcedotcom/LightningTestingService 5 | docker: 6 | - image: circleci/node:10.15.3-browsers 7 | environment: 8 | CIRCLE_ARTIFACTS: /tmp/circleci-artifacts 9 | CIRCLE_TEST_REPORTS: /tmp/circleci-test-results 10 | SFDX_AUTOUPDATE_DISABLE: true 11 | SFDX_USE_GENERIC_UNIX_KEYCHAIN: true 12 | SFDX_DOMAIN_RETRY: 300 13 | steps: 14 | - checkout 15 | # Prepare for artifact and test results collection equivalent to how it was done on 1.0. 16 | # In many cases you can simplify this from what is generated here. 17 | # 'See docs on artifact collection here https://circleci.com/docs/2.0/artifacts/' 18 | - run: mkdir -p $CIRCLE_ARTIFACTS $CIRCLE_TEST_REPORTS 19 | # Dependencies 20 | # This would typically go in either a build or a build-and-test job when using workflows 21 | # Restore the dependency cache 22 | - restore_cache: 23 | keys: 24 | # This branch if available 25 | - v1-dep-{{ .Branch }}- 26 | # Default branch if not 27 | - v1-dep-master- 28 | # Any branch if there are none on the default branch - this should be unnecessary if you have your default branch configured correctly 29 | - v1-dep- 30 | - run: sudo chown -R circleci /usr/local/lib/node_modules 31 | - run: sudo npm install -g sfdx-cli 32 | - run: sudo mkdir ~/.config 33 | - run: sudo mkdir ~/.config/sfdx 34 | - run: sudo chown -R circleci ~/.config 35 | - run: sudo echo "[\"plugin-lightning-testing-service\"]" >> ~/.config/sfdx/unsignedPluginWhiteList.json 36 | - run: npm install plugin-lightning-testing-service -g 37 | - run: sfdx plugins:link /usr/local/lib/node_modules/plugin-lightning-testing-service 38 | - run: sudo apt-get update 39 | - run: sudo apt-get install vim-common 40 | # Test 41 | - run: echo $SERVER_KEY_HEX | xxd -r -ps >> assets/server.key 42 | - run: sfdx force:auth:jwt:grant --clientid $CONSUMERKEY --jwtkeyfile assets/server.key --username $USERNAME --setdefaultdevhubusername 43 | - run: lintout=$(sfdx force:lightning:lint lightning-component-tests/main/default/aura/);if [[ $lintout =~ .*problem.*error.*warning ]]; then echo "$lintout"; false; fi 44 | - run: sfdx force:org:create -s -f config/project-scratch-def.json -a travisciorg 45 | - run: sfdx force:source:push -u travisciorg 46 | - run: sudo chown -R circleci /usr/local/lib/node_modules 47 | - run: sfdx aura-test:run -a jasmineTests.app 48 | - run: sfdx aura-test:run -a mochaTests.app 49 | - run: sfdx force:org:delete -u travisciorg -p 50 | # Teardown 51 | # Save test results 52 | - store_test_results: 53 | path: /tmp/circleci-test-results 54 | # Save artifacts 55 | - store_artifacts: 56 | path: /tmp/circleci-artifacts 57 | - store_artifacts: 58 | path: /tmp/circleci-test-results 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .sfdx 3 | .project 4 | .salesforce 5 | node_modules 6 | .idea 7 | .tern-project 8 | .classpath 9 | .settings/ 10 | selenium-client-jars/ 11 | test/artifacts 12 | .vscode -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Comment line immediately above ownership line is reserved for related gus information. Please be careful while editing. 2 | #ECCN:Open Source 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lightning Testing Service (LTS) [![CircleCI](https://circleci.com/gh/forcedotcom/LightningTestingService/tree/master.svg?style=shield)](https://circleci.com/gh/forcedotcom/LightningTestingService/tree/master) 2 | 3 | ## Lightning Testing Service Overview 4 | 5 | **NOTICE: This tool is no longer supported and no additional work is planned for this tool. We recommend if you want to use this tool, that you treat it as an example and create your own fork of it to use in your build process** 6 | 7 | The Lightning Testing Service, or LTS, is a set of tools and services that let you create test suites for your Aura components using standard JavaScript test frameworks, such as Jasmine and Mocha. 8 | 9 | **Note:** _As of Spring '19, the Lightning Component framework has two programming models, Lightning Web Components, and Aura Components, the original model. Use LTS to test Aura components._ 10 | 11 | Automated tests are the best way to achieve predictable, repeatable assessments of the quality of your custom code. Writing automated tests for your custom components gives you confidence that they work as designed, and allows you to evaluate the impact of changes, such as refactoring, or of new versions of Salesforce or third-party JavaScript libraries. 12 | 13 | The LTS supports testing with standard JavaScript test frameworks. We provide easy-to-use wrappers for using [Jasmine](https://jasmine.github.io/) and [Mocha](http://mochajs.org/). If you prefer to use an alternative test framework, you can wrap it yourself. (See [Next Steps](#next-steps) for some details.) 14 | 15 | ## Getting Started with Lightning Testing Service 16 | 17 | LTS and [Salesforce CLI](https://developer.salesforce.com/platform/dx) are two great tastes that taste great together. Installing the LTS is a one line command in the Salesforce CLI. Once installed, you can work with your test suite in a variety of ways from the command line. This approach also facilitates systematic automated testing. 18 | 19 | > **Note:** If you just want to “kick the tires,” that's cool, too. You can manually install the LTS unmanaged package. The package provides the test service app and an example test suite. It also includes example components that the test suite runs against. You run the test suite by accessing the URL for the LTS app in your org. 20 | 21 | Write your tests using a JavaScript testing framework of your choosing. We provide easy-to-use wrappers for [Jasmine](https://jasmine.github.io/) and [Mocha](http://mochajs.org/). 22 | 23 | A simple Jasmine test spec looks like the following: 24 | 25 | ```js 26 | /** 27 | * This is a 'hello world' Jasmine test spec 28 | */ 29 | describe("A simple passing test", function () { 30 | it("checks that true is always true", function () { 31 | expect(true).toBe(true); 32 | }); 33 | }); 34 | ``` 35 | 36 | A similar Mocha test looks, well, similar: 37 | 38 | ```js 39 | /** 40 | * This is a 'hello world' Mocha test 41 | */ 42 | var assert = require("assert"); 43 | describe("Test the nature of truth", function () { 44 | describe("A simple passing test", function () { 45 | it("checks that true is always true", function () { 46 | assert.equal(true, true); 47 | }); 48 | }); 49 | }); 50 | ``` 51 | 52 | You can write your own wrapper if you prefer a different testing framework. It's not hard, but plan on half a day to a day of work. 53 | 54 | The LTS also provides utilities specific to the Lightning Component framework, which let you test behavior specific to Aura components. Details are explained in comments in the example tests. 55 | 56 | Your test suite is deployed in the form of an archive (zip) static resource. Once the LTS is installed and configured, you make changes to your test suite, create the archive, and upload it to your org. Once uploaded you can run the test suite via the command line or via URL. 57 | 58 | > **Important:** Don't run tests in your production org. The LTS doesn't provide an isolated test context or transaction wrapper. **DML operations you perform in your tests won't be rolled back at the end of the test.** We recommend that you run your LTS test suites _only_ in scratch orgs, using data provided by the test suite itself. 59 | 60 | ## Installing the Lightning Testing Service 61 | 62 | There are two ways you can install the LTS. The simplest is to use the Salesforce CLI. If you're not using the Salesforce CLI, you can manually install the unmanaged package. 63 | 64 | ### Install the Lightning Testing Service with the Salesforce CLI 65 | 66 | The Salesforce CLI includes a one line command for automatically installing the LTS unmanaged package. The Salesforce CLI also allows you to use the `sfdx` command line tool to perform automated testing as part of your development process, including automated process, such as continuous integration. 67 | 68 | > NOTICE: When installing the CLI Plugin, an external request is made to `https://api.github.com/repos/forcedotcom/LightningTestingService/releases/` in order to download supporting files for the plugin. If this URL is blocked by your firewall, the installation of the plugin will fail. 69 | 70 | 1. If you haven't already, install the Salesforce CLI by following the instructions for your operating system: 71 | 72 | [Install the Salesforce CLI](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_install_cli.htm) in the _Salesforce CLI Setup Guide_ 73 | 74 | 2. Verify that the Salesforce CLI plug-in is installed and updated by running the following command in your shell or terminal application: 75 | 76 | ```bash 77 | sfdx update 78 | ``` 79 | 80 | Additional verification details are available in [Verify Your Installation and Install the Plug-In](https://developer.salesforce.com/docs/atlas.en-us.sfdx_setup.meta/sfdx_setup/sfdx_setup_install_cli_verify.htm) in the _Salesforce CLI Setup Guide._ 81 | 82 | 3. Install the [LTS CLI Plugin](https://github.com/forcedotcom/plugin-lightning-test-service) with the following command: 83 | 84 | ```bash 85 | sfdx plugins:install plugin-lightning-testing-service 86 | ``` 87 | 88 | 4. Install the LTS package with the following command: 89 | 90 | ```bash 91 | sfdx aura-test:install 92 | ``` 93 | 94 | This installs the latest version of the LTS package into your default SFDX org. See the help for the `install` command for additional options. 95 | 96 | After you install LTS, you can run your tests from the command line using the `sfdx` tool. For example: 97 | 98 | ```bash 99 | sfdx force:auth:web:login -s # connect to your scratch org 100 | sfdx force:source:push # push local source to the scratch org 101 | sfdx aura-test:run -a jasmineTests.app # run the test suite 102 | ``` 103 | 104 | When you run the `aura-test:run -a jasmineTests.app` command in a connected workspace you should see something like the following: 105 | 106 | ![Console output from a successful test suite execution using sfdx](doc-resources/lts_sfdx_test_run_output.png) 107 | 108 | This tells you that the command line tools are working, and connected to your development org. 109 | 110 | ### Install the Lightning Testing Service Unmanaged Package Manually 111 | 112 | If you're not using the Salesforce CLI, you're missing out on a lot, but you can still use LTS. Installing the LTS package is just like [installing any other unmanaged package](https://help.salesforce.com/articleView?id=distribution_installing_packages.htm&language=en&type=0). 113 | 114 | 1. Log in to your org. We recommend that you create a new DE org for evaluating the LTS. 115 | 2. Go to the project [Releases](https://github.com/forcedotcom/LightningTestingService/releases) page, and click the package installation URL for the latest release. 116 | 3. Authenticate again with the credentials for your DE org. 117 | 4. Follow the normal package installation prompts. We recommend installing the package for admins only. 118 | 119 | ## Touring the Lightning Testing Service 120 | 121 | However you install it, the LTS package includes the following items: 122 | 123 | - Example test suites 124 | - Jasmine JavaScript files in archive static resources 125 | - Mocha JavaScript files in archive static resources 126 | - Example components to be tested 127 | - Components, an Apex class, and a custom label 128 | - LTS infrastructure 129 | - Jasmine framework and wrapper 130 | - Mocha framework and wrapper 131 | - LTS test utilities 132 | - Test runner component 133 | - Wrapper test app 134 | 135 | Once installed, you can run the example test suite by going to the following URL in your org: 136 | https://<myServer>/c/jasmineTests.app (Jasmine) 137 | https://<myServer>/c/mochaTests.app (Mocha) 138 | 139 | You should see a screen that looks something like the following. 140 | 141 | ![Successful execution of the jasmineTests.app tests](doc-resources/lts_package_app_success.png) 142 | 143 | This page tells you that the package is correctly installed and LTS is working in your org. 144 | 145 | ## Next Steps 146 | 147 | When you have the LTS package installed and working in your org, you’re ready to begin exploring the test code, and even writing your own tests. Here are some next steps for you to take. 148 | 149 | ### Explore the Example Test Suites 150 | 151 | To dive into the test suites and start learning how to write tests for your Aura components, explore the tests and the components being tested side-by-side. You’ll want to open the test suite file in one window, and in another window (perhaps in your IDE) open the simple components being tested. 152 | 153 | Both the components and the test suites included in the unmanaged package are also available in a repository on GitHub: 154 | 155 | [https://github.com/forcedotcom/LightningTestingService](https://github.com/forcedotcom/LightningTestingService) 156 | 157 | #### Components for Testing 158 | 159 | All of the testable components are named beginning with “eg” (from the abbreviation “e.g.”, meaning _for example_). The components provided in the package can be accessed as you would any Aura component. 160 | 161 | - In the Developer Console 162 | - In the Force.com IDE, in the `/aura/` directory 163 | - By converting your org’s metadata into a format that you can work with locally, using Salesforce CLI 164 | 165 | You can also explore the components directly from the repo. They’re available in the [lightning-component-tests/main/default/aura](https://github.com/forcedotcom/LightningTestingService/tree/master/lightning-component-tests/main/default/aura) directory. 166 | 167 | There are more than a dozen different components, designed to be used in illustrative tests. Each of the components has a description of its behavior in its `.auradoc` documentation file. 168 | 169 | #### Test Suites 170 | 171 | The example tests are included in the form of static resources. You can also review them directly in the repo, in the [lightning-component-tests/test/default/staticresources](https://github.com/forcedotcom/LightningTestingService/tree/master/lightning-component-tests/test/default/staticresources) directory. 172 | 173 | There are four test suites included in the LTS package, three for Jasmine and one for Mocha: 174 | 175 | - `jasmineHelloWorldTests.resource` — A very simple Jasmine example including one passing and one failing test. 176 | - `jasmineExampleTests.resource` — The main Jasmine example test suite. 177 | - `jasmineLightningDataServiceTests.resource` — Some Jasmine examples specific to testing Lightning Data Service-based components. 178 | - `mochaExampleTests.resource` — The Mocha example test suite, which provides examples parallel to the main Jasmine test suite. 179 | 180 | The remainder of the static resources are infrastructure used by the LTS. They’re _briefly_ described in [Use Another JavaScript Test Framework](#use-another-javascript-test-framework). 181 | 182 | The `jasmineExampleTests.resource` and `mochaExampleTests.resource` files are each a single JavaScript file containing a complete test suite. It’s a single file for convenience in delivery and exploration. Your own test suites can include many such files. The test suites are copiously commented. The code and comments serve as the official documentation for how to write tests. 183 | 184 | ### Write Your Own Tests 185 | 186 | A separate document, [Testing Lightning Components with the Lightning Testing Service](https://github.com/forcedotcom/LightningTestingService/blob/master/developer-workflow.md), describes the flow, or lifecycle, of using the LTS to automate your testing. Once you’ve explored the example tests, use this document to dive into writing a test suite for your own custom components. 187 | 188 | ### Use Another JavaScript Test Framework 189 | 190 | The Lightning Testing Service provides a way to use standard JavaScript test frameworks with your Aura components. We’ve provided the example test suites implemented in [Jasmine](https://jasmine.github.io/) and [Mocha](http://mochajs.org/). These are well-regarded test frameworks, and if you haven’t chosen one already, we recommend you start with one of them. 191 | 192 | If you’d prefer to use another test framework, either because you’ve already selected one, or because you find something more to your taste, you can use it with the LTS instead. 193 | 194 | All of the packaged pieces of the LTS are included in the project repository, in the [lightning-component-tests/test/default](https://github.com/forcedotcom/LightningTestingService/tree/master/lightning-component-tests/test/default) directory. The pieces you’ll need to modify or replace are the following items, drawn from the Jasmine wrapper. 195 | 196 | - `aura/jasmineTests` — The front end of the LTS, this simple app includes the test runner component, and a list of test suites to feed it. 197 | - `aura/lts_jasmineRunner` — The test runner component for Jasmine. It includes references to the required Jasmine library, which it loads along with the test spec resources, and then fires the test runner. 198 | - `lts_jasmine.resource` — The Jasmine library, unmodified. 199 | - `lts_jasmineboot.resource` — A JavaScript IIFE that launches Jasmine in the LTS context. 200 | - `lts_testutil.resource` — A collection of utilities for use within your test specs, and by the test framework wrappers. They provide Lightning component-specific functions that make it easier to test your custom components from a test context. 201 | 202 | The Mocha version of the framework wrapper provides similar files. You might find the similarities and differences instructive in creating your own adapter for other frameworks. 203 | 204 | If you’re already experienced with setting up another test framework, adapting the Jasmine examples should take you a day or so, but not longer. We’d be thrilled to hear more about your adventures with other JavaScript test frameworks! 205 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /assets/server.key.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forcedotcom/LightningTestingService/fde6caccd834a29d3b9b671df3c10c5d53fe7033/assets/server.key.enc -------------------------------------------------------------------------------- /config/lts-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "webdriverio": { 3 | "desiredCapabilities": { 4 | "browserName": "chrome", 5 | "chromeOptions": { 6 | "args": ["--headless"] 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /config/project-scratch-def.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "My Company", 3 | "country": "US", 4 | "adminEmail": "myEmail@myCompany.com", 5 | "edition": "Developer", 6 | "settings": { 7 | "lightningExperienceSettings": { 8 | "enableS1DesktopEnabled": true 9 | } 10 | }, 11 | "hasSampleData": true 12 | } 13 | -------------------------------------------------------------------------------- /developer-workflow.md: -------------------------------------------------------------------------------- 1 | # Testing Lightning Components with the Lightning Testing Service 2 | 3 | The Lightning Testing Service, or LTS, is a set of tools and services that let you create test suites for your Lightning components using standard JavaScript test frameworks like Jasmine. 4 | 5 | If you haven’t already installed the LTS, start with [the project introduction](./README.md) instead of this document. 6 | 7 | ## Developer Workflows 8 | 9 | This document provides background on how the LTS works, both by itself and when used with Salesforce DX. We’ll also describe a number of common tasks you might perform while doing development work. You can combine these tasks with each other and with your own developer processes. The workflows enabled by the LTS can significantly improve your overall development lifecycle. 10 | 11 | ## LTS Basics and Some Theory of Operation 12 | 13 | The LTS consists of two major components: 14 | 15 | * The LTS unmanaged package 16 | * The LTS command for the Salesforce CLI 17 | 18 | The unmanaged package includes LTS infrastructure, including a test runner app, and some example tests. Once the package is installed in your org, you can run the example tests by going to the following URL: 19 | yourInstance/c/jasmineTests.app 20 | 21 | When you run tests manually in a browser, you’re using only the pieces of LTS provided in the package. 22 | 23 | ![Metadata visualization and runtime flowchart](doc-resources/metadata-visualization-and-runtime-flowchart.png) 24 | 25 | For more sophisticated development processes, use the LTS commands included with the salesforcedx CLI plugin. The Salesforce CLI allows you to integrate the LTS into your automated testing, continuous integration, and other source-based development processes. 26 | 27 | The command line tool automates running your test suites. It opens the same URL you can open manually, and uses WebDriver to observe the test suite as it runs. Then it parses and packages the results for use in command line-based development processes. 28 | 29 | ## Common Tasks and Workflows 30 | 31 | Many of the tasks presented here are common to all Salesforce DX workflows. They’re presented here because they’re commonly used when you’re writing tests, too. For more details, be sure to read the Salesforce DX documentation. 32 | 33 | ### Prerequisites 34 | 35 | Make sure you’ve installed the LTS unmanaged package and updated to the latest salesforcedx CLI plugin for Salesforce DX, as described in [the project introduction](./README.md). 36 | 37 | ### Create a Scratch Org 38 | 39 | It’s usually best to perform automated testing in a clean org, created with consistent settings. 40 | 41 | 1. Customize your scratch org default settings using a [scratch org configuration file](config/project-scratch-def.json). You can use this to specify a company name, email address, and so on. 42 | 43 | 2. Log in to your Dev Hub. 44 | 45 | ```bash 46 | sfdx force:auth:web:login -d 47 | ``` 48 | 49 | 3. Create a scratch org and set it as the default for your project workspace. 50 | 51 | ```bash 52 | sfdx force:org:create -s -f config/project-scratch-def.json -a scratch1 53 | ``` 54 | 55 | ### Install the Lightning Testing Service Package 56 | 57 | You can quickly install the LTS unmanaged package into an org with the following command: 58 | 59 | ```bash 60 | sfdx force:lightning:test:install -t jasmine 61 | ``` 62 | 63 | > Note that this command installs the jasmine version of LTS unmanaged package into your org, enabling you to create wrapper test applications and author jasmine tests. 64 | 65 | ### Push Metadata to a Scratch Org 66 | 67 | Your tests are written as JavaScript files saved in archive static resources. When you update your tests locally, you need to push the new static resource to the org you’re using for testing. 68 | 69 | 1. Push local metadata to your scratch org. 70 | 71 | ```bash 72 | sfdx force:source:push 73 | ``` 74 | 75 | 2. Log in to your scratch org in a web browser. Use the ```-p``` (```--path```) parameter to open your test suite’s app directly for manual review. 76 | 77 | ```bash 78 | sfdx force:org:open -p /c/jasmineTests.app 79 | ``` 80 | 81 | 3. Update your code, or your tests, and go to step 1. Repeat until you achieve perfection, or at least 100% passing tests. 82 | 83 | ### [Alternative] Push Metadata to a Developer Edition (DE) Org 84 | 85 | Salesforce DX is designed to work with a Dev Hub and scratch orgs. If you have a normal DE org you’d like to work with, the commands are slightly different. 86 | 87 | ```bash 88 | sfdx force:auth:web:login -s # connect to your DE org 89 | sfdx force:source:push -f # push local source to the DE org 90 | ``` 91 | 92 | The ```-f``` flag forces all local changes to be pushed to the DE org. Be careful with this flag! It will overwrite changes you’ve made in the org without warning you. 93 | 94 | ### Run Tests 95 | 96 | For a manual test run, visit the appropriate test app, for example, yourInstance/c/jasmineTests.app. 97 | 98 | ![Sample test run](doc-resources/lts_test_suite_page_success.png) 99 | 100 | For automated test runs, use the Salesforce CLI. 101 | 102 | ```bash 103 | sfdx force:lightning:test:run -a jasmineTests.app 104 | ``` 105 | 106 | See the command line help for other useful details. 107 | 108 | ```bash 109 | sfdx force:lightning:test:run --help 110 | ``` 111 | 112 | ### Debugging Tests 113 | 114 | When a test fails, is it a bug in your software implementation, or a bug in the test itself? Use your standard browser-based JavaScript debugging tools to investigate the behavior of both your production code and your test code. 115 | 116 | ![Sample debugging session in Chrome DevTools](doc-resources/lts_browser_debugging_example.png) 117 | -------------------------------------------------------------------------------- /doc-resources/lts_browser_debugging_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forcedotcom/LightningTestingService/fde6caccd834a29d3b9b671df3c10c5d53fe7033/doc-resources/lts_browser_debugging_example.png -------------------------------------------------------------------------------- /doc-resources/lts_package_app_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forcedotcom/LightningTestingService/fde6caccd834a29d3b9b671df3c10c5d53fe7033/doc-resources/lts_package_app_success.png -------------------------------------------------------------------------------- /doc-resources/lts_sfdx_test_run_output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forcedotcom/LightningTestingService/fde6caccd834a29d3b9b671df3c10c5d53fe7033/doc-resources/lts_sfdx_test_run_output.png -------------------------------------------------------------------------------- /doc-resources/lts_test_suite_page_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forcedotcom/LightningTestingService/fde6caccd834a29d3b9b671df3c10c5d53fe7033/doc-resources/lts_test_suite_page_success.png -------------------------------------------------------------------------------- /doc-resources/metadata-visualization-and-runtime-flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forcedotcom/LightningTestingService/fde6caccd834a29d3b9b671df3c10c5d53fe7033/doc-resources/metadata-visualization-and-runtime-flowchart.png -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egApplicationEvent/egApplicationEvent.auradoc: -------------------------------------------------------------------------------- 1 | 2 | 3 |

An application-level event for use in example components.

4 |
5 |
6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egApplicationEvent/egApplicationEvent.evt: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egApplicationEvent/egApplicationEvent.evt-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egAttributeTypes/egAttributeTypes.auradoc: -------------------------------------------------------------------------------- 1 | 2 | 3 |

An example component with attributes of various types.

4 |
5 |
6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egAttributeTypes/egAttributeTypes.cmp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Attribute Values 16 | 17 |

String: {!v.stringAtr}

18 |

Integer: {!v.integerAtr}

19 |

Boolean: {!v.booleanAtr}

20 |

Date: {!v.dateAtr}

21 |

Sobject: {!v.sobjectAtr.sObjectType}

22 |

Account: {!v.accountAtr.sObjectType},{!v.accountAtr.Name}

23 |

Object: {!v.objectAtrStringified}

24 |

Aura.Component[]: {!v.AuraComponentAtr}

25 |
26 |
-------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egAttributeTypes/egAttributeTypes.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egAttributeTypes/egAttributeTypesController.js: -------------------------------------------------------------------------------- 1 | ({ 2 | init : function(component, event, helper) { 3 | component.set("v.objectAtrStringified", JSON.stringify(component.get("v.objectAtr"))); 4 | } 5 | }) -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egClientSideAction/egClientSideAction.auradoc: -------------------------------------------------------------------------------- 1 | 2 | 3 |

An example component that updates the user interface based on processing 4 | done by a client-side action.

5 |
6 |
7 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egClientSideAction/egClientSideAction.cmp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 |
    10 | 11 |
  • {!accountName}
  • 12 |
    13 |
14 |
15 |
-------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egClientSideAction/egClientSideAction.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egClientSideAction/egClientSideActionController.js: -------------------------------------------------------------------------------- 1 | ({ 2 | search : function(component, event, helper) { 3 | var searchString = component.get("v.searchString"); 4 | component.set("v.accountList", helper.search(searchString)); 5 | } 6 | }) -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egClientSideAction/egClientSideActionHelper.js: -------------------------------------------------------------------------------- 1 | ({ 2 | search : function(searchString) { 3 | var aList = []; 4 | for(var i = 0; i<10; i++){ 5 | aList.push(searchString+" "+(Math.random() * (500 - 1) + 1)); 6 | } 7 | return aList; 8 | } 9 | }) -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egComponentEvent/egComponentEvent.auradoc: -------------------------------------------------------------------------------- 1 | 2 | 3 |

A component-level event for use in example components.

4 |
5 |
6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egComponentEvent/egComponentEvent.evt: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egComponentEvent/egComponentEvent.evt-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egComponentMethod/egComponentMethod.auradoc: -------------------------------------------------------------------------------- 1 | 2 | 3 |

An example component that exposes a method on component's public API. 4 | The method updates a component attribute when called.

5 |
6 |
7 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egComponentMethod/egComponentMethod.cmp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {!v.status} 5 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egComponentMethod/egComponentMethod.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egComponentMethod/egComponentMethodController.js: -------------------------------------------------------------------------------- 1 | ({ 2 | sampleMethodHandler : function(component, event, helper) { 3 | component.set("v.status", "sampleMethod invoked"); 4 | } 5 | }) -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egConditionalUI/egConditionalUI.auradoc: -------------------------------------------------------------------------------- 1 | 2 | 3 |

An example component that conditionally renders text to its body (and 4 | the DOM). Visibility can be toggled via a button that references a 5 | client-side action.

6 |
7 |
8 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egConditionalUI/egConditionalUI.cmp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
Salesforce
6 | 7 |
Lightning
8 |
9 |
10 |
-------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egConditionalUI/egConditionalUI.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egConditionalUI/egConditionalUIController.js: -------------------------------------------------------------------------------- 1 | ({ 2 | toggle : function(component, event, helper) { 3 | 4 | component.set("v.truthy", !component.get("v.truthy")); 5 | } 6 | }) -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egEventHandling/egEventHandling.auradoc: -------------------------------------------------------------------------------- 1 | 2 | 3 |

An example component that handles events: one application-level event and 4 | one component-level event.

5 |
6 |
7 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egEventHandling/egEventHandling.cmp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {! v.message} 7 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egEventHandling/egEventHandling.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egEventHandling/egEventHandlingController.js: -------------------------------------------------------------------------------- 1 | ({ 2 | handleEvent : function(component, event, helper) { 3 | component.set("v.message", event.getParam("data")); 4 | } 5 | }) -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egFacet/egFacet.auradoc: -------------------------------------------------------------------------------- 1 | 2 | 3 |

An example component for a test of facets. It passes text content into a facet of the component it wraps (c:egAttributeTypes).

4 |
5 |
6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egFacet/egFacet.cmp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {!v.content} 6 | 7 | 8 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egFacet/egFacet.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egGlobalValueProvider/egGlobalValueProvider.auradoc: -------------------------------------------------------------------------------- 1 | 2 | 3 |

An example component that outputs a custom label using the $Label global 4 | value provider.

5 |
6 |
7 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egGlobalValueProvider/egGlobalValueProvider.cmp: -------------------------------------------------------------------------------- 1 | 2 | Helloooooo 3 |
{! $Label.c.greeting}
4 |
-------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egGlobalValueProvider/egGlobalValueProvider.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egLdsView/egLdsView.auradoc: -------------------------------------------------------------------------------- 1 | 2 | 3 |

A complex component with extensive opportunities for testing.

4 | 5 |

This is a presentation component that contains view logic without a direct dependency on Lighting Data Service (LDS). This pattern explores ways to decouple presentation logic from server/integration logic.

6 | 7 |

Some of the decoupling illustrated is necessary because LDS can currently only be used inside Lightning Experience. (This is a documented limitation of the LDS beta.)

8 |
9 |
10 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egLdsView/egLdsView.cmp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |

Lightning Data Service Example

19 | 20 | 22 | 23 | 24 | 26 | 27 |

Account Name:

28 |

Account Industry:

29 |

Account Type:

30 |
31 |
32 | 33 | 34 |
35 |

36 |
37 |
38 |
39 |
40 |

41 | 42 |
-------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egLdsView/egLdsView.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egLdsView/egLdsViewController.js: -------------------------------------------------------------------------------- 1 | ({ 2 | getNewRecord : function(cmp, event, helper) { 3 | helper.getNewRecord(cmp); 4 | }, 5 | 6 | reloadRecord : function(cmp, event, helper) { 7 | helper.reloadRecord(cmp); 8 | }, 9 | 10 | saveRecord : function(cmp, event, helper) { 11 | helper.saveRecord(cmp); 12 | }, 13 | 14 | deleteRecord : function(cmp, event, helper) { 15 | helper.deleteRecord(cmp); 16 | }, 17 | 18 | handleRecordUpdate : function(cmp, event) { 19 | //handle the recordUpdated event 20 | var changeType = event.getParam("changeType"); 21 | if(changeType === "LOADED") { 22 | // handle record loaded 23 | cmp.set("v.logMessage", "Record has been loaded."); 24 | } else if(changeType === "CHANGED") { 25 | // handle record changed 26 | cmp.set("v.logMessage", "Record has been changed."); 27 | } else if(changeType === "REMOVED") { 28 | // handle record removed 29 | cmp.set("v.logMessage", "Record has been removed."); 30 | } else if(changeType === "ERROR") { 31 | // handle error while loading|saving|deleting record 32 | cmp.set("v.logMessage", "There is some error while loading/updating record."); 33 | } else { 34 | // you should not get any other type than these 4 (as of Summer '17) 35 | } 36 | } 37 | }) -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egLdsView/egLdsViewHelper.js: -------------------------------------------------------------------------------- 1 | ({ 2 | getNewRecord : function(cmp) { 3 | var recordDataCreate = cmp.find("recordDataCreate"); 4 | // call getNewRecord of force:recordData 5 | recordDataCreate.getNewRecord("Account", null, true, function() { 6 | // once we have the record template for new record for given entity then update and save the record 7 | var record = cmp.get('v.recordTemplate'); 8 | record.fields.Name.value = "ADSTestAccount" + (Math.round(Math.random()*10000) + 1); 9 | record.fields.Industry.value = "Banking"; 10 | record.fields.Type.value = "Prospect"; 11 | recordDataCreate.saveRecord(function(saveResult) { 12 | if (saveResult.state === 'SUCCESS') { 13 | cmp.set("v._recordId", saveResult.recordId); 14 | cmp.set('v.fetch', "true"); // this will load the Account record using force:recordData and renders it 15 | cmp.set("v.isCallbackCalled", true); 16 | } 17 | }); 18 | }); 19 | }, 20 | 21 | reloadRecord : function(cmp) { 22 | cmp.find("recordDataCmp").reloadRecord(false, function(){ 23 | // verify record is reloaded. callback will be called after recordUpdated event is fired 24 | cmp.set("v.isCallbackCalled", true); 25 | }); 26 | }, 27 | 28 | saveRecord : function(cmp) { 29 | var recordDataCmp = cmp.find("recordDataCmp"); 30 | recordDataCmp.saveRecord(function(){ 31 | // verify record is saved. callback will be called after recordUpdated event is fired 32 | cmp.set("v.mode", "VIEW"); 33 | cmp.find("recordDataCmp").reloadRecord(false, function(){ 34 | cmp.set("v.isCallbackCalled", true); 35 | }); 36 | }); 37 | }, 38 | 39 | deleteRecord : function(cmp) { 40 | cmp.find("recordDataCmp").deleteRecord(function(){ 41 | // verify record is deleted. callback will be called after recordUpdated event is fired 42 | cmp.set('v.fetch', "false"); 43 | cmp.set("v.isCallbackCalled", true); 44 | }); 45 | } 46 | }) -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egRenderElement/egRenderElement.auradoc: -------------------------------------------------------------------------------- 1 | 2 | 3 |

An example component that's very simple. 4 | It renders static text to the DOM.

5 |
6 |
7 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egRenderElement/egRenderElement.cmp: -------------------------------------------------------------------------------- 1 | 2 |
Hello World!
3 |
-------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egRenderElement/egRenderElement.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egServerSideActionCallback/egServerSideActionCallback.auradoc: -------------------------------------------------------------------------------- 1 | 2 | 3 |

An example component that calls a server-side action, and which also 4 | exposes the action callback as a method that can be called directly.

5 |
6 |
7 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egServerSideActionCallback/egServerSideActionCallback.cmp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 |
    17 | 18 |
  • {!account.Name}
  • 19 |
    20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egServerSideActionCallback/egServerSideActionCallback.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egServerSideActionCallback/egServerSideActionCallbackController.js: -------------------------------------------------------------------------------- 1 | ({ 2 | search : function(component, event, helper) { 3 | //var searchString = component.get("v.searchString"); 4 | helper.search(component); 5 | }, 6 | 7 | searchCallback : function(component, event, helper) { 8 | helper.handleSearchAccountCallback(component, event.getParam('arguments').response); 9 | } 10 | }) -------------------------------------------------------------------------------- /lightning-component-tests/main/default/aura/egServerSideActionCallback/egServerSideActionCallbackHelper.js: -------------------------------------------------------------------------------- 1 | ({ 2 | search : function(cmp) { 3 | var action = cmp.get("c.searchAccounts"); 4 | this.setupSearchAction(cmp, action); 5 | $A.enqueueAction(action); 6 | }, 7 | 8 | 9 | setupSearchAction : function(cmp, action){ 10 | action.setParams({ searchString : cmp.get("v.searchString") }); 11 | action.setCallback(this, function(response){this.handleSearchAccountCallback(cmp, response)}); 12 | }, 13 | 14 | handleSearchAccountCallback : function(cmp, response) { 15 | var state = response.getState(); 16 | if (state === "SUCCESS") { 17 | cmp.set("v.accountList", response.getReturnValue()); 18 | } 19 | else if (state === "INCOMPLETE") { 20 | $A.log("Action INCOMPLETE"); 21 | } 22 | else if (state === "ERROR") { 23 | var errors = response.getError(); 24 | if (errors) { 25 | if (errors[0] && errors[0].message) { 26 | $A.log("Error message: " + 27 | errors[0].message); 28 | } 29 | } else { 30 | $A.log("Unknown error"); 31 | } 32 | } 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/classes/egServerSideActionController.cls: -------------------------------------------------------------------------------- 1 | public with sharing class egServerSideActionController { 2 | @AuraEnabled 3 | public static account[] searchAccounts(String searchString) { 4 | 5 | // Calling server may result in data dependent undertministic tests. 6 | // Therefore, its a best practice to mock server side calls. 7 | // For demosntration purpose, this apex action just returns a static list. 8 | 9 | /* 10 | searchString = '%'+searchString+'%'; 11 | Account[] searchList = [SELECT Id, Name FROM Account WHERE Name LIKE :searchString]; 12 | */ 13 | 14 | List accts = new List(); 15 | accts.add(new Account(name=searchString + '-1')); 16 | accts.add(new Account(name=searchString + '-2')); 17 | accts.add(new Account(name=searchString + '-3')); 18 | return accts; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/classes/egServerSideActionController.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 40.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/main/default/labels/CustomLabels.labels-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | greeting 5 | en_US 6 | true 7 | greeting 8 | Aloha! 9 | 10 | 11 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/aura/jasmineTests/jasmineTests.app: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/aura/jasmineTests/jasmineTests.app-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 41.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/aura/jasmineTests/jasmineTests.auradoc: -------------------------------------------------------------------------------- 1 | 2 | An example test application that passes jasmine test spec files to LTS test Runner 3 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/aura/lts_jasmineRunner/lts_jasmineRunner.auradoc: -------------------------------------------------------------------------------- 1 | 2 | 3 | Base Component that pulls in jasmine, test reporters and test specs and manages the test run. 4 | Test applications can be created by using this component. 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/aura/lts_jasmineRunner/lts_jasmineRunner.cmp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 |
9 | 10 | 11 | 22 | 23 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/aura/lts_jasmineRunner/lts_jasmineRunner.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 41.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/aura/lts_jasmineRunner/lts_jasmineRunnerController.js: -------------------------------------------------------------------------------- 1 | ({ 2 | runTests : function(component, event, helper) { 3 | $T._setContexualRunner($A.getCallback(function(callback){callback();})); 4 | $T._sfdxReportForJasmine(jasmine); 5 | jasmine.lightningIntegration.execute(); 6 | } 7 | }) -------------------------------------------------------------------------------- /lightning-component-tests/test/default/aura/lts_mochaRunner/lts_mochaRunner.auradoc: -------------------------------------------------------------------------------- 1 | 2 | 3 | Base Component that pulls in mocha, test reporters and test specs and manages the test run. 4 | Test applications can be created by using this component. 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/aura/lts_mochaRunner/lts_mochaRunner.cmp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 |
9 | 10 | 11 | 21 | 22 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/aura/lts_mochaRunner/lts_mochaRunner.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 41.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/aura/lts_mochaRunner/lts_mochaRunnerController.js: -------------------------------------------------------------------------------- 1 | ({ 2 | runTests: function (component, event, helper) { 3 | $T._setContexualRunner($A.getCallback(function (callback) { callback(); })); 4 | var mochaRunner = mocha.run(); 5 | $T._sfdxReportForMocha(mochaRunner); 6 | } 7 | }) -------------------------------------------------------------------------------- /lightning-component-tests/test/default/aura/mochaTests/mochaTests.app: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/aura/mochaTests/mochaTests.app-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 41.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/aura/mochaTests/mochaTests.auradoc: -------------------------------------------------------------------------------- 1 | 2 | An example test application that passes mocha test spec files to LTS test Runner 3 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/jasmineExampleTests.resource: -------------------------------------------------------------------------------- 1 | /** 2 | * This test suite contains examples that illustrate reusable patterns for testing 3 | * your custom Lightning components. 4 | * 5 | * These tests are written using the [Jasmine framework](https://jasmine.github.io/2.1/introduction). 6 | * They're run in the Lightning Testing Service using a wrapper, which you can find 7 | * in jasmineboot.js, in the same repository as this test suite. 8 | * 9 | * Note that Jasmine uses "spec" as its name for a test. We use their terminology here 10 | * for consistency with their documentation. 11 | */ 12 | describe("Lightning Component Testing Examples", function(){ 13 | afterEach(function() { 14 | // Each spec (test) renders its components into the same div, 15 | // so we need to clear that div out at the end of each spec. 16 | $T.clearRenderedTestComponents(); 17 | }); 18 | 19 | /** 20 | * Component under test: 'c:egRenderElement': 21 | * This spec creates a component, adds it to the body, waits for the rendering to complete, 22 | * and then ensures that the expected content has been added to the DOM. 23 | * NOTE: The spec and the component under test are in same locker (same namespace), 24 | * so the spec is able to see the DOM owned by the component. 25 | */ 26 | describe('c:egRenderElement', function(){ 27 | // We encourage you to have the code for c:egRenderElement side by side 28 | // when reading through this spec. 29 | it('renders specific static text', function(done) { 30 | // Instantiate and render the c:egRenderElement Lightning component into the renderInto element. 31 | // The second parameter (empty here) is the list of component attribute values to set. 32 | // The third parameter, requiresRendering, is set to true. 33 | $T.createComponent("c:egRenderElement", {}, true) 34 | // The 'component' here is the instance of c:egRenderElement 35 | .then(function(component) { 36 | expect(document.getElementById("content").textContent).toContain("Hello World!"); 37 | // end this spec successfully 38 | done(); 39 | }).catch(function(e) { 40 | // end this spec as a failure 41 | done.fail(e); 42 | }); 43 | }); 44 | }); 45 | 46 | /** 47 | * Component under test: 'c:egComponentMethod' 48 | * This spec validates that calling a method on the component's public interface 49 | * causes the expected state change. 50 | */ 51 | describe('c:egComponentMethod', function() { 52 | it("updates an attribute value when a method is invoked on the component's interface", function(done) { 53 | $T.createComponent("c:egComponentMethod", null) 54 | .then(function(component) { 55 | component.sampleMethod(); 56 | expect(component.get("v.status")).toBe("sampleMethod invoked"); 57 | done(); 58 | }).catch(function(e) { 59 | done.fail(e); 60 | }); 61 | }); 62 | }); 63 | 64 | /** 65 | * Component under test: 'c:egClientSideAction' 66 | * This spec sets an attribute value, invokes a method, and then validates that 67 | * the interaction results in expected state and rendering updates. 68 | */ 69 | describe('c:egClientSideAction', function(){ 70 | it('adds items to a list when the client-side action is invoked', function(done) { 71 | $T.createComponent("c:egClientSideAction", {}, true) 72 | .then(function(component){ 73 | component.set("v.searchString", "salesforce"); 74 | expect(component.find("accountList").getElement().children.length).toBe(0); 75 | // Invoke function in component's client-side controller through aura:method 76 | component.searchAccounts(); 77 | // Assert results of using the component's function (public interface) 78 | expect(component.get("v.accountList").length).toBe(10); 79 | expect(component.get("v.accountList")[0]).toContain("salesforce"); 80 | // Assert results by checking DOM element owned by the namespace 81 | expect(component.find("accountList").getElement().children.length).toBe(10); 82 | done(); 83 | 84 | }).catch(function(e) { 85 | done.fail(e); 86 | }); 87 | }); 88 | }); 89 | 90 | /** 91 | * Component under test: 'c:egServerSideActionCallback' 92 | * This spec shows how to validate a server-side action end-to-end. The spec interacts with the component 93 | * to cause a server-side action to be invoked, and then waits for the action callback to update 94 | * component state as expected. 95 | * This technique (actually connecting to the server) is discouraged because 96 | * invoking server actions is time consuming (the spec can timeout), and can have side effects. 97 | */ 98 | describe('c:egServerSideActionCallback', function(){ 99 | it('[Discouraged: brittle, slow, side-effects] receives server data when server-side action is invoked', function(done) { 100 | $T.createComponent("c:egServerSideActionCallback", {}, true) 101 | .then(function(component){ 102 | component.set("v.searchString", "United"); 103 | expect(component.find("accountList").getElement().children.length).toBe(0); 104 | $T.run(component.search); 105 | return $T.waitFor(function(){ 106 | return component.get("v.accountList").length === 3; 107 | }) 108 | }).then(function() { 109 | done(); 110 | }).catch(function(e) { 111 | done.fail(e); 112 | }); 113 | }); 114 | }); 115 | 116 | /** 117 | * Component under test: 'c:egServerSideActionCallback' 118 | * This spec shows how server-side action callbacks can be validated in isolation by exposing the logic of the 119 | * callback via a component method, and then having the spec use the callback directly for interaction 120 | */ 121 | describe('c:egServerSideActionCallback', function() { 122 | it('updates with provided data when action callback is invoked directly', function(done) { 123 | $T.createComponent("c:egServerSideActionCallback", {}, true) 124 | .then(function(component){ 125 | // Mocking out the server-side action response 126 | var res = {getState : function(){return "SUCCESS";}, getReturnValue: function(){return [{"Name":"Acct 1"},{"Name":"Acct 2"}];}}; 127 | component.searchAccounts(res); 128 | // Assert using components interface 129 | expect(component.get("v.accountList").length).toBe(2); 130 | expect(component.get("v.accountList")[0]['Name']).toContain("Acct 1"); 131 | // Assert using DOM element owned by the namespace 132 | expect(component.find("accountList").getElement().children.length).toBe(2); 133 | done(); 134 | }).catch(function(e) { 135 | done.fail(e); 136 | }); 137 | }); 138 | }); 139 | 140 | /** 141 | * Component under test: 'c:egServerSideActionCallback' 142 | * This spec shows how server-side action callbacks could be validated by mocking the server-side actions using Jasmine spies. 143 | * This approach relies on Jasmine's mocking capabilities and requires you to construct the server responses yourself. 144 | * But it eliminates the need for restructuring component production code for testability. 145 | */ 146 | describe('c:egServerSideActionCallback', function() { 147 | it('updates with provided data when invoked via a mocked server action using Jasmine spies', function(done) { 148 | $T.createComponent("c:egServerSideActionCallback", {}, true) 149 | .then(function(component){ 150 | var res = {getState : function(){return "SUCCESS";}, getReturnValue: function(){return [{"Name":"Acct 1"},{"Name":"Acct 2"}];}}; 151 | spyOn($A, "enqueueAction").and.callFake(function(action) { 152 | var cb = action.getCallback("SUCCESS") 153 | cb.fn.apply(cb.s, [res]); 154 | }); 155 | component.search(); 156 | // Assert using components interface 157 | expect(component.get("v.accountList").length).toBe(2); 158 | expect(component.get("v.accountList")[0]['Name']).toContain("Acct 1"); 159 | // Assert using DOM element owned by the namespace 160 | expect(component.find("accountList").getElement().children.length).toBe(2); 161 | done(); 162 | }).catch(function(e) { 163 | done.fail(e); 164 | }); 165 | }); 166 | }); 167 | 168 | /** 169 | * Component under test: 'c:egAttributeTypes' 170 | * This spec shows how to interact with various attribute types from a test spec. 171 | */ 172 | describe('c:egAttributeTypes', function() { 173 | it('sets component attributes of various types and values', function(done) { 174 | var attributes = { 175 | stringAtr:"string value", 176 | integerAtr:20, 177 | dateAtr:new Date(), 178 | sobjectAtr:{"sObjectType":"Contact", "FirstName":"Marc" , "LastName":"Benioff"}, 179 | accountAtr:{"sObjectType":"Account", "Name":"salesforce"}, 180 | objectAtr:{"key1":"value1"} 181 | }; 182 | $T.createComponent("c:egAttributeTypes", attributes, true) 183 | .then(function(component){ 184 | expect(component.find("stringAtrAuraId").getElement().innerHTML).toContain(attributes.stringAtr); 185 | expect(component.get("v.integerAtr")).toBe(attributes.integerAtr); 186 | expect(component.get("v.objectAtrStringified")).toBe(JSON.stringify(attributes.objectAtr)); 187 | done(); 188 | }).catch(function(e) { 189 | done.fail(e); 190 | }); 191 | }); 192 | }); 193 | 194 | /** 195 | * Component under test: 'c:egFacet' 196 | * This spec shows how to interact with facets from a test spec. Note the use of component IDs. 197 | */ 198 | describe('c:egFacet', function(){ 199 | var attributes = {"content":"textContent"}; 200 | it('renders the expected content when used as a facet', function(done) { 201 | $T.createComponent("c:egFacet", attributes, true) 202 | .then(function(component){ 203 | expect(component.find("cmpUnderTest").find("AuraComponentAtrId").getElement().innerHTML).toContain(attributes.content); 204 | done(); 205 | }).catch(function(e) { 206 | done.fail(e); 207 | }); 208 | }); 209 | }); 210 | 211 | /** 212 | * Component under test: 'c:egGlobalValueProvider' 213 | * This spec shows how to interact with global value providers by programmatically 214 | * getting a reference to a custom label and validating that its value is being 215 | * displayed by the component. 216 | */ 217 | describe('c:egGlobalValueProvider', function(){ 218 | it('renders a custom label', function(done) { 219 | $T.createComponent("c:egGlobalValueProvider", {}, true) 220 | .then(function(component){ 221 | // You can reference a custom label in your spec using standard 222 | // Lightning component JavaScript APIs 223 | var greetingLabelVal = $A.get("$Label.c.greeting"); 224 | expect(greetingLabelVal).toBeTruthy(greetingLabelVal); 225 | expect(component.find("greeting").getElement().textContent).toBe(greetingLabelVal); 226 | done(); 227 | }).catch(function(e) { 228 | done.fail(e); 229 | }); 230 | }); 231 | }); 232 | 233 | /** 234 | * Component under test: 'c:egConditionalUI' 235 | * This spec shows to validate conditional UI by using the component's interface. 236 | */ 237 | describe('c:egConditionalUI', function() { 238 | it('renders only the "truthy" (conditional) portion of the user interface', function(done) { 239 | $T.createComponent("c:egConditionalUI", null) 240 | .then(function(component) { 241 | expect(component.find("trueDiv")).toBeTruthy(); 242 | expect(component.find("falseDiv")).toBeFalsy(); 243 | component.find("toggleButton").getEvent("press").fire(); 244 | expect(component.find("trueDiv")).toBeFalsy(); 245 | expect(component.find("falseDiv")).toBeTruthy(); 246 | done(); 247 | }).catch(function(e) { 248 | done.fail(e); 249 | }); 250 | }); 251 | }); 252 | 253 | /** 254 | * Component under test: 'c:egEventHandling' 255 | * This spec shows how to validate a component's handling of component- and 256 | * application-level events. 257 | */ 258 | describe('c:egEventHandling', function() { 259 | it('handles component- and application-level events', function(done) { 260 | $T.createComponent("c:egEventHandling", null) 261 | .then(function(component){ 262 | var cmpEvent = component.getEvent("sampleEvent"); 263 | cmpEvent.setParams({"data":"component event fired"}); 264 | cmpEvent.fire() 265 | expect(component.get("v.message")).toBe("component event fired"); 266 | $T.fireApplicationEvent("c:egApplicationEvent", {"data":"application event fired"}); 267 | expect(component.get("v.message")).toBe("application event fired"); 268 | done(); 269 | }).catch(function(e) { 270 | done.fail(e); 271 | }); 272 | }); 273 | }); 274 | }); 275 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/jasmineExampleTests.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | application/javascript 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/jasmineHelloWorldTests.resource: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a 'hello world' jasmine test spec file. 3 | * These examples show the "shape" of a Jasmine spec. 4 | */ 5 | describe("A simple passing test", function() { 6 | it("verifies that true is always true", function() { 7 | expect(true).toBe(true); 8 | }); 9 | }); 10 | 11 | describe("A simple failing test", function() { 12 | xit("fails when false does not equal true", function() { 13 | // Note: Use xit() instead of it() to temporarily disable a test 14 | expect(true).toBe(false); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/jasmineHelloWorldTests.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | application/javascript 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/jasmineLightningDataServiceTests.resource: -------------------------------------------------------------------------------- 1 | /** 2 | * This test suite contains examples that illustrate testing custom Lightning 3 | * components that use 4 | * [Lightning Data Service](https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/data_service.htm) 5 | * (LDS). 6 | * 7 | * It also demonstrates how to test components with server-side interaction, and 8 | * handling Lightning component events. 9 | * 10 | * Component under these tests: 'c:egLdsView' 11 | * 12 | */ 13 | describe('The Lightning Data Service Examples', function(){ 14 | 15 | describe('c:egLdsView', function(){ 16 | // These tests are making server-side calls and interacting with LDS events, 17 | // so increase the default timeout to reduce tests flappiness 18 | var originalTimeout; 19 | var defaultTimeout = 10000; 20 | 21 | beforeEach(function(done) { 22 | originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; 23 | jasmine.DEFAULT_TIMEOUT_INTERVAL = defaultTimeout; 24 | 25 | // Create a record in advance, for use with the Save, Reload and Delete actions 26 | var that = this; 27 | $T.createComponent("c:egLdsView", {}, true) 28 | .then(function(component){ 29 | that.component = component; 30 | component.set("v.isCallbackCalled", false); // the callback function will set this to true once it's called 31 | component.getNewRecord(); 32 | return $T.waitFor(function(){ 33 | return component.get("v.isCallbackCalled") === true && component.get("v.record") != null; 34 | }, defaultTimeout); 35 | }).then(function() { 36 | done(); 37 | }).catch(function(e) { 38 | done.fail(e); 39 | }); 40 | }); 41 | 42 | afterEach(function() { 43 | // Each spec (test) renders its components into the same div, 44 | // so we need to clear that div out at the end of each spec. 45 | $T.clearRenderedTestComponents(); 46 | jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout; 47 | }); 48 | 49 | /** 50 | * This test checks that the force:recordData create method works, and that LDS 51 | * is notifying our component with the added/updated data. 52 | */ 53 | it('updates the component and loads record data when a record is created', function() { 54 | // createRecord method in helper is creating record with ADSTestAccount 55 | // using force:recordData; the record should be loaded after save 56 | expect(this.component.find("accName").get("v.value")).toContain("ADSTestAccount"); 57 | expect(this.component.find("logMessage").get("v.value")).toBe("Record has been loaded."); 58 | }); 59 | 60 | 61 | /** 62 | * This test checks that reload record is loading the record and that the 63 | * component is notified using the recordUpdated event. 64 | */ 65 | it('loads a record and logs a success message when a record is reloaded', function(done) { 66 | var that = this; 67 | that.component.set("v.isCallbackCalled", false); // the callback function will set this to true once it's called 68 | that.component.reloadRecord(); 69 | return $T.waitFor(function(){ 70 | return that.component.get("v.isCallbackCalled") === true; 71 | }, defaultTimeout).then(function() { 72 | expect(that.component.find("logMessage").get("v.value")).toBe("Record has been loaded."); 73 | done(); 74 | }).catch(function(e) { 75 | done.fail(e); 76 | }); 77 | }); 78 | 79 | /** 80 | * This test checks that the component is notified with new record data when the 81 | * record is saved with force:recordData. 82 | */ 83 | it('updates data held in the client-side component when a record is saved', function(done) { 84 | var that = this; 85 | that.component.set("v.isCallbackCalled", false); // the callback function will set this to true once it's called 86 | // load the record in edit mode, change the value and save the record 87 | that.component.set("v.mode", "EDIT"); 88 | that.component.reloadRecord(); 89 | return $T.waitFor(function(){ 90 | return that.component.get("v.isCallbackCalled") === true; 91 | }, defaultTimeout).then(function(){ 92 | // update the record value 93 | var record = that.component.get("v.record"); 94 | record.fields.Name.value = "UpdatedRecordNameFromTest"; 95 | that.component.set("v.isCallbackCalled", false); 96 | that.component.saveRecord(); 97 | return $T.waitFor(function(){ 98 | return that.component.get("v.isCallbackCalled") === true; 99 | }, defaultTimeout).then(function() { 100 | expect(that.component.find("accName").get("v.value")).toBe("UpdatedRecordNameFromTest"); 101 | done(); 102 | }).catch(function(e) { 103 | done.fail(e); 104 | }); 105 | }).catch(function(e) { 106 | done.fail(e); 107 | });; 108 | 109 | }); 110 | 111 | /** 112 | * This test checks that the component is notified when the record is deleted 113 | * (and removed from cache). 114 | */ 115 | it('updates the component with a message when a record is deleted', function(done) { 116 | var that = this; 117 | that.component.set("v.isCallbackCalled", false); // the callback function will set this to true once it's called 118 | that.component.deleteRecord(); 119 | return $T.waitFor(function(){ 120 | return that.component.get("v.isCallbackCalled") === true; 121 | }, defaultTimeout).then(function() { 122 | expect(that.component.find("logMessage").get("v.value")).toBe("Record has been removed."); 123 | done(); 124 | }).catch(function(e) { 125 | done.fail(e); 126 | }); 127 | }); 128 | 129 | /** 130 | * This test checks that the component is notified with a change notification of 131 | * type ERROR, with the expected error message, when there is an error during 132 | * CRUD operations with LDS. 133 | */ 134 | it('handles errors with a message when there\'s an error with data access', function() { 135 | var that = this; 136 | // LDS fires an event with ERROR type when there's an actual error 137 | // This tests UI behavior by mocking the event 138 | that.component.find("recordDataCmp").getEvent("recordUpdated").setParams({"changeType":"ERROR"}).fire(); 139 | expect(that.component.find("logMessage").get("v.value")).toBe("There is some error while loading/updating record."); 140 | }); 141 | }); 142 | }); 143 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/jasmineLightningDataServiceTests.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | application/javascript 5 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/lts_chai.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | application/javascript 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/lts_jasmine.resource: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forcedotcom/LightningTestingService/fde6caccd834a29d3b9b671df3c10c5d53fe7033/lightning-component-tests/test/default/staticresources/lts_jasmine.resource -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/lts_jasmine.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | application/zip 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/lts_jasmineboot.resource: -------------------------------------------------------------------------------- 1 | /** 2 | Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project. 3 | 4 | If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms. 5 | 6 | The location of `boot.js` can be specified and/or overridden in `jasmine.yml`. 7 | 8 | [jasmine-gem]: http://github.com/pivotal/jasmine-gem 9 | */ 10 | 11 | (function() { 12 | 13 | /** 14 | * ## Require & Instantiate 15 | * 16 | * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference. 17 | */ 18 | window.jasmine = jasmineRequire.core(jasmineRequire); 19 | 20 | /** 21 | * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference. 22 | */ 23 | jasmineRequire.html(jasmine); 24 | 25 | /** 26 | * Create the Jasmine environment. This is used to run all specs in a project. 27 | */ 28 | var env = jasmine.getEnv(); 29 | 30 | /** 31 | * ## The Global Interface 32 | * 33 | * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged. 34 | */ 35 | var jasmineInterface = jasmineRequire.interface(jasmine, env); 36 | 37 | /** 38 | * Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`. 39 | */ 40 | extend(window, jasmineInterface); 41 | 42 | /** 43 | * ## Runner Parameters 44 | * 45 | * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface. 46 | */ 47 | 48 | var queryString = new jasmine.QueryString({ 49 | getWindowLocation: function() { return window.location; } 50 | }); 51 | 52 | var catchingExceptions = queryString.getParam("catch"); 53 | env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); 54 | 55 | var throwingExpectationFailures = queryString.getParam("throwFailures"); 56 | env.throwOnExpectationFailure(throwingExpectationFailures); 57 | 58 | var random = queryString.getParam("random"); 59 | env.randomizeTests(random); 60 | 61 | var seed = queryString.getParam("seed"); 62 | if (seed) { 63 | env.seed(seed); 64 | } 65 | 66 | /** 67 | * ## Reporters 68 | * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any). 69 | */ 70 | var testResultsElementId = 'jasmineHtmlTestResults'; 71 | if(!document.getElementById(testResultsElementId)){ 72 | var resultsDiv = document.createElement("div"); 73 | resultsDiv.id = testResultsElementId; 74 | document.body.appendChild(resultsDiv); 75 | } 76 | var htmlReporter = new jasmine.HtmlReporter({ 77 | env: env, 78 | onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); }, 79 | onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); }, 80 | onRandomClick: function() { queryString.navigateWithNewParam("random", !env.randomTests()); }, 81 | addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); }, 82 | getContainer: function() { return document.getElementById(testResultsElementId); }, 83 | createElement: function() { return document.createElement.apply(document, arguments); }, 84 | createTextNode: function() { return document.createTextNode.apply(document, arguments); }, 85 | timer: new jasmine.Timer() 86 | }); 87 | 88 | /** 89 | * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript. 90 | */ 91 | env.addReporter(jasmineInterface.jsApiReporter); 92 | env.addReporter(htmlReporter); 93 | 94 | /** 95 | * Filter which specs will be run by matching the start of the full name against the `spec` query param. 96 | */ 97 | var specFilter = new jasmine.HtmlSpecFilter({ 98 | filterString: function() { return queryString.getParam("spec"); } 99 | }); 100 | 101 | env.specFilter = function(spec) { 102 | return specFilter.matches(spec.getFullName()); 103 | }; 104 | 105 | 106 | 107 | /** 108 | * Helper function for readability above. 109 | */ 110 | function extend(destination, source) { 111 | for (var property in source) destination[property] = source[property]; 112 | return destination; 113 | } 114 | 115 | window.jasmine.lightningIntegration = { 116 | execute : function(){ 117 | htmlReporter.initialize(); 118 | jasmine.getEnv().execute(); 119 | } 120 | }; 121 | }()); -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/lts_jasmineboot.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | text/javascript 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/lts_mochaBootJs.resource: -------------------------------------------------------------------------------- 1 | (function (global) { 2 | mocha.setup({ ui: 'bdd' }); 3 | })(this); -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/lts_mochaBootJs.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | application/javascript 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/lts_mochaCss.resource: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | body { 4 | margin:0; 5 | } 6 | 7 | #mocha { 8 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 9 | margin: 60px 50px; 10 | } 11 | 12 | #mocha ul, 13 | #mocha li { 14 | margin: 0; 15 | padding: 0; 16 | } 17 | 18 | #mocha ul { 19 | list-style: none; 20 | } 21 | 22 | #mocha h1, 23 | #mocha h2 { 24 | margin: 0; 25 | } 26 | 27 | #mocha h1 { 28 | margin-top: 15px; 29 | font-size: 1em; 30 | font-weight: 200; 31 | } 32 | 33 | #mocha h1 a { 34 | text-decoration: none; 35 | color: inherit; 36 | } 37 | 38 | #mocha h1 a:hover { 39 | text-decoration: underline; 40 | } 41 | 42 | #mocha .suite .suite h1 { 43 | margin-top: 0; 44 | font-size: .8em; 45 | } 46 | 47 | #mocha .hidden { 48 | display: none; 49 | } 50 | 51 | #mocha h2 { 52 | font-size: 12px; 53 | font-weight: normal; 54 | cursor: pointer; 55 | } 56 | 57 | #mocha .suite { 58 | margin-left: 15px; 59 | } 60 | 61 | #mocha .test { 62 | margin-left: 15px; 63 | overflow: hidden; 64 | } 65 | 66 | #mocha .test.pending:hover h2::after { 67 | content: '(pending)'; 68 | font-family: arial, sans-serif; 69 | } 70 | 71 | #mocha .test.pass.medium .duration { 72 | background: #c09853; 73 | } 74 | 75 | #mocha .test.pass.slow .duration { 76 | background: #b94a48; 77 | } 78 | 79 | #mocha .test.pass::before { 80 | content: '✓'; 81 | font-size: 12px; 82 | display: block; 83 | float: left; 84 | margin-right: 5px; 85 | color: #00d6b2; 86 | } 87 | 88 | #mocha .test.pass .duration { 89 | font-size: 9px; 90 | margin-left: 5px; 91 | padding: 2px 5px; 92 | color: #fff; 93 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 94 | -moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 95 | box-shadow: inset 0 1px 1px rgba(0,0,0,.2); 96 | -webkit-border-radius: 5px; 97 | -moz-border-radius: 5px; 98 | -ms-border-radius: 5px; 99 | -o-border-radius: 5px; 100 | border-radius: 5px; 101 | } 102 | 103 | #mocha .test.pass.fast .duration { 104 | display: none; 105 | } 106 | 107 | #mocha .test.pending { 108 | color: #0b97c4; 109 | } 110 | 111 | #mocha .test.pending::before { 112 | content: '◦'; 113 | color: #0b97c4; 114 | } 115 | 116 | #mocha .test.fail { 117 | color: #c00; 118 | } 119 | 120 | #mocha .test.fail pre { 121 | color: black; 122 | } 123 | 124 | #mocha .test.fail::before { 125 | content: '✖'; 126 | font-size: 12px; 127 | display: block; 128 | float: left; 129 | margin-right: 5px; 130 | color: #c00; 131 | } 132 | 133 | #mocha .test pre.error { 134 | color: #c00; 135 | max-height: 300px; 136 | overflow: auto; 137 | } 138 | 139 | #mocha .test .html-error { 140 | overflow: auto; 141 | color: black; 142 | line-height: 1.5; 143 | display: block; 144 | float: left; 145 | clear: left; 146 | font: 12px/1.5 monaco, monospace; 147 | margin: 5px; 148 | padding: 15px; 149 | border: 1px solid #eee; 150 | max-width: 85%; /*(1)*/ 151 | max-width: -webkit-calc(100% - 42px); 152 | max-width: -moz-calc(100% - 42px); 153 | max-width: calc(100% - 42px); /*(2)*/ 154 | max-height: 300px; 155 | word-wrap: break-word; 156 | border-bottom-color: #ddd; 157 | -webkit-box-shadow: 0 1px 3px #eee; 158 | -moz-box-shadow: 0 1px 3px #eee; 159 | box-shadow: 0 1px 3px #eee; 160 | -webkit-border-radius: 3px; 161 | -moz-border-radius: 3px; 162 | border-radius: 3px; 163 | } 164 | 165 | #mocha .test .html-error pre.error { 166 | border: none; 167 | -webkit-border-radius: 0; 168 | -moz-border-radius: 0; 169 | border-radius: 0; 170 | -webkit-box-shadow: 0; 171 | -moz-box-shadow: 0; 172 | box-shadow: 0; 173 | padding: 0; 174 | margin: 0; 175 | margin-top: 18px; 176 | max-height: none; 177 | } 178 | 179 | /** 180 | * (1): approximate for browsers not supporting calc 181 | * (2): 42 = 2*15 + 2*10 + 2*1 (padding + margin + border) 182 | * ^^ seriously 183 | */ 184 | #mocha .test pre { 185 | display: block; 186 | float: left; 187 | clear: left; 188 | font: 12px/1.5 monaco, monospace; 189 | margin: 5px; 190 | padding: 15px; 191 | border: 1px solid #eee; 192 | max-width: 85%; /*(1)*/ 193 | max-width: -webkit-calc(100% - 42px); 194 | max-width: -moz-calc(100% - 42px); 195 | max-width: calc(100% - 42px); /*(2)*/ 196 | word-wrap: break-word; 197 | border-bottom-color: #ddd; 198 | -webkit-box-shadow: 0 1px 3px #eee; 199 | -moz-box-shadow: 0 1px 3px #eee; 200 | box-shadow: 0 1px 3px #eee; 201 | -webkit-border-radius: 3px; 202 | -moz-border-radius: 3px; 203 | border-radius: 3px; 204 | } 205 | 206 | #mocha .test h2 { 207 | position: relative; 208 | } 209 | 210 | #mocha .test a.replay { 211 | position: absolute; 212 | top: 3px; 213 | right: 0; 214 | text-decoration: none; 215 | vertical-align: middle; 216 | display: block; 217 | width: 15px; 218 | height: 15px; 219 | line-height: 15px; 220 | text-align: center; 221 | background: #eee; 222 | font-size: 15px; 223 | -webkit-border-radius: 15px; 224 | -moz-border-radius: 15px; 225 | border-radius: 15px; 226 | -webkit-transition:opacity 200ms; 227 | -moz-transition:opacity 200ms; 228 | -o-transition:opacity 200ms; 229 | transition: opacity 200ms; 230 | opacity: 0.3; 231 | color: #888; 232 | } 233 | 234 | #mocha .test:hover a.replay { 235 | opacity: 1; 236 | } 237 | 238 | #mocha-report.pass .test.fail { 239 | display: none; 240 | } 241 | 242 | #mocha-report.fail .test.pass { 243 | display: none; 244 | } 245 | 246 | #mocha-report.pending .test.pass, 247 | #mocha-report.pending .test.fail { 248 | display: none; 249 | } 250 | #mocha-report.pending .test.pass.pending { 251 | display: block; 252 | } 253 | 254 | #mocha-error { 255 | color: #c00; 256 | font-size: 1.5em; 257 | font-weight: 100; 258 | letter-spacing: 1px; 259 | } 260 | 261 | #mocha-stats { 262 | position: fixed; 263 | top: 15px; 264 | right: 10px; 265 | font-size: 12px; 266 | margin: 0; 267 | color: #888; 268 | z-index: 1; 269 | } 270 | 271 | #mocha-stats .progress { 272 | float: right; 273 | padding-top: 0; 274 | 275 | /** 276 | * Set safe initial values, so mochas .progress does not inherit these 277 | * properties from Bootstrap .progress (which causes .progress height to 278 | * equal line height set in Bootstrap). 279 | */ 280 | height: auto; 281 | -webkit-box-shadow: none; 282 | -moz-box-shadow: none; 283 | box-shadow: none; 284 | background-color: initial; 285 | } 286 | 287 | #mocha-stats em { 288 | color: black; 289 | } 290 | 291 | #mocha-stats a { 292 | text-decoration: none; 293 | color: inherit; 294 | } 295 | 296 | #mocha-stats a:hover { 297 | border-bottom: 1px solid #eee; 298 | } 299 | 300 | #mocha-stats li { 301 | display: inline-block; 302 | margin: 0 5px; 303 | list-style: none; 304 | padding-top: 11px; 305 | } 306 | 307 | #mocha-stats canvas { 308 | width: 40px; 309 | height: 40px; 310 | } 311 | 312 | #mocha code .comment { color: #ddd; } 313 | #mocha code .init { color: #2f6fad; } 314 | #mocha code .string { color: #5890ad; } 315 | #mocha code .keyword { color: #8a6343; } 316 | #mocha code .number { color: #2f6fad; } 317 | 318 | @media screen and (max-device-width: 480px) { 319 | #mocha { 320 | margin: 60px 0px; 321 | } 322 | 323 | #mocha #stats { 324 | position: absolute; 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/lts_mochaCss.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | text/css 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/lts_mochaJs.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | application/javascript 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/lts_sinon.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | application/javascript 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/lts_testutil.resource: -------------------------------------------------------------------------------- 1 | /** 2 | * This test util aims to reduce the boiler plate code needed in the test specs by providing helper methods for common test patterns. 3 | * Tests reference the helper methods via '$T'. 4 | */ 5 | (function (global) { 6 | global.$T = function $T() { }; 7 | 8 | var cmpsToCleanup = []; 9 | var contexualRunner; 10 | 11 | /** 12 | * Waits for the function to finish executing. 13 | * 14 | * @param {function} fn The function to be executed. 15 | * @param {?number} timeout The total timeout for the wait. 16 | * @param {?number} interval The interval. 17 | * @returns {Promise} The wait result. 18 | */ 19 | $T.waitFor = function (fn, timeout, interval) { 20 | timeout = timeout || 3000; 21 | interval = interval || 50; 22 | 23 | var endTime = new Date().getTime() + timeout; 24 | return new Promise(function (resolve, reject) { 25 | (function poll() { 26 | var res = fn(); 27 | if (res) { 28 | resolve(res); 29 | } else if (new Date().getTime() < endTime) { 30 | setTimeout(poll, interval); 31 | } else { 32 | reject(new Error("Timed out after " + timeout 33 | + "ms waiting for: " + fn)); 34 | } 35 | })(); 36 | }); 37 | } 38 | 39 | /** 40 | * Creates the Lightning component, adds it to the list of components to be cleared, 41 | * and render it into the specified DOM element. 42 | * 43 | * @param {string} descriptor The descriptor of the component to be rendered. 44 | * @param {Object.} attributes The attributes to be set to the component. 45 | * @param {boolean} requiresRendering If component rendering is required. Optional. 46 | * @returns {Promise} component creation or rendering result, depending on whether renderInto is specified 47 | */ 48 | $T.createComponent = function (descriptor, attributes, requiresRendering) { 49 | var renderInto = requiresRendering ? document.getElementById("renderTestComponents") : null; 50 | var callback = function (resolve, reject) { 51 | runInsideCallbackClosure(function () { 52 | $A.createComponent(descriptor, attributes, function (component, status, error) { 53 | if (status === "SUCCESS") { 54 | cmpsToCleanup.push(component.getGlobalId()); 55 | if (renderInto) { 56 | var renderingContainer = $A.getComponent(renderInto); 57 | if (!renderingContainer) { 58 | throw new Error("Could not find valid component to render into: " + renderInto); 59 | } 60 | var body = renderingContainer.get("v.body"); 61 | body.push(component); 62 | renderingContainer.set("v.body", body); 63 | } 64 | resolve(component); 65 | } 66 | else { 67 | reject(error); 68 | } 69 | }); 70 | }); 71 | }; 72 | 73 | if (renderInto) { 74 | return new Promise(callback) 75 | .then(function (component) { 76 | return $T.waitFor(function () { 77 | if (component.isRendered()) { 78 | return component; 79 | } 80 | return false; 81 | }); 82 | }); 83 | } else { 84 | return new Promise(callback); 85 | } 86 | } 87 | 88 | /** 89 | * Fire an application level event. Takes care of lightning lifecycle related setup needed to successfully interact (e.g. accessChecks) with an application event. 90 | * @param {string} eventName Name of application event (e.g. c:myAppEvt) 91 | * @param {Object.} eventArgs The event attributes 92 | * 93 | * @returns {Boolean} A boolean value indicating whether an application level event with specified name was found and fired or not 94 | */ 95 | $T.fireApplicationEvent = function (eventName, eventArgs) { 96 | var eventFired = false; 97 | runInsideCallbackClosure(function () { 98 | var appEvent = $A.get("e." + eventName); 99 | if (appEvent) { 100 | if (eventArgs) { 101 | appEvent.setParams(eventArgs); 102 | } 103 | appEvent.fire(); 104 | eventFired = true; 105 | } 106 | }); 107 | return eventFired; 108 | } 109 | 110 | /** 111 | * Clears the components registered to cmpsToCleanup list. 112 | */ 113 | $T.clearRenderedTestComponents = function () { 114 | while (cmpsToCleanup.length) { 115 | var globalId = cmpsToCleanup.shift(); 116 | var cmp = $A.getComponent(globalId); 117 | cmp.destroy(); 118 | } 119 | } 120 | 121 | /** 122 | * @param {function} fn Function to execute inside callback context set by test runner 123 | * component via $A.getCallback() 124 | */ 125 | $T.run = function(fn){ 126 | runInsideCallbackClosure(fn); 127 | } 128 | 129 | /** 130 | * [Internal Only] used by test runner component to allow execution of test code within a closure 131 | */ 132 | $T._setContexualRunner = function (fn) { 133 | contexualRunner = fn; 134 | } 135 | 136 | /** 137 | * [Internal Only] used by test runner component to prepare test results for sfdx extraction. 138 | */ 139 | $T._sfdxReportForJasmine = function (jasmine) { 140 | var run_results_full = { "tests": [] }; 141 | var suiteDescription = []; 142 | var specStartTime = 0; 143 | 144 | var sfdxReporter = { 145 | suiteStarted: function(suite) { 146 | suiteDescription.push(suite.description); 147 | }, 148 | suiteDone: function(suite) { 149 | suiteDescription.pop(); 150 | }, 151 | specStarted: function(spec) { 152 | specStartTime = Date.now(); 153 | }, 154 | specDone: function(spec) { 155 | var fullReport = {}; 156 | var failedStr = ''; 157 | fullReport.FullName = (suiteDescription.join(' : ') + ' : ' + spec.description).replace(/["]/g, "'"); 158 | fullReport.Outcome = 'Pass'; 159 | fullReport.RunTime = Date.now() - specStartTime; 160 | 161 | if ('passed' === spec.status) { 162 | console.log('passed!', fullReport.FullName, '- ' + fullReport.RunTime + 'ms'); 163 | } else if ('pending' === spec.status) { 164 | console.log('pending!', fullReport.FullName, '- ' + fullReport.RunTime + 'ms'); 165 | fullReport.Outcome = 'Skip'; 166 | fullReport.Message = ' # SKIP disabled by xit or similar'; 167 | } else if ('disabled' === spec.status) { 168 | console.log('disabled!', fullReport.FullName, '- ' + fullReport.RunTime + 'ms'); 169 | fullReport.Outcome = 'Disabled'; 170 | } else { 171 | console.log('failed!', fullReport.FullName, '- ' + fullReport.RunTime + 'ms'); 172 | for (var i = 0, failure; i < spec.failedExpectations.length; i++) { 173 | var failureMessage = spec.failedExpectations[i].message; 174 | failedStr += '\n ' + failureMessage; 175 | } 176 | 177 | fullReport.Outcome = 'Fail'; 178 | fullReport.Message = failedStr; 179 | } 180 | run_results_full.tests.push(fullReport); 181 | }, 182 | jasmineDone: function(result) { 183 | setHiddenDivContent("run_results_full", JSON.stringify(run_results_full)); 184 | setHiddenDivContent("coverage_results_full", JSON.stringify(window.__coverage__)); 185 | } 186 | }; 187 | 188 | jasmine.getEnv().addReporter(sfdxReporter); 189 | } 190 | 191 | /** 192 | * [Internal Only] used by test runner component to prepare test results for sfdx extraction. 193 | */ 194 | $T._sfdxReportForMocha = function (mochaRunner) { 195 | var run_results_full = { "tests": [] }; 196 | mochaRunner.on('pass', function (test) { 197 | run_results_full.tests.push(convertMochaTestEventToSfdxTestResult(test)); 198 | }).on('fail', function (test) { 199 | run_results_full.tests.push(convertMochaTestEventToSfdxTestResult(test)); 200 | }).on('pending', function (test) { 201 | var pendingTest = Object.assign({}, test, { 202 | err: { 203 | message: ' # SKIP disabled by xit or similar' 204 | }, 205 | duration: 0, 206 | state: 'pending' 207 | }); 208 | run_results_full.tests.push(convertMochaTestEventToSfdxTestResult(pendingTest)); 209 | }).on('end', function (suite) { 210 | setHiddenDivContent("run_results_full", JSON.stringify(run_results_full)); 211 | setHiddenDivContent("coverage_results_full", JSON.stringify(window.__coverage__)); 212 | }); 213 | } 214 | 215 | /** 216 | * Turns a mocha test event into a sfdx test result 217 | */ 218 | var convertMochaTestEventToSfdxTestResult = function (test) { 219 | console.log(test.state + '!', test.title, '-' + test.duration + 'ms'); 220 | return { 221 | FullName: generateMochaTestTitleForSfdx(test), 222 | Outcome: mochaStateToOutcome[test.state], 223 | RunTime: test.duration, 224 | Message: test.err ? test.err.message : undefined 225 | }; 226 | }; 227 | 228 | var generateMochaTestTitleForSfdx = function (test) { 229 | var sfdxTitle = test.title; 230 | while (!test.parent.root) { 231 | sfdxTitle = test.parent.title + ' : ' + sfdxTitle; 232 | test = test.parent; 233 | } 234 | return sfdxTitle.replace(/["]/g, "'"); 235 | } 236 | 237 | var mochaStateToOutcome = { 238 | 'passed': 'Pass', 239 | 'failed': 'Failed', 240 | 'pending': 'Skip' 241 | }; 242 | 243 | var setHiddenDivContent = function (id, content) { 244 | var aDiv = document.getElementById(id); 245 | 246 | if (!aDiv) { 247 | aDiv = document.createElement("div"); 248 | aDiv.id = id; 249 | aDiv.style.display = "none"; 250 | document.body.appendChild(aDiv); 251 | } 252 | 253 | aDiv.innerHTML = content; 254 | return aDiv; 255 | }; 256 | 257 | /** 258 | * Runs provided function within a closure to allow interaction with lightning API (e.g. avoid accessCheck errors) 259 | * with proper context 260 | */ 261 | var runInsideCallbackClosure = function (fn) { 262 | if (!contexualRunner) { 263 | throw new Error("callback is not set"); 264 | } 265 | contexualRunner(fn); 266 | }; 267 | })(this); 268 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/lts_testutil.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | application/javascript 5 | 6 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/mochaExampleTests.resource: -------------------------------------------------------------------------------- 1 | const expect = chai.expect; 2 | var sandbox = sinon.sandbox.create(); 3 | 4 | /** 5 | * This test suite contains examples that illustrate reusable patterns for testing 6 | * your custom Lightning components. 7 | * 8 | * These tests are written using the [Jasmine framework](https://jasmine.github.io/2.1/introduction). 9 | * They're run in the Lightning Testing Service using a wrapper, which you can find 10 | * in jasmineboot.js, in the same repository as this test suite. 11 | * 12 | * Note that Jasmine uses "spec" as its name for a test. We use their terminology here 13 | * for consistency with their documentation. 14 | */ 15 | describe("Lightning Component Testing Examples", function(){ 16 | afterEach(function() { 17 | // Each spec (test) renders its components into the same div, 18 | // so we need to clear that div out at the end of each spec. 19 | $T.clearRenderedTestComponents(); 20 | sandbox.restore(); 21 | }); 22 | 23 | /** 24 | * Component under test: 'c:egRenderElement': 25 | * This spec creates a component, adds it to the body, waits for the rendering to complete, 26 | * and then ensures that the expected content has been added to the DOM. 27 | * NOTE: The spec and the component under test are in same locker (same namespace), 28 | * so the spec is able to see the DOM owned by the component. 29 | */ 30 | describe('c:egRenderElement', function(){ 31 | // We encourage you to have the code for c:egRenderElement side by side 32 | // when reading through this spec. 33 | it('renders specific static text', function(done) { 34 | // Instantiate and render the c:egRenderElement Lightning component. 35 | // The second parameter (empty here) is the list of component attribute values to set. 36 | // The third parameter, requiresRendering, is set to true. 37 | $T.createComponent("c:egRenderElement", {}, true) 38 | // The 'component' here is the instance of c:egRenderElement 39 | .then(function(component) { 40 | expect(document.getElementById("content").textContent).to.include("Hello World!"); 41 | // end this spec successfully 42 | done(); 43 | }).catch(function(e) { 44 | // end this spec as a failure 45 | done(e); 46 | }); 47 | }); 48 | }); 49 | 50 | /** 51 | * Component under test: 'c:egComponentMethod' 52 | * This spec validates that calling a method on the component's public interface 53 | * causes the expected state change. 54 | */ 55 | describe('c:egComponentMethod', function() { 56 | it("updates an attribute value when a method is invoked on the component's interface", function(done) { 57 | $T.createComponent("c:egComponentMethod", null) 58 | .then(function(component) { 59 | component.sampleMethod(); 60 | expect(component.get("v.status")).to.equal("sampleMethod invoked"); 61 | done(); 62 | }).catch(function(e) { 63 | done(e); 64 | }); 65 | }); 66 | }); 67 | 68 | /** 69 | * Component under test: 'c:egClientSideAction' 70 | * This spec sets an attribute value, invokes a method, and then validates that 71 | * the interaction results in expected state and rendering updates. 72 | */ 73 | describe('c:egClientSideAction', function(){ 74 | it('adds items to a list when the client-side action is invoked', function(done) { 75 | $T.createComponent("c:egClientSideAction", {}, true) 76 | .then(function(component){ 77 | component.set("v.searchString", "salesforce"); 78 | expect(component.find("accountList").getElement().children.length).to.equal(0); 79 | // Invoke function in component's client-side controller through aura:method 80 | component.searchAccounts(); 81 | // Assert results of using the component's function (public interface) 82 | expect(component.get("v.accountList").length).to.equal(10); 83 | expect(component.get("v.accountList")[0]).to.include("salesforce"); 84 | // Assert results by checking DOM element owned by the namespace 85 | expect(component.find("accountList").getElement().children.length).to.equal(10); 86 | done(); 87 | 88 | }).catch(function(e) { 89 | done(e); 90 | }); 91 | }); 92 | }); 93 | 94 | /** 95 | * Component under test: 'c:egServerSideActionCallback' 96 | * This spec shows how to validate a server-side action end-to-end. The spec interacts with the component 97 | * to cause a server-side action to be invoked, and then waits for the action callback to update 98 | * component state as expected. 99 | * This technique (actually connecting to the server) is discouraged because 100 | * invoking server actions is time consuming (the spec can timeout), and can have side effects. 101 | */ 102 | describe('c:egServerSideActionCallback', function(){ 103 | it('[Discouraged: brittle, slow, side-effects] receives server data when server-side action is invoked', function(done) { 104 | $T.createComponent("c:egServerSideActionCallback", {}, true) 105 | .then(function(component){ 106 | component.set("v.searchString", "United"); 107 | expect(component.find("accountList").getElement().children.length).to.equal(0); 108 | $T.run(component.search); 109 | return $T.waitFor(function(){ 110 | return component.get("v.accountList").length === 3; 111 | }) 112 | }).then(function() { 113 | done(); 114 | }).catch(function(e) { 115 | done(e); 116 | }); 117 | }); 118 | }); 119 | 120 | /** 121 | * Component under test: 'c:egServerSideActionCallback' 122 | * This spec shows how server-side action callbacks can be validated in isolation by exposing the logic of the 123 | * callback via a component method, and then having the spec use the callback directly for interaction 124 | */ 125 | describe('c:egServerSideActionCallback', function() { 126 | it('updates with provided data when action callback is invoked directly', function(done) { 127 | $T.createComponent("c:egServerSideActionCallback", {}, true) 128 | .then(function(component){ 129 | // Mocking out the server-side action response 130 | var res = {getState : function(){return "SUCCESS";}, getReturnValue: function(){return [{"Name":"Acct 1"},{"Name":"Acct 2"}];}}; 131 | component.searchAccounts(res); 132 | // Assert using components interface 133 | expect(component.get("v.accountList").length).to.equal(2); 134 | expect(component.get("v.accountList")[0]['Name']).to.include("Acct 1"); 135 | // Assert using DOM element owned by the namespace 136 | expect(component.find("accountList").getElement().children.length).to.equal(2); 137 | done(); 138 | }).catch(function(e) { 139 | done(e); 140 | }); 141 | }); 142 | }); 143 | 144 | /** 145 | * Component under test: 'c:egServerSideActionCallback' 146 | * This spec shows how server-side action callbacks could be validated by mocking the server-side actions using Jasmine spies. 147 | * This approach relies on Jasmine's mocking capabilities and requires you to construct the server responses yourself. 148 | * But it eliminates the need for restructuring component production code for testability. 149 | */ 150 | describe('c:egServerSideActionCallback', function() { 151 | it('updates with provided data when invoked via a mocked server action using Jasmine spies', function(done) { 152 | $T.createComponent("c:egServerSideActionCallback", {}, true) 153 | .then(function(component){ 154 | var res = {getState : function(){return "SUCCESS";}, getReturnValue: function(){return [{"Name":"Acct 1"},{"Name":"Acct 2"}];}}; 155 | sandbox.stub($A, "enqueueAction").callsFake(function(action) { 156 | var cb = action.getCallback("SUCCESS") 157 | cb.fn.apply(cb.s, [res]); 158 | }); 159 | component.search(); 160 | // Assert using components interface 161 | expect(component.get("v.accountList").length).to.equal(2); 162 | expect(component.get("v.accountList")[0]['Name']).to.include("Acct 1"); 163 | // Assert using DOM element owned by the namespace 164 | expect(component.find("accountList").getElement().children.length).to.equal(2); 165 | done(); 166 | }).catch(function(e) { 167 | done(e); 168 | }); 169 | }); 170 | }); 171 | 172 | /** 173 | * Component under test: 'c:egAttributeTypes' 174 | * This spec shows how to interact with various attribute types from a test spec. 175 | */ 176 | describe('c:egAttributeTypes', function() { 177 | it('sets component attributes of various types and values', function(done) { 178 | var attributes = { 179 | stringAtr:"string value", 180 | integerAtr:20, 181 | dateAtr:new Date(), 182 | sobjectAtr:{"sObjectType":"Contact", "FirstName":"Marc" , "LastName":"Benioff"}, 183 | accountAtr:{"sObjectType":"Account", "Name":"salesforce"}, 184 | objectAtr:{"key1":"value1"} 185 | }; 186 | $T.createComponent("c:egAttributeTypes", attributes, true) 187 | .then(function(component){ 188 | expect(component.find("stringAtrAuraId").getElement().innerHTML).to.include(attributes.stringAtr); 189 | expect(component.get("v.integerAtr")).to.equal(attributes.integerAtr); 190 | expect(component.get("v.objectAtrStringified")).to.equal(JSON.stringify(attributes.objectAtr)); 191 | done(); 192 | }).catch(function(e) { 193 | done(e); 194 | }); 195 | }); 196 | }); 197 | 198 | /** 199 | * Component under test: 'c:egFacet' 200 | * This spec shows how to interact with facets from a test spec. Note the use of component IDs. 201 | */ 202 | describe('c:egFacet', function(){ 203 | var attributes = {"content":"textContent"}; 204 | it('renders the expected content when used as a facet', function(done) { 205 | $T.createComponent("c:egFacet", attributes, true) 206 | .then(function(component){ 207 | expect(component.find("cmpUnderTest").find("AuraComponentAtrId").getElement().innerHTML).to.include(attributes.content); 208 | done(); 209 | }).catch(function(e) { 210 | done(e); 211 | }); 212 | }); 213 | }); 214 | 215 | /** 216 | * Component under test: 'c:egGlobalValueProvider' 217 | * This spec shows how to interact with global value providers by programmatically 218 | * getting a reference to a custom label and validating that its value is being 219 | * displayed by the component. 220 | */ 221 | describe('c:egGlobalValueProvider', function(){ 222 | it('renders a custom label', function(done) { 223 | $T.createComponent("c:egGlobalValueProvider", {}, true) 224 | .then(function(component){ 225 | // You can reference a custom label in your spec using standard 226 | // Lightning component JavaScript APIs 227 | var greetingLabelVal = $A.get("$Label.c.greeting"); 228 | //expect(greetingLabelVal).to.equal(greetingLabelVal); 229 | expect(component.find("greeting").getElement().textContent).to.equal(greetingLabelVal); 230 | done(); 231 | }).catch(function(e) { 232 | done(e); 233 | }); 234 | }); 235 | }); 236 | 237 | /** 238 | * Component under test: 'c:egConditionalUI' 239 | * This spec shows to validate conditional UI by using the component's interface. 240 | */ 241 | describe('c:egConditionalUI', function() { 242 | it('renders only the "truthy" (conditional) portion of the user interface', function(done) { 243 | $T.createComponent("c:egConditionalUI", null) 244 | .then(function(component) { 245 | expect(component.find("trueDiv")).to.be.ok; 246 | expect(component.find("falseDiv")).to.not.be.ok; 247 | component.find("toggleButton").getEvent("press").fire(); 248 | expect(component.find("trueDiv")).to.not.be.ok; 249 | expect(component.find("falseDiv")).to.be.ok; 250 | done(); 251 | }).catch(function(e) { 252 | done(e); 253 | }); 254 | }); 255 | }); 256 | 257 | /** 258 | * Component under test: 'c:egEventHandling' 259 | * This spec shows how to validate a component's handling of component- and 260 | * application-level events. 261 | */ 262 | describe('c:egEventHandling', function() { 263 | it('handles component- and application-level events', function(done) { 264 | $T.createComponent("c:egEventHandling", null) 265 | .then(function(component){ 266 | var cmpEvent = component.getEvent("sampleEvent"); 267 | cmpEvent.setParams({"data":"component event fired"}); 268 | cmpEvent.fire() 269 | expect(component.get("v.message")).to.equal("component event fired"); 270 | $T.fireApplicationEvent("c:egApplicationEvent", {"data":"application event fired"}); 271 | expect(component.get("v.message")).to.equal("application event fired"); 272 | done(); 273 | }).catch(function(e) { 274 | done(e); 275 | }); 276 | }); 277 | }); 278 | }); 279 | -------------------------------------------------------------------------------- /lightning-component-tests/test/default/staticresources/mochaExampleTests.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | application/javascript 5 | 6 | -------------------------------------------------------------------------------- /sfdx-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageDirectories": [ { "path" : "lightning-component-tests" } ], 3 | "sourceApiVersion" : "40.0" 4 | } 5 | --------------------------------------------------------------------------------