├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml └── workflows │ └── ci.yml ├── .gitignore ├── .stylua.toml ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── lua └── neotest-phpunit │ ├── config.lua │ ├── init.lua │ └── utils.lua ├── phpunit.xml ├── scripts └── test ├── specs ├── init.vim └── utils_spec.lua ├── src ├── User.php └── autoload.php └── tests ├── Examples ├── UserTest.php └── some │ └── deep │ └── nesting │ └── NestingTest.php └── Unit ├── AttributeTest.php └── ExampleTest.php /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: If you've already asked for help with a problem and confirmed something is broken with the plugin itself, create a bug report. 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | assignees: olimorris 6 | 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for taking the time to fill out this bug report! This form may seem onerous but it makes a resolution much quicker for you and I. 12 | - type: markdown 13 | attributes: 14 | value: | 15 | ## Test with a minimal.lua file 16 | Firstly, test and run Neovim with the minimal config below. Be sure to add your own config, saving to a `minimal.lua` file and adding any additional plugins you may need: 17 | 18 | ```lua 19 | local root = vim.fn.fnamemodify("./.repro", ":p") 20 | 21 | -- set stdpaths to use .repro 22 | for _, name in ipairs({ "config", "data", "state", "cache" }) do 23 | vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name 24 | end 25 | 26 | -- bootstrap lazy 27 | local lazypath = root .. "/plugins/lazy.nvim" 28 | if not vim.loop.fs_stat(lazypath) then 29 | vim.fn.system({ 30 | "git", 31 | "clone", 32 | "--filter=blob:none", 33 | "--single-branch", 34 | "https://github.com/folke/lazy.nvim.git", 35 | lazypath, 36 | }) 37 | end 38 | vim.opt.runtimepath:prepend(lazypath) 39 | 40 | -- install plugins 41 | local plugins = { 42 | { 43 | "nvim-treesitter/nvim-treesitter", 44 | build = ":TSUpdate", 45 | config = function() 46 | require("nvim-treesitter.configs").setup({ 47 | ensure_installed = "php", 48 | }) 49 | end, 50 | }, 51 | { 52 | "nvim-neotest/neotest", 53 | lazy = true, 54 | dependencies = { 55 | "nvim-neotest/nvim-nio", 56 | "nvim-lua/plenary.nvim", 57 | "antoinemadec/FixCursorHold.nvim", 58 | "nvim-treesitter/nvim-treesitter", 59 | "olimorris/neotest-phpunit", 60 | }, 61 | config = function() 62 | log_level = vim.log.levels.DEBUG, 63 | require("neotest").setup({ 64 | adapters = { 65 | require("neotest-phpunit"), 66 | }, 67 | }) 68 | end, 69 | }, 70 | } 71 | 72 | require("lazy").setup(plugins, { 73 | root = root .. "/plugins", 74 | }) 75 | ``` 76 | 77 | Once you"ve updated for your config, run Neovim with this command: 78 | ```sh 79 | nvim --clean -u minimal.lua 80 | ``` 81 | > **Note:** You may need to do this a couple of times for Treesitter to fully download and sync 82 | 83 | - type: textarea 84 | id: phpunit-test 85 | attributes: 86 | label: PHPUnit test 87 | description: Please share the PHPUnit test you're testing (optional) 88 | placeholder: | 89 | ```php 90 | -- PHPUnit test here 91 | ``` 92 | validations: 93 | required: false 94 | 95 | - type: textarea 96 | id: errors 97 | attributes: 98 | label: Error messages 99 | description: Please paste any error messages you receive 100 | placeholder: | 101 | ```lua 102 | -- Error messages here 103 | ``` 104 | validations: 105 | required: false 106 | 107 | - type: textarea 108 | id: logs 109 | attributes: 110 | label: Neotest logs 111 | description: | 112 | 1. Wipe the `neotest.log` file in `stdpath("log")` or `stdpath("data")`. 113 | 2. Ensure `log_level = vim.log.levels.DEBUG` is set in your minimal config. 114 | 3. Reproduce the issue. 115 | 4. Provide the new logs. 116 | placeholder: | 117 | ### Neotest logs 118 | validations: 119 | required: true 120 | 121 | - type: textarea 122 | id: bug 123 | attributes: 124 | label: Describe the bug 125 | description: Please describe the bug and include any screenshots 126 | placeholder: | 127 | ### What I expect to happen 128 | [Your text here] 129 | 130 | ### What actually happens 131 | [Your text here] 132 | validations: 133 | required: true 134 | 135 | - type: textarea 136 | id: reproduce 137 | attributes: 138 | label: Reproduce the bug 139 | description: Please include the steps to reproduce the bug 140 | placeholder: | 141 | Steps to reproduce: 142 | 1. 143 | 2. 144 | 3. 145 | validations: 146 | required: false 147 | 148 | - type: checkboxes 149 | id: final-checks 150 | attributes: 151 | label: Final checks 152 | description: | 153 | Before you submit, please make sure you have completed the following steps: 154 | options: 155 | - label: I have made sure this issue exists in the latest version of the plugin 156 | required: true 157 | - label: I have tested with the `minimal.lua` config file above and still get the issue 158 | required: true 159 | - label: I have confirmed that Treesitter is working correctly by running `:checkhealth treesitter` 160 | required: true 161 | - label: I have made sure this is not a duplicate issue 162 | required: true 163 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Get Help 4 | url: https://github.com/olimorris/neotest-phpunit/discussions/new?category=q-a 5 | about: If you can't get something to work the way you expect, open a question in our discussion forums. 6 | - name: Feature Request 7 | url: https://github.com/olimorris/neotest-phpunit/discussions/new?category=ideas 8 | about: Suggest any ideas you have using our discussion forums. 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | # Cancel any in-progress CI runs for a PR if it is updated 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | unit_tests: 12 | name: unit tests 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | include: 18 | - os: ubuntu-20.04 19 | url: https://github.com/neovim/neovim/releases/download/nightly/nvim-linux64.tar.gz 20 | - os: ubuntu-20.04 21 | url: https://github.com/neovim/neovim/releases/download/v0.9.0/nvim-linux64.tar.gz 22 | - os: ubuntu-20.04 23 | url: https://github.com/neovim/neovim/releases/download/v0.8.0/nvim-linux64.tar.gz 24 | - os: ubuntu-20.04 25 | url: https://github.com/neovim/neovim/releases/download/v0.7.0/nvim-linux64.tar.gz 26 | 27 | steps: 28 | - uses: actions/checkout@v2 29 | - run: date +%F > todays-date 30 | - name: Restore cache for today's nightly. 31 | uses: actions/cache@v2 32 | with: 33 | path: _neovim 34 | key: ${{ runner.os }}-x64-${{ hashFiles('todays-date') }} 35 | 36 | - name: Prepare dependencies 37 | run: | 38 | test -d _neovim || { 39 | mkdir -p _neovim 40 | curl -sL ${{ matrix.url }} | tar xzf - --strip-components=1 -C "${PWD}/_neovim" 41 | } 42 | mkdir -p ~/.local/share/nvim/site/pack/vendor/start 43 | git clone --depth 1 https://github.com/nvim-lua/plenary.nvim.git ~/.local/share/nvim/site/pack/vendor/start/plenary.nvim 44 | git clone --depth 1 https://github.com/nvim-neotest/neotest.git ~/.local/share/nvim/site/pack/vendor/start/neotest 45 | git clone --depth 1 https://github.com/nvim-treesitter/nvim-treesitter.git ~/.local/share/nvim/site/pack/vendor/start/nvim-treesitter 46 | ln -s $(pwd) ~/.local/share/nvim/site/pack/vendor/start 47 | export PATH="${PWD}/_neovim/bin:${PATH}" 48 | export VIM="${PWD}/_neovim/share/nvim/runtime" 49 | nvim --headless -c 'TSInstallSync php | quit' 50 | 51 | - name: Run tests 52 | run: | 53 | export PATH="${PWD}/_neovim/bin:${PATH}" 54 | export VIM="${PWD}/_neovim/share/nvim/runtime" 55 | nvim --version 56 | ./scripts/test 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | # Others 43 | .luarc.json 44 | /vendor 45 | .phpunit.result.cache 46 | .php-cs-fixer.cache 47 | -------------------------------------------------------------------------------- /.stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 120 2 | indent_type = 'Spaces' 3 | indent_width = 2 4 | no_call_parentheses = false 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-present Oli Morris 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # neotest-phpunit 2 | 3 | [![Tests](https://github.com/olimorris/neotest-phpunit/actions/workflows/ci.yml/badge.svg)](https://github.com/olimorris/neotest-phpunit/actions/workflows/ci.yml) 4 | 5 | This plugin provides a [PHPUnit](https://phpunit.de) adapter for the [Neotest](https://github.com/nvim-neotest/neotest) framework. 6 | 7 | Neotest and PHPUnit 8 | 9 | ## :package: Installation 10 | 11 | Install with the package manager of your choice: 12 | 13 | **Lazy** 14 | 15 | ```lua 16 | { 17 | "nvim-neotest/neotest", 18 | lazy = true, 19 | dependencies = { 20 | ..., 21 | "olimorris/neotest-phpunit", 22 | }, 23 | config = function() 24 | require("neotest").setup({ 25 | ..., 26 | adapters = { 27 | require("neotest-phpunit") 28 | }, 29 | } 30 | end 31 | } 32 | ``` 33 | 34 | **Packer** 35 | 36 | ```lua 37 | use({ 38 | "nvim-neotest/neotest", 39 | requires = { 40 | ..., 41 | "olimorris/neotest-phpunit", 42 | }, 43 | config = function() 44 | require("neotest").setup({ 45 | ..., 46 | adapters = { 47 | require("neotest-phpunit"), 48 | } 49 | }) 50 | end 51 | }) 52 | ``` 53 | 54 | ## :wrench: Configuration 55 | 56 | ### Default configuration 57 | 58 | > [!NOTE] 59 | > You only need to the call the `setup` function if you wish to change any of the defaults. 60 | 61 |
62 | Click to see the default configuration 63 | 64 | ```lua 65 | adapters = { 66 | require("neotest-phpunit")({ 67 | phpunit_cmd = function() 68 | return "vendor/bin/phpunit" -- for `dap` strategy then it must return string (table values will cause validation error) 69 | end, 70 | root_files = { "composer.json", "phpunit.xml", ".gitignore" }, 71 | filter_dirs = { ".git", "node_modules" }, 72 | env = {}, -- for example {XDEBUG_CONFIG = 'idekey=neotest'} 73 | dap = nil, -- to configure `dap` strategy put single element from `dap.configurations.php` 74 | }), 75 | } 76 | ``` 77 | 78 |
79 | 80 | ### The test command 81 | 82 | The command used to run tests can be changed via the `phpunit_cmd` option: 83 | 84 | ```lua 85 | require("neotest-phpunit")({ 86 | phpunit_cmd = function() 87 | return "vendor/bin/phpunit" 88 | end 89 | }) 90 | ``` 91 | 92 | ### Setting the root directory 93 | 94 | For Neotest adapters to work, they need to define a project root whereby the process of discovering tests can take place. By default, the adapter looks for a `composer.json`, `phpunit.xml` or `.gitignore` file. These can be changed with: 95 | 96 | ```lua 97 | require("neotest-phpunit")({ 98 | root_files = { "README.md" } 99 | }) 100 | ``` 101 | 102 | You can even set `root_files` with a function which returns a table: 103 | 104 | ```lua 105 | require("neotest-phpunit")({ 106 | root_files = function() return { "README.md" } end 107 | }) 108 | ``` 109 | 110 | If there are projects you don't want discovered, you can instead set `root_ignore_files` to ignore any matching projects. 111 | 112 | For example, if your project uses Pest and the appropriate [neotest adapter](https://github.com/V13Axel/neotest-pest), you'll need to set: 113 | 114 | ```lua 115 | require("neotest-phpunit")({ 116 | root_ignore_files = { "tests/Pest.php" } 117 | }) 118 | ``` 119 | 120 | ### Filtering directories 121 | 122 | By default, the adapter will search test files in all dirs in the root with the exception of `node_modules` and `.git`. You can change this with: 123 | 124 | ```lua 125 | require("neotest-phpunit")({ 126 | filter_dirs = { "vendor" } 127 | }) 128 | ``` 129 | 130 | You can even set `filter_dirs` with a function which returns a table: 131 | 132 | ```lua 133 | require("neotest-phpunit")({ 134 | filter_dirs = function() return { "vendor" } end 135 | }) 136 | ``` 137 | 138 | ### Debugging 139 | 140 | The plugin can also be used for debugging via a dap strategy. 141 | 142 | Firstly, install and configure [nvim-dap](https://github.com/mfussenegger/nvim-dap) with [vscode-php-debug](https://github.com/xdebug/vscode-php-debug). Then set the following dap configuration: 143 | 144 | ```lua 145 | dap.configurations.php = { 146 | { 147 | log = true, 148 | type = "php", 149 | request = "launch", 150 | name = "Listen for XDebug", 151 | port = 9003, 152 | stopOnEntry = false, 153 | xdebugSettings = { 154 | max_children = 512, 155 | max_data = 1024, 156 | max_depth = 4, 157 | }, 158 | breakpoints = { 159 | exception = { 160 | Notice = false, 161 | Warning = false, 162 | Error = false, 163 | Exception = false, 164 | ["*"] = false, 165 | }, 166 | }, 167 | } 168 | } 169 | ``` 170 | 171 | Then in the plugin's config, add: 172 | 173 | ```lua 174 | require("neotest-phpunit")({ 175 | env = { 176 | XDEBUG_CONFIG = "idekey=neotest", 177 | }, 178 | dap = dap.configurations.php[1], 179 | }) 180 | ``` 181 | 182 | > [!NOTE] 183 | > If you run a test with the `dap` strategy from the summary window (by default by `d`) and see that the window content has been replaced by debugger content then consider setting `dap.defaults.fallback.switchbuf = "useopen"` or Neovim level [`switchbuf`](https://neovim.io/doc/user/options.html#'switchbuf') 184 | 185 | ## :rocket: Usage 186 | 187 | #### Test single method 188 | 189 | To test a single test, hover over the test and run `lua require("neotest").run.run()` 190 | 191 | #### Test file 192 | 193 | To test a file run `lua require("neotest").run.run(vim.fn.expand("%"))` 194 | 195 | #### Test directory 196 | 197 | To test a directory run `lua require("neotest").run.run("path/to/directory")` 198 | 199 | #### Test suite 200 | 201 | To test the full test suite run `lua require("neotest").run.run({ suite = true })` 202 | 203 | ## :gift: Contributing 204 | 205 | This project is maintained by the Neovim PHP community. Please raise a PR if you are interested in adding new functionality or fixing any bugs. When submitting a bug, please include an example test that we can test against. 206 | 207 | To trigger the tests for the adapter, run: 208 | 209 | ```sh 210 | ./scripts/test 211 | ``` 212 | 213 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require-dev": { 3 | "phpunit/phpunit": "^10" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "657b85759b97fae05fc2983bd4abadf7", 8 | "packages": [], 9 | "packages-dev": [ 10 | { 11 | "name": "myclabs/deep-copy", 12 | "version": "1.11.1", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/myclabs/DeepCopy.git", 16 | "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", 21 | "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": "^7.1 || ^8.0" 26 | }, 27 | "conflict": { 28 | "doctrine/collections": "<1.6.8", 29 | "doctrine/common": "<2.13.3 || >=3,<3.2.2" 30 | }, 31 | "require-dev": { 32 | "doctrine/collections": "^1.6.8", 33 | "doctrine/common": "^2.13.3 || ^3.2.2", 34 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" 35 | }, 36 | "type": "library", 37 | "autoload": { 38 | "files": [ 39 | "src/DeepCopy/deep_copy.php" 40 | ], 41 | "psr-4": { 42 | "DeepCopy\\": "src/DeepCopy/" 43 | } 44 | }, 45 | "notification-url": "https://packagist.org/downloads/", 46 | "license": [ 47 | "MIT" 48 | ], 49 | "description": "Create deep copies (clones) of your objects", 50 | "keywords": [ 51 | "clone", 52 | "copy", 53 | "duplicate", 54 | "object", 55 | "object graph" 56 | ], 57 | "support": { 58 | "issues": "https://github.com/myclabs/DeepCopy/issues", 59 | "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" 60 | }, 61 | "funding": [ 62 | { 63 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 64 | "type": "tidelift" 65 | } 66 | ], 67 | "time": "2023-03-08T13:26:56+00:00" 68 | }, 69 | { 70 | "name": "nikic/php-parser", 71 | "version": "v5.0.2", 72 | "source": { 73 | "type": "git", 74 | "url": "https://github.com/nikic/PHP-Parser.git", 75 | "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" 76 | }, 77 | "dist": { 78 | "type": "zip", 79 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", 80 | "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", 81 | "shasum": "" 82 | }, 83 | "require": { 84 | "ext-ctype": "*", 85 | "ext-json": "*", 86 | "ext-tokenizer": "*", 87 | "php": ">=7.4" 88 | }, 89 | "require-dev": { 90 | "ircmaxell/php-yacc": "^0.0.7", 91 | "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" 92 | }, 93 | "bin": [ 94 | "bin/php-parse" 95 | ], 96 | "type": "library", 97 | "extra": { 98 | "branch-alias": { 99 | "dev-master": "5.0-dev" 100 | } 101 | }, 102 | "autoload": { 103 | "psr-4": { 104 | "PhpParser\\": "lib/PhpParser" 105 | } 106 | }, 107 | "notification-url": "https://packagist.org/downloads/", 108 | "license": [ 109 | "BSD-3-Clause" 110 | ], 111 | "authors": [ 112 | { 113 | "name": "Nikita Popov" 114 | } 115 | ], 116 | "description": "A PHP parser written in PHP", 117 | "keywords": [ 118 | "parser", 119 | "php" 120 | ], 121 | "support": { 122 | "issues": "https://github.com/nikic/PHP-Parser/issues", 123 | "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" 124 | }, 125 | "time": "2024-03-05T20:51:40+00:00" 126 | }, 127 | { 128 | "name": "phar-io/manifest", 129 | "version": "2.0.4", 130 | "source": { 131 | "type": "git", 132 | "url": "https://github.com/phar-io/manifest.git", 133 | "reference": "54750ef60c58e43759730615a392c31c80e23176" 134 | }, 135 | "dist": { 136 | "type": "zip", 137 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", 138 | "reference": "54750ef60c58e43759730615a392c31c80e23176", 139 | "shasum": "" 140 | }, 141 | "require": { 142 | "ext-dom": "*", 143 | "ext-libxml": "*", 144 | "ext-phar": "*", 145 | "ext-xmlwriter": "*", 146 | "phar-io/version": "^3.0.1", 147 | "php": "^7.2 || ^8.0" 148 | }, 149 | "type": "library", 150 | "extra": { 151 | "branch-alias": { 152 | "dev-master": "2.0.x-dev" 153 | } 154 | }, 155 | "autoload": { 156 | "classmap": [ 157 | "src/" 158 | ] 159 | }, 160 | "notification-url": "https://packagist.org/downloads/", 161 | "license": [ 162 | "BSD-3-Clause" 163 | ], 164 | "authors": [ 165 | { 166 | "name": "Arne Blankerts", 167 | "email": "arne@blankerts.de", 168 | "role": "Developer" 169 | }, 170 | { 171 | "name": "Sebastian Heuer", 172 | "email": "sebastian@phpeople.de", 173 | "role": "Developer" 174 | }, 175 | { 176 | "name": "Sebastian Bergmann", 177 | "email": "sebastian@phpunit.de", 178 | "role": "Developer" 179 | } 180 | ], 181 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 182 | "support": { 183 | "issues": "https://github.com/phar-io/manifest/issues", 184 | "source": "https://github.com/phar-io/manifest/tree/2.0.4" 185 | }, 186 | "funding": [ 187 | { 188 | "url": "https://github.com/theseer", 189 | "type": "github" 190 | } 191 | ], 192 | "time": "2024-03-03T12:33:53+00:00" 193 | }, 194 | { 195 | "name": "phar-io/version", 196 | "version": "3.2.1", 197 | "source": { 198 | "type": "git", 199 | "url": "https://github.com/phar-io/version.git", 200 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" 201 | }, 202 | "dist": { 203 | "type": "zip", 204 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 205 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 206 | "shasum": "" 207 | }, 208 | "require": { 209 | "php": "^7.2 || ^8.0" 210 | }, 211 | "type": "library", 212 | "autoload": { 213 | "classmap": [ 214 | "src/" 215 | ] 216 | }, 217 | "notification-url": "https://packagist.org/downloads/", 218 | "license": [ 219 | "BSD-3-Clause" 220 | ], 221 | "authors": [ 222 | { 223 | "name": "Arne Blankerts", 224 | "email": "arne@blankerts.de", 225 | "role": "Developer" 226 | }, 227 | { 228 | "name": "Sebastian Heuer", 229 | "email": "sebastian@phpeople.de", 230 | "role": "Developer" 231 | }, 232 | { 233 | "name": "Sebastian Bergmann", 234 | "email": "sebastian@phpunit.de", 235 | "role": "Developer" 236 | } 237 | ], 238 | "description": "Library for handling version information and constraints", 239 | "support": { 240 | "issues": "https://github.com/phar-io/version/issues", 241 | "source": "https://github.com/phar-io/version/tree/3.2.1" 242 | }, 243 | "time": "2022-02-21T01:04:05+00:00" 244 | }, 245 | { 246 | "name": "phpunit/php-code-coverage", 247 | "version": "10.1.14", 248 | "source": { 249 | "type": "git", 250 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 251 | "reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b" 252 | }, 253 | "dist": { 254 | "type": "zip", 255 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/e3f51450ebffe8e0efdf7346ae966a656f7d5e5b", 256 | "reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b", 257 | "shasum": "" 258 | }, 259 | "require": { 260 | "ext-dom": "*", 261 | "ext-libxml": "*", 262 | "ext-xmlwriter": "*", 263 | "nikic/php-parser": "^4.18 || ^5.0", 264 | "php": ">=8.1", 265 | "phpunit/php-file-iterator": "^4.0", 266 | "phpunit/php-text-template": "^3.0", 267 | "sebastian/code-unit-reverse-lookup": "^3.0", 268 | "sebastian/complexity": "^3.0", 269 | "sebastian/environment": "^6.0", 270 | "sebastian/lines-of-code": "^2.0", 271 | "sebastian/version": "^4.0", 272 | "theseer/tokenizer": "^1.2.0" 273 | }, 274 | "require-dev": { 275 | "phpunit/phpunit": "^10.1" 276 | }, 277 | "suggest": { 278 | "ext-pcov": "PHP extension that provides line coverage", 279 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" 280 | }, 281 | "type": "library", 282 | "extra": { 283 | "branch-alias": { 284 | "dev-main": "10.1-dev" 285 | } 286 | }, 287 | "autoload": { 288 | "classmap": [ 289 | "src/" 290 | ] 291 | }, 292 | "notification-url": "https://packagist.org/downloads/", 293 | "license": [ 294 | "BSD-3-Clause" 295 | ], 296 | "authors": [ 297 | { 298 | "name": "Sebastian Bergmann", 299 | "email": "sebastian@phpunit.de", 300 | "role": "lead" 301 | } 302 | ], 303 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 304 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 305 | "keywords": [ 306 | "coverage", 307 | "testing", 308 | "xunit" 309 | ], 310 | "support": { 311 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", 312 | "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", 313 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.14" 314 | }, 315 | "funding": [ 316 | { 317 | "url": "https://github.com/sebastianbergmann", 318 | "type": "github" 319 | } 320 | ], 321 | "time": "2024-03-12T15:33:41+00:00" 322 | }, 323 | { 324 | "name": "phpunit/php-file-iterator", 325 | "version": "4.1.0", 326 | "source": { 327 | "type": "git", 328 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 329 | "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" 330 | }, 331 | "dist": { 332 | "type": "zip", 333 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", 334 | "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", 335 | "shasum": "" 336 | }, 337 | "require": { 338 | "php": ">=8.1" 339 | }, 340 | "require-dev": { 341 | "phpunit/phpunit": "^10.0" 342 | }, 343 | "type": "library", 344 | "extra": { 345 | "branch-alias": { 346 | "dev-main": "4.0-dev" 347 | } 348 | }, 349 | "autoload": { 350 | "classmap": [ 351 | "src/" 352 | ] 353 | }, 354 | "notification-url": "https://packagist.org/downloads/", 355 | "license": [ 356 | "BSD-3-Clause" 357 | ], 358 | "authors": [ 359 | { 360 | "name": "Sebastian Bergmann", 361 | "email": "sebastian@phpunit.de", 362 | "role": "lead" 363 | } 364 | ], 365 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 366 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 367 | "keywords": [ 368 | "filesystem", 369 | "iterator" 370 | ], 371 | "support": { 372 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", 373 | "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", 374 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" 375 | }, 376 | "funding": [ 377 | { 378 | "url": "https://github.com/sebastianbergmann", 379 | "type": "github" 380 | } 381 | ], 382 | "time": "2023-08-31T06:24:48+00:00" 383 | }, 384 | { 385 | "name": "phpunit/php-invoker", 386 | "version": "4.0.0", 387 | "source": { 388 | "type": "git", 389 | "url": "https://github.com/sebastianbergmann/php-invoker.git", 390 | "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" 391 | }, 392 | "dist": { 393 | "type": "zip", 394 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", 395 | "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", 396 | "shasum": "" 397 | }, 398 | "require": { 399 | "php": ">=8.1" 400 | }, 401 | "require-dev": { 402 | "ext-pcntl": "*", 403 | "phpunit/phpunit": "^10.0" 404 | }, 405 | "suggest": { 406 | "ext-pcntl": "*" 407 | }, 408 | "type": "library", 409 | "extra": { 410 | "branch-alias": { 411 | "dev-main": "4.0-dev" 412 | } 413 | }, 414 | "autoload": { 415 | "classmap": [ 416 | "src/" 417 | ] 418 | }, 419 | "notification-url": "https://packagist.org/downloads/", 420 | "license": [ 421 | "BSD-3-Clause" 422 | ], 423 | "authors": [ 424 | { 425 | "name": "Sebastian Bergmann", 426 | "email": "sebastian@phpunit.de", 427 | "role": "lead" 428 | } 429 | ], 430 | "description": "Invoke callables with a timeout", 431 | "homepage": "https://github.com/sebastianbergmann/php-invoker/", 432 | "keywords": [ 433 | "process" 434 | ], 435 | "support": { 436 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues", 437 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" 438 | }, 439 | "funding": [ 440 | { 441 | "url": "https://github.com/sebastianbergmann", 442 | "type": "github" 443 | } 444 | ], 445 | "time": "2023-02-03T06:56:09+00:00" 446 | }, 447 | { 448 | "name": "phpunit/php-text-template", 449 | "version": "3.0.1", 450 | "source": { 451 | "type": "git", 452 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 453 | "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" 454 | }, 455 | "dist": { 456 | "type": "zip", 457 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", 458 | "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", 459 | "shasum": "" 460 | }, 461 | "require": { 462 | "php": ">=8.1" 463 | }, 464 | "require-dev": { 465 | "phpunit/phpunit": "^10.0" 466 | }, 467 | "type": "library", 468 | "extra": { 469 | "branch-alias": { 470 | "dev-main": "3.0-dev" 471 | } 472 | }, 473 | "autoload": { 474 | "classmap": [ 475 | "src/" 476 | ] 477 | }, 478 | "notification-url": "https://packagist.org/downloads/", 479 | "license": [ 480 | "BSD-3-Clause" 481 | ], 482 | "authors": [ 483 | { 484 | "name": "Sebastian Bergmann", 485 | "email": "sebastian@phpunit.de", 486 | "role": "lead" 487 | } 488 | ], 489 | "description": "Simple template engine.", 490 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 491 | "keywords": [ 492 | "template" 493 | ], 494 | "support": { 495 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", 496 | "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", 497 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" 498 | }, 499 | "funding": [ 500 | { 501 | "url": "https://github.com/sebastianbergmann", 502 | "type": "github" 503 | } 504 | ], 505 | "time": "2023-08-31T14:07:24+00:00" 506 | }, 507 | { 508 | "name": "phpunit/php-timer", 509 | "version": "6.0.0", 510 | "source": { 511 | "type": "git", 512 | "url": "https://github.com/sebastianbergmann/php-timer.git", 513 | "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" 514 | }, 515 | "dist": { 516 | "type": "zip", 517 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", 518 | "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", 519 | "shasum": "" 520 | }, 521 | "require": { 522 | "php": ">=8.1" 523 | }, 524 | "require-dev": { 525 | "phpunit/phpunit": "^10.0" 526 | }, 527 | "type": "library", 528 | "extra": { 529 | "branch-alias": { 530 | "dev-main": "6.0-dev" 531 | } 532 | }, 533 | "autoload": { 534 | "classmap": [ 535 | "src/" 536 | ] 537 | }, 538 | "notification-url": "https://packagist.org/downloads/", 539 | "license": [ 540 | "BSD-3-Clause" 541 | ], 542 | "authors": [ 543 | { 544 | "name": "Sebastian Bergmann", 545 | "email": "sebastian@phpunit.de", 546 | "role": "lead" 547 | } 548 | ], 549 | "description": "Utility class for timing", 550 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 551 | "keywords": [ 552 | "timer" 553 | ], 554 | "support": { 555 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", 556 | "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" 557 | }, 558 | "funding": [ 559 | { 560 | "url": "https://github.com/sebastianbergmann", 561 | "type": "github" 562 | } 563 | ], 564 | "time": "2023-02-03T06:57:52+00:00" 565 | }, 566 | { 567 | "name": "phpunit/phpunit", 568 | "version": "10.5.20", 569 | "source": { 570 | "type": "git", 571 | "url": "https://github.com/sebastianbergmann/phpunit.git", 572 | "reference": "547d314dc24ec1e177720d45c6263fb226cc2ae3" 573 | }, 574 | "dist": { 575 | "type": "zip", 576 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/547d314dc24ec1e177720d45c6263fb226cc2ae3", 577 | "reference": "547d314dc24ec1e177720d45c6263fb226cc2ae3", 578 | "shasum": "" 579 | }, 580 | "require": { 581 | "ext-dom": "*", 582 | "ext-json": "*", 583 | "ext-libxml": "*", 584 | "ext-mbstring": "*", 585 | "ext-xml": "*", 586 | "ext-xmlwriter": "*", 587 | "myclabs/deep-copy": "^1.10.1", 588 | "phar-io/manifest": "^2.0.3", 589 | "phar-io/version": "^3.0.2", 590 | "php": ">=8.1", 591 | "phpunit/php-code-coverage": "^10.1.5", 592 | "phpunit/php-file-iterator": "^4.0", 593 | "phpunit/php-invoker": "^4.0", 594 | "phpunit/php-text-template": "^3.0", 595 | "phpunit/php-timer": "^6.0", 596 | "sebastian/cli-parser": "^2.0", 597 | "sebastian/code-unit": "^2.0", 598 | "sebastian/comparator": "^5.0", 599 | "sebastian/diff": "^5.0", 600 | "sebastian/environment": "^6.0", 601 | "sebastian/exporter": "^5.1", 602 | "sebastian/global-state": "^6.0.1", 603 | "sebastian/object-enumerator": "^5.0", 604 | "sebastian/recursion-context": "^5.0", 605 | "sebastian/type": "^4.0", 606 | "sebastian/version": "^4.0" 607 | }, 608 | "suggest": { 609 | "ext-soap": "To be able to generate mocks based on WSDL files" 610 | }, 611 | "bin": [ 612 | "phpunit" 613 | ], 614 | "type": "library", 615 | "extra": { 616 | "branch-alias": { 617 | "dev-main": "10.5-dev" 618 | } 619 | }, 620 | "autoload": { 621 | "files": [ 622 | "src/Framework/Assert/Functions.php" 623 | ], 624 | "classmap": [ 625 | "src/" 626 | ] 627 | }, 628 | "notification-url": "https://packagist.org/downloads/", 629 | "license": [ 630 | "BSD-3-Clause" 631 | ], 632 | "authors": [ 633 | { 634 | "name": "Sebastian Bergmann", 635 | "email": "sebastian@phpunit.de", 636 | "role": "lead" 637 | } 638 | ], 639 | "description": "The PHP Unit Testing framework.", 640 | "homepage": "https://phpunit.de/", 641 | "keywords": [ 642 | "phpunit", 643 | "testing", 644 | "xunit" 645 | ], 646 | "support": { 647 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", 648 | "security": "https://github.com/sebastianbergmann/phpunit/security/policy", 649 | "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.20" 650 | }, 651 | "funding": [ 652 | { 653 | "url": "https://phpunit.de/sponsors.html", 654 | "type": "custom" 655 | }, 656 | { 657 | "url": "https://github.com/sebastianbergmann", 658 | "type": "github" 659 | }, 660 | { 661 | "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", 662 | "type": "tidelift" 663 | } 664 | ], 665 | "time": "2024-04-24T06:32:35+00:00" 666 | }, 667 | { 668 | "name": "sebastian/cli-parser", 669 | "version": "2.0.1", 670 | "source": { 671 | "type": "git", 672 | "url": "https://github.com/sebastianbergmann/cli-parser.git", 673 | "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" 674 | }, 675 | "dist": { 676 | "type": "zip", 677 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", 678 | "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", 679 | "shasum": "" 680 | }, 681 | "require": { 682 | "php": ">=8.1" 683 | }, 684 | "require-dev": { 685 | "phpunit/phpunit": "^10.0" 686 | }, 687 | "type": "library", 688 | "extra": { 689 | "branch-alias": { 690 | "dev-main": "2.0-dev" 691 | } 692 | }, 693 | "autoload": { 694 | "classmap": [ 695 | "src/" 696 | ] 697 | }, 698 | "notification-url": "https://packagist.org/downloads/", 699 | "license": [ 700 | "BSD-3-Clause" 701 | ], 702 | "authors": [ 703 | { 704 | "name": "Sebastian Bergmann", 705 | "email": "sebastian@phpunit.de", 706 | "role": "lead" 707 | } 708 | ], 709 | "description": "Library for parsing CLI options", 710 | "homepage": "https://github.com/sebastianbergmann/cli-parser", 711 | "support": { 712 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues", 713 | "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", 714 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" 715 | }, 716 | "funding": [ 717 | { 718 | "url": "https://github.com/sebastianbergmann", 719 | "type": "github" 720 | } 721 | ], 722 | "time": "2024-03-02T07:12:49+00:00" 723 | }, 724 | { 725 | "name": "sebastian/code-unit", 726 | "version": "2.0.0", 727 | "source": { 728 | "type": "git", 729 | "url": "https://github.com/sebastianbergmann/code-unit.git", 730 | "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" 731 | }, 732 | "dist": { 733 | "type": "zip", 734 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", 735 | "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", 736 | "shasum": "" 737 | }, 738 | "require": { 739 | "php": ">=8.1" 740 | }, 741 | "require-dev": { 742 | "phpunit/phpunit": "^10.0" 743 | }, 744 | "type": "library", 745 | "extra": { 746 | "branch-alias": { 747 | "dev-main": "2.0-dev" 748 | } 749 | }, 750 | "autoload": { 751 | "classmap": [ 752 | "src/" 753 | ] 754 | }, 755 | "notification-url": "https://packagist.org/downloads/", 756 | "license": [ 757 | "BSD-3-Clause" 758 | ], 759 | "authors": [ 760 | { 761 | "name": "Sebastian Bergmann", 762 | "email": "sebastian@phpunit.de", 763 | "role": "lead" 764 | } 765 | ], 766 | "description": "Collection of value objects that represent the PHP code units", 767 | "homepage": "https://github.com/sebastianbergmann/code-unit", 768 | "support": { 769 | "issues": "https://github.com/sebastianbergmann/code-unit/issues", 770 | "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" 771 | }, 772 | "funding": [ 773 | { 774 | "url": "https://github.com/sebastianbergmann", 775 | "type": "github" 776 | } 777 | ], 778 | "time": "2023-02-03T06:58:43+00:00" 779 | }, 780 | { 781 | "name": "sebastian/code-unit-reverse-lookup", 782 | "version": "3.0.0", 783 | "source": { 784 | "type": "git", 785 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 786 | "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" 787 | }, 788 | "dist": { 789 | "type": "zip", 790 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", 791 | "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", 792 | "shasum": "" 793 | }, 794 | "require": { 795 | "php": ">=8.1" 796 | }, 797 | "require-dev": { 798 | "phpunit/phpunit": "^10.0" 799 | }, 800 | "type": "library", 801 | "extra": { 802 | "branch-alias": { 803 | "dev-main": "3.0-dev" 804 | } 805 | }, 806 | "autoload": { 807 | "classmap": [ 808 | "src/" 809 | ] 810 | }, 811 | "notification-url": "https://packagist.org/downloads/", 812 | "license": [ 813 | "BSD-3-Clause" 814 | ], 815 | "authors": [ 816 | { 817 | "name": "Sebastian Bergmann", 818 | "email": "sebastian@phpunit.de" 819 | } 820 | ], 821 | "description": "Looks up which function or method a line of code belongs to", 822 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 823 | "support": { 824 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", 825 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" 826 | }, 827 | "funding": [ 828 | { 829 | "url": "https://github.com/sebastianbergmann", 830 | "type": "github" 831 | } 832 | ], 833 | "time": "2023-02-03T06:59:15+00:00" 834 | }, 835 | { 836 | "name": "sebastian/comparator", 837 | "version": "5.0.1", 838 | "source": { 839 | "type": "git", 840 | "url": "https://github.com/sebastianbergmann/comparator.git", 841 | "reference": "2db5010a484d53ebf536087a70b4a5423c102372" 842 | }, 843 | "dist": { 844 | "type": "zip", 845 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372", 846 | "reference": "2db5010a484d53ebf536087a70b4a5423c102372", 847 | "shasum": "" 848 | }, 849 | "require": { 850 | "ext-dom": "*", 851 | "ext-mbstring": "*", 852 | "php": ">=8.1", 853 | "sebastian/diff": "^5.0", 854 | "sebastian/exporter": "^5.0" 855 | }, 856 | "require-dev": { 857 | "phpunit/phpunit": "^10.3" 858 | }, 859 | "type": "library", 860 | "extra": { 861 | "branch-alias": { 862 | "dev-main": "5.0-dev" 863 | } 864 | }, 865 | "autoload": { 866 | "classmap": [ 867 | "src/" 868 | ] 869 | }, 870 | "notification-url": "https://packagist.org/downloads/", 871 | "license": [ 872 | "BSD-3-Clause" 873 | ], 874 | "authors": [ 875 | { 876 | "name": "Sebastian Bergmann", 877 | "email": "sebastian@phpunit.de" 878 | }, 879 | { 880 | "name": "Jeff Welch", 881 | "email": "whatthejeff@gmail.com" 882 | }, 883 | { 884 | "name": "Volker Dusch", 885 | "email": "github@wallbash.com" 886 | }, 887 | { 888 | "name": "Bernhard Schussek", 889 | "email": "bschussek@2bepublished.at" 890 | } 891 | ], 892 | "description": "Provides the functionality to compare PHP values for equality", 893 | "homepage": "https://github.com/sebastianbergmann/comparator", 894 | "keywords": [ 895 | "comparator", 896 | "compare", 897 | "equality" 898 | ], 899 | "support": { 900 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 901 | "security": "https://github.com/sebastianbergmann/comparator/security/policy", 902 | "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1" 903 | }, 904 | "funding": [ 905 | { 906 | "url": "https://github.com/sebastianbergmann", 907 | "type": "github" 908 | } 909 | ], 910 | "time": "2023-08-14T13:18:12+00:00" 911 | }, 912 | { 913 | "name": "sebastian/complexity", 914 | "version": "3.2.0", 915 | "source": { 916 | "type": "git", 917 | "url": "https://github.com/sebastianbergmann/complexity.git", 918 | "reference": "68ff824baeae169ec9f2137158ee529584553799" 919 | }, 920 | "dist": { 921 | "type": "zip", 922 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", 923 | "reference": "68ff824baeae169ec9f2137158ee529584553799", 924 | "shasum": "" 925 | }, 926 | "require": { 927 | "nikic/php-parser": "^4.18 || ^5.0", 928 | "php": ">=8.1" 929 | }, 930 | "require-dev": { 931 | "phpunit/phpunit": "^10.0" 932 | }, 933 | "type": "library", 934 | "extra": { 935 | "branch-alias": { 936 | "dev-main": "3.2-dev" 937 | } 938 | }, 939 | "autoload": { 940 | "classmap": [ 941 | "src/" 942 | ] 943 | }, 944 | "notification-url": "https://packagist.org/downloads/", 945 | "license": [ 946 | "BSD-3-Clause" 947 | ], 948 | "authors": [ 949 | { 950 | "name": "Sebastian Bergmann", 951 | "email": "sebastian@phpunit.de", 952 | "role": "lead" 953 | } 954 | ], 955 | "description": "Library for calculating the complexity of PHP code units", 956 | "homepage": "https://github.com/sebastianbergmann/complexity", 957 | "support": { 958 | "issues": "https://github.com/sebastianbergmann/complexity/issues", 959 | "security": "https://github.com/sebastianbergmann/complexity/security/policy", 960 | "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" 961 | }, 962 | "funding": [ 963 | { 964 | "url": "https://github.com/sebastianbergmann", 965 | "type": "github" 966 | } 967 | ], 968 | "time": "2023-12-21T08:37:17+00:00" 969 | }, 970 | { 971 | "name": "sebastian/diff", 972 | "version": "5.1.1", 973 | "source": { 974 | "type": "git", 975 | "url": "https://github.com/sebastianbergmann/diff.git", 976 | "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" 977 | }, 978 | "dist": { 979 | "type": "zip", 980 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", 981 | "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", 982 | "shasum": "" 983 | }, 984 | "require": { 985 | "php": ">=8.1" 986 | }, 987 | "require-dev": { 988 | "phpunit/phpunit": "^10.0", 989 | "symfony/process": "^6.4" 990 | }, 991 | "type": "library", 992 | "extra": { 993 | "branch-alias": { 994 | "dev-main": "5.1-dev" 995 | } 996 | }, 997 | "autoload": { 998 | "classmap": [ 999 | "src/" 1000 | ] 1001 | }, 1002 | "notification-url": "https://packagist.org/downloads/", 1003 | "license": [ 1004 | "BSD-3-Clause" 1005 | ], 1006 | "authors": [ 1007 | { 1008 | "name": "Sebastian Bergmann", 1009 | "email": "sebastian@phpunit.de" 1010 | }, 1011 | { 1012 | "name": "Kore Nordmann", 1013 | "email": "mail@kore-nordmann.de" 1014 | } 1015 | ], 1016 | "description": "Diff implementation", 1017 | "homepage": "https://github.com/sebastianbergmann/diff", 1018 | "keywords": [ 1019 | "diff", 1020 | "udiff", 1021 | "unidiff", 1022 | "unified diff" 1023 | ], 1024 | "support": { 1025 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1026 | "security": "https://github.com/sebastianbergmann/diff/security/policy", 1027 | "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" 1028 | }, 1029 | "funding": [ 1030 | { 1031 | "url": "https://github.com/sebastianbergmann", 1032 | "type": "github" 1033 | } 1034 | ], 1035 | "time": "2024-03-02T07:15:17+00:00" 1036 | }, 1037 | { 1038 | "name": "sebastian/environment", 1039 | "version": "6.1.0", 1040 | "source": { 1041 | "type": "git", 1042 | "url": "https://github.com/sebastianbergmann/environment.git", 1043 | "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" 1044 | }, 1045 | "dist": { 1046 | "type": "zip", 1047 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", 1048 | "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", 1049 | "shasum": "" 1050 | }, 1051 | "require": { 1052 | "php": ">=8.1" 1053 | }, 1054 | "require-dev": { 1055 | "phpunit/phpunit": "^10.0" 1056 | }, 1057 | "suggest": { 1058 | "ext-posix": "*" 1059 | }, 1060 | "type": "library", 1061 | "extra": { 1062 | "branch-alias": { 1063 | "dev-main": "6.1-dev" 1064 | } 1065 | }, 1066 | "autoload": { 1067 | "classmap": [ 1068 | "src/" 1069 | ] 1070 | }, 1071 | "notification-url": "https://packagist.org/downloads/", 1072 | "license": [ 1073 | "BSD-3-Clause" 1074 | ], 1075 | "authors": [ 1076 | { 1077 | "name": "Sebastian Bergmann", 1078 | "email": "sebastian@phpunit.de" 1079 | } 1080 | ], 1081 | "description": "Provides functionality to handle HHVM/PHP environments", 1082 | "homepage": "https://github.com/sebastianbergmann/environment", 1083 | "keywords": [ 1084 | "Xdebug", 1085 | "environment", 1086 | "hhvm" 1087 | ], 1088 | "support": { 1089 | "issues": "https://github.com/sebastianbergmann/environment/issues", 1090 | "security": "https://github.com/sebastianbergmann/environment/security/policy", 1091 | "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" 1092 | }, 1093 | "funding": [ 1094 | { 1095 | "url": "https://github.com/sebastianbergmann", 1096 | "type": "github" 1097 | } 1098 | ], 1099 | "time": "2024-03-23T08:47:14+00:00" 1100 | }, 1101 | { 1102 | "name": "sebastian/exporter", 1103 | "version": "5.1.2", 1104 | "source": { 1105 | "type": "git", 1106 | "url": "https://github.com/sebastianbergmann/exporter.git", 1107 | "reference": "955288482d97c19a372d3f31006ab3f37da47adf" 1108 | }, 1109 | "dist": { 1110 | "type": "zip", 1111 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", 1112 | "reference": "955288482d97c19a372d3f31006ab3f37da47adf", 1113 | "shasum": "" 1114 | }, 1115 | "require": { 1116 | "ext-mbstring": "*", 1117 | "php": ">=8.1", 1118 | "sebastian/recursion-context": "^5.0" 1119 | }, 1120 | "require-dev": { 1121 | "phpunit/phpunit": "^10.0" 1122 | }, 1123 | "type": "library", 1124 | "extra": { 1125 | "branch-alias": { 1126 | "dev-main": "5.1-dev" 1127 | } 1128 | }, 1129 | "autoload": { 1130 | "classmap": [ 1131 | "src/" 1132 | ] 1133 | }, 1134 | "notification-url": "https://packagist.org/downloads/", 1135 | "license": [ 1136 | "BSD-3-Clause" 1137 | ], 1138 | "authors": [ 1139 | { 1140 | "name": "Sebastian Bergmann", 1141 | "email": "sebastian@phpunit.de" 1142 | }, 1143 | { 1144 | "name": "Jeff Welch", 1145 | "email": "whatthejeff@gmail.com" 1146 | }, 1147 | { 1148 | "name": "Volker Dusch", 1149 | "email": "github@wallbash.com" 1150 | }, 1151 | { 1152 | "name": "Adam Harvey", 1153 | "email": "aharvey@php.net" 1154 | }, 1155 | { 1156 | "name": "Bernhard Schussek", 1157 | "email": "bschussek@gmail.com" 1158 | } 1159 | ], 1160 | "description": "Provides the functionality to export PHP variables for visualization", 1161 | "homepage": "https://www.github.com/sebastianbergmann/exporter", 1162 | "keywords": [ 1163 | "export", 1164 | "exporter" 1165 | ], 1166 | "support": { 1167 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 1168 | "security": "https://github.com/sebastianbergmann/exporter/security/policy", 1169 | "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" 1170 | }, 1171 | "funding": [ 1172 | { 1173 | "url": "https://github.com/sebastianbergmann", 1174 | "type": "github" 1175 | } 1176 | ], 1177 | "time": "2024-03-02T07:17:12+00:00" 1178 | }, 1179 | { 1180 | "name": "sebastian/global-state", 1181 | "version": "6.0.2", 1182 | "source": { 1183 | "type": "git", 1184 | "url": "https://github.com/sebastianbergmann/global-state.git", 1185 | "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" 1186 | }, 1187 | "dist": { 1188 | "type": "zip", 1189 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", 1190 | "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", 1191 | "shasum": "" 1192 | }, 1193 | "require": { 1194 | "php": ">=8.1", 1195 | "sebastian/object-reflector": "^3.0", 1196 | "sebastian/recursion-context": "^5.0" 1197 | }, 1198 | "require-dev": { 1199 | "ext-dom": "*", 1200 | "phpunit/phpunit": "^10.0" 1201 | }, 1202 | "type": "library", 1203 | "extra": { 1204 | "branch-alias": { 1205 | "dev-main": "6.0-dev" 1206 | } 1207 | }, 1208 | "autoload": { 1209 | "classmap": [ 1210 | "src/" 1211 | ] 1212 | }, 1213 | "notification-url": "https://packagist.org/downloads/", 1214 | "license": [ 1215 | "BSD-3-Clause" 1216 | ], 1217 | "authors": [ 1218 | { 1219 | "name": "Sebastian Bergmann", 1220 | "email": "sebastian@phpunit.de" 1221 | } 1222 | ], 1223 | "description": "Snapshotting of global state", 1224 | "homepage": "https://www.github.com/sebastianbergmann/global-state", 1225 | "keywords": [ 1226 | "global state" 1227 | ], 1228 | "support": { 1229 | "issues": "https://github.com/sebastianbergmann/global-state/issues", 1230 | "security": "https://github.com/sebastianbergmann/global-state/security/policy", 1231 | "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" 1232 | }, 1233 | "funding": [ 1234 | { 1235 | "url": "https://github.com/sebastianbergmann", 1236 | "type": "github" 1237 | } 1238 | ], 1239 | "time": "2024-03-02T07:19:19+00:00" 1240 | }, 1241 | { 1242 | "name": "sebastian/lines-of-code", 1243 | "version": "2.0.2", 1244 | "source": { 1245 | "type": "git", 1246 | "url": "https://github.com/sebastianbergmann/lines-of-code.git", 1247 | "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" 1248 | }, 1249 | "dist": { 1250 | "type": "zip", 1251 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", 1252 | "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", 1253 | "shasum": "" 1254 | }, 1255 | "require": { 1256 | "nikic/php-parser": "^4.18 || ^5.0", 1257 | "php": ">=8.1" 1258 | }, 1259 | "require-dev": { 1260 | "phpunit/phpunit": "^10.0" 1261 | }, 1262 | "type": "library", 1263 | "extra": { 1264 | "branch-alias": { 1265 | "dev-main": "2.0-dev" 1266 | } 1267 | }, 1268 | "autoload": { 1269 | "classmap": [ 1270 | "src/" 1271 | ] 1272 | }, 1273 | "notification-url": "https://packagist.org/downloads/", 1274 | "license": [ 1275 | "BSD-3-Clause" 1276 | ], 1277 | "authors": [ 1278 | { 1279 | "name": "Sebastian Bergmann", 1280 | "email": "sebastian@phpunit.de", 1281 | "role": "lead" 1282 | } 1283 | ], 1284 | "description": "Library for counting the lines of code in PHP source code", 1285 | "homepage": "https://github.com/sebastianbergmann/lines-of-code", 1286 | "support": { 1287 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", 1288 | "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", 1289 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" 1290 | }, 1291 | "funding": [ 1292 | { 1293 | "url": "https://github.com/sebastianbergmann", 1294 | "type": "github" 1295 | } 1296 | ], 1297 | "time": "2023-12-21T08:38:20+00:00" 1298 | }, 1299 | { 1300 | "name": "sebastian/object-enumerator", 1301 | "version": "5.0.0", 1302 | "source": { 1303 | "type": "git", 1304 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1305 | "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" 1306 | }, 1307 | "dist": { 1308 | "type": "zip", 1309 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", 1310 | "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", 1311 | "shasum": "" 1312 | }, 1313 | "require": { 1314 | "php": ">=8.1", 1315 | "sebastian/object-reflector": "^3.0", 1316 | "sebastian/recursion-context": "^5.0" 1317 | }, 1318 | "require-dev": { 1319 | "phpunit/phpunit": "^10.0" 1320 | }, 1321 | "type": "library", 1322 | "extra": { 1323 | "branch-alias": { 1324 | "dev-main": "5.0-dev" 1325 | } 1326 | }, 1327 | "autoload": { 1328 | "classmap": [ 1329 | "src/" 1330 | ] 1331 | }, 1332 | "notification-url": "https://packagist.org/downloads/", 1333 | "license": [ 1334 | "BSD-3-Clause" 1335 | ], 1336 | "authors": [ 1337 | { 1338 | "name": "Sebastian Bergmann", 1339 | "email": "sebastian@phpunit.de" 1340 | } 1341 | ], 1342 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1343 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1344 | "support": { 1345 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", 1346 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" 1347 | }, 1348 | "funding": [ 1349 | { 1350 | "url": "https://github.com/sebastianbergmann", 1351 | "type": "github" 1352 | } 1353 | ], 1354 | "time": "2023-02-03T07:08:32+00:00" 1355 | }, 1356 | { 1357 | "name": "sebastian/object-reflector", 1358 | "version": "3.0.0", 1359 | "source": { 1360 | "type": "git", 1361 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1362 | "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" 1363 | }, 1364 | "dist": { 1365 | "type": "zip", 1366 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", 1367 | "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", 1368 | "shasum": "" 1369 | }, 1370 | "require": { 1371 | "php": ">=8.1" 1372 | }, 1373 | "require-dev": { 1374 | "phpunit/phpunit": "^10.0" 1375 | }, 1376 | "type": "library", 1377 | "extra": { 1378 | "branch-alias": { 1379 | "dev-main": "3.0-dev" 1380 | } 1381 | }, 1382 | "autoload": { 1383 | "classmap": [ 1384 | "src/" 1385 | ] 1386 | }, 1387 | "notification-url": "https://packagist.org/downloads/", 1388 | "license": [ 1389 | "BSD-3-Clause" 1390 | ], 1391 | "authors": [ 1392 | { 1393 | "name": "Sebastian Bergmann", 1394 | "email": "sebastian@phpunit.de" 1395 | } 1396 | ], 1397 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1398 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1399 | "support": { 1400 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues", 1401 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" 1402 | }, 1403 | "funding": [ 1404 | { 1405 | "url": "https://github.com/sebastianbergmann", 1406 | "type": "github" 1407 | } 1408 | ], 1409 | "time": "2023-02-03T07:06:18+00:00" 1410 | }, 1411 | { 1412 | "name": "sebastian/recursion-context", 1413 | "version": "5.0.0", 1414 | "source": { 1415 | "type": "git", 1416 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1417 | "reference": "05909fb5bc7df4c52992396d0116aed689f93712" 1418 | }, 1419 | "dist": { 1420 | "type": "zip", 1421 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", 1422 | "reference": "05909fb5bc7df4c52992396d0116aed689f93712", 1423 | "shasum": "" 1424 | }, 1425 | "require": { 1426 | "php": ">=8.1" 1427 | }, 1428 | "require-dev": { 1429 | "phpunit/phpunit": "^10.0" 1430 | }, 1431 | "type": "library", 1432 | "extra": { 1433 | "branch-alias": { 1434 | "dev-main": "5.0-dev" 1435 | } 1436 | }, 1437 | "autoload": { 1438 | "classmap": [ 1439 | "src/" 1440 | ] 1441 | }, 1442 | "notification-url": "https://packagist.org/downloads/", 1443 | "license": [ 1444 | "BSD-3-Clause" 1445 | ], 1446 | "authors": [ 1447 | { 1448 | "name": "Sebastian Bergmann", 1449 | "email": "sebastian@phpunit.de" 1450 | }, 1451 | { 1452 | "name": "Jeff Welch", 1453 | "email": "whatthejeff@gmail.com" 1454 | }, 1455 | { 1456 | "name": "Adam Harvey", 1457 | "email": "aharvey@php.net" 1458 | } 1459 | ], 1460 | "description": "Provides functionality to recursively process PHP variables", 1461 | "homepage": "https://github.com/sebastianbergmann/recursion-context", 1462 | "support": { 1463 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 1464 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" 1465 | }, 1466 | "funding": [ 1467 | { 1468 | "url": "https://github.com/sebastianbergmann", 1469 | "type": "github" 1470 | } 1471 | ], 1472 | "time": "2023-02-03T07:05:40+00:00" 1473 | }, 1474 | { 1475 | "name": "sebastian/type", 1476 | "version": "4.0.0", 1477 | "source": { 1478 | "type": "git", 1479 | "url": "https://github.com/sebastianbergmann/type.git", 1480 | "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" 1481 | }, 1482 | "dist": { 1483 | "type": "zip", 1484 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", 1485 | "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", 1486 | "shasum": "" 1487 | }, 1488 | "require": { 1489 | "php": ">=8.1" 1490 | }, 1491 | "require-dev": { 1492 | "phpunit/phpunit": "^10.0" 1493 | }, 1494 | "type": "library", 1495 | "extra": { 1496 | "branch-alias": { 1497 | "dev-main": "4.0-dev" 1498 | } 1499 | }, 1500 | "autoload": { 1501 | "classmap": [ 1502 | "src/" 1503 | ] 1504 | }, 1505 | "notification-url": "https://packagist.org/downloads/", 1506 | "license": [ 1507 | "BSD-3-Clause" 1508 | ], 1509 | "authors": [ 1510 | { 1511 | "name": "Sebastian Bergmann", 1512 | "email": "sebastian@phpunit.de", 1513 | "role": "lead" 1514 | } 1515 | ], 1516 | "description": "Collection of value objects that represent the types of the PHP type system", 1517 | "homepage": "https://github.com/sebastianbergmann/type", 1518 | "support": { 1519 | "issues": "https://github.com/sebastianbergmann/type/issues", 1520 | "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" 1521 | }, 1522 | "funding": [ 1523 | { 1524 | "url": "https://github.com/sebastianbergmann", 1525 | "type": "github" 1526 | } 1527 | ], 1528 | "time": "2023-02-03T07:10:45+00:00" 1529 | }, 1530 | { 1531 | "name": "sebastian/version", 1532 | "version": "4.0.1", 1533 | "source": { 1534 | "type": "git", 1535 | "url": "https://github.com/sebastianbergmann/version.git", 1536 | "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" 1537 | }, 1538 | "dist": { 1539 | "type": "zip", 1540 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", 1541 | "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", 1542 | "shasum": "" 1543 | }, 1544 | "require": { 1545 | "php": ">=8.1" 1546 | }, 1547 | "type": "library", 1548 | "extra": { 1549 | "branch-alias": { 1550 | "dev-main": "4.0-dev" 1551 | } 1552 | }, 1553 | "autoload": { 1554 | "classmap": [ 1555 | "src/" 1556 | ] 1557 | }, 1558 | "notification-url": "https://packagist.org/downloads/", 1559 | "license": [ 1560 | "BSD-3-Clause" 1561 | ], 1562 | "authors": [ 1563 | { 1564 | "name": "Sebastian Bergmann", 1565 | "email": "sebastian@phpunit.de", 1566 | "role": "lead" 1567 | } 1568 | ], 1569 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1570 | "homepage": "https://github.com/sebastianbergmann/version", 1571 | "support": { 1572 | "issues": "https://github.com/sebastianbergmann/version/issues", 1573 | "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" 1574 | }, 1575 | "funding": [ 1576 | { 1577 | "url": "https://github.com/sebastianbergmann", 1578 | "type": "github" 1579 | } 1580 | ], 1581 | "time": "2023-02-07T11:34:05+00:00" 1582 | }, 1583 | { 1584 | "name": "theseer/tokenizer", 1585 | "version": "1.2.3", 1586 | "source": { 1587 | "type": "git", 1588 | "url": "https://github.com/theseer/tokenizer.git", 1589 | "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" 1590 | }, 1591 | "dist": { 1592 | "type": "zip", 1593 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", 1594 | "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", 1595 | "shasum": "" 1596 | }, 1597 | "require": { 1598 | "ext-dom": "*", 1599 | "ext-tokenizer": "*", 1600 | "ext-xmlwriter": "*", 1601 | "php": "^7.2 || ^8.0" 1602 | }, 1603 | "type": "library", 1604 | "autoload": { 1605 | "classmap": [ 1606 | "src/" 1607 | ] 1608 | }, 1609 | "notification-url": "https://packagist.org/downloads/", 1610 | "license": [ 1611 | "BSD-3-Clause" 1612 | ], 1613 | "authors": [ 1614 | { 1615 | "name": "Arne Blankerts", 1616 | "email": "arne@blankerts.de", 1617 | "role": "Developer" 1618 | } 1619 | ], 1620 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 1621 | "support": { 1622 | "issues": "https://github.com/theseer/tokenizer/issues", 1623 | "source": "https://github.com/theseer/tokenizer/tree/1.2.3" 1624 | }, 1625 | "funding": [ 1626 | { 1627 | "url": "https://github.com/theseer", 1628 | "type": "github" 1629 | } 1630 | ], 1631 | "time": "2024-03-03T12:36:25+00:00" 1632 | } 1633 | ], 1634 | "aliases": [], 1635 | "minimum-stability": "stable", 1636 | "stability-flags": [], 1637 | "prefer-stable": false, 1638 | "prefer-lowest": false, 1639 | "platform": [], 1640 | "platform-dev": [], 1641 | "plugin-api-version": "2.6.0" 1642 | } 1643 | -------------------------------------------------------------------------------- /lua/neotest-phpunit/config.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | M.get_phpunit_cmd = function() 4 | return "vendor/bin/phpunit" 5 | end 6 | 7 | M.get_env = function() 8 | return {} 9 | end 10 | 11 | M.get_root_ignore_files = function() 12 | return {} 13 | end 14 | 15 | M.get_root_files = function() 16 | return { "composer.json", "phpunit.xml", ".gitignore" } 17 | end 18 | 19 | M.get_filter_dirs = function() 20 | return { ".git", "node_modules" } 21 | end 22 | 23 | return M 24 | -------------------------------------------------------------------------------- /lua/neotest-phpunit/init.lua: -------------------------------------------------------------------------------- 1 | local ok, async = pcall(require, "nio") 2 | if not ok then 3 | async = require("neotest.async") 4 | end 5 | 6 | local lib = require("neotest.lib") 7 | local logger = require("neotest.logging") 8 | local utils = require("neotest-phpunit.utils") 9 | local config = require("neotest-phpunit.config") 10 | 11 | local dap_configuration 12 | 13 | local function get_strategy_config(strategy, program, args) 14 | local cfg = { 15 | dap = function() 16 | vim.validate({ 17 | dap = { 18 | dap_configuration, 19 | function(val) 20 | local valid = type(val) == "table" and not vim.tbl_isempty(val) 21 | 22 | return valid, "Configure `dap` field (like in dap.configurations.php) before using this strategy" 23 | end, 24 | "not empty table", 25 | }, 26 | }) 27 | vim.validate({ 28 | phpunit_cmd = { 29 | program, 30 | function(val) 31 | return type(val) == "string", "For `dap` strategy `phpunit_cmd` must be (or return) string." 32 | end, 33 | "string", 34 | }, 35 | }) 36 | 37 | return vim.tbl_extend("keep", { 38 | type = "php", 39 | name = "Neotest Debugger", 40 | cwd = async.fn.getcwd(), 41 | program = program, 42 | args = args, 43 | runtimeArgs = { "-dzend_extension=xdebug.so" }, 44 | }, dap_configuration or {}) 45 | end, 46 | } 47 | if cfg[strategy] then 48 | return cfg[strategy]() 49 | end 50 | end 51 | 52 | ---@class neotest.Adapter 53 | ---@field name string 54 | local NeotestAdapter = { name = "neotest-phpunit" } 55 | 56 | ---Find the project root directory given a current directory to work from. 57 | ---Should no root be found, the adapter can still be used in a non-project context if a test file matches. 58 | ---@async 59 | ---@param dir string @Directory to treat as cwd 60 | ---@return string | nil @Absolute root dir of test suite 61 | function NeotestAdapter.root(dir) 62 | local result = nil 63 | 64 | for _, root_ignore_file in ipairs(config.get_root_ignore_files()) do 65 | result = lib.files.match_root_pattern(root_ignore_file)(dir) 66 | if result then 67 | return nil 68 | end 69 | end 70 | 71 | for _, root_file in ipairs(config.get_root_files()) do 72 | result = lib.files.match_root_pattern(root_file)(dir) 73 | if result then 74 | break 75 | end 76 | end 77 | 78 | return result 79 | end 80 | 81 | ---@async 82 | ---@param file_path string 83 | ---@return boolean 84 | function NeotestAdapter.is_test_file(file_path) 85 | return vim.endswith(file_path, "Test.php") 86 | end 87 | 88 | ---Filter directories when searching for test files 89 | ---@async 90 | ---@param name string Name of directory 91 | ---@return boolean 92 | function NeotestAdapter.filter_dir(name) 93 | for _, filter_dir in ipairs(config.get_filter_dirs()) do 94 | if name == filter_dir then 95 | return false 96 | end 97 | end 98 | 99 | return true 100 | end 101 | 102 | ---Given a file path, parse all the tests within it. 103 | ---@async 104 | ---@param file_path string Absolute file path 105 | ---@return neotest.Tree | nil 106 | function NeotestAdapter.discover_positions(path) 107 | if not NeotestAdapter.is_test_file(path) then 108 | return nil 109 | end 110 | 111 | local query = [[ 112 | ((class_declaration 113 | name: (name) @namespace.name (#match? @namespace.name "Test") 114 | )) @namespace.definition 115 | 116 | ((method_declaration 117 | (attribute_list 118 | (attribute_group 119 | (attribute) @test_attribute (#match? @test_attribute "Test") 120 | ) 121 | ) 122 | ( 123 | (visibility_modifier) 124 | (name) @test.name 125 | ) @test.definition 126 | )) 127 | 128 | ((method_declaration 129 | (name) @test.name (#match? @test.name "test") 130 | )) @test.definition 131 | 132 | (((comment) @test_comment (#match? @test_comment "\\@test") . 133 | (method_declaration 134 | (name) @test.name 135 | ) @test.definition 136 | )) 137 | ]] 138 | 139 | return lib.treesitter.parse_positions(path, query, { 140 | position_id = "require('neotest-phpunit.utils').make_test_id", 141 | }) 142 | end 143 | 144 | ---@param args neotest.RunArgs 145 | ---@return neotest.RunSpec | nil 146 | function NeotestAdapter.build_spec(args) 147 | local position = args.tree:data() 148 | local results_path = async.fn.tempname() 149 | local program = config.get_phpunit_cmd() 150 | 151 | local script_args = { 152 | position.name ~= "tests" and position.path, 153 | "--log-junit=" .. results_path, 154 | } 155 | 156 | if position.type == "test" then 157 | local filter_args = vim.tbl_flatten({ 158 | "--filter", 159 | "::" .. position.name .. "( with data set .*)?$", 160 | }) 161 | 162 | logger.info("position.path:", { position.path }) 163 | logger.info("--filter position.name:", { position.name }) 164 | 165 | script_args = vim.tbl_flatten({ 166 | script_args, 167 | filter_args, 168 | }) 169 | end 170 | 171 | local command = vim.tbl_flatten({ 172 | program, 173 | script_args, 174 | }) 175 | 176 | logger.trace("PHPUnit command: ", { command }) 177 | 178 | ---@type neotest.RunSpec 179 | return { 180 | command = command, 181 | context = { 182 | results_path = results_path, 183 | }, 184 | strategy = get_strategy_config(args.strategy, program, script_args), 185 | env = args.env or config.get_env(), 186 | } 187 | end 188 | 189 | ---@async 190 | ---@param spec neotest.RunSpec 191 | ---@param result neotest.StrategyResult 192 | ---@param tree neotest.Tree 193 | ---@return neotest.Result[] 194 | function NeotestAdapter.results(test, result, tree) 195 | local output_file = test.context.results_path 196 | 197 | local ok, data = pcall(lib.files.read, output_file) 198 | if not ok then 199 | logger.error("No test output file found:", output_file) 200 | return {} 201 | end 202 | 203 | local ok, parsed_data = pcall(lib.xml.parse, data) 204 | if not ok then 205 | logger.error("Failed to parse test output:", output_file) 206 | return {} 207 | end 208 | 209 | local ok, results = pcall(utils.get_test_results, parsed_data, output_file) 210 | if not ok then 211 | logger.error("Could not get test results", output_file) 212 | return {} 213 | end 214 | 215 | logger.trace("Results:", results) 216 | return results 217 | end 218 | 219 | local is_callable = function(obj) 220 | return type(obj) == "function" or (type(obj) == "table" and obj.__call) 221 | end 222 | 223 | setmetatable(NeotestAdapter, { 224 | __call = function(_, opts) 225 | if is_callable(opts.phpunit_cmd) then 226 | config.get_phpunit_cmd = opts.phpunit_cmd 227 | elseif opts.phpunit_cmd then 228 | config.get_phpunit_cmd = function() 229 | return opts.phpunit_cmd 230 | end 231 | end 232 | if is_callable(opts.root_ignore_files) then 233 | config.get_root_ignore_files = opts.root_ignore_files 234 | elseif opts.root_ignore_files then 235 | config.get_root_ignore_files = function() 236 | return opts.root_ignore_files 237 | end 238 | end 239 | if is_callable(opts.root_files) then 240 | config.get_root_files = opts.root_files 241 | elseif opts.root_files then 242 | config.get_root_files = function() 243 | return opts.root_files 244 | end 245 | end 246 | if is_callable(opts.filter_dirs) then 247 | config.get_filter_dirs = opts.filter_dirs 248 | elseif opts.filter_dirs then 249 | config.get_filter_dirs = function() 250 | return opts.filter_dirs 251 | end 252 | end 253 | if is_callable(opts.env) then 254 | config.get_env = opts.env 255 | elseif type(opts.env) == "table" then 256 | config.get_env = function() 257 | return opts.env 258 | end 259 | end 260 | if type(opts.dap) == "table" then 261 | dap_configuration = opts.dap 262 | end 263 | return NeotestAdapter 264 | end, 265 | }) 266 | 267 | return NeotestAdapter 268 | -------------------------------------------------------------------------------- /lua/neotest-phpunit/utils.lua: -------------------------------------------------------------------------------- 1 | local logger = require("neotest.logging") 2 | 3 | local M = {} 4 | local separator = "::" 5 | 6 | ---Generate an id which we can use to match Treesitter queries and PHPUnit tests 7 | ---@param position neotest.Position The position to return an ID for 8 | ---@param namespace neotest.Position[] Any namespaces the position is within 9 | ---@return string 10 | M.make_test_id = function(position) 11 | -- Treesitter starts line numbers from 0 so we add 1 12 | local id = position.path .. separator .. (tonumber(position.range[1]) + 1) 13 | 14 | logger.info("Path to test file:", { position.path }) 15 | logger.info("Treesitter id:", { id }) 16 | 17 | return id 18 | end 19 | 20 | ---Recursively iterate through a deeply nested table to obtain specified keys 21 | ---@param data_table table 22 | ---@param key string 23 | ---@param output_table table 24 | ---@return table 25 | local function iterate_key(data_table, key, output_table) 26 | if type(data_table) == "table" then 27 | for k, v in pairs(data_table) do 28 | if key == k then 29 | table.insert(output_table, v) 30 | end 31 | iterate_key(v, key, output_table) 32 | end 33 | end 34 | return output_table 35 | end 36 | 37 | ---Extract the failure messages from the tests 38 | ---@param tests table, 39 | ---@return boolean|table 40 | local function errors_or_fails(tests) 41 | local errors_fails = {} 42 | 43 | iterate_key(tests, "error", errors_fails) 44 | iterate_key(tests, "failure", errors_fails) 45 | 46 | if #errors_fails == 0 then 47 | return false 48 | end 49 | 50 | return errors_fails 51 | end 52 | 53 | ---Make the outputs for a given test 54 | ---@param test table 55 | ---@param output_file string 56 | ---@return table 57 | local function make_outputs(test, output_file) 58 | local test_attr = test["_attr"] or test[1]["_attr"] 59 | 60 | local test_id = test_attr.file .. separator .. test_attr.line 61 | logger.info("PHPUnit id:", { test_id }) 62 | 63 | local classname = test_attr.classname or test_attr.class 64 | local test_output = { 65 | status = "passed", 66 | short = string.upper(classname) .. "\n-> " .. "PASSED" .. " - " .. test_attr.name, 67 | output_file = output_file, 68 | } 69 | 70 | local test_failed = errors_or_fails(test) 71 | if test_failed then 72 | test_output.status = "failed" 73 | test_output.short = test_failed[1]["failure"] or test_failed[1]["errors"] 74 | test_output.errors = { 75 | { 76 | line = test_attr.line, 77 | }, 78 | } 79 | end 80 | 81 | return test_id, test_output 82 | end 83 | 84 | ---Iterate through test results and create a table of test IDs and outputs 85 | ---@param tests table 86 | ---@param output_file string 87 | ---@param output_table table 88 | ---@return table 89 | local function iterate_test_outputs(tests, output_file, output_table) 90 | for i = 1, #tests, 1 do 91 | if #tests[i] == 0 then 92 | local test_id, test_output = make_outputs(tests[i], output_file) 93 | output_table[test_id] = test_output 94 | else 95 | iterate_test_outputs(tests[i], output_file, output_table) 96 | end 97 | end 98 | return output_table 99 | end 100 | 101 | ---Get the test results from the parsed xml 102 | ---@param parsed_xml_output table 103 | ---@param output_file string 104 | ---@return neotest.Result[] 105 | M.get_test_results = function(parsed_xml_output, output_file) 106 | local tests = iterate_key(parsed_xml_output, "testcase", {}) 107 | return iterate_test_outputs(tests, output_file, {}) 108 | end 109 | 110 | return M 111 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests/Unit 10 | 11 | 12 | ./tests/Examples 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | tempfile=".test_output.tmp" 3 | 4 | if [[ -n $1 ]]; then 5 | nvim --headless --noplugin -u specs/init.vim -c "PlenaryBustedFile $1" | tee "${tempfile}" 6 | else 7 | nvim --headless --noplugin -u specs/init.vim -c "PlenaryBustedDirectory specs/ {minimal_init = 'specs/init.vim'}" | tee "${tempfile}" 8 | fi 9 | 10 | # Plenary doesn't emit exit code 1 when tests have errors during setup 11 | errors=$(sed 's/\x1b\[[0-9;]*m//g' "${tempfile}" | awk '/(Errors|Failed) :/ {print $3}' | grep -v '0') 12 | 13 | rm "${tempfile}" 14 | 15 | if [[ -n $errors ]]; then 16 | echo "Tests failed" 17 | exit 1 18 | fi 19 | 20 | exit 0 21 | -------------------------------------------------------------------------------- /specs/init.vim: -------------------------------------------------------------------------------- 1 | set rtp+=. 2 | set rtp+=../plenary.nvim 3 | runtime! plugin/plenary.vim 4 | -------------------------------------------------------------------------------- /specs/utils_spec.lua: -------------------------------------------------------------------------------- 1 | local utils = require("neotest-phpunit.utils") 2 | 3 | describe("get_test_results", function() 4 | it("parses output when testing a method", function() 5 | local output_file = "/tmp/nvimhYaIPj/3" 6 | local xml_output = { 7 | testsuites = { 8 | testsuite = { 9 | _attr = { 10 | assertions = "1", 11 | errors = "0", 12 | failures = "0", 13 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Unit/ExampleTest.php", 14 | name = "ExampleTest", 15 | skipped = "0", 16 | tests = "1", 17 | time = "0.002292", 18 | warnings = "0", 19 | }, 20 | testcase = { 21 | _attr = { 22 | assertions = "1", 23 | class = "ExampleTest", 24 | classname = "ExampleTest", 25 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Unit/ExampleTest.php", 26 | line = "7", 27 | name = "test_that_true_is_true", 28 | time = "0.002292", 29 | }, 30 | }, 31 | }, 32 | }, 33 | } 34 | 35 | local expected = { 36 | ["/Users/Oli/Code/Projects/neotest-phpunit/tests/Unit/ExampleTest.php::7"] = { 37 | ["output_file"] = "/tmp/nvimhYaIPj/3", 38 | ["short"] = [[EXAMPLETEST 39 | -> PASSED - test_that_true_is_true]], 40 | ["status"] = "passed", 41 | }, 42 | } 43 | 44 | assert.are.same(utils.get_test_results(xml_output, output_file), expected) 45 | end) 46 | 47 | it("parses output when testing a file", function() 48 | local output_file = "/tmp/nvimhYaIPj/3" 49 | local xml_output = { 50 | testsuites = { 51 | testsuite = { 52 | _attr = { 53 | assertions = "2", 54 | errors = "0", 55 | failures = "0", 56 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php", 57 | name = "TestProject\\UserTest", 58 | skipped = "0", 59 | tests = "2", 60 | time = "0.001525", 61 | warnings = "0", 62 | }, 63 | testcase = { 64 | { 65 | _attr = { 66 | assertions = "3", 67 | class = "TestProject\\UserTest", 68 | classname = "TestProject.UserTest", 69 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php", 70 | line = "13", 71 | name = "testClassConstructor", 72 | time = "0.000949", 73 | }, 74 | }, 75 | { 76 | _attr = { 77 | assertions = "2", 78 | class = "TestProject\\UserTest", 79 | classname = "TestProject.UserTest", 80 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php", 81 | line = "22", 82 | name = "testTellName", 83 | time = "0.000135", 84 | }, 85 | }, 86 | }, 87 | }, 88 | }, 89 | } 90 | 91 | local expected = { 92 | ["/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php::13"] = { 93 | ["output_file"] = "/tmp/nvimhYaIPj/3", 94 | ["short"] = [[TESTPROJECT.USERTEST 95 | -> PASSED - testClassConstructor]], 96 | ["status"] = "passed", 97 | }, 98 | ["/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php::22"] = { 99 | ["output_file"] = "/tmp/nvimhYaIPj/3", 100 | ["short"] = [[TESTPROJECT.USERTEST 101 | -> PASSED - testTellName]], 102 | ["status"] = "passed", 103 | }, 104 | } 105 | 106 | assert.are.same(utils.get_test_results(xml_output, output_file), expected) 107 | end) 108 | 109 | it("parses a file even if there is a failure", function() 110 | local output_file = "/tmp/nvimhYaIPj/3" 111 | local xml_output = { 112 | testsuites = { 113 | testsuite = { 114 | _attr = { 115 | assertions = "2", 116 | errors = "0", 117 | failures = "1", 118 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Unit/ExampleTest.php", 119 | name = "ExampleTest", 120 | skipped = "0", 121 | tests = "2", 122 | time = "0.001008", 123 | warnings = "0", 124 | }, 125 | testcase = { 126 | { 127 | _attr = { 128 | assertions = "1", 129 | class = "ExampleTest", 130 | classname = "ExampleTest", 131 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Unit/ExampleTest.php", 132 | line = "12", 133 | name = "test_that_false_is_true", 134 | time = "0.000141", 135 | }, 136 | failure = { 137 | [[ExampleTest::test_that_false_is_true 138 | Failed asserting that false is true. 139 | 140 | /Users/Oli/Code/Projects/neotest-phpunit/tests/Unit/ExampleTest.php:14]], 141 | _attr = { 142 | type = "PHPUnit\\Framework\\ExpectationFailedException", 143 | }, 144 | }, 145 | }, 146 | }, 147 | }, 148 | }, 149 | } 150 | 151 | local expected = { 152 | ["/Users/Oli/Code/Projects/neotest-phpunit/tests/Unit/ExampleTest.php::12"] = { 153 | errors = { 154 | { 155 | line = "12", 156 | }, 157 | }, 158 | output_file = "/tmp/nvimhYaIPj/3", 159 | status = "failed", 160 | }, 161 | } 162 | 163 | assert.are.same(utils.get_test_results(xml_output, output_file), expected) 164 | end) 165 | 166 | it("parses multiple files", function() 167 | local output_file = "/tmp/nvimhYaIPj/3" 168 | local xml_output = { 169 | testsuites = { 170 | testsuite = { 171 | _attr = { 172 | assertions = "18", 173 | errors = "0", 174 | failures = "1", 175 | name = "", 176 | skipped = "0", 177 | tests = "8", 178 | time = "0.001904", 179 | warnings = "0", 180 | }, 181 | testsuite = { 182 | { 183 | _attr = { 184 | assertions = "2", 185 | errors = "0", 186 | failures = "1", 187 | name = "Unit", 188 | skipped = "0", 189 | tests = "2", 190 | time = "0.001233", 191 | warnings = "0", 192 | }, 193 | testsuite = { 194 | _attr = { 195 | assertions = "2", 196 | errors = "0", 197 | failures = "1", 198 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Unit/ExampleTest.php", 199 | name = "ExampleTest", 200 | skipped = "0", 201 | tests = "2", 202 | time = "0.001233", 203 | warnings = "0", 204 | }, 205 | testcase = { 206 | { 207 | _attr = { 208 | assertions = "1", 209 | class = "ExampleTest", 210 | classname = "ExampleTest", 211 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Unit/ExampleTest.php", 212 | line = "7", 213 | name = "test_that_true_is_true", 214 | time = "0.001027", 215 | }, 216 | }, 217 | { 218 | _attr = { 219 | assertions = "1", 220 | class = "ExampleTest", 221 | classname = "ExampleTest", 222 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Unit/ExampleTest.php", 223 | line = "13", 224 | name = "this_should_fail", 225 | time = "0.000207", 226 | }, 227 | failure = { 228 | [[ExampleTest::this_should_fail 229 | Failed asserting that false is true. 230 | 231 | /Users/Oli/Code/Projects/neotest-phpunit/tests/Unit/ExampleTest.php:15]], 232 | _attr = { 233 | type = "PHPUnit\\Framework\\ExpectationFailedException", 234 | }, 235 | }, 236 | }, 237 | }, 238 | }, 239 | }, 240 | { 241 | _attr = { 242 | assertions = "16", 243 | errors = "0", 244 | failures = "0", 245 | name = "Examples", 246 | skipped = "0", 247 | tests = "6", 248 | time = "0.000671", 249 | warnings = "0", 250 | }, 251 | testsuite = { 252 | { 253 | _attr = { 254 | assertions = "15", 255 | errors = "0", 256 | failures = "0", 257 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php", 258 | name = "TestProject\\UserTest", 259 | skipped = "0", 260 | tests = "5", 261 | time = "0.000646", 262 | warnings = "0", 263 | }, 264 | testcase = { 265 | { 266 | _attr = { 267 | assertions = "3", 268 | class = "TestProject\\UserTest", 269 | classname = "TestProject.UserTest", 270 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php", 271 | line = "13", 272 | name = "testClassConstructor", 273 | time = "0.000145", 274 | }, 275 | }, 276 | { 277 | _attr = { 278 | assertions = "2", 279 | class = "TestProject\\UserTest", 280 | classname = "TestProject.UserTest", 281 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php", 282 | line = "22", 283 | name = "testTellName", 284 | time = "0.000143", 285 | }, 286 | }, 287 | { 288 | _attr = { 289 | assertions = "2", 290 | class = "TestProject\\UserTest", 291 | classname = "TestProject.UserTest", 292 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php", 293 | line = "30", 294 | name = "testTellAge", 295 | time = "0.000026", 296 | }, 297 | }, 298 | { 299 | _attr = { 300 | assertions = "3", 301 | class = "TestProject\\UserTest", 302 | classname = "TestProject.UserTest", 303 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php", 304 | line = "38", 305 | name = "testAddFavoriteMovie", 306 | time = "0.000149", 307 | }, 308 | }, 309 | { 310 | _attr = { 311 | assertions = "5", 312 | class = "TestProject\\UserTest", 313 | classname = "TestProject.UserTest", 314 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php", 315 | line = "47", 316 | name = "testRemoveFavoriteMovie", 317 | time = "0.000183", 318 | }, 319 | }, 320 | }, 321 | }, 322 | { 323 | _attr = { 324 | assertions = "1", 325 | errors = "0", 326 | failures = "0", 327 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/some/deep/nesting/NestingTest.php", 328 | name = "NestingTest", 329 | skipped = "0", 330 | tests = "1", 331 | time = "0.000025", 332 | warnings = "0", 333 | }, 334 | testcase = { 335 | _attr = { 336 | assertions = "1", 337 | class = "NestingTest", 338 | classname = "NestingTest", 339 | file = "/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/some/deep/nesting/NestingTest.php", 340 | line = "7", 341 | name = "test_something_that_is_true", 342 | time = "0.000025", 343 | }, 344 | }, 345 | }, 346 | }, 347 | }, 348 | }, 349 | }, 350 | }, 351 | } 352 | 353 | local expected = { 354 | ["/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php::13"] = { 355 | output_file = "/tmp/nvimhYaIPj/3", 356 | short = [[TESTPROJECT.USERTEST 357 | -> PASSED - testClassConstructor]], 358 | status = "passed", 359 | }, 360 | ["/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php::22"] = { 361 | output_file = "/tmp/nvimhYaIPj/3", 362 | short = [[TESTPROJECT.USERTEST 363 | -> PASSED - testTellName]], 364 | status = "passed", 365 | }, 366 | ["/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php::30"] = { 367 | output_file = "/tmp/nvimhYaIPj/3", 368 | short = [[TESTPROJECT.USERTEST 369 | -> PASSED - testTellAge]], 370 | status = "passed", 371 | }, 372 | ["/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php::38"] = { 373 | output_file = "/tmp/nvimhYaIPj/3", 374 | short = [[TESTPROJECT.USERTEST 375 | -> PASSED - testAddFavoriteMovie]], 376 | status = "passed", 377 | }, 378 | ["/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/UserTest.php::47"] = { 379 | output_file = "/tmp/nvimhYaIPj/3", 380 | short = [[TESTPROJECT.USERTEST 381 | -> PASSED - testRemoveFavoriteMovie]], 382 | status = "passed", 383 | }, 384 | ["/Users/Oli/Code/Projects/neotest-phpunit/tests/Examples/some/deep/nesting/NestingTest.php::7"] = { 385 | output_file = "/tmp/nvimhYaIPj/3", 386 | short = [[NESTINGTEST 387 | -> PASSED - test_something_that_is_true]], 388 | status = "passed", 389 | }, 390 | ["/Users/Oli/Code/Projects/neotest-phpunit/tests/Unit/ExampleTest.php::13"] = { 391 | errors = { 392 | { 393 | line = "13", 394 | }, 395 | }, 396 | output_file = "/tmp/nvimhYaIPj/3", 397 | status = "failed", 398 | }, 399 | ["/Users/Oli/Code/Projects/neotest-phpunit/tests/Unit/ExampleTest.php::7"] = { 400 | output_file = "/tmp/nvimhYaIPj/3", 401 | short = [[EXAMPLETEST 402 | -> PASSED - test_that_true_is_true]], 403 | status = "passed", 404 | }, 405 | } 406 | 407 | assert.are.same(utils.get_test_results(xml_output, output_file), expected) 408 | end) 409 | end) 410 | -------------------------------------------------------------------------------- /src/User.php: -------------------------------------------------------------------------------- 1 | age = $age; 20 | $this->name = $name; 21 | } 22 | 23 | public function tellName(): string 24 | { 25 | return "My name is " . $this->name . "."; 26 | } 27 | 28 | public function tellAge(): string 29 | { 30 | return "I am " . $this->age . " years old."; 31 | } 32 | 33 | public function addFavoriteMovie(string $movie): bool 34 | { 35 | $this->favorite_movies[] = $movie; 36 | 37 | return true; 38 | } 39 | 40 | public function removeFavoriteMovie(string $movie): bool 41 | { 42 | if (!in_array($movie, $this->favorite_movies)) throw new InvalidArgumentException("Unknown movie: " . $movie); 43 | 44 | unset($this->favorite_movies[array_search($movie, $this->favorite_movies)]); 45 | 46 | return true; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/autoload.php: -------------------------------------------------------------------------------- 1 | assertSame('John', $user->name); 18 | $this->assertSame(18, $user->age); 19 | $this->assertEmpty($user->favorite_movies); 20 | } 21 | 22 | public function testTellName() 23 | { 24 | $user = new User(18, 'John'); 25 | 26 | $this->assertIsString($user->tellName()); 27 | $this->assertStringContainsStringIgnoringCase('John', $user->tellName()); 28 | } 29 | 30 | public function testTellAge() 31 | { 32 | $user = new User(18, 'John'); 33 | 34 | $this->assertIsString($user->tellAge()); 35 | $this->assertStringContainsStringIgnoringCase('18', $user->tellAge()); 36 | } 37 | 38 | public function testAddFavoriteMovie() 39 | { 40 | $user = new User(18, 'John'); 41 | 42 | $this->assertTrue($user->addFavoriteMovie('Avengers')); 43 | $this->assertContains('Avengers', $user->favorite_movies); 44 | $this->assertCount(1, $user->favorite_movies); 45 | } 46 | 47 | public function testRemoveFavoriteMovie() 48 | { 49 | $user = new User(18, 'John'); 50 | 51 | $this->assertTrue($user->addFavoriteMovie('Avengers')); 52 | $this->assertTrue($user->addFavoriteMovie('Justice League')); 53 | 54 | $this->assertTrue($user->removeFavoriteMovie('Avengers')); 55 | $this->assertNotContains('Avengers', $user->favorite_movies); 56 | $this->assertCount(1, $user->favorite_movies); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Examples/some/deep/nesting/NestingTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tests/Unit/AttributeTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/Unit/ExampleTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 10 | } 11 | 12 | public function test_that_true_is_true_again() 13 | { 14 | $this->assertTrue(true); 15 | } 16 | 17 | /** @test */ 18 | public function test_this_should_fail() 19 | { 20 | $this->assertTrue(false); 21 | } 22 | 23 | /** @test */ 24 | public function test_this_should_fail_also() 25 | { 26 | $this->assertTrue(false); 27 | } 28 | 29 | /** @test */ 30 | public function this_should_fail_as_well() 31 | { 32 | $this->assertTrue(false); 33 | } 34 | 35 | public function this_should_not_run() 36 | { 37 | $this->assertTrue(true); 38 | } 39 | 40 | /** 41 | * @dataProvider myProvider 42 | */ 43 | public function testWithDataProvider(bool $val): void 44 | { 45 | $this->assertTrue($val); 46 | } 47 | 48 | public function myProvider(): array 49 | { 50 | return [ 51 | 'set one' => [true], 52 | 'set two' => [false], 53 | ]; 54 | } 55 | } 56 | --------------------------------------------------------------------------------