├── .gitignore ├── LICENSE.md ├── README.md ├── doc ├── create_test_dialog.png ├── default_run_configuration.png ├── preferences.png ├── run_configuration.png └── test_results.png ├── intellij-nette-tester.iml ├── intellij-nette-tester.jar ├── resources ├── META-INF │ ├── plugin.xml │ └── pluginIcon.svg ├── fileTemplates │ └── internal │ │ └── Tester TestCase.ft ├── icons │ ├── run.png │ ├── runClass.png │ ├── runMethod.png │ └── testConfig.png ├── inspectionDescriptions │ ├── TestCaseAnnotation.html │ ├── TestCaseIsRun.html │ └── TestFileName.html ├── messages │ └── TesterBundle.properties ├── setup.php └── setup2-0.php └── src └── cz └── jiripudil └── intellij └── nette └── tester ├── TesterBundle.java ├── TesterIcons.java ├── TesterPostStartupActivity.java ├── TesterTestFinder.java ├── TesterUtil.java ├── action ├── BaseTesterRunAction.java ├── TesterCreateMethodRunTestAction.java ├── TesterCreateRunTestAction.java ├── TesterMethodRunTestAction.java └── TesterRunTestAction.java ├── codeGeneration ├── TesterAbstractGenerateMethodAction.java ├── TesterGenerateSetupMethodAction.java ├── TesterGenerateTeardownMethodAction.java ├── TesterGenerateTestMethodAction.java ├── TesterNamespaceMapper.java ├── TesterNewTestCaseAction.java ├── TesterNewTestCaseDialog.form ├── TesterNewTestCaseDialog.java └── TesterTestCreator.java ├── configuration ├── TesterRunConfiguration.java ├── TesterRunConfigurationProducer.java ├── TesterRunConfigurationType.java ├── TesterSettings.java ├── TesterTestMethodRunConfiguration.java ├── TesterTestMethodRunConfigurationProducer.java ├── TesterTestMethodRunConfigurationType.java └── editor │ ├── PhpCommandLineSettingsEditor.form │ ├── PhpCommandLineSettingsEditor.java │ ├── TesterRunConfigurationEditor.form │ ├── TesterRunConfigurationEditor.java │ ├── TesterSettingsEditor.form │ ├── TesterSettingsEditor.java │ ├── TesterTestEnvironmentSettingsEditor.form │ └── TesterTestEnvironmentSettingsEditor.java ├── execution ├── TesterConsoleProperties.java ├── TesterExecutionUtil.java ├── TesterStackTraceFilter.java └── TesterTestLocator.java ├── inspections ├── TestCaseAnnotationInspection.java ├── TestCaseIsRunInspection.java └── TestFileNameInspection.java ├── lineMarker ├── TesterMethodRunLineMarkerProvider.java └── TesterRunLineMarkerProvider.java └── projectSettings ├── TesterConfigurable.java ├── TesterNamespaceMapping.java ├── TesterProjectSettings.java ├── TesterProjectSettingsManager.java └── editor ├── NamespaceMappingTable.java ├── NamespaceTableCellEditor.form ├── NamespaceTableCellEditor.java ├── TesterConfigurableForm.form └── TesterConfigurableForm.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | out/ 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Jiří Pudil 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nette Tester integration into PhpStorm 2 | 3 | [![Version](http://phpstorm.espend.de/badge/8226/version)](https://plugins.jetbrains.com/plugin/8226) 4 | [![Downloads](http://phpstorm.espend.de/badge/8226/downloads)](https://plugins.jetbrains.com/plugin/8226) 5 | 6 | This plugin integrates [Nette Tester](https://tester.nette.org) into PhpStorm IDE. 7 | 8 | 9 | ## Installation and requirements 10 | 11 | This plugin is written for PhpStorm 2016.3 and above and is compiled for Java 8. You can find it in the Jetbrains plugin repository. Install it from Preferences → Plugins → Browse repositories... and search for `Tester`. 12 | 13 | 14 | ## Usage 15 | 16 | ### Configuration 17 | 18 | This plugin provides a new configuration type for Nette Tester: 19 | 20 | ![Run configuration](doc/run_configuration.png) 21 | 22 | - **Test scope** is the directory containing the tests you wish to run. 23 | - **Tester executable** specifies path to the Tester runner (`/path/to/your/project/vendor/bin/tester` if you installed Tester via Composer). 24 | - **Tester options** allows you to specify options for Tester script (refer to the [docs](https://tester.nette.org/en/)). This field serves for options you cannot specify otherwise (see below). 25 | - **Setup script** allows you to specify the setup script (`--setup` option). You can leave this field blank if you don't use any. 26 | - **Interpreter** and **interpreter options** allow you to modify the environment in which your tests run (`-p` option and `-d` options). 27 | - **Path to php.ini** specifies the configuration file to use (`-c` option). You can leave this field blank, in which case tests will run without any configuration loaded. 28 | 29 | #### Usage on Windows 30 | 31 | Composer seems to do some necessary, but unfortunate transformations on vendor binaries. Therefore you need to point the **Tester executable** option to the actual PHP file in `/path/to/your/project/vendor/nette/tester/src/tester.php`. 32 | 33 | 34 | ### Interpreting results 35 | 36 | If you now run this configuration, test results will start to show in the Test Runner window: 37 | 38 | ![Test results](doc/test_results.png) 39 | 40 | To the left, there is a list of tests. You can toggle showing passed and skipped tests. If you click the test, you will see the detailed output in the console window to the right. 41 | 42 | 43 | ### Navigating between a class and its test 44 | 45 | You can navigate between a class and its test, or create a test for a class easily, via Navigate → Test. 46 | 47 | The navigation is based on convention (class name + `Test` suffix), the creation assumes your tests reside in the same namespace as the code; if you use a different scheme, you can configure source to test namespace mapping in the project settings under Languages & Frameworks → PHP → Nette Tester. 48 | 49 | ![Preferences](doc/preferences.png) 50 | 51 | If you create a test for a class, the dialog now follows your namespace mapping and automatically updates the target test namespace (and directory, following the Directories project settings). 52 | 53 | ![Create a TestCase dialog](doc/create_test_dialog.png) 54 | 55 | In the project settings, you can also configure the path to your test environment bootstrap file, which is then automatically required in the generated TestCase files. 56 | 57 | 58 | ### Running a single test method 59 | 60 | You can right-click a single test method to run or even debug it in isolation. This merely executes the file as a PHP script, with the method name as a single argument. 61 | 62 | 63 | ### Running tests from a directory 64 | 65 | You can right-click a directory to run the tests within it. For this to be truly a one-click action, you should configure the Tester executable in the Nette Tester default run configuration template. 66 | 67 | ![Default run configuration](doc/default_run_configuration.png) 68 | -------------------------------------------------------------------------------- /doc/create_test_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nette-intellij/intellij-nette-tester/ece7132bd107c93af2f956f183ff2a23efa0f89e/doc/create_test_dialog.png -------------------------------------------------------------------------------- /doc/default_run_configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nette-intellij/intellij-nette-tester/ece7132bd107c93af2f956f183ff2a23efa0f89e/doc/default_run_configuration.png -------------------------------------------------------------------------------- /doc/preferences.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nette-intellij/intellij-nette-tester/ece7132bd107c93af2f956f183ff2a23efa0f89e/doc/preferences.png -------------------------------------------------------------------------------- /doc/run_configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nette-intellij/intellij-nette-tester/ece7132bd107c93af2f956f183ff2a23efa0f89e/doc/run_configuration.png -------------------------------------------------------------------------------- /doc/test_results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nette-intellij/intellij-nette-tester/ece7132bd107c93af2f956f183ff2a23efa0f89e/doc/test_results.png -------------------------------------------------------------------------------- /intellij-nette-tester.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /intellij-nette-tester.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nette-intellij/intellij-nette-tester/ece7132bd107c93af2f956f183ff2a23efa0f89e/intellij-nette-tester.jar -------------------------------------------------------------------------------- /resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | cz.jiripudil.intellij.nette.tester 3 | Nette Tester 4 | 2.1.0 5 | Jiří Pudil 6 | messages.TesterBundle 7 | 8 | Github 10 | 11 |

This plugin integrates Nette Tester 12 | into PhpStorm IDE.

13 | ]]>
14 | 15 | 2.1.0 17 | 24 | 25 |

2.0.0-beta.3

26 | 34 | 35 |

2.0.0-beta.2

36 |

I know I said this release branch would be feature-frozen, but... well... not just yet. I'm far too excited about bringing the new features to be able to postpone them. Semver doesn't play well with the plugin's distribution channel anyway. Here they come:

37 | 43 | 44 |

2.0.0-beta.1

45 |

After tens of hours of work, here comes a total rework of the plugin. This beta.1 release marks the feature freeze, now I'd like to focus on fixing bugs and releasing a stable version soon.

46 | 53 |

To support a wide range of Tester versions, I had to drop a few features. However, they should, in some form or another, be back once OutputHandler refactoring is resolved.

54 | 58 | 59 |

1.0.0-alpha.4

60 | 63 | 64 |

1.0.0-alpha.3

65 | 68 | 69 |

1.0-alpha2

70 | 73 | 74 |

1.0-alpha

75 |

Initial alpha version with basic capabilities:

76 | 80 | ]]>
81 | 82 | 83 | 84 | com.intellij.modules.lang 85 | com.jetbrains.php 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 113 | 114 | 120 | 126 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 |
149 | -------------------------------------------------------------------------------- /resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /resources/fileTemplates/internal/Tester TestCase.ft: -------------------------------------------------------------------------------- 1 | run(); 27 | -------------------------------------------------------------------------------- /resources/icons/run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nette-intellij/intellij-nette-tester/ece7132bd107c93af2f956f183ff2a23efa0f89e/resources/icons/run.png -------------------------------------------------------------------------------- /resources/icons/runClass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nette-intellij/intellij-nette-tester/ece7132bd107c93af2f956f183ff2a23efa0f89e/resources/icons/runClass.png -------------------------------------------------------------------------------- /resources/icons/runMethod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nette-intellij/intellij-nette-tester/ece7132bd107c93af2f956f183ff2a23efa0f89e/resources/icons/runMethod.png -------------------------------------------------------------------------------- /resources/icons/testConfig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nette-intellij/intellij-nette-tester/ece7132bd107c93af2f956f183ff2a23efa0f89e/resources/icons/testConfig.png -------------------------------------------------------------------------------- /resources/inspectionDescriptions/TestCaseAnnotation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Tester test cases can run in parallel if annotated.

4 | 5 |

By annotating your test case with @testCase tag, you can instrument Nette Tester to run each of the contained test methods in a separate thread.

6 | 7 | -------------------------------------------------------------------------------- /resources/inspectionDescriptions/TestCaseIsRun.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Tester test cases must be run manually.

4 | 5 |

In the test file, the test case must be instantiated and executed via the run() method in order to actually run the tests.

6 | 7 | -------------------------------------------------------------------------------- /resources/inspectionDescriptions/TestFileName.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Tester test case files must have correct names.

4 | 5 |

Nette Tester searches for files with the phpt extension, or, starting from 2.0, files with the php extension and Test name suffix.

6 | 7 | -------------------------------------------------------------------------------- /resources/messages/TesterBundle.properties: -------------------------------------------------------------------------------- 1 | # actions and dialogs 2 | 3 | action.generateSetupMethod.name=TestCase setUp() method 4 | action.generateSetupMethod.description=Creates a TestCase setUp() method 5 | 6 | action.runTestAction.name=Run 7 | action.createRunTestAction.name=Create 8 | 9 | action.generateTeardownMethod.name=TestCase tearDown() method 10 | action.generateTeardownMethod.description=Creates a TestCase tearDown() method 11 | 12 | action.generateTestMethod.name=TestCase test() Method 13 | action.generateTestMethod.description=Creates a TestCase test() method 14 | 15 | action.newTestCase.name=Nette Tester TestCase 16 | action.newTestCase.description=Creates a new Nette Tester TestCase 17 | 18 | dialog.newTestCase.title=Create Nette Tester Test 19 | dialog.newTestCase.completionShortcut=Use {0} for {1} completion 20 | dialog.newTestCase.label.classToTest=Class to Test: 21 | dialog.newTestCase.label.testClass=Test name: 22 | dialog.newTestCase.label.namespace=Namespace: 23 | dialog.newTestCase.label.fileName=File name: 24 | dialog.newTestCase.label.directory=Directory: 25 | 26 | 27 | # configuration types 28 | 29 | configurationType.displayName=Nette Tester 30 | configurationType.description=Run tests via Nette Tester 31 | 32 | configurationType.method.displayName=Nette Tester test method 33 | configurationType.method.description=Run a single Nette Tester TestCase test method 34 | 35 | 36 | # run configuration settings 37 | 38 | runConfiguration.errors.phpInterpreterNotSet=PHP interpreter for current project is not set or was not be found. 39 | runConfiguration.errors.phpExecutableNotFound=PHP executable of the project PHP interpreter was not found. 40 | runConfiguration.errors.noTestScope=You must specify the test scope. 41 | runConfiguration.errors.testScopeNotFound=File or directory set as the test scope was not found. 42 | runConfiguration.errors.testEnvInterpreterNotFound=Test environment PHP interpreter was not found. 43 | runConfiguration.errors.testEnvExecutableNotFound=Test environment PHP executable was not found. 44 | runConfiguration.errors.noExecutable=You must specify the path to Tester executable. 45 | runConfiguration.errors.executableNotFound=Tester executable was not found at given path. 46 | runConfiguration.errors.phpIniNotFound=The php.ini file was not found at given path. 47 | runConfiguration.errors.setupScriptNotFound=The setup script file was not found at given path. 48 | 49 | runConfiguration.editor.tester.title=Nette Tester 50 | runConfiguration.editor.tester.testScope=Test scope: 51 | runConfiguration.editor.tester.testerExecutable=Tester executable: 52 | runConfiguration.editor.tester.testerOptions=Tester options: 53 | runConfiguration.editor.tester.setupScript=Setup script: 54 | 55 | runConfiguration.editor.testEnv.title=Test Environment 56 | runConfiguration.editor.testEnv.interpreter=Interpreter: 57 | runConfiguration.editor.testEnv.interpreterOptions=Interpreter options: 58 | runConfiguration.editor.testEnv.phpIni=Path to php.ini 59 | runConfiguration.editor.testEnv.useSystemPhpIni=Use system php.ini 60 | 61 | runConfiguration.editor.cli.title=Command Line 62 | runConfiguration.editor.cli.interpreterOptions=Interpreter options: 63 | runConfiguration.editor.cli.workingDirectory=Custom working directory: 64 | 65 | runConfiguration.mainConfiguration.missing.title=Run configuration 'tester' missing 66 | runConfiguration.mainConfiguration.missing.description=To use line markers, you need create run configuration named 'tester' with interpreter. 67 | 68 | runConfiguration.mainConfiguration.invalid.title=Run configuration 'tester' is invalid 69 | runConfiguration.mainConfiguration.invalid.description=To use line markers, you need valid run configuration named 'tester'. 70 | 71 | runConfiguration.mainConfiguration.alreadyCreated.title=Run configuration already created 72 | runConfiguration.mainConfiguration.alreadyCreated.description=Run configuration already exists. It can not be created again. 73 | 74 | runConfiguration.mainConfiguration.created.title=Run configuration created 75 | runConfiguration.mainConfiguration.created.description=Run configuration was successfully created. 76 | 77 | 78 | # project settings 79 | 80 | settings.defaultExtension=Default extension: 81 | settings.bootstrapFile=Bootstrap file: 82 | settings.testerVersion=Tester version: 83 | settings.namespaceMappings.title=Namespace mappings 84 | settings.namespaceMappings.noMappings=No mappings 85 | settings.namespaceMappings.sourceNamespace=Source namespace 86 | settings.namespaceMappings.testsNamespace=Tests namespace 87 | 88 | 89 | # inspections 90 | 91 | inspections.familyName=Nette Tester 92 | 93 | inspections.fileName.description=Test case file has an incorrect name 94 | inspections.fileName.quickFix.phpt=Change extension to 'phpt' 95 | inspections.fileName.quickFix.test=Add 'Test' suffix to file name 96 | 97 | inspections.annotation.description=Test case should have @testCase annotation 98 | inspections.annotation.quickFix=Annotate class with @testCase 99 | 100 | inspections.runTestCase.description=Test case is not run 101 | inspections.runTestCase.addRunMethodCall.quickFix=Add run() method call 102 | inspections.runTestCase.makeAbstract.quickFix=Make test case abstract 103 | -------------------------------------------------------------------------------- /resources/setup.php: -------------------------------------------------------------------------------- 1 | file = \fopen($output, 'w'); 25 | } 26 | 27 | 28 | public function begin() 29 | { 30 | // \fwrite($this->file, $this->message('testCount', array('count' => 0))); 31 | } 32 | 33 | 34 | public function result($testName, $result, $message) 35 | { 36 | $flowId = \md5($testName); 37 | \fwrite($this->file, $this->message('testStarted', array('name' => $testName, 'flowId' => $flowId))); 38 | 39 | if ($result === 2) { // Runner::SKIPPED, Test::SKIPPED 40 | \fwrite($this->file, $this->message('testIgnored', array('name' => $testName, 'flowId' => $flowId, 'message' => 'Test skipped', 'details' => $message))); 41 | 42 | } elseif ($result === 3) { // Runner::FAILED, Test::FAILED 43 | $extraArguments = array(); 44 | if (\preg_match("/^diff \"(.*)\" \"(.*)\"$/m", $message, $matches)) { // Windows build 45 | $expectedFile = \str_replace('""', '"', $matches[1]); 46 | $actualFile = \str_replace('""', '"', $matches[2]); 47 | $extraArguments = array('type' => 'comparisonFailure', 'expectedFile' => $expectedFile, 'actualFile' => $actualFile); 48 | 49 | } elseif (\preg_match("/^diff '?(.*)'? '?(.*)'?$/m", $message, $matches)) { 50 | $expectedFile = \trim($matches[1], "'"); 51 | $actualFile = \trim($matches[2], "'"); 52 | $extraArguments = array('type' => 'comparisonFailure', 'expectedFile' => $expectedFile, 'actualFile' => $actualFile); 53 | 54 | } elseif (\preg_match("/Failed: (.*) should be( equal to)?\s+\.*\s*(.*) in/is", $message, $matches)) { 55 | $expected = $matches[3]; 56 | $actual = $matches[1]; 57 | $extraArguments = array('type' => 'comparisonFailure', 'expected' => $expected, 'actual' => $actual); 58 | } 59 | 60 | $args = \array_merge(array( 61 | 'name' => $testName, 62 | 'flowId' => $flowId, 63 | 'message' => 'Test failed', 64 | 'details' => $message, 65 | ), $extraArguments); 66 | 67 | \fwrite($this->file, $this->message('testFailed', $args)); 68 | } 69 | 70 | \fwrite($this->file, $this->message('testFinished', array('name' => $testName, 'flowId' => $flowId))); 71 | } 72 | 73 | 74 | public function end() 75 | { 76 | } 77 | 78 | 79 | private function message($messageName, $args) 80 | { 81 | $argsPairs = array(); 82 | foreach ($args as $arg => $value) { 83 | $argsPairs[] = \sprintf("%s='%s'", $arg, $this->escape($value)); 84 | } 85 | 86 | return \sprintf( 87 | "##teamcity[%s %s]\n\n", 88 | $messageName, 89 | \implode(' ', $argsPairs) 90 | ); 91 | } 92 | 93 | 94 | private function escape($value) 95 | { 96 | $replace = array( 97 | "|" => "||", 98 | "'" => "|'", 99 | "\n" => "|n", 100 | "\r" => "|r", 101 | "]" => "|]", 102 | "[" => "|[", 103 | ); 104 | 105 | return \strtr($value, $replace); 106 | } 107 | 108 | } 109 | 110 | 111 | /** @var Runner $runner */ 112 | // replace registered output handlers with TC 113 | $runner->outputHandlers = array(); 114 | $runner->outputHandlers[] = new TeamCityOutputHandler(); 115 | -------------------------------------------------------------------------------- /resources/setup2-0.php: -------------------------------------------------------------------------------- 1 | file = \fopen($output, 'w'); 25 | } 26 | 27 | 28 | public function begin(): void 29 | { 30 | // \fwrite($this->file, $this->message('testCount', array('count' => 0))); 31 | } 32 | 33 | function prepare(\Tester\Runner\Test $test): void 34 | { 35 | // TODO: Implement prepare() method. 36 | } 37 | 38 | function finish(\Tester\Runner\Test $test): void 39 | { 40 | $testName = $test->title ?: $test->getFile(); 41 | $result = $test->getResult(); 42 | $message = $test->message; 43 | 44 | $flowId = \md5($testName); 45 | \fwrite($this->file, $this->message('testStarted', array('name' => $testName, 'flowId' => $flowId))); 46 | 47 | if ($result === \Tester\Runner\Test::SKIPPED) { // Runner::SKIPPED, Test::SKIPPED 48 | \fwrite($this->file, $this->message('testIgnored', array('name' => $testName, 'flowId' => $flowId, 'message' => 'Test skipped', 'details' => $message))); 49 | 50 | } elseif ($result === \Tester\Runner\Test::FAILED) { // Runner::FAILED, Test::FAILED 51 | $extraArguments = array(); 52 | if (\preg_match("/^diff \"(.*)\" \"(.*)\"$/m", $message, $matches)) { // Windows build 53 | $expectedFile = \str_replace('""', '"', $matches[1]); 54 | $actualFile = \str_replace('""', '"', $matches[2]); 55 | $extraArguments = array('type' => 'comparisonFailure', 'expectedFile' => $expectedFile, 'actualFile' => $actualFile); 56 | 57 | } elseif (\preg_match("/^diff '?(.*)'? '?(.*)'?$/m", $message, $matches)) { 58 | $expectedFile = \trim($matches[1], "'"); 59 | $actualFile = \trim($matches[2], "'"); 60 | $extraArguments = array('type' => 'comparisonFailure', 'expectedFile' => $expectedFile, 'actualFile' => $actualFile); 61 | 62 | } elseif (\preg_match("/Failed: (.*) should be( equal to)?\s+\.*\s*(.*) in/is", $message, $matches)) { 63 | $expected = $matches[3]; 64 | $actual = $matches[1]; 65 | $extraArguments = array('type' => 'comparisonFailure', 'expected' => $expected, 'actual' => $actual); 66 | } 67 | 68 | $args = \array_merge(array( 69 | 'name' => $testName, 70 | 'flowId' => $flowId, 71 | 'message' => 'Test failed', 72 | 'details' => $message, 73 | ), $extraArguments); 74 | 75 | \fwrite($this->file, $this->message('testFailed', $args)); 76 | } 77 | 78 | \fwrite($this->file, $this->message('testFinished', array('name' => $testName, 'flowId' => $flowId))); 79 | } 80 | 81 | 82 | public function end(): void 83 | { 84 | } 85 | 86 | 87 | private function message($messageName, $args): string 88 | { 89 | $argsPairs = array(); 90 | foreach ($args as $arg => $value) { 91 | $argsPairs[] = \sprintf("%s='%s'", $arg, $this->escape($value)); 92 | } 93 | 94 | return \sprintf( 95 | "##teamcity[%s %s]\n\n", 96 | $messageName, 97 | \implode(' ', $argsPairs) 98 | ); 99 | } 100 | 101 | 102 | private function escape($value): string 103 | { 104 | $replace = array( 105 | "|" => "||", 106 | "'" => "|'", 107 | "\n" => "|n", 108 | "\r" => "|r", 109 | "]" => "|]", 110 | "[" => "|[", 111 | ); 112 | 113 | return \strtr($value, $replace); 114 | } 115 | 116 | } 117 | 118 | 119 | /** @var Runner $runner */ 120 | // replace registered output handlers with TC 121 | $runner->outputHandlers = array(); 122 | $runner->outputHandlers[] = new TeamCityOutputHandler(); -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/TesterBundle.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester; 2 | 3 | import com.intellij.CommonBundle; 4 | import org.jetbrains.annotations.NonNls; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.PropertyKey; 7 | 8 | import java.util.ResourceBundle; 9 | 10 | public class TesterBundle { 11 | @NonNls private static final String BUNDLE_NAME = "messages.TesterBundle"; 12 | @NotNull private static final ResourceBundle BUNDLE = ResourceBundle.getBundle(BUNDLE_NAME); 13 | 14 | private TesterBundle() { 15 | } 16 | 17 | public static String message(@PropertyKey(resourceBundle = BUNDLE_NAME) String key, Object... params) { 18 | return CommonBundle.message(BUNDLE, key, params); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/TesterIcons.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester; 2 | 3 | import com.intellij.openapi.util.IconLoader; 4 | 5 | import javax.swing.*; 6 | 7 | public class TesterIcons { 8 | /** file icon */ 9 | public static final Icon RUN_CLASS = IconLoader.getIcon("/icons/runClass.png"); 10 | public static final Icon RUN_METHOD = IconLoader.getIcon("/icons/runMethod.png"); 11 | public static final Icon RUN = IconLoader.getIcon("/icons/run.png"); 12 | public static final Icon TESTER_CONFIG = IconLoader.getIcon("/icons/testConfig.png"); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/TesterPostStartupActivity.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.startup.StartupActivity; 5 | import com.intellij.testIntegration.LanguageTestCreators; 6 | import com.jetbrains.php.lang.PhpLanguage; 7 | import cz.jiripudil.intellij.nette.tester.codeGeneration.TesterTestCreator; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public class TesterPostStartupActivity implements StartupActivity { 11 | @Override 12 | public void runActivity(@NotNull Project project) { 13 | LanguageTestCreators.INSTANCE.addExplicitExtension(PhpLanguage.INSTANCE, TesterTestCreator.INSTANCE); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/TesterTestFinder.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.util.text.StringUtil; 5 | import com.intellij.psi.PsiElement; 6 | import com.intellij.psi.PsiFile; 7 | import com.intellij.psi.search.FilenameIndex; 8 | import com.intellij.psi.search.GlobalSearchScope; 9 | import com.intellij.psi.util.PsiTreeUtil; 10 | import com.intellij.testIntegration.TestFinder; 11 | import com.jetbrains.php.PhpIndex; 12 | import com.jetbrains.php.lang.psi.elements.PhpClass; 13 | import com.jetbrains.php.lang.psi.elements.PhpNamedElement; 14 | import com.jetbrains.php.lang.psi.elements.PhpPsiElement; 15 | import org.jetbrains.annotations.NotNull; 16 | import org.jetbrains.annotations.Nullable; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.Collection; 21 | 22 | public class TesterTestFinder implements TestFinder { 23 | @Nullable 24 | @Override 25 | public PsiElement findSourceElement(@NotNull PsiElement psiElement) { 26 | return PsiTreeUtil.getParentOfType(psiElement, PhpNamedElement.class, false); 27 | } 28 | 29 | @NotNull 30 | @Override 31 | public Collection findTestsForClass(@NotNull PsiElement psiElement) { 32 | Collection tests = new ArrayList<>(); 33 | 34 | PhpClass phpClass = PsiTreeUtil.getStubOrPsiParentOfType(psiElement, PhpClass.class); 35 | if (phpClass != null) { 36 | Project project = phpClass.getProject(); 37 | Collection testClasses = PhpIndex.getInstance(project).getClassesByName(phpClass.getName() + "Test"); 38 | if (!testClasses.isEmpty()) { 39 | tests.addAll(testClasses); 40 | } 41 | 42 | PsiFile[] files = FilenameIndex.getFilesByName(project, phpClass.getName() + ".phpt", GlobalSearchScope.projectScope(project)); 43 | tests.addAll(Arrays.asList(files)); 44 | } 45 | 46 | return tests; 47 | } 48 | 49 | @NotNull 50 | @Override 51 | public Collection findClassesForTest(@NotNull PsiElement psiElement) { 52 | Collection classes = new ArrayList<>(); 53 | 54 | PhpClass testClass = PsiTreeUtil.getStubOrPsiParentOfType(psiElement, PhpClass.class); 55 | if (testClass != null && StringUtil.endsWith(testClass.getName(), "Test")) { 56 | Project project = testClass.getProject(); 57 | Collection sourceClasses = PhpIndex.getInstance(project).getClassesByName(testClass.getName().substring(0, testClass.getName().length() - "Test".length())); 58 | if (!sourceClasses.isEmpty()) { 59 | classes.addAll(sourceClasses); 60 | } 61 | } 62 | 63 | PsiFile containingFile = psiElement.getContainingFile(); 64 | if (containingFile != null) { 65 | Project project = containingFile.getProject(); 66 | String name = containingFile.getVirtualFile().getNameWithoutExtension(); 67 | if (StringUtil.endsWith(name, "Test")) { 68 | name = name.substring(0, name.length() - "Test".length()); 69 | } 70 | 71 | Collection sourceClasses = PhpIndex.getInstance(project).getClassesByName(name); 72 | classes.addAll(sourceClasses); 73 | } 74 | 75 | return classes; 76 | } 77 | 78 | @Override 79 | public boolean isTest(@NotNull PsiElement psiElement) { 80 | PsiFile containingFile = psiElement.getContainingFile(); 81 | return containingFile instanceof PhpPsiElement && ( 82 | (containingFile.getVirtualFile() != null && containingFile.getVirtualFile().getExtension() != null && containingFile.getVirtualFile().getExtension().equals("phpt")) 83 | || StringUtil.endsWith(containingFile.getName(), "Test") 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/TesterUtil.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester; 2 | 3 | import com.intellij.execution.RunManager; 4 | import com.intellij.execution.RunnerAndConfigurationSettings; 5 | import com.intellij.execution.configurations.RunConfiguration; 6 | import com.intellij.execution.configurations.RuntimeConfigurationException; 7 | import com.intellij.notification.Notification; 8 | import com.intellij.notification.NotificationType; 9 | import com.intellij.notification.Notifications; 10 | import com.intellij.openapi.application.Application; 11 | import com.intellij.openapi.application.ApplicationManager; 12 | import com.intellij.openapi.project.Project; 13 | import com.intellij.openapi.util.Ref; 14 | import com.intellij.openapi.util.text.StringUtil; 15 | import com.jetbrains.php.PhpClassHierarchyUtils; 16 | import com.jetbrains.php.lang.PhpLangUtil; 17 | import com.jetbrains.php.lang.psi.elements.Method; 18 | import com.jetbrains.php.lang.psi.elements.PhpClass; 19 | import cz.jiripudil.intellij.nette.tester.configuration.TesterRunConfiguration; 20 | import cz.jiripudil.intellij.nette.tester.configuration.TesterRunConfigurationType; 21 | import cz.jiripudil.intellij.nette.tester.configuration.TesterTestMethodRunConfiguration; 22 | import cz.jiripudil.intellij.nette.tester.configuration.TesterTestMethodRunConfigurationType; 23 | import cz.jiripudil.intellij.nette.tester.projectSettings.TesterProjectSettings; 24 | import cz.jiripudil.intellij.nette.tester.projectSettings.TesterProjectSettingsManager; 25 | import org.jetbrains.annotations.Nls; 26 | import org.jetbrains.annotations.NotNull; 27 | import org.jetbrains.annotations.Nullable; 28 | 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | public class TesterUtil { 33 | public static String NOTIFICATION_GROUP = "Nette tester"; 34 | 35 | public static boolean isTestClass(@NotNull PhpClass phpClass) { 36 | if (phpClass.isAbstract() || phpClass.isInterface() || phpClass.isTrait()) { 37 | return false; 38 | } 39 | 40 | final Ref isTestCase = new Ref<>(false); 41 | PhpClassHierarchyUtils.processSuperClasses(phpClass, true, true, phpClass1 -> { 42 | String superFQN = phpClass1.getSuperFQN(); 43 | if (superFQN != null && PhpLangUtil.equalsClassNames("\\Tester\\TestCase", superFQN)) { 44 | isTestCase.set(true); 45 | } 46 | 47 | return !isTestCase.get(); 48 | }); 49 | 50 | return isTestCase.get(); 51 | } 52 | 53 | public static TesterProjectSettings getTesterSettings(@NotNull Project project) { 54 | return TesterProjectSettingsManager.getInstance(project).getState(); 55 | } 56 | 57 | public static boolean isTestMethod(@NotNull Method method) { 58 | return method.getContainingClass() != null 59 | && isTestClass(method.getContainingClass()) 60 | && StringUtil.startsWith(method.getName(), "test") 61 | && method.getModifier().isPublic(); 62 | } 63 | 64 | @Nullable 65 | public static TesterRunConfiguration getMainConfiguration(@NotNull Project project) { 66 | List configurations = TesterUtil.getRunConfigurations(project); 67 | return getMainConfiguration(project, configurations); 68 | } 69 | 70 | @Nullable 71 | public static TesterRunConfiguration getMainConfiguration( 72 | @NotNull Project project, 73 | @NotNull List configurations 74 | ) { 75 | @Nullable TesterRunConfiguration mainConfiguration = configurations.stream() 76 | .filter(configuration -> "tests".equals(configuration.getName())) 77 | .findAny() 78 | .orElse(null); 79 | if (mainConfiguration == null) { 80 | TesterUtil.doNotify( 81 | TesterBundle.message("runConfiguration.mainConfiguration.missing.title"), 82 | TesterBundle.message("runConfiguration.mainConfiguration.missing.description"), 83 | NotificationType.ERROR, 84 | project 85 | ); 86 | return null; 87 | 88 | } else { 89 | try { 90 | mainConfiguration.checkConfiguration(); 91 | return mainConfiguration; 92 | 93 | } catch (RuntimeConfigurationException ex) { 94 | TesterUtil.doNotify( 95 | TesterBundle.message("runConfiguration.mainConfiguration.invalid.title"), 96 | TesterBundle.message("runConfiguration.mainConfiguration.invalid.description"), 97 | NotificationType.ERROR, 98 | project 99 | ); 100 | } 101 | return null; 102 | } 103 | } 104 | 105 | public static List getRunConfigurations(@NotNull Project project) { 106 | List configurations = new ArrayList(); 107 | List settings = RunManager.getInstance(project) 108 | .getConfigurationSettingsList(TesterRunConfigurationType.class); 109 | for (RunnerAndConfigurationSettings setting : settings) { 110 | RunConfiguration configuration = setting.getConfiguration(); 111 | if (configuration instanceof TesterRunConfiguration) { 112 | configurations.add((TesterRunConfiguration) configuration); 113 | } 114 | } 115 | return configurations; 116 | } 117 | 118 | public static List getMethodRunConfigurations(@NotNull Project project) { 119 | List configurations = new ArrayList(); 120 | List settings = RunManager.getInstance(project) 121 | .getConfigurationSettingsList(TesterTestMethodRunConfigurationType.class); 122 | for (RunnerAndConfigurationSettings setting : settings) { 123 | RunConfiguration configuration = setting.getConfiguration(); 124 | if (configuration instanceof TesterTestMethodRunConfiguration) { 125 | configurations.add((TesterTestMethodRunConfiguration) configuration); 126 | } 127 | } 128 | return configurations; 129 | } 130 | 131 | public static void doNotify( 132 | @NotNull String title, 133 | @NotNull @Nls(capitalization = Nls.Capitalization.Sentence) String content, 134 | @NotNull NotificationType type, 135 | @Nullable Project project 136 | ) { 137 | Notification notification = new Notification(NOTIFICATION_GROUP, title, content, NotificationType.ERROR); 138 | doNotify(notification, project); 139 | } 140 | 141 | public static void doNotify(Notification notification, @Nullable Project project) { 142 | if (project != null && !project.isDisposed() && !project.isDefault()) { 143 | ((Notifications)project.getMessageBus().syncPublisher(Notifications.TOPIC)).notify(notification); 144 | } else { 145 | Application app = ApplicationManager.getApplication(); 146 | if (!app.isDisposed()) { 147 | ((Notifications)app.getMessageBus().syncPublisher(Notifications.TOPIC)).notify(notification); 148 | } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/action/BaseTesterRunAction.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.action; 2 | 3 | import com.intellij.execution.RunnerAndConfigurationSettings; 4 | import com.intellij.ide.actions.runAnything.RunAnythingAction; 5 | import com.intellij.notification.NotificationType; 6 | import com.intellij.openapi.actionSystem.AnActionEvent; 7 | import com.intellij.openapi.project.Project; 8 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 9 | import cz.jiripudil.intellij.nette.tester.TesterUtil; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | abstract public class BaseTesterRunAction extends RunAnythingAction { 14 | 15 | protected Project project; 16 | protected boolean wasCreated = false; 17 | 18 | BaseTesterRunAction(@NotNull Project project) { 19 | this.project = project; 20 | } 21 | 22 | @Nullable 23 | abstract protected RunnerAndConfigurationSettings prepareAction(); 24 | 25 | protected boolean isTemporary() { 26 | return false; 27 | } 28 | 29 | protected Project getProject() { 30 | return project; 31 | } 32 | 33 | @Override 34 | public void actionPerformed(@NotNull AnActionEvent e) { 35 | prepareAction(); 36 | 37 | String title; 38 | String description; 39 | if (!wasCreated) { 40 | title = TesterBundle.message("runConfiguration.mainConfiguration.alreadyCreated.title"); 41 | description = TesterBundle.message("runConfiguration.mainConfiguration.alreadyCreated.description"); 42 | } else { 43 | title = TesterBundle.message("runConfiguration.mainConfiguration.created.title"); 44 | description = TesterBundle.message("runConfiguration.mainConfiguration.created.description"); 45 | } 46 | TesterUtil.doNotify(title, description, NotificationType.INFORMATION, project); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/action/TesterCreateMethodRunTestAction.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.action; 2 | 3 | import com.intellij.execution.RunManager; 4 | import com.intellij.execution.RunnerAndConfigurationSettings; 5 | import com.intellij.execution.configurations.ConfigurationFactory; 6 | import com.intellij.execution.configurations.RuntimeConfigurationException; 7 | import com.intellij.psi.PsiElement; 8 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 9 | import cz.jiripudil.intellij.nette.tester.TesterIcons; 10 | import cz.jiripudil.intellij.nette.tester.TesterUtil; 11 | import cz.jiripudil.intellij.nette.tester.configuration.TesterRunConfiguration; 12 | import cz.jiripudil.intellij.nette.tester.configuration.TesterTestMethodRunConfiguration; 13 | import cz.jiripudil.intellij.nette.tester.configuration.TesterTestMethodRunConfigurationType; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | import java.util.List; 18 | import java.util.stream.Collectors; 19 | 20 | public class TesterCreateMethodRunTestAction extends BaseTesterRunAction { 21 | private final String path; 22 | private final String methodName; 23 | private final String testName; 24 | 25 | public TesterCreateMethodRunTestAction(@NotNull PsiElement element, @NotNull String testName, @NotNull String testMethod) { 26 | super(element.getProject()); 27 | 28 | this.path = element.getContainingFile().getVirtualFile().getPath(); 29 | this.testName = testName; 30 | this.methodName = testMethod; 31 | 32 | getTemplatePresentation().setText(TesterBundle.message("action.createRunTestAction.name") + " '" + testMethod + "()'..."); 33 | getTemplatePresentation().setIcon(TesterIcons.TESTER_CONFIG); 34 | } 35 | 36 | @Nullable 37 | protected RunnerAndConfigurationSettings prepareAction() { 38 | wasCreated = false; 39 | List configurations = TesterUtil.getMethodRunConfigurations(project); 40 | List samePath = configurations.stream() 41 | .filter(configuration -> { 42 | if (configuration.getMethod() == null) { 43 | return false; 44 | } 45 | 46 | try { 47 | configuration.checkConfiguration(); 48 | 49 | } catch (RuntimeConfigurationException ex) { 50 | return false; 51 | } 52 | return path.equals(configuration.getSettings().getPath()) 53 | && configuration.getMethod().startsWith(methodName); 54 | }) 55 | .collect(Collectors.toList()); 56 | 57 | RunManager manager = RunManager.getInstance(project); 58 | if (samePath.size() == 0) { 59 | TesterRunConfiguration mainConfiguration = TesterUtil.getMainConfiguration(project); 60 | if (mainConfiguration == null) { 61 | return null; 62 | } 63 | 64 | ConfigurationFactory factory = TesterTestMethodRunConfigurationType.createFactory(); 65 | TesterTestMethodRunConfiguration current = new TesterTestMethodRunConfiguration( 66 | project, 67 | factory, 68 | getConfigurationName() 69 | ); 70 | current.getSettings().setPath(path); 71 | current.setMethodName(methodName); 72 | 73 | //todo: get more information from mainConfiguration and save to method configuration 74 | current.getSettings().setCommandLineSettings(mainConfiguration.getSettings().getPhpCommandLineSettings()); 75 | 76 | RunnerAndConfigurationSettings action = manager.createConfiguration(current, factory); 77 | action.setTemporary(isTemporary()); 78 | 79 | manager.addConfiguration(action); 80 | manager.setSelectedConfiguration(action); 81 | wasCreated = true; 82 | return action; 83 | 84 | } else { 85 | TesterTestMethodRunConfiguration existing = samePath.get(0); 86 | if (existing.getFactory() == null) { 87 | return null; 88 | } 89 | RunnerAndConfigurationSettings action = manager.createConfiguration(existing, existing.getFactory()); 90 | manager.setSelectedConfiguration(action); 91 | return action; 92 | } 93 | } 94 | 95 | private String getConfigurationName() { 96 | return "tests:" + testName + ":" + TesterTestMethodRunConfiguration.createSuggestedName(methodName).trim(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/action/TesterCreateRunTestAction.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.action; 2 | 3 | import com.intellij.execution.RunManager; 4 | import com.intellij.execution.RunnerAndConfigurationSettings; 5 | import com.intellij.execution.configurations.RuntimeConfigurationException; 6 | import com.intellij.psi.PsiElement; 7 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 8 | import cz.jiripudil.intellij.nette.tester.TesterIcons; 9 | import cz.jiripudil.intellij.nette.tester.TesterUtil; 10 | import cz.jiripudil.intellij.nette.tester.configuration.TesterRunConfiguration; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | 17 | public class TesterCreateRunTestAction extends BaseTesterRunAction { 18 | private final String path; 19 | private final String testName; 20 | 21 | public TesterCreateRunTestAction(@NotNull PsiElement element, @NotNull String testName) { 22 | super(element.getProject()); 23 | this.path = element.getContainingFile().getVirtualFile().getPath(); 24 | this.testName = testName; 25 | 26 | getTemplatePresentation().setText(TesterBundle.message("action.createRunTestAction.name") + " '" + testName + "'..."); 27 | getTemplatePresentation().setIcon(TesterIcons.TESTER_CONFIG); 28 | } 29 | 30 | @Nullable 31 | protected RunnerAndConfigurationSettings prepareAction() { 32 | wasCreated = false; 33 | List configurations = TesterUtil.getRunConfigurations(getProject()); 34 | List samePath = configurations.stream() 35 | .filter(configuration -> { 36 | try { 37 | configuration.checkConfiguration(); 38 | 39 | } catch (RuntimeConfigurationException ex) { 40 | return false; 41 | } 42 | return path.equals(configuration.getSettings().getTestScope()); 43 | }) 44 | .collect(Collectors.toList()); 45 | 46 | RunManager manager = RunManager.getInstance(getProject()); 47 | if (samePath.size() == 0) { 48 | TesterRunConfiguration mainConfiguration = TesterUtil.getMainConfiguration(getProject(), configurations); 49 | if (mainConfiguration == null || mainConfiguration.getFactory() == null) { 50 | return null; 51 | } 52 | 53 | TesterRunConfiguration current = (TesterRunConfiguration) mainConfiguration.clone(); 54 | current.setName("tests:" + testName); 55 | current.getSettings().setTestScope(path); 56 | RunnerAndConfigurationSettings action = manager.createConfiguration(current, mainConfiguration.getFactory()); 57 | action.setTemporary(isTemporary()); 58 | 59 | manager.addConfiguration(action); 60 | manager.setSelectedConfiguration(action); 61 | wasCreated = true; 62 | return action; 63 | 64 | } else { 65 | TesterRunConfiguration existing = samePath.get(0); 66 | if (existing.getFactory() == null) { 67 | return null; 68 | } 69 | 70 | RunnerAndConfigurationSettings action = manager.createConfiguration(existing, existing.getFactory()); 71 | manager.setSelectedConfiguration(action); 72 | return action; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/action/TesterMethodRunTestAction.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.action; 2 | 3 | import com.intellij.execution.ProgramRunnerUtil; 4 | import com.intellij.execution.RunnerAndConfigurationSettings; 5 | import com.intellij.execution.executors.DefaultRunExecutor; 6 | import com.intellij.openapi.actionSystem.AnActionEvent; 7 | import com.intellij.psi.PsiElement; 8 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 9 | import cz.jiripudil.intellij.nette.tester.TesterIcons; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public class TesterMethodRunTestAction extends TesterCreateMethodRunTestAction { 13 | 14 | public TesterMethodRunTestAction(@NotNull PsiElement element, @NotNull String testName, @NotNull String testMethod) { 15 | super(element, testName, testMethod); 16 | 17 | getTemplatePresentation().setText(TesterBundle.message("action.runTestAction.name") + " '" + testMethod + "()'"); 18 | getTemplatePresentation().setIcon(TesterIcons.RUN); 19 | } 20 | 21 | protected boolean isTemporary() { 22 | return true; 23 | } 24 | 25 | @Override 26 | public void actionPerformed(@NotNull AnActionEvent e) { 27 | RunnerAndConfigurationSettings forRun = prepareAction(); 28 | if (forRun != null) { 29 | ProgramRunnerUtil.executeConfiguration(forRun, DefaultRunExecutor.getRunExecutorInstance()); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/action/TesterRunTestAction.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.action; 2 | 3 | import com.intellij.execution.ProgramRunnerUtil; 4 | import com.intellij.execution.RunnerAndConfigurationSettings; 5 | import com.intellij.execution.executors.DefaultRunExecutor; 6 | import com.intellij.openapi.actionSystem.AnActionEvent; 7 | import com.intellij.psi.PsiElement; 8 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 9 | import cz.jiripudil.intellij.nette.tester.TesterIcons; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public class TesterRunTestAction extends TesterCreateRunTestAction { 13 | 14 | public TesterRunTestAction(@NotNull PsiElement element, @NotNull String testName) { 15 | super(element, testName); 16 | 17 | getTemplatePresentation().setText(TesterBundle.message("action.runTestAction.name") + " '" + testName + "'"); 18 | getTemplatePresentation().setIcon(TesterIcons.RUN); 19 | } 20 | 21 | protected boolean isTemporary() { 22 | return true; 23 | } 24 | 25 | @Override 26 | public void actionPerformed(@NotNull AnActionEvent e) { 27 | RunnerAndConfigurationSettings forRun = prepareAction(); 28 | if (forRun != null) { 29 | ProgramRunnerUtil.executeConfiguration(forRun, DefaultRunExecutor.getRunExecutorInstance()); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/codeGeneration/TesterAbstractGenerateMethodAction.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.codeGeneration; 2 | 3 | import com.intellij.codeInsight.CodeInsightActionHandler; 4 | import com.intellij.codeInsight.actions.CodeInsightAction; 5 | import com.intellij.openapi.editor.Editor; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.util.TextRange; 8 | import com.intellij.psi.PsiDocumentManager; 9 | import com.intellij.psi.PsiElement; 10 | import com.intellij.psi.PsiFile; 11 | import com.jetbrains.php.lang.psi.PhpCodeEditUtil; 12 | import com.jetbrains.php.lang.psi.PhpPsiElementFactory; 13 | import com.jetbrains.php.lang.psi.elements.Method; 14 | import com.jetbrains.php.lang.psi.elements.PhpClass; 15 | import cz.jiripudil.intellij.nette.tester.TesterUtil; 16 | import org.jetbrains.annotations.NotNull; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | abstract public class TesterAbstractGenerateMethodAction extends CodeInsightAction implements CodeInsightActionHandler { 20 | @NotNull 21 | @Override 22 | protected CodeInsightActionHandler getHandler() { 23 | return this; 24 | } 25 | 26 | @Override 27 | protected boolean isValidForFile(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { 28 | return findTestClass(editor, file) != null; 29 | } 30 | 31 | @Nullable 32 | private static PhpClass findTestClass(@NotNull Editor editor, @NotNull PsiFile file) { 33 | PhpClass testClass = PhpCodeEditUtil.findClassAtCaret(editor, file); 34 | return testClass != null && TesterUtil.isTestClass(testClass) ? testClass : null; 35 | } 36 | 37 | @Override 38 | public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile psiFile) { 39 | PhpClass phpClass = findTestClass(editor, psiFile); 40 | Method method = PhpPsiElementFactory.createMethod(project, "public function testFoo(){}"); 41 | 42 | assert phpClass != null; 43 | 44 | PsiElement element = PhpCodeEditUtil.insertClassMember(phpClass, method); 45 | PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument()); 46 | TextRange range = element.getTextRange(); 47 | editor.getDocument().replaceString(range.getStartOffset(), range.getEndOffset(), ""); 48 | editor.getCaretModel().moveToOffset(range.getStartOffset()); 49 | insertMethod(project, editor); 50 | } 51 | 52 | abstract protected void insertMethod(@NotNull Project project, @NotNull Editor editor); 53 | } 54 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/codeGeneration/TesterGenerateSetupMethodAction.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.codeGeneration; 2 | 3 | import com.intellij.codeInsight.template.Template; 4 | import com.intellij.codeInsight.template.TemplateManager; 5 | import com.intellij.openapi.editor.Editor; 6 | import com.intellij.openapi.project.Project; 7 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public class TesterGenerateSetupMethodAction extends TesterAbstractGenerateMethodAction { 11 | public TesterGenerateSetupMethodAction() { 12 | getTemplatePresentation().setText(TesterBundle.message("action.generateSetupMethod.name")); 13 | getTemplatePresentation().setDescription(TesterBundle.message("action.generateSetupMethod.description")); 14 | } 15 | 16 | @Override 17 | protected void insertMethod(@NotNull Project project, @NotNull Editor editor) { 18 | Template template = TemplateManager.getInstance(project).createTemplate("", ""); 19 | template.addTextSegment("protected function setUp()\n{\n"); 20 | template.addTextSegment("parent::setUp();\n"); 21 | template.addEndVariable(); 22 | template.addTextSegment("\n}"); 23 | template.setToIndent(true); 24 | template.setToReformat(true); 25 | template.setToShortenLongNames(true); 26 | TemplateManager.getInstance(project).startTemplate(editor, template, null); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/codeGeneration/TesterGenerateTeardownMethodAction.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.codeGeneration; 2 | 3 | import com.intellij.codeInsight.template.Template; 4 | import com.intellij.codeInsight.template.TemplateManager; 5 | import com.intellij.openapi.editor.Editor; 6 | import com.intellij.openapi.project.Project; 7 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 8 | import org.jetbrains.annotations.NotNull; 9 | 10 | public class TesterGenerateTeardownMethodAction extends TesterAbstractGenerateMethodAction { 11 | public TesterGenerateTeardownMethodAction() { 12 | getTemplatePresentation().setText(TesterBundle.message("action.generateTeardownMethod.name")); 13 | getTemplatePresentation().setDescription(TesterBundle.message("action.generateTeardownMethod.description")); 14 | } 15 | 16 | @Override 17 | protected void insertMethod(@NotNull Project project, @NotNull Editor editor) { 18 | Template template = TemplateManager.getInstance(project).createTemplate("", ""); 19 | template.addTextSegment("protected function tearDown()\n{\n"); 20 | template.addTextSegment("parent::tearDown();\n"); 21 | template.addEndVariable(); 22 | template.addTextSegment("\n}"); 23 | template.setToIndent(true); 24 | template.setToReformat(true); 25 | template.setToShortenLongNames(true); 26 | TemplateManager.getInstance(project).startTemplate(editor, template, null); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/codeGeneration/TesterGenerateTestMethodAction.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.codeGeneration; 2 | 3 | import com.intellij.codeInsight.template.Expression; 4 | import com.intellij.codeInsight.template.Template; 5 | import com.intellij.codeInsight.template.TemplateManager; 6 | import com.intellij.codeInsight.template.impl.ConstantNode; 7 | import com.intellij.openapi.editor.Editor; 8 | import com.intellij.openapi.project.Project; 9 | import com.jetbrains.php.config.PhpLanguageFeature; 10 | import com.jetbrains.php.config.PhpLanguageLevel; 11 | import com.jetbrains.php.config.PhpProjectConfigurationFacade; 12 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | public class TesterGenerateTestMethodAction extends TesterAbstractGenerateMethodAction { 16 | public TesterGenerateTestMethodAction() { 17 | getTemplatePresentation().setText(TesterBundle.message("action.generateTestMethod.name")); 18 | getTemplatePresentation().setDescription(TesterBundle.message("action.generateTestMethod.description")); 19 | } 20 | 21 | @Override 22 | protected void insertMethod(@NotNull Project project, @NotNull Editor editor) { 23 | Template template = TemplateManager.getInstance(project).createTemplate("", ""); 24 | template.addTextSegment("public function test"); 25 | 26 | Expression nameExpr = new ConstantNode(""); 27 | template.addVariable("name", nameExpr, nameExpr, true); 28 | template.addTextSegment("()"); 29 | 30 | PhpLanguageLevel languageLevel = PhpProjectConfigurationFacade.getInstance(project).getLanguageLevel(); 31 | if (languageLevel.hasFeature(PhpLanguageFeature.RETURN_VOID)) { 32 | template.addTextSegment(": void"); 33 | } 34 | 35 | template.addTextSegment("\n{\n"); 36 | template.addEndVariable(); 37 | template.addTextSegment("\n}"); 38 | template.setToIndent(true); 39 | template.setToReformat(true); 40 | template.setToShortenLongNames(true); 41 | TemplateManager.getInstance(project).startTemplate(editor, template, null); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/codeGeneration/TesterNamespaceMapper.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.codeGeneration; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.util.text.StringUtil; 5 | import com.jetbrains.php.lang.psi.elements.PhpClass; 6 | import cz.jiripudil.intellij.nette.tester.TesterUtil; 7 | import cz.jiripudil.intellij.nette.tester.projectSettings.TesterNamespaceMapping; 8 | import cz.jiripudil.intellij.nette.tester.projectSettings.TesterProjectSettings; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.List; 12 | 13 | public class TesterNamespaceMapper { 14 | @NotNull private final Project project; 15 | 16 | private TesterNamespaceMapper(@NotNull final Project project) { 17 | this.project = project; 18 | } 19 | 20 | public static TesterNamespaceMapper getInstance(@NotNull final Project project) { 21 | return new TesterNamespaceMapper(project); 22 | } 23 | 24 | @NotNull 25 | String mapSourceNamespaceToTestNamespace(@NotNull PhpClass sourceClass) { 26 | TesterProjectSettings settings = TesterUtil.getTesterSettings(project); 27 | String namespaceName = trimNamespaceSeparators(sourceClass.getNamespaceName()); 28 | if (settings == null) { 29 | return namespaceName; 30 | } 31 | 32 | List mappings = settings.getNamespaceMappings(); 33 | for (TesterNamespaceMapping mapping : mappings) { 34 | String fqn = trimNamespaceSeparators(sourceClass.getFQN()); 35 | if (StringUtil.startsWith(fqn, mapping.getSourceNamespace() + "\\")) { 36 | return trimNamespaceSeparators( 37 | mapping.getTestsNamespace() 38 | + "\\" + 39 | trimNamespaceSeparators(namespaceName.substring(mapping.getSourceNamespace().length())) 40 | ); 41 | } 42 | } 43 | 44 | return namespaceName; 45 | } 46 | 47 | @NotNull 48 | private String trimNamespaceSeparators(@NotNull String namespace) { 49 | return StringUtil.trimStart(StringUtil.trimEnd(namespace, "\\"), "\\"); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/codeGeneration/TesterNewTestCaseAction.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.codeGeneration; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.psi.PsiDirectory; 5 | import com.intellij.psi.PsiFile; 6 | import com.jetbrains.php.PhpIcons; 7 | import com.jetbrains.php.actions.PhpNewBaseAction; 8 | import com.jetbrains.php.templates.PhpCreateFileFromTemplateDataProvider; 9 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | public class TesterNewTestCaseAction extends PhpNewBaseAction { 14 | private final boolean fixedDirectory; 15 | 16 | public TesterNewTestCaseAction() { 17 | this(true); 18 | } 19 | 20 | TesterNewTestCaseAction(boolean fixedDirectory) { 21 | super(TesterBundle.message("action.newTestCase.name"), TesterBundle.message("action.newTestCase.description"), PhpIcons.PHP_TEST_FILE); 22 | this.fixedDirectory = fixedDirectory; 23 | } 24 | 25 | @Nullable 26 | @Override 27 | protected PhpCreateFileFromTemplateDataProvider getDataProvider(@NotNull Project project, @NotNull PsiDirectory psiDirectory, @Nullable PsiFile psiFile) { 28 | TesterNewTestCaseDialog dialog = new TesterNewTestCaseDialog(project, psiDirectory, psiFile, fixedDirectory); 29 | return dialog.showAndGet() ? dialog : null; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/codeGeneration/TesterNewTestCaseDialog.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 |
144 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/codeGeneration/TesterNewTestCaseDialog.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.codeGeneration; 2 | 3 | import com.intellij.openapi.editor.Editor; 4 | import com.intellij.openapi.editor.event.DocumentAdapter; 5 | import com.intellij.openapi.editor.event.DocumentEvent; 6 | import com.intellij.openapi.fileTypes.FileTypes; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.roots.ProjectFileIndex; 9 | import com.intellij.openapi.roots.ProjectRootManager; 10 | import com.intellij.openapi.util.text.StringUtil; 11 | import com.intellij.openapi.vfs.LocalFileSystem; 12 | import com.intellij.openapi.vfs.VirtualFile; 13 | import com.intellij.psi.PsiDirectory; 14 | import com.intellij.psi.PsiFile; 15 | import com.intellij.ui.EditorTextField; 16 | import com.intellij.ui.components.JBLabel; 17 | import com.intellij.util.ui.UIUtil; 18 | import com.jetbrains.php.PhpIndex; 19 | import com.jetbrains.php.actions.PhpBaseNewClassDialog; 20 | import com.jetbrains.php.completion.PhpCompletionUtil; 21 | import com.jetbrains.php.lang.PhpLangUtil; 22 | import com.jetbrains.php.lang.psi.elements.PhpClass; 23 | import com.jetbrains.php.roots.ui.PhpNamespaceComboBox; 24 | import com.jetbrains.php.roots.ui.PhpPsrDirectoryComboBox; 25 | import com.jetbrains.php.ui.PhpUiUtil; 26 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 27 | import cz.jiripudil.intellij.nette.tester.TesterUtil; 28 | import cz.jiripudil.intellij.nette.tester.projectSettings.TesterProjectSettings; 29 | import org.jetbrains.annotations.NotNull; 30 | import org.jetbrains.annotations.Nullable; 31 | 32 | import javax.swing.*; 33 | import java.nio.file.Path; 34 | import java.nio.file.Paths; 35 | import java.util.Collection; 36 | import java.util.Properties; 37 | 38 | public class TesterNewTestCaseDialog extends PhpBaseNewClassDialog { 39 | private JPanel contentPane; 40 | 41 | private EditorTextField testTargetTextField; 42 | private JBLabel testTargetCompletionHint; 43 | 44 | private EditorTextField nameTextField; 45 | private PhpNamespaceComboBox namespaceComboBox; 46 | private JBLabel namespaceCompletionHint; 47 | 48 | private EditorTextField fileNameTextField; 49 | private PhpPsrDirectoryComboBox directoryComboBox; 50 | private JBLabel directoryCompletionHint; 51 | 52 | private JBLabel classToTestLabel; 53 | private JBLabel testClassLabel; 54 | private JBLabel namespaceLabel; 55 | private JBLabel fileNameLabel; 56 | private JBLabel directoryLabel; 57 | 58 | TesterNewTestCaseDialog(@NotNull Project project, @Nullable PsiDirectory directory, @Nullable PsiFile file, boolean fixedDirectory) { 59 | super(project, directory); 60 | this.init(contentPane, nameTextField, namespaceComboBox, namespaceCompletionHint, fileNameTextField, directoryComboBox, directoryCompletionHint); 61 | 62 | testTargetTextField.addDocumentListener(new DocumentAdapter() { 63 | @Override 64 | public void documentChanged(DocumentEvent e) { 65 | TesterNewTestCaseDialog.this.addUpdateRequest(() -> { 66 | String text = TesterNewTestCaseDialog.this.testTargetTextField.getText(); 67 | if (!StringUtil.isEmpty(text) && !StringUtil.endsWith(text, "\\")) { 68 | PhpIndex instance = PhpIndex.getInstance(TesterNewTestCaseDialog.this.getProject()); 69 | Collection classes = instance.getClassesByFQN(text); 70 | if (!classes.isEmpty()) { 71 | TesterNewTestCaseDialog.this.nameTextField.setText(PhpLangUtil.toShortName(text) + "Test"); 72 | TesterNewTestCaseDialog.this.fileNameTextField.setText(PhpLangUtil.toShortName(text) + "Test"); 73 | 74 | if (!fixedDirectory) { 75 | PhpClass phpClass = classes.iterator().next(); 76 | String namespace = TesterNamespaceMapper.getInstance(project).mapSourceNamespaceToTestNamespace(phpClass); 77 | TesterNewTestCaseDialog.this.namespaceComboBox.getEditorTextField().setText(namespace); 78 | } 79 | } 80 | } 81 | }); 82 | } 83 | }); 84 | PhpCompletionUtil.installClassCompletion(testTargetTextField, null, this.getDisposable()); 85 | 86 | String codeCompletionShortcut = PhpUiUtil.getShortcutTextByActionName("CodeCompletion"); 87 | testTargetCompletionHint.setText(TesterBundle.message("dialog.newTestCase.completionShortcut", codeCompletionShortcut, "class reference")); 88 | namespaceCompletionHint.setText(TesterBundle.message("dialog.newTestCase.completionShortcut", codeCompletionShortcut, "namespace")); 89 | directoryCompletionHint.setText(TesterBundle.message("dialog.newTestCase.completionShortcut", codeCompletionShortcut, "path")); 90 | 91 | this.addUpdateRequest(() -> { 92 | PhpClass phpClass = TesterTestCreator.findClass(file); 93 | if (phpClass != null) { 94 | String fqn = phpClass.getFQN(); 95 | this.testTargetTextField.setText(PhpLangUtil.toName(fqn)); 96 | return; 97 | } 98 | 99 | this.nameTextField.setText("Test"); 100 | Editor editor = this.nameTextField.getEditor(); 101 | if (editor != null) { 102 | editor.getCaretModel().moveToOffset(0); 103 | } 104 | }); 105 | 106 | this.init(); 107 | } 108 | 109 | @Override 110 | protected void init() { 111 | super.init(); 112 | this.setTitle(TesterBundle.message("dialog.newTestCase.title")); 113 | } 114 | 115 | @Nullable 116 | @Override 117 | protected String getDimensionServiceKey() { 118 | return "cz.jiripudil.intellij.nette.tester.codeGeneration.TesterNewTestCaseDialog"; 119 | } 120 | 121 | @Nullable 122 | @Override 123 | public JComponent getPreferredFocusedComponent() { 124 | return testTargetTextField; 125 | } 126 | 127 | @NotNull 128 | @Override 129 | public String getTemplateName() { 130 | return "Tester TestCase"; 131 | } 132 | 133 | @NotNull 134 | @Override 135 | protected String getExtension() { 136 | TesterProjectSettings settings = TesterUtil.getTesterSettings(getProject()); 137 | return settings != null ? settings.getDefaultExtension() : "phpt"; 138 | } 139 | 140 | @NotNull 141 | @Override 142 | public Properties getProperties(@NotNull PsiDirectory directory) { 143 | Properties properties = super.getProperties(directory); 144 | String fqn = PhpLangUtil.toName(testTargetTextField.getText()); 145 | String testedName = PhpLangUtil.toShortName(fqn); 146 | 147 | if (StringUtil.isNotEmpty(testedName)) { 148 | properties.setProperty("TESTED_NAME", testedName); 149 | } 150 | 151 | String namespace = PhpLangUtil.getParentQualifiedName(fqn); 152 | if (!PhpLangUtil.isGlobalNamespaceName(namespace)) { 153 | properties.setProperty("TESTED_NAMESPACE", namespace); 154 | } 155 | 156 | // bootstrap 157 | TesterProjectSettings settings = TesterUtil.getTesterSettings(getProject()); 158 | if (settings != null && settings.getBootstrapFile() != null) { 159 | VirtualFile bootstrapFile = LocalFileSystem.getInstance().findFileByPath(settings.getBootstrapFile()); 160 | if (bootstrapFile != null && getDirectory() != null) { 161 | Path bootstrapFilePath = Paths.get(bootstrapFile.getPath()); 162 | Path testDirectoryPath = Paths.get(directoryComboBox.getSelectedPath()); 163 | 164 | Path bootstrapRelativePath = testDirectoryPath.relativize(bootstrapFilePath); 165 | properties.setProperty("BOOTSTRAP_RELATIVE_PATH", bootstrapRelativePath.toString()); 166 | } 167 | } 168 | 169 | return properties; 170 | } 171 | 172 | private void createUIComponents() { 173 | testTargetTextField = new EditorTextField("", getProject(), FileTypes.PLAIN_TEXT); 174 | namespaceComboBox = new PhpNamespaceComboBox(getProject(), "", getDisposable()); 175 | directoryComboBox = new PhpPsrDirectoryComboBox(getProject()) { 176 | @Override 177 | public void init(@NotNull VirtualFile baseDir, @NotNull String namespace) { 178 | super.init(baseDir, namespace); 179 | ProjectFileIndex index = ProjectRootManager.getInstance(TesterNewTestCaseDialog.this.getProject()).getFileIndex(); 180 | this.setDirectoriesFilter(index::isInTestSourceContent); 181 | 182 | this.updateDirectories(TesterNewTestCaseDialog.this.getNamespace()); 183 | } 184 | }; 185 | 186 | classToTestLabel = new JBLabel(TesterBundle.message("dialog.newTestCase.label.classToTest")); 187 | testClassLabel = new JBLabel(TesterBundle.message("dialog.newTestCase.label.testClass")); 188 | namespaceLabel = new JBLabel(TesterBundle.message("dialog.newTestCase.label.namespace")); 189 | fileNameLabel = new JBLabel(TesterBundle.message("dialog.newTestCase.label.fileName")); 190 | directoryLabel = new JBLabel(TesterBundle.message("dialog.newTestCase.label.directory")); 191 | 192 | testTargetCompletionHint = new JBLabel(UIUtil.ComponentStyle.MINI); 193 | namespaceCompletionHint = new JBLabel(UIUtil.ComponentStyle.MINI); 194 | directoryCompletionHint = new JBLabel(UIUtil.ComponentStyle.MINI); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/codeGeneration/TesterTestCreator.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.codeGeneration; 2 | 3 | import com.intellij.openapi.editor.Editor; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.util.Conditions; 6 | import com.intellij.platform.ProjectBaseDirectory; 7 | import com.intellij.psi.PsiFile; 8 | import com.intellij.testIntegration.TestCreator; 9 | import com.jetbrains.php.lang.psi.PhpFile; 10 | import com.jetbrains.php.lang.psi.PhpPsiUtil; 11 | import com.jetbrains.php.lang.psi.elements.PhpClass; 12 | import cz.jiripudil.intellij.nette.tester.TesterUtil; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | public class TesterTestCreator implements TestCreator { 16 | public static final TesterTestCreator INSTANCE = new TesterTestCreator(); 17 | 18 | private TesterTestCreator() { 19 | } 20 | 21 | @Override 22 | public boolean isAvailable(Project project, Editor editor, PsiFile psiFile) { 23 | PhpClass phpClass = findClass(psiFile); 24 | return phpClass != null; 25 | } 26 | 27 | @Nullable 28 | static PhpClass findClass(PsiFile psiFile) { 29 | if (psiFile instanceof PhpFile) { 30 | PhpClass phpClass = PhpPsiUtil.findClass((PhpFile) psiFile, Conditions.alwaysTrue()); 31 | if (phpClass != null && !TesterUtil.isTestClass(phpClass)) { 32 | return phpClass; 33 | } 34 | } 35 | 36 | return null; 37 | } 38 | 39 | @Override 40 | public void createTest(Project project, Editor editor, PsiFile psiFile) { 41 | (new TesterNewTestCaseAction(false)).invoke(project, psiFile.getContainingDirectory(), psiFile, null); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/configuration/TesterRunConfiguration.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.configuration; 2 | 3 | import com.intellij.execution.DefaultExecutionResult; 4 | import com.intellij.execution.ExecutionException; 5 | import com.intellij.execution.ExecutionResult; 6 | import com.intellij.execution.Executor; 7 | import com.intellij.execution.configurations.*; 8 | import com.intellij.execution.process.ProcessHandler; 9 | import com.intellij.execution.process.ProcessTerminatedListener; 10 | import com.intellij.execution.runners.ExecutionEnvironment; 11 | import com.intellij.execution.runners.ProgramRunner; 12 | import com.intellij.execution.ui.ConsoleView; 13 | import com.intellij.openapi.options.SettingsEditor; 14 | import com.intellij.openapi.project.Project; 15 | import com.intellij.openapi.util.InvalidDataException; 16 | import com.intellij.openapi.util.WriteExternalException; 17 | import com.intellij.openapi.util.text.StringUtil; 18 | import com.intellij.openapi.vfs.VirtualFile; 19 | import com.intellij.util.PathUtil; 20 | import com.intellij.util.xmlb.annotations.Attribute; 21 | import com.jetbrains.php.config.PhpProjectConfigurationFacade; 22 | import com.jetbrains.php.config.commandLine.PhpCommandLinePathProcessor; 23 | import com.jetbrains.php.config.commandLine.PhpCommandSettings; 24 | import com.jetbrains.php.config.commandLine.PhpCommandSettingsBuilder; 25 | import com.jetbrains.php.config.interpreters.PhpInterpreter; 26 | import com.jetbrains.php.run.PhpCommandLineSettings; 27 | import com.jetbrains.php.run.PhpExecutionUtil; 28 | import com.jetbrains.php.run.PhpRefactoringListenerRunConfiguration; 29 | import com.jetbrains.php.run.PhpRunUtil; 30 | import com.jetbrains.php.util.PhpConfigurationUtil; 31 | import com.jetbrains.php.util.pathmapper.PhpPathMapper; 32 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 33 | import cz.jiripudil.intellij.nette.tester.configuration.editor.TesterRunConfigurationEditor; 34 | import cz.jiripudil.intellij.nette.tester.execution.TesterExecutionUtil; 35 | import cz.jiripudil.intellij.nette.tester.execution.TesterTestLocator; 36 | import org.jdom.Element; 37 | import org.jetbrains.annotations.NotNull; 38 | import org.jetbrains.annotations.Nullable; 39 | 40 | import java.util.Collections; 41 | import java.util.List; 42 | import java.util.Map; 43 | 44 | public class TesterRunConfiguration extends PhpRefactoringListenerRunConfiguration implements LocatableConfiguration { 45 | private boolean isGenerated; 46 | 47 | TesterRunConfiguration(@NotNull Project project, @NotNull ConfigurationFactory factory, String name) { 48 | super(project, factory, name); 49 | } 50 | 51 | @NotNull 52 | @Override 53 | public SettingsEditor getConfigurationEditor() { 54 | return new TesterRunConfigurationEditor(getProject()); 55 | } 56 | 57 | @Override 58 | public void checkConfiguration() throws RuntimeConfigurationException { 59 | TesterSettings settings = getSettings(); 60 | PhpInterpreter interpreter = settings.getPhpInterpreter(getProject()); 61 | if (interpreter == null) { 62 | interpreter = PhpProjectConfigurationFacade.getInstance(getProject()).getInterpreter(); 63 | if (interpreter == null) { 64 | throw new RuntimeConfigurationError(TesterBundle.message("runConfiguration.errors.phpInterpreterNotSet")); 65 | 66 | } else if (interpreter.getPathToPhpExecutable() == null) { 67 | throw new RuntimeConfigurationError(TesterBundle.message("runConfiguration.errors.phpExecutableNotFound")); 68 | } 69 | } 70 | 71 | //TesterSettings settings = getSettings(); 72 | VirtualFile scopeDirectory = PhpRunUtil.findDirectory(settings.getTestScope()); 73 | VirtualFile scopeFile = PhpRunUtil.findFile(settings.getTestScope()); 74 | if (StringUtil.isEmpty(settings.getTestScope())) { 75 | throw new RuntimeConfigurationError(TesterBundle.message("runConfiguration.errors.noTestScope")); 76 | 77 | } else if (scopeDirectory == null && scopeFile == null) { 78 | throw new RuntimeConfigurationError(TesterBundle.message("runConfiguration.errors.testScopeNotFound")); 79 | } 80 | 81 | //PhpInterpreter phpInterpreter = settings.getPhpInterpreter(getProject()); 82 | if (interpreter.getPathToPhpExecutable() == null) { 83 | throw new RuntimeConfigurationError(TesterBundle.message("runConfiguration.errors.testEnvExecutableNotFound")); 84 | } 85 | 86 | if (StringUtil.isEmpty(settings.getTesterExecutable())) { 87 | throw new RuntimeConfigurationError(TesterBundle.message("runConfiguration.errors.noExecutable")); 88 | 89 | } else if (PhpRunUtil.findFile(settings.getTesterExecutable()) == null) { 90 | throw new RuntimeConfigurationError(TesterBundle.message("runConfiguration.errors.executableNotFound")); 91 | } 92 | 93 | if (!settings.getUseSystemPhpIni() && StringUtil.isNotEmpty(settings.getPhpIniPath()) && PhpRunUtil.findFile(settings.getPhpIniPath()) == null) { 94 | throw new RuntimeConfigurationError(TesterBundle.message("runConfiguration.errors.phpIniNotFound")); 95 | } 96 | 97 | if (StringUtil.isNotEmpty(settings.getSetupScriptPath()) && PhpRunUtil.findFile(settings.getSetupScriptPath()) == null) { 98 | throw new RuntimeConfigurationError(TesterBundle.message("runConfiguration.errors.setupScriptNotFound")); 99 | } 100 | 101 | PhpRunUtil.checkCommandLineSettings(getProject(), settings.getPhpCommandLineSettings()); 102 | } 103 | 104 | @Nullable 105 | @Override 106 | public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment executionEnvironment) throws ExecutionException { 107 | try { 108 | return this.getState(executionEnvironment, this.createCommand(Collections.emptyMap(), Collections.emptyList(), false)); 109 | } catch (CloneNotSupportedException e) { 110 | return null; 111 | } 112 | } 113 | 114 | @NotNull 115 | private RunProfileState getState(@NotNull final ExecutionEnvironment executionEnvironment, @NotNull final PhpCommandSettings command) throws ExecutionException { 116 | try { 117 | this.checkConfiguration(); 118 | } catch (RuntimeConfigurationException e) { 119 | throw new ExecutionException(e.getMessage() + " for " + this.getName() + " run-configuration"); 120 | } 121 | 122 | return new CommandLineState(executionEnvironment) { 123 | @NotNull 124 | @Override 125 | protected ProcessHandler startProcess() throws ExecutionException { 126 | ProcessHandler processHandler = createProcessHandler(getProject(), command); 127 | PhpRunUtil.attachProcessOutputDebugDumper(processHandler); 128 | ProcessTerminatedListener.attach(processHandler, getProject()); 129 | return processHandler; 130 | } 131 | 132 | @NotNull 133 | public ExecutionResult execute(@NotNull Executor executor, @NotNull ProgramRunner runner) throws ExecutionException { 134 | ProcessHandler processHandler = this.startProcess(); 135 | 136 | PhpPathMapper pathMapper = command.getPathProcessor().createPathMapper(getProject()); 137 | TesterTestLocator locationProvider = TesterTestLocator.create(pathMapper); 138 | ConsoleView consoleView = TesterExecutionUtil.createConsole(getProject(), processHandler, executionEnvironment, locationProvider); 139 | PhpExecutionUtil.addMessageFilters(getProject(), consoleView, locationProvider.getPathMapper()); 140 | return new DefaultExecutionResult(consoleView, processHandler); 141 | } 142 | }; 143 | } 144 | 145 | @NotNull 146 | private PhpCommandSettings createCommand(Map envParameters, List arguments, boolean withDebuggerOptions) throws ExecutionException, CloneNotSupportedException { 147 | PhpInterpreter interpreter = PhpProjectConfigurationFacade.getInstance(getProject()).getInterpreter(); 148 | if (interpreter == null) { 149 | throw new ExecutionException(TesterBundle.message("runConfiguration.errors.phpInterpreterNotSet")); 150 | 151 | } else { 152 | TesterSettings settings = getSettings().clone(); 153 | 154 | PhpCommandSettings command = PhpCommandSettingsBuilder.create(getProject(), interpreter, withDebuggerOptions); 155 | 156 | processSettings(command.getPathProcessor(), settings); 157 | 158 | command.setScript(settings.getTesterExecutable()); 159 | 160 | PhpCommandLineSettings commandLineSettings = settings.getPhpCommandLineSettings(); 161 | command.importCommandLineSettings(commandLineSettings, null); 162 | command.addEnvs(envParameters); 163 | 164 | // support for user setup 165 | if (settings.getSetupScriptPath() != null) { 166 | command.addEnv("INTELLIJ_NETTE_TESTER_USER_SETUP", command.getPathProcessor().process(settings.getSetupScriptPath())); 167 | } 168 | 169 | TesterExecutionUtil.addCommandArguments(getProject(), command, settings, arguments); 170 | 171 | return command; 172 | } 173 | } 174 | 175 | @NotNull 176 | @Override 177 | protected TesterSettings createSettings() { 178 | return new TesterSettings(); 179 | } 180 | 181 | protected void processSettings(@NotNull PhpCommandLinePathProcessor processor, @NotNull TesterSettings settings) { 182 | settings.setTestScope(processor.process(settings.getTestScope())); 183 | settings.setPhpIniPath(processor.process(StringUtil.notNullize(settings.getPhpIniPath()))); 184 | settings.setSetupScriptPath(processor.process(StringUtil.notNullize(settings.getSetupScriptPath()))); 185 | } 186 | 187 | @Override 188 | protected void fixSettingsAfterDeserialization(@NotNull TesterSettings settings) { 189 | settings.setTesterExecutable(PhpConfigurationUtil.deserializePath(settings.getTesterExecutable())); 190 | settings.setTestScope(PhpConfigurationUtil.deserializePath(settings.getTestScope())); 191 | settings.setPhpIniPath(PhpConfigurationUtil.deserializePath(settings.getPhpIniPath())); 192 | settings.setSetupScriptPath(PhpConfigurationUtil.deserializePath(settings.getSetupScriptPath())); 193 | } 194 | 195 | @Override 196 | protected void fixSettingsBeforeSerialization(@NotNull TesterSettings settings) { 197 | settings.setTesterExecutable(PhpConfigurationUtil.serializePath(settings.getTesterExecutable())); 198 | settings.setTestScope(PhpConfigurationUtil.serializePath(settings.getTestScope())); 199 | settings.setPhpIniPath(PhpConfigurationUtil.serializePath(settings.getPhpIniPath())); 200 | settings.setSetupScriptPath(PhpConfigurationUtil.serializePath(settings.getSetupScriptPath())); 201 | } 202 | 203 | @Override 204 | public void readExternal(Element element) throws InvalidDataException { 205 | super.readExternal(element); 206 | 207 | if (!isNewSerializationUsed()) { 208 | isGenerated = "true".equals(element.getAttributeValue("isGeneratedName")); 209 | } 210 | } 211 | 212 | @Override 213 | public void writeExternal(@NotNull Element element) throws WriteExternalException { 214 | if (!isNewSerializationUsed() && isGeneratedName()) { 215 | element.setAttribute("isGeneratedName", "true"); 216 | } 217 | 218 | super.writeExternal(element); 219 | } 220 | 221 | @Attribute("isGeneratedName") 222 | @Override 223 | public boolean isGeneratedName() { 224 | return isGenerated && PhpRunUtil.isGeneratedName(this); 225 | } 226 | 227 | void setGeneratedName(@Nullable String name) { 228 | setName(name); 229 | isGenerated = true; 230 | } 231 | 232 | @Nullable 233 | @Override 234 | public String suggestedName() { 235 | return PathUtil.getFileName(getSettings().getTestScope()); 236 | } 237 | 238 | @NotNull 239 | @Override 240 | protected List> getPathsToUpdate() { 241 | List> pathsToUpdate = super.getPathsToUpdate(); 242 | 243 | pathsToUpdate.add(new PhpRefValue() { 244 | @NotNull 245 | @Override 246 | public String getValue() { 247 | return TesterRunConfiguration.this.getSettings().getTestScope(); 248 | } 249 | 250 | @Override 251 | public void setValue(@Nullable String newName) { 252 | TesterRunConfiguration.this.getSettings().setTestScope(newName); 253 | } 254 | }); 255 | 256 | pathsToUpdate.add(new PhpRefValue() { 257 | @NotNull 258 | @Override 259 | public String getValue() { 260 | return TesterRunConfiguration.this.getSettings().getTesterExecutable(); 261 | } 262 | 263 | @Override 264 | public void setValue(@Nullable String newName) { 265 | TesterRunConfiguration.this.getSettings().setTesterExecutable(newName); 266 | } 267 | }); 268 | 269 | pathsToUpdate.add(new PhpRefValue() { 270 | @Nullable 271 | @Override 272 | public String getValue() { 273 | return TesterRunConfiguration.this.getSettings().getPhpIniPath(); 274 | } 275 | 276 | @Override 277 | public void setValue(@Nullable String newName) { 278 | TesterRunConfiguration.this.getSettings().setPhpIniPath(newName); 279 | } 280 | }); 281 | 282 | pathsToUpdate.add(new PhpRefValue() { 283 | @Nullable 284 | @Override 285 | public String getValue() { 286 | return TesterRunConfiguration.this.getSettings().getSetupScriptPath(); 287 | } 288 | 289 | @Override 290 | public void setValue(@Nullable String newName) { 291 | TesterRunConfiguration.this.getSettings().setSetupScriptPath(newName); 292 | } 293 | }); 294 | 295 | return pathsToUpdate; 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/configuration/TesterRunConfigurationProducer.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.configuration; 2 | 3 | import com.intellij.execution.actions.ConfigurationContext; 4 | import com.intellij.execution.actions.RunConfigurationProducer; 5 | import com.intellij.openapi.util.Ref; 6 | import com.intellij.psi.PsiDirectory; 7 | import com.intellij.psi.PsiElement; 8 | import com.jetbrains.php.lang.psi.PhpFile; 9 | import com.jetbrains.php.lang.psi.PhpPsiUtil; 10 | import com.jetbrains.php.lang.psi.elements.PhpClass; 11 | import cz.jiripudil.intellij.nette.tester.TesterUtil; 12 | 13 | public class TesterRunConfigurationProducer extends RunConfigurationProducer { 14 | protected TesterRunConfigurationProducer() { 15 | super(TesterRunConfigurationType.getInstance()); 16 | } 17 | 18 | @Override 19 | protected boolean setupConfigurationFromContext(TesterRunConfiguration runConfiguration, ConfigurationContext configurationContext, Ref ref) { 20 | PsiElement location = configurationContext.getPsiLocation(); 21 | if (location instanceof PsiDirectory) { 22 | PsiDirectory directory = (PsiDirectory) location; 23 | runConfiguration.getSettings().setTestScope(directory.getVirtualFile().getPresentableUrl()); 24 | runConfiguration.setGeneratedName(runConfiguration.suggestedName()); 25 | ref.set(directory); 26 | return true; 27 | } 28 | 29 | if (location instanceof PhpFile) { 30 | PhpFile phpFile = (PhpFile) location; 31 | PhpClass testClass = PhpPsiUtil.findClass(phpFile, TesterUtil::isTestClass); 32 | if (testClass == null) { 33 | return false; 34 | } 35 | 36 | runConfiguration.getSettings().setTestScope(phpFile.getVirtualFile().getPresentableUrl()); 37 | runConfiguration.setGeneratedName(runConfiguration.suggestedName()); 38 | ref.set(phpFile); 39 | return true; 40 | } 41 | 42 | return false; 43 | } 44 | 45 | @Override 46 | public boolean isConfigurationFromContext(TesterRunConfiguration runConfiguration, ConfigurationContext configurationContext) { 47 | PsiElement location = configurationContext.getPsiLocation(); 48 | if (location instanceof PsiDirectory) { 49 | PsiDirectory directory = (PsiDirectory) location; 50 | return runConfiguration.getSettings().getTestScope().equals(directory.getVirtualFile().getPresentableUrl()); 51 | } 52 | 53 | if (location instanceof PhpFile) { 54 | PhpFile file = (PhpFile) location; 55 | return runConfiguration.getSettings().getTestScope().equals(file.getVirtualFile().getPresentableUrl()); 56 | } 57 | 58 | return false; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/configuration/TesterRunConfigurationType.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.configuration; 2 | 3 | import com.intellij.execution.configurations.ConfigurationFactory; 4 | import com.intellij.execution.configurations.ConfigurationTypeBase; 5 | import com.intellij.execution.configurations.ConfigurationTypeUtil; 6 | import com.intellij.execution.configurations.RunConfiguration; 7 | import com.intellij.openapi.project.Project; 8 | import com.jetbrains.php.PhpIcons; 9 | import com.jetbrains.php.run.PhpRunConfigurationFactoryBase; 10 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | public class TesterRunConfigurationType extends ConfigurationTypeBase { 14 | protected TesterRunConfigurationType() { 15 | super("nette-tester", TesterBundle.message("configurationType.displayName"), TesterBundle.message("configurationType.description"), PhpIcons.PHP_TEST_FILE); 16 | this.addFactory(createFactory(this)); 17 | } 18 | 19 | static TesterRunConfigurationType getInstance() { 20 | return ConfigurationTypeUtil.findConfigurationType(TesterRunConfigurationType.class); 21 | } 22 | 23 | public static ConfigurationFactory createFactory(TesterRunConfigurationType type) { 24 | return new PhpRunConfigurationFactoryBase(type) { 25 | @NotNull 26 | @Override 27 | public RunConfiguration createTemplateConfiguration(@NotNull Project project) { 28 | return new TesterRunConfiguration(project, this, this.getName()); 29 | } 30 | 31 | @Override 32 | public String getName() { 33 | return TesterBundle.message("configurationType.displayName"); 34 | } 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/configuration/TesterSettings.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.configuration; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.util.text.StringUtil; 5 | import com.intellij.util.xmlb.annotations.Property; 6 | import com.intellij.util.xmlb.annotations.Transient; 7 | import com.jetbrains.php.config.PhpProjectConfigurationFacade; 8 | import com.jetbrains.php.config.interpreters.PhpConfigurationOptionData; 9 | import com.jetbrains.php.config.interpreters.PhpInterpreter; 10 | import com.jetbrains.php.config.interpreters.PhpInterpretersManagerImpl; 11 | import com.jetbrains.php.run.PhpCommandLineSettings; 12 | import com.jetbrains.php.run.PhpRunConfigurationSettings; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | public class TesterSettings implements PhpRunConfigurationSettings, Cloneable { 20 | @Nullable private String testScope; 21 | @Nullable private String testerExecutable; 22 | @Nullable private String testerOptions; 23 | @Nullable private String setupScriptPath; 24 | 25 | @Nullable private String phpInterpreterId; 26 | @NotNull private List phpInterpreterOptions = new ArrayList<>(); 27 | @Nullable private String phpIniPath; 28 | @NotNull private Boolean useSystemPhpIni = Boolean.FALSE; 29 | 30 | @NotNull private PhpCommandLineSettings phpCommandLineSettings = new PhpCommandLineSettings(); 31 | 32 | @Property 33 | @NotNull 34 | public String getTestScope() { 35 | return StringUtil.notNullize(testScope); 36 | } 37 | 38 | public void setTestScope(@Nullable String testScope) { 39 | this.testScope = testScope; 40 | } 41 | 42 | @Property 43 | @NotNull 44 | public String getTesterExecutable() { 45 | return StringUtil.notNullize(testerExecutable); 46 | } 47 | 48 | public void setTesterExecutable(@Nullable String testerExecutable) { 49 | this.testerExecutable = testerExecutable; 50 | } 51 | 52 | @Property 53 | @Nullable 54 | public String getTesterOptions() { 55 | return testerOptions; 56 | } 57 | 58 | public void setTesterOptions(@Nullable String testerOptions) { 59 | this.testerOptions = testerOptions; 60 | } 61 | 62 | @Property 63 | @Nullable 64 | public String getSetupScriptPath() { 65 | return setupScriptPath; 66 | } 67 | 68 | public void setSetupScriptPath(@Nullable String setupScriptPath) { 69 | this.setupScriptPath = setupScriptPath; 70 | } 71 | 72 | @Property 73 | @Nullable 74 | public String getPhpInterpreterId() { 75 | return phpInterpreterId; 76 | } 77 | 78 | @Transient 79 | @Nullable 80 | public PhpInterpreter getPhpInterpreter(@NotNull Project project) { 81 | return phpInterpreterId != null 82 | ? PhpInterpretersManagerImpl.getInstance(project).findInterpreterById(phpInterpreterId) 83 | : PhpProjectConfigurationFacade.getInstance(project).getInterpreter(); 84 | } 85 | 86 | public void setPhpInterpreterId(@Nullable String phpInterpreterId) { 87 | this.phpInterpreterId = phpInterpreterId; 88 | } 89 | 90 | @Property 91 | @NotNull 92 | public List getPhpInterpreterOptions() { 93 | return phpInterpreterOptions; 94 | } 95 | 96 | public void setPhpInterpreterOptions(@NotNull List phpInterpreterOptions) { 97 | this.phpInterpreterOptions = phpInterpreterOptions; 98 | } 99 | 100 | @Property 101 | @Nullable 102 | public String getPhpIniPath() { 103 | return phpIniPath; 104 | } 105 | 106 | public void setPhpIniPath(@Nullable String phpIniPath) { 107 | this.phpIniPath = phpIniPath; 108 | } 109 | 110 | @Property 111 | @NotNull 112 | public Boolean getUseSystemPhpIni() { 113 | return useSystemPhpIni; 114 | } 115 | 116 | public void setUseSystemPhpIni(@NotNull Boolean useSystemPhpIni) { 117 | this.useSystemPhpIni = useSystemPhpIni; 118 | } 119 | 120 | @Property 121 | @NotNull 122 | public PhpCommandLineSettings getPhpCommandLineSettings() { 123 | return phpCommandLineSettings; 124 | } 125 | 126 | @Nullable 127 | @Override 128 | public String getWorkingDirectory() { 129 | return phpCommandLineSettings.getWorkingDirectory(); 130 | } 131 | 132 | @Override 133 | public void setWorkingDirectory(@Nullable String workingDirectory) { 134 | phpCommandLineSettings.setWorkingDirectory(workingDirectory); 135 | } 136 | 137 | @Override 138 | public boolean equals(Object o) { 139 | if (this == o) return true; 140 | if (!(o instanceof TesterSettings)) return false; 141 | 142 | TesterSettings that = (TesterSettings) o; 143 | 144 | if (testScope != null ? !testScope.equals(that.testScope) : that.testScope != null) return false; 145 | if (testerExecutable != null ? !testerExecutable.equals(that.testerExecutable) : that.testerExecutable != null) 146 | return false; 147 | if (testerOptions != null ? !testerOptions.equals(that.testerOptions) : that.testerOptions != null) 148 | return false; 149 | if (phpIniPath != null ? !phpIniPath.equals(that.phpIniPath) : that.phpIniPath != null) return false; 150 | if (setupScriptPath != null ? !setupScriptPath.equals(that.setupScriptPath) : that.setupScriptPath != null) return false; 151 | 152 | return phpCommandLineSettings.equals(that.getPhpCommandLineSettings()); 153 | } 154 | 155 | @Override 156 | public int hashCode() { 157 | int result = testScope != null ? testScope.hashCode() : 0; 158 | result = 31 * result + (testerExecutable != null ? testerExecutable.hashCode() : 0); 159 | result = 31 * result + (testerOptions != null ? testerOptions.hashCode() : 0); 160 | result = 31 * result + (phpIniPath != null ? phpIniPath.hashCode() : 0); 161 | result = 31 * result + (setupScriptPath != null ? setupScriptPath.hashCode() : 0); 162 | result = 31 * result + phpCommandLineSettings.hashCode(); 163 | return result; 164 | } 165 | 166 | @Override 167 | public TesterSettings clone() throws CloneNotSupportedException { 168 | return (TesterSettings) super.clone(); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/configuration/TesterTestMethodRunConfiguration.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.configuration; 2 | 3 | import com.intellij.execution.configurations.ConfigurationFactory; 4 | import com.intellij.openapi.project.Project; 5 | import com.jetbrains.php.PhpIcons; 6 | import com.jetbrains.php.lang.psi.elements.Method; 7 | import com.jetbrains.php.run.script.PhpScriptRunConfiguration; 8 | import org.jetbrains.annotations.NotNull; 9 | import org.jetbrains.annotations.Nullable; 10 | 11 | import javax.swing.*; 12 | 13 | public class TesterTestMethodRunConfiguration extends PhpScriptRunConfiguration { 14 | public TesterTestMethodRunConfiguration(Project project, ConfigurationFactory factory, String name) { 15 | super(project, factory, name); 16 | } 17 | 18 | @Override 19 | public Icon getIcon() { 20 | return PhpIcons.PHP_TEST_METHOD; 21 | } 22 | 23 | @Override 24 | public String suggestedName() { 25 | return super.suggestedName() + createSuggestedName(parseMethod()); 26 | } 27 | 28 | public static String createSuggestedName(@Nullable String methodName) { 29 | return methodName != null ? " " + methodName + "()" : ""; 30 | } 31 | 32 | void setMethod(@NotNull Method method) { 33 | setMethodName(method.getName()); 34 | } 35 | 36 | public void setMethodName(@NotNull String methodName) { 37 | getSettings().setScriptParameters(methodName); 38 | } 39 | 40 | boolean isMethod(@NotNull Method method) { 41 | return method.getName().equals(parseMethod()); 42 | } 43 | 44 | @Nullable 45 | public String getMethod() { 46 | return parseMethod(); 47 | } 48 | 49 | @Nullable 50 | public String parseMethod() { 51 | return getSettings().getScriptParameters(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/configuration/TesterTestMethodRunConfigurationProducer.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.configuration; 2 | 3 | import com.intellij.execution.actions.ConfigurationContext; 4 | import com.intellij.execution.actions.RunConfigurationProducer; 5 | import com.intellij.ide.scratch.ScratchFileType; 6 | import com.intellij.openapi.fileTypes.FileTypeManager; 7 | import com.intellij.openapi.roots.ProjectRootManager; 8 | import com.intellij.openapi.util.Ref; 9 | import com.intellij.openapi.util.text.StringUtil; 10 | import com.intellij.openapi.vfs.LocalFileSystem; 11 | import com.intellij.openapi.vfs.VirtualFile; 12 | import com.intellij.psi.PsiElement; 13 | import com.intellij.psi.PsiFile; 14 | import com.jetbrains.php.lang.PhpFileType; 15 | import com.jetbrains.php.lang.psi.PhpPsiUtil; 16 | import com.jetbrains.php.lang.psi.elements.Method; 17 | import com.jetbrains.php.run.script.PhpScriptRunConfiguration; 18 | import cz.jiripudil.intellij.nette.tester.TesterUtil; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | public class TesterTestMethodRunConfigurationProducer extends RunConfigurationProducer { 22 | protected TesterTestMethodRunConfigurationProducer() { 23 | super(TesterTestMethodRunConfigurationType.getInstance()); 24 | } 25 | 26 | @Override 27 | protected boolean setupConfigurationFromContext(TesterTestMethodRunConfiguration runConfiguration, ConfigurationContext context, Ref ref) { 28 | PsiElement element = context.getPsiLocation(); 29 | Method method = PhpPsiUtil.getParentByCondition(element, parent -> parent instanceof Method); 30 | 31 | if (method != null && isValid(method)) { 32 | VirtualFile file = method.getContainingFile().getVirtualFile(); 33 | ref.set(method); 34 | 35 | if (!FileTypeManager.getInstance().isFileOfType(file, ScratchFileType.INSTANCE)) { 36 | VirtualFile root = ProjectRootManager.getInstance(element.getProject()).getFileIndex().getContentRootForFile(file); 37 | if (root == null) { 38 | return false; 39 | } 40 | } 41 | 42 | PhpScriptRunConfiguration.Settings settings = runConfiguration.getSettings(); 43 | settings.setPath(file.getPresentableUrl()); 44 | runConfiguration.setMethod(method); 45 | runConfiguration.setName(runConfiguration.suggestedName()); 46 | return true; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | @Override 53 | public boolean isConfigurationFromContext(TesterTestMethodRunConfiguration runConfiguration, ConfigurationContext context) { 54 | PsiElement element = context.getPsiLocation(); 55 | Method method = PhpPsiUtil.getParentByCondition(element, parent -> parent instanceof Method); 56 | 57 | if (method != null && isValid(method)) { 58 | VirtualFile containingVirtualFile = method.getContainingFile().getVirtualFile(); 59 | PhpScriptRunConfiguration.Settings settings = runConfiguration.getSettings(); 60 | String path = settings.getPath(); 61 | 62 | if (path != null) { 63 | VirtualFile configurationFile = LocalFileSystem.getInstance().findFileByPath(path); 64 | if (configurationFile != null) { 65 | return StringUtil.equals(containingVirtualFile.getPath(), configurationFile.getPath()) 66 | && runConfiguration.isMethod(method); 67 | } 68 | } 69 | } 70 | 71 | return false; 72 | } 73 | 74 | private boolean isValid(@Nullable Method method) { 75 | return method != null 76 | && isValid(method.getContainingFile()) 77 | && TesterUtil.isTestMethod(method); 78 | } 79 | 80 | private boolean isValid(@Nullable PsiFile containingFile) { 81 | return containingFile != null 82 | && containingFile.getFileType() == PhpFileType.INSTANCE 83 | && containingFile.getVirtualFile() != null; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/configuration/TesterTestMethodRunConfigurationType.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.configuration; 2 | 3 | import com.intellij.execution.configurations.ConfigurationFactory; 4 | import com.intellij.execution.configurations.ConfigurationTypeBase; 5 | import com.intellij.execution.configurations.ConfigurationTypeUtil; 6 | import com.intellij.execution.configurations.RunConfiguration; 7 | import com.intellij.openapi.project.Project; 8 | import com.jetbrains.php.PhpIcons; 9 | import com.jetbrains.php.run.PhpRunConfigurationFactoryBase; 10 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 11 | import org.jetbrains.annotations.NotNull; 12 | 13 | public class TesterTestMethodRunConfigurationType extends ConfigurationTypeBase { 14 | protected TesterTestMethodRunConfigurationType() { 15 | super("nette-tester-method", TesterBundle.message("configurationType.method.displayName"), TesterBundle.message("configurationType.method.description"), PhpIcons.PHP_TEST_METHOD); 16 | this.addFactory(createFactory(this)); 17 | } 18 | 19 | static TesterTestMethodRunConfigurationType getInstance() { 20 | return ConfigurationTypeUtil.findConfigurationType(TesterTestMethodRunConfigurationType.class); 21 | } 22 | 23 | public static ConfigurationFactory createFactory() { 24 | return createFactory(getInstance()); 25 | } 26 | 27 | public static ConfigurationFactory createFactory(TesterTestMethodRunConfigurationType type) { 28 | return new PhpRunConfigurationFactoryBase(type) { 29 | @NotNull 30 | @Override 31 | public RunConfiguration createTemplateConfiguration(@NotNull Project project) { 32 | return new TesterTestMethodRunConfiguration(project, this, ""); 33 | } 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/configuration/editor/PhpCommandLineSettingsEditor.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
56 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/configuration/editor/PhpCommandLineSettingsEditor.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.configuration.editor; 2 | 3 | import com.intellij.execution.configuration.EnvironmentVariablesComponent; 4 | import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; 5 | import com.intellij.openapi.options.ConfigurationException; 6 | import com.intellij.openapi.options.SettingsEditor; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.openapi.ui.TextFieldWithBrowseButton; 9 | import com.intellij.ui.RawCommandLineEditor; 10 | import com.intellij.ui.components.JBLabel; 11 | import com.jetbrains.php.run.PhpCommandLineSettings; 12 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 13 | import cz.jiripudil.intellij.nette.tester.configuration.TesterRunConfiguration; 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import javax.swing.*; 17 | 18 | public class PhpCommandLineSettingsEditor extends SettingsEditor { 19 | @NotNull final private Project project; 20 | 21 | private JPanel panel; 22 | private RawCommandLineEditor interpreterOptions; 23 | private TextFieldWithBrowseButton customWorkingDirectory; 24 | private EnvironmentVariablesComponent environmentVariables; 25 | 26 | private JBLabel interpreterOptionsLabel; 27 | private JBLabel workingDirectoryLabel; 28 | 29 | PhpCommandLineSettingsEditor(@NotNull final Project project) { 30 | this.project = project; 31 | } 32 | 33 | private void createUIComponents() { 34 | interpreterOptions = new RawCommandLineEditor(); 35 | interpreterOptions.setDialogCaption("Options"); 36 | 37 | customWorkingDirectory = new TextFieldWithBrowseButton(); 38 | customWorkingDirectory.addBrowseFolderListener(null, null, project, FileChooserDescriptorFactory.createSingleFolderDescriptor()); 39 | 40 | environmentVariables = new EnvironmentVariablesComponent(); 41 | 42 | interpreterOptionsLabel = new JBLabel(TesterBundle.message("runConfiguration.editor.cli.interpreterOptions")); 43 | workingDirectoryLabel = new JBLabel(TesterBundle.message("runConfiguration.editor.cli.workingDirectory")); 44 | } 45 | 46 | @Override 47 | protected void resetEditorFrom(@NotNull TesterRunConfiguration runConfiguration) { 48 | PhpCommandLineSettings commandLineSettings = runConfiguration.getSettings().getPhpCommandLineSettings(); 49 | interpreterOptions.setText(commandLineSettings.getParameters()); 50 | customWorkingDirectory.setText(commandLineSettings.getWorkingDirectory()); 51 | environmentVariables.setEnvs(commandLineSettings.getEnvs()); 52 | environmentVariables.setPassParentEnvs(commandLineSettings.isPassParentEnvs()); 53 | } 54 | 55 | @Override 56 | protected void applyEditorTo(@NotNull TesterRunConfiguration runConfiguration) throws ConfigurationException { 57 | PhpCommandLineSettings commandLineSettings = runConfiguration.getSettings().getPhpCommandLineSettings(); 58 | commandLineSettings.setParameters(interpreterOptions.getText()); 59 | commandLineSettings.setWorkingDirectory(customWorkingDirectory.getText()); 60 | commandLineSettings.setEnvs(environmentVariables.getEnvs()); 61 | commandLineSettings.setPassParentEnvs(environmentVariables.isPassParentEnvs()); 62 | } 63 | 64 | @NotNull 65 | @Override 66 | protected JComponent createEditor() { 67 | return panel; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/configuration/editor/TesterRunConfigurationEditor.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
74 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/configuration/editor/TesterRunConfigurationEditor.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.configuration.editor; 2 | 3 | import com.intellij.openapi.options.ConfigurationException; 4 | import com.intellij.openapi.options.SettingsEditor; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.ui.JBColor; 7 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 8 | import cz.jiripudil.intellij.nette.tester.configuration.TesterRunConfiguration; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import javax.swing.*; 12 | 13 | public class TesterRunConfigurationEditor extends SettingsEditor { 14 | @NotNull private final Project project; 15 | 16 | private JPanel panel; 17 | 18 | private JPanel testerSettingsPanel; 19 | private TesterSettingsEditor testerSettingsEditor; 20 | 21 | private JPanel testEnvironmentSettingsPanel; 22 | private TesterTestEnvironmentSettingsEditor testEnvironmentSettingsEditor; 23 | 24 | private JPanel cliSettingsPanel; 25 | private PhpCommandLineSettingsEditor phpCommandLineSettingsEditor; 26 | 27 | public TesterRunConfigurationEditor(@NotNull final Project project) { 28 | super(); 29 | this.project = project; 30 | } 31 | 32 | @Override 33 | protected void resetEditorFrom(@NotNull TesterRunConfiguration runConfiguration) { 34 | testerSettingsEditor.resetEditorFrom(runConfiguration); 35 | testEnvironmentSettingsEditor.resetEditorFrom(runConfiguration); 36 | phpCommandLineSettingsEditor.resetEditorFrom(runConfiguration); 37 | } 38 | 39 | @Override 40 | protected void applyEditorTo(@NotNull TesterRunConfiguration runConfiguration) throws ConfigurationException { 41 | testerSettingsEditor.applyEditorTo(runConfiguration); 42 | testEnvironmentSettingsEditor.applyEditorTo(runConfiguration); 43 | phpCommandLineSettingsEditor.applyEditorTo(runConfiguration); 44 | } 45 | 46 | @NotNull 47 | @Override 48 | protected JComponent createEditor() { 49 | return panel; 50 | } 51 | 52 | private void createUIComponents() { 53 | testerSettingsPanel = new JPanel(); 54 | testerSettingsPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, JBColor.LIGHT_GRAY), TesterBundle.message("runConfiguration.editor.tester.title"))); 55 | testerSettingsEditor = new TesterSettingsEditor(project); 56 | 57 | testEnvironmentSettingsPanel = new JPanel(); 58 | testEnvironmentSettingsPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, JBColor.LIGHT_GRAY), TesterBundle.message("runConfiguration.editor.testEnv.title"))); 59 | testEnvironmentSettingsEditor = new TesterTestEnvironmentSettingsEditor(project); 60 | 61 | cliSettingsPanel = new JPanel(); 62 | cliSettingsPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, JBColor.LIGHT_GRAY), TesterBundle.message("runConfiguration.editor.cli.title"))); 63 | phpCommandLineSettingsEditor = new PhpCommandLineSettingsEditor(project); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/configuration/editor/TesterSettingsEditor.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
77 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/configuration/editor/TesterSettingsEditor.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.configuration.editor; 2 | 3 | import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; 4 | import com.intellij.openapi.options.ConfigurationException; 5 | import com.intellij.openapi.options.SettingsEditor; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.ui.TextFieldWithBrowseButton; 8 | import com.intellij.ui.RawCommandLineEditor; 9 | import com.intellij.ui.components.JBLabel; 10 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 11 | import cz.jiripudil.intellij.nette.tester.configuration.TesterRunConfiguration; 12 | import cz.jiripudil.intellij.nette.tester.configuration.TesterSettings; 13 | import org.jetbrains.annotations.NotNull; 14 | 15 | import javax.swing.*; 16 | 17 | public class TesterSettingsEditor extends SettingsEditor { 18 | @NotNull private final Project project; 19 | 20 | private JPanel panel; 21 | private TextFieldWithBrowseButton testScope; 22 | private TextFieldWithBrowseButton testerExecutable; 23 | private RawCommandLineEditor testerOptions; 24 | private TextFieldWithBrowseButton userSetupScript; 25 | 26 | private JBLabel testScopeLabel; 27 | private JBLabel testerExecutableLabel; 28 | private JBLabel testerOptionsLabel; 29 | private JBLabel setupScriptLabel; 30 | 31 | TesterSettingsEditor(@NotNull final Project project) { 32 | this.project = project; 33 | } 34 | 35 | @Override 36 | protected void resetEditorFrom(@NotNull TesterRunConfiguration runConfiguration) { 37 | TesterSettings settings = runConfiguration.getSettings(); 38 | testScope.setText(settings.getTestScope()); 39 | testerExecutable.setText(settings.getTesterExecutable()); 40 | testerOptions.setText(settings.getTesterOptions()); 41 | userSetupScript.setText(settings.getSetupScriptPath()); 42 | } 43 | 44 | @Override 45 | protected void applyEditorTo(@NotNull TesterRunConfiguration runConfiguration) throws ConfigurationException { 46 | TesterSettings settings = runConfiguration.getSettings(); 47 | settings.setTestScope(testScope.getText()); 48 | settings.setTesterExecutable(testerExecutable.getText()); 49 | settings.setTesterOptions(testerOptions.getText()); 50 | settings.setSetupScriptPath(userSetupScript.getText()); 51 | } 52 | 53 | @NotNull 54 | @Override 55 | protected JComponent createEditor() { 56 | return panel; 57 | } 58 | 59 | private void createUIComponents() { 60 | testScope = new TextFieldWithBrowseButton(); 61 | testScope.addBrowseFolderListener(null, null, project, FileChooserDescriptorFactory.createSingleFileOrFolderDescriptor()); 62 | 63 | testerExecutable = new TextFieldWithBrowseButton(); 64 | testerExecutable.addBrowseFolderListener(null, null, project, FileChooserDescriptorFactory.createSingleFileOrFolderDescriptor()); 65 | 66 | testerOptions = new RawCommandLineEditor(); 67 | testerOptions.setDialogCaption("Options"); 68 | 69 | userSetupScript = new TextFieldWithBrowseButton(); 70 | userSetupScript.addBrowseFolderListener(null, null, project, FileChooserDescriptorFactory.createSingleFileDescriptor("php")); 71 | 72 | testScopeLabel = new JBLabel(TesterBundle.message("runConfiguration.editor.tester.testScope")); 73 | testerExecutableLabel = new JBLabel(TesterBundle.message("runConfiguration.editor.tester.testerExecutable")); 74 | testerOptionsLabel = new JBLabel(TesterBundle.message("runConfiguration.editor.tester.testerOptions")); 75 | setupScriptLabel = new JBLabel(TesterBundle.message("runConfiguration.editor.tester.setupScript")); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/configuration/editor/TesterTestEnvironmentSettingsEditor.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
75 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/configuration/editor/TesterTestEnvironmentSettingsEditor.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.configuration.editor; 2 | 3 | import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; 4 | import com.intellij.openapi.options.ConfigurationException; 5 | import com.intellij.openapi.options.SettingsEditor; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.ui.TextFieldWithBrowseButton; 8 | import com.intellij.ui.components.JBCheckBox; 9 | import com.intellij.ui.components.JBLabel; 10 | import com.jetbrains.php.config.PhpProjectConfigurationFacade; 11 | import com.jetbrains.php.config.interpreters.PhpConfigurationOptionsComponent; 12 | import com.jetbrains.php.config.interpreters.PhpInterpreter; 13 | import com.jetbrains.php.config.interpreters.PhpInterpreterComboBox; 14 | import com.jetbrains.php.config.interpreters.PhpInterpretersManagerImpl; 15 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 16 | import cz.jiripudil.intellij.nette.tester.configuration.TesterRunConfiguration; 17 | import cz.jiripudil.intellij.nette.tester.configuration.TesterSettings; 18 | import org.jetbrains.annotations.NotNull; 19 | import org.jetbrains.annotations.Nullable; 20 | 21 | import javax.swing.*; 22 | 23 | public class TesterTestEnvironmentSettingsEditor extends SettingsEditor { 24 | @NotNull private final Project project; 25 | 26 | private JPanel panel; 27 | private PhpInterpreterComboBox phpInterpreter; 28 | private PhpConfigurationOptionsComponent interpreterOptions; 29 | private TextFieldWithBrowseButton phpIniPath; 30 | private JBCheckBox useSystemPhpIniCheckbox; 31 | 32 | private JBLabel interpreterLabel; 33 | private JBLabel interpreterOptionsLabel; 34 | private JBLabel pathToPhpIniLabel; 35 | 36 | TesterTestEnvironmentSettingsEditor(@NotNull final Project project) { 37 | super(); 38 | this.project = project; 39 | useSystemPhpIniCheckbox.addItemListener(e -> phpIniPath.setEnabled(!useSystemPhpIniCheckbox.isSelected())); 40 | } 41 | 42 | @Override 43 | protected void resetEditorFrom(@NotNull TesterRunConfiguration runConfiguration) { 44 | TesterSettings settings = runConfiguration.getSettings(); 45 | 46 | PhpInterpreter interpreter = settings.getPhpInterpreter(project); 47 | phpInterpreter.reset(PhpInterpretersManagerImpl.getInstance(project).findInterpreterName(interpreter != null ? interpreter.getId() : null)); 48 | interpreterOptions.setConfigurationOptions(settings.getPhpInterpreterOptions()); 49 | phpIniPath.setText(settings.getPhpIniPath()); 50 | useSystemPhpIniCheckbox.setSelected(settings.getUseSystemPhpIni()); 51 | } 52 | 53 | @Override 54 | protected void applyEditorTo(@NotNull TesterRunConfiguration runConfiguration) throws ConfigurationException { 55 | TesterSettings settings = runConfiguration.getSettings(); 56 | 57 | PhpInterpreter interpreter = getSelectedInterpreter(); 58 | settings.setPhpInterpreterId(interpreter != null ? interpreter.getId() : null); 59 | settings.setPhpInterpreterOptions(interpreterOptions.getConfigurationOptionsData()); 60 | settings.setPhpIniPath(phpIniPath.getText()); 61 | settings.setUseSystemPhpIni(useSystemPhpIniCheckbox.isSelected()); 62 | } 63 | 64 | @Nullable 65 | private PhpInterpreter getSelectedInterpreter() { 66 | String interpreterName = phpInterpreter.getSelectedItemName(); 67 | if (interpreterName == null) { 68 | return PhpProjectConfigurationFacade.getInstance(project).getInterpreter(); 69 | } 70 | 71 | return PhpInterpretersManagerImpl.getInstance(project).findInterpreter(interpreterName); 72 | } 73 | 74 | @NotNull 75 | @Override 76 | protected JComponent createEditor() { 77 | return panel; 78 | } 79 | 80 | private void createUIComponents() { 81 | phpInterpreter = new PhpInterpreterComboBox(project, null); 82 | phpInterpreter.setModel(PhpInterpretersManagerImpl.getInstance(project).getInterpreters(), null); 83 | phpInterpreter.setNoItemText(""); 84 | 85 | interpreterOptions = new PhpConfigurationOptionsComponent(); 86 | 87 | phpIniPath = new TextFieldWithBrowseButton(); 88 | phpIniPath.addBrowseFolderListener(null, null, project, FileChooserDescriptorFactory.createSingleFileDescriptor("ini")); 89 | 90 | useSystemPhpIniCheckbox = new JBCheckBox(TesterBundle.message("runConfiguration.editor.testEnv.useSystemPhpIni")); 91 | 92 | interpreterLabel = new JBLabel(TesterBundle.message("runConfiguration.editor.testEnv.interpreter")); 93 | interpreterOptionsLabel = new JBLabel(TesterBundle.message("runConfiguration.editor.testEnv.interpreterOptions")); 94 | pathToPhpIniLabel = new JBLabel(TesterBundle.message("runConfiguration.editor.testEnv.phpIni")); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/execution/TesterConsoleProperties.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.execution; 2 | 3 | import com.intellij.execution.Executor; 4 | import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties; 5 | import com.intellij.execution.testframework.sm.runner.SMTestLocator; 6 | import cz.jiripudil.intellij.nette.tester.configuration.TesterRunConfiguration; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | public class TesterConsoleProperties extends SMTRunnerConsoleProperties { 11 | private final SMTestLocator locator; 12 | 13 | TesterConsoleProperties(@NotNull TesterRunConfiguration config, Executor executor, SMTestLocator locator) { 14 | super(config, "Nette Tester", executor); 15 | this.locator = locator; 16 | } 17 | 18 | @Nullable 19 | @Override 20 | public SMTestLocator getTestLocator() { 21 | return locator; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/execution/TesterExecutionUtil.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.execution; 2 | 3 | import com.intellij.execution.ExecutionException; 4 | import com.intellij.execution.process.ProcessHandler; 5 | import com.intellij.execution.runners.ExecutionEnvironment; 6 | import com.intellij.execution.testframework.sm.SMTestRunnerConnectionUtil; 7 | import com.intellij.execution.testframework.ui.BaseTestsOutputConsoleView; 8 | import com.intellij.execution.ui.ConsoleView; 9 | import com.intellij.openapi.application.PathManager; 10 | import com.intellij.openapi.project.Project; 11 | import com.intellij.openapi.util.Disposer; 12 | import com.intellij.openapi.util.text.StringUtil; 13 | import com.jetbrains.php.config.commandLine.PhpCommandSettings; 14 | import com.jetbrains.php.config.interpreters.PhpConfigurationOptionData; 15 | import com.jetbrains.php.config.interpreters.PhpInterpreter; 16 | import com.jetbrains.php.run.filters.XdebugCallStackFilter; 17 | import cz.jiripudil.intellij.nette.tester.TesterUtil; 18 | import cz.jiripudil.intellij.nette.tester.configuration.TesterRunConfiguration; 19 | import cz.jiripudil.intellij.nette.tester.configuration.TesterSettings; 20 | import cz.jiripudil.intellij.nette.tester.projectSettings.TesterProjectSettings; 21 | import org.jetbrains.annotations.NotNull; 22 | 23 | import java.io.IOException; 24 | import java.io.InputStream; 25 | import java.nio.file.Files; 26 | import java.nio.file.Path; 27 | import java.nio.file.Paths; 28 | import java.nio.file.StandardCopyOption; 29 | import java.util.Arrays; 30 | import java.util.List; 31 | 32 | public class TesterExecutionUtil { 33 | public static void addCommandArguments(@NotNull Project project, @NotNull PhpCommandSettings command, TesterSettings settings, List arguments) throws ExecutionException { 34 | PhpInterpreter testEnvironmentInterpreter = settings.getPhpInterpreter(project); 35 | if (testEnvironmentInterpreter != null && testEnvironmentInterpreter.getPathToPhpExecutable() != null) { 36 | command.addArgument("-p"); 37 | command.addArgument(testEnvironmentInterpreter.getPathToPhpExecutable()); 38 | } 39 | 40 | if (settings.getUseSystemPhpIni()) { 41 | command.addArgument("-C"); 42 | 43 | } else if (!StringUtil.isEmpty(settings.getPhpIniPath())) { 44 | command.addArgument("-c"); 45 | command.addArgument(settings.getPhpIniPath()); 46 | } 47 | 48 | try { 49 | Path tempDir = getTempPath(project); 50 | if (!Files.isDirectory(tempDir)) { 51 | Files.createDirectory(tempDir); 52 | } 53 | 54 | TesterProjectSettings testerSettings = TesterUtil.getTesterSettings(project); 55 | String setupFile = testerSettings.getSetupFile(); 56 | 57 | Path setupScriptPath = Paths.get(tempDir.toString(), setupFile); 58 | InputStream setupResourceStream = TesterExecutionUtil.class.getClassLoader().getResourceAsStream(setupFile); 59 | if (setupResourceStream == null) { 60 | throw new ExecutionException("Input stream can not be null"); 61 | } 62 | Files.copy(setupResourceStream, setupScriptPath, StandardCopyOption.REPLACE_EXISTING); 63 | setupResourceStream.close(); 64 | 65 | command.addArgument("--setup"); 66 | command.addArgument(command.getPathProcessor().process(setupScriptPath.toString())); 67 | 68 | } catch (IOException e) { 69 | throw new ExecutionException(e); 70 | } 71 | 72 | if (!StringUtil.isEmpty(settings.getTesterOptions())) { 73 | String[] optionsArray = settings.getTesterOptions().split(" "); 74 | command.addArguments(Arrays.asList(optionsArray)); 75 | } 76 | 77 | for (PhpConfigurationOptionData configurationOption : settings.getPhpInterpreterOptions()) { 78 | command.addArgument("-d"); 79 | command.addArgument(configurationOption.getName() + (!configurationOption.getValue().isEmpty() ? "=" + configurationOption.getValue() : "")); 80 | } 81 | 82 | command.addArguments(arguments); 83 | command.addArgument(settings.getTestScope()); 84 | } 85 | 86 | private static Path getTempPath(Project project) { 87 | if (project.getBasePath() != null) { 88 | return Paths.get(project.getBasePath(), ".idea", "intellij-nette-tester"); 89 | } else { 90 | return Paths.get(PathManager.getPluginsPath(), "intellij-nette-tester"); 91 | } 92 | } 93 | 94 | public static ConsoleView createConsole(Project project, ProcessHandler processHandler, ExecutionEnvironment executionEnvironment, TesterTestLocator locationProvider) { 95 | TesterRunConfiguration profile = (TesterRunConfiguration) executionEnvironment.getRunProfile(); 96 | 97 | TesterConsoleProperties properties = new TesterConsoleProperties(profile, executionEnvironment.getExecutor(), locationProvider); 98 | properties.addStackTraceFilter(new XdebugCallStackFilter(project, locationProvider.getPathMapper())); 99 | 100 | BaseTestsOutputConsoleView testsOutputConsoleView = SMTestRunnerConnectionUtil.createConsole("Nette Tester", properties); 101 | testsOutputConsoleView.addMessageFilter(new TesterStackTraceFilter(project, locationProvider.getPathMapper())); 102 | testsOutputConsoleView.attachToProcess(processHandler); 103 | Disposer.register(project, testsOutputConsoleView); 104 | return testsOutputConsoleView; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/execution/TesterStackTraceFilter.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.execution; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | import com.intellij.openapi.progress.ProcessCanceledException; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.util.text.StringUtil; 7 | import com.jetbrains.php.run.filters.PhpFilter; 8 | import com.jetbrains.php.util.pathmapper.PhpPathMapper; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | 16 | public class TesterStackTraceFilter extends PhpFilter { 17 | private static final Logger LOG = Logger.getInstance("#cz.jiripudil.intellij.nette.tester.execution.TesterStackTraceFilter"); 18 | private static final Pattern STACK_TRACE_PATTERN; 19 | 20 | TesterStackTraceFilter(@NotNull Project project, @NotNull PhpPathMapper pathMapper) { 21 | super(project, pathMapper); 22 | } 23 | 24 | @Nullable 25 | @Override 26 | public MyResult applyFilter(@NotNull String line) { 27 | return apply(STACK_TRACE_PATTERN, line); 28 | } 29 | 30 | private MyResult apply(Pattern pattern, String line) { 31 | try { 32 | Matcher matcher = pattern.matcher(StringUtil.newBombedCharSequence(line, 1000L)); 33 | if (matcher.find()) { 34 | String fileName = matcher.group(1); 35 | String lineNumberString = matcher.group(2); 36 | if (fileName == null || lineNumberString == null) { 37 | return null; 38 | } 39 | 40 | try { 41 | int lineNumber = Integer.parseInt(lineNumberString); 42 | return new MyResult(fileName, lineNumber, matcher.start(1), matcher.end(1)); 43 | } catch (NumberFormatException var6) { 44 | // ignore 45 | } 46 | } 47 | 48 | return null; 49 | } catch (ProcessCanceledException var7) { 50 | LOG.warn("Matching took too long for line: " + line); 51 | return null; 52 | } 53 | } 54 | 55 | static { 56 | STACK_TRACE_PATTERN = Pattern.compile("in\\s(" + WHOLE_FILENAME_PATTERN + ")\\((\\p{Digit}*)\\)"); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/execution/TesterTestLocator.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.execution; 2 | 3 | import com.intellij.execution.Location; 4 | import com.intellij.execution.PsiLocation; 5 | import com.intellij.execution.testframework.sm.runner.SMTestLocator; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import com.intellij.psi.PsiElement; 9 | import com.intellij.psi.PsiFile; 10 | import com.intellij.psi.PsiManager; 11 | import com.intellij.psi.search.GlobalSearchScope; 12 | import com.intellij.psi.stubs.StubIndex; 13 | import com.jetbrains.php.lang.psi.PhpFile; 14 | import com.jetbrains.php.lang.psi.elements.Method; 15 | import com.jetbrains.php.lang.psi.stubs.indexes.PhpMethodIndex; 16 | import com.jetbrains.php.util.pathmapper.PhpPathMapper; 17 | import org.jetbrains.annotations.NotNull; 18 | import org.jetbrains.annotations.Nullable; 19 | 20 | import java.util.Collection; 21 | import java.util.Collections; 22 | import java.util.List; 23 | 24 | public class TesterTestLocator implements SMTestLocator { 25 | private static final String PROTOCOL_FILE = "tester_file"; 26 | private static final String PROTOCOL_METHOD = "tester_method"; 27 | 28 | @NotNull 29 | private final PhpPathMapper pathMapper; 30 | 31 | private TesterTestLocator(@NotNull PhpPathMapper pathMapper) { 32 | this.pathMapper = pathMapper; 33 | } 34 | 35 | public static TesterTestLocator create(@NotNull PhpPathMapper pathMapper) { 36 | return new TesterTestLocator(pathMapper); 37 | } 38 | 39 | @NotNull 40 | public PhpPathMapper getPathMapper() { 41 | return pathMapper; 42 | } 43 | 44 | @NotNull 45 | @Override 46 | public List getLocation(@NotNull String protocol, @NotNull String path, @NotNull Project project, @NotNull GlobalSearchScope globalSearchScope) { 47 | if (protocol.equals(PROTOCOL_FILE)) { 48 | PsiFile element = findFile(path, project); 49 | if (element != null) { 50 | return Collections.singletonList(new PsiLocation<>(project, element)); 51 | } 52 | 53 | } else if (protocol.equals(PROTOCOL_METHOD)) { 54 | Method element = findMethod(path, project); 55 | if (element != null) { 56 | return Collections.singletonList(new PsiLocation<>(project, element)); 57 | } 58 | } 59 | 60 | return Collections.emptyList(); 61 | } 62 | 63 | @Nullable 64 | private PsiFile findFile(String path, Project project) { 65 | VirtualFile virtualFile = this.pathMapper.getLocalFile(path); 66 | if (virtualFile == null) { 67 | return null; 68 | } 69 | 70 | PsiFile result = PsiManager.getInstance(project).findFile(virtualFile); 71 | if (!(result instanceof PhpFile)) { 72 | return null; 73 | } 74 | 75 | return result; 76 | } 77 | 78 | @Nullable 79 | private Method findMethod(String path, Project project) { 80 | String[] location = path.split("#"); 81 | int tokensNumber = location.length; 82 | if (tokensNumber == 2) { 83 | String filePath = location[0]; 84 | String methodName = location[1]; 85 | 86 | if (filePath == null) { 87 | return null; 88 | 89 | } else { 90 | PsiFile file = findFile(filePath, project); 91 | if (file == null) { 92 | return null; 93 | } 94 | 95 | GlobalSearchScope scope = GlobalSearchScope.fileScope(project, file.getVirtualFile()); 96 | Collection methods = StubIndex.getElements(PhpMethodIndex.KEY, methodName, project, scope, Method.class); 97 | if (methods.size() == 1) { 98 | return methods.iterator().next(); 99 | } 100 | } 101 | } 102 | 103 | return null; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/inspections/TestCaseAnnotationInspection.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.inspections; 2 | 3 | import com.intellij.codeInspection.*; 4 | import com.intellij.openapi.editor.Document; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.util.TextRange; 7 | import com.intellij.psi.*; 8 | import com.intellij.psi.codeStyle.CodeStyleManager; 9 | import com.intellij.psi.util.PsiTreeUtil; 10 | import com.jetbrains.php.lang.documentation.phpdoc.lexer.PhpDocTokenTypes; 11 | import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment; 12 | import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocTag; 13 | import com.jetbrains.php.lang.psi.PhpPsiElementFactory; 14 | import com.jetbrains.php.lang.psi.PhpPsiUtil; 15 | import com.jetbrains.php.lang.psi.elements.PhpClass; 16 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor; 17 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 18 | import cz.jiripudil.intellij.nette.tester.TesterUtil; 19 | import org.jetbrains.annotations.Nls; 20 | import org.jetbrains.annotations.NotNull; 21 | 22 | public class TestCaseAnnotationInspection extends LocalInspectionTool { 23 | private static final AnnotateTestCaseQuickFix QUICK_FIX = new AnnotateTestCaseQuickFix(); 24 | 25 | @NotNull 26 | @Override 27 | public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { 28 | return new PhpElementVisitor() { 29 | @Override 30 | public void visitPhpClass(PhpClass phpClass) { 31 | if ( ! TesterUtil.isTestClass(phpClass)) { 32 | return; 33 | } 34 | 35 | PhpDocComment docComment = phpClass.getDocComment(); 36 | if (phpClass.getIdentifyingElement() != null && (docComment == null || docComment.getTagElementsByName("@testCase").length == 0)) { 37 | holder.registerProblem(phpClass.getIdentifyingElement(), TesterBundle.message("inspections.annotation.description"), QUICK_FIX); 38 | } 39 | } 40 | }; 41 | } 42 | 43 | private static class AnnotateTestCaseQuickFix implements LocalQuickFix { 44 | @Nls 45 | @NotNull 46 | @Override 47 | public String getFamilyName() { 48 | return TesterBundle.message("inspections.familyName"); 49 | } 50 | 51 | @Nls 52 | @NotNull 53 | @Override 54 | public String getName() { 55 | return TesterBundle.message("inspections.annotation.quickFix"); 56 | } 57 | 58 | @Override 59 | public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor problemDescriptor) { 60 | PhpClass phpClass = (PhpClass) problemDescriptor.getPsiElement(); 61 | PsiFile containingFile = phpClass.getContainingFile(); 62 | 63 | PsiDocumentManager manager = PsiDocumentManager.getInstance(project); 64 | Document document = manager.getDocument(containingFile); 65 | 66 | String template = "/**\n* @testCase\n*/"; 67 | PsiElement testCaseTag = PhpPsiElementFactory.createFromText(project, PhpDocTag.class, template); 68 | assert testCaseTag != null; 69 | 70 | PhpDocComment docComment = phpClass.getDocComment(); 71 | if (docComment == null) { 72 | PsiElement testCaseDocComment = PhpPsiElementFactory.createFromText(project, PhpDocComment.class, template); 73 | assert testCaseDocComment != null; 74 | phpClass.getParent().addBefore(testCaseDocComment, phpClass); 75 | 76 | if (document != null) { 77 | manager.doPostponedOperationsAndUnblockDocument(document); 78 | TextRange reformatRange = testCaseDocComment.getTextRange(); 79 | CodeStyleManager.getInstance(project).reformatText(containingFile, reformatRange.getStartOffset(), reformatRange.getEndOffset()); 80 | } 81 | 82 | } else { 83 | PsiElement star = PhpPsiElementFactory.createFromText(project, PhpDocTokenTypes.DOC_LEADING_ASTERISK, template); 84 | PsiElement lineFeed = star.getPrevSibling(); 85 | PsiElement insertAfter = PhpPsiUtil.getChildOfType(docComment, PhpDocTokenTypes.DOC_COMMENT_END); 86 | 87 | if (insertAfter != null) { 88 | insertAfter = insertAfter.getPrevSibling(); 89 | int offset = insertAfter.getTextRange().getStartOffset(); 90 | PsiElement insertedLF = null; 91 | if (insertAfter instanceof PsiWhiteSpace && ! insertAfter.textContains('\n') && lineFeed != null) { 92 | insertedLF = docComment.addAfter(lineFeed, insertAfter); 93 | } 94 | 95 | testCaseTag = docComment.addAfter(testCaseTag, insertedLF == null ? insertAfter : insertedLF); 96 | docComment.addBefore(star, testCaseTag); 97 | if (document != null) { 98 | manager.doPostponedOperationsAndUnblockDocument(document); 99 | PhpDocComment updatedComment = PsiTreeUtil.getParentOfType(containingFile.findElementAt(offset), PhpDocComment.class); 100 | if (updatedComment != null) { 101 | TextRange reformatRange = updatedComment.getTextRange(); 102 | CodeStyleManager.getInstance(project).reformatText(containingFile, reformatRange.getStartOffset(), reformatRange.getEndOffset()); 103 | } 104 | } 105 | } 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/inspections/TestCaseIsRunInspection.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.inspections; 2 | 3 | import com.intellij.codeInspection.*; 4 | import com.intellij.openapi.editor.Document; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.util.TextRange; 7 | import com.intellij.psi.PsiDocumentManager; 8 | import com.intellij.psi.PsiElement; 9 | import com.intellij.psi.PsiElementVisitor; 10 | import com.intellij.psi.PsiFile; 11 | import com.intellij.psi.codeStyle.CodeStyleManager; 12 | import com.intellij.psi.impl.source.tree.LeafPsiElement; 13 | import com.jetbrains.php.lang.psi.PhpFile; 14 | import com.jetbrains.php.lang.psi.PhpPsiElementFactory; 15 | import com.jetbrains.php.lang.psi.elements.ExtendsList; 16 | import com.jetbrains.php.lang.psi.elements.MethodReference; 17 | import com.jetbrains.php.lang.psi.elements.PhpClass; 18 | import com.jetbrains.php.lang.psi.elements.Statement; 19 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor; 20 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 21 | import cz.jiripudil.intellij.nette.tester.TesterUtil; 22 | import org.jetbrains.annotations.Nls; 23 | import org.jetbrains.annotations.NotNull; 24 | 25 | import java.util.ArrayList; 26 | import java.util.HashSet; 27 | 28 | public class TestCaseIsRunInspection extends LocalInspectionTool { 29 | private static final AddRunMethodCallQuickFix ADD_RUN_METHOD_CALL_QUICK_FIX = new AddRunMethodCallQuickFix(); 30 | private static final MakeAbstractQuickFix MAKE_ABSTRACT_QUICK_FIX = new MakeAbstractQuickFix(); 31 | 32 | private ArrayList testClasses; 33 | private HashSet runReferencedClassNames; 34 | 35 | @NotNull 36 | @Override 37 | public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { 38 | return new PhpElementVisitor() { 39 | @Override 40 | public void visitPhpClass(PhpClass phpClass) { 41 | if (TesterUtil.isTestClass(phpClass)) { 42 | testClasses.add(phpClass); 43 | } 44 | } 45 | 46 | @Override 47 | public void visitPhpMethodReference(MethodReference reference) { 48 | if (reference.getClassReference() == null) { 49 | return; 50 | } 51 | 52 | if (reference.getCanonicalText().equalsIgnoreCase("run") || reference.getCanonicalText().equalsIgnoreCase("runTest")) { 53 | runReferencedClassNames.add(reference.getClassReference().getType().toString()); 54 | } 55 | } 56 | }; 57 | } 58 | 59 | @Override 60 | public void inspectionStarted(@NotNull LocalInspectionToolSession session, boolean isOnTheFly) { 61 | if ( ! (session.getFile() instanceof PhpFile)) { 62 | return; 63 | } 64 | 65 | testClasses = new ArrayList<>(); 66 | runReferencedClassNames = new HashSet<>(); 67 | } 68 | 69 | @Override 70 | public void inspectionFinished(@NotNull LocalInspectionToolSession session, @NotNull ProblemsHolder problemsHolder) { 71 | if ( ! (session.getFile() instanceof PhpFile)) { 72 | return; 73 | } 74 | 75 | testClasses.forEach((PhpClass testClass) -> { 76 | if ( ! runReferencedClassNames.contains(testClass.getFQN())) { 77 | ExtendsList extendsList = testClass.getExtendsList(); 78 | TextRange highlightRange = new TextRange(0, extendsList.getStartOffsetInParent() + extendsList.getTextLength()); 79 | problemsHolder.registerProblem( 80 | testClass, TesterBundle.message("inspections.runTestCase.description"), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, 81 | highlightRange, ADD_RUN_METHOD_CALL_QUICK_FIX, ! testClass.isFinal() ? MAKE_ABSTRACT_QUICK_FIX : null 82 | ); 83 | } 84 | }); 85 | } 86 | 87 | private static class AddRunMethodCallQuickFix implements LocalQuickFix { 88 | @Nls 89 | @NotNull 90 | @Override 91 | public String getFamilyName() { 92 | return TesterBundle.message("inspections.familyName"); 93 | } 94 | 95 | @Nls 96 | @NotNull 97 | @Override 98 | public String getName() { 99 | return TesterBundle.message("inspections.runTestCase.addRunMethodCall.quickFix"); 100 | } 101 | 102 | @Override 103 | public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor problemDescriptor) { 104 | PsiElement element = problemDescriptor.getPsiElement(); 105 | if ( ! (element instanceof PhpClass)) { 106 | return; 107 | } 108 | 109 | PsiFile containingFile = element.getContainingFile(); 110 | PsiDocumentManager manager = PsiDocumentManager.getInstance(project); 111 | Document document = manager.getDocument(containingFile); 112 | 113 | PhpClass phpClass = (PhpClass) element; 114 | String template = "\n\n(new " + phpClass.getName() + "())->run();"; 115 | Statement runMethodCall = PhpPsiElementFactory.createStatement(project, template); 116 | 117 | phpClass.getParent().addAfter(runMethodCall, phpClass); 118 | 119 | if (document != null) { 120 | manager.doPostponedOperationsAndUnblockDocument(document); 121 | TextRange reformatRange = runMethodCall.getTextRange(); 122 | CodeStyleManager.getInstance(project).reformatText(containingFile, reformatRange.getStartOffset(), reformatRange.getEndOffset()); 123 | } 124 | } 125 | } 126 | 127 | private static class MakeAbstractQuickFix implements LocalQuickFix { 128 | @Nls 129 | @NotNull 130 | @Override 131 | public String getFamilyName() { 132 | return TesterBundle.message("inspections.familyName"); 133 | } 134 | 135 | @Nls 136 | @NotNull 137 | @Override 138 | public String getName() { 139 | return TesterBundle.message("inspections.runTestCase.makeAbstract.quickFix"); 140 | } 141 | 142 | @Override 143 | public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor problemDescriptor) { 144 | PsiElement element = problemDescriptor.getPsiElement(); 145 | if ( ! (element instanceof PhpClass)) { 146 | return; 147 | } 148 | 149 | PsiFile containingFile = element.getContainingFile(); 150 | PsiDocumentManager manager = PsiDocumentManager.getInstance(project); 151 | Document document = manager.getDocument(containingFile); 152 | 153 | PhpClass phpClass = (PhpClass) element; 154 | PsiElement abstractKeyword = PhpPsiElementFactory.createFromText(project, LeafPsiElement.class, "abstract"); 155 | if (abstractKeyword == null) { 156 | return; 157 | } 158 | 159 | phpClass.addBefore(abstractKeyword, phpClass.getFirstChild()); 160 | 161 | if (document != null) { 162 | manager.doPostponedOperationsAndUnblockDocument(document); 163 | TextRange reformatRange = abstractKeyword.getTextRange(); 164 | CodeStyleManager.getInstance(project).reformatText(containingFile, reformatRange.getStartOffset(), reformatRange.getEndOffset()); 165 | } 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/inspections/TestFileNameInspection.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.inspections; 2 | 3 | import com.intellij.codeInspection.*; 4 | import com.intellij.openapi.diagnostic.Logger; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.util.text.StringUtil; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import com.intellij.psi.PsiElement; 9 | import com.intellij.psi.PsiElementVisitor; 10 | import com.jetbrains.php.lang.psi.PhpFile; 11 | import com.jetbrains.php.lang.psi.PhpPsiUtil; 12 | import com.jetbrains.php.lang.psi.elements.PhpClass; 13 | import com.jetbrains.php.lang.psi.visitors.PhpElementVisitor; 14 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 15 | import cz.jiripudil.intellij.nette.tester.TesterUtil; 16 | import org.jetbrains.annotations.Nls; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | import java.io.IOException; 20 | 21 | public class TestFileNameInspection extends LocalInspectionTool { 22 | private static final Logger LOG = Logger.getInstance("#cz.jiripudil.intellij.nette.tester.inspections.TestFileNameInspection"); 23 | private static final ChangeExtensionToPhptQuickFix CHANGE_EXTENSION_TO_PHPT_QUICK_FIX = new ChangeExtensionToPhptQuickFix(); 24 | private static final AddTestSuffixQuickFix ADD_TEST_SUFFIX_QUICK_FIX = new AddTestSuffixQuickFix(); 25 | 26 | @NotNull 27 | @Override 28 | public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { 29 | return new PhpElementVisitor() { 30 | @Override 31 | public void visitPhpFile(PhpFile phpFile) { 32 | PhpClass testClass = PhpPsiUtil.findClass(phpFile, TesterUtil::isTestClass); 33 | if (testClass != null && ! hasValidName(phpFile.getVirtualFile())) { 34 | holder.registerProblem(phpFile, TesterBundle.message("inspections.fileName.description"), CHANGE_EXTENSION_TO_PHPT_QUICK_FIX, ADD_TEST_SUFFIX_QUICK_FIX); 35 | } 36 | } 37 | }; 38 | } 39 | 40 | private boolean hasValidName(VirtualFile file) { 41 | return "phpt".equals(file.getExtension()) 42 | || ("php".equals(file.getExtension()) && StringUtil.endsWith(file.getNameWithoutExtension(), "Test")); 43 | } 44 | 45 | private static class ChangeExtensionToPhptQuickFix implements LocalQuickFix { 46 | @Nls 47 | @NotNull 48 | @Override 49 | public String getFamilyName() { 50 | return TesterBundle.message("inspections.familyName"); 51 | } 52 | 53 | @Nls 54 | @NotNull 55 | @Override 56 | public String getName() { 57 | return TesterBundle.message("inspections.fileName.quickFix.phpt"); 58 | } 59 | 60 | @Override 61 | public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor problemDescriptor) { 62 | PsiElement element = problemDescriptor.getPsiElement(); 63 | if (element instanceof PhpFile) { 64 | PhpFile phpFile = (PhpFile) element; 65 | VirtualFile virtualFile = phpFile.getVirtualFile(); 66 | String newName = virtualFile.getNameWithoutExtension() + ".phpt"; 67 | 68 | try { 69 | virtualFile.rename(this, newName); 70 | 71 | } catch (IOException e) { 72 | LOG.error(e); 73 | } 74 | } 75 | } 76 | } 77 | 78 | private static class AddTestSuffixQuickFix implements LocalQuickFix { 79 | @Nls 80 | @NotNull 81 | @Override 82 | public String getFamilyName() { 83 | return TesterBundle.message("inspections.familyName"); 84 | } 85 | 86 | @Nls 87 | @NotNull 88 | @Override 89 | public String getName() { 90 | return TesterBundle.message("inspections.fileName.quickFix.test"); 91 | } 92 | 93 | @Override 94 | public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor problemDescriptor) { 95 | PsiElement element = problemDescriptor.getPsiElement(); 96 | if (element instanceof PhpFile) { 97 | PhpFile phpFile = (PhpFile) element; 98 | VirtualFile virtualFile = phpFile.getVirtualFile(); 99 | String oldName = virtualFile.getNameWithoutExtension(); 100 | String newName = oldName + "Test"; 101 | 102 | PhpClass phpClass = PhpPsiUtil.findClass(phpFile, checkedClass -> checkedClass.getName().equals(oldName)); 103 | if (phpClass != null) { 104 | phpClass.setName(newName); 105 | } 106 | 107 | try { 108 | virtualFile.rename(this, newName + "." + virtualFile.getExtension()); 109 | 110 | } catch (IOException e) { 111 | LOG.error(e); 112 | } 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/lineMarker/TesterMethodRunLineMarkerProvider.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.lineMarker; 2 | 3 | import com.intellij.execution.lineMarker.RunLineMarkerContributor; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.psi.PsiElement; 6 | import com.jetbrains.php.lang.lexer.PhpTokenTypes; 7 | import com.jetbrains.php.lang.psi.elements.Method; 8 | import com.jetbrains.php.lang.psi.elements.PhpClass; 9 | import cz.jiripudil.intellij.nette.tester.TesterIcons; 10 | import cz.jiripudil.intellij.nette.tester.action.TesterCreateMethodRunTestAction; 11 | import cz.jiripudil.intellij.nette.tester.action.TesterMethodRunTestAction; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | public class TesterMethodRunLineMarkerProvider extends RunLineMarkerContributor { 16 | static String TEST_METHOD_PREFIX = "test"; 17 | 18 | @Override 19 | public @Nullable Info getInfo(@NotNull PsiElement psiElement) { 20 | if (psiElement.getNode().getElementType() == PhpTokenTypes.IDENTIFIER) { 21 | PsiElement method = psiElement.getParent(); 22 | if (method instanceof Method) { 23 | String methodName = ((Method) method).getName(); 24 | if (!methodName.startsWith(TEST_METHOD_PREFIX)) { 25 | return null; 26 | } 27 | 28 | PhpClass phpClass = ((Method) method).getContainingClass(); 29 | String className = phpClass != null ? phpClass.getName() : "null"; 30 | 31 | if (psiElement.getText().equals(methodName)) { 32 | AnAction[] actions = new AnAction[2]; 33 | actions[0] = new TesterMethodRunTestAction(psiElement, className, methodName); 34 | actions[1] = new TesterCreateMethodRunTestAction(psiElement, className, methodName); 35 | 36 | return new Info(TesterIcons.RUN_METHOD, actions, RunLineMarkerContributor.RUN_TEST_TOOLTIP_PROVIDER); 37 | } 38 | } 39 | } 40 | return null; 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/lineMarker/TesterRunLineMarkerProvider.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.lineMarker; 2 | 3 | import com.intellij.execution.lineMarker.RunLineMarkerContributor; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.psi.PsiElement; 6 | import com.jetbrains.php.lang.lexer.PhpTokenTypes; 7 | import com.jetbrains.php.lang.psi.elements.PhpClass; 8 | import cz.jiripudil.intellij.nette.tester.TesterIcons; 9 | import cz.jiripudil.intellij.nette.tester.TesterUtil; 10 | import cz.jiripudil.intellij.nette.tester.action.TesterCreateRunTestAction; 11 | import cz.jiripudil.intellij.nette.tester.action.TesterRunTestAction; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | public class TesterRunLineMarkerProvider extends RunLineMarkerContributor { 16 | @Override 17 | public @Nullable Info getInfo(@NotNull PsiElement psiElement) { 18 | if (psiElement.getNode().getElementType() == PhpTokenTypes.IDENTIFIER) { 19 | PsiElement phpClass = psiElement.getParent(); 20 | if (!(phpClass instanceof PhpClass) || !TesterUtil.isTestClass((PhpClass) phpClass)) { 21 | return null; 22 | } 23 | 24 | String className = psiElement.getText(); 25 | if (((PhpClass) phpClass).getName().equals(className)) { 26 | AnAction[] actions = new AnAction[2]; 27 | actions[0] = new TesterRunTestAction(psiElement, className); 28 | actions[1] = new TesterCreateRunTestAction(psiElement, className); 29 | 30 | return new Info(TesterIcons.RUN_CLASS, actions, RunLineMarkerContributor.RUN_TEST_TOOLTIP_PROVIDER); 31 | } 32 | } 33 | return null; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/projectSettings/TesterConfigurable.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.projectSettings; 2 | 3 | import com.intellij.openapi.options.Configurable; 4 | import com.intellij.openapi.options.ConfigurationException; 5 | import com.intellij.openapi.options.SearchableConfigurable; 6 | import com.intellij.openapi.project.Project; 7 | import cz.jiripudil.intellij.nette.tester.projectSettings.editor.TesterConfigurableForm; 8 | import org.jetbrains.annotations.Nls; 9 | import org.jetbrains.annotations.NotNull; 10 | import org.jetbrains.annotations.Nullable; 11 | 12 | import javax.swing.*; 13 | 14 | public class TesterConfigurable implements SearchableConfigurable, Configurable.NoScroll { 15 | private final Project project; 16 | private TesterConfigurableForm form; 17 | 18 | public TesterConfigurable(@NotNull final Project project) { 19 | this.project = project; 20 | } 21 | 22 | @NotNull 23 | @Override 24 | public String getId() { 25 | return this.getClass().getName(); 26 | } 27 | 28 | @Nls 29 | @Override 30 | public String getDisplayName() { 31 | return "Nette Tester"; 32 | } 33 | 34 | @Nullable 35 | @Override 36 | public JComponent createComponent() { 37 | if (form == null) { 38 | form = new TesterConfigurableForm(project); 39 | } 40 | 41 | return form.getComponent(); 42 | } 43 | 44 | @Override 45 | public boolean isModified() { 46 | return form.isModified(); 47 | } 48 | 49 | @Override 50 | public void apply() throws ConfigurationException { 51 | form.apply(); 52 | } 53 | 54 | @Override 55 | public void reset() { 56 | form.reset(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/projectSettings/TesterNamespaceMapping.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.projectSettings; 2 | 3 | import com.intellij.util.xmlb.annotations.Attribute; 4 | import com.intellij.util.xmlb.annotations.Tag; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | @Tag("testNamespaceMapping") 8 | public class TesterNamespaceMapping { 9 | @NotNull private String sourceNamespace; 10 | @NotNull private String testsNamespace; 11 | 12 | private TesterNamespaceMapping(@NotNull String sourceNamespace, @NotNull String testsNamespace) { 13 | this.sourceNamespace = sourceNamespace; 14 | this.testsNamespace = testsNamespace; 15 | } 16 | 17 | public TesterNamespaceMapping() { 18 | this("", ""); 19 | } 20 | 21 | @Attribute("sourceNamespace") 22 | @NotNull 23 | public String getSourceNamespace() { 24 | return sourceNamespace; 25 | } 26 | 27 | public void setSourceNamespace(@NotNull String sourceNamespace) { 28 | this.sourceNamespace = sourceNamespace; 29 | } 30 | 31 | @Attribute("testsNamespace") 32 | @NotNull 33 | public String getTestsNamespace() { 34 | return testsNamespace; 35 | } 36 | 37 | public void setTestsNamespace(@NotNull String testsNamespace) { 38 | this.testsNamespace = testsNamespace; 39 | } 40 | 41 | @Override 42 | public boolean equals(Object other) { 43 | if (this == other) return true; 44 | if (!(other instanceof TesterNamespaceMapping)) return false; 45 | 46 | TesterNamespaceMapping that = (TesterNamespaceMapping) other; 47 | 48 | return sourceNamespace.equals(that.sourceNamespace) 49 | && testsNamespace.equals(that.testsNamespace); 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | int result = sourceNamespace.hashCode(); 55 | result = 31 * result + testsNamespace.hashCode(); 56 | return result; 57 | } 58 | 59 | @Override 60 | public TesterNamespaceMapping clone() { 61 | return new TesterNamespaceMapping( 62 | this.sourceNamespace, 63 | this.testsNamespace 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/projectSettings/TesterProjectSettings.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.projectSettings; 2 | 3 | import com.intellij.util.xmlb.annotations.Attribute; 4 | import com.intellij.util.xmlb.annotations.Tag; 5 | import com.jetbrains.php.util.PhpConfigurationUtil; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | @Tag("testerSettings") 13 | public class TesterProjectSettings { 14 | @NotNull private String defaultExtension = "phpt"; 15 | @NotNull private String testerVersion = ">= 2.0"; 16 | @Nullable private String bootstrapFile; 17 | @NotNull private List namespaceMappings = new ArrayList<>(); 18 | 19 | @Attribute("defaultExtension") 20 | @NotNull 21 | public String getDefaultExtension() { 22 | return defaultExtension; 23 | } 24 | 25 | @Attribute("testerVersion") 26 | @NotNull 27 | public String getTesterVersion() { 28 | return testerVersion; 29 | } 30 | 31 | public String getSetupFile() { 32 | return testerVersion.equals("< 2.0") ? "setup.php" : "setup2-0.php"; 33 | } 34 | 35 | public void setTesterVersion(@NotNull String defaultExtension) { 36 | this.testerVersion = defaultExtension; 37 | } 38 | 39 | public void setDefaultExtension(@NotNull String defaultExtension) { 40 | this.defaultExtension = defaultExtension; 41 | } 42 | 43 | @Attribute("bootstrapFile") 44 | @Nullable 45 | public String getBootstrapFile() { 46 | return PhpConfigurationUtil.deserializePath(bootstrapFile); 47 | } 48 | 49 | public void setBootstrapFile(@Nullable String bootstrapFile) { 50 | this.bootstrapFile = PhpConfigurationUtil.serializePath(bootstrapFile); 51 | } 52 | 53 | @Tag("namespaceMappings") 54 | @NotNull 55 | public List getNamespaceMappings() { 56 | return namespaceMappings; 57 | } 58 | 59 | public void setNamespaceMappings(@NotNull List namespaceMappings) { 60 | this.namespaceMappings = namespaceMappings; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/projectSettings/TesterProjectSettingsManager.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.projectSettings; 2 | 3 | import com.intellij.openapi.components.*; 4 | import com.intellij.openapi.project.Project; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | @State( 9 | name = "Tester", 10 | storages = {@Storage("tester.xml")} 11 | ) 12 | public class TesterProjectSettingsManager implements PersistentStateComponent { 13 | private TesterProjectSettings settings; 14 | 15 | public TesterProjectSettingsManager() { 16 | this.settings = new TesterProjectSettings(); 17 | } 18 | 19 | @NotNull 20 | public static TesterProjectSettingsManager getInstance(@NotNull Project project) { 21 | return ServiceManager.getService(project, TesterProjectSettingsManager.class); 22 | } 23 | 24 | @Nullable 25 | @Override 26 | public TesterProjectSettings getState() { 27 | return settings; 28 | } 29 | 30 | @Override 31 | public void loadState(TesterProjectSettings settings) { 32 | this.settings = settings; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/projectSettings/editor/NamespaceMappingTable.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.projectSettings.editor; 2 | 3 | import com.intellij.execution.util.ListTableWithButtons; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.util.text.StringUtil; 6 | import com.intellij.util.ui.ColumnInfo; 7 | import com.intellij.util.ui.ListTableModel; 8 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 9 | import cz.jiripudil.intellij.nette.tester.projectSettings.TesterNamespaceMapping; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import javax.swing.table.TableCellEditor; 13 | 14 | public class NamespaceMappingTable extends ListTableWithButtons { 15 | @NotNull private final Project project; 16 | 17 | NamespaceMappingTable(@NotNull final Project project) { 18 | super(); 19 | this.project = project; 20 | this.getTableView().getEmptyText().setText(TesterBundle.message("settings.namespaceMappings.noMappings")); 21 | } 22 | 23 | @Override 24 | protected ListTableModel createListModel() { 25 | ColumnInfo sourceNamespace = new ElementsColumnInfoBase(TesterBundle.message("settings.namespaceMappings.sourceNamespace")) { 26 | @NotNull 27 | @Override 28 | public String valueOf(TesterNamespaceMapping testerTestMapping) { 29 | return testerTestMapping.getSourceNamespace(); 30 | } 31 | 32 | @Override 33 | public boolean isCellEditable(TesterNamespaceMapping testerTestMapping) { 34 | return NamespaceMappingTable.this.canDeleteElement(testerTestMapping); 35 | } 36 | 37 | @Override 38 | public void setValue(TesterNamespaceMapping testerTestMapping, String value) { 39 | testerTestMapping.setSourceNamespace(value); 40 | } 41 | 42 | @NotNull 43 | @Override 44 | protected String getDescription(TesterNamespaceMapping testerTestMapping) { 45 | return valueOf(testerTestMapping); 46 | } 47 | 48 | @NotNull 49 | @Override 50 | public TableCellEditor getEditor(TesterNamespaceMapping testerTestMapping) { 51 | return new NamespaceTableCellEditor(project); 52 | } 53 | }; 54 | 55 | ColumnInfo testNamespace = new ElementsColumnInfoBase(TesterBundle.message("settings.namespaceMappings.testsNamespace")) { 56 | @NotNull 57 | @Override 58 | public String valueOf(TesterNamespaceMapping testerTestMapping) { 59 | return testerTestMapping.getTestsNamespace(); 60 | } 61 | 62 | @Override 63 | public boolean isCellEditable(TesterNamespaceMapping testerTestMapping) { 64 | return NamespaceMappingTable.this.canDeleteElement(testerTestMapping); 65 | } 66 | 67 | @Override 68 | public void setValue(TesterNamespaceMapping testerTestMapping, String value) { 69 | testerTestMapping.setTestsNamespace(value); 70 | } 71 | 72 | @NotNull 73 | @Override 74 | protected String getDescription(TesterNamespaceMapping testerTestMapping) { 75 | return valueOf(testerTestMapping); 76 | } 77 | 78 | @NotNull 79 | @Override 80 | public TableCellEditor getEditor(TesterNamespaceMapping testerTestMapping) { 81 | return new NamespaceTableCellEditor(project); 82 | } 83 | }; 84 | 85 | return new ListTableModel(sourceNamespace, testNamespace); 86 | } 87 | 88 | @Override 89 | protected TesterNamespaceMapping createElement() { 90 | return new TesterNamespaceMapping(); 91 | } 92 | 93 | @Override 94 | protected boolean isEmpty(TesterNamespaceMapping testerTestMapping) { 95 | return StringUtil.isEmpty(testerTestMapping.getSourceNamespace()) 96 | && StringUtil.isEmpty(testerTestMapping.getTestsNamespace()); 97 | } 98 | 99 | @Override 100 | protected TesterNamespaceMapping cloneElement(TesterNamespaceMapping testerTestMapping) { 101 | return testerTestMapping.clone(); 102 | } 103 | 104 | @Override 105 | protected boolean canDeleteElement(TesterNamespaceMapping testerTestMapping) { 106 | return true; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/projectSettings/editor/NamespaceTableCellEditor.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/projectSettings/editor/NamespaceTableCellEditor.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.projectSettings.editor; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.ui.EditorTextField; 5 | import com.intellij.util.ui.AbstractTableCellEditor; 6 | import com.jetbrains.php.completion.PhpCompletionUtil; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | import javax.swing.*; 10 | import java.awt.*; 11 | 12 | public class NamespaceTableCellEditor extends AbstractTableCellEditor { 13 | @NotNull private final Project project; 14 | 15 | private JPanel panel; 16 | private EditorTextField namespaceField; 17 | 18 | NamespaceTableCellEditor(@NotNull final Project project) { 19 | this.project = project; 20 | } 21 | 22 | @Override 23 | public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { 24 | namespaceField.setText(value.toString()); 25 | return panel; 26 | } 27 | 28 | @Override 29 | public Object getCellEditorValue() { 30 | return namespaceField.getText(); 31 | } 32 | 33 | private void createUIComponents() { 34 | namespaceField = new EditorTextField("", project, null); 35 | PhpCompletionUtil.installNamespaceCompletion(namespaceField, null, () -> {}); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/projectSettings/editor/TesterConfigurableForm.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 |
72 | -------------------------------------------------------------------------------- /src/cz/jiripudil/intellij/nette/tester/projectSettings/editor/TesterConfigurableForm.java: -------------------------------------------------------------------------------- 1 | package cz.jiripudil.intellij.nette.tester.projectSettings.editor; 2 | 3 | import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.ui.ComboBox; 6 | import com.intellij.openapi.ui.TextFieldWithBrowseButton; 7 | import com.intellij.openapi.util.text.StringUtil; 8 | import com.intellij.ui.JBColor; 9 | import com.intellij.ui.ToolbarDecorator; 10 | import com.intellij.ui.components.JBLabel; 11 | import com.intellij.util.ui.ElementProducer; 12 | import com.jetbrains.php.util.ConfigurableForm; 13 | import cz.jiripudil.intellij.nette.tester.TesterBundle; 14 | import cz.jiripudil.intellij.nette.tester.TesterUtil; 15 | import cz.jiripudil.intellij.nette.tester.projectSettings.TesterNamespaceMapping; 16 | import cz.jiripudil.intellij.nette.tester.projectSettings.TesterProjectSettings; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | import javax.swing.*; 20 | import java.util.List; 21 | import java.util.stream.Collectors; 22 | 23 | public class TesterConfigurableForm implements ConfigurableForm { 24 | private final Project project; 25 | private JPanel panel; 26 | 27 | private JBLabel defaultExtensionLabel; 28 | private ComboBox defaultExtensionCombobox; 29 | private JBLabel bootstrapFileLabel; 30 | private TextFieldWithBrowseButton bootstrapFileField; 31 | 32 | private JPanel namespaceMappingPanel; 33 | private ComboBox testerVersionCombobox; 34 | private JBLabel testerVersionLabel; 35 | private NamespaceMappingTable namespaceMappingTable; 36 | 37 | public TesterConfigurableForm(@NotNull final Project project) { 38 | this.project = project; 39 | } 40 | 41 | @NotNull 42 | @Override 43 | public JComponent getComponent() { 44 | return panel; 45 | } 46 | 47 | @Override 48 | public boolean isModified() { 49 | TesterProjectSettings settings = TesterUtil.getTesterSettings(project); 50 | return settings != null && !( 51 | settings.getDefaultExtension().equals(defaultExtensionCombobox.getSelectedItem()) 52 | && settings.getTesterVersion().equals(testerVersionCombobox.getSelectedItem()) 53 | && StringUtil.notNullize(settings.getBootstrapFile()).equals(bootstrapFileField.getText()) 54 | && settings.getNamespaceMappings().equals(namespaceMappingTable.getTableView().getItems()) 55 | ); 56 | 57 | } 58 | 59 | @Override 60 | public void apply() { 61 | TesterProjectSettings settings = TesterUtil.getTesterSettings(project); 62 | if (settings == null) { 63 | return; 64 | } 65 | 66 | settings.setDefaultExtension(StringUtil.notNullize((String) defaultExtensionCombobox.getSelectedItem())); 67 | settings.setTesterVersion(StringUtil.notNullize((String) testerVersionCombobox.getSelectedItem())); 68 | settings.setBootstrapFile(bootstrapFileField.getText()); 69 | 70 | // lists work with references which complicates detecting modification, cloning each item helps 71 | settings.setNamespaceMappings(cloneNamespaceMappings(namespaceMappingTable.getTableView().getItems())); 72 | } 73 | 74 | @Override 75 | public void reset() { 76 | TesterProjectSettings settings = TesterUtil.getTesterSettings(project); 77 | if (settings == null) { 78 | return; 79 | } 80 | 81 | defaultExtensionCombobox.setSelectedItem(settings.getDefaultExtension()); 82 | testerVersionCombobox.setSelectedItem(settings.getTesterVersion().equals("> 2.0") ? ">= 2.0" : settings.getTesterVersion()); 83 | bootstrapFileField.setText(settings.getBootstrapFile()); 84 | 85 | // lists work with references which complicates detecting modification, cloning each item helps 86 | namespaceMappingTable.getTableView().getTableViewModel().setItems(cloneNamespaceMappings(settings.getNamespaceMappings())); 87 | } 88 | 89 | private List cloneNamespaceMappings(List input) { 90 | return input.stream() 91 | .map(TesterNamespaceMapping::clone) 92 | .collect(Collectors.toList()); 93 | } 94 | 95 | private void createUIComponents() { 96 | defaultExtensionLabel = new JBLabel(TesterBundle.message("settings.defaultExtension")); 97 | defaultExtensionCombobox = new ComboBox<>(new String[]{"phpt", "php"}); 98 | 99 | testerVersionLabel = new JBLabel(TesterBundle.message("settings.testerVersion")); 100 | testerVersionCombobox = new ComboBox<>(new String[]{"< 2.0", ">= 2.0"}); 101 | 102 | bootstrapFileLabel = new JBLabel(TesterBundle.message("settings.bootstrapFile")); 103 | bootstrapFileField = new TextFieldWithBrowseButton(); 104 | bootstrapFileField.addBrowseFolderListener(null, null, project, FileChooserDescriptorFactory.createSingleFileDescriptor("php")); 105 | 106 | namespaceMappingTable = new NamespaceMappingTable(project); 107 | namespaceMappingPanel = ToolbarDecorator.createDecorator(namespaceMappingTable.getTableView(), new ElementProducer() { 108 | @Override 109 | public TesterNamespaceMapping createElement() { 110 | return new TesterNamespaceMapping(); 111 | } 112 | 113 | @Override 114 | public boolean canCreateElement() { 115 | return true; 116 | } 117 | }).createPanel(); 118 | 119 | namespaceMappingPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, JBColor.LIGHT_GRAY), TesterBundle.message("settings.namespaceMappings.title"))); 120 | } 121 | } 122 | --------------------------------------------------------------------------------