Initial commit
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
|||||||
|
# phpstorm project files
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# netbeans project files
|
||||||
|
nbproject
|
||||||
|
|
||||||
|
# zend studio for eclipse project files
|
||||||
|
.buildpath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
|
||||||
|
# windows thumbnail cache
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Mac DS_Store Files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# phpunit itself is not needed
|
||||||
|
phpunit.phar
|
||||||
|
# local phpunit config
|
||||||
|
/phpunit.xml
|
||||||
|
|
||||||
|
tests/_output/*
|
||||||
|
tests/_support/_generated
|
||||||
|
|
||||||
|
#vagrant folder
|
||||||
|
/.vagrant
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com)
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions
|
||||||
|
are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* 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.
|
||||||
|
* Neither the name of Yii Software LLC nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
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 OWNER 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.
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://github.com/yiisoft" target="_blank">
|
||||||
|
<img src="https://avatars0.githubusercontent.com/u/993323" height="100px">
|
||||||
|
</a>
|
||||||
|
<h1 align="center">Yii 2 Basic Project Template</h1>
|
||||||
|
<br>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
Yii 2 Basic Project Template is a skeleton [Yii 2](http://www.yiiframework.com/) application best for
|
||||||
|
rapidly creating small projects.
|
||||||
|
|
||||||
|
The template contains the basic features including user login/logout and a contact page.
|
||||||
|
It includes all commonly used configurations that would allow you to focus on adding new
|
||||||
|
features to your application.
|
||||||
|
|
||||||
|
[](https://packagist.org/packages/yiisoft/yii2-app-basic)
|
||||||
|
[](https://packagist.org/packages/yiisoft/yii2-app-basic)
|
||||||
|
[](https://github.com/yiisoft/yii2-app-basic/actions?query=workflow%3Abuild)
|
||||||
|
|
||||||
|
DIRECTORY STRUCTURE
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
assets/ contains assets definition
|
||||||
|
commands/ contains console commands (controllers)
|
||||||
|
config/ contains application configurations
|
||||||
|
controllers/ contains Web controller classes
|
||||||
|
mail/ contains view files for e-mails
|
||||||
|
models/ contains model classes
|
||||||
|
runtime/ contains files generated during runtime
|
||||||
|
tests/ contains various tests for the basic application
|
||||||
|
vendor/ contains dependent 3rd-party packages
|
||||||
|
views/ contains view files for the Web application
|
||||||
|
web/ contains the entry script and Web resources
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
REQUIREMENTS
|
||||||
|
------------
|
||||||
|
|
||||||
|
The minimum requirement by this project template that your Web server supports PHP 7.4.
|
||||||
|
|
||||||
|
|
||||||
|
INSTALLATION
|
||||||
|
------------
|
||||||
|
|
||||||
|
### Install via Composer
|
||||||
|
|
||||||
|
If you do not have [Composer](http://getcomposer.org/), you may install it by following the instructions
|
||||||
|
at [getcomposer.org](http://getcomposer.org/doc/00-intro.md#installation-nix).
|
||||||
|
|
||||||
|
You can then install this project template using the following command:
|
||||||
|
|
||||||
|
~~~
|
||||||
|
composer create-project --prefer-dist yiisoft/yii2-app-basic basic
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Now you should be able to access the application through the following URL, assuming `basic` is the directory
|
||||||
|
directly under the Web root.
|
||||||
|
|
||||||
|
~~~
|
||||||
|
http://localhost/basic/web/
|
||||||
|
~~~
|
||||||
|
|
||||||
|
### Install from an Archive File
|
||||||
|
|
||||||
|
Extract the archive file downloaded from [yiiframework.com](http://www.yiiframework.com/download/) to
|
||||||
|
a directory named `basic` that is directly under the Web root.
|
||||||
|
|
||||||
|
Set cookie validation key in `config/web.php` file to some random secret string:
|
||||||
|
|
||||||
|
```php
|
||||||
|
'request' => [
|
||||||
|
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
|
||||||
|
'cookieValidationKey' => '<secret random string goes here>',
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
You can then access the application through the following URL:
|
||||||
|
|
||||||
|
~~~
|
||||||
|
http://localhost/basic/web/
|
||||||
|
~~~
|
||||||
|
|
||||||
|
|
||||||
|
### Install with Docker
|
||||||
|
|
||||||
|
Update your vendor packages
|
||||||
|
|
||||||
|
docker-compose run --rm php composer update --prefer-dist
|
||||||
|
|
||||||
|
Run the installation triggers (creating cookie validation code)
|
||||||
|
|
||||||
|
docker-compose run --rm php composer install
|
||||||
|
|
||||||
|
Start the container
|
||||||
|
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
You can then access the application through the following URL:
|
||||||
|
|
||||||
|
http://127.0.0.1:8000
|
||||||
|
|
||||||
|
**NOTES:**
|
||||||
|
- Minimum required Docker engine version `17.04` for development (see [Performance tuning for volume mounts](https://docs.docker.com/docker-for-mac/osxfs-caching/))
|
||||||
|
- The default configuration uses a host-volume in your home directory `.docker-composer` for composer caches
|
||||||
|
|
||||||
|
|
||||||
|
CONFIGURATION
|
||||||
|
-------------
|
||||||
|
|
||||||
|
### Database
|
||||||
|
|
||||||
|
Edit the file `config/db.php` with real data, for example:
|
||||||
|
|
||||||
|
```php
|
||||||
|
return [
|
||||||
|
'class' => 'yii\db\Connection',
|
||||||
|
'dsn' => 'mysql:host=localhost;dbname=yii2basic',
|
||||||
|
'username' => 'root',
|
||||||
|
'password' => '1234',
|
||||||
|
'charset' => 'utf8',
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
**NOTES:**
|
||||||
|
- Yii won't create the database for you, this has to be done manually before you can access it.
|
||||||
|
- Check and edit the other files in the `config/` directory to customize your application as required.
|
||||||
|
- Refer to the README in the `tests` directory for information specific to basic application tests.
|
||||||
|
|
||||||
|
|
||||||
|
TESTING
|
||||||
|
-------
|
||||||
|
|
||||||
|
Tests are located in `tests` directory. They are developed with [Codeception PHP Testing Framework](http://codeception.com/).
|
||||||
|
By default, there are 3 test suites:
|
||||||
|
|
||||||
|
- `unit`
|
||||||
|
- `functional`
|
||||||
|
- `acceptance`
|
||||||
|
|
||||||
|
Tests can be executed by running
|
||||||
|
|
||||||
|
```
|
||||||
|
vendor/bin/codecept run
|
||||||
|
```
|
||||||
|
|
||||||
|
The command above will execute unit and functional tests. Unit tests are testing the system components, while functional
|
||||||
|
tests are for testing user interaction. Acceptance tests are disabled by default as they require additional setup since
|
||||||
|
they perform testing in real browser.
|
||||||
|
|
||||||
|
|
||||||
|
### Running acceptance tests
|
||||||
|
|
||||||
|
To execute acceptance tests do the following:
|
||||||
|
|
||||||
|
1. Rename `tests/acceptance.suite.yml.example` to `tests/acceptance.suite.yml` to enable suite configuration
|
||||||
|
|
||||||
|
2. Replace `codeception/base` package in `composer.json` with `codeception/codeception` to install full-featured
|
||||||
|
version of Codeception
|
||||||
|
|
||||||
|
3. Update dependencies with Composer
|
||||||
|
|
||||||
|
```
|
||||||
|
composer update
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Download [Selenium Server](http://www.seleniumhq.org/download/) and launch it:
|
||||||
|
|
||||||
|
```
|
||||||
|
java -jar ~/selenium-server-standalone-x.xx.x.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
In case of using Selenium Server 3.0 with Firefox browser since v48 or Google Chrome since v53 you must download [GeckoDriver](https://github.com/mozilla/geckodriver/releases) or [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/downloads) and launch Selenium with it:
|
||||||
|
|
||||||
|
```
|
||||||
|
# for Firefox
|
||||||
|
java -jar -Dwebdriver.gecko.driver=~/geckodriver ~/selenium-server-standalone-3.xx.x.jar
|
||||||
|
|
||||||
|
# for Google Chrome
|
||||||
|
java -jar -Dwebdriver.chrome.driver=~/chromedriver ~/selenium-server-standalone-3.xx.x.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
As an alternative way you can use already configured Docker container with older versions of Selenium and Firefox:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --net=host selenium/standalone-firefox:2.53.0
|
||||||
|
```
|
||||||
|
|
||||||
|
5. (Optional) Create `yii2basic_test` database and update it by applying migrations if you have them.
|
||||||
|
|
||||||
|
```
|
||||||
|
tests/bin/yii migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
The database configuration can be found at `config/test_db.php`.
|
||||||
|
|
||||||
|
|
||||||
|
6. Start web server:
|
||||||
|
|
||||||
|
```
|
||||||
|
tests/bin/yii serve
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Now you can run all available tests
|
||||||
|
|
||||||
|
```
|
||||||
|
# run all available tests
|
||||||
|
vendor/bin/codecept run
|
||||||
|
|
||||||
|
# run acceptance tests
|
||||||
|
vendor/bin/codecept run acceptance
|
||||||
|
|
||||||
|
# run only unit and functional tests
|
||||||
|
vendor/bin/codecept run unit,functional
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code coverage support
|
||||||
|
|
||||||
|
By default, code coverage is disabled in `codeception.yml` configuration file, you should uncomment needed rows to be able
|
||||||
|
to collect code coverage. You can run your tests and collect coverage with the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
#collect coverage for all tests
|
||||||
|
vendor/bin/codecept run --coverage --coverage-html --coverage-xml
|
||||||
|
|
||||||
|
#collect coverage only for unit tests
|
||||||
|
vendor/bin/codecept run unit --coverage --coverage-html --coverage-xml
|
||||||
|
|
||||||
|
#collect coverage for unit and functional tests
|
||||||
|
vendor/bin/codecept run functional,unit --coverage --coverage-html --coverage-xml
|
||||||
|
```
|
||||||
|
|
||||||
|
You can see code coverage output under the `tests/_output` directory.
|
||||||
Vendored
+92
@@ -0,0 +1,92 @@
|
|||||||
|
require 'yaml'
|
||||||
|
require 'fileutils'
|
||||||
|
|
||||||
|
required_plugins_installed = nil
|
||||||
|
required_plugins = %w( vagrant-hostmanager vagrant-vbguest )
|
||||||
|
required_plugins.each do |plugin|
|
||||||
|
unless Vagrant.has_plugin? plugin
|
||||||
|
system "vagrant plugin install #{plugin}"
|
||||||
|
required_plugins_installed = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# IF plugin[s] was just installed - restart required
|
||||||
|
if required_plugins_installed
|
||||||
|
# Get CLI command[s] and call again
|
||||||
|
system 'vagrant' + ARGV.to_s.gsub(/\[\"|\", \"|\"\]/, ' ')
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
|
||||||
|
domains = {
|
||||||
|
app: 'yii2basic.test'
|
||||||
|
}
|
||||||
|
|
||||||
|
vagrantfile_dir_path = File.dirname(__FILE__)
|
||||||
|
|
||||||
|
config = {
|
||||||
|
local: vagrantfile_dir_path + '/vagrant/config/vagrant-local.yml',
|
||||||
|
example: vagrantfile_dir_path + '/vagrant/config/vagrant-local.example.yml'
|
||||||
|
}
|
||||||
|
|
||||||
|
# copy config from example if local config not exists
|
||||||
|
FileUtils.cp config[:example], config[:local] unless File.exist?(config[:local])
|
||||||
|
# read config
|
||||||
|
options = YAML.load_file config[:local]
|
||||||
|
|
||||||
|
# check github token
|
||||||
|
if options['github_token'].nil? || options['github_token'].to_s.length != 40
|
||||||
|
puts "You must place REAL GitHub token into configuration:\n/yii2-app-basic/vagrant/config/vagrant-local.yml"
|
||||||
|
exit
|
||||||
|
end
|
||||||
|
|
||||||
|
# vagrant configurate
|
||||||
|
Vagrant.configure(2) do |config|
|
||||||
|
# select the box
|
||||||
|
config.vm.box = 'bento/ubuntu-18.04'
|
||||||
|
|
||||||
|
# should we ask about box updates?
|
||||||
|
config.vm.box_check_update = options['box_check_update']
|
||||||
|
|
||||||
|
config.vm.provider 'virtualbox' do |vb|
|
||||||
|
# machine cpus count
|
||||||
|
vb.cpus = options['cpus']
|
||||||
|
# machine memory size
|
||||||
|
vb.memory = options['memory']
|
||||||
|
# machine name (for VirtualBox UI)
|
||||||
|
vb.name = options['machine_name']
|
||||||
|
end
|
||||||
|
|
||||||
|
# machine name (for vagrant console)
|
||||||
|
config.vm.define options['machine_name']
|
||||||
|
|
||||||
|
# machine name (for guest machine console)
|
||||||
|
config.vm.hostname = options['machine_name']
|
||||||
|
|
||||||
|
# network settings
|
||||||
|
config.vm.network 'private_network', ip: options['ip']
|
||||||
|
|
||||||
|
# sync: folder 'yii2-app-advanced' (host machine) -> folder '/app' (guest machine)
|
||||||
|
config.vm.synced_folder './', '/app', owner: 'vagrant', group: 'vagrant'
|
||||||
|
|
||||||
|
# disable folder '/vagrant' (guest machine)
|
||||||
|
config.vm.synced_folder '.', '/vagrant', disabled: true
|
||||||
|
|
||||||
|
# hosts settings (host machine)
|
||||||
|
config.vm.provision :hostmanager
|
||||||
|
config.hostmanager.enabled = true
|
||||||
|
config.hostmanager.manage_host = true
|
||||||
|
config.hostmanager.ignore_private_ip = false
|
||||||
|
config.hostmanager.include_offline = true
|
||||||
|
config.hostmanager.aliases = domains.values
|
||||||
|
|
||||||
|
# quick fix for failed guest additions installations
|
||||||
|
# config.vbguest.auto_update = false
|
||||||
|
|
||||||
|
# provisioners
|
||||||
|
config.vm.provision 'shell', path: './vagrant/provision/once-as-root.sh', args: [options['timezone'], options['ip']]
|
||||||
|
config.vm.provision 'shell', path: './vagrant/provision/once-as-vagrant.sh', args: [options['github_token']], privileged: false
|
||||||
|
config.vm.provision 'shell', path: './vagrant/provision/always-as-root.sh', run: 'always'
|
||||||
|
|
||||||
|
# post-install message (vagrant console)
|
||||||
|
config.vm.post_up_message = "App URL: http://#{domains[:app]}"
|
||||||
|
end
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @link http://www.yiiframework.com/
|
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||||
|
* @license http://www.yiiframework.com/license/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace app\assets;
|
||||||
|
|
||||||
|
use yii\web\AssetBundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main application asset bundle.
|
||||||
|
*
|
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
class AppAsset extends AssetBundle
|
||||||
|
{
|
||||||
|
public $basePath = '@webroot';
|
||||||
|
public $baseUrl = '@web';
|
||||||
|
public $css = [
|
||||||
|
'css/site.css',
|
||||||
|
];
|
||||||
|
public $js = [
|
||||||
|
];
|
||||||
|
public $depends = [
|
||||||
|
'yii\web\YiiAsset',
|
||||||
|
'yii\bootstrap5\BootstrapAsset'
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
actor: Tester
|
||||||
|
bootstrap: _bootstrap.php
|
||||||
|
paths:
|
||||||
|
tests: tests
|
||||||
|
output: tests/_output
|
||||||
|
data: tests/_data
|
||||||
|
helpers: tests/_support
|
||||||
|
settings:
|
||||||
|
memory_limit: 1024M
|
||||||
|
colors: true
|
||||||
|
modules:
|
||||||
|
config:
|
||||||
|
Yii2:
|
||||||
|
configFile: 'config/test.php'
|
||||||
|
|
||||||
|
# To enable code coverage:
|
||||||
|
#coverage:
|
||||||
|
# #c3_url: http://localhost:8080/index-test.php/
|
||||||
|
# enabled: true
|
||||||
|
# #remote: true
|
||||||
|
# #remote_config: '../codeception.yml'
|
||||||
|
# whitelist:
|
||||||
|
# include:
|
||||||
|
# - models/*
|
||||||
|
# - controllers/*
|
||||||
|
# - commands/*
|
||||||
|
# - mail/*
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @link http://www.yiiframework.com/
|
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||||
|
* @license http://www.yiiframework.com/license/
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace app\commands;
|
||||||
|
|
||||||
|
use yii\console\Controller;
|
||||||
|
use yii\console\ExitCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This command echoes the first argument that you have entered.
|
||||||
|
*
|
||||||
|
* This command is provided as an example for you to learn how to create console commands.
|
||||||
|
*
|
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
class HelloController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* This command echoes what you have entered as the message.
|
||||||
|
* @param string $message the message to be echoed.
|
||||||
|
* @return int Exit code
|
||||||
|
*/
|
||||||
|
public function actionIndex($message = 'hello world')
|
||||||
|
{
|
||||||
|
echo $message . "\n";
|
||||||
|
|
||||||
|
return ExitCode::OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
{
|
||||||
|
"name": "yiisoft/yii2-app-basic",
|
||||||
|
"description": "Yii 2 Basic Project Template",
|
||||||
|
"keywords": ["yii2", "framework", "basic", "project template"],
|
||||||
|
"homepage": "https://www.yiiframework.com/",
|
||||||
|
"type": "project",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/yiisoft/yii2/issues?state=open",
|
||||||
|
"forum": "https://www.yiiframework.com/forum/",
|
||||||
|
"wiki": "https://www.yiiframework.com/wiki/",
|
||||||
|
"irc": "irc://irc.freenode.net/yii",
|
||||||
|
"source": "https://github.com/yiisoft/yii2"
|
||||||
|
},
|
||||||
|
"minimum-stability": "stable",
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.4.0",
|
||||||
|
"yiisoft/yii2": "~2.0.45",
|
||||||
|
"yiisoft/yii2-bootstrap5": "~2.0.2",
|
||||||
|
"yiisoft/yii2-symfonymailer": "~2.0.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"yiisoft/yii2-debug": "~2.1.0",
|
||||||
|
"yiisoft/yii2-gii": "~2.2.0",
|
||||||
|
"yiisoft/yii2-faker": "~2.0.0",
|
||||||
|
"phpunit/phpunit": "~9.5.0",
|
||||||
|
"codeception/codeception": "^5.0.0 || ^4.0",
|
||||||
|
"codeception/lib-innerbrowser": "^3.0 || ^1.1",
|
||||||
|
"codeception/module-asserts": "^3.0 || ^1.1",
|
||||||
|
"codeception/module-yii2": "^1.1",
|
||||||
|
"codeception/module-filesystem": "^2.0 || ^1.1",
|
||||||
|
"codeception/verify": "^2.2",
|
||||||
|
"symfony/browser-kit": "^6.0 || >=2.7 <=4.2.4"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"allow-plugins": {
|
||||||
|
"yiisoft/yii2-composer" : true
|
||||||
|
},
|
||||||
|
"process-timeout": 1800,
|
||||||
|
"fxp-asset": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"post-install-cmd": [
|
||||||
|
"yii\\composer\\Installer::postInstall"
|
||||||
|
],
|
||||||
|
"post-create-project-cmd": [
|
||||||
|
"yii\\composer\\Installer::postCreateProject",
|
||||||
|
"yii\\composer\\Installer::postInstall"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"yii\\composer\\Installer::postCreateProject": {
|
||||||
|
"setPermission": [
|
||||||
|
{
|
||||||
|
"runtime": "0777",
|
||||||
|
"web/assets": "0777",
|
||||||
|
"yii": "0755"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"yii\\composer\\Installer::postInstall": {
|
||||||
|
"generateCookieValidationKey": [
|
||||||
|
"config/web.php"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "composer",
|
||||||
|
"url": "https://asset-packagist.org"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Generated
+5571
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class only exists here for IDE (PHPStorm/Netbeans/...) autocompletion.
|
||||||
|
* This file is never included anywhere.
|
||||||
|
* Adjust this file to match classes configured in your application config, to enable IDE autocompletion for custom components.
|
||||||
|
* Example: A property phpdoc can be added in `__Application` class as `@property \vendor\package\Rollbar|__Rollbar $rollbar` and adding a class in this file
|
||||||
|
* ```php
|
||||||
|
* // @property of \vendor\package\Rollbar goes here
|
||||||
|
* class __Rollbar {
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
class Yii {
|
||||||
|
/**
|
||||||
|
* @var \yii\web\Application|\yii\console\Application|__Application
|
||||||
|
*/
|
||||||
|
public static $app;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property yii\rbac\DbManager $authManager
|
||||||
|
* @property \yii\web\User|__WebUser $user
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class __Application {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property app\models\User $identity
|
||||||
|
*/
|
||||||
|
class __WebUser {
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$params = require __DIR__ . '/params.php';
|
||||||
|
$db = require __DIR__ . '/db.php';
|
||||||
|
|
||||||
|
$config = [
|
||||||
|
'id' => 'basic-console',
|
||||||
|
'basePath' => dirname(__DIR__),
|
||||||
|
'bootstrap' => ['log'],
|
||||||
|
'controllerNamespace' => 'app\commands',
|
||||||
|
'aliases' => [
|
||||||
|
'@bower' => '@vendor/bower-asset',
|
||||||
|
'@npm' => '@vendor/npm-asset',
|
||||||
|
'@tests' => '@app/tests',
|
||||||
|
],
|
||||||
|
'components' => [
|
||||||
|
'cache' => [
|
||||||
|
'class' => 'yii\caching\FileCache',
|
||||||
|
],
|
||||||
|
'log' => [
|
||||||
|
'targets' => [
|
||||||
|
[
|
||||||
|
'class' => 'yii\log\FileTarget',
|
||||||
|
'levels' => ['error', 'warning'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'db' => $db,
|
||||||
|
],
|
||||||
|
'params' => $params,
|
||||||
|
/*
|
||||||
|
'controllerMap' => [
|
||||||
|
'fixture' => [ // Fixture generation command line.
|
||||||
|
'class' => 'yii\faker\FixtureController',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
*/
|
||||||
|
];
|
||||||
|
|
||||||
|
if (YII_ENV_DEV) {
|
||||||
|
// configuration adjustments for 'dev' environment
|
||||||
|
$config['bootstrap'][] = 'gii';
|
||||||
|
$config['modules']['gii'] = [
|
||||||
|
'class' => 'yii\gii\Module',
|
||||||
|
];
|
||||||
|
// configuration adjustments for 'dev' environment
|
||||||
|
// requires version `2.1.21` of yii2-debug module
|
||||||
|
$config['bootstrap'][] = 'debug';
|
||||||
|
$config['modules']['debug'] = [
|
||||||
|
'class' => 'yii\debug\Module',
|
||||||
|
// uncomment the following to add your IP if you are not connecting from localhost.
|
||||||
|
//'allowedIPs' => ['127.0.0.1', '::1'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $config;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'class' => 'yii\db\Connection',
|
||||||
|
'dsn' => 'mysql:host=localhost;dbname=yii2basic',
|
||||||
|
'username' => 'root',
|
||||||
|
'password' => '',
|
||||||
|
'charset' => 'utf8',
|
||||||
|
|
||||||
|
// Schema cache options (for production environment)
|
||||||
|
//'enableSchemaCache' => true,
|
||||||
|
//'schemaCacheDuration' => 60,
|
||||||
|
//'schemaCache' => 'cache',
|
||||||
|
];
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'adminEmail' => 'admin@example.com',
|
||||||
|
'senderEmail' => 'noreply@example.com',
|
||||||
|
'senderName' => 'Example.com mailer',
|
||||||
|
];
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
$params = require __DIR__ . '/params.php';
|
||||||
|
$db = require __DIR__ . '/test_db.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application configuration shared by all test types
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'id' => 'basic-tests',
|
||||||
|
'basePath' => dirname(__DIR__),
|
||||||
|
'aliases' => [
|
||||||
|
'@bower' => '@vendor/bower-asset',
|
||||||
|
'@npm' => '@vendor/npm-asset',
|
||||||
|
],
|
||||||
|
'language' => 'en-US',
|
||||||
|
'components' => [
|
||||||
|
'db' => $db,
|
||||||
|
'mailer' => [
|
||||||
|
'class' => \yii\symfonymailer\Mailer::class,
|
||||||
|
'viewPath' => '@app/mail',
|
||||||
|
// send all mails to a file by default.
|
||||||
|
'useFileTransport' => true,
|
||||||
|
'messageClass' => 'yii\symfonymailer\Message'
|
||||||
|
],
|
||||||
|
'assetManager' => [
|
||||||
|
'basePath' => __DIR__ . '/../web/assets',
|
||||||
|
],
|
||||||
|
'urlManager' => [
|
||||||
|
'showScriptName' => true,
|
||||||
|
],
|
||||||
|
'user' => [
|
||||||
|
'identityClass' => 'app\models\User',
|
||||||
|
],
|
||||||
|
'request' => [
|
||||||
|
'cookieValidationKey' => 'test',
|
||||||
|
'enableCsrfValidation' => false,
|
||||||
|
// but if you absolutely need it set cookie domain to localhost
|
||||||
|
/*
|
||||||
|
'csrfCookie' => [
|
||||||
|
'domain' => 'localhost',
|
||||||
|
],
|
||||||
|
*/
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'params' => $params,
|
||||||
|
];
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
$db = require __DIR__ . '/db.php';
|
||||||
|
// test database! Important not to run tests on production or development databases
|
||||||
|
$db['dsn'] = 'mysql:host=localhost;dbname=yii2basic_test';
|
||||||
|
|
||||||
|
return $db;
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$params = require __DIR__ . '/params.php';
|
||||||
|
$db = require __DIR__ . '/db.php';
|
||||||
|
|
||||||
|
$config = [
|
||||||
|
'id' => 'basic',
|
||||||
|
'basePath' => dirname(__DIR__),
|
||||||
|
'bootstrap' => ['log'],
|
||||||
|
'aliases' => [
|
||||||
|
'@bower' => '@vendor/bower-asset',
|
||||||
|
'@npm' => '@vendor/npm-asset',
|
||||||
|
],
|
||||||
|
'components' => [
|
||||||
|
'request' => [
|
||||||
|
// !!! insert a secret key in the following (if it is empty) - this is required by cookie validation
|
||||||
|
'cookieValidationKey' => 'F-CcLGsK_NTOnIr4hPNRSqYaftrDkNt8',
|
||||||
|
],
|
||||||
|
'cache' => [
|
||||||
|
'class' => 'yii\caching\FileCache',
|
||||||
|
],
|
||||||
|
'user' => [
|
||||||
|
'identityClass' => 'app\models\User',
|
||||||
|
'enableAutoLogin' => true,
|
||||||
|
],
|
||||||
|
'errorHandler' => [
|
||||||
|
'errorAction' => 'site/error',
|
||||||
|
],
|
||||||
|
'mailer' => [
|
||||||
|
'class' => \yii\symfonymailer\Mailer::class,
|
||||||
|
'viewPath' => '@app/mail',
|
||||||
|
// send all mails to a file by default.
|
||||||
|
'useFileTransport' => true,
|
||||||
|
],
|
||||||
|
'log' => [
|
||||||
|
'traceLevel' => YII_DEBUG ? 3 : 0,
|
||||||
|
'targets' => [
|
||||||
|
[
|
||||||
|
'class' => 'yii\log\FileTarget',
|
||||||
|
'levels' => ['error', 'warning'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'db' => $db,
|
||||||
|
/*
|
||||||
|
'urlManager' => [
|
||||||
|
'enablePrettyUrl' => true,
|
||||||
|
'showScriptName' => false,
|
||||||
|
'rules' => [
|
||||||
|
],
|
||||||
|
],
|
||||||
|
*/
|
||||||
|
],
|
||||||
|
'params' => $params,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (YII_ENV_DEV) {
|
||||||
|
// configuration adjustments for 'dev' environment
|
||||||
|
$config['bootstrap'][] = 'debug';
|
||||||
|
$config['modules']['debug'] = [
|
||||||
|
'class' => 'yii\debug\Module',
|
||||||
|
// uncomment the following to add your IP if you are not connecting from localhost.
|
||||||
|
//'allowedIPs' => ['127.0.0.1', '::1'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$config['bootstrap'][] = 'gii';
|
||||||
|
$config['modules']['gii'] = [
|
||||||
|
'class' => 'yii\gii\Module',
|
||||||
|
// uncomment the following to add your IP if you are not connecting from localhost.
|
||||||
|
//'allowedIPs' => ['127.0.0.1', '::1'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $config;
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\controllers;
|
||||||
|
|
||||||
|
use Yii;
|
||||||
|
use yii\filters\AccessControl;
|
||||||
|
use yii\web\Controller;
|
||||||
|
use yii\web\Response;
|
||||||
|
use yii\filters\VerbFilter;
|
||||||
|
use app\models\LoginForm;
|
||||||
|
use app\models\ContactForm;
|
||||||
|
|
||||||
|
class SiteController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function behaviors()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'access' => [
|
||||||
|
'class' => AccessControl::class,
|
||||||
|
'only' => ['logout'],
|
||||||
|
'rules' => [
|
||||||
|
[
|
||||||
|
'actions' => ['logout'],
|
||||||
|
'allow' => true,
|
||||||
|
'roles' => ['@'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'verbs' => [
|
||||||
|
'class' => VerbFilter::class,
|
||||||
|
'actions' => [
|
||||||
|
'logout' => ['post'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function actions()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'error' => [
|
||||||
|
'class' => 'yii\web\ErrorAction',
|
||||||
|
],
|
||||||
|
'captcha' => [
|
||||||
|
'class' => 'yii\captcha\CaptchaAction',
|
||||||
|
'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays homepage.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function actionIndex()
|
||||||
|
{
|
||||||
|
return $this->render('index');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login action.
|
||||||
|
*
|
||||||
|
* @return Response|string
|
||||||
|
*/
|
||||||
|
public function actionLogin()
|
||||||
|
{
|
||||||
|
if (!Yii::$app->user->isGuest) {
|
||||||
|
return $this->goHome();
|
||||||
|
}
|
||||||
|
|
||||||
|
$model = new LoginForm();
|
||||||
|
if ($model->load(Yii::$app->request->post()) && $model->login()) {
|
||||||
|
return $this->goBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
$model->password = '';
|
||||||
|
return $this->render('login', [
|
||||||
|
'model' => $model,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout action.
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function actionLogout()
|
||||||
|
{
|
||||||
|
Yii::$app->user->logout();
|
||||||
|
|
||||||
|
return $this->goHome();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays contact page.
|
||||||
|
*
|
||||||
|
* @return Response|string
|
||||||
|
*/
|
||||||
|
public function actionContact()
|
||||||
|
{
|
||||||
|
$model = new ContactForm();
|
||||||
|
if ($model->load(Yii::$app->request->post()) && $model->contact(Yii::$app->params['adminEmail'])) {
|
||||||
|
Yii::$app->session->setFlash('contactFormSubmitted');
|
||||||
|
|
||||||
|
return $this->refresh();
|
||||||
|
}
|
||||||
|
return $this->render('contact', [
|
||||||
|
'model' => $model,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays about page.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function actionAbout()
|
||||||
|
{
|
||||||
|
return $this->render('about');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
php:
|
||||||
|
image: yiisoftware/yii2-php:7.4-apache
|
||||||
|
volumes:
|
||||||
|
- ~/.composer-docker/cache:/root/.composer/cache:delegated
|
||||||
|
- ./:/app:delegated
|
||||||
|
ports:
|
||||||
|
- '8000:80'
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
use yii\helpers\Html;
|
||||||
|
|
||||||
|
/** @var \yii\web\View $this view component instance */
|
||||||
|
/** @var \yii\mail\MessageInterface $message the message being composed */
|
||||||
|
/** @var string $content main view render result */
|
||||||
|
?>
|
||||||
|
<?php $this->beginPage() ?>
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=<?= Yii::$app->charset ?>" />
|
||||||
|
<title><?= Html::encode($this->title) ?></title>
|
||||||
|
<?php $this->head() ?>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<?php $this->beginBody() ?>
|
||||||
|
<?= $content ?>
|
||||||
|
<?php $this->endBody() ?>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<?php $this->endPage() ?>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var yii\web\View $this view component instance
|
||||||
|
* @var yii\mail\BaseMessage $message the message being composed
|
||||||
|
* @var string $content main view render result
|
||||||
|
*/
|
||||||
|
|
||||||
|
$this->beginPage();
|
||||||
|
$this->beginBody();
|
||||||
|
echo $content;
|
||||||
|
$this->endBody();
|
||||||
|
$this->endPage();
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\models;
|
||||||
|
|
||||||
|
use Yii;
|
||||||
|
use yii\base\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ContactForm is the model behind the contact form.
|
||||||
|
*/
|
||||||
|
class ContactForm extends Model
|
||||||
|
{
|
||||||
|
public $name;
|
||||||
|
public $email;
|
||||||
|
public $subject;
|
||||||
|
public $body;
|
||||||
|
public $verifyCode;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array the validation rules.
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// name, email, subject and body are required
|
||||||
|
[['name', 'email', 'subject', 'body'], 'required'],
|
||||||
|
// email has to be a valid email address
|
||||||
|
['email', 'email'],
|
||||||
|
// verifyCode needs to be entered correctly
|
||||||
|
['verifyCode', 'captcha'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array customized attribute labels
|
||||||
|
*/
|
||||||
|
public function attributeLabels()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'verifyCode' => 'Verification Code',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an email to the specified email address using the information collected by this model.
|
||||||
|
* @param string $email the target email address
|
||||||
|
* @return bool whether the model passes validation
|
||||||
|
*/
|
||||||
|
public function contact($email)
|
||||||
|
{
|
||||||
|
if ($this->validate()) {
|
||||||
|
Yii::$app->mailer->compose()
|
||||||
|
->setTo($email)
|
||||||
|
->setFrom([Yii::$app->params['senderEmail'] => Yii::$app->params['senderName']])
|
||||||
|
->setReplyTo([$this->email => $this->name])
|
||||||
|
->setSubject($this->subject)
|
||||||
|
->setTextBody($this->body)
|
||||||
|
->send();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\models;
|
||||||
|
|
||||||
|
use Yii;
|
||||||
|
use yii\base\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LoginForm is the model behind the login form.
|
||||||
|
*
|
||||||
|
* @property-read User|null $user
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class LoginForm extends Model
|
||||||
|
{
|
||||||
|
public $username;
|
||||||
|
public $password;
|
||||||
|
public $rememberMe = true;
|
||||||
|
|
||||||
|
private $_user = false;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array the validation rules.
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
// username and password are both required
|
||||||
|
[['username', 'password'], 'required'],
|
||||||
|
// rememberMe must be a boolean value
|
||||||
|
['rememberMe', 'boolean'],
|
||||||
|
// password is validated by validatePassword()
|
||||||
|
['password', 'validatePassword'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the password.
|
||||||
|
* This method serves as the inline validation for password.
|
||||||
|
*
|
||||||
|
* @param string $attribute the attribute currently being validated
|
||||||
|
* @param array $params the additional name-value pairs given in the rule
|
||||||
|
*/
|
||||||
|
public function validatePassword($attribute, $params)
|
||||||
|
{
|
||||||
|
if (!$this->hasErrors()) {
|
||||||
|
$user = $this->getUser();
|
||||||
|
|
||||||
|
if (!$user || !$user->validatePassword($this->password)) {
|
||||||
|
$this->addError($attribute, 'Incorrect username or password.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs in a user using the provided username and password.
|
||||||
|
* @return bool whether the user is logged in successfully
|
||||||
|
*/
|
||||||
|
public function login()
|
||||||
|
{
|
||||||
|
if ($this->validate()) {
|
||||||
|
return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds user by [[username]]
|
||||||
|
*
|
||||||
|
* @return User|null
|
||||||
|
*/
|
||||||
|
public function getUser()
|
||||||
|
{
|
||||||
|
if ($this->_user === false) {
|
||||||
|
$this->_user = User::findByUsername($this->username);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_user;
|
||||||
|
}
|
||||||
|
}
|
||||||
+104
@@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\models;
|
||||||
|
|
||||||
|
class User extends \yii\base\BaseObject implements \yii\web\IdentityInterface
|
||||||
|
{
|
||||||
|
public $id;
|
||||||
|
public $username;
|
||||||
|
public $password;
|
||||||
|
public $authKey;
|
||||||
|
public $accessToken;
|
||||||
|
|
||||||
|
private static $users = [
|
||||||
|
'100' => [
|
||||||
|
'id' => '100',
|
||||||
|
'username' => 'admin',
|
||||||
|
'password' => 'admin',
|
||||||
|
'authKey' => 'test100key',
|
||||||
|
'accessToken' => '100-token',
|
||||||
|
],
|
||||||
|
'101' => [
|
||||||
|
'id' => '101',
|
||||||
|
'username' => 'demo',
|
||||||
|
'password' => 'demo',
|
||||||
|
'authKey' => 'test101key',
|
||||||
|
'accessToken' => '101-token',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function findIdentity($id)
|
||||||
|
{
|
||||||
|
return isset(self::$users[$id]) ? new static(self::$users[$id]) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function findIdentityByAccessToken($token, $type = null)
|
||||||
|
{
|
||||||
|
foreach (self::$users as $user) {
|
||||||
|
if ($user['accessToken'] === $token) {
|
||||||
|
return new static($user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds user by username
|
||||||
|
*
|
||||||
|
* @param string $username
|
||||||
|
* @return static|null
|
||||||
|
*/
|
||||||
|
public static function findByUsername($username)
|
||||||
|
{
|
||||||
|
foreach (self::$users as $user) {
|
||||||
|
if (strcasecmp($user['username'], $username) === 0) {
|
||||||
|
return new static($user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getAuthKey()
|
||||||
|
{
|
||||||
|
return $this->authKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function validateAuthKey($authKey)
|
||||||
|
{
|
||||||
|
return $this->authKey === $authKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates password
|
||||||
|
*
|
||||||
|
* @param string $password password to validate
|
||||||
|
* @return bool if password provided is valid for current user
|
||||||
|
*/
|
||||||
|
public function validatePassword($password)
|
||||||
|
{
|
||||||
|
return $this->password === $password;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Application requirement checker script.
|
||||||
|
*
|
||||||
|
* In order to run this script use the following console command:
|
||||||
|
* php requirements.php
|
||||||
|
*
|
||||||
|
* In order to run this script from the web, you should copy it to the web root.
|
||||||
|
* If you are using Linux you can create a hard link instead, using the following command:
|
||||||
|
* ln ../requirements.php requirements.php
|
||||||
|
*/
|
||||||
|
|
||||||
|
// you may need to adjust this path to the correct Yii framework path
|
||||||
|
// uncomment and adjust the following line if Yii is not located at the default path
|
||||||
|
//$frameworkPath = dirname(__FILE__) . '/vendor/yiisoft/yii2';
|
||||||
|
|
||||||
|
|
||||||
|
if (!isset($frameworkPath)) {
|
||||||
|
$searchPaths = array(
|
||||||
|
dirname(__FILE__) . '/vendor/yiisoft/yii2',
|
||||||
|
dirname(__FILE__) . '/../vendor/yiisoft/yii2',
|
||||||
|
);
|
||||||
|
foreach ($searchPaths as $path) {
|
||||||
|
if (is_dir($path)) {
|
||||||
|
$frameworkPath = $path;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($frameworkPath) || !is_dir($frameworkPath)) {
|
||||||
|
$message = "<h1>Error</h1>\n\n"
|
||||||
|
. "<p><strong>The path to yii framework seems to be incorrect.</strong></p>\n"
|
||||||
|
. '<p>You need to install Yii framework via composer or adjust the framework path in file <abbr title="' . __FILE__ . '">' . basename(__FILE__) . "</abbr>.</p>\n"
|
||||||
|
. '<p>Please refer to the <abbr title="' . dirname(__FILE__) . "/README.md\">README</abbr> on how to install Yii.</p>\n";
|
||||||
|
|
||||||
|
if (!empty($_SERVER['argv'])) {
|
||||||
|
// do not print HTML when used in console mode
|
||||||
|
echo strip_tags($message);
|
||||||
|
} else {
|
||||||
|
echo $message;
|
||||||
|
}
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once($frameworkPath . '/requirements/YiiRequirementChecker.php');
|
||||||
|
$requirementsChecker = new YiiRequirementChecker();
|
||||||
|
|
||||||
|
$gdMemo = $imagickMemo = 'Either GD PHP extension with FreeType support or ImageMagick PHP extension with PNG support is required for image CAPTCHA.';
|
||||||
|
$gdOK = $imagickOK = false;
|
||||||
|
|
||||||
|
if (extension_loaded('imagick')) {
|
||||||
|
$imagick = new Imagick();
|
||||||
|
$imagickFormats = $imagick->queryFormats('PNG');
|
||||||
|
if (in_array('PNG', $imagickFormats)) {
|
||||||
|
$imagickOK = true;
|
||||||
|
} else {
|
||||||
|
$imagickMemo = 'Imagick extension should be installed with PNG support in order to be used for image CAPTCHA.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extension_loaded('gd')) {
|
||||||
|
$gdInfo = gd_info();
|
||||||
|
if (!empty($gdInfo['FreeType Support'])) {
|
||||||
|
$gdOK = true;
|
||||||
|
} else {
|
||||||
|
$gdMemo = 'GD extension should be installed with FreeType support in order to be used for image CAPTCHA.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adjust requirements according to your application specifics.
|
||||||
|
*/
|
||||||
|
$requirements = array(
|
||||||
|
// Database :
|
||||||
|
array(
|
||||||
|
'name' => 'PDO extension',
|
||||||
|
'mandatory' => true,
|
||||||
|
'condition' => extension_loaded('pdo'),
|
||||||
|
'by' => 'All DB-related classes',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'name' => 'PDO SQLite extension',
|
||||||
|
'mandatory' => false,
|
||||||
|
'condition' => extension_loaded('pdo_sqlite'),
|
||||||
|
'by' => 'All DB-related classes',
|
||||||
|
'memo' => 'Required for SQLite database.',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'name' => 'PDO MySQL extension',
|
||||||
|
'mandatory' => false,
|
||||||
|
'condition' => extension_loaded('pdo_mysql'),
|
||||||
|
'by' => 'All DB-related classes',
|
||||||
|
'memo' => 'Required for MySQL database.',
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'name' => 'PDO PostgreSQL extension',
|
||||||
|
'mandatory' => false,
|
||||||
|
'condition' => extension_loaded('pdo_pgsql'),
|
||||||
|
'by' => 'All DB-related classes',
|
||||||
|
'memo' => 'Required for PostgreSQL database.',
|
||||||
|
),
|
||||||
|
// Cache :
|
||||||
|
array(
|
||||||
|
'name' => 'Memcache extension',
|
||||||
|
'mandatory' => false,
|
||||||
|
'condition' => extension_loaded('memcache') || extension_loaded('memcached'),
|
||||||
|
'by' => '<a href="http://www.yiiframework.com/doc-2.0/yii-caching-memcache.html">MemCache</a>',
|
||||||
|
'memo' => extension_loaded('memcached') ? 'To use memcached set <a href="http://www.yiiframework.com/doc-2.0/yii-caching-memcache.html#$useMemcached-detail">MemCache::useMemcached</a> to <code>true</code>.' : ''
|
||||||
|
),
|
||||||
|
// CAPTCHA:
|
||||||
|
array(
|
||||||
|
'name' => 'GD PHP extension with FreeType support',
|
||||||
|
'mandatory' => false,
|
||||||
|
'condition' => $gdOK,
|
||||||
|
'by' => '<a href="http://www.yiiframework.com/doc-2.0/yii-captcha-captcha.html">Captcha</a>',
|
||||||
|
'memo' => $gdMemo,
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'name' => 'ImageMagick PHP extension with PNG support',
|
||||||
|
'mandatory' => false,
|
||||||
|
'condition' => $imagickOK,
|
||||||
|
'by' => '<a href="http://www.yiiframework.com/doc-2.0/yii-captcha-captcha.html">Captcha</a>',
|
||||||
|
'memo' => $imagickMemo,
|
||||||
|
),
|
||||||
|
// PHP ini :
|
||||||
|
'phpExposePhp' => array(
|
||||||
|
'name' => 'Expose PHP',
|
||||||
|
'mandatory' => false,
|
||||||
|
'condition' => $requirementsChecker->checkPhpIniOff("expose_php"),
|
||||||
|
'by' => 'Security reasons',
|
||||||
|
'memo' => '"expose_php" should be disabled at php.ini',
|
||||||
|
),
|
||||||
|
'phpAllowUrlInclude' => array(
|
||||||
|
'name' => 'PHP allow url include',
|
||||||
|
'mandatory' => false,
|
||||||
|
'condition' => $requirementsChecker->checkPhpIniOff("allow_url_include"),
|
||||||
|
'by' => 'Security reasons',
|
||||||
|
'memo' => '"allow_url_include" should be disabled at php.ini',
|
||||||
|
),
|
||||||
|
'phpSmtp' => array(
|
||||||
|
'name' => 'PHP mail SMTP',
|
||||||
|
'mandatory' => false,
|
||||||
|
'condition' => strlen(ini_get('SMTP')) > 0,
|
||||||
|
'by' => 'Email sending',
|
||||||
|
'memo' => 'PHP mail SMTP server required',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// OPcache check
|
||||||
|
if (!version_compare(phpversion(), '5.5', '>=')) {
|
||||||
|
$requirements[] = array(
|
||||||
|
'name' => 'APC extension',
|
||||||
|
'mandatory' => false,
|
||||||
|
'condition' => extension_loaded('apc'),
|
||||||
|
'by' => '<a href="http://www.yiiframework.com/doc-2.0/yii-caching-apccache.html">ApcCache</a>',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $requirementsChecker->checkYii()->check($requirements)->getResult();
|
||||||
|
$requirementsChecker->render();
|
||||||
|
exit($result['summary']['errors'] === 0 ? 0 : 1);
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
define('YII_ENV', 'test');
|
||||||
|
defined('YII_DEBUG') or define('YII_DEBUG', true);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../vendor/yiisoft/yii2/Yii.php';
|
||||||
|
require __DIR__ .'/../vendor/autoload.php';
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherited Methods
|
||||||
|
* @method void wantToTest($text)
|
||||||
|
* @method void wantTo($text)
|
||||||
|
* @method void execute($callable)
|
||||||
|
* @method void expectTo($prediction)
|
||||||
|
* @method void expect($prediction)
|
||||||
|
* @method void amGoingTo($argumentation)
|
||||||
|
* @method void am($role)
|
||||||
|
* @method void lookForwardTo($achieveValue)
|
||||||
|
* @method void comment($description)
|
||||||
|
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
|
||||||
|
*
|
||||||
|
* @SuppressWarnings(PHPMD)
|
||||||
|
*/
|
||||||
|
class AcceptanceTester extends \Codeception\Actor
|
||||||
|
{
|
||||||
|
use _generated\AcceptanceTesterActions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define custom actions here
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherited Methods
|
||||||
|
* @method void wantToTest($text)
|
||||||
|
* @method void wantTo($text)
|
||||||
|
* @method void execute($callable)
|
||||||
|
* @method void expectTo($prediction)
|
||||||
|
* @method void expect($prediction)
|
||||||
|
* @method void amGoingTo($argumentation)
|
||||||
|
* @method void am($role)
|
||||||
|
* @method void lookForwardTo($achieveValue)
|
||||||
|
* @method void comment($description)
|
||||||
|
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
|
||||||
|
*
|
||||||
|
* @SuppressWarnings(PHPMD)
|
||||||
|
*/
|
||||||
|
class FunctionalTester extends \Codeception\Actor
|
||||||
|
{
|
||||||
|
use _generated\FunctionalTesterActions;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherited Methods
|
||||||
|
* @method void wantToTest($text)
|
||||||
|
* @method void wantTo($text)
|
||||||
|
* @method void execute($callable)
|
||||||
|
* @method void expectTo($prediction)
|
||||||
|
* @method void expect($prediction)
|
||||||
|
* @method void amGoingTo($argumentation)
|
||||||
|
* @method void am($role)
|
||||||
|
* @method void lookForwardTo($achieveValue)
|
||||||
|
* @method void comment($description)
|
||||||
|
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
|
||||||
|
*
|
||||||
|
* @SuppressWarnings(PHPMD)
|
||||||
|
*/
|
||||||
|
class UnitTester extends \Codeception\Actor
|
||||||
|
{
|
||||||
|
use _generated\UnitTesterActions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define custom actions here
|
||||||
|
*/
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
actor: AcceptanceTester
|
||||||
|
modules:
|
||||||
|
enabled:
|
||||||
|
- WebDriver:
|
||||||
|
url: http://127.0.0.1:8080/
|
||||||
|
browser: firefox
|
||||||
|
- Yii2:
|
||||||
|
part: orm
|
||||||
|
entryScript: index-test.php
|
||||||
|
cleanup: false
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use yii\helpers\Url;
|
||||||
|
|
||||||
|
class AboutCest
|
||||||
|
{
|
||||||
|
public function ensureThatAboutWorks(AcceptanceTester $I)
|
||||||
|
{
|
||||||
|
$I->amOnPage(Url::toRoute('/site/about'));
|
||||||
|
$I->see('About', 'h1');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use yii\helpers\Url;
|
||||||
|
|
||||||
|
class ContactCest
|
||||||
|
{
|
||||||
|
public function _before(\AcceptanceTester $I)
|
||||||
|
{
|
||||||
|
$I->amOnPage(Url::toRoute('/site/contact'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contactPageWorks(AcceptanceTester $I)
|
||||||
|
{
|
||||||
|
$I->wantTo('ensure that contact page works');
|
||||||
|
$I->see('Contact', 'h1');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function contactFormCanBeSubmitted(AcceptanceTester $I)
|
||||||
|
{
|
||||||
|
$I->amGoingTo('submit contact form with correct data');
|
||||||
|
$I->fillField('#contactform-name', 'tester');
|
||||||
|
$I->fillField('#contactform-email', 'tester@example.com');
|
||||||
|
$I->fillField('#contactform-subject', 'test subject');
|
||||||
|
$I->fillField('#contactform-body', 'test content');
|
||||||
|
$I->fillField('#contactform-verifycode', 'testme');
|
||||||
|
|
||||||
|
$I->click('contact-button');
|
||||||
|
|
||||||
|
$I->wait(2); // wait for button to be clicked
|
||||||
|
|
||||||
|
$I->dontSeeElement('#contact-form');
|
||||||
|
$I->see('Thank you for contacting us. We will respond to you as soon as possible.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use yii\helpers\Url;
|
||||||
|
|
||||||
|
class HomeCest
|
||||||
|
{
|
||||||
|
public function ensureThatHomePageWorks(AcceptanceTester $I)
|
||||||
|
{
|
||||||
|
$I->amOnPage(Url::toRoute('/site/index'));
|
||||||
|
$I->see('My Company');
|
||||||
|
|
||||||
|
$I->seeLink('About');
|
||||||
|
$I->click('About');
|
||||||
|
$I->wait(2); // wait for page to be opened
|
||||||
|
|
||||||
|
$I->see('This is the About page.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use yii\helpers\Url;
|
||||||
|
|
||||||
|
class LoginCest
|
||||||
|
{
|
||||||
|
public function ensureThatLoginWorks(AcceptanceTester $I)
|
||||||
|
{
|
||||||
|
$I->amOnPage(Url::toRoute('/site/login'));
|
||||||
|
$I->see('Login', 'h1');
|
||||||
|
|
||||||
|
$I->amGoingTo('try to login with correct credentials');
|
||||||
|
$I->fillField('input[name="LoginForm[username]"]', 'admin');
|
||||||
|
$I->fillField('input[name="LoginForm[password]"]', 'admin');
|
||||||
|
$I->click('login-button');
|
||||||
|
$I->wait(2); // wait for button to be clicked
|
||||||
|
|
||||||
|
$I->expectTo('see user info');
|
||||||
|
$I->see('Logout');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<?php
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Yii console bootstrap file.
|
||||||
|
*
|
||||||
|
* @link http://www.yiiframework.com/
|
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC
|
||||||
|
* @license http://www.yiiframework.com/license/
|
||||||
|
*/
|
||||||
|
|
||||||
|
defined('YII_DEBUG') or define('YII_DEBUG', true);
|
||||||
|
defined('YII_ENV') or define('YII_ENV', 'test');
|
||||||
|
|
||||||
|
require __DIR__ . '/../../vendor/autoload.php';
|
||||||
|
require __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php';
|
||||||
|
|
||||||
|
$config = yii\helpers\ArrayHelper::merge(
|
||||||
|
require __DIR__ . '/../../config/console.php',
|
||||||
|
[
|
||||||
|
'components' => [
|
||||||
|
'db' => require __DIR__ . '/../../config/test_db.php'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
$application = new yii\console\Application($config);
|
||||||
|
$exitCode = $application->run();
|
||||||
|
exit($exitCode);
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
rem -------------------------------------------------------------
|
||||||
|
rem Yii command line bootstrap script for Windows.
|
||||||
|
rem
|
||||||
|
rem @author Qiang Xue <qiang.xue@gmail.com>
|
||||||
|
rem @link http://www.yiiframework.com/
|
||||||
|
rem @copyright Copyright (c) 2008 Yii Software LLC
|
||||||
|
rem @license http://www.yiiframework.com/license/
|
||||||
|
rem -------------------------------------------------------------
|
||||||
|
|
||||||
|
@setlocal
|
||||||
|
|
||||||
|
set YII_PATH=%~dp0
|
||||||
|
|
||||||
|
if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe
|
||||||
|
|
||||||
|
"%PHP_COMMAND%" "%YII_PATH%yii" %*
|
||||||
|
|
||||||
|
@endlocal
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Codeception Test Suite Configuration
|
||||||
|
|
||||||
|
# suite for functional (integration) tests.
|
||||||
|
# emulate web requests and make application process them.
|
||||||
|
# (tip: better to use with frameworks).
|
||||||
|
|
||||||
|
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
|
||||||
|
#basic/web/index.php
|
||||||
|
actor: FunctionalTester
|
||||||
|
modules:
|
||||||
|
enabled:
|
||||||
|
- Filesystem
|
||||||
|
- Yii2
|
||||||
|
- Asserts
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class ContactFormCest
|
||||||
|
{
|
||||||
|
public function _before(\FunctionalTester $I)
|
||||||
|
{
|
||||||
|
$I->amOnRoute('site/contact');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function openContactPage(\FunctionalTester $I)
|
||||||
|
{
|
||||||
|
$I->see('Contact', 'h1');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submitEmptyForm(\FunctionalTester $I)
|
||||||
|
{
|
||||||
|
$I->submitForm('#contact-form', []);
|
||||||
|
$I->expectTo('see validations errors');
|
||||||
|
$I->see('Contact', 'h1');
|
||||||
|
$I->see('Name cannot be blank');
|
||||||
|
$I->see('Email cannot be blank');
|
||||||
|
$I->see('Subject cannot be blank');
|
||||||
|
$I->see('Body cannot be blank');
|
||||||
|
$I->see('The verification code is incorrect');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submitFormWithIncorrectEmail(\FunctionalTester $I)
|
||||||
|
{
|
||||||
|
$I->submitForm('#contact-form', [
|
||||||
|
'ContactForm[name]' => 'tester',
|
||||||
|
'ContactForm[email]' => 'tester.email',
|
||||||
|
'ContactForm[subject]' => 'test subject',
|
||||||
|
'ContactForm[body]' => 'test content',
|
||||||
|
'ContactForm[verifyCode]' => 'testme',
|
||||||
|
]);
|
||||||
|
$I->expectTo('see that email address is wrong');
|
||||||
|
$I->dontSee('Name cannot be blank', '.help-inline');
|
||||||
|
$I->see('Email is not a valid email address.');
|
||||||
|
$I->dontSee('Subject cannot be blank', '.help-inline');
|
||||||
|
$I->dontSee('Body cannot be blank', '.help-inline');
|
||||||
|
$I->dontSee('The verification code is incorrect', '.help-inline');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submitFormSuccessfully(\FunctionalTester $I)
|
||||||
|
{
|
||||||
|
$I->submitForm('#contact-form', [
|
||||||
|
'ContactForm[name]' => 'tester',
|
||||||
|
'ContactForm[email]' => 'tester@example.com',
|
||||||
|
'ContactForm[subject]' => 'test subject',
|
||||||
|
'ContactForm[body]' => 'test content',
|
||||||
|
'ContactForm[verifyCode]' => 'testme',
|
||||||
|
]);
|
||||||
|
$I->seeEmailIsSent();
|
||||||
|
$I->dontSeeElement('#contact-form');
|
||||||
|
$I->see('Thank you for contacting us. We will respond to you as soon as possible.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class LoginFormCest
|
||||||
|
{
|
||||||
|
public function _before(\FunctionalTester $I)
|
||||||
|
{
|
||||||
|
$I->amOnRoute('site/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function openLoginPage(\FunctionalTester $I)
|
||||||
|
{
|
||||||
|
$I->see('Login', 'h1');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// demonstrates `amLoggedInAs` method
|
||||||
|
public function internalLoginById(\FunctionalTester $I)
|
||||||
|
{
|
||||||
|
$I->amLoggedInAs(100);
|
||||||
|
$I->amOnPage('/');
|
||||||
|
$I->see('Logout (admin)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// demonstrates `amLoggedInAs` method
|
||||||
|
public function internalLoginByInstance(\FunctionalTester $I)
|
||||||
|
{
|
||||||
|
$I->amLoggedInAs(\app\models\User::findByUsername('admin'));
|
||||||
|
$I->amOnPage('/');
|
||||||
|
$I->see('Logout (admin)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loginWithEmptyCredentials(\FunctionalTester $I)
|
||||||
|
{
|
||||||
|
$I->submitForm('#login-form', []);
|
||||||
|
$I->expectTo('see validations errors');
|
||||||
|
$I->see('Username cannot be blank.');
|
||||||
|
$I->see('Password cannot be blank.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loginWithWrongCredentials(\FunctionalTester $I)
|
||||||
|
{
|
||||||
|
$I->submitForm('#login-form', [
|
||||||
|
'LoginForm[username]' => 'admin',
|
||||||
|
'LoginForm[password]' => 'wrong',
|
||||||
|
]);
|
||||||
|
$I->expectTo('see validations errors');
|
||||||
|
$I->see('Incorrect username or password.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loginSuccessfully(\FunctionalTester $I)
|
||||||
|
{
|
||||||
|
$I->submitForm('#login-form', [
|
||||||
|
'LoginForm[username]' => 'admin',
|
||||||
|
'LoginForm[password]' => 'admin',
|
||||||
|
]);
|
||||||
|
$I->see('Logout (admin)');
|
||||||
|
$I->dontSeeElement('form#login-form');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<?php
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Codeception Test Suite Configuration
|
||||||
|
|
||||||
|
# suite for unit (internal) tests.
|
||||||
|
# RUN `build` COMMAND AFTER ADDING/REMOVING MODULES.
|
||||||
|
|
||||||
|
actor: UnitTester
|
||||||
|
modules:
|
||||||
|
enabled:
|
||||||
|
- Asserts
|
||||||
|
- Yii2:
|
||||||
|
part: [orm, email, fixtures]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// add unit testing specific bootstrap code here
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace tests\unit\models;
|
||||||
|
|
||||||
|
use app\models\ContactForm;
|
||||||
|
use yii\mail\MessageInterface;
|
||||||
|
|
||||||
|
class ContactFormTest extends \Codeception\Test\Unit
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \UnitTester
|
||||||
|
*/
|
||||||
|
public $tester;
|
||||||
|
|
||||||
|
public function testEmailIsSentOnContact()
|
||||||
|
{
|
||||||
|
$model = new ContactForm();
|
||||||
|
|
||||||
|
$model->attributes = [
|
||||||
|
'name' => 'Tester',
|
||||||
|
'email' => 'tester@example.com',
|
||||||
|
'subject' => 'very important letter subject',
|
||||||
|
'body' => 'body of current message',
|
||||||
|
'verifyCode' => 'testme',
|
||||||
|
];
|
||||||
|
|
||||||
|
verify($model->contact('admin@example.com'))->notEmpty();
|
||||||
|
|
||||||
|
// using Yii2 module actions to check email was sent
|
||||||
|
$this->tester->seeEmailIsSent();
|
||||||
|
|
||||||
|
/** @var MessageInterface $emailMessage */
|
||||||
|
$emailMessage = $this->tester->grabLastSentEmail();
|
||||||
|
verify($emailMessage)->instanceOf('yii\mail\MessageInterface');
|
||||||
|
verify($emailMessage->getTo())->arrayHasKey('admin@example.com');
|
||||||
|
verify($emailMessage->getFrom())->arrayHasKey('noreply@example.com');
|
||||||
|
verify($emailMessage->getReplyTo())->arrayHasKey('tester@example.com');
|
||||||
|
verify($emailMessage->getSubject())->equals('very important letter subject');
|
||||||
|
verify($emailMessage->toString())->stringContainsString('body of current message');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace tests\unit\models;
|
||||||
|
|
||||||
|
use app\models\LoginForm;
|
||||||
|
|
||||||
|
class LoginFormTest extends \Codeception\Test\Unit
|
||||||
|
{
|
||||||
|
private $model;
|
||||||
|
|
||||||
|
protected function _after()
|
||||||
|
{
|
||||||
|
\Yii::$app->user->logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoginNoUser()
|
||||||
|
{
|
||||||
|
$this->model = new LoginForm([
|
||||||
|
'username' => 'not_existing_username',
|
||||||
|
'password' => 'not_existing_password',
|
||||||
|
]);
|
||||||
|
|
||||||
|
verify($this->model->login())->false();
|
||||||
|
verify(\Yii::$app->user->isGuest)->true();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoginWrongPassword()
|
||||||
|
{
|
||||||
|
$this->model = new LoginForm([
|
||||||
|
'username' => 'demo',
|
||||||
|
'password' => 'wrong_password',
|
||||||
|
]);
|
||||||
|
|
||||||
|
verify($this->model->login())->false();
|
||||||
|
verify(\Yii::$app->user->isGuest)->true();
|
||||||
|
verify($this->model->errors)->arrayHasKey('password');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLoginCorrect()
|
||||||
|
{
|
||||||
|
$this->model = new LoginForm([
|
||||||
|
'username' => 'demo',
|
||||||
|
'password' => 'demo',
|
||||||
|
]);
|
||||||
|
|
||||||
|
verify($this->model->login())->true();
|
||||||
|
verify(\Yii::$app->user->isGuest)->false();
|
||||||
|
verify($this->model->errors)->arrayHasNotKey('password');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace tests\unit\models;
|
||||||
|
|
||||||
|
use app\models\User;
|
||||||
|
|
||||||
|
class UserTest extends \Codeception\Test\Unit
|
||||||
|
{
|
||||||
|
public function testFindUserById()
|
||||||
|
{
|
||||||
|
verify($user = User::findIdentity(100))->notEmpty();
|
||||||
|
verify($user->username)->equals('admin');
|
||||||
|
|
||||||
|
verify(User::findIdentity(999))->empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFindUserByAccessToken()
|
||||||
|
{
|
||||||
|
verify($user = User::findIdentityByAccessToken('100-token'))->notEmpty();
|
||||||
|
verify($user->username)->equals('admin');
|
||||||
|
|
||||||
|
verify(User::findIdentityByAccessToken('non-existing'))->empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFindUserByUsername()
|
||||||
|
{
|
||||||
|
verify($user = User::findByUsername('admin'))->notEmpty();
|
||||||
|
verify(User::findByUsername('not-admin'))->empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @depends testFindUserByUsername
|
||||||
|
*/
|
||||||
|
public function testValidateUser()
|
||||||
|
{
|
||||||
|
$user = User::findByUsername('admin');
|
||||||
|
verify($user->validateAuthKey('test100key'))->notEmpty();
|
||||||
|
verify($user->validateAuthKey('test102key'))->empty();
|
||||||
|
|
||||||
|
verify($user->validatePassword('admin'))->notEmpty();
|
||||||
|
verify($user->validatePassword('123456'))->empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace tests\unit\widgets;
|
||||||
|
|
||||||
|
use app\widgets\Alert;
|
||||||
|
use Yii;
|
||||||
|
|
||||||
|
class AlertTest extends \Codeception\Test\Unit
|
||||||
|
{
|
||||||
|
public function testSingleErrorMessage()
|
||||||
|
{
|
||||||
|
$message = 'This is an error message';
|
||||||
|
|
||||||
|
Yii::$app->session->setFlash('error', $message);
|
||||||
|
|
||||||
|
$renderingResult = Alert::widget();
|
||||||
|
|
||||||
|
verify($renderingResult)->stringContainsString($message);
|
||||||
|
verify($renderingResult)->stringContainsString('alert-danger');
|
||||||
|
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMultipleErrorMessages()
|
||||||
|
{
|
||||||
|
$firstMessage = 'This is the first error message';
|
||||||
|
$secondMessage = 'This is the second error message';
|
||||||
|
|
||||||
|
Yii::$app->session->setFlash('error', [$firstMessage, $secondMessage]);
|
||||||
|
|
||||||
|
$renderingResult = Alert::widget();
|
||||||
|
|
||||||
|
verify($renderingResult)->stringContainsString($firstMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($secondMessage);
|
||||||
|
verify($renderingResult)->stringContainsString('alert-danger');
|
||||||
|
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSingleDangerMessage()
|
||||||
|
{
|
||||||
|
$message = 'This is a danger message';
|
||||||
|
|
||||||
|
Yii::$app->session->setFlash('danger', $message);
|
||||||
|
|
||||||
|
$renderingResult = Alert::widget();
|
||||||
|
|
||||||
|
verify($renderingResult)->stringContainsString($message);
|
||||||
|
verify($renderingResult)->stringContainsString('alert-danger');
|
||||||
|
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMultipleDangerMessages()
|
||||||
|
{
|
||||||
|
$firstMessage = 'This is the first danger message';
|
||||||
|
$secondMessage = 'This is the second danger message';
|
||||||
|
|
||||||
|
Yii::$app->session->setFlash('danger', [$firstMessage, $secondMessage]);
|
||||||
|
|
||||||
|
$renderingResult = Alert::widget();
|
||||||
|
|
||||||
|
verify($renderingResult)->stringContainsString($firstMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($secondMessage);
|
||||||
|
verify($renderingResult)->stringContainsString('alert-danger');
|
||||||
|
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSingleSuccessMessage()
|
||||||
|
{
|
||||||
|
$message = 'This is a success message';
|
||||||
|
|
||||||
|
Yii::$app->session->setFlash('success', $message);
|
||||||
|
|
||||||
|
$renderingResult = Alert::widget();
|
||||||
|
|
||||||
|
verify($renderingResult)->stringContainsString($message);
|
||||||
|
verify($renderingResult)->stringContainsString('alert-success');
|
||||||
|
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-danger');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMultipleSuccessMessages()
|
||||||
|
{
|
||||||
|
$firstMessage = 'This is the first danger message';
|
||||||
|
$secondMessage = 'This is the second danger message';
|
||||||
|
|
||||||
|
Yii::$app->session->setFlash('success', [$firstMessage, $secondMessage]);
|
||||||
|
|
||||||
|
$renderingResult = Alert::widget();
|
||||||
|
|
||||||
|
verify($renderingResult)->stringContainsString($firstMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($secondMessage);
|
||||||
|
verify($renderingResult)->stringContainsString('alert-success');
|
||||||
|
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-danger');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSingleInfoMessage()
|
||||||
|
{
|
||||||
|
$message = 'This is an info message';
|
||||||
|
|
||||||
|
Yii::$app->session->setFlash('info', $message);
|
||||||
|
|
||||||
|
$renderingResult = Alert::widget();
|
||||||
|
|
||||||
|
verify($renderingResult)->stringContainsString($message);
|
||||||
|
verify($renderingResult)->stringContainsString('alert-info');
|
||||||
|
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-danger');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMultipleInfoMessages()
|
||||||
|
{
|
||||||
|
$firstMessage = 'This is the first info message';
|
||||||
|
$secondMessage = 'This is the second info message';
|
||||||
|
|
||||||
|
Yii::$app->session->setFlash('info', [$firstMessage, $secondMessage]);
|
||||||
|
|
||||||
|
$renderingResult = Alert::widget();
|
||||||
|
|
||||||
|
verify($renderingResult)->stringContainsString($firstMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($secondMessage);
|
||||||
|
verify($renderingResult)->stringContainsString('alert-info');
|
||||||
|
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-danger');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-warning');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSingleWarningMessage()
|
||||||
|
{
|
||||||
|
$message = 'This is a warning message';
|
||||||
|
|
||||||
|
Yii::$app->session->setFlash('warning', $message);
|
||||||
|
|
||||||
|
$renderingResult = Alert::widget();
|
||||||
|
|
||||||
|
verify($renderingResult)->stringContainsString($message);
|
||||||
|
verify($renderingResult)->stringContainsString('alert-warning');
|
||||||
|
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-danger');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMultipleWarningMessages()
|
||||||
|
{
|
||||||
|
$firstMessage = 'This is the first warning message';
|
||||||
|
$secondMessage = 'This is the second warning message';
|
||||||
|
|
||||||
|
Yii::$app->session->setFlash('warning', [$firstMessage, $secondMessage]);
|
||||||
|
|
||||||
|
$renderingResult = Alert::widget();
|
||||||
|
|
||||||
|
verify($renderingResult)->stringContainsString($firstMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($secondMessage);
|
||||||
|
verify($renderingResult)->stringContainsString('alert-warning');
|
||||||
|
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-danger');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-success');
|
||||||
|
verify($renderingResult)->stringNotContainsString('alert-info');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSingleMixedMessages() {
|
||||||
|
$errorMessage = 'This is an error message';
|
||||||
|
$dangerMessage = 'This is a danger message';
|
||||||
|
$successMessage = 'This is a success message';
|
||||||
|
$infoMessage = 'This is a info message';
|
||||||
|
$warningMessage = 'This is a warning message';
|
||||||
|
|
||||||
|
Yii::$app->session->setFlash('error', $errorMessage);
|
||||||
|
Yii::$app->session->setFlash('danger', $dangerMessage);
|
||||||
|
Yii::$app->session->setFlash('success', $successMessage);
|
||||||
|
Yii::$app->session->setFlash('info', $infoMessage);
|
||||||
|
Yii::$app->session->setFlash('warning', $warningMessage);
|
||||||
|
|
||||||
|
$renderingResult = Alert::widget();
|
||||||
|
|
||||||
|
verify($renderingResult)->stringContainsString($errorMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($dangerMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($successMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($infoMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($warningMessage);
|
||||||
|
|
||||||
|
verify($renderingResult)->stringContainsString('alert-danger');
|
||||||
|
verify($renderingResult)->stringContainsString('alert-success');
|
||||||
|
verify($renderingResult)->stringContainsString('alert-info');
|
||||||
|
verify($renderingResult)->stringContainsString('alert-warning');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMultipleMixedMessages() {
|
||||||
|
$firstErrorMessage = 'This is the first error message';
|
||||||
|
$secondErrorMessage = 'This is the second error message';
|
||||||
|
$firstDangerMessage = 'This is the first danger message';
|
||||||
|
$secondDangerMessage = 'This is the second';
|
||||||
|
$firstSuccessMessage = 'This is the first success message';
|
||||||
|
$secondSuccessMessage = 'This is the second success message';
|
||||||
|
$firstInfoMessage = 'This is the first info message';
|
||||||
|
$secondInfoMessage = 'This is the second info message';
|
||||||
|
$firstWarningMessage = 'This is the first warning message';
|
||||||
|
$secondWarningMessage = 'This is the second warning message';
|
||||||
|
|
||||||
|
Yii::$app->session->setFlash('error', [$firstErrorMessage, $secondErrorMessage]);
|
||||||
|
Yii::$app->session->setFlash('danger', [$firstDangerMessage, $secondDangerMessage]);
|
||||||
|
Yii::$app->session->setFlash('success', [$firstSuccessMessage, $secondSuccessMessage]);
|
||||||
|
Yii::$app->session->setFlash('info', [$firstInfoMessage, $secondInfoMessage]);
|
||||||
|
Yii::$app->session->setFlash('warning', [$firstWarningMessage, $secondWarningMessage]);
|
||||||
|
|
||||||
|
$renderingResult = Alert::widget();
|
||||||
|
|
||||||
|
verify($renderingResult)->stringContainsString($firstErrorMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($secondErrorMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($firstDangerMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($secondDangerMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($firstSuccessMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($secondSuccessMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($firstInfoMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($secondInfoMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($firstWarningMessage);
|
||||||
|
verify($renderingResult)->stringContainsString($secondWarningMessage);
|
||||||
|
|
||||||
|
verify($renderingResult)->stringContainsString('alert-danger');
|
||||||
|
verify($renderingResult)->stringContainsString('alert-success');
|
||||||
|
verify($renderingResult)->stringContainsString('alert-info');
|
||||||
|
verify($renderingResult)->stringContainsString('alert-warning');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFlashIntegrity()
|
||||||
|
{
|
||||||
|
$errorMessage = 'This is an error message';
|
||||||
|
$unrelatedMessage = 'This is a message that is not related to the alert widget';
|
||||||
|
|
||||||
|
Yii::$app->session->setFlash('error', $errorMessage);
|
||||||
|
Yii::$app->session->setFlash('unrelated', $unrelatedMessage);
|
||||||
|
|
||||||
|
Alert::widget();
|
||||||
|
|
||||||
|
// Simulate redirect
|
||||||
|
Yii::$app->session->close();
|
||||||
|
Yii::$app->session->open();
|
||||||
|
|
||||||
|
verify(Yii::$app->session->getFlash('error'))->empty();
|
||||||
|
verify(Yii::$app->session->getFlash('unrelated'))->equals($unrelatedMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# local configuration
|
||||||
|
vagrant-local.yml
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# Your personal GitHub token
|
||||||
|
github_token: <your-personal-github-token>
|
||||||
|
# Read more: https://github.com/blog/1509-personal-api-tokens
|
||||||
|
# You can generate it here: https://github.com/settings/tokens
|
||||||
|
|
||||||
|
# Guest OS timezone
|
||||||
|
timezone: Europe/London
|
||||||
|
|
||||||
|
# Are we need check box updates for every 'vagrant up'?
|
||||||
|
box_check_update: false
|
||||||
|
|
||||||
|
# Virtual machine name
|
||||||
|
machine_name: yii2basic
|
||||||
|
|
||||||
|
# Virtual machine IP
|
||||||
|
ip: 192.168.83.137
|
||||||
|
|
||||||
|
# Virtual machine CPU cores number
|
||||||
|
cpus: 1
|
||||||
|
|
||||||
|
# Virtual machine RAM
|
||||||
|
memory: 1024
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
server {
|
||||||
|
charset utf-8;
|
||||||
|
client_max_body_size 128M;
|
||||||
|
sendfile off;
|
||||||
|
|
||||||
|
listen 80; ## listen for ipv4
|
||||||
|
#listen [::]:80 default_server ipv6only=on; ## listen for ipv6
|
||||||
|
|
||||||
|
server_name yii2basic.test;
|
||||||
|
root /app/web/;
|
||||||
|
index index.php;
|
||||||
|
|
||||||
|
access_log /app/vagrant/nginx/log/yii2basic.access.log;
|
||||||
|
error_log /app/vagrant/nginx/log/yii2basic.error.log;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# Redirect everything that isn't a real file to index.php
|
||||||
|
try_files $uri $uri/ /index.php$is_args$args;
|
||||||
|
}
|
||||||
|
|
||||||
|
# uncomment to avoid processing of calls to non-existing static files by Yii
|
||||||
|
#location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ {
|
||||||
|
# try_files $uri =404;
|
||||||
|
#}
|
||||||
|
#error_page 404 /404.html;
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
#fastcgi_pass 127.0.0.1:9000;
|
||||||
|
fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
|
||||||
|
try_files $uri =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /\.(ht|svn|git) {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#nginx logs
|
||||||
|
yii2basic.access.log
|
||||||
|
yii2basic.error.log
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#== Bash helpers ==
|
||||||
|
|
||||||
|
function info {
|
||||||
|
echo " "
|
||||||
|
echo "--> $1"
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
|
||||||
|
#== Provision script ==
|
||||||
|
|
||||||
|
info "Provision-script user: `whoami`"
|
||||||
|
|
||||||
|
info "Restart web-stack"
|
||||||
|
service php7.2-fpm restart
|
||||||
|
service nginx restart
|
||||||
|
service mysql restart
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#== Import script args ==
|
||||||
|
|
||||||
|
timezone=$(echo "$1")
|
||||||
|
readonly IP=$2
|
||||||
|
|
||||||
|
#== Bash helpers ==
|
||||||
|
|
||||||
|
function info {
|
||||||
|
echo " "
|
||||||
|
echo "--> $1"
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
|
||||||
|
#== Provision script ==
|
||||||
|
|
||||||
|
info "Provision-script user: `whoami`"
|
||||||
|
|
||||||
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
info "Configure timezone"
|
||||||
|
timedatectl set-timezone ${timezone} --no-ask-password
|
||||||
|
|
||||||
|
info "Add the VM IP to the list of allowed IPs"
|
||||||
|
awk -v ip=$IP -f /app/vagrant/provision/provision.awk /app/config/web.php
|
||||||
|
|
||||||
|
info "Prepare root password for MySQL"
|
||||||
|
debconf-set-selections <<< 'mariadb-server mysql-server/root_password password'
|
||||||
|
debconf-set-selections <<< 'mariadb-server mysql-server/root_password_again password'
|
||||||
|
echo "Done!"
|
||||||
|
|
||||||
|
info "Update OS software"
|
||||||
|
apt-get update
|
||||||
|
apt-get upgrade -y
|
||||||
|
|
||||||
|
info "Install additional software"
|
||||||
|
apt-get install -y php7.2-curl php7.2-cli php7.2-intl php7.2-mysqlnd php7.2-gd php7.2-fpm php7.2-mbstring php7.2-xml unzip nginx mariadb-server-10.1 php.xdebug
|
||||||
|
|
||||||
|
info "Configure MySQL"
|
||||||
|
sed -i 's/.*bind-address.*/bind-address = 0.0.0.0/' /etc/mysql/mariadb.conf.d/50-server.cnf
|
||||||
|
mysql <<< "CREATE USER 'root'@'%' IDENTIFIED BY ''"
|
||||||
|
mysql <<< "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'"
|
||||||
|
mysql <<< "DROP USER 'root'@'localhost'"
|
||||||
|
mysql <<< 'FLUSH PRIVILEGES'
|
||||||
|
echo "Done!"
|
||||||
|
|
||||||
|
info "Configure PHP-FPM"
|
||||||
|
sed -i 's/user = www-data/user = vagrant/g' /etc/php/7.2/fpm/pool.d/www.conf
|
||||||
|
sed -i 's/group = www-data/group = vagrant/g' /etc/php/7.2/fpm/pool.d/www.conf
|
||||||
|
sed -i 's/owner = www-data/owner = vagrant/g' /etc/php/7.2/fpm/pool.d/www.conf
|
||||||
|
cat << EOF > /etc/php/7.2/mods-available/xdebug.ini
|
||||||
|
zend_extension=xdebug.so
|
||||||
|
xdebug.remote_enable=1
|
||||||
|
xdebug.remote_connect_back=1
|
||||||
|
xdebug.remote_port=9000
|
||||||
|
xdebug.remote_autostart=1
|
||||||
|
EOF
|
||||||
|
echo "Done!"
|
||||||
|
|
||||||
|
info "Configure NGINX"
|
||||||
|
sed -i 's/user www-data/user vagrant/g' /etc/nginx/nginx.conf
|
||||||
|
echo "Done!"
|
||||||
|
|
||||||
|
info "Enabling site configuration"
|
||||||
|
ln -s /app/vagrant/nginx/app.conf /etc/nginx/sites-enabled/app.conf
|
||||||
|
echo "Done!"
|
||||||
|
|
||||||
|
info "Removing default site configuration"
|
||||||
|
rm /etc/nginx/sites-enabled/default
|
||||||
|
echo "Done!"
|
||||||
|
|
||||||
|
info "Initialize databases for MySQL"
|
||||||
|
mysql <<< 'CREATE DATABASE yii2basic'
|
||||||
|
mysql <<< 'CREATE DATABASE yii2basic_test'
|
||||||
|
echo "Done!"
|
||||||
|
|
||||||
|
info "Install composer"
|
||||||
|
curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#== Import script args ==
|
||||||
|
|
||||||
|
github_token=$(echo "$1")
|
||||||
|
|
||||||
|
#== Bash helpers ==
|
||||||
|
|
||||||
|
function info {
|
||||||
|
echo " "
|
||||||
|
echo "--> $1"
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
|
||||||
|
#== Provision script ==
|
||||||
|
|
||||||
|
info "Provision-script user: `whoami`"
|
||||||
|
|
||||||
|
info "Configure composer"
|
||||||
|
composer config --global github-oauth.github.com ${github_token}
|
||||||
|
echo "Done!"
|
||||||
|
|
||||||
|
info "Install project dependencies"
|
||||||
|
cd /app
|
||||||
|
composer --no-progress --prefer-dist install
|
||||||
|
|
||||||
|
info "Create bash-alias 'app' for vagrant user"
|
||||||
|
echo 'alias app="cd /app"' | tee /home/vagrant/.bash_aliases
|
||||||
|
|
||||||
|
info "Enabling colorized prompt for guest console"
|
||||||
|
sed -i "s/#force_color_prompt=yes/force_color_prompt=yes/" /home/vagrant/.bashrc
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
###
|
||||||
|
# Modifying Yii2's files for Vagrant VM
|
||||||
|
#
|
||||||
|
# @author HA3IK <golubha3ik@gmail.com>
|
||||||
|
# @version 1.0.0
|
||||||
|
|
||||||
|
BEGIN {
|
||||||
|
print "AWK BEGINs its work:"
|
||||||
|
IGNORECASE = 1
|
||||||
|
|
||||||
|
# Correct IP - wildcard last octet
|
||||||
|
match(ip, /(([0-9]+\.)+)/, arr)
|
||||||
|
ip = arr[1] "*"
|
||||||
|
}
|
||||||
|
# BODY
|
||||||
|
{
|
||||||
|
# Check if it's the same file
|
||||||
|
if (FILENAME != isFile["same"]){
|
||||||
|
msg = "- Work with: " FILENAME
|
||||||
|
# Close a previous file
|
||||||
|
close(isFile["same"])
|
||||||
|
# Delete previous data
|
||||||
|
delete isFile
|
||||||
|
# Save current file
|
||||||
|
isFile["same"] = FILENAME
|
||||||
|
# Define array index for the file
|
||||||
|
switch (FILENAME){
|
||||||
|
case /config\/web\.php$/:
|
||||||
|
isFile["IsConfWeb"] = 1
|
||||||
|
msg = msg " - add allowed IP: " ip
|
||||||
|
break
|
||||||
|
}
|
||||||
|
# Print the concatenated message for the file
|
||||||
|
print msg
|
||||||
|
}
|
||||||
|
|
||||||
|
# IF config/web.php
|
||||||
|
if (isFile["IsConfWeb"]){
|
||||||
|
# IF line has "allowedIPs" and doesn't has our IP
|
||||||
|
if (match($0, "allowedIPs") && !match($0, ip)){
|
||||||
|
match($0, /([^\]]+)(.+)/, arr)
|
||||||
|
$0 = sprintf("%s, '%s'%s", arr[1], ip, arr[2])
|
||||||
|
}
|
||||||
|
# Rewrite the file
|
||||||
|
print $0 > FILENAME
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
print "AWK ENDs its work."
|
||||||
|
}
|
||||||
Vendored
+25
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
// autoload.php @generated by Composer
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID < 50600) {
|
||||||
|
if (!headers_sent()) {
|
||||||
|
header('HTTP/1.1 500 Internal Server Error');
|
||||||
|
}
|
||||||
|
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
|
||||||
|
if (!ini_get('display_errors')) {
|
||||||
|
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||||
|
fwrite(STDERR, $err);
|
||||||
|
} elseif (!headers_sent()) {
|
||||||
|
echo $err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trigger_error(
|
||||||
|
$err,
|
||||||
|
E_USER_ERROR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . '/composer/autoload_real.php';
|
||||||
|
|
||||||
|
return ComposerAutoloaderInitc501b52c18a78741dc0d2bdb0f412b76::getLoader();
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Build and test
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
php: [7.2, 7.3, 7.4, 8.0, 8.1]
|
||||||
|
composer-flags: [ "" ]
|
||||||
|
symfony-version: [ "" ]
|
||||||
|
include:
|
||||||
|
- php: 7.2
|
||||||
|
symfony-version: '3.*'
|
||||||
|
- php: 7.3
|
||||||
|
symfony-version: '4.*'
|
||||||
|
- php: 7.4
|
||||||
|
symfony-version: '5.*'
|
||||||
|
- php: 8.0
|
||||||
|
symfony-version: '5.*'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: "${{ matrix.php }}"
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Update Symfony version
|
||||||
|
if: matrix.symfony-version != ''
|
||||||
|
run: composer require --no-update "symfony/symfony:${{ matrix.symfony-version }}"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer update ${{ matrix.composer-flags }}
|
||||||
|
|
||||||
|
- name: Run tests (phpunit)
|
||||||
|
run: ./vendor/bin/phpunit
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
name: Update Cucumber
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 7 * * *'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cucumber-update:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Upstream cucumber update
|
||||||
|
steps:
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: 7.4
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: composer update
|
||||||
|
|
||||||
|
- name: Update cucumber tag
|
||||||
|
id: cucumber
|
||||||
|
run: bin/update_cucumber
|
||||||
|
|
||||||
|
- name: Re-install dependencies
|
||||||
|
run: composer update
|
||||||
|
if: steps.cucumber.outputs.cucumber_version
|
||||||
|
|
||||||
|
- name: Update translations
|
||||||
|
run: bin/update_i18n
|
||||||
|
if: steps.cucumber.outputs.cucumber_version
|
||||||
|
|
||||||
|
- name: Find changelog
|
||||||
|
id: changelog
|
||||||
|
run: bin/cucumber_changelog ${{ steps.cucumber.outputs.cucumber_version }}
|
||||||
|
if: steps.cucumber.outputs.cucumber_version
|
||||||
|
|
||||||
|
- name: Open a PR
|
||||||
|
uses: peter-evans/create-pull-request@v3
|
||||||
|
if: steps.cucumber.outputs.cucumber_version
|
||||||
|
with:
|
||||||
|
commit-message: Automatic Cucumber tag update to ${{ steps.cucumber.outputs.cucumber_version }}
|
||||||
|
branch: cucumber-update-${{ steps.cucumber.outputs.cucumber_version }}
|
||||||
|
delete-branch: true
|
||||||
|
title: Cucumber update ${{ steps.cucumber.outputs.cucumber_version }}
|
||||||
|
body: ${{ steps.changelog.outputs.changelog }}
|
||||||
|
base: 'master'
|
||||||
Vendored
+442
@@ -0,0 +1,442 @@
|
|||||||
|
4.9.0 / 2021-10-12
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Simplify the boolean condition for the tag matching by @stof in https://github.com/Behat/Gherkin/pull/219
|
||||||
|
* Remove symfony phpunit bridge by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/220
|
||||||
|
* Ignore the bin folder in archives by @stof in https://github.com/Behat/Gherkin/pull/226
|
||||||
|
* Cast table node exceptions into ParserExceptions when throwing by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/216
|
||||||
|
* Cucumber changelog in PRs and using correct hash by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/225
|
||||||
|
* Support alternative docstrings format (```) by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/214
|
||||||
|
* Fix DocBlocks (Boolean -> bool) by @simonhammes in https://github.com/Behat/Gherkin/pull/237
|
||||||
|
* Tag parsing by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/215
|
||||||
|
* Remove test - cucumber added an example with Rule which is not supported by @ciaranmcnulty in https://github.com/Behat/Gherkin/pull/239
|
||||||
|
* Add PHP 8.1 support by @javer in https://github.com/Behat/Gherkin/pull/242
|
||||||
|
* Fix main branch alias version by @mvorisek in https://github.com/Behat/Gherkin/pull/244
|
||||||
|
|
||||||
|
4.8.0 / 2021-02-04
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Drop support for PHP before version 7.2
|
||||||
|
|
||||||
|
4.7.3 / 2021-02-04
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Refactored comments parsing to avoid Maximum function nesting level errors
|
||||||
|
|
||||||
|
4.7.2 / 2021-02-03
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Issue where Scenario Outline title was not populated into Examples
|
||||||
|
* Updated translations from cucumber 16.0.0
|
||||||
|
|
||||||
|
4.7.1 / 2021-01-26
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Issue parsing comments before scenarios when following an Examples table
|
||||||
|
|
||||||
|
4.7.0 / 2021-01-24
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Provides better messages for TableNode construct errors
|
||||||
|
* Now allows single character steps
|
||||||
|
* Supports multiple Example Tables with tags
|
||||||
|
|
||||||
|
4.6.2 / 2020-03-17
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fixed issues due to incorrect cache key
|
||||||
|
|
||||||
|
4.6.1 / 2020-02-27
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix AZ translations
|
||||||
|
* Correctly filter features, now that the base path is correctly set
|
||||||
|
|
||||||
|
4.6.0 / 2019-01-16
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Updated translations (including 'Example' as synonym for 'Scenario' in `en`)
|
||||||
|
|
||||||
|
4.5.1 / 2017-08-30
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix regression in `PathsFilter`
|
||||||
|
|
||||||
|
4.5.0 / 2017-08-30
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Sync i18n with Cucumber Gherkin
|
||||||
|
* Drop support for HHVM tests on Travis
|
||||||
|
* Add `TableNode::fromList()` method (thanks @TravisCarden)
|
||||||
|
* Add `ExampleNode::getOutlineTitle()` method (thanks @duxet)
|
||||||
|
* Use realpath, so the feature receives the cwd prefixed (thanks @glennunipro)
|
||||||
|
* Explicitly handle non-two-dimensional arrays in TableNode (thanks @TravisCarden)
|
||||||
|
* Fix to line/linefilter scenario runs which take relative paths to files (thanks @generalconsensus)
|
||||||
|
|
||||||
|
4.4.5 / 2016-10-30
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix partial paths matching in `PathsFilter`
|
||||||
|
|
||||||
|
4.4.4 / 2016-09-18
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Provide clearer exception for non-writeable cache directories
|
||||||
|
|
||||||
|
4.4.3 / 2016-09-18
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Ensure we reset tags between features
|
||||||
|
|
||||||
|
4.4.2 / 2016-09-03
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Sync 18n with gherkin 3
|
||||||
|
|
||||||
|
4.4.1 / 2015-12-30
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Ensure keywords are trimmed when syncing translations
|
||||||
|
* Sync 18n with cucumber
|
||||||
|
|
||||||
|
4.4.0 / 2015-09-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Added validation enforcing that all rows of a `TableNode` have the same number of columns
|
||||||
|
* Added `TableNode::getColumn` to get a column from the table
|
||||||
|
* Sync 18n with cucumber
|
||||||
|
|
||||||
|
4.3.0 / 2014-06-06
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Added `setFilters(array)` method to `Gherkin` class
|
||||||
|
* Added `NarrativeFilter` for non-english `RoleFilter` lovers
|
||||||
|
|
||||||
|
4.2.1 / 2014-06-06
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix parsing of features without line feed at the end
|
||||||
|
|
||||||
|
4.2.0 / 2014-05-27
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Added `getKeyword()` and `getKeywordType()` methods to `StepNode`, deprecated `getType()`.
|
||||||
|
Thanks to @kibao
|
||||||
|
|
||||||
|
4.1.3 / 2014-05-25
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Properly handle tables with rows terminating in whitespace
|
||||||
|
|
||||||
|
4.1.2 / 2014-05-14
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Handle case where Gherkin cache is broken
|
||||||
|
|
||||||
|
4.1.1 / 2014-05-05
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fixed the compatibility with PHP 5.6-beta by avoiding to use the broken PHP array function
|
||||||
|
* The YamlFileLoader no longer extend from ArrayLoader but from AbstractFileLoader
|
||||||
|
|
||||||
|
4.1.0 / 2014-04-20
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fixed scenario tag filtering
|
||||||
|
* Do not allow multiple multiline step arguments
|
||||||
|
* Sync 18n with cucumber
|
||||||
|
|
||||||
|
4.0.0 / 2014-01-05
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Changed the behavior when no loader can be found for the resource. Instead of throwing an exception, the
|
||||||
|
Gherkin class now returns an empty array.
|
||||||
|
|
||||||
|
3.1.3 / 2014-01-04
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Dropped the dependency on the Symfony Finder by using SPL iterators directly
|
||||||
|
* Added testing on HHVM on Travis. HHVM is officially supported (previous release was actually already compatible)
|
||||||
|
|
||||||
|
3.1.2 / 2014-01-01
|
||||||
|
==================
|
||||||
|
|
||||||
|
* All paths passed to PathsFilter are converted using realpath
|
||||||
|
|
||||||
|
3.1.1 / 2013-12-31
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Add `ComplexFilterInterace` that has complex behavior for scenarios and requires to pass
|
||||||
|
feature too
|
||||||
|
* `TagFilter` is an instance of a `ComplexFilterInterace` now
|
||||||
|
|
||||||
|
3.1.0 / 2013-12-31
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Example node is a scenario
|
||||||
|
* Nodes do not have uprefs (memory usage fix)
|
||||||
|
* Scenario filters do not depend on feature nodes
|
||||||
|
|
||||||
|
3.0.5 / 2014-01-01
|
||||||
|
==================
|
||||||
|
|
||||||
|
* All paths passed to PathsFilter are converted using realpath
|
||||||
|
|
||||||
|
3.0.4 / 2013-12-31
|
||||||
|
==================
|
||||||
|
|
||||||
|
* TableNode is now traversable using foreach
|
||||||
|
* All possibly thrown exceptions implement Gherkin\Exception interface
|
||||||
|
* Sync i18n with cucumber
|
||||||
|
|
||||||
|
3.0.3 / 2013-09-15
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Extend ExampleNode with additional methods
|
||||||
|
|
||||||
|
3.0.2 / 2013-09-14
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Extract `KeywordNodeInterface` and `ScenarioLikeInterface`
|
||||||
|
* Add `getIndex()` methods to scenarios, outlines, steps and examples
|
||||||
|
* Throw proper exception for fractured node tree
|
||||||
|
|
||||||
|
3.0.1 / 2013-09-14
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Use versioned subfolder in FileCache
|
||||||
|
|
||||||
|
3.0.0 / 2013-09-14
|
||||||
|
==================
|
||||||
|
|
||||||
|
* A lot of optimizations in Parser and Lexer
|
||||||
|
* Node tree is now immutable by nature (no setters)
|
||||||
|
* Example nodes are now part of the node tree. They are lazily generated by Outline node
|
||||||
|
* Sync with latest cucumber i18n
|
||||||
|
|
||||||
|
2.3.4 / 2013-08-11
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix leaks in memory cache
|
||||||
|
|
||||||
|
2.3.3 / 2013-08-11
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix encoding bug introduced with previous release
|
||||||
|
* Sync i18n with cucumber
|
||||||
|
|
||||||
|
2.3.2 / 2013-08-11
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Explicitly use utf8 encoding
|
||||||
|
|
||||||
|
2.3.1 / 2013-08-10
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Support `an` prefix with RoleFilter
|
||||||
|
|
||||||
|
2.3.0 / 2013-08-04
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Add RoleFilter
|
||||||
|
* Add PathsFilter
|
||||||
|
* Add MemoryCache
|
||||||
|
|
||||||
|
2.2.9 / 2013-03-02
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fix dependency version requirement
|
||||||
|
|
||||||
|
2.2.8 / 2013-03-02
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Features filtering behavior change. Now emptified (by filtering) features
|
||||||
|
that do not match filter themselves are removed from resultset.
|
||||||
|
* Small potential bug fix in TableNode
|
||||||
|
|
||||||
|
2.2.7 / 2013-01-27
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fixed bug in i18n syncing script
|
||||||
|
* Resynced Gherkin i18n
|
||||||
|
|
||||||
|
2.2.6 / 2013-01-26
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Support long row hashes in tables ([see](https://github.com/Behat/Gherkin/issues/40))
|
||||||
|
* Synced Gherkin i18n
|
||||||
|
|
||||||
|
2.2.5 / 2012-09-26
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fixed issue with loading empty features
|
||||||
|
* Synced Gherkin i18n
|
||||||
|
|
||||||
|
2.2.4 / 2012-08-03
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fixed exception message for "no loader found"
|
||||||
|
|
||||||
|
2.2.3 / 2012-08-03
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fixed minor loader bug with empty base path
|
||||||
|
* Synced Gherkin i18n
|
||||||
|
|
||||||
|
2.2.2 / 2012-07-01
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Added ability to filter outline scenarios by line and range filters
|
||||||
|
* Synced Gherkin i18n
|
||||||
|
* Refactored table parser to read row line numbers too
|
||||||
|
|
||||||
|
2.2.1 / 2012-05-04
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fixed StepNode `getLanguage()` and `getFile()`
|
||||||
|
|
||||||
|
2.2.0 / 2012-05-03
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Features freeze after parsing
|
||||||
|
* Implemented GherkinDumper (@Halleck45)
|
||||||
|
* Synced i18n with Cucumber
|
||||||
|
* Updated inline documentation
|
||||||
|
|
||||||
|
2.1.1 / 2012-03-09
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fixed caching bug, where `isFresh()` always returned false
|
||||||
|
|
||||||
|
2.1.0 / 2012-03-09
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Added parser caching layer
|
||||||
|
* Added support for table delimiter escaping (use `\|` for that)
|
||||||
|
* Added LineRangeFilter (thanks @headrevision)
|
||||||
|
* Synced i18n dictionary with cucumber/gherkin
|
||||||
|
|
||||||
|
2.0.2 / 2012-02-04
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Synced i18n dictionary with cucumber/gherkin
|
||||||
|
|
||||||
|
2.0.1 / 2012-01-26
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fixed issue about parsing features without indentation
|
||||||
|
|
||||||
|
2.0.0 / 2012-01-19
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Background titles support
|
||||||
|
* Correct parsing of titles/descriptions (hirarchy lexing)
|
||||||
|
* Migration to the cucumber/gherkin i18n dictionary
|
||||||
|
* Speed optimizations
|
||||||
|
* Refactored KeywordsDumper
|
||||||
|
* New loaders
|
||||||
|
* Bugfixes
|
||||||
|
|
||||||
|
1.1.4 / 2012-01-08
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Read feature description even if it looks like a step
|
||||||
|
|
||||||
|
1.1.3 / 2011-12-14
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Removed file loading routines from Parser (fixes `is_file()` issue on some systems - thanks
|
||||||
|
@flodocteurklein)
|
||||||
|
|
||||||
|
1.1.2 / 2011-12-01
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Updated spanish trasnaltion (@anbotero)
|
||||||
|
* Integration with Composer and Travis CI
|
||||||
|
|
||||||
|
1.1.1 / 2011-07-29
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Updated pt language step types (@danielcsgomes)
|
||||||
|
* Updated vendors
|
||||||
|
|
||||||
|
1.1.0 / 2011-07-16
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Return all tags, including inherited in `Scenario::getTags()`
|
||||||
|
* New `Feature::getOwnTags()` and `Scenario::getOwnTags()` method added,
|
||||||
|
which returns only own tags
|
||||||
|
|
||||||
|
1.0.8 / 2011-06-29
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fixed comments parsing.
|
||||||
|
You can’t have comments at the end of a line # like this
|
||||||
|
# But you can still have comments at the beginning of a line
|
||||||
|
|
||||||
|
1.0.7 / 2011-06-28
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Added `getRaw()` method to PyStringNode
|
||||||
|
* Updated vendors
|
||||||
|
|
||||||
|
1.0.6 / 2011-06-17
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Updated vendors
|
||||||
|
|
||||||
|
1.0.5 / 2011-06-10
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fixed bug, introduced with 1.0.4 - hash in PyStrings
|
||||||
|
|
||||||
|
1.0.4 / 2011-06-10
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fixed inability to comment pystrings
|
||||||
|
|
||||||
|
1.0.3 / 2011-04-21
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fixed introduced with 1.0.2 pystring parsing bug
|
||||||
|
|
||||||
|
1.0.2 / 2011-04-18
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Fixed bugs in text with comments parsing
|
||||||
|
|
||||||
|
1.0.1 / 2011-04-01
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Updated vendors
|
||||||
|
|
||||||
|
1.0.0 / 2011-03-08
|
||||||
|
==================
|
||||||
|
|
||||||
|
* Updated vendors
|
||||||
|
|
||||||
|
1.0.0RC2 / 2011-02-25
|
||||||
|
=====================
|
||||||
|
|
||||||
|
* Windows support
|
||||||
|
* Missing phpunit config
|
||||||
|
|
||||||
|
1.0.0RC1 / 2011-02-15
|
||||||
|
=====================
|
||||||
|
|
||||||
|
* Huge optimizations to Lexer & Parser
|
||||||
|
* Additional loaders (Yaml, Array, Directory)
|
||||||
|
* Filters (Tag, Name, Line)
|
||||||
|
* Code refactoring
|
||||||
|
* Nodes optimizations
|
||||||
|
* Additional tests for exceptions and translations
|
||||||
|
* Keywords dumper
|
||||||
|
|
||||||
|
0.2.0 / 2011-01-05
|
||||||
|
==================
|
||||||
|
|
||||||
|
* New Parser & Lexer (based on AST)
|
||||||
|
* New verbose parsing exception handling
|
||||||
|
* New translation mechanics
|
||||||
|
* 47 brand new translations (see i18n)
|
||||||
|
* Full test suite for everything from AST nodes to translations
|
||||||
Vendored
+33
@@ -0,0 +1,33 @@
|
|||||||
|
Contributing
|
||||||
|
------------
|
||||||
|
|
||||||
|
Gherkin is an open source, community-driven project. If you'd like to contribute, feel free to do this, but remember to follow this few simple rules:
|
||||||
|
|
||||||
|
- Make your feature addition or bug fix,
|
||||||
|
- Always use the `master` branch as base for your changes (all new development happens in `master`),
|
||||||
|
- Add tests for those changes (please look into `tests/` folder for some examples). This is important so we don't break it in a future version unintentionally,
|
||||||
|
- Commit your code, but do not mess with `CHANGES.md`,
|
||||||
|
- __Remember__: when you create Pull Request, always select `master` branch as target (done by default), otherwise it will be closed.
|
||||||
|
|
||||||
|
Running tests
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Make sure that you don't break anything with your changes by running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$> phpunit
|
||||||
|
```
|
||||||
|
|
||||||
|
Contributing to Gherkin Translations
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
Gherkin supports →40 different languages and you could add more! You might notice
|
||||||
|
`i18n.php` file in the root of the library. This file is downloaded and **autogenerated**
|
||||||
|
from original [cucumber/gherkin translations](https://github.com/cucumber/cucumber/blob/master/gherkin/gherkin-languages.json).
|
||||||
|
So, in order to fix/update/add some translation, you should send Pull Request to the
|
||||||
|
`cucumber/gherkin` repository. `Behat\Gherkin` will redownload/regenerate translations
|
||||||
|
from there before each release.
|
||||||
|
|
||||||
|
It might sounds difficult, but this way of dictionary sharing gives you ability to
|
||||||
|
migrate your `*.feature` files from language to language and library to library without
|
||||||
|
the need to rewrite/modify them - same dictionary (Gherkin) used everywhere.
|
||||||
Vendored
+22
@@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2011-2013 Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person
|
||||||
|
obtaining a copy of this software and associated documentation
|
||||||
|
files (the "Software"), to deal in the Software without
|
||||||
|
restriction, including without limitation the rights to use,
|
||||||
|
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the
|
||||||
|
Software is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||||
|
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
Vendored
+64
@@ -0,0 +1,64 @@
|
|||||||
|
Behat Gherkin Parser
|
||||||
|
====================
|
||||||
|
|
||||||
|
This is the php Gherkin parser for Behat. It comes bundled with more than 40 native languages
|
||||||
|
(see `i18n.php`) support & clean architecture.
|
||||||
|
|
||||||
|
Useful Links
|
||||||
|
------------
|
||||||
|
|
||||||
|
- Official Google Group is at [http://groups.google.com/group/behat](http://groups.google.com/group/behat)
|
||||||
|
- IRC channel on [#freenode](http://freenode.net/) is `#behat`
|
||||||
|
- [Note on Patches/Pull Requests](CONTRIBUTING.md)
|
||||||
|
|
||||||
|
Usage Example
|
||||||
|
-------------
|
||||||
|
|
||||||
|
``` php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$keywords = new Behat\Gherkin\Keywords\ArrayKeywords(array(
|
||||||
|
'en' => array(
|
||||||
|
'feature' => 'Feature',
|
||||||
|
'background' => 'Background',
|
||||||
|
'scenario' => 'Scenario',
|
||||||
|
'scenario_outline' => 'Scenario Outline|Scenario Template',
|
||||||
|
'examples' => 'Examples|Scenarios',
|
||||||
|
'given' => 'Given',
|
||||||
|
'when' => 'When',
|
||||||
|
'then' => 'Then',
|
||||||
|
'and' => 'And',
|
||||||
|
'but' => 'But'
|
||||||
|
),
|
||||||
|
'en-pirate' => array(
|
||||||
|
'feature' => 'Ahoy matey!',
|
||||||
|
'background' => 'Yo-ho-ho',
|
||||||
|
'scenario' => 'Heave to',
|
||||||
|
'scenario_outline' => 'Shiver me timbers',
|
||||||
|
'examples' => 'Dead men tell no tales',
|
||||||
|
'given' => 'Gangway!',
|
||||||
|
'when' => 'Blimey!',
|
||||||
|
'then' => 'Let go and haul',
|
||||||
|
'and' => 'Aye',
|
||||||
|
'but' => 'Avast!'
|
||||||
|
)
|
||||||
|
));
|
||||||
|
$lexer = new Behat\Gherkin\Lexer($keywords);
|
||||||
|
$parser = new Behat\Gherkin\Parser($lexer);
|
||||||
|
|
||||||
|
$feature = $parser->parse(file_get_contents('some.feature'));
|
||||||
|
```
|
||||||
|
|
||||||
|
Installing Dependencies
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
$> curl http://getcomposer.org/installer | php
|
||||||
|
$> php composer.phar update
|
||||||
|
```
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Konstantin Kudryashov [everzet](http://github.com/everzet) [lead developer]
|
||||||
|
* Other [awesome developers](https://github.com/Behat/Gherkin/graphs/contributors)
|
||||||
Vendored
+67
@@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"name": "behat/gherkin",
|
||||||
|
"description": "Gherkin DSL parser for PHP",
|
||||||
|
"keywords": ["BDD", "parser", "DSL", "Behat", "Gherkin", "Cucumber"],
|
||||||
|
"homepage": "http://behat.org/",
|
||||||
|
"type": "library",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Konstantin Kudryashov",
|
||||||
|
"email": "ever.zet@gmail.com",
|
||||||
|
"homepage": "http://everzet.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
"require": {
|
||||||
|
"php": "~7.2|~8.0"
|
||||||
|
},
|
||||||
|
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/yaml": "~3|~4|~5",
|
||||||
|
"phpunit/phpunit": "~8|~9",
|
||||||
|
"cucumber/cucumber": "dev-gherkin-22.0.0"
|
||||||
|
},
|
||||||
|
|
||||||
|
"suggest": {
|
||||||
|
"symfony/yaml": "If you want to parse features, represented in YAML files"
|
||||||
|
},
|
||||||
|
|
||||||
|
"autoload": {
|
||||||
|
"psr-0": {
|
||||||
|
"Behat\\Gherkin": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Tests\\Behat\\": "tests/Behat/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "4.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"repositories": [
|
||||||
|
{
|
||||||
|
"type": "package",
|
||||||
|
"package": {
|
||||||
|
"name": "cucumber/cucumber",
|
||||||
|
"version": "dev-gherkin-22.0.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/cucumber/cucumber.git",
|
||||||
|
"reference": "379280b6b98ee9a1bf79444e7c75e23265ccb3e0"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/cucumber/cucumber/zipball/379280b6b98ee9a1bf79444e7c75e23265ccb3e0",
|
||||||
|
"reference": "379280b6b98ee9a1bf79444e7c75e23265ccb3e0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+1245
File diff suppressed because it is too large
Load Diff
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return __DIR__;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Cache;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parser cache interface.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
interface CacheInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Checks that cache for feature exists and is fresh.
|
||||||
|
*
|
||||||
|
* @param string $path Feature path
|
||||||
|
* @param integer $timestamp The last time feature was updated
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isFresh($path, $timestamp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads feature cache from path.
|
||||||
|
*
|
||||||
|
* @param string $path Feature path
|
||||||
|
*
|
||||||
|
* @return FeatureNode
|
||||||
|
*/
|
||||||
|
public function read($path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches feature node.
|
||||||
|
*
|
||||||
|
* @param string $path Feature path
|
||||||
|
* @param FeatureNode $feature Feature instance
|
||||||
|
*/
|
||||||
|
public function write($path, FeatureNode $feature);
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Cache;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Exception\CacheException;
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
use Behat\Gherkin\Gherkin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File cache.
|
||||||
|
* Caches feature into a file.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class FileCache implements CacheInterface
|
||||||
|
{
|
||||||
|
private $path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes file cache.
|
||||||
|
*
|
||||||
|
* @param string $path Path to the folder where to store caches.
|
||||||
|
*
|
||||||
|
* @throws CacheException
|
||||||
|
*/
|
||||||
|
public function __construct($path)
|
||||||
|
{
|
||||||
|
$this->path = rtrim($path, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'v'.Gherkin::VERSION;
|
||||||
|
|
||||||
|
if (!is_dir($this->path)) {
|
||||||
|
@mkdir($this->path, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_writeable($this->path)) {
|
||||||
|
throw new CacheException(sprintf('Cache path "%s" is not writeable. Check your filesystem permissions or disable Gherkin file cache.', $this->path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that cache for feature exists and is fresh.
|
||||||
|
*
|
||||||
|
* @param string $path Feature path
|
||||||
|
* @param integer $timestamp The last time feature was updated
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isFresh($path, $timestamp)
|
||||||
|
{
|
||||||
|
$cachePath = $this->getCachePathFor($path);
|
||||||
|
|
||||||
|
if (!file_exists($cachePath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filemtime($cachePath) > $timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads feature cache from path.
|
||||||
|
*
|
||||||
|
* @param string $path Feature path
|
||||||
|
*
|
||||||
|
* @return FeatureNode
|
||||||
|
*
|
||||||
|
* @throws CacheException
|
||||||
|
*/
|
||||||
|
public function read($path)
|
||||||
|
{
|
||||||
|
$cachePath = $this->getCachePathFor($path);
|
||||||
|
$feature = unserialize(file_get_contents($cachePath));
|
||||||
|
|
||||||
|
if (!$feature instanceof FeatureNode) {
|
||||||
|
throw new CacheException(sprintf('Can not load cache for a feature "%s" from "%s".', $path, $cachePath ));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches feature node.
|
||||||
|
*
|
||||||
|
* @param string $path Feature path
|
||||||
|
* @param FeatureNode $feature Feature instance
|
||||||
|
*/
|
||||||
|
public function write($path, FeatureNode $feature)
|
||||||
|
{
|
||||||
|
file_put_contents($this->getCachePathFor($path), serialize($feature));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns feature cache file path from features path.
|
||||||
|
*
|
||||||
|
* @param string $path Feature path
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getCachePathFor($path)
|
||||||
|
{
|
||||||
|
return $this->path.'/'.md5($path).'.feature.cache';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Cache;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Memory cache.
|
||||||
|
* Caches feature into a memory.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class MemoryCache implements CacheInterface
|
||||||
|
{
|
||||||
|
private $features = array();
|
||||||
|
private $timestamps = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that cache for feature exists and is fresh.
|
||||||
|
*
|
||||||
|
* @param string $path Feature path
|
||||||
|
* @param integer $timestamp The last time feature was updated
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isFresh($path, $timestamp)
|
||||||
|
{
|
||||||
|
if (!isset($this->features[$path])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->timestamps[$path] > $timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads feature cache from path.
|
||||||
|
*
|
||||||
|
* @param string $path Feature path
|
||||||
|
*
|
||||||
|
* @return FeatureNode
|
||||||
|
*/
|
||||||
|
public function read($path)
|
||||||
|
{
|
||||||
|
return $this->features[$path];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caches feature node.
|
||||||
|
*
|
||||||
|
* @param string $path Feature path
|
||||||
|
* @param FeatureNode $feature Feature instance
|
||||||
|
*/
|
||||||
|
public function write($path, FeatureNode $feature)
|
||||||
|
{
|
||||||
|
$this->features[$path] = $feature;
|
||||||
|
$this->timestamps[$path] = time();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Exception;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache exception.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class CacheException extends RuntimeException implements Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Exception;
|
||||||
|
|
||||||
|
interface Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Exception;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class LexerException extends RuntimeException implements Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Exception;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class NodeException extends RuntimeException implements Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Exception;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class ParserException extends RuntimeException implements Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Filter;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract filter class.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
abstract class ComplexFilter implements ComplexFilterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Filters feature according to the filter.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature
|
||||||
|
*
|
||||||
|
* @return FeatureNode
|
||||||
|
*/
|
||||||
|
public function filterFeature(FeatureNode $feature)
|
||||||
|
{
|
||||||
|
$scenarios = array();
|
||||||
|
foreach ($feature->getScenarios() as $scenario) {
|
||||||
|
if (!$this->isScenarioMatch($feature, $scenario)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scenarios[] = $scenario;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FeatureNode(
|
||||||
|
$feature->getTitle(),
|
||||||
|
$feature->getDescription(),
|
||||||
|
$feature->getTags(),
|
||||||
|
$feature->getBackground(),
|
||||||
|
$scenarios,
|
||||||
|
$feature->getKeyword(),
|
||||||
|
$feature->getLanguage(),
|
||||||
|
$feature->getFile(),
|
||||||
|
$feature->getLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Filter;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
use Behat\Gherkin\Node\ScenarioInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter interface.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
interface ComplexFilterInterface extends FeatureFilterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Checks if scenario or outline matches specified filter.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature Feature node instance
|
||||||
|
* @param ScenarioInterface $scenario Scenario or Outline node instance
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isScenarioMatch(FeatureNode $feature, ScenarioInterface $scenario);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Filter;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature filter interface.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
interface FeatureFilterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Checks if Feature matches specified filter.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature Feature instance
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isFeatureMatch(FeatureNode $feature);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters feature according to the filter and returns new one.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature
|
||||||
|
*
|
||||||
|
* @return FeatureNode
|
||||||
|
*/
|
||||||
|
public function filterFeature(FeatureNode $feature);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Filter;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\ScenarioInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter interface.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
interface FilterInterface extends FeatureFilterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Checks if scenario or outline matches specified filter.
|
||||||
|
*
|
||||||
|
* @param ScenarioInterface $scenario Scenario or Outline node instance
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isScenarioMatch(ScenarioInterface $scenario);
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Filter;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\ExampleTableNode;
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
use Behat\Gherkin\Node\OutlineNode;
|
||||||
|
use Behat\Gherkin\Node\ScenarioInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters scenarios by definition line number.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class LineFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
protected $filterLine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes filter.
|
||||||
|
*
|
||||||
|
* @param string $filterLine Line of the scenario to filter on
|
||||||
|
*/
|
||||||
|
public function __construct($filterLine)
|
||||||
|
{
|
||||||
|
$this->filterLine = intval($filterLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if Feature matches specified filter.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature Feature instance
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isFeatureMatch(FeatureNode $feature)
|
||||||
|
{
|
||||||
|
return $this->filterLine === $feature->getLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if scenario or outline matches specified filter.
|
||||||
|
*
|
||||||
|
* @param ScenarioInterface $scenario Scenario or Outline node instance
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isScenarioMatch(ScenarioInterface $scenario)
|
||||||
|
{
|
||||||
|
if ($this->filterLine === $scenario->getLine()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
|
||||||
|
return $this->filterLine === $scenario->getLine()
|
||||||
|
|| in_array($this->filterLine, $scenario->getExampleTable()->getLines());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters feature according to the filter and returns new one.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature
|
||||||
|
*
|
||||||
|
* @return FeatureNode
|
||||||
|
*/
|
||||||
|
public function filterFeature(FeatureNode $feature)
|
||||||
|
{
|
||||||
|
$scenarios = array();
|
||||||
|
foreach ($feature->getScenarios() as $scenario) {
|
||||||
|
if (!$this->isScenarioMatch($scenario)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
|
||||||
|
foreach ($scenario->getExampleTables() as $exampleTable) {
|
||||||
|
$table = $exampleTable->getTable();
|
||||||
|
$lines = array_keys($table);
|
||||||
|
|
||||||
|
if (in_array($this->filterLine, $lines)) {
|
||||||
|
$filteredTable = array($lines[0] => $table[$lines[0]]);
|
||||||
|
|
||||||
|
if ($lines[0] !== $this->filterLine) {
|
||||||
|
$filteredTable[$this->filterLine] = $table[$this->filterLine];
|
||||||
|
}
|
||||||
|
|
||||||
|
$scenario = new OutlineNode(
|
||||||
|
$scenario->getTitle(),
|
||||||
|
$scenario->getTags(),
|
||||||
|
$scenario->getSteps(),
|
||||||
|
array(new ExampleTableNode($filteredTable, $exampleTable->getKeyword(), $exampleTable->getTags())),
|
||||||
|
$scenario->getKeyword(),
|
||||||
|
$scenario->getLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scenarios[] = $scenario;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FeatureNode(
|
||||||
|
$feature->getTitle(),
|
||||||
|
$feature->getDescription(),
|
||||||
|
$feature->getTags(),
|
||||||
|
$feature->getBackground(),
|
||||||
|
$scenarios,
|
||||||
|
$feature->getKeyword(),
|
||||||
|
$feature->getLanguage(),
|
||||||
|
$feature->getFile(),
|
||||||
|
$feature->getLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Filter;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\ExampleTableNode;
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
use Behat\Gherkin\Node\OutlineNode;
|
||||||
|
use Behat\Gherkin\Node\ScenarioInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters scenarios by definition line number range.
|
||||||
|
*
|
||||||
|
* @author Fabian Kiss <headrevision@gmail.com>
|
||||||
|
*/
|
||||||
|
class LineRangeFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
protected $filterMinLine;
|
||||||
|
protected $filterMaxLine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes filter.
|
||||||
|
*
|
||||||
|
* @param string $filterMinLine Minimum line of a scenario to filter on
|
||||||
|
* @param string $filterMaxLine Maximum line of a scenario to filter on
|
||||||
|
*/
|
||||||
|
public function __construct($filterMinLine, $filterMaxLine)
|
||||||
|
{
|
||||||
|
$this->filterMinLine = intval($filterMinLine);
|
||||||
|
if ($filterMaxLine == '*') {
|
||||||
|
$this->filterMaxLine = PHP_INT_MAX;
|
||||||
|
} else {
|
||||||
|
$this->filterMaxLine = intval($filterMaxLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if Feature matches specified filter.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature Feature instance
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isFeatureMatch(FeatureNode $feature)
|
||||||
|
{
|
||||||
|
return $this->filterMinLine <= $feature->getLine()
|
||||||
|
&& $this->filterMaxLine >= $feature->getLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if scenario or outline matches specified filter.
|
||||||
|
*
|
||||||
|
* @param ScenarioInterface $scenario Scenario or Outline node instance
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isScenarioMatch(ScenarioInterface $scenario)
|
||||||
|
{
|
||||||
|
if ($this->filterMinLine <= $scenario->getLine() && $this->filterMaxLine >= $scenario->getLine()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
|
||||||
|
foreach ($scenario->getExampleTable()->getLines() as $line) {
|
||||||
|
if ($this->filterMinLine <= $line && $this->filterMaxLine >= $line) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters feature according to the filter.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature
|
||||||
|
*
|
||||||
|
* @return FeatureNode
|
||||||
|
*/
|
||||||
|
public function filterFeature(FeatureNode $feature)
|
||||||
|
{
|
||||||
|
$scenarios = array();
|
||||||
|
foreach ($feature->getScenarios() as $scenario) {
|
||||||
|
if (!$this->isScenarioMatch($scenario)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
|
||||||
|
// first accumulate examples and then create scenario
|
||||||
|
$exampleTableNodes = array();
|
||||||
|
|
||||||
|
foreach ($scenario->getExampleTables() as $exampleTable) {
|
||||||
|
$table = $exampleTable->getTable();
|
||||||
|
$lines = array_keys($table);
|
||||||
|
|
||||||
|
$filteredTable = array($lines[0] => $table[$lines[0]]);
|
||||||
|
unset($table[$lines[0]]);
|
||||||
|
|
||||||
|
foreach ($table as $line => $row) {
|
||||||
|
if ($this->filterMinLine <= $line && $this->filterMaxLine >= $line) {
|
||||||
|
$filteredTable[$line] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($filteredTable) > 1) {
|
||||||
|
$exampleTableNodes[] = new ExampleTableNode($filteredTable, $exampleTable->getKeyword(), $exampleTable->getTags());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scenario = new OutlineNode(
|
||||||
|
$scenario->getTitle(),
|
||||||
|
$scenario->getTags(),
|
||||||
|
$scenario->getSteps(),
|
||||||
|
$exampleTableNodes,
|
||||||
|
$scenario->getKeyword(),
|
||||||
|
$scenario->getLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scenarios[] = $scenario;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FeatureNode(
|
||||||
|
$feature->getTitle(),
|
||||||
|
$feature->getDescription(),
|
||||||
|
$feature->getTags(),
|
||||||
|
$feature->getBackground(),
|
||||||
|
$scenarios,
|
||||||
|
$feature->getKeyword(),
|
||||||
|
$feature->getLanguage(),
|
||||||
|
$feature->getFile(),
|
||||||
|
$feature->getLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Filter;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
use Behat\Gherkin\Node\ScenarioInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters scenarios by feature/scenario name.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class NameFilter extends SimpleFilter
|
||||||
|
{
|
||||||
|
protected $filterString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes filter.
|
||||||
|
*
|
||||||
|
* @param string $filterString Name filter string
|
||||||
|
*/
|
||||||
|
public function __construct($filterString)
|
||||||
|
{
|
||||||
|
$this->filterString = trim($filterString);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if Feature matches specified filter.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature Feature instance
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isFeatureMatch(FeatureNode $feature)
|
||||||
|
{
|
||||||
|
if (null === $feature->getTitle()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('/' === $this->filterString[0]) {
|
||||||
|
return 1 === preg_match($this->filterString, $feature->getTitle());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false !== mb_strpos($feature->getTitle(), $this->filterString, 0, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if scenario or outline matches specified filter.
|
||||||
|
*
|
||||||
|
* @param ScenarioInterface $scenario Scenario or Outline node instance
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isScenarioMatch(ScenarioInterface $scenario)
|
||||||
|
{
|
||||||
|
if (null === $scenario->getTitle()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('/' === $this->filterString[0] && 1 === preg_match($this->filterString, $scenario->getTitle())) {
|
||||||
|
return true;
|
||||||
|
} elseif (false !== mb_strpos($scenario->getTitle(), $this->filterString, 0, 'utf8')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Filter;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\ScenarioInterface;
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters features by their narrative using regular expression.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class NarrativeFilter extends SimpleFilter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $regex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes filter.
|
||||||
|
*
|
||||||
|
* @param string $regex
|
||||||
|
*/
|
||||||
|
public function __construct($regex)
|
||||||
|
{
|
||||||
|
$this->regex = $regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if Feature matches specified filter.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature Feature instance
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isFeatureMatch(FeatureNode $feature)
|
||||||
|
{
|
||||||
|
return 1 === preg_match($this->regex, $feature->getDescription() ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if scenario or outline matches specified filter.
|
||||||
|
*
|
||||||
|
* @param ScenarioInterface $scenario Scenario or Outline node instance
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isScenarioMatch(ScenarioInterface $scenario)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Filter;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
use Behat\Gherkin\Node\ScenarioInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters features by their paths.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class PathsFilter extends SimpleFilter
|
||||||
|
{
|
||||||
|
protected $filterPaths = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes filter.
|
||||||
|
*
|
||||||
|
* @param string[] $paths List of approved paths
|
||||||
|
*/
|
||||||
|
public function __construct(array $paths)
|
||||||
|
{
|
||||||
|
$this->filterPaths = array_map(
|
||||||
|
function ($realpath) {
|
||||||
|
return rtrim($realpath, DIRECTORY_SEPARATOR) .
|
||||||
|
(is_dir($realpath) ? DIRECTORY_SEPARATOR : '');
|
||||||
|
},
|
||||||
|
array_filter(
|
||||||
|
array_map('realpath', $paths)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if Feature matches specified filter.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature Feature instance
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isFeatureMatch(FeatureNode $feature)
|
||||||
|
{
|
||||||
|
foreach ($this->filterPaths as $path) {
|
||||||
|
if (0 === strpos(realpath($feature->getFile()), $path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if scenario or outline matches specified filter.
|
||||||
|
*
|
||||||
|
* @param ScenarioInterface $scenario Scenario or Outline node instance
|
||||||
|
*
|
||||||
|
* @return false This filter is designed to work only with features
|
||||||
|
*/
|
||||||
|
public function isScenarioMatch(ScenarioInterface $scenario)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Filter;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
use Behat\Gherkin\Node\ScenarioInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters features by their actors role.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class RoleFilter extends SimpleFilter
|
||||||
|
{
|
||||||
|
protected $pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes filter.
|
||||||
|
*
|
||||||
|
* @param string $role Approved role wildcard
|
||||||
|
*/
|
||||||
|
public function __construct($role)
|
||||||
|
{
|
||||||
|
$this->pattern = '/as an? ' . strtr(preg_quote($role, '/'), array(
|
||||||
|
'\*' => '.*',
|
||||||
|
'\?' => '.',
|
||||||
|
'\[' => '[',
|
||||||
|
'\]' => ']'
|
||||||
|
)) . '[$\n]/i';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if Feature matches specified filter.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature Feature instance
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isFeatureMatch(FeatureNode $feature)
|
||||||
|
{
|
||||||
|
return 1 === preg_match($this->pattern, $feature->getDescription() ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if scenario or outline matches specified filter.
|
||||||
|
*
|
||||||
|
* @param ScenarioInterface $scenario Scenario or Outline node instance
|
||||||
|
*
|
||||||
|
* @return false This filter is designed to work only with features
|
||||||
|
*/
|
||||||
|
public function isScenarioMatch(ScenarioInterface $scenario)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Filter;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract filter class.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
abstract class SimpleFilter implements FilterInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Filters feature according to the filter.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature
|
||||||
|
*
|
||||||
|
* @return FeatureNode
|
||||||
|
*/
|
||||||
|
public function filterFeature(FeatureNode $feature)
|
||||||
|
{
|
||||||
|
if ($this->isFeatureMatch($feature)) {
|
||||||
|
return $feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scenarios = array();
|
||||||
|
foreach ($feature->getScenarios() as $scenario) {
|
||||||
|
if (!$this->isScenarioMatch($scenario)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scenarios[] = $scenario;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FeatureNode(
|
||||||
|
$feature->getTitle(),
|
||||||
|
$feature->getDescription(),
|
||||||
|
$feature->getTags(),
|
||||||
|
$feature->getBackground(),
|
||||||
|
$scenarios,
|
||||||
|
$feature->getKeyword(),
|
||||||
|
$feature->getLanguage(),
|
||||||
|
$feature->getFile(),
|
||||||
|
$feature->getLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Filter;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
use Behat\Gherkin\Node\OutlineNode;
|
||||||
|
use Behat\Gherkin\Node\ScenarioInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters scenarios by feature/scenario tag.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class TagFilter extends ComplexFilter
|
||||||
|
{
|
||||||
|
protected $filterString;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes filter.
|
||||||
|
*
|
||||||
|
* @param string $filterString Name filter string
|
||||||
|
*/
|
||||||
|
public function __construct($filterString)
|
||||||
|
{
|
||||||
|
$this->filterString = trim($filterString);
|
||||||
|
|
||||||
|
if(preg_match('/\s/u', $this->filterString)) {
|
||||||
|
trigger_error(
|
||||||
|
"Tags with whitespace are deprecated and may be removed in a future version",
|
||||||
|
E_USER_DEPRECATED
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters feature according to the filter.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature
|
||||||
|
*
|
||||||
|
* @return FeatureNode
|
||||||
|
*/
|
||||||
|
public function filterFeature(FeatureNode $feature)
|
||||||
|
{
|
||||||
|
$scenarios = array();
|
||||||
|
foreach ($feature->getScenarios() as $scenario) {
|
||||||
|
if (!$this->isScenarioMatch($feature, $scenario)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
|
||||||
|
|
||||||
|
$exampleTables = array();
|
||||||
|
|
||||||
|
foreach ($scenario->getExampleTables() as $exampleTable) {
|
||||||
|
if ($this->isTagsMatchCondition(array_merge($feature->getTags(), $scenario->getTags(), $exampleTable->getTags()))) {
|
||||||
|
$exampleTables[] = $exampleTable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$scenario = new OutlineNode(
|
||||||
|
$scenario->getTitle(),
|
||||||
|
$scenario->getTags(),
|
||||||
|
$scenario->getSteps(),
|
||||||
|
$exampleTables,
|
||||||
|
$scenario->getKeyword(),
|
||||||
|
$scenario->getLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$scenarios[] = $scenario;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FeatureNode(
|
||||||
|
$feature->getTitle(),
|
||||||
|
$feature->getDescription(),
|
||||||
|
$feature->getTags(),
|
||||||
|
$feature->getBackground(),
|
||||||
|
$scenarios,
|
||||||
|
$feature->getKeyword(),
|
||||||
|
$feature->getLanguage(),
|
||||||
|
$feature->getFile(),
|
||||||
|
$feature->getLine()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if Feature matches specified filter.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature Feature instance
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isFeatureMatch(FeatureNode $feature)
|
||||||
|
{
|
||||||
|
return $this->isTagsMatchCondition($feature->getTags());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if scenario or outline matches specified filter.
|
||||||
|
*
|
||||||
|
* @param FeatureNode $feature Feature node instance
|
||||||
|
* @param ScenarioInterface $scenario Scenario or Outline node instance
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isScenarioMatch(FeatureNode $feature, ScenarioInterface $scenario)
|
||||||
|
{
|
||||||
|
if ($scenario instanceof OutlineNode && $scenario->hasExamples()) {
|
||||||
|
foreach ($scenario->getExampleTables() as $example) {
|
||||||
|
if ($this->isTagsMatchCondition(array_merge($feature->getTags(), $scenario->getTags(), $example->getTags()))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->isTagsMatchCondition(array_merge($feature->getTags(), $scenario->getTags()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that node matches condition.
|
||||||
|
*
|
||||||
|
* @param string[] $tags
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function isTagsMatchCondition($tags)
|
||||||
|
{
|
||||||
|
$satisfies = true;
|
||||||
|
|
||||||
|
foreach (explode('&&', $this->filterString) as $andTags) {
|
||||||
|
$satisfiesComma = false;
|
||||||
|
|
||||||
|
foreach (explode(',', $andTags) as $tag) {
|
||||||
|
$tag = str_replace('@', '', trim($tag));
|
||||||
|
|
||||||
|
if ('~' === $tag[0]) {
|
||||||
|
$tag = mb_substr($tag, 1, mb_strlen($tag, 'utf8') - 1, 'utf8');
|
||||||
|
$satisfiesComma = !in_array($tag, $tags) || $satisfiesComma;
|
||||||
|
} else {
|
||||||
|
$satisfiesComma = in_array($tag, $tags) || $satisfiesComma;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$satisfies = $satisfiesComma && $satisfies;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $satisfies;
|
||||||
|
}
|
||||||
|
}
|
||||||
+142
@@ -0,0 +1,142 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Filter\FeatureFilterInterface;
|
||||||
|
use Behat\Gherkin\Filter\LineFilter;
|
||||||
|
use Behat\Gherkin\Filter\LineRangeFilter;
|
||||||
|
use Behat\Gherkin\Loader\FileLoaderInterface;
|
||||||
|
use Behat\Gherkin\Loader\LoaderInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gherkin manager.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class Gherkin
|
||||||
|
{
|
||||||
|
const VERSION = '4.8.0';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var LoaderInterface[]
|
||||||
|
*/
|
||||||
|
protected $loaders = array();
|
||||||
|
/**
|
||||||
|
* @var FeatureFilterInterface[]
|
||||||
|
*/
|
||||||
|
protected $filters = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds loader to manager.
|
||||||
|
*
|
||||||
|
* @param LoaderInterface $loader Feature loader
|
||||||
|
*/
|
||||||
|
public function addLoader(LoaderInterface $loader)
|
||||||
|
{
|
||||||
|
$this->loaders[] = $loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds filter to manager.
|
||||||
|
*
|
||||||
|
* @param FeatureFilterInterface $filter Feature filter
|
||||||
|
*/
|
||||||
|
public function addFilter(FeatureFilterInterface $filter)
|
||||||
|
{
|
||||||
|
$this->filters[] = $filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets filters to the parser.
|
||||||
|
*
|
||||||
|
* @param FeatureFilterInterface[] $filters
|
||||||
|
*/
|
||||||
|
public function setFilters(array $filters)
|
||||||
|
{
|
||||||
|
$this->filters = array();
|
||||||
|
array_map(array($this, 'addFilter'), $filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets base features path.
|
||||||
|
*
|
||||||
|
* @param string $path Loaders base path
|
||||||
|
*/
|
||||||
|
public function setBasePath($path)
|
||||||
|
{
|
||||||
|
foreach ($this->loaders as $loader) {
|
||||||
|
if ($loader instanceof FileLoaderInterface) {
|
||||||
|
$loader->setBasePath($path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads & filters resource with added loaders.
|
||||||
|
*
|
||||||
|
* @param mixed $resource Resource to load
|
||||||
|
* @param FeatureFilterInterface[] $filters Additional filters
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function load($resource, array $filters = array())
|
||||||
|
{
|
||||||
|
$filters = array_merge($this->filters, $filters);
|
||||||
|
|
||||||
|
$matches = array();
|
||||||
|
if (preg_match('/^(.*)\:(\d+)-(\d+|\*)$/', $resource, $matches)) {
|
||||||
|
$resource = $matches[1];
|
||||||
|
$filters[] = new LineRangeFilter($matches[2], $matches[3]);
|
||||||
|
} elseif (preg_match('/^(.*)\:(\d+)$/', $resource, $matches)) {
|
||||||
|
$resource = $matches[1];
|
||||||
|
$filters[] = new LineFilter($matches[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$loader = $this->resolveLoader($resource);
|
||||||
|
|
||||||
|
if (null === $loader) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$features = array();
|
||||||
|
foreach ($loader->load($resource) as $feature) {
|
||||||
|
foreach ($filters as $filter) {
|
||||||
|
$feature = $filter->filterFeature($feature);
|
||||||
|
|
||||||
|
if (!$feature->hasScenarios() && !$filter->isFeatureMatch($feature)) {
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$features[] = $feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $features;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves loader by resource.
|
||||||
|
*
|
||||||
|
* @param mixed $resource Resource to load
|
||||||
|
*
|
||||||
|
* @return LoaderInterface
|
||||||
|
*/
|
||||||
|
public function resolveLoader($resource)
|
||||||
|
{
|
||||||
|
foreach ($this->loaders as $loader) {
|
||||||
|
if ($loader->supports($resource)) {
|
||||||
|
return $loader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Keywords;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array initializable keywords holder.
|
||||||
|
*
|
||||||
|
* $keywords = new Behat\Gherkin\Keywords\ArrayKeywords(array(
|
||||||
|
* 'en' => array(
|
||||||
|
* 'feature' => 'Feature',
|
||||||
|
* 'background' => 'Background',
|
||||||
|
* 'scenario' => 'Scenario',
|
||||||
|
* 'scenario_outline' => 'Scenario Outline|Scenario Template',
|
||||||
|
* 'examples' => 'Examples|Scenarios',
|
||||||
|
* 'given' => 'Given',
|
||||||
|
* 'when' => 'When',
|
||||||
|
* 'then' => 'Then',
|
||||||
|
* 'and' => 'And',
|
||||||
|
* 'but' => 'But'
|
||||||
|
* ),
|
||||||
|
* 'ru' => array(
|
||||||
|
* 'feature' => 'Функционал',
|
||||||
|
* 'background' => 'Предыстория',
|
||||||
|
* 'scenario' => 'Сценарий',
|
||||||
|
* 'scenario_outline' => 'Структура сценария',
|
||||||
|
* 'examples' => 'Примеры',
|
||||||
|
* 'given' => 'Допустим',
|
||||||
|
* 'when' => 'Если',
|
||||||
|
* 'then' => 'То',
|
||||||
|
* 'and' => 'И',
|
||||||
|
* 'but' => 'Но'
|
||||||
|
* )
|
||||||
|
* ));
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class ArrayKeywords implements KeywordsInterface
|
||||||
|
{
|
||||||
|
private $keywords = array();
|
||||||
|
private $keywordString = array();
|
||||||
|
private $language;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes holder with keywords.
|
||||||
|
*
|
||||||
|
* @param array $keywords Keywords array
|
||||||
|
*/
|
||||||
|
public function __construct(array $keywords)
|
||||||
|
{
|
||||||
|
$this->keywords = $keywords;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets keywords holder language.
|
||||||
|
*
|
||||||
|
* @param string $language Language name
|
||||||
|
*/
|
||||||
|
public function setLanguage($language)
|
||||||
|
{
|
||||||
|
if (!isset($this->keywords[$language])) {
|
||||||
|
$this->language = 'en';
|
||||||
|
} else {
|
||||||
|
$this->language = $language;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Feature keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getFeatureKeywords()
|
||||||
|
{
|
||||||
|
return $this->keywords[$this->language]['feature'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Background keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getBackgroundKeywords()
|
||||||
|
{
|
||||||
|
return $this->keywords[$this->language]['background'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Scenario keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getScenarioKeywords()
|
||||||
|
{
|
||||||
|
return $this->keywords[$this->language]['scenario'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Scenario Outline keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getOutlineKeywords()
|
||||||
|
{
|
||||||
|
return $this->keywords[$this->language]['scenario_outline'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Examples keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getExamplesKeywords()
|
||||||
|
{
|
||||||
|
return $this->keywords[$this->language]['examples'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Given keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getGivenKeywords()
|
||||||
|
{
|
||||||
|
return $this->keywords[$this->language]['given'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns When keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getWhenKeywords()
|
||||||
|
{
|
||||||
|
return $this->keywords[$this->language]['when'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Then keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getThenKeywords()
|
||||||
|
{
|
||||||
|
return $this->keywords[$this->language]['then'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns And keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAndKeywords()
|
||||||
|
{
|
||||||
|
return $this->keywords[$this->language]['and'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns But keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getButKeywords()
|
||||||
|
{
|
||||||
|
return $this->keywords[$this->language]['but'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all step keywords (Given, When, Then, And, But).
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getStepKeywords()
|
||||||
|
{
|
||||||
|
if (!isset($this->keywordString[$this->language])) {
|
||||||
|
$keywords = array_merge(
|
||||||
|
explode('|', $this->getGivenKeywords()),
|
||||||
|
explode('|', $this->getWhenKeywords()),
|
||||||
|
explode('|', $this->getThenKeywords()),
|
||||||
|
explode('|', $this->getAndKeywords()),
|
||||||
|
explode('|', $this->getButKeywords())
|
||||||
|
);
|
||||||
|
|
||||||
|
usort($keywords, function ($keyword1, $keyword2) {
|
||||||
|
return mb_strlen($keyword2, 'utf8') - mb_strlen($keyword1, 'utf8');
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->keywordString[$this->language] = implode('|', $keywords);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->keywordString[$this->language];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Keywords;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File initializable keywords holder.
|
||||||
|
*
|
||||||
|
* $keywords = new Behat\Gherkin\Keywords\CachedArrayKeywords($file);
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class CachedArrayKeywords extends ArrayKeywords
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Initializes holder with file.
|
||||||
|
*
|
||||||
|
* @param string $file Cached array path
|
||||||
|
*/
|
||||||
|
public function __construct($file)
|
||||||
|
{
|
||||||
|
parent::__construct(include($file));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Keywords;
|
||||||
|
|
||||||
|
use Symfony\Component\Yaml\Exception\ParseException;
|
||||||
|
use Symfony\Component\Yaml\Yaml;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cucumber-translations reader.
|
||||||
|
*
|
||||||
|
* $keywords = new Behat\Gherkin\Keywords\CucumberKeywords($i18nYmlPath);
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class CucumberKeywords extends ArrayKeywords
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Initializes holder with yaml string OR file.
|
||||||
|
*
|
||||||
|
* @param string $yaml Yaml string or file path
|
||||||
|
*/
|
||||||
|
public function __construct($yaml)
|
||||||
|
{
|
||||||
|
// Handle filename explicitly for BC reasons, as Symfony Yaml 3.0 does not do it anymore
|
||||||
|
$file = null;
|
||||||
|
if (strpos($yaml, "\n") === false && is_file($yaml)) {
|
||||||
|
if (false === is_readable($yaml)) {
|
||||||
|
throw new ParseException(sprintf('Unable to parse "%s" as the file is not readable.', $yaml));
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = $yaml;
|
||||||
|
$yaml = file_get_contents($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$content = Yaml::parse($yaml);
|
||||||
|
} catch (ParseException $e) {
|
||||||
|
if ($file) {
|
||||||
|
$e->setParsedFile($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::__construct($content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Feature keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getGivenKeywords()
|
||||||
|
{
|
||||||
|
return $this->prepareStepString(parent::getGivenKeywords());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns When keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getWhenKeywords()
|
||||||
|
{
|
||||||
|
return $this->prepareStepString(parent::getWhenKeywords());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Then keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getThenKeywords()
|
||||||
|
{
|
||||||
|
return $this->prepareStepString(parent::getThenKeywords());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns And keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAndKeywords()
|
||||||
|
{
|
||||||
|
return $this->prepareStepString(parent::getAndKeywords());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns But keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getButKeywords()
|
||||||
|
{
|
||||||
|
return $this->prepareStepString(parent::getButKeywords());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trim *| from the begining of the list.
|
||||||
|
*
|
||||||
|
* @param string $keywordsString Keywords string
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function prepareStepString($keywordsString)
|
||||||
|
{
|
||||||
|
if (0 === mb_strpos($keywordsString, '*|', 0, 'UTF-8')) {
|
||||||
|
$keywordsString = mb_substr($keywordsString, 2, mb_strlen($keywordsString, 'utf8') - 2, 'utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $keywordsString;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,365 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Keywords;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gherkin keywords dumper.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class KeywordsDumper
|
||||||
|
{
|
||||||
|
private $keywords;
|
||||||
|
private $keywordsDumper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes dumper.
|
||||||
|
*
|
||||||
|
* @param KeywordsInterface $keywords Keywords instance
|
||||||
|
*/
|
||||||
|
public function __construct(KeywordsInterface $keywords)
|
||||||
|
{
|
||||||
|
$this->keywords = $keywords;
|
||||||
|
$this->keywordsDumper = array($this, 'dumpKeywords');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets keywords mapper function.
|
||||||
|
*
|
||||||
|
* Callable should accept 2 arguments (array $keywords and bool $isShort)
|
||||||
|
*
|
||||||
|
* @param callable $mapper Mapper function
|
||||||
|
*/
|
||||||
|
public function setKeywordsDumperFunction($mapper)
|
||||||
|
{
|
||||||
|
$this->keywordsDumper = $mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defaults keywords dumper.
|
||||||
|
*
|
||||||
|
* @param array $keywords Keywords list
|
||||||
|
* @param bool $isShort Is short version
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function dumpKeywords(array $keywords, $isShort)
|
||||||
|
{
|
||||||
|
if ($isShort) {
|
||||||
|
return 1 < count($keywords) ? '(' . implode('|', $keywords) . ')' : $keywords[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $keywords[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dumps keyworded feature into string.
|
||||||
|
*
|
||||||
|
* @param string $language Keywords language
|
||||||
|
* @param bool $short Dump short version
|
||||||
|
* @param bool $excludeAsterisk
|
||||||
|
*
|
||||||
|
* @return string|array String for short version and array of features for extended
|
||||||
|
*/
|
||||||
|
public function dump($language, $short = true, $excludeAsterisk = false)
|
||||||
|
{
|
||||||
|
$this->keywords->setLanguage($language);
|
||||||
|
$languageComment = '';
|
||||||
|
if ('en' !== $language) {
|
||||||
|
$languageComment = "# language: $language\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
$keywords = explode('|', $this->keywords->getFeatureKeywords());
|
||||||
|
|
||||||
|
if ($short) {
|
||||||
|
$keywords = call_user_func($this->keywordsDumper, $keywords, $short);
|
||||||
|
|
||||||
|
return trim($languageComment . $this->dumpFeature($keywords, $short, $excludeAsterisk));
|
||||||
|
}
|
||||||
|
|
||||||
|
$features = array();
|
||||||
|
foreach ($keywords as $keyword) {
|
||||||
|
$keyword = call_user_func($this->keywordsDumper, array($keyword), $short);
|
||||||
|
$features[] = trim($languageComment . $this->dumpFeature($keyword, $short, $excludeAsterisk));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $features;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dumps feature example.
|
||||||
|
*
|
||||||
|
* @param string $keyword Item keyword
|
||||||
|
* @param bool $short Dump short version?
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function dumpFeature($keyword, $short = true, $excludeAsterisk = false)
|
||||||
|
{
|
||||||
|
$dump = <<<GHERKIN
|
||||||
|
{$keyword}: Internal operations
|
||||||
|
In order to stay secret
|
||||||
|
As a secret organization
|
||||||
|
We need to be able to erase past agents' memory
|
||||||
|
|
||||||
|
|
||||||
|
GHERKIN;
|
||||||
|
|
||||||
|
// Background
|
||||||
|
$keywords = explode('|', $this->keywords->getBackgroundKeywords());
|
||||||
|
if ($short) {
|
||||||
|
$keywords = call_user_func($this->keywordsDumper, $keywords, $short);
|
||||||
|
$dump .= $this->dumpBackground($keywords, $short, $excludeAsterisk);
|
||||||
|
} else {
|
||||||
|
$keyword = call_user_func($this->keywordsDumper, array($keywords[0]), $short);
|
||||||
|
$dump .= $this->dumpBackground($keyword, $short, $excludeAsterisk);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario
|
||||||
|
$keywords = explode('|', $this->keywords->getScenarioKeywords());
|
||||||
|
if ($short) {
|
||||||
|
$keywords = call_user_func($this->keywordsDumper, $keywords, $short);
|
||||||
|
$dump .= $this->dumpScenario($keywords, $short, $excludeAsterisk);
|
||||||
|
} else {
|
||||||
|
foreach ($keywords as $keyword) {
|
||||||
|
$keyword = call_user_func($this->keywordsDumper, array($keyword), $short);
|
||||||
|
$dump .= $this->dumpScenario($keyword, $short, $excludeAsterisk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Outline
|
||||||
|
$keywords = explode('|', $this->keywords->getOutlineKeywords());
|
||||||
|
if ($short) {
|
||||||
|
$keywords = call_user_func($this->keywordsDumper, $keywords, $short);
|
||||||
|
$dump .= $this->dumpOutline($keywords, $short, $excludeAsterisk);
|
||||||
|
} else {
|
||||||
|
foreach ($keywords as $keyword) {
|
||||||
|
$keyword = call_user_func($this->keywordsDumper, array($keyword), $short);
|
||||||
|
$dump .= $this->dumpOutline($keyword, $short, $excludeAsterisk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dump;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dumps background example.
|
||||||
|
*
|
||||||
|
* @param string $keyword Item keyword
|
||||||
|
* @param bool $short Dump short version?
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function dumpBackground($keyword, $short = true, $excludeAsterisk = false)
|
||||||
|
{
|
||||||
|
$dump = <<<GHERKIN
|
||||||
|
{$keyword}:
|
||||||
|
|
||||||
|
GHERKIN;
|
||||||
|
|
||||||
|
// Given
|
||||||
|
$dump .= $this->dumpStep(
|
||||||
|
$this->keywords->getGivenKeywords(),
|
||||||
|
'there is agent A',
|
||||||
|
$short,
|
||||||
|
$excludeAsterisk
|
||||||
|
);
|
||||||
|
|
||||||
|
// And
|
||||||
|
$dump .= $this->dumpStep(
|
||||||
|
$this->keywords->getAndKeywords(),
|
||||||
|
'there is agent B',
|
||||||
|
$short,
|
||||||
|
$excludeAsterisk
|
||||||
|
);
|
||||||
|
|
||||||
|
return $dump . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dumps scenario example.
|
||||||
|
*
|
||||||
|
* @param string $keyword Item keyword
|
||||||
|
* @param bool $short Dump short version?
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function dumpScenario($keyword, $short = true, $excludeAsterisk = false)
|
||||||
|
{
|
||||||
|
$dump = <<<GHERKIN
|
||||||
|
{$keyword}: Erasing agent memory
|
||||||
|
|
||||||
|
GHERKIN;
|
||||||
|
|
||||||
|
// Given
|
||||||
|
$dump .= $this->dumpStep(
|
||||||
|
$this->keywords->getGivenKeywords(),
|
||||||
|
'there is agent J',
|
||||||
|
$short,
|
||||||
|
$excludeAsterisk
|
||||||
|
);
|
||||||
|
|
||||||
|
// And
|
||||||
|
$dump .= $this->dumpStep(
|
||||||
|
$this->keywords->getAndKeywords(),
|
||||||
|
'there is agent K',
|
||||||
|
$short,
|
||||||
|
$excludeAsterisk
|
||||||
|
);
|
||||||
|
|
||||||
|
// When
|
||||||
|
$dump .= $this->dumpStep(
|
||||||
|
$this->keywords->getWhenKeywords(),
|
||||||
|
'I erase agent K\'s memory',
|
||||||
|
$short,
|
||||||
|
$excludeAsterisk
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
$dump .= $this->dumpStep(
|
||||||
|
$this->keywords->getThenKeywords(),
|
||||||
|
'there should be agent J',
|
||||||
|
$short,
|
||||||
|
$excludeAsterisk
|
||||||
|
);
|
||||||
|
|
||||||
|
// But
|
||||||
|
$dump .= $this->dumpStep(
|
||||||
|
$this->keywords->getButKeywords(),
|
||||||
|
'there should not be agent K',
|
||||||
|
$short,
|
||||||
|
$excludeAsterisk
|
||||||
|
);
|
||||||
|
|
||||||
|
return $dump . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dumps outline example.
|
||||||
|
*
|
||||||
|
* @param string $keyword Item keyword
|
||||||
|
* @param bool $short Dump short version?
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function dumpOutline($keyword, $short = true, $excludeAsterisk = false)
|
||||||
|
{
|
||||||
|
$dump = <<<GHERKIN
|
||||||
|
{$keyword}: Erasing other agents' memory
|
||||||
|
|
||||||
|
GHERKIN;
|
||||||
|
|
||||||
|
// Given
|
||||||
|
$dump .= $this->dumpStep(
|
||||||
|
$this->keywords->getGivenKeywords(),
|
||||||
|
'there is agent <agent1>',
|
||||||
|
$short,
|
||||||
|
$excludeAsterisk
|
||||||
|
);
|
||||||
|
|
||||||
|
// And
|
||||||
|
$dump .= $this->dumpStep(
|
||||||
|
$this->keywords->getAndKeywords(),
|
||||||
|
'there is agent <agent2>',
|
||||||
|
$short,
|
||||||
|
$excludeAsterisk
|
||||||
|
);
|
||||||
|
|
||||||
|
// When
|
||||||
|
$dump .= $this->dumpStep(
|
||||||
|
$this->keywords->getWhenKeywords(),
|
||||||
|
'I erase agent <agent2>\'s memory',
|
||||||
|
$short,
|
||||||
|
$excludeAsterisk
|
||||||
|
);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
$dump .= $this->dumpStep(
|
||||||
|
$this->keywords->getThenKeywords(),
|
||||||
|
'there should be agent <agent1>',
|
||||||
|
$short,
|
||||||
|
$excludeAsterisk
|
||||||
|
);
|
||||||
|
|
||||||
|
// But
|
||||||
|
$dump .= $this->dumpStep(
|
||||||
|
$this->keywords->getButKeywords(),
|
||||||
|
'there should not be agent <agent2>',
|
||||||
|
$short,
|
||||||
|
$excludeAsterisk
|
||||||
|
);
|
||||||
|
|
||||||
|
$keywords = explode('|', $this->keywords->getExamplesKeywords());
|
||||||
|
if ($short) {
|
||||||
|
$keyword = call_user_func($this->keywordsDumper, $keywords, $short);
|
||||||
|
} else {
|
||||||
|
$keyword = call_user_func($this->keywordsDumper, array($keywords[0]), $short);
|
||||||
|
}
|
||||||
|
|
||||||
|
$dump .= <<<GHERKIN
|
||||||
|
|
||||||
|
{$keyword}:
|
||||||
|
| agent1 | agent2 |
|
||||||
|
| D | M |
|
||||||
|
|
||||||
|
GHERKIN;
|
||||||
|
|
||||||
|
return $dump . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dumps step example.
|
||||||
|
*
|
||||||
|
* @param string $keywords Item keyword
|
||||||
|
* @param string $text Step text
|
||||||
|
* @param bool $short Dump short version?
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function dumpStep($keywords, $text, $short = true, $excludeAsterisk = false)
|
||||||
|
{
|
||||||
|
$dump = '';
|
||||||
|
|
||||||
|
$keywords = explode('|', $keywords);
|
||||||
|
if ($short) {
|
||||||
|
$keywords = array_map(
|
||||||
|
function ($keyword) {
|
||||||
|
return str_replace('<', '', $keyword);
|
||||||
|
},
|
||||||
|
$keywords
|
||||||
|
);
|
||||||
|
$keywords = call_user_func($this->keywordsDumper, $keywords, $short);
|
||||||
|
$dump .= <<<GHERKIN
|
||||||
|
{$keywords} {$text}
|
||||||
|
|
||||||
|
GHERKIN;
|
||||||
|
} else {
|
||||||
|
foreach ($keywords as $keyword) {
|
||||||
|
if ($excludeAsterisk && '*' === $keyword) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$indent = ' ';
|
||||||
|
if (false !== mb_strpos($keyword, '<', 0, 'utf8')) {
|
||||||
|
$keyword = mb_substr($keyword, 0, -1, 'utf8');
|
||||||
|
$indent = '';
|
||||||
|
}
|
||||||
|
$keyword = call_user_func($this->keywordsDumper, array($keyword), $short);
|
||||||
|
$dump .= <<<GHERKIN
|
||||||
|
{$keyword}{$indent}{$text}
|
||||||
|
|
||||||
|
GHERKIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dump;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Keywords;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keywords holder interface.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
interface KeywordsInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Sets keywords holder language.
|
||||||
|
*
|
||||||
|
* @param string $language Language name
|
||||||
|
*/
|
||||||
|
public function setLanguage($language);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Feature keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getFeatureKeywords();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Background keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getBackgroundKeywords();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Scenario keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getScenarioKeywords();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Scenario Outline keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getOutlineKeywords();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Examples keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getExamplesKeywords();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Given keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getGivenKeywords();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns When keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getWhenKeywords();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Then keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getThenKeywords();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns And keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAndKeywords();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns But keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getButKeywords();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all step keywords (splitted by "|").
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getStepKeywords();
|
||||||
|
}
|
||||||
+653
@@ -0,0 +1,653 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Exception\LexerException;
|
||||||
|
use Behat\Gherkin\Keywords\KeywordsInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gherkin lexer.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class Lexer
|
||||||
|
{
|
||||||
|
private $language;
|
||||||
|
private $lines;
|
||||||
|
private $linesCount;
|
||||||
|
private $line;
|
||||||
|
private $trimmedLine;
|
||||||
|
private $lineNumber;
|
||||||
|
private $eos;
|
||||||
|
private $keywords;
|
||||||
|
private $keywordsCache = array();
|
||||||
|
private $stepKeywordTypesCache = array();
|
||||||
|
private $deferredObjects = array();
|
||||||
|
private $deferredObjectsCount = 0;
|
||||||
|
private $stashedToken;
|
||||||
|
private $inPyString = false;
|
||||||
|
private $pyStringSwallow = 0;
|
||||||
|
private $featureStarted = false;
|
||||||
|
private $allowMultilineArguments = false;
|
||||||
|
private $allowSteps = false;
|
||||||
|
private $pyStringDelimiter = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes lexer.
|
||||||
|
*
|
||||||
|
* @param KeywordsInterface $keywords Keywords holder
|
||||||
|
*/
|
||||||
|
public function __construct(KeywordsInterface $keywords)
|
||||||
|
{
|
||||||
|
$this->keywords = $keywords;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets lexer input.
|
||||||
|
*
|
||||||
|
* @param string $input Input string
|
||||||
|
* @param string $language Language name
|
||||||
|
*
|
||||||
|
* @throws Exception\LexerException
|
||||||
|
*/
|
||||||
|
public function analyse($input, $language = 'en')
|
||||||
|
{
|
||||||
|
// try to detect unsupported encoding
|
||||||
|
if ('UTF-8' !== mb_detect_encoding($input, 'UTF-8', true)) {
|
||||||
|
throw new LexerException('Feature file is not in UTF8 encoding');
|
||||||
|
}
|
||||||
|
|
||||||
|
$input = strtr($input, array("\r\n" => "\n", "\r" => "\n"));
|
||||||
|
|
||||||
|
$this->lines = explode("\n", $input);
|
||||||
|
$this->linesCount = count($this->lines);
|
||||||
|
$this->line = $this->lines[0];
|
||||||
|
$this->lineNumber = 1;
|
||||||
|
$this->trimmedLine = null;
|
||||||
|
$this->eos = false;
|
||||||
|
|
||||||
|
$this->deferredObjects = array();
|
||||||
|
$this->deferredObjectsCount = 0;
|
||||||
|
$this->stashedToken = null;
|
||||||
|
$this->inPyString = false;
|
||||||
|
$this->pyStringSwallow = 0;
|
||||||
|
|
||||||
|
$this->featureStarted = false;
|
||||||
|
$this->allowMultilineArguments = false;
|
||||||
|
$this->allowSteps = false;
|
||||||
|
|
||||||
|
$this->keywords->setLanguage($this->language = $language);
|
||||||
|
$this->keywordsCache = array();
|
||||||
|
$this->stepKeywordTypesCache = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns current lexer language.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getLanguage()
|
||||||
|
{
|
||||||
|
return $this->language;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns next token or previously stashed one.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getAdvancedToken()
|
||||||
|
{
|
||||||
|
return $this->getStashedToken() ?: $this->getNextToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defers token.
|
||||||
|
*
|
||||||
|
* @param array $token Token to defer
|
||||||
|
*/
|
||||||
|
public function deferToken(array $token)
|
||||||
|
{
|
||||||
|
$token['deferred'] = true;
|
||||||
|
$this->deferredObjects[] = $token;
|
||||||
|
++$this->deferredObjectsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Predicts the upcoming token without passing over it.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function predictToken()
|
||||||
|
{
|
||||||
|
if (null === $this->stashedToken) {
|
||||||
|
$this->stashedToken = $this->getNextToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->stashedToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips over the currently-predicted token, if any.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function skipPredictedToken()
|
||||||
|
{
|
||||||
|
$this->stashedToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs token with specified parameters.
|
||||||
|
*
|
||||||
|
* @param string $type Token type
|
||||||
|
* @param string $value Token value
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function takeToken($type, $value = null)
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
'type' => $type,
|
||||||
|
'line' => $this->lineNumber,
|
||||||
|
'value' => $value ?: null,
|
||||||
|
'deferred' => false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consumes line from input & increments line counter.
|
||||||
|
*/
|
||||||
|
protected function consumeLine()
|
||||||
|
{
|
||||||
|
++$this->lineNumber;
|
||||||
|
|
||||||
|
if (($this->lineNumber - 1) === $this->linesCount) {
|
||||||
|
$this->eos = true;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->line = $this->lines[$this->lineNumber - 1];
|
||||||
|
$this->trimmedLine = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consumes first part of line from input without incrementing the line number
|
||||||
|
*/
|
||||||
|
protected function consumeLineUntil(int $trimmedOffset)
|
||||||
|
{
|
||||||
|
$this->line = mb_substr(ltrim($this->line), $trimmedOffset, null, 'utf-8');
|
||||||
|
$this->trimmedLine = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns trimmed version of line.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getTrimmedLine()
|
||||||
|
{
|
||||||
|
return null !== $this->trimmedLine ? $this->trimmedLine : $this->trimmedLine = trim($this->line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns stashed token or null if hasn't.
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function getStashedToken()
|
||||||
|
{
|
||||||
|
$stashedToken = $this->stashedToken;
|
||||||
|
$this->stashedToken = null;
|
||||||
|
|
||||||
|
return $stashedToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns deferred token or null if hasn't.
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
protected function getDeferredToken()
|
||||||
|
{
|
||||||
|
if (!$this->deferredObjectsCount) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
--$this->deferredObjectsCount;
|
||||||
|
|
||||||
|
return array_shift($this->deferredObjects);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns next token from input.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function getNextToken()
|
||||||
|
{
|
||||||
|
return $this->getDeferredToken()
|
||||||
|
?: $this->scanEOS()
|
||||||
|
?: $this->scanLanguage()
|
||||||
|
?: $this->scanComment()
|
||||||
|
?: $this->scanPyStringOp()
|
||||||
|
?: $this->scanPyStringContent()
|
||||||
|
?: $this->scanStep()
|
||||||
|
?: $this->scanScenario()
|
||||||
|
?: $this->scanBackground()
|
||||||
|
?: $this->scanOutline()
|
||||||
|
?: $this->scanExamples()
|
||||||
|
?: $this->scanFeature()
|
||||||
|
?: $this->scanTags()
|
||||||
|
?: $this->scanTableRow()
|
||||||
|
?: $this->scanNewline()
|
||||||
|
?: $this->scanText();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans for token with specified regex.
|
||||||
|
*
|
||||||
|
* @param string $regex Regular expression
|
||||||
|
* @param string $type Expected token type
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanInput($regex, $type)
|
||||||
|
{
|
||||||
|
if (!preg_match($regex, $this->line, $matches)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->takeToken($type, $matches[1]);
|
||||||
|
$this->consumeLine();
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans for token with specified keywords.
|
||||||
|
*
|
||||||
|
* @param string $keywords Keywords (splitted with |)
|
||||||
|
* @param string $type Expected token type
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanInputForKeywords($keywords, $type)
|
||||||
|
{
|
||||||
|
if (!preg_match('/^(\s*)(' . $keywords . '):\s*(.*)/u', $this->line, $matches)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->takeToken($type, $matches[3]);
|
||||||
|
$token['keyword'] = $matches[2];
|
||||||
|
$token['indent'] = mb_strlen($matches[1], 'utf8');
|
||||||
|
|
||||||
|
$this->consumeLine();
|
||||||
|
|
||||||
|
// turn off language searching
|
||||||
|
if ('Feature' === $type) {
|
||||||
|
$this->featureStarted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn off PyString and Table searching
|
||||||
|
if ('Feature' === $type || 'Scenario' === $type || 'Outline' === $type) {
|
||||||
|
$this->allowMultilineArguments = false;
|
||||||
|
} elseif ('Examples' === $type) {
|
||||||
|
$this->allowMultilineArguments = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn on steps searching
|
||||||
|
if ('Scenario' === $type || 'Background' === $type || 'Outline' === $type) {
|
||||||
|
$this->allowSteps = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans EOS from input & returns it if found.
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanEOS()
|
||||||
|
{
|
||||||
|
if (!$this->eos) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->takeToken('EOS');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns keywords for provided type.
|
||||||
|
*
|
||||||
|
* @param string $type Keyword type
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getKeywords($type)
|
||||||
|
{
|
||||||
|
if (!isset($this->keywordsCache[$type])) {
|
||||||
|
$getter = 'get' . $type . 'Keywords';
|
||||||
|
$keywords = $this->keywords->$getter();
|
||||||
|
|
||||||
|
if ('Step' === $type) {
|
||||||
|
$padded = array();
|
||||||
|
foreach (explode('|', $keywords) as $keyword) {
|
||||||
|
$padded[] = false !== mb_strpos($keyword, '<', 0, 'utf8')
|
||||||
|
? preg_quote(mb_substr($keyword, 0, -1, 'utf8'), '/') . '\s*'
|
||||||
|
: preg_quote($keyword, '/') . '\s+';
|
||||||
|
}
|
||||||
|
|
||||||
|
$keywords = implode('|', $padded);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->keywordsCache[$type] = $keywords;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->keywordsCache[$type];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans Feature from input & returns it if found.
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanFeature()
|
||||||
|
{
|
||||||
|
return $this->scanInputForKeywords($this->getKeywords('Feature'), 'Feature');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans Background from input & returns it if found.
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanBackground()
|
||||||
|
{
|
||||||
|
return $this->scanInputForKeywords($this->getKeywords('Background'), 'Background');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans Scenario from input & returns it if found.
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanScenario()
|
||||||
|
{
|
||||||
|
return $this->scanInputForKeywords($this->getKeywords('Scenario'), 'Scenario');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans Scenario Outline from input & returns it if found.
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanOutline()
|
||||||
|
{
|
||||||
|
return $this->scanInputForKeywords($this->getKeywords('Outline'), 'Outline');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans Scenario Outline Examples from input & returns it if found.
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanExamples()
|
||||||
|
{
|
||||||
|
return $this->scanInputForKeywords($this->getKeywords('Examples'), 'Examples');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans Step from input & returns it if found.
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanStep()
|
||||||
|
{
|
||||||
|
if (!$this->allowSteps) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$keywords = $this->getKeywords('Step');
|
||||||
|
if (!preg_match('/^\s*(' . $keywords . ')([^\s].*)/u', $this->line, $matches)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$keyword = trim($matches[1]);
|
||||||
|
$token = $this->takeToken('Step', $keyword);
|
||||||
|
$token['keyword_type'] = $this->getStepKeywordType($keyword);
|
||||||
|
$token['text'] = $matches[2];
|
||||||
|
|
||||||
|
$this->consumeLine();
|
||||||
|
$this->allowMultilineArguments = true;
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans PyString from input & returns it if found.
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanPyStringOp()
|
||||||
|
{
|
||||||
|
if (!$this->allowMultilineArguments) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!preg_match('/^\s*(?<delimiter>"""|```)/u', $this->line, $matches, PREG_OFFSET_CAPTURE)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
['delimiter' => [0 => $delimiter, 1 => $indent]] = $matches;
|
||||||
|
|
||||||
|
if ($this->inPyString) {
|
||||||
|
if ($this->pyStringDelimiter !== $delimiter) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$this->pyStringDelimiter = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->pyStringDelimiter= $delimiter;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->inPyString = !$this->inPyString;
|
||||||
|
$token = $this->takeToken('PyStringOp');
|
||||||
|
$this->pyStringSwallow = $indent;
|
||||||
|
|
||||||
|
$this->consumeLine();
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans PyString content.
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanPyStringContent()
|
||||||
|
{
|
||||||
|
if (!$this->inPyString) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->scanText();
|
||||||
|
// swallow trailing spaces
|
||||||
|
$token['value'] = preg_replace('/^\s{0,' . $this->pyStringSwallow . '}/u', '', $token['value'] ?? '');
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans Table Row from input & returns it if found.
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanTableRow()
|
||||||
|
{
|
||||||
|
if (!$this->allowMultilineArguments) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$line = $this->getTrimmedLine();
|
||||||
|
if (!isset($line[0]) || '|' !== $line[0] || '|' !== substr($line, -1)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->takeToken('TableRow');
|
||||||
|
$line = mb_substr($line, 1, mb_strlen($line, 'utf8') - 2, 'utf8');
|
||||||
|
$columns = array_map(function ($column) {
|
||||||
|
return trim(str_replace('\\|', '|', $column));
|
||||||
|
}, preg_split('/(?<!\\\)\|/u', $line));
|
||||||
|
$token['columns'] = $columns;
|
||||||
|
|
||||||
|
$this->consumeLine();
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans Tags from input & returns it if found.
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanTags()
|
||||||
|
{
|
||||||
|
$line = $this->getTrimmedLine();
|
||||||
|
|
||||||
|
if (!isset($line[0]) || '@' !== $line[0]) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(preg_match('/^(?<line>.*)\s+#.*$/', $line, $matches)) {
|
||||||
|
['line' => $line] = $matches;
|
||||||
|
$this->consumeLineUntil(mb_strlen($line, 'utf-8'));
|
||||||
|
} else {
|
||||||
|
$this->consumeLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->takeToken('Tag');
|
||||||
|
$tags = explode('@', mb_substr($line, 1, mb_strlen($line, 'utf8') - 1, 'utf8'));
|
||||||
|
$tags = array_map('trim', $tags);
|
||||||
|
$token['tags'] = $tags;
|
||||||
|
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans Language specifier from input & returns it if found.
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanLanguage()
|
||||||
|
{
|
||||||
|
if ($this->featureStarted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->inPyString) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 !== mb_strpos(ltrim($this->line), '#', 0, 'utf8')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->scanInput('/^\s*\#\s*language:\s*([\w_\-]+)\s*$/', 'Language');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans Comment from input & returns it if found.
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanComment()
|
||||||
|
{
|
||||||
|
if ($this->inPyString) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$line = $this->getTrimmedLine();
|
||||||
|
if (0 !== mb_strpos($line, '#', 0, 'utf8')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->takeToken('Comment', $line);
|
||||||
|
$this->consumeLine();
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans Newline from input & returns it if found.
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanNewline()
|
||||||
|
{
|
||||||
|
if ('' !== $this->getTrimmedLine()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$token = $this->takeToken('Newline', mb_strlen($this->line, 'utf8'));
|
||||||
|
$this->consumeLine();
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans text from input & returns it if found.
|
||||||
|
*
|
||||||
|
* @return null|array
|
||||||
|
*/
|
||||||
|
protected function scanText()
|
||||||
|
{
|
||||||
|
$token = $this->takeToken('Text', $this->line);
|
||||||
|
$this->consumeLine();
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns step type keyword (Given, When, Then, etc.).
|
||||||
|
*
|
||||||
|
* @param string $native Step keyword in provided language
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getStepKeywordType($native)
|
||||||
|
{
|
||||||
|
// Consider "*" as a AND keyword so that it is normalized to the previous step type
|
||||||
|
if ('*' === $native) {
|
||||||
|
return 'And';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->stepKeywordTypesCache)) {
|
||||||
|
$this->stepKeywordTypesCache = array(
|
||||||
|
'Given' => explode('|', $this->keywords->getGivenKeywords()),
|
||||||
|
'When' => explode('|', $this->keywords->getWhenKeywords()),
|
||||||
|
'Then' => explode('|', $this->keywords->getThenKeywords()),
|
||||||
|
'And' => explode('|', $this->keywords->getAndKeywords()),
|
||||||
|
'But' => explode('|', $this->keywords->getButKeywords())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->stepKeywordTypesCache as $type => $keywords) {
|
||||||
|
if (in_array($native, $keywords) || in_array($native . '<', $keywords)) {
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Given';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Loader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract filesystem loader.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
abstract class AbstractFileLoader implements FileLoaderInterface
|
||||||
|
{
|
||||||
|
protected $basePath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets base features path.
|
||||||
|
*
|
||||||
|
* @param string $path Base loader path
|
||||||
|
*/
|
||||||
|
public function setBasePath($path)
|
||||||
|
{
|
||||||
|
$this->basePath = realpath($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds relative path for provided absolute (relative to base features path).
|
||||||
|
*
|
||||||
|
* @param string $path Absolute path
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function findRelativePath($path)
|
||||||
|
{
|
||||||
|
if (null !== $this->basePath) {
|
||||||
|
return strtr($path, array($this->basePath . DIRECTORY_SEPARATOR => ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds absolute path for provided relative (relative to base features path).
|
||||||
|
*
|
||||||
|
* @param string $path Relative path
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function findAbsolutePath($path)
|
||||||
|
{
|
||||||
|
if (is_file($path) || is_dir($path)) {
|
||||||
|
return realpath($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === $this->basePath) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_file($this->basePath . DIRECTORY_SEPARATOR . $path)
|
||||||
|
|| is_dir($this->basePath . DIRECTORY_SEPARATOR . $path)) {
|
||||||
|
return realpath($this->basePath . DIRECTORY_SEPARATOR . $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Loader;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\BackgroundNode;
|
||||||
|
use Behat\Gherkin\Node\ExampleTableNode;
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
use Behat\Gherkin\Node\OutlineNode;
|
||||||
|
use Behat\Gherkin\Node\PyStringNode;
|
||||||
|
use Behat\Gherkin\Node\ScenarioNode;
|
||||||
|
use Behat\Gherkin\Node\StepNode;
|
||||||
|
use Behat\Gherkin\Node\TableNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From-array loader.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class ArrayLoader implements LoaderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Checks if current loader supports provided resource.
|
||||||
|
*
|
||||||
|
* @param mixed $resource Resource to load
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function supports($resource)
|
||||||
|
{
|
||||||
|
return is_array($resource) && (isset($resource['features']) || isset($resource['feature']));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads features from provided resource.
|
||||||
|
*
|
||||||
|
* @param mixed $resource Resource to load
|
||||||
|
*
|
||||||
|
* @return FeatureNode[]
|
||||||
|
*/
|
||||||
|
public function load($resource)
|
||||||
|
{
|
||||||
|
$features = array();
|
||||||
|
|
||||||
|
if (isset($resource['features'])) {
|
||||||
|
foreach ($resource['features'] as $iterator => $hash) {
|
||||||
|
$feature = $this->loadFeatureHash($hash, $iterator);
|
||||||
|
$features[] = $feature;
|
||||||
|
}
|
||||||
|
} elseif (isset($resource['feature'])) {
|
||||||
|
$feature = $this->loadFeatureHash($resource['feature']);
|
||||||
|
$features[] = $feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $features;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads feature from provided feature hash.
|
||||||
|
*
|
||||||
|
* @param array $hash Feature hash
|
||||||
|
* @param integer $line
|
||||||
|
*
|
||||||
|
* @return FeatureNode
|
||||||
|
*/
|
||||||
|
protected function loadFeatureHash(array $hash, $line = 0)
|
||||||
|
{
|
||||||
|
$hash = array_merge(
|
||||||
|
array(
|
||||||
|
'title' => null,
|
||||||
|
'description' => null,
|
||||||
|
'tags' => array(),
|
||||||
|
'keyword' => 'Feature',
|
||||||
|
'language' => 'en',
|
||||||
|
'line' => $line,
|
||||||
|
'scenarios' => array(),
|
||||||
|
),
|
||||||
|
$hash
|
||||||
|
);
|
||||||
|
$background = isset($hash['background']) ? $this->loadBackgroundHash($hash['background']) : null;
|
||||||
|
|
||||||
|
$scenarios = array();
|
||||||
|
foreach ((array) $hash['scenarios'] as $scenarioIterator => $scenarioHash) {
|
||||||
|
if (isset($scenarioHash['type']) && 'outline' === $scenarioHash['type']) {
|
||||||
|
$scenarios[] = $this->loadOutlineHash($scenarioHash, $scenarioIterator);
|
||||||
|
} else {
|
||||||
|
$scenarios[] = $this->loadScenarioHash($scenarioHash, $scenarioIterator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FeatureNode($hash['title'], $hash['description'], $hash['tags'], $background, $scenarios, $hash['keyword'], $hash['language'], null, $hash['line']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads background from provided hash.
|
||||||
|
*
|
||||||
|
* @param array $hash Background hash
|
||||||
|
*
|
||||||
|
* @return BackgroundNode
|
||||||
|
*/
|
||||||
|
protected function loadBackgroundHash(array $hash)
|
||||||
|
{
|
||||||
|
$hash = array_merge(
|
||||||
|
array(
|
||||||
|
'title' => null,
|
||||||
|
'keyword' => 'Background',
|
||||||
|
'line' => 0,
|
||||||
|
'steps' => array(),
|
||||||
|
),
|
||||||
|
$hash
|
||||||
|
);
|
||||||
|
|
||||||
|
$steps = $this->loadStepsHash($hash['steps']);
|
||||||
|
|
||||||
|
return new BackgroundNode($hash['title'], $steps, $hash['keyword'], $hash['line']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads scenario from provided scenario hash.
|
||||||
|
*
|
||||||
|
* @param array $hash Scenario hash
|
||||||
|
* @param integer $line Scenario definition line
|
||||||
|
*
|
||||||
|
* @return ScenarioNode
|
||||||
|
*/
|
||||||
|
protected function loadScenarioHash(array $hash, $line = 0)
|
||||||
|
{
|
||||||
|
$hash = array_merge(
|
||||||
|
array(
|
||||||
|
'title' => null,
|
||||||
|
'tags' => array(),
|
||||||
|
'keyword' => 'Scenario',
|
||||||
|
'line' => $line,
|
||||||
|
'steps' => array(),
|
||||||
|
),
|
||||||
|
$hash
|
||||||
|
);
|
||||||
|
|
||||||
|
$steps = $this->loadStepsHash($hash['steps']);
|
||||||
|
|
||||||
|
return new ScenarioNode($hash['title'], $hash['tags'], $steps, $hash['keyword'], $hash['line']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads outline from provided outline hash.
|
||||||
|
*
|
||||||
|
* @param array $hash Outline hash
|
||||||
|
* @param integer $line Outline definition line
|
||||||
|
*
|
||||||
|
* @return OutlineNode
|
||||||
|
*/
|
||||||
|
protected function loadOutlineHash(array $hash, $line = 0)
|
||||||
|
{
|
||||||
|
$hash = array_merge(
|
||||||
|
array(
|
||||||
|
'title' => null,
|
||||||
|
'tags' => array(),
|
||||||
|
'keyword' => 'Scenario Outline',
|
||||||
|
'line' => $line,
|
||||||
|
'steps' => array(),
|
||||||
|
'examples' => array(),
|
||||||
|
),
|
||||||
|
$hash
|
||||||
|
);
|
||||||
|
|
||||||
|
$steps = $this->loadStepsHash($hash['steps']);
|
||||||
|
|
||||||
|
if (isset($hash['examples']['keyword'])) {
|
||||||
|
$examplesKeyword = $hash['examples']['keyword'];
|
||||||
|
unset($hash['examples']['keyword']);
|
||||||
|
} else {
|
||||||
|
$examplesKeyword = 'Examples';
|
||||||
|
}
|
||||||
|
|
||||||
|
$exHash = $hash['examples'];
|
||||||
|
$examples = array();
|
||||||
|
|
||||||
|
if ($this->examplesAreInArray($exHash)) {
|
||||||
|
$examples = $this->processExamplesArray($exHash, $examplesKeyword, $examples);
|
||||||
|
} else {
|
||||||
|
// examples as a single table - we create an array with the only one element
|
||||||
|
$examples[] = new ExampleTableNode($exHash, $examplesKeyword);;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OutlineNode($hash['title'], $hash['tags'], $steps, $examples, $hash['keyword'], $hash['line']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads steps from provided hash.
|
||||||
|
*
|
||||||
|
* @param array $hash
|
||||||
|
*
|
||||||
|
* @return StepNode[]
|
||||||
|
*/
|
||||||
|
private function loadStepsHash(array $hash)
|
||||||
|
{
|
||||||
|
$steps = array();
|
||||||
|
foreach ($hash as $stepIterator => $stepHash) {
|
||||||
|
$steps[] = $this->loadStepHash($stepHash, $stepIterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $steps;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads step from provided hash.
|
||||||
|
*
|
||||||
|
* @param array $hash Step hash
|
||||||
|
* @param integer $line Step definition line
|
||||||
|
*
|
||||||
|
* @return StepNode
|
||||||
|
*/
|
||||||
|
protected function loadStepHash(array $hash, $line = 0)
|
||||||
|
{
|
||||||
|
$hash = array_merge(
|
||||||
|
array(
|
||||||
|
'keyword_type' => 'Given',
|
||||||
|
'type' => 'Given',
|
||||||
|
'text' => null,
|
||||||
|
'keyword' => 'Scenario',
|
||||||
|
'line' => $line,
|
||||||
|
'arguments' => array(),
|
||||||
|
),
|
||||||
|
$hash
|
||||||
|
);
|
||||||
|
|
||||||
|
$arguments = array();
|
||||||
|
foreach ($hash['arguments'] as $argumentHash) {
|
||||||
|
if ('table' === $argumentHash['type']) {
|
||||||
|
$arguments[] = $this->loadTableHash($argumentHash['rows']);
|
||||||
|
} elseif ('pystring' === $argumentHash['type']) {
|
||||||
|
$arguments[] = $this->loadPyStringHash($argumentHash, $hash['line'] + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StepNode($hash['type'], $hash['text'], $arguments, $hash['line'], $hash['keyword_type']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads table from provided hash.
|
||||||
|
*
|
||||||
|
* @param array $hash Table hash
|
||||||
|
*
|
||||||
|
* @return TableNode
|
||||||
|
*/
|
||||||
|
protected function loadTableHash(array $hash)
|
||||||
|
{
|
||||||
|
return new TableNode($hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads PyString from provided hash.
|
||||||
|
*
|
||||||
|
* @param array $hash PyString hash
|
||||||
|
* @param integer $line
|
||||||
|
*
|
||||||
|
* @return PyStringNode
|
||||||
|
*/
|
||||||
|
protected function loadPyStringHash(array $hash, $line = 0)
|
||||||
|
{
|
||||||
|
$line = isset($hash['line']) ? $hash['line'] : $line;
|
||||||
|
|
||||||
|
$strings = array();
|
||||||
|
foreach (explode("\n", $hash['text']) as $string) {
|
||||||
|
$strings[] = $string;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PyStringNode($strings, $line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if examples node is an array
|
||||||
|
* @param $exHash object hash
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function examplesAreInArray($exHash)
|
||||||
|
{
|
||||||
|
return isset($exHash[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes cases when examples are in the form of array of arrays
|
||||||
|
* OR in the form of array of objects
|
||||||
|
*
|
||||||
|
* @param $exHash array hash
|
||||||
|
* @param $examplesKeyword string
|
||||||
|
* @param $examples array
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function processExamplesArray($exHash, $examplesKeyword, $examples)
|
||||||
|
{
|
||||||
|
for ($i = 0; $i < count($exHash); $i++) {
|
||||||
|
if (isset($exHash[$i]['table'])) {
|
||||||
|
// we have examples as objects, hence there could be tags
|
||||||
|
$exHashTags = isset($exHash[$i]['tags']) ? $exHash[$i]['tags'] : array();
|
||||||
|
$examples[] = new ExampleTableNode($exHash[$i]['table'], $examplesKeyword, $exHashTags);
|
||||||
|
} else {
|
||||||
|
// we have examples as arrays
|
||||||
|
$examples[] = new ExampleTableNode($exHash[$i], $examplesKeyword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $examples;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Loader;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Node\BackgroundNode;
|
||||||
|
use Behat\Gherkin\Node\ExampleTableNode;
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
use Behat\Gherkin\Node\OutlineNode;
|
||||||
|
use Behat\Gherkin\Node\ScenarioInterface;
|
||||||
|
use Behat\Gherkin\Node\ScenarioNode;
|
||||||
|
use Behat\Gherkin\Node\StepNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a feature from cucumber's protobuf JSON format
|
||||||
|
*/
|
||||||
|
class CucumberNDJsonAstLoader implements LoaderInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
public function supports($resource)
|
||||||
|
{
|
||||||
|
return is_string($resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function load($resource)
|
||||||
|
{
|
||||||
|
return array_values(array_filter(array_map(
|
||||||
|
static function ($line) use ($resource) {
|
||||||
|
return self::getFeature(json_decode($line, true), $resource);
|
||||||
|
},
|
||||||
|
file($resource)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return FeatureNode|null
|
||||||
|
*/
|
||||||
|
private static function getFeature(array $json, $filePath)
|
||||||
|
{
|
||||||
|
if (!isset($json['gherkinDocument']['feature'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$featureJson = $json['gherkinDocument']['feature'];
|
||||||
|
|
||||||
|
$feature = new FeatureNode(
|
||||||
|
isset($featureJson['name']) ? $featureJson['name'] : null,
|
||||||
|
$featureJson['description'] ? trim($featureJson['description']) : null,
|
||||||
|
self::getTags($featureJson),
|
||||||
|
self::getBackground($featureJson),
|
||||||
|
self::getScenarios($featureJson),
|
||||||
|
$featureJson['keyword'],
|
||||||
|
$featureJson['language'],
|
||||||
|
preg_replace('/(?<=\\.feature).*$/', '', $filePath),
|
||||||
|
$featureJson['location']['line']
|
||||||
|
);
|
||||||
|
|
||||||
|
return $feature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
private static function getTags(array $json)
|
||||||
|
{
|
||||||
|
return array_map(
|
||||||
|
static function(array $tag) { return preg_replace('/^@/', '', $tag['name']); },
|
||||||
|
isset($json['tags']) ? $json['tags'] : []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ScenarioInterface[]
|
||||||
|
*/
|
||||||
|
private static function getScenarios(array $json)
|
||||||
|
{
|
||||||
|
|
||||||
|
return array_values(
|
||||||
|
array_map(
|
||||||
|
static function ($child) {
|
||||||
|
|
||||||
|
if ($child['scenario']['examples']) {
|
||||||
|
return new OutlineNode(
|
||||||
|
isset($child['scenario']['name']) ? $child['scenario']['name'] : null,
|
||||||
|
self::getTags($child['scenario']),
|
||||||
|
self::getSteps(isset($child['scenario']['steps']) ? $child['scenario']['steps'] : []),
|
||||||
|
self::getTables($child['scenario']['examples']),
|
||||||
|
$child['scenario']['keyword'],
|
||||||
|
$child['scenario']['location']['line']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new ScenarioNode(
|
||||||
|
$child['scenario']['name'],
|
||||||
|
self::getTags($child['scenario']),
|
||||||
|
self::getSteps(isset($child['scenario']['steps']) ? $child['scenario']['steps'] : []),
|
||||||
|
$child['scenario']['keyword'],
|
||||||
|
$child['scenario']['location']['line']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
array_filter(
|
||||||
|
isset($json['children']) ? $json['children'] : [],
|
||||||
|
static function ($child) {
|
||||||
|
return isset($child['scenario']);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return BackgroundNode|null
|
||||||
|
*/
|
||||||
|
private static function getBackground(array $json)
|
||||||
|
{
|
||||||
|
$backgrounds = array_values(
|
||||||
|
array_map(
|
||||||
|
static function ($child) {
|
||||||
|
return new BackgroundNode(
|
||||||
|
$child['background']['name'],
|
||||||
|
self::getSteps(isset($child['background']['steps']) ? $child['background']['steps'] : []),
|
||||||
|
$child['background']['keyword'],
|
||||||
|
$child['background']['location']['line']
|
||||||
|
);
|
||||||
|
},
|
||||||
|
array_filter(
|
||||||
|
isset($json['children']) ? $json['children'] : [],
|
||||||
|
static function ($child) {
|
||||||
|
return isset($child['background']);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return count($backgrounds) == 1 ? $backgrounds[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return StepNode[]
|
||||||
|
*/
|
||||||
|
private static function getSteps(array $json)
|
||||||
|
{
|
||||||
|
return array_map(
|
||||||
|
static function(array $json) {
|
||||||
|
return new StepNode(
|
||||||
|
trim($json['keyword']),
|
||||||
|
$json['text'],
|
||||||
|
[],
|
||||||
|
$json['location']['line'],
|
||||||
|
trim($json['keyword'])
|
||||||
|
);
|
||||||
|
},
|
||||||
|
$json
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ExampleTableNode[]
|
||||||
|
*/
|
||||||
|
private static function getTables(array $json)
|
||||||
|
{
|
||||||
|
return array_map(
|
||||||
|
static function($tableJson) {
|
||||||
|
|
||||||
|
$table = [];
|
||||||
|
|
||||||
|
$table[$tableJson['tableHeader']['location']['line']] = array_map(
|
||||||
|
static function($cell) {
|
||||||
|
return $cell['value'];
|
||||||
|
},
|
||||||
|
$tableJson['tableHeader']['cells']
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($tableJson['tableBody'] as $bodyRow) {
|
||||||
|
$table[$bodyRow['location']['line']] = array_map(
|
||||||
|
static function($cell) {
|
||||||
|
return $cell['value'];
|
||||||
|
},
|
||||||
|
$bodyRow['cells']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ExampleTableNode(
|
||||||
|
$table,
|
||||||
|
$tableJson['keyword'],
|
||||||
|
self::getTags($tableJson)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
$json
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Behat Gherkin.
|
||||||
|
* (c) Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Behat\Gherkin\Loader;
|
||||||
|
|
||||||
|
use Behat\Gherkin\Gherkin;
|
||||||
|
use Behat\Gherkin\Node\FeatureNode;
|
||||||
|
use RecursiveDirectoryIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directory contents loader.
|
||||||
|
*
|
||||||
|
* @author Konstantin Kudryashov <ever.zet@gmail.com>
|
||||||
|
*/
|
||||||
|
class DirectoryLoader extends AbstractFileLoader
|
||||||
|
{
|
||||||
|
protected $gherkin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes loader.
|
||||||
|
*
|
||||||
|
* @param Gherkin $gherkin Gherkin manager
|
||||||
|
*/
|
||||||
|
public function __construct(Gherkin $gherkin)
|
||||||
|
{
|
||||||
|
$this->gherkin = $gherkin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if current loader supports provided resource.
|
||||||
|
*
|
||||||
|
* @param mixed $path Resource to load
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function supports($path)
|
||||||
|
{
|
||||||
|
return is_string($path)
|
||||||
|
&& is_dir($this->findAbsolutePath($path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads features from provided resource.
|
||||||
|
*
|
||||||
|
* @param string $path Resource to load
|
||||||
|
*
|
||||||
|
* @return FeatureNode[]
|
||||||
|
*/
|
||||||
|
public function load($path)
|
||||||
|
{
|
||||||
|
$path = $this->findAbsolutePath($path);
|
||||||
|
|
||||||
|
$iterator = new RecursiveIteratorIterator(
|
||||||
|
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS)
|
||||||
|
);
|
||||||
|
$paths = array_map('strval', iterator_to_array($iterator));
|
||||||
|
uasort($paths, 'strnatcasecmp');
|
||||||
|
|
||||||
|
$features = array();
|
||||||
|
|
||||||
|
foreach ($paths as $path) {
|
||||||
|
$path = (string) $path;
|
||||||
|
$loader = $this->gherkin->resolveLoader($path);
|
||||||
|
|
||||||
|
if (null !== $loader) {
|
||||||
|
$features = array_merge($features, $loader->load($path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $features;
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user