├── ChangeLog ├── LICENSE ├── README.md ├── compat └── v10 │ ├── Bootstrap │ └── Bootstrap.php │ ├── TestCase │ ├── FunctionalTestCase.php │ └── ViewHelperBaseTestcase.php │ └── TestSystem │ └── TestSystem.php ├── composer.json ├── res ├── Configuration │ ├── FunctionalTests.xml │ ├── FunctionalTestsBootstrap.php │ ├── UnitTests.xml │ └── UnitTestsBootstrap.php └── Fixtures │ ├── Database │ ├── be_users.xml │ ├── pages.xml │ ├── pages_language_overlay.xml │ ├── sys_file_storage.xml │ ├── sys_language.xml │ └── tt_content.xml │ ├── Frontend │ ├── Request.tpl │ └── site.yaml │ └── TypoScript │ ├── JsonRenderer.ts │ └── JsonRendererNoOverlay.ts ├── sonar-project.properties └── src └── TestingFramework ├── Bootstrap ├── AbstractBootstrap.php ├── Bootstrap.php ├── BootstrapFactory.php ├── RequestBootstrap.php └── SystemEnvironmentBuilder.php ├── Composer └── ExtensionTestEnvironment.php ├── Database ├── Database.php └── DatabaseInterface.php ├── Exception ├── Exception.php └── NtfStreamException.php ├── File ├── FileStreamWrapper.php └── NtfStreamWrapper.php ├── Frontend ├── Collector.php ├── Parser.php └── Renderer.php ├── Http ├── Response.php ├── ResponseContent.php └── ResponseSection.php ├── MockObject └── AccessibleMockObjectInterface.php ├── Rendering └── RenderingContextFixture.php ├── TestCase ├── AbstractFunctionalTestCase.php ├── AbstractTestCase.php ├── AbstractViewHelperBaseTestcase.php ├── FunctionalTestCase.php ├── UnitTestCase.php └── ViewHelperBaseTestcase.php └── TestSystem ├── AbstractTestSystem.php └── TestSystem.php /ChangeLog: -------------------------------------------------------------------------------- 1 | 2022-03-15 [RELEASE] Release of nimut/testing-framework 6.0.1 (Nicole Cordes) 2 | 2022-03-15 836db68 [TASK] Add additional extension path test (Nicole Cordes) 3 | 2022-03-15 9940810 [BUGFIX] Ensure non-composer mode in entry points (Nicole Cordes) 4 | 2022-01-18 2515ad6 [FEATURE] Add functional tests for extension class loading (Nicole Cordes) 5 | 2021-11-15 2e24079 [BUGFIX] Make Functional tests work again with TYPO3 11.5.3 (Helmut Hummel) 6 | 2022-01-18 388d788 [BUGFIX] Increase PHPUnit dependency for PHP 8.1 support (Nicole Cordes) 7 | 2022-01-18 dd50ad5 [BUGFIX] Use correct main branch as composer dependency (Nicole Cordes) 8 | 2022-01-18 a7ffc05 [BUGFIX] Use correct PHP version for Travis CI (Nicole Cordes) 9 | 2022-01-18 0bc657d [TASK] Adjust PHP dependency in composer.json (Nicole Cordes) 10 | 2022-01-14 b313250 [TASK] Raise PHP compatibility to 8.1 (Tomas Norre Mikkelsen) 11 | 2021-11-05 f2d1362 [TASK] Change branch alias in composer.json (Nicole Cordes) 12 | 13 | 2021-10-21 [RELEASE] Release of nimut/testing-framework 6.0.0 (Nicole Cordes) 14 | 2021-10-21 8ec50f0 [TASK] Add small code adjustments (Nicole Cordes) 15 | 2021-08-19 7792981 [TASK] Only truncate tables that have been touched (Oliver Klee) 16 | 2021-10-11 1466718 [TASK] Raise PHP compatibility to 8.0 (Nicole Cordes) 17 | 2021-10-07 1dcacec [BUGFIX] Enforce setting request instance in ViewHelper renderingContext (Nicole Cordes) 18 | 2021-10-07 2ab348c [TASK] Enforce running "prefer-lowest tests" (Nicole Cordes) 19 | 2021-10-07 da71fb9 [TASK] Raise nimut/phpunit-merger version constraint (Nicole Cordes) 20 | 2021-10-05 6a51b31 [BUGFIX] Update text template class for PHPUnit 9 (Mathias Brodala) 21 | 2021-10-05 9b5ab07 [FEATURE] Add compatibility to TYPO3 11.5 (Nicole Cordes) 22 | 2021-10-05 56f3272 [BUGFIX] Correctly initialize backend user (Nicole Cordes) 23 | 2021-10-05 ee1b162 [BUGFIX] Correctly initialize extension configuration (Nicole Cordes) 24 | 2021-10-04 b0edfff [BUGFIX] Correctly create PackageArtifact file (Nicole Cordes) 25 | 2021-10-04 e4a3180 [BUGFIX] Correctly create Bootstrap core cache (Nicole Cordes) 26 | 2021-10-04 96a3605 [FEATURE] Add compatibility to TYPO3 11.4 (Nicole Cordes) 27 | 2021-10-04 57aacdd [BUGFIX] Make ViewHelperBaseTestcase compatible with TYPO3 11 (Nicole Cordes) 28 | 2021-08-23 9e4f5df [TASK] Raise phpunit/phpunit compatibility to ^8.5 and ^9.5 (Nicole Cordes) 29 | 2021-08-21 4774cfe [FEATURE] Add compatibility to TYPO3 11.3 (Nicole Cordes) 30 | 2021-08-21 8d7c355 [!!!][TASK] Remove 8.7 support (Nicole Cordes) 31 | 2021-08-21 bc3b06a [TASK] Raise branch alias to 6.x-dev (Nicole Cordes) 32 | 33 | 2021-10-21 [RELEASE] Release of nimut/testing-framework 5.3.0 (Nicole Cordes) 34 | 2021-10-21 adf8a1f [TASK] Add small code adjustments (Nicole Cordes) 35 | 2021-08-19 6d9c135 [TASK] Only truncate tables that have been touched (Oliver Klee) 36 | 2021-08-15 162cd54 [TASK] Switch to Composer 2 as a dev dependency (Oliver Klee) 37 | 38 | 2021-10-07 [RELEASE] Release of nimut/phpunit-merger 5.2.2 (Nicole Cordes) 39 | 2021-10-07 778479c Revert "[TASK] Raise TYPO3 dependencies to include 11.0.0" (Nicole Cordes) 40 | 41 | 2021-05-22 [RELEASE] Release of nimut/testing-framework 5.2.1 (Nicole Cordes) 42 | 2021-05-22 810b032 [BUGFIX] Update Travis CI configuration for SonarQube Scanner (Nicole Cordes) 43 | 2021-05-22 1df92c4 [BUGFIX] Add conflict to current doctrine/dbal 2.13.x versions (Nicole Cordes) 44 | 45 | 2021-01-19 [RELEASE] Release of nimut/testing-framework 5.2.0 (Nicole Cordes) 46 | 2021-01-15 b249af9 [TASK] Raise TYPO3 dependencies to include 11.0.0 (Nicole Cordes) 47 | 2021-01-15 3d0573b [TASK] Update rules for PHP-CS-Fixer and StyleCI (Nicole Cordes) 48 | 2021-01-14 4274dbc [BUGFIX] Slightly adjust FunctionalTest::routesAreInitialized expectation (Nicole Cordes) 49 | 2021-01-14 55b024c [BUGFIX] Remove dropped fields from be_users fixture (Nicole Cordes) 50 | 2021-01-14 d641b4f [BUGFIX] Enforce int return type for Database::selectCount (Nicole Cordes) 51 | 52 | 2020-07-07 [RELEASE] Release of nimut/testing-framework 5.1.0 (Nicole Cordes) 53 | 2020-07-03 4377e15 [TASK] Add preparation info to README.md (Nicole Cordes) 54 | 2020-06-30 0eddc5b [TASK] Add extension preparation tests to Travis CI (Nicole Cordes) 55 | 2020-06-30 f6a4b1c [BUGFIX] Use TYPO3 CMS Installers for configuration resolving (Nicole Cordes) 56 | 2020-06-30 9161a5b [TASK] Clean up class description (Nicole Cordes) 57 | 2020-06-30 330e4de [TASK] Require Composer as dev-dependency (Nicole Cordes) 58 | 2020-06-30 8cf36e1 [FEATURE] Copy ExtensionTestEnvironment from TYPO3 testing framework (Simon Schaufelberger) 59 | 60 | 2020-06-16 [RELEASE] Release of nimut/testing-framework 5.0.4 (Nicole Cordes) 61 | 2020-06-15 53feb18 [FEATURE] Add support for SQLite database (Nicole Cordes) 62 | 63 | 2020-04-22 [RELEASE] Release of nimut/testing-framework 5.0.3 (Nicole Cordes) 64 | 2020-04-22 59eb923 [TASK] Update version constraints (Nicole Cordes) 65 | 2020-04-22 3fcd69b [TASK] Raise TYPO3 dependencies to require 10.4.0 (Helmut Hummel) 66 | 67 | 2020-03-06 [RELEASE] Release of nimut/testing-framework 5.0.2 (Nicole Cordes) 68 | 2020-03-06 7f8c7c8 [TASK] Raise TYPO3 dependencies to include 10.3.0 (Nicole Cordes) 69 | 70 | 2020-02-25 [RELEASE] Release of nimut/testing-framework 5.0.1 (Nicole Cordes) 71 | 2020-02-25 e04c93b [BUGFIX] Ensure old error_reporting (Nicole Cordes) 72 | 2019-12-28 3cc80ab [BUGFIX] Require minimum dependency versions for "prefer-lowest" tests (Nicole Cordes) 73 | 74 | 2019-12-19 [RELEASE] Release of nimut/testing-framework 5.0.0 (Nicole Cordes) 75 | 2019-12-18 f350f11 [BUGFIX] Ensure database fixtures can be imported (Nicole Cordes) 76 | 2019-12-18 055c662 [TASK] Update supporting files to EXT:my_skeleton 0.3.0 (Nicole Cordes) 77 | 2019-12-17 38c1187 [TASK] Raise PHP compatibility to 7.4 (Nicole Cordes) 78 | 2019-12-03 6d805e7 [BUGFIX] Prefer str_replace to environment variable (Nicole Cordes) 79 | 2019-12-03 9c5999e [TASK] Raise TYPO3 dependencies to include 10.2.0 (Nicole Cordes) 80 | 2019-10-25 28fc377 [FEATURE] Add compatibility to TYPO3 10 for functional tests (Nicole Cordes) 81 | 2019-10-25 f271f7e [FEATURE] Add compatibility to TYPO3 10 for unit tests (Nicole Cordes) 82 | 2019-11-22 ff71fa5 [BUGFIX] Correctly cleanup test fixture files (Nicole Cordes) 83 | 2019-11-22 4145be8 [BUGFIX] Prevent deleting pathsToLinkInTestInstance after test (Nicole Cordes) 84 | 2019-10-23 0d63090 Merge pull request #122 from IchHabRecht/initialize-reflection-service-mock (Nicole Cordes) 85 | 2019-09-09 a0737b4 [!!!][TASK] Remove 7.6 support (Nicole Cordes) 86 | 2019-09-09 eae3f4a [TASK] Raise branch alias to 5.x-dev (Nicole Cordes) 87 | 88 | 2019-09-09 [RELEASE] Release of nimut/testing-framework 4.1.6 (Nicole Cordes) 89 | 2019-07-01 891e353 [TASK] Fix ViewHelper example in readme (Jan Kiesewetter) 90 | 2019-05-30 4fb852d [TASK] Enable MySQL database for Travis CI tests (Nicole Cordes) 91 | 2019-04-09 9b958ba [BUGFIX] Correctly remove pathsToLinkInTestInstance in tearDown (Nicole Cordes) 92 | 93 | 2019-03-08 [RELEASE] Release of nimut/testing-framework 4.1.5 (Nicole Cordes) 94 | 2019-03-08 fc2e2dd [BUGFIX] Use lowercased package name for mikey179/vfsstream (Nicole Cordes) 95 | 2019-03-08 f85e26e [TASK] Raise phpunit/phpunit compatibility to ^7.0 (Nicole Cordes) 96 | 2019-03-08 862394a [BUGFIX] Exclude TRAVIS_CMD in xdebug grep (Nicole Cordes) 97 | 2019-03-08 6d1d173 [TASK] Raise PHP compatibility to 7.3 (Nicole Cordes) 98 | 2019-03-08 63f78db [BUGFIX] Add missing build job for PHP 7.2 / TYPO3 7.6 (Nicole Cordes) 99 | 100 | 2018-11-01 [RELEASE] Release of nimut/testing-framework 4.1.4 (Nicole Cordes) 101 | 2018-10-29 fa6c602 [BUGFIX] Set correct default core extension dependencies (Nicole Cordes) 102 | 2018-10-03 20be88d [TASK] Remove support for non LTS versions (Nicole Cordes) 103 | 104 | 2018-10-02 [RELEASE] Release of nimut/testing-framework 4.1.3 (Nicole Cordes) 105 | 2018-10-02 71099fb [TASK] Raise TYPO3 dependencies to include 9.5 (Nicole Cordes) 106 | 107 | 2018-09-18 [RELEASE] Release of nimut/testing-framework 4.1.2 (Nicole Cordes) 108 | 2018-09-18 bb768c6 [FEATURE] Add functional test for backend routes (Nicole Cordes) 109 | 2018-09-18 a2ae145 [TASK] Initialize backend routes with extension config (Mathias Brodala) 110 | 2018-09-17 40ac176 [BUGFIX] Initialize backend routes for TYPO3 >= 9.2 (Mathias Brodala) 111 | 112 | 2018-09-11 [RELEASE] Release of nimut/testing-framework 4.1.1 (Nicole Cordes) 113 | 2018-09-11 7e9d5da [BUGFIX] Initialize UnitTest Bootstrap with minimum requirements (Nicole Cordes) 114 | 2018-09-10 7b572c1 [BUGFIX] Ensure TYPO3_PATH_APP is reset to store local caches (Nicole Cordes) 115 | 2018-09-10 fe9957c [BUGFIX] Ensure ext_tables.php files are loaded (Nicole Cordes) 116 | 117 | 2018-09-10 [RELEASE] Release of nimut/testing-framework 4.1.0 (Nicole Cordes) 118 | 2018-09-05 5666525 [BUGFIX] Correctly initialize UnitTest Bootstrap in TYPO3 >= 9.2 (Nicole Cordes) 119 | 2018-09-05 fbbacff [BUGFIX] Reset database connection to delete internal state (Nicole Cordes) 120 | 2018-09-05 33f1fc3 [BUGFIX] Correctly load extension configuration in TYPO3 >= 9.2 (Nicole Cordes) 121 | 2018-09-05 427e7b6 [BUGFIX] Correctly initialize Bootstrap in TYPO3 >= 9.2 (Nicole Cordes) 122 | 2018-09-05 72da3ce [TASK] Raise TYPO3 dependencies to include 9.4 (Nicole Cordes) 123 | 2018-08-14 c79af6d [BUGFIX] Use updated package of nimut/typo3-complete (Nicole Cordes) 124 | 2018-07-17 ec08709 Update README.md (Matthias Vogel) 125 | 126 | 2018-06-22 [RELEASE] Release of nimut/testing-framework 4.0.0 (Nicole Cordes) 127 | 2018-06-22 d891b25 [TASK] Raise branch alias to 4.x-dev (Nicole Cordes) 128 | 2018-06-16 aa375a1 [BUGFIX] Prefer stable package versions (Nicole Cordes) 129 | 2018-06-16 985dbfa [BUGFIX] Use different error type for exception test (Nicole Cordes) 130 | 2018-06-12 68d8da5 Mark as compatible with TYPO3 9.3 (Helmut Hummel) 131 | 2018-05-29 3a4681a [TASK] Add FileStreamWrapperTest from TYPO3 core (Nicole Cordes) 132 | 2018-05-29 d3d7334 [BUGFIX] Prevent notice on empty touch arguments (Nicole Cordes) 133 | 2018-05-28 ae26846 Simplify PHP version constraint (Nicole Cordes) 134 | 2018-05-27 5c0607f [TASK] Set a minimum PHP version in composer.json (Oliver Klee) 135 | 2018-05-25 d02da2a [TASK] Fix a typo in a comment (Oliver Klee) 136 | 137 | 2018-04-13 [RELEASE] Release of nimut/testing-framework 3.0.7 (Nicole Cordes) 138 | 2018-04-13 67b0e33 [BUGFIX] Explicitly require 9.1.1 as nimut/typo3-complete version (Nicole Cordes) 139 | 2018-04-13 d6cb420 [TASK] Raise TYPO3 dependencies to include 9.2 (Nicole Cordes) 140 | 141 | 2018-04-05 [RELEASE] Release of nimut/testing-framework 3.0.6 (Nicole Cordes) 142 | 2018-04-05 657589b [BUGFIX] Set conflict to broken symfony/finder versions (Nicole Cordes) 143 | 2018-04-05 768f1c0 [TASK] Add app-dir to composer.json (Nicole Cordes) 144 | 2018-03-15 ce6a159 [BUGFIX] Add missing dependencies for functional tests (Nicole Cordes) 145 | 2018-03-15 331e2a0 [BUGFIX] Don't run SonarQube Scanner without SONAR_TOKEN (Nicole Cordes) 146 | 2018-03-15 937f137 [TASK] Run tests extending FunctionalTestCase in separate processes (Nicole Cordes) 147 | 148 | 2018-03-14 [RELEASE] Release of nimut/testing-framework 3.0.5 (Nicole Cordes) 149 | 2018-03-14 5d6473f [BUGFIX] Prevent unsetting PHPUnit properties (Nicole Cordes) 150 | 2018-03-09 489c645 [BUGFIX] Remove importing fixtures for pages_language_overlay (Nicole Cordes) 151 | 152 | 2018-03-09 [RELEASE] Release of nimut/testing-framework 3.0.4 (Nicole Cordes) 153 | 2018-03-09 a94e1b1 [BUGFIX] Enforce database name to be lowerstring (Nicole Cordes) 154 | 155 | 2018-02-28 [RELEASE] Release of nimut/testing-framework 3.0.3 (Nicole Cordes) 156 | 2018-02-28 13c2563 [BUGFIX] Remove superfluous database field from be_users fixture file (Nicole Cordes) 157 | 2018-02-23 6ea971b [BUGFIX] Allow to override database setting for function test system (Nicole Cordes) 158 | 2018-02-04 f67e9fb [BUGFIX] Prevent deprecation errors due to trigger_error (Nicole Cordes) 159 | 160 | 2018-02-02 [RELEASE] Release of nimut/testing-framework 3.0.2 (Nicole Cordes) 161 | 2018-02-01 62f1ff8 [TASK] SonarQube: Exclude tests/Package directory for coverage report (Nicole Cordes) 162 | 2018-02-01 409e6b7 [FEATURE] SonarQube: Execute tests with all TYPO3 versions (Nicole Cordes) 163 | 2018-02-01 dfe1776 [BUGFIX] Move v90 AbstractTestSystem to TestSystem (Nicole Cordes) 164 | 2018-02-01 4e15b46 [BUGFIX] Add missing psr-4 autoload information (Nicole Cordes) 165 | 2018-02-01 698a0d9 [BUGFIX] Remove files before unsetting properties (Nicole Cordes) 166 | 167 | 2018-01-30 [RELEASE] Release of nimut/testing-framework 3.0.1 (Nicole Cordes) 168 | 2018-01-30 34b70e6 [BUGFIX] Re-add TYPO3 9.0 support (Nicole Cordes) 169 | 2018-01-30 3836623 [TASK] Raise TYPO3 dependencies to include 9.1 (Nicole Cordes) 170 | 2018-01-23 88950ef [TASK] Update composer.json license definition (Nicole Cordes) 171 | 2018-01-23 522d51f [BUGFIX] Initialize TYPO3 Backend Routing (Nicole Cordes) 172 | 173 | 2018-01-13 [RELEASE] Release of nimut/testing-framework 3.0.0 (Nicole Cordes) 174 | 2018-01-13 59901ec [BUGFIX] Fix broken Travis link and badge image (Oliver Klee) 175 | 2018-01-12 b2b3e71 [TASK] Update version numbers in the README (Oliver Klee) 176 | 2017-12-17 cc2d6ec [FEATURE] SonarQube: Add PHPUnit test and coverage results import (Nicole Cordes) 177 | 2017-12-17 88507ca [FEATURE] Add SonarQube Scanner to Travis CI (Nicole Cordes) 178 | 2017-12-17 1839044 [TASK] Travis CI: introduce build stages (Nicole Cordes) 179 | 2017-12-16 c66b848 [TASK] Fix coding style according to PHP-CS-Fixer (Nicole Cordes) 180 | 2017-12-16 14f94e6 [FEATURE] Add stricter rules for PHP-CS-Fixer and StyleCI (Nicole Cordes) 181 | 2017-12-16 bf87eca [BUGFIX] Remove wrong require statement (Nicole Cordes) 182 | 2017-12-16 66e4b8b [FEATURE] Optimize class loading using Composer ClassLoader (Nicole Cordes) 183 | 2017-12-15 e9f1e40 [FEATURE] Move Database classes to compat folder (Nicole Cordes) 184 | 2017-12-15 8fb0f38 [FEATURE] Move TestSystem classes to compat folder (Nicole Cordes) 185 | 2017-12-15 44e3bb6 [FEATURE] Move Bootstrap classes to compat folder (Nicole Cordes) 186 | 2017-12-15 8ab1ec4 [BUGFIX] Ensure default extension configuration is loaded (Nicole Cordes) 187 | 2017-12-14 5ef585a [BUGFIX] Prevent calling non-existing method injectReflectionService (Nicole Cordes) 188 | 2017-12-13 82edabd [FEATURE] Switch to PHPUnit 6 classes (Nicole Cordes) 189 | 2017-12-13 c507b34 [TASK] Adjust Travis CI configuration to current settings (Nicole Cordes) 190 | 2017-12-13 e6d1145 [!!!][TASK] Remove legacy and compat classes (Nicole Cordes) 191 | 2017-12-13 b990079 [FEATURE] Upgrade PHP and PHPUnit dependencies (Nicole Cordes) 192 | 2017-12-13 9b80fbf [TASK] Raise branch alias to 3.x-dev (Nicole Cordes) 193 | 194 | 2017-12-14 [RELEASE] Release of nimut/testing-framework 2.0.2 (Nicole Cordes) 195 | 2017-12-14 9f11259 [BUGFIX] Apply SYS/setDBinit to default database configuration in TestSystem (Nicole Cordes) 196 | 2017-12-14 1d11ca6 [BUGFIX] Add missing dependency typo3/cms-frontend (Nicole Cordes) 197 | 198 | 2017-11-15 [RELEASE] Release of nimut/testing-framework 2.0.1 (Nicole Cordes) 199 | 2017-10-29 d95e865 Update FunctionalTestCase.php (Nicole Cordes) 200 | 2017-10-27 68304b9 [BUGFIX] Avoid serialization of database (Mathias Brodala) 201 | 2017-09-13 b6ba24a [BUGFIX] Remove symlinks from pathsToLinkInTestInstance in tearDown (Nicole Cordes) 202 | 2017-09-13 e653c85 [TASK] Add additional test to ensure database exception is thrown (Nicole Cordes) 203 | 2017-09-12 a85f8d8 [FEATURE] Throws exception on database errors during Testsystem::setUp (Nicole Cordes) 204 | 2017-09-04 c082b01 [TASK] Add additional information links to README.md (Nicole Cordes) 205 | 206 | 2017-09-03 [RELEASE] Release of nimut/testing-framework 2.0.0 (Nicole Cordes) 207 | 2017-09-03 b21a595 [TASK] Add documentation for database abstraction layer (Nicole Cordes) 208 | 2017-09-02 7de1398 [BUGFIX] Search autoload.php in web root (Nicole Cordes) 209 | 2017-09-02 e0211e9 [BUGFIX] Prevent calling removed function in Bootstrap (Nicole Cordes) 210 | 2017-05-14 30bc0f4 [TASK] Raise branch alias (Nicole Cordes) 211 | 2017-05-14 469b01d [FEATURE] Add database abstraction for Doctrine DBAL api (Nicole Cordes) 212 | 2017-05-14 b7a560f [!!!][FEATURE] Introduce database abstraction layer (Nicole Cordes) 213 | 2017-09-02 2949fee [TASK] Downgrade Travis CI dist (Nicole Cordes) 214 | 215 | 2017-08-30 [RELEASE] Release of nimut/testing-framework 1.1.9 (Nicole Cordes) 216 | 2017-08-30 75f1a9d [TASK] Set an encryptionKey in functional test framework (Nicole Cordes) 217 | 218 | 2017-05-24 [RELEASE] Release of nimut/testing-framework 1.1.8 (Nicole Cordes) 219 | 2017-05-23 445ea06 [BUGFIX] Fix path to test environment (Helmut Hummel) 220 | 221 | 2017-05-14 [RELEASE] Release of nimut/testing-framework 1.1.7 (Nicole Cordes) 222 | 2017-05-14 bba9b4b [BUGFIX] Ensure TYPO3_CONTEXT is set before bootstrap initialization (Nicole Cordes) 223 | 224 | 2017-05-13 [RELEASE] Release of nimut/testing-framework 1.1.6 (Nicole Cordes) 225 | 2017-05-10 0325afb [BUGFIX] Prevent calling removed initializeTypo3DbGlobal function (Nicole Cordes) 226 | 2017-05-10 b1d3933 [BUGFIX] Prevent calling removed initializeTypo3DbGlobal function (Nicole Cordes) 227 | 2017-05-10 1925ca8 [BUGFIX] Correct class loading information in OldBootstrap for non-composer mode (Nicole Cordes) 228 | 2017-04-19 2796cc1 [TASK] Drop 6.2.x-dev build job on Travis CI (Nicole Cordes) 229 | 2017-04-19 b77b46b [BUGFIX] Add version alias for dev-master (Nicole Cordes) 230 | 231 | 2017-04-19 [RELEASE] Release of nimut/testing-framework 1.1.5 (Nicole Cordes) 232 | 2017-04-18 a8284d7 [BUGFIX] Fix detection of path to entry script for TYPO3 CMS 9.x (Nicole Cordes) 233 | 2017-04-17 4cf56aa [BUGFIX] Do not require TYPO3_OS constant to be set (Helmut Hummel) 234 | 2017-04-17 c7ac1a5 [BUGFIX] Avoid serialization of TestSystem (Helmut Hummel) 235 | 2017-04-10 ace2b41 [TASK] Reduce build jobs to stable versions only (Nicole Cordes) 236 | 237 | 2017-03-30 [RELEASE] Release of nimut/testing-framework 1.1.4 (Nicole Cordes) 238 | 2017-03-23 7b7af2d [BUGFIX] Try to stabilize mkdir for parallel test execution (Nicole Cordes) 239 | 240 | 2017-03-12 [RELEASE] Release of nimut/testing-framework 1.1.3 (Nicole Cordes) 241 | 2017-03-10 dc6c399 [BUGFIX] Support phpdbg (Mathias Brodala) 242 | 2017-03-05 76ef7ab [TASk] Move friendsofphp/php-cs-fixer to suggest section (Nicole Cordes) 243 | 244 | 2017-03-05 [RELEASE] Release of nimut/testing-framework 1.1.2 (Nicole Cordes) 245 | 2017-03-04 665eba1 [TASK] Bind Core\Bootstrap to Bootstrap classes (Nicole Cordes) 246 | 2017-03-04 6423258 [BUGFIX] Correct class loading information in non-composer mode (Nicole Cordes) 247 | 2017-03-04 1b4546b [TASK] Add more coding style rules (Nicole Cordes) 248 | 249 | 2017-03-03 [RELEASE] Release of nimut/testing-framework 1.1.1 (Nicole Cordes) 250 | 2017-03-03 a34716d [BUGFIX] Re-add moved bootstrap files for compatibility (Nicole Cordes) 251 | 252 | 2017-03-03 [RELEASE] Release of nimut/testing-framework 1.1.0 (Nicole Cordes) 253 | 2017-03-03 8cb754d [TASK] Streamline mk_dir usage and prevent warnings (Nicole Cordes) 254 | 2017-03-03 34cbba1 [TASK] Add branch alias 1.x-dev for dev-master (Nicole Cordes) 255 | 2017-03-02 ee6f85d [TASK] Loose typo3/cms dependencies to allow dev versions (Nicole Cordes) 256 | 2017-03-01 b6cbe7e [TASK] Rename Factory::getInstance() methods (Nicole Cordes) 257 | 2017-02-26 d789b9d [TASK] Remove coupling to Bootstrap\AbstractTestsBootstrap (Nicole Cordes) 258 | 2017-02-26 e92de5e [TASK] Refactor bootstrap classes (Nicole Cordes) 259 | 2017-02-26 98d0a96 [TASK] Remove Bootstrap\FunctionalTestCaseBootstrapUtility (Nicole Cordes) 260 | 2017-02-26 3ee4ae2 [TASK] Introduce TestSystem classes for functional test bootstrap (Nicole Cordes) 261 | 2017-02-25 baeb59d [TASK] Rename TestCase\BaseTestCase to TestCase\AbstractTestCase (Nicole Cordes) 262 | 2017-02-25 1c48c1e [TASK] Use checkout in Travis CI configuration (Nicole Cordes) 263 | 2017-02-25 54d2efe [TASK] Use single Bootstrap\Functional instances for different versions (Nicole Cordes) 264 | 2017-02-25 919bef8 [TASK] Use single Bootstrap\Unit instances for different versions (Nicole Cordes) 265 | 2017-02-25 53e354e [BUGFIX] Correct class loading in UnitTestsBootstrap for own tests (Nicole Cordes) 266 | 2017-02-24 c1a1a88 [BUGFIX] Correct UnitTestsBootstrap initialization order (Nicole Cordes) 267 | 2017-02-24 9d76ce5 [TASK] Split UnitTestsBootstrap.php into main and legacy classes (Nicole Cordes) 268 | 269 | 2017-02-24 [RELEASE] Release of nimut/testing-framework 1.0.1 (Nicole Cordes) 270 | 2017-02-24 c6db724 [BUGFIX] Add missing method getConnectionPool to FunctionalTestCase (Nicole Cordes) 271 | 2017-02-24 df18c81 [BUGFIX] Ensure tag property is set only for AccessibleMockObjectInterface (Nicole Cordes) 272 | 2017-02-24 dd17ebe [BUGFIX] Add missing use statement for TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperInterface (Nicole Cordes) 273 | 274 | 2017-02-23 [RELEASE] Release of nimut/testing-framework 1.0.0 (Nicole Cordes) 275 | 2017-02-23 4c7a531 [TASK] Add basic documentation of frontend requests to README.md (Nicole Cordes) 276 | 277 | 2017-02-23 [RELEASE] Release of nimut/testing-framework 0.4.1 (Nicole Cordes) 278 | 2017-02-23 4e04fa5 [TASK] Set testbase version to 0.5.0 (Nicole Cordes) 279 | 2017-02-23 bf30dd3 [TASK] Add test for own TypoScript in frontend request to testbase extension (Nicole Cordes) 280 | 2017-02-23 298b0ec [TASK] Be more verbose on frontend request error (Nicole Cordes) 281 | 2017-02-23 2287bd6 [BUGFIX] Move RequestBootstrap to correct folder (Nicole Cordes) 282 | 2017-02-23 440aa3f [BUGFIX] Support TypoScript files from extensions for frontend request (Nicole Cordes) 283 | 284 | 2017-02-23 [RELEASE] Release of nimut/testing-framework 0.4.0 (Nicole Cordes) 285 | 2017-02-23 ed0e8fc [TASK] Set testbase version to 0.4.0 (Nicole Cordes) 286 | 2017-02-23 6eae8ee [TASK] Reformat database fixtures code style (Nicole Cordes) 287 | 2017-02-23 5bd4647 [BUGFIX] Set necessary value for filelist_sorting in tt_content.xml (Nicole Cordes) 288 | 2017-02-23 ab3b894 [BUGFIX] Write TypoScript to sys_template record for frontend request (Nicole Cordes) 289 | 2017-02-22 e46480e [FEATURE] Add Request.tpl for frontend request in FunctionalTestCase (Nicole Cordes) 290 | 2017-02-22 e5001f2 [TASK] Add frontend request test to testbase extension (Nicole Cordes) 291 | 2017-02-22 c99f863 [FEATURE] Add classes for frontend request handling in functional tests (Nicole Cordes) 292 | 293 | 2017-02-22 [RELEASE] Release of nimut/testing-framework 0.3.1 (Nicole Cordes) 294 | 2017-02-22 22816a3 [BUGFIX] Fix NtfStreamWrapper for PHP 5.3 (Nicole Cordes) 295 | 2017-02-22 aa4e0ff [BUGFIX] Prevent expressions in empty method (Nicole Cordes) 296 | 2017-02-22 460bedf [TASK] Import fully qualified class names (Nicole Cordes) 297 | 2017-02-22 426afd9 [BUGFIX] Remove ::class usage for class names (Nicole Cordes) 298 | 2017-02-22 9337452 [BUGFIX] Switch from short to long array syntax for minor PHP versions (Nicole Cordes) 299 | 2017-02-22 5f944c1 [TASK] Add PHP 5.3 and 5.4 builds to Travis CI configuration (Nicole Cordes) 300 | 2017-02-22 16bb965 [TASK] Add PHP requirement to composer.json (Nicole Cordes) 301 | 2017-02-22 5e0f635 [TASK] Add documentation of the testing framework to README.me (Nicole Cordes) 302 | 2017-02-22 bf842f2 [TASK] Add metadata badges to README.md (Nicole Cordes) 303 | 2017-02-22 6d001f8 [TASK] Improve functional test class descriptions (Nicole Cordes) 304 | 2017-02-22 56e5e3b [TASK] Show mkdir errors (Nicole Cordes) 305 | 2017-02-22 bb02873 [TASK] Remove superfluous createDirectory call in FunctionalTestsBootstrap (Nicole Cordes) 306 | 2017-02-22 e8e6c2e [TASK] Add .gitatributes file to exclude development files from Git archives (Nicole Cordes) 307 | 2017-02-22 30c6674 [TASK] Remove unnecessary class-alias package (Nicole Cordes) 308 | 309 | 2017-02-22 [RELEASE] Release of nimut/testing-framework 0.3.0 (Nicole Cordes) 310 | 2017-02-22 15063be [TASK] Set testbase version to 0.3.0 (Nicole Cordes) 311 | 2017-02-21 903de19 [TASK] Add functional test for database fixture path resolving to testbase extension (Nicole Cordes) 312 | 2017-02-21 4b1fa53 [TASK] Add additional database fixtures (Nicole Cordes) 313 | 2017-02-21 11d7ed2 [FEATURE] Use ntf:// protocol for backendUserFixture in FunctionalTestCase (Nicole Cordes) 314 | 2017-02-21 8005956 [TASK] Register NtfStreamWrapper in test bootstraps (Nicole Cordes) 315 | 2017-02-21 4aded16 [FEATURE] Add stream wrapper for ntf:// protocol (Nicole Cordes) 316 | 317 | 2017-02-21 [RELEASE] Release of nimut/testing-framework 0.2.1 (Nicole Cordes) 318 | 2017-02-21 852bfdc [TASK] Set testbase version to 0.2.0 (Nicole Cordes) 319 | 2017-02-21 c3009ec [FEATURE] Add functional test classes and ensure backend user login (Nicole Cordes) 320 | 2017-02-21 204132a [BUGFIX] Use own getMock method in Nimut\TestingFramework\TestCase\ViewHelperBaseTestcase (Nicole Cordes) 321 | 2017-02-21 f288cf4 [BUGFIX] Add missing test class Nimut\TestingFramework\Rendering\RenderingContextFixture (Nicole Cordes) 322 | 2017-02-21 10a75ed [BUGFIX] Move TYPO3\CMS\Fluid\Tests\Unit\ViewHelpers\ViewHelperBaseTestcase compat class to correct subfolder (Nicole Cordes) 323 | 2017-02-21 c2ef0e3 [TASK] Include tests with typo3/cms dev source (Nicole Cordes) 324 | 2017-02-21 7a92568 [TASK] Add missing Travis CI build job (Nicole Cordes) 325 | 2017-02-21 5d1e00d [FEATURE] Add RenderChildrenViewHelper class and unit tests in testbase extension (Nicole Cordes) 326 | 2017-02-21 ed304b7 [TASK] Exclude ext_emconf.php from code style check (Nicole Cordes) 327 | 2017-02-21 098f566 [FEATURE] Add Mock class and Unit Tests in testbase extension (Nicole Cordes) 328 | 2017-02-21 70ef84b [FEATURE] Execute PHPUnit tests from own testbase extension (Nicole Cordes) 329 | 2017-02-21 dfa1a60 [FEATURE] Add basic UnitTest files (Nicole Cordes) 330 | 2017-02-21 43a83e8 [TASK] Add basic extension files (Nicole Cordes) 331 | 332 | 2017-02-20 [RELEASE] Release of nimut/testing-framework 0.2.0 (Nicole Cordes) 333 | 2017-02-16 31a0b37 [FEATURE] Add missing compatibility classes for TYPO3 CMS 8.6 (Nicole Cordes) 334 | 2017-02-16 22fd37a [FEATURE] Add support for TYPO3 CMS 6.2 (Nicole Cordes) 335 | 2017-02-16 e0d1cbc [FEATURE] Add functional test framework classes (Nicole Cordes) 336 | 2017-02-16 0c2b887 [FEATURE] Add basic functional testing classes (Nicole Cordes) 337 | 2017-02-16 59b00d2 [TASK] Ignore composer.lock file (Nicole Cordes) 338 | 339 | 2017-02-16 [RELEASE] Release of nimut/testing-framework 0.1.1 (Nicole Cordes) 340 | 2017-02-16 c6e0292 [BUGFIX] Use correct Exception class in compat/Exception.php (Nicole Cordes) 341 | 342 | 2017-02-16 [RELEASE] Release of nimut/testing-framework 0.1.0 (Nicole Cordes) 343 | 2017-02-16 c8d8cf9 [TASK] Ignore .Build folder (Nicole Cordes) 344 | 2017-02-16 a5f6e2a [TASK] Mention TYPO3 CMS project in license header (Nicole Cordes) 345 | 2017-02-16 533da27 [TASK] Provide TYPO3\Components\TestingFramework classes for dev only (Nicole Cordes) 346 | 2017-02-16 82d59d5 [TASK] Remove unnecessary class property in UnitTestCase (Nicole Cordes) 347 | 2017-02-16 7fa4fcc [TASK] Move compatibility classes to compat folder (Nicole Cordes) 348 | 2017-02-16 0c3a40a [TASK] Remove incompatible build job (Nicole Cordes) 349 | 2017-02-16 fea9f13 [TASK] Execute unit tests from typo3/cms-core (Nicole Cordes) 350 | 2017-02-16 c682eaa [FEATURE] Execute PHP lint tests on Travis CI (Nicole Cordes) 351 | 2017-02-16 cd00812 [TASK] Fix coding style according to php-cs-fixer (Nicole Cordes) 352 | 2017-02-16 c66577e [FEATURE] Add StyleCI Coding Style Service (Nicole Cordes) 353 | 2017-02-16 6b09981 [FEATURE] Add friendsofphp/php-cs-fixer as dev requirement (Nicole Cordes) 354 | 2017-02-16 b045a49 [TASK] Prevent deprecated method getMock in BaseTestCase (Nicole Cordes) 355 | 2017-02-16 3ec50b3 [FEATURE] Add Exception classes (Nicole Cordes) 356 | 2017-02-16 8a28904 [FEATURE] Add FileStreamWrapper classes (Nicole Cordes) 357 | 2017-02-16 59b027d [FEATURE] Add ViewHelperBaseTestCase classes (Nicole Cordes) 358 | 2017-02-16 0d1a345 [FEATURE] Add missing compatibility class for TYPO3 CMS 8.6 (Nicole Cordes) 359 | 2017-02-16 9e678fe [FEATURE] Add missing compatibility class for TYPO3 CMS 7.6 (Nicole Cordes) 360 | 2017-02-16 441b615 [FEATURE] Add support for TYPO3 CMS 6.2 (Nicole Cordes) 361 | 2017-02-16 84d6ca2 [TASK] Add README.md (Nicole Cordes) 362 | 2017-02-15 fab0af7 [TASK] Remove superfluous dependency to StringUtility in UnitTestCase (Nicole Cordes) 363 | 2017-02-15 2abb809 [TASK] Move configuration to root (Nicole Cordes) 364 | 2017-02-15 3d40095 [FEATURE] Add compatibility UnitTestCase class (Nicole Cordes) 365 | 2017-02-15 ab9477f [FEATURE] Add basic unit testing classes (Nicole Cordes) 366 | 2017-02-15 8934be4 [TASK] Add bootstrap file for Unit Tests (Nicole Cordes) 367 | 2017-02-15 690603f [TASK] Add UnitTests.xml configuration file (Nicole Cordes) 368 | 2017-02-15 4558f04 [TASK] Add composer.json (Nicole Cordes) 369 | 2017-02-14 9ee7bb2 [TASK] Ignore .idea folder (Nicole Cordes) 370 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Testing Framework for TYPO3 CMS Extensions 2 | 3 | There are no plans to support nimut/testing-framework for versions above TYPO3 v11. You are advised to switch to the 4 | typo3/testing-framework for TYPO3 v12 and above. The typo3/testing-framework has improved support for testing more 5 | than one version of the core. Further information and an introduction can be found in the official TYPO3 documentation: 6 | 7 | * [Testing Extensions](https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/Testing/ExtensionTesting.html) 8 | 9 |
10 | ℹ️ Show docs for TYPO3 9.5 up to 11.5 11 | 12 | [![Latest Stable Version](https://img.shields.io/packagist/v/nimut/testing-framework.svg)](https://packagist.org/packages/nimut/testing-framework) 13 | [![Build Status](https://img.shields.io/travis/Nimut/testing-framework/master.svg)](https://travis-ci.org/Nimut/testing-framework) 14 | [![StyleCI](https://styleci.io/repos/81999184/shield?branch=master)](https://styleci.io/repos/81999184) 15 | 16 | The aim of the testing framework is to provide a good way to write and run unit and functional tests for multiple versions 17 | of the TYPO3 CMS. Currently **TYPO3 CMS 9.5 up to 11.5** are tested and supported. 18 | 19 | ## Installation 20 | 21 | Use [Composer](https://getcomposer.org/) to install the testing framework. 22 | 23 | ```bash 24 | composer require --dev nimut/testing-framework 25 | ``` 26 | 27 | Composer will add the package as a dev requirement to your composer.json and install PHPUnit and vfsStream as its 28 | dependencies. 29 | 30 | ## Usage 31 | 32 | ### Unit Tests 33 | 34 | Inherit your test class from `\Nimut\TestingFramework\TestCase\UnitTestCase`. 35 | 36 | To execute the unit tests of your extension run 37 | 38 | ```bash 39 | vendor/bin/phpunit -c vendor/nimut/testing-framework/res/Configuration/UnitTests.xml \ 40 | typo3conf/ext/example_extension/Tests/Unit 41 | ``` 42 | 43 | #### ViewHelper 44 | 45 | For an easy way to test your Fluid ViewHelper you can inherit the test class from `\Nimut\TestingFramework\TestCase\ViewHelperBaseTestcase`. 46 | 47 | You should setup your subject class in your setUp() method of the test class: 48 | 49 | ```php 50 | /** 51 | * @var \PHPUnit_Framework_MockObject_MockObject 52 | */ 53 | protected $viewHelper; 54 | 55 | protected function setUp() 56 | { 57 | parent::setUp(); 58 | $this->viewHelper = $this->getMockBuilder(RenderChildrenViewHelper::class)->setMethods(['renderChildren'])->getMock(); 59 | $this->injectDependenciesIntoViewHelper($this->viewHelper); 60 | $this->viewHelper->initializeArguments(); 61 | } 62 | ``` 63 | 64 | ### Functional Tests 65 | 66 | Inherit your test class from `\Nimut\TestingFramework\TestCase\FunctionalTestCase`. 67 | 68 | To execute the functional tests of your extension run 69 | 70 | ```bash 71 | vendor/bin/phpunit -c vendor/nimut/testing-framework/res/Configuration/FunctionalTests.xml \ 72 | typo3conf/ext/example_extension/Tests/Functional 73 | ``` 74 | 75 | ### Extension preparation 76 | 77 | You can add a script section to your `composer.json` file to symlink your extension to the proper root-dir/web-dir. 78 | 79 | ```json 80 | "scripts": { 81 | "post-autoload-dump": [ 82 | "@prepare-extension-test-structure" 83 | ], 84 | "prepare-extension-test-structure": [ 85 | "Nimut\\TestingFramework\\Composer\\ExtensionTestEnvironment::prepare" 86 | ] 87 | } 88 | ``` 89 | 90 | ### Database abstraction 91 | 92 | To be able to test against TYPO3 CMS 8 and later, nimut/testing-framework provides an own database abstraction layer. 93 | 94 | In your FunctionalTestCase call `$this->getDatabaseConnection()` to get an instance of 95 | `\Nimut\TestingFramework\Database\DatabaseInterface`. 96 | 97 | Following database functions are built in the nimut/testing-framework database interface: 98 | 99 | - select 100 | - selectSingleRow 101 | - selectCount 102 | - insertArray 103 | - lastInsertId 104 | - updateArray 105 | - delete 106 | - getDatabaseInstance 107 | 108 | If you need own database requests you can get the proper database instance of the current TYPO3 version by using 109 | `$this->getDatabaseConnection()->getDatabaseInstance()`. You have to check weather this instance is a 110 | `\TYPO3\CMS\Core\Database\Query\QueryBuilder` (TYPO3 CMS 8 and above) or an instance of 111 | `\TYPO3\CMS\Core\Database\DatabaseConnection` (TYPO3 CMS 7). 112 | 113 | #### Database fixtures 114 | 115 | The nimut/testing-framework ships database fixtures for several TYPO3 CMS core database tables: 116 | 117 | - pages 118 | - pages_language_overlay 119 | - sys_file_storage 120 | - sys_language 121 | - tt_content 122 | 123 | To use the database fixtures you can trigger an import in your test file 124 | 125 | ```php 126 | $this->importDataSet('ntf://Database/pages.xml'); 127 | ``` 128 | 129 | ### Frontend requests 130 | 131 | The nimut/testing-framework ships an own TypoScript file for supporting frontend requests out of the box. 132 | 133 | ```php 134 | // First import some page records 135 | $this->importDataSet('ntf://Database/pages.xml'); 136 | 137 | // Import tt_content record that should be shown on your home page 138 | $this->importDataSet('ntf://Database/tt_content.xml'); 139 | 140 | // Setup the page with uid 1 and include the TypoScript as sys_template record 141 | $this->setUpFrontendRootPage(1, array('ntf://TypoScript/JsonRenderer.ts')); 142 | 143 | // Fetch the frontend response 144 | $response = $this->getFrontendResponse(1); 145 | 146 | // Assert no error has occured 147 | $this->assertSame('success', $response->getStatus()); 148 | 149 | // Get the first section from the response 150 | $sections = $response->getResponseSections(); 151 | $defaultSection = array_shift($sections); 152 | 153 | // Get the section structure 154 | $structure = $defaultSection->getStructure(); 155 | 156 | // Make assertions for the structure 157 | $this->assertTrue(is_array($structure['pages:1']['__contents']['tt_content:1'])); 158 | ``` 159 | 160 | #### Structure 161 | 162 | The returned structure of a frontend request is an array with some information about your page and its children. 163 | 164 | ```php 165 | [ 166 | // Page for your request 167 | 'pages:1' => [ 168 | 'uid' => '1', 169 | 'pid' => '0', 170 | 'sorting' => '0', 171 | 'title' => 'Root', 172 | // Array with subpages 173 | '__pages' => [ 174 | 'pages:2' => [ 175 | 'uid' => '2', 176 | 'pid' => '1', 177 | 'sorting' => '0', 178 | 'title' => 'Dummy 1-2', 179 | ], 180 | 'pages:5' => [ 181 | 'uid' => '5', 182 | 'pid' => '1', 183 | 'sorting' => '0', 184 | 'title' => 'Dummy 1-5', 185 | ], 186 | ], 187 | // Array with content elements 188 | '__contents' => [ 189 | 'tt_content:1' => [ 190 | 'uid' => '1', 191 | 'pid' => '1', 192 | 'sorting' => '0', 193 | 'header' => 'Test content', 194 | 'sys_language_uid' => '0', 195 | 'categories' => '0', 196 | ], 197 | ], 198 | ], 199 | ] 200 | ``` 201 | 202 | If you need additional information about a record, you can provide additional TypoScript with the needed configuration. 203 | 204 | ```php 205 | // Setup the page with uid 1 and include ntf and own TypoScript 206 | $this->setUpFrontendRootPage( 207 | 1, 208 | array( 209 | 'ntf://TypoScript/JsonRenderer.ts', 210 | 'EXT:example_extension/Tests/Functional/Fixtures/TypoScript/Config.ts' 211 | ) 212 | ); 213 | ``` 214 | 215 | Content of the TypoScript file *Config.ts* 216 | 217 | ``` 218 | config.watcher.tableFields.tt_content = uid,_ORIG_uid,_LOCALIZED_UID,pid,sorting,sys_language_uid,header,categories,CType,subheader,bodytext 219 | ``` 220 | ## Additional information 221 | 222 | Following links provide documentation and additional information about TYPO3 CMS extension testing 223 | 224 | - [Unit tests for dummies](https://de.slideshare.net/cpsitgmbh/unit-tests-for-dummies) 225 | - [How to write functional test](https://wiki.typo3.org/Functional_testing#How_to_write_functional_test.3F) 226 | - [Functional tests with TYPO3](https://de.slideshare.net/cpsitgmbh/functional-tests-with-typo3) 227 | - [Functional tests for dummies](https://de.slideshare.net/cpsitgmbh/functional-tests-for-dummies-65673214) 228 | 229 | Last but not least you may ask for further support in the Slack channel "[#cig-testing](https://typo3.slack.com/messages/cig-testing)". 230 | 231 |
-------------------------------------------------------------------------------- /compat/v10/Bootstrap/Bootstrap.php: -------------------------------------------------------------------------------- 1 | coreCacheName, new NullBackend('production', [])); 38 | $packageManager = CoreBootstrap::createPackageManager(UnitTestPackageManager::class, $coreCache); 39 | 40 | GeneralUtility::setSingletonInstance(PackageManager::class, $packageManager); 41 | ExtensionManagementUtility::setPackageManager($packageManager); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /compat/v10/TestCase/FunctionalTestCase.php: -------------------------------------------------------------------------------- 1 | importDataSet($this->backendUserFixture); 38 | $database = $this->getDatabaseConnection(); 39 | $userRow = $database->selectSingleRow('*', 'be_users', 'uid = ' . (int)$userUid); 40 | 41 | $backendUser = GeneralUtility::makeInstance(BackendUserAuthentication::class); 42 | $sessionId = $backendUser->createSessionId(); 43 | $_COOKIE[$backendUser->name] = $sessionId; 44 | $backendUser->id = $sessionId; 45 | $backendUser->sendNoCacheHeaders = false; 46 | $backendUser->dontSetCookie = true; 47 | $backendUser->lockIP = 0; 48 | $backendUser->createUserSession($userRow); 49 | 50 | $backendUser->start(); 51 | if (!is_array($backendUser->user) || !$backendUser->user['uid']) { 52 | throw new Exception( 53 | 'Can not initialize backend user', 54 | 1377095807 55 | ); 56 | } 57 | $backendUser->backendCheckLogin(); 58 | $GLOBALS['BE_USER'] = $backendUser; 59 | 60 | return $backendUser; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /compat/v10/TestCase/ViewHelperBaseTestcase.php: -------------------------------------------------------------------------------- 1 | expects($this->any())->method('setUseCacheHash')->will($this->returnValue($this->uriBuilder)); 29 | 30 | return $uriBuilder; 31 | } 32 | 33 | protected function getRequest() 34 | { 35 | return $this->prophesize(\TYPO3\CMS\Extbase\Mvc\Web\Request::class); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /compat/v10/TestSystem/TestSystem.php: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /res/Configuration/FunctionalTestsBootstrap.php: -------------------------------------------------------------------------------- 1 | bootstrapFunctionalTestSystem(); 36 | }); 37 | -------------------------------------------------------------------------------- /res/Configuration/UnitTests.xml: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /res/Configuration/UnitTestsBootstrap.php: -------------------------------------------------------------------------------- 1 | bootstrapUnitTestSystem(); 36 | }); 37 | -------------------------------------------------------------------------------- /res/Fixtures/Database/be_users.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | 0 6 | 1366642540 7 | admin 8 | $1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1 9 | 1 10 | 0 11 | 0 12 | 0 13 | 0 14 | 1366642540 15 | 0 16 | 1 17 | 0 18 | NULL 19 | 1371033743 20 | 0 21 | 22 | 23 | -------------------------------------------------------------------------------- /res/Fixtures/Database/pages.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | 0 6 | Root 7 | 0 8 | 15 9 | 10 | 11 | 2 12 | 1 13 | Dummy 1-2 14 | 0 15 | 15 16 | 17 | 18 | 3 19 | 2 20 | Dummy 1-2-3 21 | 0 22 | 15 23 | 24 | 25 | 4 26 | 3 27 | Dummy 1-2-3-4 28 | 0 29 | 15 30 | 31 | 32 | 5 33 | 1 34 | Dummy 1-5 35 | 0 36 | 15 37 | 38 | 39 | 6 40 | 5 41 | Dummy 1-5-6 42 | 0 43 | 15 44 | 45 | 46 | 7 47 | 0 48 | Root 2 49 | 0 50 | 15 51 | 52 | 53 | -------------------------------------------------------------------------------- /res/Fixtures/Database/pages_language_overlay.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | 1 6 | Root [Dansk] 7 | 0 8 | 0 9 | 1 10 | 11 | 12 | 2 13 | 1 14 | Root [Deutsch] 15 | 0 16 | 0 17 | 2 18 | 19 | 20 | -------------------------------------------------------------------------------- /res/Fixtures/Database/sys_file_storage.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | 0 6 | fileadmin/ (auto-created) 7 | typo3temp/assets/_processed_/ 8 | Local 9 | 10 | 11 | 12 | 13 | 14 | 15 | fileadmin/ 16 | 17 | 18 | relative 19 | 20 | 21 | 1 22 | 23 | 24 | 25 | 26 | ]]> 27 | 1 28 | 1 29 | 1 30 | 1 31 | 32 | 33 | -------------------------------------------------------------------------------- /res/Fixtures/Database/sys_language.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | 0 6 | 1277119475 7 | 0 8 | Dansk 9 | dk 10 | 11 | 12 | 2 13 | 0 14 | 1277119475 15 | 0 16 | Deutsch 17 | de 18 | 19 | 20 | -------------------------------------------------------------------------------- /res/Fixtures/Database/tt_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | 1 6 | header 7 |
Test content
8 | 0 9 | 0 10 | 0 11 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /res/Fixtures/Frontend/Request.tpl: -------------------------------------------------------------------------------- 1 | addRecordData 30 | 99 = RESTORE_REGISTER 31 | } 32 | 33 | lib.watcherFileObject = COA 34 | lib.watcherFileObject { 35 | 1 = LOAD_REGISTER 36 | 1.watcher.dataWrap = | 37 | 2 = USER 38 | 2.userFunc = Nimut\TestingFramework\Frontend\Collector->addFileData 39 | 99 = RESTORE_REGISTER 40 | } 41 | 42 | page = PAGE 43 | page { 44 | 10 = COA 45 | 10 { 46 | 1 = LOAD_REGISTER 47 | 1.watcher.dataWrap = pages:{field:uid} 48 | 2 = USER 49 | 2.userFunc = Nimut\TestingFramework\Frontend\Collector->addRecordData 50 | 10 = CONTENT 51 | 10 { 52 | stdWrap.required = 1 53 | table = pages 54 | select { 55 | orderBy = sorting 56 | pidInList = this 57 | # prevent sys_language_uid lookup 58 | languageField = 0 59 | } 60 | 61 | renderObj < lib.watcherDataObject 62 | renderObj.1.watcher.dataWrap = {register:watcher}|.__pages/pages:{field:uid} 63 | } 64 | 65 | 20 = CONTENT 66 | 20 { 67 | table = tt_content 68 | select { 69 | orderBy = sorting 70 | where = colPos=0 71 | } 72 | 73 | renderObj < lib.watcherDataObject 74 | renderObj.1.watcher.dataWrap = {register:watcher}|.__contents/tt_content:{field:uid} 75 | renderObj { 76 | 10 = CONTENT 77 | 10 { 78 | if.isTrue.field = categories 79 | table = sys_category 80 | select { 81 | pidInList = root,-1 82 | selectFields = sys_category.* 83 | join = sys_category_record_mm ON sys_category_record_mm.uid_local = sys_category.uid 84 | where.data = field:_ORIG_uid // field:uid 85 | where.intval = 1 86 | where.wrap = sys_category_record_mm.uid_foreign=| 87 | orderBy = sys_category_record_mm.sorting_foreign 88 | languageField = sys_category.sys_language_uid 89 | } 90 | 91 | renderObj < lib.watcherDataObject 92 | renderObj.1.watcher.dataWrap = {register:watcher}|.categories/sys_category:{field:uid} 93 | } 94 | 95 | 40 = FILES 96 | 40 { 97 | if.isTrue.field = image 98 | references { 99 | fieldName = image 100 | } 101 | 102 | renderObj < lib.watcherFileObject 103 | renderObj.1.watcher.dataWrap = {register:watcher}|.image/ 104 | } 105 | } 106 | } 107 | 108 | stdWrap.postUserFunc = Nimut\TestingFramework\Frontend\Collector->attachSection 109 | stdWrap.postUserFunc.as = Default 110 | } 111 | 112 | stdWrap.postUserFunc = Nimut\TestingFramework\Frontend\Renderer->renderSections 113 | } 114 | 115 | [globalVar = GP:L = 1] 116 | config.sys_language_uid = 1 117 | [end] 118 | 119 | [globalVar = GP:L = 2] 120 | config.sys_language_uid = 2 121 | [end] 122 | -------------------------------------------------------------------------------- /res/Fixtures/TypoScript/JsonRendererNoOverlay.ts: -------------------------------------------------------------------------------- 1 | config { 2 | sys_language_overlay = 0 3 | } -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=testing-framework 2 | sonar.projectName=nimut/testing-framework 3 | sonar.projectVersion=5.x 4 | sonar.sources=. 5 | sonar.exclusions=.Build/**, tests/Packages/** 6 | 7 | # Set Database Cleaner limits 8 | sonar.dbcleaner.hoursBeforeKeepingOnlyOneSnapshotByDay=24 9 | sonar.dbcleaner.weeksBeforeKeepingOnlyOneSnapshotByWeek=12 10 | sonar.dbcleaner.weeksBeforeKeepingOnlyOneSnapshotByMonth=52 11 | 12 | # Ignore issues on multiple criteria 13 | sonar.issue.ignore.multicriteria = e1 14 | 15 | # Exclude "String literals should not be duplicated" 16 | sonar.issue.ignore.multicriteria.e1.ruleKey=php:S1192 17 | sonar.issue.ignore.multicriteria.e1.resourceKey=**/*.php 18 | 19 | # PHPUnit test and coverage results import 20 | sonar.php.tests.reportPath=.Log/junit.xml 21 | sonar.php.coverage.reportPaths=.Log/coverage.xml 22 | -------------------------------------------------------------------------------- /src/TestingFramework/Bootstrap/AbstractBootstrap.php: -------------------------------------------------------------------------------- 1 | getClassLoaderFilepath(); 49 | $classLoader = require $classLoaderFilepath; 50 | 51 | SystemEnvironmentBuilder::run(0, SystemEnvironmentBuilder::REQUESTTYPE_BE | SystemEnvironmentBuilder::REQUESTTYPE_CLI); 52 | CoreBootstrap::initializeClassLoader($classLoader); 53 | CoreBootstrap::baseSetup(); 54 | } 55 | 56 | /** 57 | * Bootstraps the system for functional tests 58 | * 59 | * @return void 60 | */ 61 | public function bootstrapFunctionalTestSystem() 62 | { 63 | $this->enableDisplayErrors(); 64 | $this->createNecessaryDirectoriesInDocumentRoot(); 65 | $this->defineOriginalRootPath(); 66 | } 67 | 68 | /** 69 | * Bootstraps the system for unit tests 70 | * 71 | * @return void 72 | */ 73 | public function bootstrapUnitTestSystem() 74 | { 75 | $this->enableDisplayErrors(); 76 | $this->createNecessaryDirectoriesInDocumentRoot(); 77 | $this->defineSitePath(); 78 | $this->setTypo3Context(); 79 | $this->includeAndStartCoreBootstrap(); 80 | $this->initializeConfiguration(); 81 | $this->initializePackageManager(); 82 | $this->dumpAutoloadFiles(); 83 | $this->registerNtfStreamWrapper(); 84 | } 85 | 86 | /** 87 | * Makes sure error messages during the tests get displayed no matter what is set in php.ini. 88 | * 89 | * @return void 90 | */ 91 | protected function enableDisplayErrors() 92 | { 93 | @ini_set('display_errors', 1); 94 | @error_reporting(E_ALL & ~(E_STRICT | E_NOTICE | E_DEPRECATED)); 95 | } 96 | 97 | /** 98 | * Creates the following directories in the TYPO3 document root: 99 | * - typo3conf/ext 100 | * - typo3temp/assets 101 | * - typo3temp/var/tests 102 | * - typo3temp/var/transient 103 | * - uploads 104 | * 105 | * @return void 106 | */ 107 | protected function createNecessaryDirectoriesInDocumentRoot() 108 | { 109 | $webRoot = $this->getWebRoot(); 110 | $this->createDirectory($webRoot . 'typo3conf/ext'); 111 | $this->createDirectory($webRoot . 'typo3temp/assets'); 112 | $this->createDirectory($webRoot . 'typo3temp/var/tests'); 113 | $this->createDirectory($webRoot . 'typo3temp/var/transient'); 114 | $this->createDirectory($webRoot . 'uploads'); 115 | } 116 | 117 | /** 118 | * Defines the constant ORIGINAL_ROOT for the path to the original TYPO3 document root 119 | * 120 | * @return void 121 | */ 122 | protected function defineOriginalRootPath() 123 | { 124 | if (!defined('ORIGINAL_ROOT')) { 125 | /** @var string */ 126 | define('ORIGINAL_ROOT', $this->getWebRoot()); 127 | } 128 | 129 | if (!file_exists(ORIGINAL_ROOT . 'typo3/index.php')) { 130 | $this->exitWithMessage( 131 | 'Unable to determine path to entry script.' 132 | . ' Please check your path or set an environment variable \'TYPO3_PATH_ROOT\' to your root path.' 133 | ); 134 | } 135 | } 136 | 137 | /** 138 | * Returns the absolute path to the TYPO3 document root 139 | * 140 | * @return string the TYPO3 document root using Unix path separators 141 | */ 142 | protected function getWebRoot() 143 | { 144 | if (getenv('TYPO3_PATH_ROOT')) { 145 | $webRoot = getenv('TYPO3_PATH_ROOT'); 146 | } elseif (getenv('TYPO3_PATH_WEB')) { 147 | $webRoot = getenv('TYPO3_PATH_WEB'); 148 | } else { 149 | $webRoot = getcwd(); 150 | } 151 | 152 | return rtrim(str_replace('\\', '/', $webRoot), '/') . '/'; 153 | } 154 | 155 | /** 156 | * Creates the directory $directory (recursively if required). 157 | * 158 | * If $directory already exists, this method is a no-op. 159 | * 160 | * @param string $directory absolute path of the directory to be created 161 | * @throws \RuntimeException 162 | * @return void 163 | */ 164 | protected function createDirectory($directory) 165 | { 166 | clearstatcache(); 167 | if (is_dir($directory)) { 168 | return; 169 | } 170 | 171 | if (!@mkdir($directory, 0777, true)) { 172 | // Wait a couple of microseconds to prevent multiple derectory access due to parallel testing 173 | usleep(mt_rand(1000, 2000)); 174 | if (!@mkdir($directory, 0777, true) && !is_dir($directory)) { 175 | throw new \RuntimeException('Directory "' . $directory . '" could not be created', 1423043755); 176 | } 177 | } 178 | } 179 | 180 | /** 181 | * Echo out a text message and exit with error code 182 | * 183 | * @param string $message 184 | */ 185 | protected function exitWithMessage($message) 186 | { 187 | echo $message . PHP_EOL; 188 | exit(1); 189 | } 190 | 191 | /** 192 | * Defines the PATH_site, PATH_thisScript constant and sets $_SERVER['SCRIPT_NAME']. 193 | * 194 | * @return void 195 | */ 196 | protected function defineSitePath() 197 | { 198 | /** @var string */ 199 | define('PATH_site', $this->getWebRoot()); 200 | /** @var string */ 201 | define('PATH_thisScript', PATH_site . 'typo3/index.php'); 202 | $_SERVER['SCRIPT_NAME'] = PATH_thisScript; 203 | 204 | if (!file_exists(PATH_thisScript)) { 205 | $this->exitWithMessage('Unable to determine path to entry script. Please check your path or set an environment variable \'TYPO3_PATH_ROOT\' to your root path.'); 206 | } 207 | } 208 | 209 | /** 210 | * Defines some constants and sets the environment variable TYPO3_CONTEXT 211 | * 212 | * @return void 213 | */ 214 | protected function setTypo3Context() 215 | { 216 | /** @var string */ 217 | define('TYPO3_MODE', 'BE'); 218 | /** @var string */ 219 | define('TYPO3_cliMode', true); 220 | // Disable TYPO3_DLOG 221 | define('TYPO3_DLOG', false); 222 | } 223 | 224 | /** 225 | * Provides the default configuration in $GLOBALS['TYPO3_CONF_VARS'] 226 | * 227 | * @return void 228 | */ 229 | protected function initializeConfiguration() 230 | { 231 | $configurationManager = new ConfigurationManager(); 232 | $GLOBALS['TYPO3_CONF_VARS'] = $configurationManager->getDefaultConfiguration(); 233 | 234 | // Avoid failing tests that rely on HTTP_HOST retrieval 235 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = '.*'; 236 | } 237 | 238 | /** 239 | * Initializes a package manager for tests that activates all packages by default 240 | * 241 | * @return void 242 | */ 243 | protected function initializePackageManager() 244 | { 245 | $coreCache = new PhpFrontend($this->coreCacheName, new NullBackend('production', [])); 246 | $packageCache = CoreBootstrap::createPackageCache($coreCache); 247 | $packageManager = CoreBootstrap::createPackageManager(UnitTestPackageManager::class, $packageCache); 248 | 249 | GeneralUtility::setSingletonInstance(PackageManager::class, $packageManager); 250 | ExtensionManagementUtility::setPackageManager($packageManager); 251 | } 252 | 253 | /** 254 | * Dump autoload info if in non composer mode 255 | * 256 | * @return void 257 | */ 258 | protected function dumpAutoloadFiles() 259 | { 260 | if (!ClassLoadingInformation::isClassLoadingInformationAvailable()) { 261 | ClassLoadingInformation::dumpClassLoadingInformation(); 262 | ClassLoadingInformation::registerClassLoadingInformation(); 263 | } 264 | } 265 | 266 | /** 267 | * Registers the NtfStreamWrapper for ntf:// protocol 268 | * 269 | * @return void 270 | */ 271 | protected function registerNtfStreamWrapper() 272 | { 273 | NtfStreamWrapper::register(); 274 | } 275 | 276 | /** 277 | * Defines a list of basic constants that are used by GeneralUtility and other 278 | * helpers during tests setup. Those are sanitized in SystemEnvironmentBuilder 279 | * to be not defined again. 280 | * 281 | * @return void 282 | * @see SystemEnvironmentBuilder::defineBaseConstants() 283 | */ 284 | protected function defineBaseConstants() 285 | { 286 | // A null, a tabulator, a linefeed, a carriage return, a substitution, a CR-LF combination 287 | defined('NUL') ?: define('NUL', chr(0)); 288 | defined('TAB') ?: define('TAB', chr(9)); 289 | defined('LF') ?: define('LF', chr(10)); 290 | defined('CR') ?: define('CR', chr(13)); 291 | defined('SUB') ?: define('SUB', chr(26)); 292 | defined('CRLF') ?: define('CRLF', CR . LF); 293 | 294 | if (!defined('TYPO3_OS')) { 295 | // Operating system identifier 296 | // Either "WIN" or empty string 297 | $typoOs = ''; 298 | if (!stristr(PHP_OS, 'darwin') && !stristr(PHP_OS, 'cygwin') && stristr(PHP_OS, 'win')) { 299 | $typoOs = 'WIN'; 300 | } 301 | define('TYPO3_OS', $typoOs); 302 | } 303 | } 304 | 305 | /** 306 | * Checks and returns the file path of the autoload.php 307 | * 308 | * @return string 309 | */ 310 | protected function getClassLoaderFilepath() 311 | { 312 | $classLoaderFilepath = __DIR__ . '/../../../../../autoload.php'; 313 | if (!file_exists($classLoaderFilepath)) { 314 | if (file_exists(__DIR__ . '/../../../.Build/vendor/autoload.php')) { 315 | $classLoaderFilepath = __DIR__ . '/../../../.Build/vendor/autoload.php'; 316 | } elseif (file_exists($this->getWebRoot() . '../vendor/autoload.php')) { 317 | $classLoaderFilepath = $this->getWebRoot() . '../vendor/autoload.php'; 318 | } else { 319 | $this->exitWithMessage( 320 | 'ClassLoader can\'t be loaded.' 321 | . ' Tried to find "' . $classLoaderFilepath . '".' 322 | . ' Please check your path or set an environment variable \'TYPO3_PATH_ROOT\' to your root path.' 323 | ); 324 | } 325 | } 326 | 327 | return $classLoaderFilepath; 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /src/TestingFramework/Bootstrap/Bootstrap.php: -------------------------------------------------------------------------------- 1 | = 9.2 18 | */ 19 | class Bootstrap extends AbstractBootstrap 20 | { 21 | /** 22 | * Defines the constant ORIGINAL_ROOT for the path to the original TYPO3 document root 23 | * 24 | * @return void 25 | */ 26 | protected function defineOriginalRootPath() 27 | { 28 | parent::defineOriginalRootPath(); 29 | $this->defineBaseConstants(); 30 | } 31 | 32 | /** 33 | * Defines some constants and sets the environment variable TYPO3_CONTEXT 34 | * 35 | * @return void 36 | */ 37 | protected function setTypo3Context() 38 | { 39 | parent::setTypo3Context(); 40 | $this->defineBaseConstants(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/TestingFramework/Bootstrap/BootstrapFactory.php: -------------------------------------------------------------------------------- 1 | findFile($compatibilityClassName)) { 51 | require $file; 52 | class_alias($compatibilityClassName, $className); 53 | } 54 | }, 55 | true, 56 | true 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/TestingFramework/Bootstrap/RequestBootstrap.php: -------------------------------------------------------------------------------- 1 | 'failure', 'content' => null, 'error' => null]; 95 | 96 | ob_start(); 97 | try { 98 | chdir($_SERVER['DOCUMENT_ROOT']); 99 | include($_SERVER['SCRIPT_FILENAME']); 100 | $result['status'] = 'success'; 101 | $result['content'] = ob_get_contents(); 102 | } catch (\Exception $exception) { 103 | $result['error'] = $exception->__toString(); 104 | } 105 | ob_end_clean(); 106 | 107 | echo json_encode($result); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/TestingFramework/Bootstrap/SystemEnvironmentBuilder.php: -------------------------------------------------------------------------------- 1 | /typo3conf/ext/". 28 | * 29 | * This class is added as composer "script" in TYPO3 extensions: 30 | * 31 | * "scripts": { 32 | * "post-autoload-dump": [ 33 | * "@prepare-extension-test-structure" 34 | * ], 35 | * "prepare-extension-test-structure": [ 36 | * "Nimut\\TestingFramework\\Composer\\ExtensionTestEnvironment::prepare" 37 | * ] 38 | * }, 39 | * 40 | * It additionally needs the "extension key" (that will become the directory name in 41 | * typo3conf/ext) and the name of the target directory in the extra section. Example for 42 | * a extension "my_extension": 43 | * 44 | * "extra": { 45 | * "typo3/cms": { 46 | * "web-dir": ".Build/public", 47 | * "extension-key": "my_extension" 48 | * } 49 | * } 50 | */ 51 | class ExtensionTestEnvironment 52 | { 53 | /** 54 | * Link base directory as .//typo3conf/ext/ 55 | */ 56 | public static function prepare(Event $event) 57 | { 58 | $composer = $event->getComposer(); 59 | $io = $event->getIO(); 60 | 61 | $config = Config::load($composer, $io); 62 | $rootPackage = $composer->getPackage(); 63 | 64 | $rootDir = $config->get('root-dir'); 65 | $extensionKey = ExtensionKeyResolver::resolve($rootPackage); 66 | 67 | $typo3ExtDir = $rootDir . '/typo3conf/ext'; 68 | $extDir = $typo3ExtDir . '/' . $extensionKey; 69 | $fileSystem = new Filesystem(); 70 | $fileSystem->ensureDirectoryExists($typo3ExtDir); 71 | 72 | if (!file_exists($extDir)) { 73 | symlink($config->getBaseDir(), $extDir); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/TestingFramework/Database/Database.php: -------------------------------------------------------------------------------- 1 | connection = $connection ?? GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\ConnectionPool') 35 | ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME); 36 | } 37 | 38 | /** 39 | * @param string $fields 40 | * @param string $table 41 | * @param string $where 42 | * @return int|Statement 43 | */ 44 | public function select($fields, $table, $where) 45 | { 46 | return $this->getDatabaseInstance() 47 | ->select($fields) 48 | ->from($table) 49 | ->where($where) 50 | ->execute(); 51 | } 52 | 53 | /** 54 | * @param string $fields 55 | * @param string $table 56 | * @param string $where 57 | * @return array 58 | */ 59 | public function selectSingleRow($fields, $table, $where) 60 | { 61 | return $this->getDatabaseInstance() 62 | ->select($fields) 63 | ->from($table) 64 | ->where($where) 65 | ->execute() 66 | ->fetch(); 67 | } 68 | 69 | /** 70 | * @param string $fields 71 | * @param string $table 72 | * @param string $where 73 | * @return int 74 | */ 75 | public function selectCount($fields, $table, $where = '1=1') 76 | { 77 | return (int)$this->getDatabaseInstance() 78 | ->count($fields) 79 | ->from($table) 80 | ->where($where) 81 | ->execute() 82 | ->fetchColumn(0); 83 | } 84 | 85 | /** 86 | * @param string $table 87 | * @param array $insertArray 88 | * @return int 89 | */ 90 | public function insertArray($table, array $insertArray) 91 | { 92 | return $this->connection->insert($table, $insertArray); 93 | } 94 | 95 | /** 96 | * @return int 97 | */ 98 | public function lastInsertId() 99 | { 100 | return $this->connection->lastInsertId(); 101 | } 102 | 103 | /** 104 | * @param string $table 105 | * @param array $whereArray 106 | * @param array $updateArray 107 | * @return mixed 108 | */ 109 | public function updateArray($table, array $whereArray, array $updateArray) 110 | { 111 | return $this->connection->update($table, $updateArray, $whereArray); 112 | } 113 | 114 | /** 115 | * @param string $table 116 | * @param array $whereArray 117 | * @return mixed 118 | */ 119 | public function delete($table, array $whereArray) 120 | { 121 | return $this->connection->delete($table, $whereArray); 122 | } 123 | 124 | /** 125 | * @return QueryBuilder 126 | */ 127 | public function getDatabaseInstance() 128 | { 129 | $queryBuilder = $this->connection->createQueryBuilder(); 130 | $queryBuilder->getRestrictions()->removeAll(); 131 | 132 | return $queryBuilder; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/TestingFramework/Database/DatabaseInterface.php: -------------------------------------------------------------------------------- 1 | 32 | * use org\bovigo\vfs\vfsStream; 33 | * use org\bovigo\vfs\visitor\vfsStreamStructureVisitor; 34 | * 35 | * 36 | * 37 | * $root = \org\bovigo\vfs\vfsStream::setup('root'); 38 | * $subfolder = \org\bovigo\vfs\vfsStream::newDirectory('fileadmin'); 39 | * $root->addChild($subfolder); 40 | * // Load fixture files and folders from disk 41 | * \org\bovigo\vfs\vfsStream::copyFromFileSystem(__DIR__ . '/Fixture/Files', $subfolder, 1024*1024); 42 | * FileStreamWrapper::init(PATH_site); 43 | * FileStreamWrapper::registerOverlayPath('fileadmin', 'vfs://root/fileadmin'); 44 | * 45 | * // Use file functions as usual 46 | * mkdir(PATH_site . 'fileadmin/test/'); 47 | * $file = PATH_site . 'fileadmin/test/Foo.bar'; 48 | * file_put_contents($file, 'Baz'); 49 | * $content = file_get_contents($file); 50 | * $this->assertSame('Baz', $content); 51 | * 52 | * $this->assertEqual(**array(file system structure as array**), vfsStream::inspect(new vfsStreamStructureVisitor())->getStructure()); 53 | * 54 | * FileStreamWrapper::destroy(); 55 | * 56 | * 57 | * @see http://www.php.net/manual/en/class.streamwrapper.php 58 | */ 59 | class FileStreamWrapper 60 | { 61 | /** 62 | * @var resource 63 | */ 64 | protected $dirHandle = null; 65 | 66 | /** 67 | * @var resource 68 | */ 69 | protected $fileHandle = null; 70 | 71 | /** 72 | * Switch whether class has already been registered as stream wrapper or not 73 | * 74 | * @type bool 75 | */ 76 | protected static $registered = false; 77 | 78 | /** 79 | * Array of paths to overlay 80 | * 81 | * @var array 82 | */ 83 | protected static $overlayPaths = []; 84 | 85 | /** 86 | * The first part of each (absolute) path that shall be ignored 87 | * 88 | * @var string 89 | */ 90 | protected static $rootPath = ''; 91 | 92 | /** 93 | * Initialize the stream wrapper with a root path and register itself 94 | * 95 | * @param $rootPath 96 | * @return void 97 | */ 98 | public static function init($rootPath) 99 | { 100 | self::$rootPath = rtrim(str_replace('\\', '/', $rootPath), '/') . '/'; 101 | self::register(); 102 | } 103 | 104 | /** 105 | * Unregister the stream wrapper and reset all static members to their default values 106 | * 107 | * @return void 108 | */ 109 | public static function destroy() 110 | { 111 | self::$overlayPaths = []; 112 | self::$rootPath = ''; 113 | if (self::$registered) { 114 | self::restore(); 115 | } 116 | } 117 | 118 | /** 119 | * Register a path relative to the root path (set in init) to be overlaid 120 | * 121 | * @param string $overlay Relative path to the root folder 122 | * @param string $replace The path that should replace the overlay path 123 | * @param bool $createFolder TRUE of the folder should be created (mkdir) 124 | * @return void 125 | */ 126 | public static function registerOverlayPath($overlay, $replace, $createFolder = true) 127 | { 128 | $overlay = trim(str_replace('\\', '/', $overlay), '/') . '/'; 129 | $replace = rtrim(str_replace('\\', '/', $replace), '/') . '/'; 130 | self::$overlayPaths[$overlay] = $replace; 131 | if ($createFolder) { 132 | mkdir($replace); 133 | } 134 | } 135 | 136 | /** 137 | * Checks and overlays a path 138 | * 139 | * @param string $path The path to check 140 | * @return string The potentially overlaid path 141 | */ 142 | protected static function overlayPath($path) 143 | { 144 | $path = str_replace('\\', '/', $path); 145 | $hasOverlay = false; 146 | if (strpos($path, self::$rootPath) !== 0) { 147 | // Path is not below root path, ignore it 148 | return $path; 149 | } 150 | 151 | $newPath = ltrim(substr($path, strlen(self::$rootPath)), '/'); 152 | foreach (self::$overlayPaths as $overlay => $replace) { 153 | if (strpos($newPath, $overlay) === 0) { 154 | $newPath = $replace . substr($newPath, strlen($overlay)); 155 | $hasOverlay = true; 156 | break; 157 | } 158 | } 159 | 160 | return $hasOverlay ? $newPath : $path; 161 | } 162 | 163 | /** 164 | * Method to register the stream wrapper 165 | * 166 | * If the stream is already registered the method returns silently. If there 167 | * is already another stream wrapper registered for the scheme used by 168 | * file:// scheme a \BadFunctionCallException will be thrown. 169 | * 170 | * @throws \BadFunctionCallException 171 | * @return void 172 | */ 173 | protected static function register() 174 | { 175 | if (self::$registered) { 176 | return; 177 | } 178 | 179 | if (@stream_wrapper_unregister('file') === false) { 180 | throw new \BadFunctionCallException('Cannot unregister file:// stream wrapper.', 1396340331); 181 | } 182 | if (@stream_wrapper_register('file', __CLASS__) === false) { 183 | throw new \BadFunctionCallException('A handler has already been registered for the file:// scheme.', 1396340332); 184 | } 185 | 186 | self::$registered = true; 187 | } 188 | 189 | /** 190 | * Restore the file handler 191 | * 192 | * @throws \BadFunctionCallException 193 | * @return void 194 | */ 195 | protected static function restore() 196 | { 197 | if (!self::$registered) { 198 | return; 199 | } 200 | if (@stream_wrapper_restore('file') === false) { 201 | throw new \BadFunctionCallException('Cannot restore the default file:// stream handler.', 1396340333); 202 | } 203 | self::$registered = false; 204 | } 205 | 206 | /* 207 | * The following list of functions is implemented as of 208 | * @see http://www.php.net/manual/en/streamwrapper.dir-closedir.php 209 | */ 210 | 211 | /** 212 | * Close the directory 213 | * 214 | * @return bool 215 | */ 216 | public function dir_closedir() 217 | { 218 | if ($this->dirHandle === null) { 219 | return false; 220 | } 221 | self::restore(); 222 | closedir($this->dirHandle); 223 | self::register(); 224 | $this->dirHandle = null; 225 | 226 | return true; 227 | } 228 | 229 | /** 230 | * Opens a directory for reading 231 | * 232 | * @param string $path 233 | * @param int $options 234 | * @return bool 235 | */ 236 | public function dir_opendir($path, $options = 0) 237 | { 238 | if ($this->dirHandle !== null) { 239 | return false; 240 | } 241 | self::restore(); 242 | $path = self::overlayPath($path); 243 | $this->dirHandle = opendir($path); 244 | self::register(); 245 | 246 | return $this->dirHandle !== false; 247 | } 248 | 249 | /** 250 | * Read a single filename of a directory 251 | * 252 | * @return string|bool 253 | */ 254 | public function dir_readdir() 255 | { 256 | if ($this->dirHandle === null) { 257 | return false; 258 | } 259 | self::restore(); 260 | $success = readdir($this->dirHandle); 261 | self::register(); 262 | 263 | return $success; 264 | } 265 | 266 | /** 267 | * Reset directory name pointer 268 | * 269 | * @return bool 270 | */ 271 | public function dir_rewinddir() 272 | { 273 | if ($this->dirHandle === null) { 274 | return false; 275 | } 276 | self::restore(); 277 | rewinddir($this->dirHandle); 278 | self::register(); 279 | 280 | return true; 281 | } 282 | 283 | /** 284 | * Create a directory 285 | * 286 | * @param string $path 287 | * @param int $mode 288 | * @param int $options 289 | * @return bool 290 | */ 291 | public function mkdir($path, $mode, $options = 0) 292 | { 293 | self::restore(); 294 | $path = self::overlayPath($path); 295 | $success = mkdir($path, $mode, (bool)($options & STREAM_MKDIR_RECURSIVE)); 296 | self::register(); 297 | 298 | return $success; 299 | } 300 | 301 | /** 302 | * Rename a file 303 | * 304 | * @param string $pathFrom 305 | * @param string $pathTo 306 | * @return bool 307 | */ 308 | public function rename($pathFrom, $pathTo) 309 | { 310 | self::restore(); 311 | $pathFrom = self::overlayPath($pathFrom); 312 | $pathTo = self::overlayPath($pathTo); 313 | $success = rename($pathFrom, $pathTo); 314 | self::register(); 315 | 316 | return $success; 317 | } 318 | 319 | /** 320 | * Remove a directory 321 | * 322 | * @param string $path 323 | * @return bool 324 | */ 325 | public function rmdir($path) 326 | { 327 | self::restore(); 328 | $path = self::overlayPath($path); 329 | $success = rmdir($path); 330 | self::register(); 331 | 332 | return $success; 333 | } 334 | 335 | /** 336 | * Retrieve the underlying resource 337 | * 338 | * @param int $castAs can be STREAM_CAST_FOR_SELECT when stream_select() 339 | * is calling stream_cast() or STREAM_CAST_AS_STREAM when stream_cast() 340 | * is called for other uses 341 | * @return resource|bool 342 | */ 343 | public function stream_cast($castAs) 344 | { 345 | if ($this->fileHandle !== null && $castAs & STREAM_CAST_AS_STREAM) { 346 | return $this->fileHandle; 347 | } 348 | 349 | return false; 350 | } 351 | 352 | /** 353 | * Close a file 354 | */ 355 | public function stream_close() 356 | { 357 | self::restore(); 358 | if ($this->fileHandle !== null) { 359 | fclose($this->fileHandle); 360 | $this->fileHandle = null; 361 | } 362 | self::register(); 363 | } 364 | 365 | /** 366 | * Test for end-of-file on a file pointer 367 | * 368 | * @return bool 369 | */ 370 | public function stream_eof() 371 | { 372 | if ($this->fileHandle === null) { 373 | return false; 374 | } 375 | self::restore(); 376 | $success = feof($this->fileHandle); 377 | self::register(); 378 | 379 | return $success; 380 | } 381 | 382 | /** 383 | * Flush the output 384 | * 385 | * @return bool 386 | */ 387 | public function stream_flush() 388 | { 389 | if ($this->fileHandle === null) { 390 | return false; 391 | } 392 | self::restore(); 393 | $success = fflush($this->fileHandle); 394 | self::register(); 395 | 396 | return $success; 397 | } 398 | 399 | /** 400 | * Advisory file locking 401 | * 402 | * @param int $operation 403 | * @return bool 404 | */ 405 | public function stream_lock($operation) 406 | { 407 | if ($this->fileHandle === null) { 408 | return false; 409 | } 410 | self::restore(); 411 | $success = flock($this->fileHandle, $operation); 412 | self::register(); 413 | 414 | return $success; 415 | } 416 | 417 | /** 418 | * Change file options 419 | * 420 | * @param string $path 421 | * @param int $options 422 | * @param mixed $value 423 | * @return bool 424 | */ 425 | public function stream_metadata($path, $options, $value) 426 | { 427 | self::restore(); 428 | $path = self::overlayPath($path); 429 | switch ($options) { 430 | case STREAM_META_TOUCH: 431 | if (!empty($value)) { 432 | $success = touch($path, $value[0], $value[1]); 433 | } else { 434 | $success = touch($path); 435 | } 436 | break; 437 | case STREAM_META_OWNER_NAME: 438 | // Fall through 439 | case STREAM_META_OWNER: 440 | $success = chown($path, $value); 441 | break; 442 | case STREAM_META_GROUP_NAME: 443 | // Fall through 444 | case STREAM_META_GROUP: 445 | $success = chgrp($path, $value); 446 | break; 447 | case STREAM_META_ACCESS: 448 | $success = chmod($path, $value); 449 | break; 450 | default: 451 | $success = false; 452 | } 453 | self::register(); 454 | 455 | return $success; 456 | } 457 | 458 | /** 459 | * Open a file 460 | * 461 | * @param string $path 462 | * @param string $mode 463 | * @param int $options 464 | * @param string &$opened_path 465 | * @return bool 466 | */ 467 | public function stream_open($path, $mode, $options, &$opened_path) 468 | { 469 | if ($this->fileHandle !== null) { 470 | return false; 471 | } 472 | self::restore(); 473 | $path = self::overlayPath($path); 474 | $this->fileHandle = fopen($path, $mode, (bool)($options & STREAM_USE_PATH)); 475 | self::register(); 476 | 477 | return $this->fileHandle !== false; 478 | } 479 | 480 | /** 481 | * Read from a file 482 | * 483 | * @param int $length 484 | * @return string 485 | */ 486 | public function stream_read($length) 487 | { 488 | if ($this->fileHandle === null) { 489 | return false; 490 | } 491 | self::restore(); 492 | $content = fread($this->fileHandle, $length); 493 | self::register(); 494 | 495 | return $content; 496 | } 497 | 498 | /** 499 | * Seek to specific location in a stream 500 | * 501 | * @param int $offset 502 | * @param int $whence = SEEK_SET 503 | * @return bool 504 | */ 505 | public function stream_seek($offset, $whence = SEEK_SET) 506 | { 507 | if ($this->fileHandle === null) { 508 | return false; 509 | } 510 | self::restore(); 511 | $success = fseek($this->fileHandle, $offset, $whence); 512 | self::register(); 513 | 514 | return $success; 515 | } 516 | 517 | /** 518 | * Change stream options (not implemented) 519 | * 520 | * @param int $option 521 | * @param int $arg1 522 | * @param int $arg2 523 | * @return bool 524 | */ 525 | public function stream_set_option($option, $arg1, $arg2) 526 | { 527 | return false; 528 | } 529 | 530 | /** 531 | * Retrieve information about a file resource 532 | * 533 | * @return array 534 | */ 535 | public function stream_stat() 536 | { 537 | if ($this->fileHandle === null) { 538 | return false; 539 | } 540 | self::restore(); 541 | $stats = fstat($this->fileHandle); 542 | self::register(); 543 | 544 | return $stats; 545 | } 546 | 547 | /** 548 | * Retrieve the current position of a stream 549 | * 550 | * @return int 551 | */ 552 | public function stream_tell() 553 | { 554 | if ($this->fileHandle === null) { 555 | return false; 556 | } 557 | self::restore(); 558 | $position = ftell($this->fileHandle); 559 | self::register(); 560 | 561 | return $position; 562 | } 563 | 564 | /** 565 | * Truncates a file to the given size 566 | * 567 | * @param int $size Truncate to this size 568 | * @return bool 569 | */ 570 | public function stream_truncate($size) 571 | { 572 | if ($this->fileHandle === null) { 573 | return false; 574 | } 575 | self::restore(); 576 | $success = ftruncate($this->fileHandle, $size); 577 | self::register(); 578 | 579 | return $success; 580 | } 581 | 582 | /** 583 | * Write to stream 584 | * 585 | * @param string $data 586 | * @return int 587 | */ 588 | public function stream_write($data) 589 | { 590 | if ($this->fileHandle === null) { 591 | return false; 592 | } 593 | self::restore(); 594 | $length = fwrite($this->fileHandle, $data); 595 | self::register(); 596 | 597 | return $length; 598 | } 599 | 600 | /** 601 | * Unlink a file 602 | * 603 | * @param string $path 604 | * @return bool 605 | */ 606 | public function unlink($path) 607 | { 608 | self::restore(); 609 | $path = self::overlayPath($path); 610 | $success = unlink($path); 611 | self::register(); 612 | 613 | return $success; 614 | } 615 | 616 | /** 617 | * Retrieve information about a file 618 | * 619 | * @param string $path 620 | * @param int $flags 621 | * @return array 622 | */ 623 | public function url_stat($path, $flags) 624 | { 625 | self::restore(); 626 | $path = self::overlayPath($path); 627 | if ($flags & STREAM_URL_STAT_LINK) { 628 | $information = @lstat($path); 629 | } else { 630 | $information = @stat($path); 631 | } 632 | self::register(); 633 | 634 | return $information; 635 | } 636 | } 637 | -------------------------------------------------------------------------------- /src/TestingFramework/File/NtfStreamWrapper.php: -------------------------------------------------------------------------------- 1 | fileHandle !== null && $castAs & STREAM_CAST_AS_STREAM) { 182 | return $this->fileHandle; 183 | } 184 | 185 | return false; 186 | } 187 | 188 | /** 189 | * Closes a resource 190 | */ 191 | public function stream_close() 192 | { 193 | if ($this->fileHandle !== null) { 194 | fclose($this->fileHandle); 195 | $this->fileHandle = null; 196 | } 197 | } 198 | 199 | /** 200 | * Tests for end-of-file on a file pointer 201 | * 202 | * @return bool 203 | */ 204 | public function stream_eof() 205 | { 206 | if ($this->fileHandle === null) { 207 | return false; 208 | } 209 | 210 | return feof($this->fileHandle); 211 | } 212 | 213 | /** 214 | * Flushes the output 215 | * 216 | * @return bool 217 | */ 218 | public function stream_flush() 219 | { 220 | return true; 221 | } 222 | 223 | /** 224 | * Advisory file locking 225 | * 226 | * @param int $operation 227 | * @return bool 228 | */ 229 | public function stream_lock($operation) 230 | { 231 | return true; 232 | } 233 | 234 | /** 235 | * Changes stream metadata 236 | * 237 | * @param string $path 238 | * @param int $options 239 | * @param mixed $value 240 | * @return bool 241 | */ 242 | public function stream_metadata($path, $options, $value) 243 | { 244 | return true; 245 | } 246 | 247 | /** 248 | * Opens a file 249 | * 250 | * @param string $path 251 | * @param string $mode 252 | * @param int $options 253 | * @param string &$opened_path 254 | * @return bool 255 | */ 256 | public function stream_open($path, $mode, $options, &$opened_path) 257 | { 258 | if ($this->fileHandle !== null) { 259 | return false; 260 | } 261 | 262 | $this->fileHandle = fopen($this->resolvePath($path), $mode, (bool)($options & STREAM_USE_PATH)); 263 | 264 | return $this->fileHandle !== false; 265 | } 266 | 267 | /** 268 | * Reads from stream 269 | * 270 | * @param int $length 271 | * @return string 272 | */ 273 | public function stream_read($length) 274 | { 275 | if ($this->fileHandle === null) { 276 | return false; 277 | } 278 | 279 | return fread($this->fileHandle, $length); 280 | } 281 | 282 | /** 283 | * Seeks to specific location in a stream 284 | * 285 | * @param int $offset 286 | * @param int $whence 287 | * @return bool 288 | */ 289 | public function stream_seek($offset, $whence = SEEK_SET) 290 | { 291 | if ($this->fileHandle === null) { 292 | return false; 293 | } 294 | 295 | return fseek($this->fileHandle, $offset, $whence); 296 | } 297 | 298 | /** 299 | * Changes stream options 300 | * 301 | * @param int $option 302 | * @param int $arg1 303 | * @param int $arg2 304 | * @return bool 305 | */ 306 | public function stream_set_option($option, $arg1, $arg2) 307 | { 308 | return true; 309 | } 310 | 311 | /** 312 | * Retrieves information about a file resource 313 | * 314 | * @return array|bool 315 | */ 316 | public function stream_stat() 317 | { 318 | if ($this->fileHandle === null) { 319 | return false; 320 | } 321 | 322 | return fstat($this->fileHandle); 323 | } 324 | 325 | /** 326 | * Retrieves the current position of a stream 327 | * 328 | * @return int 329 | */ 330 | public function stream_tell() 331 | { 332 | if ($this->fileHandle === null) { 333 | return -1; 334 | } 335 | 336 | return ftell($this->fileHandle); 337 | } 338 | 339 | /** 340 | * Truncates stream 341 | * 342 | * @param int $size 343 | * @return bool 344 | */ 345 | public function stream_truncate($size) 346 | { 347 | if ($this->fileHandle === null) { 348 | return false; 349 | } 350 | 351 | return ftruncate($this->fileHandle, $size); 352 | } 353 | 354 | /** 355 | * Writes to stream 356 | * 357 | * @param string $data 358 | * @return int 359 | */ 360 | public function stream_write($data) 361 | { 362 | return strlen($data); 363 | } 364 | 365 | /** 366 | * Deletes a file 367 | * 368 | * @param string $path 369 | * @return bool 370 | */ 371 | public function unlink($path) 372 | { 373 | return true; 374 | } 375 | 376 | /** 377 | * Retrieves information about a file 378 | * 379 | * @param string $path 380 | * @param int $flags 381 | * @return array 382 | */ 383 | public function url_stat($path, $flags) 384 | { 385 | $path = $this->resolvePath($path); 386 | if ($flags & STREAM_URL_STAT_LINK) { 387 | return @lstat($path); 388 | } 389 | 390 | return @stat($path); 391 | } 392 | 393 | /** 394 | * Helper method to resolve the path 395 | * 396 | * @param string $path 397 | * @return string 398 | */ 399 | protected function resolvePath($path) 400 | { 401 | $newPath = []; 402 | 403 | $path = trim($path); 404 | $path = substr($path, strlen(self::$scheme . '://')); 405 | $path = strtr($path, ['\\' => '/', '//' => '/']); 406 | 407 | foreach (explode('/', $path) as $part) { 408 | if ($part !== '.') { 409 | if ($part !== '..') { 410 | $newPath[] = $part; 411 | } elseif (count($newPath) > 1) { 412 | array_pop($newPath); 413 | } 414 | } 415 | } 416 | 417 | return self::$root . implode('/', $newPath); 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /src/TestingFramework/Frontend/Collector.php: -------------------------------------------------------------------------------- 1 | cObj->currentRecord; 58 | list($tableName) = explode(':', $recordIdentifier); 59 | $currentWatcherValue = $this->getCurrentWatcherValue(); 60 | $position = strpos($currentWatcherValue, '/' . $recordIdentifier); 61 | 62 | $recordData = $this->filterFields($tableName, $this->cObj->data); 63 | $this->records[$recordIdentifier] = $recordData; 64 | 65 | if ($currentWatcherValue === $recordIdentifier) { 66 | $this->structure[$recordIdentifier] = $recordData; 67 | $this->structurePaths[$recordIdentifier] = [[]]; 68 | } elseif (!empty($position)) { 69 | $levelIdentifier = substr($currentWatcherValue, 0, $position); 70 | $this->addToStructure($levelIdentifier, $recordIdentifier, $recordData); 71 | } 72 | } 73 | 74 | public function addFileData($content, array $configuration = null) 75 | { 76 | $currentFile = $this->cObj->getCurrentFile(); 77 | 78 | if ($currentFile instanceof File) { 79 | $tableName = 'sys_file'; 80 | } elseif ($currentFile instanceof FileReference) { 81 | $tableName = 'sys_file_reference'; 82 | } else { 83 | return; 84 | } 85 | 86 | $recordData = $this->filterFields($tableName, $currentFile->getProperties()); 87 | $recordIdentifier = $tableName . ':' . $currentFile->getUid(); 88 | $this->records[$recordIdentifier] = $recordData; 89 | 90 | $currentWatcherValue = $this->getCurrentWatcherValue(); 91 | $levelIdentifier = rtrim($currentWatcherValue, '/'); 92 | $this->addToStructure($levelIdentifier, $recordIdentifier, $recordData); 93 | } 94 | 95 | /** 96 | * @param string $tableName 97 | * @param array $recordData 98 | * @return array 99 | */ 100 | protected function filterFields($tableName, array $recordData) 101 | { 102 | $recordData = array_intersect_key( 103 | $recordData, 104 | array_flip($this->getTableFields($tableName)) 105 | ); 106 | 107 | return $recordData; 108 | } 109 | 110 | protected function addToStructure($levelIdentifier, $recordIdentifier, array $recordData) 111 | { 112 | $steps = explode('/', $levelIdentifier); 113 | $structurePaths = []; 114 | $structure = &$this->structure; 115 | 116 | foreach ($steps as $step) { 117 | list($identifier, $fieldName) = explode('.', $step); 118 | $structurePaths[] = $identifier; 119 | $structurePaths[] = $fieldName; 120 | if (!isset($structure[$identifier])) { 121 | return; 122 | } 123 | $structure = &$structure[$identifier]; 124 | if (!isset($structure[$fieldName]) || !is_array($structure[$fieldName])) { 125 | $structure[$fieldName] = []; 126 | } 127 | $structure = &$structure[$fieldName]; 128 | } 129 | 130 | $structure[$recordIdentifier] = $recordData; 131 | $this->structurePaths[$recordIdentifier][] = $structurePaths; 132 | } 133 | 134 | /** 135 | * @param string $content 136 | * @param null|array $configuration 137 | * @return void 138 | */ 139 | public function attachSection($content, array $configuration = null) 140 | { 141 | $section = [ 142 | 'structure' => $this->structure, 143 | 'structurePaths' => $this->structurePaths, 144 | 'records' => $this->records, 145 | ]; 146 | 147 | $as = (!empty($configuration['as']) ? $configuration['as'] : null); 148 | $this->getRenderer()->addSection($section, $as); 149 | $this->reset(); 150 | } 151 | 152 | /** 153 | * @param string $tableName 154 | * @return array 155 | */ 156 | protected function getTableFields($tableName) 157 | { 158 | if (!isset($this->tableFields) && !empty($this->getFrontendController()->tmpl->setup['config.']['watcher.']['tableFields.'])) { 159 | $this->tableFields = $this->getFrontendController()->tmpl->setup['config.']['watcher.']['tableFields.']; 160 | foreach ($this->tableFields as &$fieldList) { 161 | $fieldList = GeneralUtility::trimExplode(',', $fieldList, true); 162 | } 163 | unset($fieldList); 164 | } 165 | 166 | return !empty($this->tableFields[$tableName]) ? $this->tableFields[$tableName] : []; 167 | } 168 | 169 | /** 170 | * @return string 171 | */ 172 | protected function getCurrentWatcherValue() 173 | { 174 | $watcherValue = null; 175 | if (isset($this->getFrontendController()->register['watcher'])) { 176 | $watcherValue = $this->getFrontendController()->register['watcher']; 177 | } 178 | 179 | return $watcherValue; 180 | } 181 | 182 | /** 183 | * @return Renderer 184 | */ 185 | protected function getRenderer() 186 | { 187 | return GeneralUtility::makeInstance('Nimut\\TestingFramework\\Frontend\\Renderer'); 188 | } 189 | 190 | /** 191 | * @return TypoScriptFrontendController 192 | */ 193 | protected function getFrontendController() 194 | { 195 | return $GLOBALS['TSFE']; 196 | } 197 | 198 | /** 199 | * Collector needs to be reset after attaching a section, otherwise records will pile up. 200 | * 201 | * @return void 202 | */ 203 | protected function reset() 204 | { 205 | $this->structure = []; 206 | $this->structurePaths = []; 207 | $this->records = []; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/TestingFramework/Frontend/Parser.php: -------------------------------------------------------------------------------- 1 | paths; 41 | } 42 | 43 | /** 44 | * @return array 45 | */ 46 | public function getRecords() 47 | { 48 | return $this->records; 49 | } 50 | 51 | /** 52 | * @param array $structure 53 | * @param array $path 54 | */ 55 | public function parse(array $structure, array $path = []) 56 | { 57 | $this->process($structure); 58 | } 59 | 60 | /** 61 | * @param array $iterator 62 | * @param array $path 63 | */ 64 | protected function process(array $iterator, array $path = []) 65 | { 66 | foreach ($iterator as $identifier => $properties) { 67 | $this->addRecord($identifier, $properties); 68 | $this->addPath($identifier, $path); 69 | foreach ($properties as $propertyName => $propertyValue) { 70 | if (!is_array($propertyValue)) { 71 | continue; 72 | } 73 | $nestedPath = array_merge($path, [$identifier, $propertyName]); 74 | $this->process($propertyValue, $nestedPath); 75 | } 76 | } 77 | } 78 | 79 | /** 80 | * @param string $identifier 81 | * @param array $properties 82 | */ 83 | protected function addRecord($identifier, array $properties) 84 | { 85 | if (isset($this->records[$identifier])) { 86 | return; 87 | } 88 | 89 | foreach ($properties as $propertyName => $propertyValue) { 90 | if (is_array($propertyValue)) { 91 | unset($properties[$propertyName]); 92 | } 93 | } 94 | 95 | $this->records[$identifier] = $properties; 96 | } 97 | 98 | /** 99 | * @param string $identifier 100 | * @param array $path 101 | */ 102 | protected function addPath($identifier, array $path) 103 | { 104 | if (!isset($this->paths[$identifier])) { 105 | $this->paths[$identifier] = []; 106 | } 107 | 108 | $this->paths[$identifier][] = $path; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/TestingFramework/Frontend/Renderer.php: -------------------------------------------------------------------------------- 1 | $structure) { 50 | $parser = $this->createParser(); 51 | $parser->parse($structure); 52 | 53 | $section = [ 54 | 'structure' => $structure, 55 | 'structurePaths' => $parser->getPaths(), 56 | 'records' => $parser->getRecords(), 57 | ]; 58 | 59 | $this->addSection($section, $asPrefix . $identifier); 60 | } 61 | } 62 | 63 | /** 64 | * @param array $section 65 | * @param null|string $as 66 | */ 67 | public function addSection(array $section, $as = null) 68 | { 69 | if (!empty($as)) { 70 | $this->sections[$as] = $section; 71 | } else { 72 | $this->sections[] = $section; 73 | } 74 | } 75 | 76 | /** 77 | * @param string $content 78 | * @param null|array $configuration 79 | * @return string 80 | */ 81 | public function renderSections($content, array $configuration = null) 82 | { 83 | $content = json_encode($this->sections); 84 | 85 | return $content; 86 | } 87 | 88 | /** 89 | * @return Parser 90 | */ 91 | protected function createParser() 92 | { 93 | return GeneralUtility::makeInstance('Nimut\\TestingFramework\\Frontend\\Parser'); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/TestingFramework/Http/Response.php: -------------------------------------------------------------------------------- 1 | status = $status; 54 | $this->content = $content; 55 | $this->error = $error; 56 | } 57 | 58 | /** 59 | * @return string 60 | */ 61 | public function getStatus() 62 | { 63 | return $this->status; 64 | } 65 | 66 | /** 67 | * @return array|null|string 68 | */ 69 | public function getContent() 70 | { 71 | return $this->content; 72 | } 73 | 74 | /** 75 | * @return string 76 | */ 77 | public function getError() 78 | { 79 | return $this->error; 80 | } 81 | 82 | /** 83 | * @return ResponseContent 84 | */ 85 | public function getResponseContent() 86 | { 87 | if (!isset($this->responseContent)) { 88 | $this->responseContent = new ResponseContent($this); 89 | } 90 | 91 | return $this->responseContent; 92 | } 93 | 94 | /** 95 | * @return null|array|ResponseSection[] 96 | */ 97 | public function getResponseSections() 98 | { 99 | $sectionIdentifiers = func_get_args(); 100 | 101 | if (empty($sectionIdentifiers)) { 102 | $sectionIdentifiers = ['Default']; 103 | } 104 | 105 | $sections = []; 106 | foreach ($sectionIdentifiers as $sectionIdentifier) { 107 | $sections[] = $this->getResponseContent()->getSection($sectionIdentifier); 108 | } 109 | 110 | return $sections; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/TestingFramework/Http/ResponseContent.php: -------------------------------------------------------------------------------- 1 | getContent(), true); 54 | 55 | if ($content !== null && is_array($content)) { 56 | foreach ($content as $sectionIdentifier => $sectionData) { 57 | $section = new ResponseSection($sectionIdentifier, $sectionData); 58 | $this->sections[$sectionIdentifier] = $section; 59 | } 60 | } 61 | } 62 | 63 | /** 64 | * @param string $sectionIdentifier 65 | * @throws \RuntimeException 66 | * @return null|ResponseSection 67 | */ 68 | public function getSection($sectionIdentifier) 69 | { 70 | if (isset($this->sections[$sectionIdentifier])) { 71 | return $this->sections[$sectionIdentifier]; 72 | } 73 | 74 | throw new \RuntimeException('ResponseSection "' . $sectionIdentifier . '" does not exist'); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/TestingFramework/Http/ResponseSection.php: -------------------------------------------------------------------------------- 1 | identifier = (string)$identifier; 55 | $this->structure = $data['structure']; 56 | $this->structurePaths = $data['structurePaths']; 57 | $this->records = $data['records']; 58 | 59 | if (!empty($data['queries'])) { 60 | $this->queries = $data['queries']; 61 | } 62 | } 63 | 64 | /** 65 | * @return string 66 | */ 67 | public function getIdentifier() 68 | { 69 | return $this->identifier; 70 | } 71 | 72 | /** 73 | * @return array 74 | */ 75 | public function getStructure() 76 | { 77 | return $this->structure; 78 | } 79 | 80 | /** 81 | * @return array 82 | */ 83 | public function getStructurePaths() 84 | { 85 | return $this->structurePaths; 86 | } 87 | 88 | /** 89 | * @return array 90 | */ 91 | public function getRecords() 92 | { 93 | return $this->records; 94 | } 95 | 96 | /** 97 | * @return array 98 | */ 99 | public function getQueries() 100 | { 101 | return $this->queries; 102 | } 103 | 104 | /** 105 | * @param string $recordIdentifier 106 | * @param string $fieldName 107 | * @return array 108 | */ 109 | public function findStructures($recordIdentifier, $fieldName = '') 110 | { 111 | $structures = []; 112 | 113 | if (empty($this->structurePaths[$recordIdentifier])) { 114 | return $structures; 115 | } 116 | 117 | foreach ((array)$this->structurePaths[$recordIdentifier] as $steps) { 118 | $structure = $this->structure; 119 | $steps[] = $recordIdentifier; 120 | 121 | if (!empty($fieldName)) { 122 | $steps[] = $fieldName; 123 | } 124 | 125 | foreach ((array)$steps as $step) { 126 | if (!isset($structure[$step])) { 127 | $structure = null; 128 | break; 129 | } 130 | $structure = $structure[$step]; 131 | } 132 | 133 | if (!empty($structure)) { 134 | $structures[implode('/', $steps)] = $structure; 135 | } 136 | } 137 | 138 | return $structures; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/TestingFramework/MockObject/AccessibleMockObjectInterface.php: -------------------------------------------------------------------------------- 1 | 'link-destination' 92 | * ); 93 | * 94 | * Given paths are expected to be relative to the test instance root. 95 | * The array keys are the source paths and the array values are the destination 96 | * paths, example: 97 | * 98 | * array( 99 | * 'typo3/sysext/impext/Tests/Functional/Fixtures/Folders/fileadmin/user_upload' => 100 | * 'fileadmin/user_upload', 101 | * 'typo3conf/ext/my_own_ext/Tests/Functional/Fixtures/Folders/uploads/tx_myownext' => 102 | * 'uploads/tx_myownext' 103 | * ); 104 | * 105 | * To be able to link from my_own_ext the extension path needs also to be registered in 106 | * property $testExtensionsToLoad 107 | * 108 | * @var array 109 | */ 110 | protected $pathsToLinkInTestInstance = []; 111 | 112 | /** 113 | * This configuration array is merged with TYPO3_CONF_VARS 114 | * that are set in default configuration and factory configuration 115 | * 116 | * @var array 117 | */ 118 | protected $configurationToUseInTestInstance = []; 119 | 120 | /** 121 | * Array of folders that should be created inside the test instance document root. 122 | * 123 | * This property will stay empty in this abstract, so it is possible 124 | * to just overwrite it in extending classes. Path noted here will 125 | * be linked for every test of a test case and it is not possible to change 126 | * the list of folders between single tests of a test case. 127 | * 128 | * Per default the following folder are created 129 | * /fileadmin 130 | * /typo3temp 131 | * /typo3conf/ext 132 | * /typo3temp/var/tests 133 | * /typo3temp/var/transient 134 | * /uploads 135 | * 136 | * To create additional folders add the paths to this array. Given paths are expected to be 137 | * relative to the test instance root and have to begin with a slash. Example: 138 | * 139 | * array( 140 | * 'fileadmin/user_upload' 141 | * ); 142 | * 143 | * @var array 144 | */ 145 | protected $additionalFoldersToCreate = []; 146 | 147 | /** 148 | * The fixture which is used when initializing a backend user 149 | * 150 | * @var string 151 | */ 152 | protected $backendUserFixture = 'ntf://Database/be_users.xml'; 153 | 154 | /** 155 | * Private utility class used for database abstraction. Do NOT use it in test cases! 156 | * 157 | * @var DatabaseInterface 158 | */ 159 | private $database = null; 160 | 161 | /** 162 | * Private utility class used in setUp() and tearDown(). Do NOT use in test cases! 163 | * 164 | * @var AbstractTestSystem 165 | */ 166 | private $testSystem = null; 167 | 168 | /** 169 | * Run tests in a separate PHP process each 170 | * 171 | * @var bool 172 | */ 173 | protected $runTestInSeparateProcess = true; 174 | 175 | /** 176 | * Avoid serialization of some properties containing objects 177 | * 178 | * @return array 179 | */ 180 | public function __sleep() 181 | { 182 | $objectVars = get_object_vars($this); 183 | unset($objectVars['database'], $objectVars['testSystem']); 184 | 185 | return $objectVars; 186 | } 187 | 188 | /** 189 | * Setup creates a test instance and database 190 | * 191 | * This method has to be called with parent::setUp() in your test cases 192 | * 193 | * @return void 194 | */ 195 | protected function setUp(): void 196 | { 197 | if (!defined('ORIGINAL_ROOT')) { 198 | $this->markTestSkipped('Functional tests must be called through phpunit on CLI'); 199 | } 200 | $this->testSystem = new TestSystem(get_class($this)); 201 | $this->testSystem->setUp( 202 | $this->coreExtensionsToLoad, 203 | $this->testExtensionsToLoad, 204 | $this->pathsToLinkInTestInstance, 205 | $this->configurationToUseInTestInstance, 206 | $this->additionalFoldersToCreate 207 | ); 208 | } 209 | 210 | /** 211 | * Returns the system identifier 212 | * 213 | * @return string 214 | */ 215 | protected function getInstanceIdentifier() 216 | { 217 | return $this->testSystem->getSystemIdentifier(); 218 | } 219 | 220 | /** 221 | * Return the path for the test system 222 | * 223 | * @return string 224 | */ 225 | protected function getInstancePath() 226 | { 227 | return $this->testSystem->getSystemPath(); 228 | } 229 | 230 | /** 231 | * Get database connection instance 232 | * 233 | * This method should be used instead of direct access to 234 | * any database instance. 235 | * 236 | * @return DatabaseInterface 237 | */ 238 | protected function getDatabaseConnection() 239 | { 240 | if (null === $this->database) { 241 | $this->database = new Database(); 242 | } 243 | 244 | return $this->database; 245 | } 246 | 247 | /** 248 | * @return ConnectionPool 249 | * @deprecated will be removed once TYPO3 9 LTS is released 250 | */ 251 | protected function getConnectionPool() 252 | { 253 | return GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Database\\ConnectionPool'); 254 | } 255 | 256 | /** 257 | * Initialize backend user 258 | * 259 | * @param int $userUid uid of the user we want to initialize. This user must exist in the fixture file 260 | * @throws Exception 261 | * @return BackendUserAuthentication 262 | */ 263 | protected function setUpBackendUserFromFixture($userUid) 264 | { 265 | $this->importDataSet($this->backendUserFixture); 266 | $database = $this->getDatabaseConnection(); 267 | $userRow = $database->selectSingleRow('*', 'be_users', 'uid = ' . (int)$userUid); 268 | 269 | $backendUser = GeneralUtility::makeInstance(BackendUserAuthentication::class); 270 | $session = $backendUser->createUserSession($userRow); 271 | 272 | $request = new ServerRequest(); 273 | $request = $request->withCookieParams([ 274 | $backendUser->name => $session->getIdentifier(), 275 | ]); 276 | 277 | $backendUser->start($request); 278 | if (!is_array($backendUser->user) || !$backendUser->user['uid']) { 279 | throw new Exception( 280 | 'Can not initialize backend user', 281 | 1633442752 282 | ); 283 | } 284 | $backendUser->backendCheckLogin(); 285 | 286 | $GLOBALS['BE_USER'] = $backendUser; 287 | GeneralUtility::makeInstance(Context::class)->setAspect( 288 | 'backend.user', 289 | GeneralUtility::makeInstance(UserAspect::class, $backendUser) 290 | ); 291 | 292 | return $backendUser; 293 | } 294 | 295 | /** 296 | * Imports a data set represented as XML into the test database, 297 | * 298 | * @param string $path Absolute path to the XML file containing the data set to load 299 | * @throws Exception 300 | * @return void 301 | */ 302 | protected function importDataSet($path) 303 | { 304 | if (!is_file($path)) { 305 | throw new Exception( 306 | 'Fixture file ' . $path . ' not found', 307 | 1376746261 308 | ); 309 | } 310 | 311 | $database = $this->getDatabaseConnection(); 312 | 313 | $fileContent = file_get_contents($path); 314 | // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept 315 | $previousValueOfEntityLoader = libxml_disable_entity_loader(true); 316 | $xml = simplexml_load_string($fileContent); 317 | libxml_disable_entity_loader($previousValueOfEntityLoader); 318 | $foreignKeys = []; 319 | 320 | /** @var $table \SimpleXMLElement */ 321 | foreach ($xml->children() as $table) { 322 | $insertArray = []; 323 | 324 | /** @var $column \SimpleXMLElement */ 325 | foreach ($table->children() as $column) { 326 | $columnName = $column->getName(); 327 | $columnValue = null; 328 | 329 | if (isset($column['ref'])) { 330 | [$tableName, $elementId] = explode('#', $column['ref']); 331 | $columnValue = $foreignKeys[$tableName][$elementId]; 332 | } elseif (isset($column['is-NULL']) && ($column['is-NULL'] === 'yes')) { 333 | $columnValue = null; 334 | } else { 335 | $columnValue = (string)$table->$columnName; 336 | } 337 | 338 | $insertArray[$columnName] = $columnValue; 339 | } 340 | 341 | $tableName = $table->getName(); 342 | if ($database->getDatabaseInstance() instanceof QueryBuilder) { 343 | try { 344 | $database->insertArray($tableName, $insertArray); 345 | } catch (DBALException $e) { 346 | throw new Exception( 347 | 'Error when processing fixture file: ' . $path . ' Can not insert data to table ' . $tableName . ': ' . $e->getMessage(), 348 | 1494782046 349 | ); 350 | } 351 | } else { 352 | $result = $database->insertArray($tableName, $insertArray); 353 | if ($result === false) { 354 | throw new Exception( 355 | 'Error when processing fixture file: ' . $path . ' Can not insert data to table ' . $tableName . ': ' . $database->getDatabaseInstance()->sql_error(), 356 | 1376746262 357 | ); 358 | } 359 | } 360 | if (isset($table['id'])) { 361 | $elementId = (string)$table['id']; 362 | $foreignKeys[$tableName][$elementId] = $database->lastInsertId(); 363 | } 364 | } 365 | } 366 | 367 | /** 368 | * @param int $pageId 369 | * @param array $typoScriptFiles 370 | * @param array $sites 371 | */ 372 | protected function setUpFrontendRootPage($pageId, array $typoScriptFiles = [], array $sites = []) 373 | { 374 | $pageId = (int)$pageId; 375 | 376 | $this->setUpPageRecord($pageId); 377 | $this->setUpTemplateRecord($pageId, $typoScriptFiles); 378 | $this->setUpSites($pageId, $sites); 379 | } 380 | 381 | /** 382 | * @param int $pageId 383 | */ 384 | protected function setUpPageRecord($pageId) 385 | { 386 | $page = $this->getDatabaseConnection()->selectSingleRow('*', 'pages', 'uid=' . $pageId); 387 | if (empty($page)) { 388 | $this->fail('Cannot set up frontend root page "' . $pageId . '"'); 389 | } 390 | 391 | $pagesFields = [ 392 | 'is_siteroot' => 1, 393 | ]; 394 | $this->getDatabaseConnection()->updateArray('pages', ['uid' => $pageId], $pagesFields); 395 | } 396 | 397 | /** 398 | * @param int $pageId 399 | * @param array $typoScriptFiles 400 | */ 401 | protected function setUpTemplateRecord($pageId, array $typoScriptFiles) 402 | { 403 | $templateFields = [ 404 | 'pid' => $pageId, 405 | 'title' => '', 406 | 'config' => '', 407 | 'clear' => 3, 408 | 'root' => 1, 409 | ]; 410 | 411 | foreach ($typoScriptFiles as $typoScriptFile) { 412 | if (!file_exists($typoScriptFile)) { 413 | $templateFields['config'] .= '' . LF . LF; 414 | } else { 415 | $templateFields['config'] .= '// ' . LF; 416 | $templateFields['config'] .= file_get_contents($typoScriptFile) . LF . LF; 417 | } 418 | } 419 | 420 | $this->getDatabaseConnection()->delete('sys_template', ['pid' => $pageId]); 421 | $this->getDatabaseConnection()->insertArray('sys_template', $templateFields); 422 | } 423 | 424 | /** 425 | * @param int $pageId 426 | * @param array $sites 427 | */ 428 | protected function setUpSites($pageId, array $sites) 429 | { 430 | if (empty($sites[$pageId])) { 431 | $sites[$pageId] = 'ntf://Frontend/site.yaml'; 432 | } 433 | 434 | foreach ($sites as $identifier => $file) { 435 | $path = Environment::getConfigPath() . '/sites/' . $identifier . '/'; 436 | $target = $path . 'config.yaml'; 437 | if (!file_exists($target)) { 438 | GeneralUtility::mkdir_deep($path); 439 | if (!file_exists($file)) { 440 | $file = GeneralUtility::getFileAbsFileName($file); 441 | } 442 | $fileContent = file_get_contents($file); 443 | $fileContent = str_replace('\'{rootPageId}\'', $pageId, $fileContent); 444 | GeneralUtility::writeFile($target, $fileContent); 445 | } 446 | } 447 | } 448 | 449 | /** 450 | * @param int $pageId 451 | * @param int $languageId 452 | * @param int $backendUserId 453 | * @param int $workspaceId 454 | * @param bool $failOnFailure 455 | * @param int $frontendUserId 456 | * @return Response 457 | */ 458 | protected function getFrontendResponse($pageId, $languageId = 0, $backendUserId = 0, $workspaceId = 0, $failOnFailure = true, $frontendUserId = 0) 459 | { 460 | $pageId = (int)$pageId; 461 | $languageId = (int)$languageId; 462 | 463 | $additionalParameter = ''; 464 | 465 | if (!empty($frontendUserId)) { 466 | $additionalParameter .= '&frontendUserId=' . (int)$frontendUserId; 467 | } 468 | if (!empty($backendUserId)) { 469 | $additionalParameter .= '&backendUserId=' . (int)$backendUserId; 470 | } 471 | if (!empty($workspaceId)) { 472 | $additionalParameter .= '&workspaceId=' . (int)$workspaceId; 473 | } 474 | 475 | $arguments = [ 476 | 'documentRoot' => $this->getInstancePath(), 477 | 'requestUrl' => 'http://localhost/?id=' . $pageId . '&L=' . $languageId . $additionalParameter, 478 | ]; 479 | 480 | $textTemplateClass = class_exists(Template::class) ? Template::class : \Text_Template::class; 481 | $template = new $textTemplateClass('ntf://Frontend/Request.tpl'); 482 | $template->setVar( 483 | [ 484 | 'arguments' => var_export($arguments, true), 485 | 'originalRoot' => ORIGINAL_ROOT, 486 | 'ntfRoot' => __DIR__ . '/../../../', 487 | ] 488 | ); 489 | 490 | $php = DefaultPhpProcess::factory(); 491 | $response = $php->runJob($template->render()); 492 | $result = json_decode($response['stdout'], true); 493 | 494 | if ($result === null) { 495 | $this->fail('Frontend Response is empty.' . LF . 'Error: ' . LF . $response['stderr']); 496 | } 497 | 498 | if ($failOnFailure && $result['status'] === Response::STATUS_Failure) { 499 | $this->fail('Frontend Response has failure:' . LF . $result['error']); 500 | } 501 | 502 | $response = new Response($result['status'], $result['content'], $result['error']); 503 | 504 | return $response; 505 | } 506 | } 507 | -------------------------------------------------------------------------------- /src/TestingFramework/TestCase/AbstractTestCase.php: -------------------------------------------------------------------------------- 1 | getMockBuilder($this->buildAccessibleProxy($originalClassName)) 59 | ->setMethods($methods) 60 | ->setConstructorArgs($arguments) 61 | ->setMockClassName($mockClassName); 62 | 63 | if (!$callOriginalConstructor) { 64 | $mockBuilder->disableOriginalConstructor(); 65 | } 66 | 67 | if (!$callOriginalClone) { 68 | $mockBuilder->disableOriginalClone(); 69 | } 70 | 71 | if (!$callAutoload) { 72 | $mockBuilder->disableAutoload(); 73 | } 74 | 75 | return $mockBuilder->getMock(); 76 | } 77 | 78 | /** 79 | * Returns a mock object which allows for calling protected methods and access 80 | * of protected properties. Concrete methods to mock can be specified with 81 | * the last parameter 82 | * 83 | * @param string $originalClassName Full qualified name of the original class 84 | * @param array $arguments 85 | * @param string $mockClassName 86 | * @param bool $callOriginalConstructor 87 | * @param bool $callOriginalClone 88 | * @param bool $callAutoload 89 | * @param array $mockedMethods 90 | * @throws \InvalidArgumentException 91 | * @throws Exception 92 | * @return MockObject|AccessibleMockObjectInterface 93 | */ 94 | protected function getAccessibleMockForAbstractClass( 95 | $originalClassName, 96 | array $arguments = [], 97 | $mockClassName = '', 98 | $callOriginalConstructor = true, 99 | $callOriginalClone = true, 100 | $callAutoload = true, 101 | $mockedMethods = [] 102 | ) { 103 | if ($originalClassName === '') { 104 | throw new \InvalidArgumentException('$originalClassName must not be empty.', 1384268260); 105 | } 106 | 107 | return $this->getMockForAbstractClass( 108 | $this->buildAccessibleProxy($originalClassName), 109 | $arguments, 110 | $mockClassName, 111 | $callOriginalConstructor, 112 | $callOriginalClone, 113 | $callAutoload, 114 | $mockedMethods 115 | ); 116 | } 117 | 118 | /** 119 | * Creates a proxy class of the specified class which allows 120 | * for calling even protected methods and access of protected properties. 121 | * 122 | * @param string $className Name of class to make available, must not be empty 123 | * @return string Fully qualified name of the built class, will not be empty 124 | */ 125 | protected function buildAccessibleProxy($className) 126 | { 127 | $accessibleClassName = $this->getUniqueId('Tx_Phpunit_AccessibleProxy'); 128 | $class = new \ReflectionClass($className); 129 | $abstractModifier = $class->isAbstract() ? 'abstract ' : ''; 130 | 131 | eval( 132 | $abstractModifier . 'class ' . $accessibleClassName . 133 | ' extends ' . $className . ' implements Nimut\\TestingFramework\\MockObject\\AccessibleMockObjectInterface {' . 134 | 'public function _call($methodName) {' . 135 | 'if ($methodName === \'\') {' . 136 | 'throw new \InvalidArgumentException(\'$methodName must not be empty.\', 1334663993);' . 137 | '}' . 138 | '$args = func_get_args();' . 139 | 'return call_user_func_array(array($this, $methodName), array_slice($args, 1));' . 140 | '}' . 141 | 'public function _callRef(' . 142 | '$methodName, &$arg1 = NULL, &$arg2 = NULL, &$arg3 = NULL, &$arg4 = NULL, &$arg5= NULL, &$arg6 = NULL, ' . 143 | '&$arg7 = NULL, &$arg8 = NULL, &$arg9 = NULL' . 144 | ') {' . 145 | 'if ($methodName === \'\') {' . 146 | 'throw new \InvalidArgumentException(\'$methodName must not be empty.\', 1334664210);' . 147 | '}' . 148 | 'switch (func_num_args()) {' . 149 | 'case 0:' . 150 | 'throw new RuntimeException(\'The case of 0 arguments is not supposed to happen.\', 1334703124);' . 151 | 'break;' . 152 | 'case 1:' . 153 | '$returnValue = $this->$methodName();' . 154 | 'break;' . 155 | 'case 2:' . 156 | '$returnValue = $this->$methodName($arg1);' . 157 | 'break;' . 158 | 'case 3:' . 159 | '$returnValue = $this->$methodName($arg1, $arg2);' . 160 | 'break;' . 161 | 'case 4:' . 162 | '$returnValue = $this->$methodName($arg1, $arg2, $arg3);' . 163 | 'break;' . 164 | 'case 5:' . 165 | '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4);' . 166 | 'break;' . 167 | 'case 6:' . 168 | '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5);' . 169 | 'break;' . 170 | 'case 7:' . 171 | '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5, $arg6);' . 172 | 'break;' . 173 | 'case 8:' . 174 | '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7);' . 175 | 'break;' . 176 | 'case 9:' . 177 | '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7, $arg8);' . 178 | 'break;' . 179 | 'case 10:' . 180 | '$returnValue = $this->$methodName(' . 181 | '$arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7, $arg8, $arg9' . 182 | ');' . 183 | 'break;' . 184 | 'default:' . 185 | 'throw new \InvalidArgumentException(' . 186 | '\'_callRef currently only allows calls to methods with no more than 9 parameters.\', 1476049901' . 187 | ');' . 188 | '}' . 189 | 'return $returnValue;' . 190 | '}' . 191 | 'public function _set($propertyName, $value) {' . 192 | 'if ($propertyName === \'\') {' . 193 | 'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1334664355);' . 194 | '}' . 195 | '$this->$propertyName = $value;' . 196 | '}' . 197 | 'public function _setRef($propertyName, &$value) {' . 198 | 'if ($propertyName === \'\') {' . 199 | 'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1334664545);' . 200 | '}' . 201 | '$this->$propertyName = $value;' . 202 | '}' . 203 | 'public function _setStatic($propertyName, $value) {' . 204 | 'if ($propertyName === \'\') {' . 205 | 'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1344242602);' . 206 | '}' . 207 | 'self::$$propertyName = $value;' . 208 | '}' . 209 | 'public function _get($propertyName) {' . 210 | 'if ($propertyName === \'\') {' . 211 | 'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1334664967);' . 212 | '}' . 213 | 'return $this->$propertyName;' . 214 | '}' . 215 | 'public function _getStatic($propertyName) {' . 216 | 'if ($propertyName === \'\') {' . 217 | 'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1344242603);' . 218 | '}' . 219 | 'return self::$$propertyName;' . 220 | '}' . 221 | '}' 222 | ); 223 | 224 | return $accessibleClassName; 225 | } 226 | 227 | /** 228 | * Helper function to call protected or private methods 229 | * 230 | * @param object $object The object to be invoked 231 | * @param string $name the name of the method to call 232 | * @return mixed 233 | */ 234 | protected function callInaccessibleMethod($object, $name) 235 | { 236 | // Remove first two arguments ($object and $name) 237 | $arguments = func_get_args(); 238 | array_splice($arguments, 0, 2); 239 | 240 | $reflectionObject = new \ReflectionObject($object); 241 | $reflectionMethod = $reflectionObject->getMethod($name); 242 | $reflectionMethod->setAccessible(true); 243 | 244 | return $reflectionMethod->invokeArgs($object, $arguments); 245 | } 246 | 247 | /** 248 | * Injects $dependency into property $name of $target 249 | * 250 | * This is a convenience method for setting a protected or private property in 251 | * a test subject for the purpose of injecting a dependency. 252 | * 253 | * @param object $target The instance which needs the dependency 254 | * @param string $name Name of the property to be injected 255 | * @param mixed $dependency The dependency to inject – usually an object but can also be any other type 256 | * @throws \RuntimeException 257 | * @throws \InvalidArgumentException 258 | * @return void 259 | */ 260 | protected function inject($target, $name, $dependency) 261 | { 262 | if (!is_object($target)) { 263 | throw new \InvalidArgumentException('Wrong type for argument $target, must be object.', 1476107338); 264 | } 265 | 266 | $objectReflection = new \ReflectionObject($target); 267 | $methodNamePart = strtoupper($name[0]) . substr($name, 1); 268 | if ($objectReflection->hasMethod('set' . $methodNamePart)) { 269 | $methodName = 'set' . $methodNamePart; 270 | $target->$methodName($dependency); 271 | } elseif ($objectReflection->hasMethod('inject' . $methodNamePart)) { 272 | $methodName = 'inject' . $methodNamePart; 273 | $target->$methodName($dependency); 274 | } elseif ($objectReflection->hasProperty($name)) { 275 | $property = $objectReflection->getProperty($name); 276 | $property->setAccessible(true); 277 | $property->setValue($target, $dependency); 278 | } else { 279 | throw new \RuntimeException( 280 | 'Could not inject ' . $name . ' into object of type ' . get_class($target), 281 | 1476107339 282 | ); 283 | } 284 | } 285 | 286 | /** 287 | * Create and return a unique id optionally prepended by a given string 288 | * 289 | * This function is used because on windows and in cygwin environments uniqid() has a resolution of one second which 290 | * results in identical ids if simply uniqid('Foo'); is called. 291 | * 292 | * @param string $prefix 293 | * @return string 294 | */ 295 | protected function getUniqueId($prefix = '') 296 | { 297 | $uniqueId = uniqid(mt_rand(), true); 298 | 299 | return $prefix . str_replace('.', '', $uniqueId); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/TestingFramework/TestCase/AbstractViewHelperBaseTestcase.php: -------------------------------------------------------------------------------- 1 | viewHelperVariableContainer = $this->prophesize(ViewHelperVariableContainer::class); 91 | 92 | $this->uriBuilder = $this->getUriBuilder(); 93 | $this->request = $this->getRequest(); 94 | 95 | $this->arguments = []; 96 | $this->mvcPropertyMapperConfigurationService = $this->getAccessibleMock(MvcPropertyMappingConfigurationService::class, ['dummy']); 97 | $this->templateVariableContainer = $this->getMockBuilder(StandardVariableProvider::class)->getMock(); 98 | $this->tagBuilder = new TagBuilder(); 99 | 100 | $this->controllerContext = $this->getMockBuilder(ControllerContext::class)->getMock(); 101 | $this->controllerContext->expects($this->any())->method('getUriBuilder')->will($this->returnValue($this->uriBuilder)); 102 | $this->controllerContext->expects($this->any())->method('getRequest')->will($this->returnValue($this->request->reveal())); 103 | 104 | $this->renderingContext = $this->getAccessibleMock(RenderingContextFixture::class, ['getControllerContext']); 105 | $this->renderingContext->expects($this->any())->method('getControllerContext')->willReturn($this->controllerContext); 106 | $this->renderingContext->setVariableProvider($this->templateVariableContainer); 107 | $this->renderingContext->_set('viewHelperVariableContainer', $this->viewHelperVariableContainer->reveal()); 108 | $this->renderingContext->_set('request', $this->controllerContext->getRequest()); 109 | $this->renderingContext->setControllerContext($this->controllerContext); 110 | } 111 | 112 | protected function getUriBuilder() 113 | { 114 | $uriBuilder = $this->getMockBuilder(UriBuilder::class)->getMock(); 115 | $uriBuilder->expects($this->any())->method('reset')->will($this->returnValue($this->uriBuilder)); 116 | $uriBuilder->expects($this->any())->method('setArguments')->will($this->returnValue($this->uriBuilder)); 117 | $uriBuilder->expects($this->any())->method('setSection')->will($this->returnValue($this->uriBuilder)); 118 | $uriBuilder->expects($this->any())->method('setFormat')->will($this->returnValue($this->uriBuilder)); 119 | $uriBuilder->expects($this->any())->method('setCreateAbsoluteUri')->will($this->returnValue($this->uriBuilder)); 120 | $uriBuilder->expects($this->any())->method('setAddQueryString')->will($this->returnValue($this->uriBuilder)); 121 | $uriBuilder->expects($this->any())->method('setArgumentsToBeExcludedFromQueryString')->will($this->returnValue($this->uriBuilder)); 122 | $uriBuilder->expects($this->any())->method('setLinkAccessRestrictedPages')->will($this->returnValue($this->uriBuilder)); 123 | $uriBuilder->expects($this->any())->method('setTargetPageUid')->will($this->returnValue($this->uriBuilder)); 124 | $uriBuilder->expects($this->any())->method('setTargetPageType')->will($this->returnValue($this->uriBuilder)); 125 | $uriBuilder->expects($this->any())->method('setNoCache')->will($this->returnValue($this->uriBuilder)); 126 | $uriBuilder->expects($this->any())->method('setAddQueryStringMethod')->will($this->returnValue($this->uriBuilder)); 127 | 128 | return $uriBuilder; 129 | } 130 | 131 | protected function getRequest() 132 | { 133 | return $this->prophesize(Request::class); 134 | } 135 | 136 | /** 137 | * @param ViewHelperInterface|AbstractViewHelper $viewHelper 138 | * @return void 139 | */ 140 | protected function injectDependenciesIntoViewHelper($viewHelper) 141 | { 142 | if (!$viewHelper instanceof ViewHelperInterface && !$viewHelper instanceof AbstractViewHelper) { 143 | throw new \RuntimeException( 144 | 'Invalid viewHelper type "' . get_class($viewHelper) . '" in injectDependenciesIntoViewHelper', 145 | 1487208085 146 | ); 147 | } 148 | $viewHelper->setRenderingContext($this->renderingContext); 149 | $viewHelper->setArguments($this->arguments); 150 | // this condition is needed, because the (Be)/Security\*ViewHelper don't extend the 151 | // AbstractViewHelper and contain no method injectReflectionService() 152 | if ($viewHelper instanceof AbstractViewHelper && method_exists($viewHelper, 'injectReflectionService')) { 153 | $reflectionServiceProphecy = $this->prophesize(ReflectionService::class); 154 | $reflectionServiceProphecy->getMethodParameters(Argument::type('string'), Argument::type('string'))->willReturn([]); 155 | $viewHelper->injectReflectionService($reflectionServiceProphecy->reveal()); 156 | } 157 | if ($viewHelper instanceof AbstractTagBasedViewHelper && $viewHelper instanceof AccessibleMockObjectInterface) { 158 | $viewHelper->_set('tag', $this->tagBuilder); 159 | } 160 | } 161 | 162 | /** 163 | * Helper function to merge arguments with default arguments according to their registration 164 | * This usually happens in ViewHelperInvoker before the view helper methods are called 165 | * 166 | * @param ViewHelperInterface $viewHelper 167 | * @param array $arguments 168 | */ 169 | protected function setArgumentsUnderTest(ViewHelperInterface $viewHelper, array $arguments = []) 170 | { 171 | $argumentDefinitions = $viewHelper->prepareArguments(); 172 | foreach ($argumentDefinitions as $argumentName => $argumentDefinition) { 173 | if (!isset($arguments[$argumentName])) { 174 | $arguments[$argumentName] = $argumentDefinition->getDefaultValue(); 175 | } 176 | } 177 | $viewHelper->setArguments($arguments); 178 | } 179 | 180 | /** 181 | * Helper function for a valid mapping result 182 | */ 183 | protected function stubRequestWithoutMappingErrors() 184 | { 185 | $this->request->getOriginalRequest()->willReturn(null); 186 | $this->request->getArguments()->willReturn([]); 187 | $result = $this->prophesize(Result::class); 188 | $result->forProperty('objectName')->willReturn($result->reveal()); 189 | $result->forProperty('someProperty')->willReturn($result->reveal()); 190 | $result->hasErrors()->willReturn(false); 191 | $this->request->getOriginalRequestMappingResults()->willReturn($result->reveal()); 192 | } 193 | 194 | /** 195 | * Helper function for a mapping result with errors 196 | */ 197 | protected function stubRequestWithMappingErrors() 198 | { 199 | $this->request->getOriginalRequest()->willReturn(null); 200 | $this->request->getArguments()->willReturn([]); 201 | $result = $this->prophesize(Result::class); 202 | $result->forProperty('objectName')->willReturn($result->reveal()); 203 | $result->forProperty('someProperty')->willReturn($result->reveal()); 204 | $result->hasErrors()->willReturn(true); 205 | $this->request->getOriginalRequestMappingResults()->willReturn($result->reveal()); 206 | } 207 | 208 | /** 209 | * Helper function for the bound property 210 | * 211 | * @param $formObject 212 | */ 213 | protected function stubVariableContainer($formObject) 214 | { 215 | $this->viewHelperVariableContainer->exists(Argument::cetera())->willReturn(true); 216 | $this->viewHelperVariableContainer->get(Argument::any(), 'formObjectName')->willReturn('objectName'); 217 | $this->viewHelperVariableContainer->get(Argument::any(), 'fieldNamePrefix')->willReturn('fieldPrefix'); 218 | $this->viewHelperVariableContainer->get(Argument::any(), 'formFieldNames')->willReturn([]); 219 | $this->viewHelperVariableContainer->get(Argument::any(), 'formObject')->willReturn($formObject); 220 | $this->viewHelperVariableContainer->get(Argument::any(), 'renderedHiddenFields')->willReturn([]); 221 | $this->viewHelperVariableContainer->addOrUpdate(Argument::cetera())->willReturn(null); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/TestingFramework/TestCase/FunctionalTestCase.php: -------------------------------------------------------------------------------- 1 | testFilesToDelete as $absoluteFileName) { 51 | $absoluteFileName = GeneralUtility::fixWindowsFilePath(PathUtility::getCanonicalPath($absoluteFileName)); 52 | if (!GeneralUtility::validPathStr($absoluteFileName)) { 53 | throw new \RuntimeException('tearDown() cleanup: Filename contains illegal characters', 1410633087); 54 | } 55 | if (strpos($absoluteFileName, PATH_site . 'typo3temp/') !== 0) { 56 | throw new \RuntimeException( 57 | 'tearDown() cleanup: Files to delete must be within typo3temp/', 58 | 1410633412 59 | ); 60 | } 61 | // file_exists returns false for links pointing to not existing targets, so handle links before next check. 62 | if (@is_link($absoluteFileName) || @is_file($absoluteFileName)) { 63 | unlink($absoluteFileName); 64 | } elseif (@is_dir($absoluteFileName)) { 65 | GeneralUtility::rmdir($absoluteFileName, true); 66 | } else { 67 | throw new \RuntimeException('tearDown() cleanup: File, link or directory does not exist', 1410633510); 68 | } 69 | } 70 | 71 | // Unset properties of test classes to save memory 72 | $reflection = new \ReflectionObject($this); 73 | foreach ($reflection->getProperties() as $property) { 74 | $declaringClass = $property->getDeclaringClass()->getName(); 75 | if ( 76 | !$property->isStatic() 77 | && $declaringClass !== 'Nimut\\TestingFramework\\TestCase\\AbstractTestCase' 78 | && $declaringClass !== get_class($this) 79 | && strpos($property->getDeclaringClass()->getName(), 'PHPUnit') !== 0 80 | ) { 81 | $propertyName = $property->getName(); 82 | unset($this->$propertyName); 83 | } 84 | } 85 | unset($reflection); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/TestingFramework/TestCase/ViewHelperBaseTestcase.php: -------------------------------------------------------------------------------- 1 |