├── src
├── routes
│ ├── .gitkeep
│ └── web.php
├── config
│ ├── tddd
│ │ ├── .gitkeep
│ │ ├── pipers
│ │ │ ├── tee.yml
│ │ │ ├── script-macos.yml
│ │ │ └── script-debian.yml
│ │ ├── editors
│ │ │ ├── sublime.yml
│ │ │ ├── phpstorm.yml
│ │ │ └── vscode.yml
│ │ ├── testers
│ │ │ ├── atoum.yml
│ │ │ ├── behat.yml
│ │ │ ├── phpspec.yml
│ │ │ ├── phpunit.yml
│ │ │ ├── tester.yml
│ │ │ ├── rake.yml
│ │ │ ├── simple-phpunit.yml
│ │ │ ├── ava.yml
│ │ │ ├── jest.yml
│ │ │ ├── react-scripts.yml
│ │ │ ├── codeception.yml
│ │ │ └── dusk.yml
│ │ ├── routes.yml
│ │ ├── projects
│ │ │ └── phpunit-example.yml
│ │ ├── notifications.yml
│ │ └── root.yml
│ └── tddd-base.php
├── database
│ └── migrations
│ │ ├── .gitkeep
│ │ ├── 2017_10_09_800008_add_sha1.php
│ │ ├── 2017_10_10_800009_add_env.php
│ │ ├── 2017_10_10_800010_add_editor.php
│ │ ├── 2017_10_11_800011_add_sha1_index.php
│ │ ├── 2017_09_30_800003_add_uses_tee.php
│ │ ├── 2017_09_25_800001_create_runs_indexes.php
│ │ ├── 2017_10_11_800012_add_project_enabled.php
│ │ ├── 2017_10_01_800005_add_regex_pattern.php
│ │ ├── 2017_10_08_800007_add_require_script.php
│ │ ├── 2017_10_01_800006_add_test_path.php
│ │ ├── 2017_09_30_800004_add_screenshots.php
│ │ ├── 2014_11_14_300000_create_tddd_projects_table.php
│ │ ├── 2017_12_07_800015_add_coverage.php
│ │ ├── 2017_11_03_800014_add_pipers.php
│ │ ├── 2014_11_14_200000_create_tddd_testers_table.php
│ │ ├── 2014_11_14_700000_create_tddd_queue_table.php
│ │ ├── 2017_09_27_800002_create_runs_start_end.php
│ │ ├── 2014_11_14_800000_create_tddd_runs_table.php
│ │ ├── 2014_11_14_500000_create_tddd_tests_table.php
│ │ ├── 2017_10_27_800013_log_medium_text.php
│ │ └── 2014_11_14_400000_create_tddd_suites_table.php
├── resources
│ ├── assets
│ │ ├── js
│ │ │ ├── constants.js
│ │ │ ├── bootstrap-app.js
│ │ │ ├── app.js
│ │ │ └── components
│ │ │ │ ├── State.vue
│ │ │ │ ├── Projects.vue
│ │ │ │ └── Log.vue
│ │ └── sass
│ │ │ ├── _variables.scss
│ │ │ └── app.scss
│ ├── lang
│ │ └── en
│ │ │ ├── pagination.php
│ │ │ ├── auth.php
│ │ │ ├── passwords.php
│ │ │ └── validation.php
│ └── views
│ │ └── dashboard.blade.php
├── package
│ ├── Support
│ │ ├── jasonlewis
│ │ │ └── resource-watcher
│ │ │ │ ├── .gitignore
│ │ │ │ ├── src
│ │ │ │ └── JasonLewis
│ │ │ │ │ └── ResourceWatcher
│ │ │ │ │ ├── Resource
│ │ │ │ │ ├── ResourceInterface.php
│ │ │ │ │ ├── DirectoryResource.php
│ │ │ │ │ └── FileResource.php
│ │ │ │ │ ├── Integration
│ │ │ │ │ └── LaravelServiceProvider.php
│ │ │ │ │ ├── Event.php
│ │ │ │ │ ├── Tracker.php
│ │ │ │ │ ├── Watcher.php
│ │ │ │ │ └── Listener.php
│ │ │ │ ├── composer.json
│ │ │ │ ├── LICENSE
│ │ │ │ └── README.md
│ │ ├── Constants.php
│ │ ├── helpers.php
│ │ ├── Executor.php
│ │ └── Notifier.php
│ ├── Notifications
│ │ ├── Channels
│ │ │ ├── Contract.php
│ │ │ ├── Mail.php
│ │ │ ├── Slack.php
│ │ │ └── BaseChannel.php
│ │ └── Status.php
│ ├── Facades
│ │ └── Config.php
│ ├── Events
│ │ ├── UserNotifiedOfFailure.php
│ │ ├── TestsFailed.php
│ │ └── DataUpdated.php
│ ├── Console
│ │ └── Commands
│ │ │ ├── BaseCommand.php
│ │ │ ├── TestCommand.php
│ │ │ └── WatchCommand.php
│ ├── Data
│ │ ├── Models
│ │ │ ├── Queue.php
│ │ │ ├── Run.php
│ │ │ ├── Project.php
│ │ │ ├── Tester.php
│ │ │ ├── User.php
│ │ │ ├── Model.php
│ │ │ ├── Test.php
│ │ │ └── Suite.php
│ │ └── Repositories
│ │ │ ├── Support
│ │ │ ├── Notifications.php
│ │ │ ├── Messages.php
│ │ │ ├── Testers.php
│ │ │ ├── Runs.php
│ │ │ ├── Queue.php
│ │ │ ├── Suites.php
│ │ │ ├── Projects.php
│ │ │ └── Tests.php
│ │ │ └── Data.php
│ ├── Http
│ │ └── Controllers
│ │ │ ├── Html.php
│ │ │ ├── Dashboard.php
│ │ │ ├── Files.php
│ │ │ ├── Tests.php
│ │ │ ├── Controller.php
│ │ │ └── Projects.php
│ ├── Listeners
│ │ ├── MarkAsNotified.php
│ │ └── Notify.php
│ ├── Services
│ │ ├── Cache.php
│ │ ├── Base.php
│ │ ├── Config.php
│ │ └── Loader.php
│ └── ServiceProvider.php
└── public
│ └── img
│ └── ring-spinner.svg
├── .gitattributes
├── .babelrc
├── upgrading.md
├── docs
├── atoum.png
├── behat.png
├── video.png
├── phpspec.png
├── tester.png
├── dashboard.png
├── errorlog1.png
├── errorlog2.png
├── errorlog3.png
└── acr-screenshot 2017-10-02 20.25.27.png
├── .gitignore
├── mix-manifest.json
├── fonts
└── vendor
│ ├── font-awesome
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.ttf
│ ├── fontawesome-webfont.woff
│ └── fontawesome-webfont.woff2
│ └── bootstrap-sass
│ └── bootstrap
│ ├── glyphicons-halflings-regular.eot
│ ├── glyphicons-halflings-regular.ttf
│ ├── glyphicons-halflings-regular.woff
│ └── glyphicons-halflings-regular.woff2
├── tests
├── DashboardTest.php
├── bootstrap.php
└── TestCase.php
├── .scrutinizer.yml
├── .travis.yml
├── LICENSE.md
├── phpunit.xml
├── package.json
├── webpack.mix.js
├── composer.json
├── CHANGELOG.md
└── README.md
/src/routes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/config/tddd/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/database/migrations/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/resources/assets/js/constants.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | src/public/* linguist-vendored
2 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["transform-object-rest-spread"]
3 | }
4 |
--------------------------------------------------------------------------------
/upgrading.md:
--------------------------------------------------------------------------------
1 | # Laravel Stats SDK Upgrading Guide
2 |
3 | ## to 0.1.0
4 |
5 |
--------------------------------------------------------------------------------
/docs/atoum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/docs/atoum.png
--------------------------------------------------------------------------------
/docs/behat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/docs/behat.png
--------------------------------------------------------------------------------
/docs/video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/docs/video.png
--------------------------------------------------------------------------------
/docs/phpspec.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/docs/phpspec.png
--------------------------------------------------------------------------------
/docs/tester.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/docs/tester.png
--------------------------------------------------------------------------------
/docs/dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/docs/dashboard.png
--------------------------------------------------------------------------------
/docs/errorlog1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/docs/errorlog1.png
--------------------------------------------------------------------------------
/docs/errorlog2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/docs/errorlog2.png
--------------------------------------------------------------------------------
/docs/errorlog3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/docs/errorlog3.png
--------------------------------------------------------------------------------
/src/config/tddd/pipers/tee.yml:
--------------------------------------------------------------------------------
1 | bin: "/usr/bin/tee"
2 | execute: "{$command} | {$bin} > {$tempFile}"
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.phar
3 | composer.lock
4 | .DS_Store
5 | .idea
6 | /node_modules
7 | /coverage
8 |
--------------------------------------------------------------------------------
/src/config/tddd/pipers/script-macos.yml:
--------------------------------------------------------------------------------
1 | bin: "/usr/bin/script"
2 | execute: "{$bin} -q {$tempFile} {$command}"
3 |
--------------------------------------------------------------------------------
/src/package/Support/jasonlewis/resource-watcher/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | composer.phar
3 | composer.lock
4 | .DS_Store
--------------------------------------------------------------------------------
/src/config/tddd/editors/sublime.yml:
--------------------------------------------------------------------------------
1 | code: sublime
2 | name: SublimeText 3
3 | bin: "/usr/local/bin/subl {file}:{line}"
4 |
--------------------------------------------------------------------------------
/src/config/tddd/pipers/script-debian.yml:
--------------------------------------------------------------------------------
1 | bin: "/usr/bin/script"
2 | execute: "{$bin} -q -c '{$command}' {$tempFile}"
3 |
--------------------------------------------------------------------------------
/src/config/tddd/testers/atoum.yml:
--------------------------------------------------------------------------------
1 | code: atoum
2 | name: Atoum
3 | command: sh vendor/bin/atoum
4 | pipers:
5 | - tee
6 |
--------------------------------------------------------------------------------
/docs/acr-screenshot 2017-10-02 20.25.27.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/docs/acr-screenshot 2017-10-02 20.25.27.png
--------------------------------------------------------------------------------
/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/src/public/js/app.js": "/src/public/js/app.js",
3 | "/src/public/css/app.css": "/src/public/css/app.css"
4 | }
--------------------------------------------------------------------------------
/src/config/tddd/editors/phpstorm.yml:
--------------------------------------------------------------------------------
1 | code: phpstorm
2 | name: PHPStorm
3 | bin: "/usr/local/bin/pstorm {file}:{line}"
4 | default: true
5 |
--------------------------------------------------------------------------------
/fonts/vendor/font-awesome/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/fonts/vendor/font-awesome/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/fonts/vendor/font-awesome/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/fonts/vendor/font-awesome/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/fonts/vendor/font-awesome/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/fonts/vendor/font-awesome/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/fonts/vendor/font-awesome/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/fonts/vendor/font-awesome/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/src/config/tddd/testers/behat.yml:
--------------------------------------------------------------------------------
1 | code: behat
2 | name: Behat
3 | command: sh vendor/bin/behat
4 | pipers:
5 | - "script-{{ config('tddd-base.host_os') }}"
6 |
--------------------------------------------------------------------------------
/src/config/tddd/testers/phpspec.yml:
--------------------------------------------------------------------------------
1 | code: phpspec
2 | name: phpspec
3 | command: phpspec run
4 | pipers:
5 | - "script-{{ config('tddd-base.host_os') }}"
6 |
--------------------------------------------------------------------------------
/src/config/tddd/testers/phpunit.yml:
--------------------------------------------------------------------------------
1 | code: phpunit
2 | name: PHPUnit
3 | command: vendor/bin/phpunit
4 | pipers:
5 | - "script-{{ config('tddd-base.host_os') }}"
6 |
--------------------------------------------------------------------------------
/src/config/tddd/testers/tester.yml:
--------------------------------------------------------------------------------
1 | code: tester
2 | name: Tester
3 | command: sh vendor/bin/tester
4 | pipers:
5 | - "script-{{ config('tddd-base.host_os') }}"
6 |
--------------------------------------------------------------------------------
/src/config/tddd/editors/vscode.yml:
--------------------------------------------------------------------------------
1 | code: vscode
2 | name: VSCode
3 | bin: "/Applications/Visual\\ Studio\\ Code.app/Contents/Resources/app/bin/code --goto {file}:{line}"
4 |
--------------------------------------------------------------------------------
/src/config/tddd/routes.yml:
--------------------------------------------------------------------------------
1 | prefixes:
2 | global: ''
3 | dashboard: ''
4 | tests: "/tests"
5 | projects: "/projects"
6 | files: "/files"
7 | html: "/html"
8 |
--------------------------------------------------------------------------------
/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonioribeiro/tddd/HEAD/fonts/vendor/bootstrap-sass/bootstrap/glyphicons-halflings-regular.woff2
--------------------------------------------------------------------------------
/src/config/tddd/testers/rake.yml:
--------------------------------------------------------------------------------
1 | code: rake
2 | name: Rake
3 | command: bin/rails test
4 | error_pattern: Test\s+Suites:\s+[0-9]+\s+failed
5 | pipers:
6 | - "script-{{ config('tddd-base.host_os') }}"
7 |
--------------------------------------------------------------------------------
/src/config/tddd/testers/simple-phpunit.yml:
--------------------------------------------------------------------------------
1 | code: simple-phpunit
2 | name: Simple PHPUnit (Symfony)
3 | command: vendor/bin/simple-phpunit
4 | pipers:
5 | - "script-{{ config('tddd-base.host_os') }}"
6 |
--------------------------------------------------------------------------------
/src/config/tddd/testers/ava.yml:
--------------------------------------------------------------------------------
1 | code: ava
2 | name: AVA
3 | command: node_modules/.bin/ava --verbose
4 | error_pattern: "[1-9]+\\s+(exception|failure)"
5 | pipers:
6 | - "script-{{ config('tddd-base.host_os') }}"
7 |
--------------------------------------------------------------------------------
/src/config/tddd/testers/jest.yml:
--------------------------------------------------------------------------------
1 | code: jest
2 | name: Jest
3 | command: npm test
4 | output_folder: tests/__snapshots__
5 | output_html_fail_extension: ".snap"
6 | pipers:
7 | - "script-{{ config('tddd-base.host_os') }}"
8 |
--------------------------------------------------------------------------------
/src/config/tddd/testers/react-scripts.yml:
--------------------------------------------------------------------------------
1 | code: react-scripts
2 | name: React Scripts (Tester)
3 | env: CI=true
4 | command: npm test
5 | error_pattern: Test\s+Suites:\s+[0-9]+\s+failed
6 | pipers:
7 | - "script-{{ config('tddd-base.host_os') }}"
8 |
--------------------------------------------------------------------------------
/src/config/tddd/testers/codeception.yml:
--------------------------------------------------------------------------------
1 | code: codeception
2 | name: Codeception
3 | command: sh {$project_path}/vendor/bin/codecept run
4 | output_folder: tests/_output
5 | output_html_fail_extension: ".fail.html"
6 | output_png_fail_extension: ".fail.png"
7 | pipers:
8 | - "script-{{ config('tddd-base.host_os') }}"
9 |
--------------------------------------------------------------------------------
/src/package/Notifications/Channels/Contract.php:
--------------------------------------------------------------------------------
1 | env('TDDD_CONFIG_PATH', __DIR__),
9 |
10 | 'host_os' => env('TDDD_HOST_OS', __DIR__),
11 |
12 | 'user_home' => env('HOME', __DIR__),
13 | ];
14 |
--------------------------------------------------------------------------------
/src/package/Support/jasonlewis/resource-watcher/src/JasonLewis/ResourceWatcher/Resource/ResourceInterface.php:
--------------------------------------------------------------------------------
1 | tests = $tests;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/package/Console/Commands/BaseCommand.php:
--------------------------------------------------------------------------------
1 | line(str_repeat('-', max($len, 80)));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/package/Data/Models/Queue.php:
--------------------------------------------------------------------------------
1 | belongsTo('PragmaRX\Tddd\Package\Data\Models\Test');
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/resources/assets/js/bootstrap-app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * lodash _
3 | */
4 |
5 | window._ = require('lodash');
6 |
7 | /**
8 | * jQuery & Bootstrap
9 | */
10 |
11 | window.$ = window.jQuery = require('jquery');
12 |
13 | window.Popper = require('popper.js');
14 |
15 | window.bootstrap = require('bootstrap');
16 |
17 | const Pusher = require('pusher-js');
18 |
19 | /**
20 | * Axios
21 | */
22 |
23 | window.axios = require('axios');
24 |
25 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
26 |
--------------------------------------------------------------------------------
/src/package/Http/Controllers/Html.php:
--------------------------------------------------------------------------------
1 | get('index')),
14 | public_path(config('tddd.root.coverage.path'))
15 | );
16 |
17 | return redirect()->to('/'.config('tddd.root.coverage.path').'/'.basename($index));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/package/Events/TestsFailed.php:
--------------------------------------------------------------------------------
1 | tests = $tests;
25 |
26 | $this->channel = $channel;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/package/Data/Models/Run.php:
--------------------------------------------------------------------------------
1 | path,
25 | $this->tests_path,
26 | ]
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/resources/lang/en/pagination.php:
--------------------------------------------------------------------------------
1 | '« Previous',
17 | 'next' => 'Next »',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/src/package/Listeners/MarkAsNotified.php:
--------------------------------------------------------------------------------
1 | dataRepository = $dataRepository;
13 | }
14 |
15 | /**
16 | * Handle the event.
17 | *
18 | * @param UserNotifiedOfFailure $event
19 | *
20 | * @return void
21 | */
22 | public function handle(UserNotifiedOfFailure $event)
23 | {
24 | $this->dataRepository->markTestsAsNotified($event->tests);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/DashboardTest.php:
--------------------------------------------------------------------------------
1 | watcher = app(Watcher::class);
18 |
19 | $this->worker = app(Tester::class);
20 | }
21 |
22 | public function test_can_instantiate_watcher()
23 | {
24 | $this->assertInstanceOf(Watcher::class, $this->watcher);
25 |
26 | $this->assertInstanceOf(Tester::class, $this->worker);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/resources/lang/en/auth.php:
--------------------------------------------------------------------------------
1 | 'These credentials do not match our records.',
17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
18 |
19 | ];
20 |
--------------------------------------------------------------------------------
/src/config/tddd/projects/phpunit-example.yml:
--------------------------------------------------------------------------------
1 | ## More examples at:
2 |
3 | name: "PHPUnit Example"
4 | path: "{{ config('tddd.root.code.path') }}/pragmarx/tddd" # absolute
5 | watch_folders:
6 | - app # all other directories are relative
7 | - tests # watching tests folder so it runs only the changed test
8 | exclude: []
9 | depends: []
10 | tests_path: tests # root tests path relative to $path
11 | suites:
12 | unit:
13 | tester: phpunit
14 | tests_path: Unit # relative to $tests_path
15 | command_options: ''
16 | file_mask: "*Test.php"
17 | retries: 0
18 | functional:
19 | tester: dusk
20 | tests_path: Browser
21 | command_options: ''
22 | file_mask: "*Test.php"
23 | retries: 0
24 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | checks:
2 | php:
3 | remove_extra_empty_lines: true
4 | remove_php_closing_tag: true
5 | remove_trailing_whitespace: true
6 | fix_use_statements:
7 | remove_unused: true
8 | preserve_multiple: false
9 | preserve_blanklines: true
10 | order_alphabetically: true
11 | fix_php_opening_tag: true
12 | fix_linefeed: true
13 | fix_line_ending: true
14 | fix_identation_4spaces: true
15 | fix_doc_comments: true
16 |
17 | filter:
18 | paths: [src/*]
19 | excluded_paths: ["tests/*", "update/*"]
20 |
21 | coding_style:
22 | php: { }
23 |
24 | tools:
25 | external_code_coverage: true
26 |
--------------------------------------------------------------------------------
/src/package/Support/jasonlewis/resource-watcher/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jasonlewis/resource-watcher",
3 | "description": "Simple PHP resource watcher library.",
4 | "keywords": ["resource", "assets", "laravel"],
5 | "authors": [
6 | {
7 | "name": "Jason Lewis",
8 | "email": "jason.lewis1991@gmail.com"
9 | }
10 | ],
11 | "require": {
12 | "php": ">=5.3.0",
13 | "illuminate/support": "~4.0",
14 | "illuminate/filesystem": "~4.0"
15 | },
16 | "require-dev": {
17 | "mockery/mockery": "~0.9"
18 | },
19 | "autoload": {
20 | "psr-0": {
21 | "JasonLewis\\ResourceWatcher": "src/"
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/package/Notifications/Channels/Mail.php:
--------------------------------------------------------------------------------
1 | line($this->getMessage($item))
19 | ->from(
20 | config('tddd.notifications.from.address'),
21 | config('tddd.notifications.from.name')
22 | )
23 | ->action($this->getActionTitle(), $this->getActionLink());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/public/img/ring-spinner.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/package/Http/Controllers/Dashboard.php:
--------------------------------------------------------------------------------
1 | with('laravel', $this->dataRepository->getJavascriptClientData());
17 | }
18 |
19 | /**
20 | * Dashboard index.
21 | *
22 | * @return \Illuminate\Http\Response
23 | */
24 | public function data()
25 | {
26 | return $this->success([
27 | 'projects' => $this->dataRepository->getProjectsAndCacheResult(),
28 | ]);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/config/tddd/notifications.yml:
--------------------------------------------------------------------------------
1 | notify_on:
2 | fail: true
3 | pass: false
4 | routes:
5 | dashboard: tests-watcher.dashboard
6 | action-title: Tests Failed
7 | action_message: One or more tests have failed.
8 | from:
9 | name: Test Driven Development Dashboard
10 | address: tddd@mydomain.com
11 | icon_emoji: ''
12 | icon_url: https://emojipedia-us.s3.amazonaws.com/thumbs/120/apple/96/lady-beetle_1f41e.png
13 | users:
14 | model: PragmaRX\Tddd\Package\Data\Models\User
15 | emails:
16 | - tddd@mydomain.com
17 | channels:
18 | mail:
19 | enabled: false
20 | sender: PragmaRX\Tddd\Package\Notifications\Channels\Mail
21 | slack:
22 | enabled: true
23 | sender: PragmaRX\Tddd\Package\Notifications\Channels\Slack
24 | notifier: PragmaRX\Tddd\Notifications
25 |
--------------------------------------------------------------------------------
/src/database/migrations/2017_10_09_800008_add_sha1.php:
--------------------------------------------------------------------------------
1 | string('sha1')->nullable();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function migrateDown()
27 | {
28 | Schema::table('tddd_tests', function (Blueprint $table) {
29 | $table->dropColumn('sha1');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/database/migrations/2017_10_10_800009_add_env.php:
--------------------------------------------------------------------------------
1 | string('env')->nullable();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function migrateDown()
27 | {
28 | Schema::table('tddd_testers', function (Blueprint $table) {
29 | $table->dropColumn('env');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | dist: trusty
2 | language: php
3 |
4 | php:
5 | - 7.0
6 | - 7.1
7 | #- 7.2
8 |
9 | # This triggers builds to run on the new TravisCI infrastructure.
10 | # See: http://docs.travis-ci.com/user/workers/container-based-infrastructure/
11 | sudo: false
12 |
13 | ## Cache composer
14 | cache:
15 | directories:
16 | - $HOME/.composer/cache
17 |
18 | before_script:
19 | - travis_retry composer update ${COMPOSER_FLAGS} --no-interaction --prefer-dist
20 |
21 | script:
22 | - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover
23 |
24 | after_script:
25 | - |
26 | if [[ "$TRAVIS_PHP_VERSION" != 'hhvm' && "$TRAVIS_PHP_VERSION" != '7.0' ]]; then
27 | wget https://scrutinizer-ci.com/ocular.phar
28 | php ocular.phar code-coverage:upload --format=php-clover coverage.clover
29 | fi
30 |
--------------------------------------------------------------------------------
/src/database/migrations/2017_10_10_800010_add_editor.php:
--------------------------------------------------------------------------------
1 | string('editor')->nullable();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function migrateDown()
27 | {
28 | Schema::table('tddd_suites', function (Blueprint $table) {
29 | $table->dropColumn('editor');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/database/migrations/2017_10_11_800011_add_sha1_index.php:
--------------------------------------------------------------------------------
1 | index('sha1');
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function migrateDown()
27 | {
28 | Schema::table('tddd_tests', function (Blueprint $table) {
29 | $table->dropIndex('tddd_tests_sha1_index');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/package/Console/Commands/TestCommand.php:
--------------------------------------------------------------------------------
1 | getLaravel()->make('tddd.tester')->run($this);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/database/migrations/2017_09_30_800003_add_uses_tee.php:
--------------------------------------------------------------------------------
1 | boolean('require_tee')->default(false);
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function migrateDown()
27 | {
28 | Schema::table('tddd_testers', function (Blueprint $table) {
29 | $table->dropColumn('require_tee');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/database/migrations/2017_09_25_800001_create_runs_indexes.php:
--------------------------------------------------------------------------------
1 | index('created_at');
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function migrateDown()
27 | {
28 | Schema::table('tddd_runs', function (Blueprint $table) {
29 | $table->dropIndex('tddd_runs_created_at_index');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/database/migrations/2017_10_11_800012_add_project_enabled.php:
--------------------------------------------------------------------------------
1 | boolean('enabled')->default(true);
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function migrateDown()
27 | {
28 | Schema::table('tddd_projects', function (Blueprint $table) {
29 | $table->dropColumn('enabled');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/resources/assets/js/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Application bootstrap
3 | */
4 |
5 | require('./bootstrap-app');
6 |
7 | /**
8 | * Vue
9 | */
10 |
11 | window.Vue = require('vue');
12 | window.Vuex = require('vuex');
13 |
14 | /**
15 | * Vuex store
16 | */
17 |
18 | Vue.use(Vuex);
19 |
20 | import store from './store';
21 |
22 | /**
23 | * Axios
24 | */
25 |
26 | Vue.prototype.$http = window.axios;
27 |
28 | /**
29 | * Load components
30 | */
31 |
32 | Vue.component('projects', require('./components/Projects.vue'));
33 | Vue.component('tests', require('./components/Tests.vue'));
34 | Vue.component('state', require('./components/State.vue'));
35 | Vue.component('log', require('./components/Log.vue'));
36 |
37 | /**
38 | * Start application
39 | */
40 |
41 |
42 | new Vue({
43 | el: '#app',
44 |
45 | store: new Vuex.Store(store),
46 | });
47 |
--------------------------------------------------------------------------------
/src/database/migrations/2017_10_01_800005_add_regex_pattern.php:
--------------------------------------------------------------------------------
1 | string('error_pattern')->nullable();
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function migrateDown()
27 | {
28 | Schema::table('tddd_testers', function (Blueprint $table) {
29 | $table->dropColumn('error_pattern');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/database/migrations/2017_10_08_800007_add_require_script.php:
--------------------------------------------------------------------------------
1 | boolean('require_script')->default(false);
18 | });
19 | }
20 |
21 | /**
22 | * Reverse the migrations.
23 | *
24 | * @return void
25 | */
26 | public function migrateDown()
27 | {
28 | Schema::table('tddd_testers', function (Blueprint $table) {
29 | $table->dropColumn('require_script');
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/package/Data/Models/Tester.php:
--------------------------------------------------------------------------------
1 | mapWithKeys(function ($piper) {
25 | return [$piper => config("tddd.pipers.{$piper}")];
26 | });
27 | }
28 |
29 | public function setPipersAttribute($value)
30 | {
31 | $this->attributes['pipers'] = collect($value)->toJson();
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/resources/lang/en/passwords.php:
--------------------------------------------------------------------------------
1 | 'Passwords must be at least six characters and match the confirmation.',
17 | 'reset' => 'Your password has been reset!',
18 | 'sent' => 'We have e-mailed your password reset link!',
19 | 'token' => 'This password reset token is invalid.',
20 | 'user' => "We can't find a user with that e-mail address.",
21 |
22 | ];
23 |
--------------------------------------------------------------------------------
/src/config/tddd/root.yml:
--------------------------------------------------------------------------------
1 | names:
2 | dashboard: {{ config('app.name') }}
3 | watcher: TDDD - Watcher
4 | worker: TDDD - Worker
5 | regex_file_matcher: "/([A-Za-z0-9\\/._-]+)(?::| on line )([1-9][0-9]*)/"
6 | poll:
7 | enabled: false
8 | interval: 300 # milliseconds
9 | tmp_dir: "/var/tmp/"
10 | show_progress: false
11 | cache:
12 | event_timeout: 10 # seconds
13 | instance: 'cache'
14 | code:
15 | path: "{{ config('tddd-base.user_home') }}/code"
16 | coverage:
17 | path: coverage # under /public
18 | broadcasting:
19 | enabled: true
20 | pusher:
21 | key: "{{ config('broadcasting.connections.pusher.key') }}"
22 | secret: "{{ config('broadcasting.connections.pusher.secret') }}"
23 | app_id: "{{ config('broadcasting.connections.pusher.app_id') }}"
24 | cluster: "{{ env('PUSHER_CLUSTER') }}"
25 | channel_name: "tddd"
26 | cache:
27 | enabled: true
28 | instance: 'cache'
29 |
--------------------------------------------------------------------------------
/src/database/migrations/2017_10_01_800006_add_test_path.php:
--------------------------------------------------------------------------------
1 | string('path')->nullable();
21 | });
22 | }
23 |
24 | /**
25 | * Reverse the migrations.
26 | *
27 | * @return void
28 | */
29 | public function migrateDown()
30 | {
31 | Schema::table('tddd_tests', function (Blueprint $table) {
32 | $table->dropColumn('path');
33 | });
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/package/Console/Commands/WatchCommand.php:
--------------------------------------------------------------------------------
1 | getLaravel()->make('tddd.watcher')->run($this, $this->option('show-tests'));
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/database/migrations/2017_09_30_800004_add_screenshots.php:
--------------------------------------------------------------------------------
1 | text('screenshots')->nullable();
18 | $table->dropColumn('png');
19 | });
20 | }
21 |
22 | /**
23 | * Reverse the migrations.
24 | *
25 | * @return void
26 | */
27 | public function migrateDown()
28 | {
29 | Schema::table('tddd_runs', function (Blueprint $table) {
30 | $table->dropColumn('screenshots');
31 | $table->text('png')->nullable();
32 | });
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/database/migrations/2014_11_14_300000_create_tddd_projects_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 |
19 | $table->string('name');
20 |
21 | $table->string('path');
22 |
23 | $table->string('tests_path');
24 |
25 | $table->timestamps();
26 | });
27 | }
28 |
29 | /**
30 | * Reverse the migrations.
31 | *
32 | * @return void
33 | */
34 | public function migrateDown()
35 | {
36 | Schema::drop('tddd_projects');
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/database/migrations/2017_12_07_800015_add_coverage.php:
--------------------------------------------------------------------------------
1 | string('coverage_enabled')->boolean(false);
18 |
19 | $table->string('coverage_index')->nullable();
20 | });
21 | }
22 |
23 | /**
24 | * Reverse the migrations.
25 | *
26 | * @return void
27 | */
28 | public function migrateDown()
29 | {
30 | Schema::table('tddd_suites', function (Blueprint $table) {
31 | $table->dropColumn('coverage_enabled');
32 |
33 | $table->dropColumn('coverage_index');
34 | });
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/package/Http/Controllers/Files.php:
--------------------------------------------------------------------------------
1 | executor->exec(
19 | $command = $this->dataRepository->makeEditFileCommand($fileName, $line, $suite_id)
20 | );
21 |
22 | return $this->success();
23 | }
24 |
25 | /**
26 | * Download an image.
27 | *
28 | * @param $filename
29 | *
30 | * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
31 | */
32 | public function imageDownload($filename)
33 | {
34 | return response()->download(
35 | base64_decode($filename)
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/package/Data/Models/User.php:
--------------------------------------------------------------------------------
1 | routeNotificationForSlack();
16 | }
17 |
18 | return $this->routeNotificationForEmail();
19 | }
20 |
21 | /**
22 | * Route notifications for e-mail.
23 | *
24 | * @return string
25 | */
26 | private function routeNotificationForEmail()
27 | {
28 | return config('tddd.notifications.user.email');
29 | }
30 |
31 | /**
32 | * Route notifications for slack.
33 | *
34 | * @return \Illuminate\Config\Repository|mixed
35 | */
36 | private function routeNotificationForSlack()
37 | {
38 | return config('services.slack.webhook_url');
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/package/Listeners/Notify.php:
--------------------------------------------------------------------------------
1 | map(function ($item) {
17 | $model = instantiate(config('tddd.notifications.users.model'));
18 |
19 | $model->email = $item;
20 |
21 | return $model;
22 | });
23 | }
24 |
25 | /**
26 | * Handle the event.
27 | *
28 | * @param TestsFailed $event
29 | *
30 | * @return void
31 | */
32 | public function handle(TestsFailed $event)
33 | {
34 | Notification::send(
35 | $this->getNotifiableUsers(),
36 | new Status($event->tests, $event->channel)
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/database/migrations/2017_11_03_800014_add_pipers.php:
--------------------------------------------------------------------------------
1 | dropColumn('require_tee');
18 |
19 | $table->dropColumn('require_script');
20 |
21 | $table->text('pipers');
22 | });
23 | }
24 |
25 | /**
26 | * Reverse the migrations.
27 | *
28 | * @return void
29 | */
30 | public function migrateDown()
31 | {
32 | Schema::table('tddd_testers', function (Blueprint $table) {
33 | $table->boolean('require_tee');
34 |
35 | $table->boolean('require_script');
36 |
37 | $table->dropColumn('pipers');
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/package/Data/Repositories/Support/Notifications.php:
--------------------------------------------------------------------------------
1 | notifier;
20 | }
21 |
22 | /**
23 | * Notify users.
24 | *
25 | * @param $project_id
26 | */
27 | public function notify($project_id)
28 | {
29 | $this->notifier->notifyViaChannels(
30 | $this->getProjectTests($project_id)->reject(function ($item) {
31 | return $item['state'] != 'failed' && is_null($item['notified_at']);
32 | })
33 | );
34 | }
35 |
36 | /**
37 | * @param Notifier $notifier
38 | */
39 | public function setNotifier($notifier)
40 | {
41 | $this->notifier = $notifier;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/resources/assets/js/components/State.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ text ? text : state }}
4 |
5 |
6 |
7 |
39 |
--------------------------------------------------------------------------------
/src/package/Data/Repositories/Support/Messages.php:
--------------------------------------------------------------------------------
1 | messages;
22 | }
23 |
24 | /**
25 | * Add a message to the list.
26 | *
27 | * @param $type
28 | * @param $body
29 | *
30 | * @internal param $string
31 | * @internal param $string1
32 | */
33 | protected function addMessage($body, $type = 'line')
34 | {
35 | $this->messages->push(['type' => $type, 'body' => $body]);
36 | }
37 |
38 | /**
39 | * Set messages.
40 | *
41 | * @param \Illuminate\Support\Collection $messages
42 | */
43 | public function setMessages($messages)
44 | {
45 | $this->messages = $messages;
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/package/Support/jasonlewis/resource-watcher/src/JasonLewis/ResourceWatcher/Integration/LaravelServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->singleton('watcher', function ($app) {
26 | $tracker = new Tracker();
27 |
28 | return new Watcher($tracker, $app['files']);
29 | });
30 | }
31 |
32 | /**
33 | * Get the services provided by the provider.
34 | *
35 | * @return array
36 | */
37 | public function provides()
38 | {
39 | return ['watcher'];
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/database/migrations/2014_11_14_200000_create_tddd_testers_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 |
19 | $table->string('name');
20 |
21 | $table->string('command');
22 |
23 | $table->string('output_folder')->nullable();
24 |
25 | $table->string('output_html_fail_extension')->nullable();
26 |
27 | $table->string('output_png_fail_extension')->nullable();
28 |
29 | $table->timestamps();
30 | });
31 | }
32 |
33 | /**
34 | * Reverse the migrations.
35 | *
36 | * @return void
37 | */
38 | public function migrateDown()
39 | {
40 | Schema::drop('tddd_testers');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/package/Data/Repositories/Data.php:
--------------------------------------------------------------------------------
1 | setAnsiConverter(new AnsiToHtmlConverter());
27 |
28 | $this->setNotifier($notifier);
29 |
30 | $this->setMessages(collect());
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 |
19 | $table->integer('test_id')->unsigned();
20 |
21 | $table->timestamps();
22 | });
23 |
24 | Schema::table('tddd_queue', function (Blueprint $table) {
25 | $table->foreign('test_id')
26 | ->references('id')
27 | ->on('tddd_tests')
28 | ->onDelete('cascade')
29 | ->onUpdate('cascade');
30 | });
31 | }
32 |
33 | /**
34 | * Reverse the migrations.
35 | *
36 | * @return void
37 | */
38 | public function migrateDown()
39 | {
40 | Schema::drop('tddd_queue');
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/package/Http/Controllers/Tests.php:
--------------------------------------------------------------------------------
1 | dataRepository->reset($project_id);
17 |
18 | return $this->success();
19 | }
20 |
21 | /**
22 | * Run a test.
23 | *
24 | * @param $test_id
25 | *
26 | * @return mixed
27 | */
28 | public function run($test_id)
29 | {
30 | $this->dataRepository->runTest($test_id);
31 |
32 | return $this->success();
33 | }
34 |
35 | /**
36 | * Enable tests.
37 | *
38 | * @param $enable
39 | * @param $project_id
40 | * @param null $test_id
41 | *
42 | * @return mixed
43 | */
44 | public function enable($project_id, $test_id, $enable)
45 | {
46 | $enabled = $this->dataRepository->enableTests($enable, $project_id, $test_id);
47 |
48 | return $this->success(['enabled' => $enabled]);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/database/migrations/2017_09_27_800002_create_runs_start_end.php:
--------------------------------------------------------------------------------
1 | timestamp('started_at')->nullable();
21 |
22 | $table->timestamp('ended_at')->nullable();
23 |
24 | $table->timestamp('notified_at')->nullable();
25 | });
26 | }
27 |
28 | /**
29 | * Reverse the migrations.
30 | *
31 | * @return void
32 | */
33 | public function migrateDown()
34 | {
35 | Schema::table('tddd_runs', function (Blueprint $table) {
36 | $table->dropColumn('started_at');
37 |
38 | $table->dropColumn('ended_at');
39 |
40 | $table->dropColumn('notified_at');
41 | });
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/package/Notifications/Channels/Slack.php:
--------------------------------------------------------------------------------
1 | error()
19 | ->from(
20 | config('tddd.notifications.from.name'),
21 | $icon = (config('tddd.notifications.from.icon_emoji') ?: null)
22 | )
23 | ->content($this->getMessage($tests));
24 |
25 | if (is_null($icon)) {
26 | $notification->image(config('tddd.notifications.from.icon_url') ?: null);
27 | }
28 |
29 | $tests->each(function ($test) use ($notification) {
30 | $notification->attachment(function ($attachment) use ($test) {
31 | $attachment->title($this->makeActionTitle($test), $this->makeActionLink($test));
32 | });
33 | });
34 |
35 | return $notification;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 |
3 | Copyright (c) 2013-2017 Antonio Carlos Ribeiro acr@antoniocarlosribeiro.com
4 |
5 | > Permission is hereby granted, free of charge, to any person obtaining a copy
6 | > of this software and associated documentation files (the "Software"), to deal
7 | > in the Software without restriction, including without limitation the rights
8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | > copies of the Software, and to permit persons to whom the Software is
10 | > furnished to do so, subject to the following conditions:
11 | >
12 | > The above copyright notice and this permission notice shall be included in
13 | > all copies or substantial portions of the Software.
14 | >
15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | > THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/package/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | dataRepository = $dataRepository;
31 |
32 | $this->executor = $executor;
33 | }
34 |
35 | /**
36 | * Return a success response.
37 | *
38 | * @param array $result
39 | *
40 | * @return \Illuminate\Http\Response
41 | */
42 | public function success($result = [])
43 | {
44 | return Response::json(array_merge(['success' => true], $result));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/package/Services/Cache.php:
--------------------------------------------------------------------------------
1 | getCacheInstance()->put($key, $value, $minutes);
24 |
25 | return $value;
26 | }
27 |
28 | /**
29 | * Get a value from the cache store.
30 | *
31 | * @throws \Exception
32 | *
33 | * @return mixed
34 | */
35 | public function get($key)
36 | {
37 | $cached = $this->getCacheInstance()->get($key);
38 |
39 | return $cached;
40 | }
41 |
42 | /**
43 | * Get the cache instance.
44 | *
45 | * @throws \Exception
46 | *
47 | * @return array|\Illuminate\Foundation\Application|mixed
48 | */
49 | protected function getCacheInstance()
50 | {
51 | if (!$this->cache) {
52 | $this->cache = app($this->config('root.cache.instance'));
53 | }
54 |
55 | return $this->cache;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/package/Support/jasonlewis/resource-watcher/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013, Jason Lewis
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5 |
6 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7 | 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.
8 |
9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/src/database/migrations/2014_11_14_800000_create_tddd_runs_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 |
19 | $table->integer('test_id')->unsigned();
20 |
21 | $table->boolean('was_ok');
22 |
23 | $table->mediumText('log');
24 |
25 | $table->text('html')->nullable();
26 |
27 | $table->text('png')->nullable();
28 |
29 | $table->timestamps();
30 | });
31 |
32 | Schema::table('tddd_runs', function (Blueprint $table) {
33 | $table->foreign('test_id')
34 | ->references('id')
35 | ->on('tddd_tests')
36 | ->onDelete('cascade')
37 | ->onUpdate('cascade');
38 | });
39 | }
40 |
41 | /**
42 | * Reverse the migrations.
43 | *
44 | * @return void
45 | */
46 | public function migrateDown()
47 | {
48 | Schema::drop('tddd_runs');
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 | ./tests
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/database/migrations/2014_11_14_500000_create_tddd_tests_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 |
19 | $table->integer('suite_id')->unsigned();
20 |
21 | $table->string('name');
22 |
23 | $table->string('state')->default('idle');
24 |
25 | $table->boolean('enabled')->default(true);
26 |
27 | $table->integer('last_run_id')->unsigned()->nullable();
28 |
29 | $table->timestamps();
30 | });
31 |
32 | Schema::table('tddd_tests', function (Blueprint $table) {
33 | $table->foreign('suite_id')
34 | ->references('id')
35 | ->on('tddd_suites')
36 | ->onDelete('cascade')
37 | ->onUpdate('cascade');
38 | });
39 | }
40 |
41 | /**
42 | * Reverse the migrations.
43 | *
44 | * @return void
45 | */
46 | public function migrateDown()
47 | {
48 | Schema::drop('tddd_tests');
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/package/Data/Models/Model.php:
--------------------------------------------------------------------------------
1 | projectSha1HasChanged()) {
14 | broadcast(new DataUpdated());
15 | }
16 | }
17 |
18 | /**
19 | * Save the model to the database.
20 | *
21 | * @param array $options
22 | *
23 | * @return bool
24 | */
25 | public function save(array $options = [])
26 | {
27 | parent::save($options);
28 |
29 | $this->broadcastDataUpdated();
30 | }
31 |
32 | /**
33 | * Get the connection of the entity.
34 | *
35 | * @return string|null
36 | */
37 | public function getQueueableConnection()
38 | {
39 | // TODO: Implement getQueueableConnection() method.
40 | }
41 |
42 | /**
43 | * Retrieve the model for a bound value.
44 | *
45 | * @param mixed $value
46 | *
47 | * @return \Illuminate\Database\Eloquent\Model|null
48 | */
49 | public function resolveRouteBinding($value)
50 | {
51 | // TODO: Implement resolveRouteBinding() method.
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/database/migrations/2017_10_27_800013_log_medium_text.php:
--------------------------------------------------------------------------------
1 | mediumText('log_new')->nullable()->after('was_ok');
19 | });
20 |
21 | Database::statement('update tddd_runs set log_new = log;');
22 |
23 | Schema::table('tddd_runs', function (Blueprint $table) {
24 | $table->dropColumn('log');
25 | $table->renameColumn('log_new', 'log');
26 | });
27 | }
28 |
29 | /**
30 | * Reverse the migrations.
31 | *
32 | * @return void
33 | */
34 | public function migrateDown()
35 | {
36 | Schema::table('tddd_runs', function (Blueprint $table) {
37 | $table->text('log_old')->nullable();
38 | });
39 |
40 | Database::statement('update tddd_runs set log_old = log;');
41 |
42 | Schema::table('tddd_runs', function (Blueprint $table) {
43 | $table->dropColumn('log');
44 | $table->renameColumn('log_old', 'log');
45 | });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/package/Support/helpers.php:
--------------------------------------------------------------------------------
1 | project->path, $string);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "npm run development",
5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
6 | "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
7 | "watch-poll": "npm run watch -- --watch-poll",
8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
9 | "prod": "npm run production",
10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
11 | },
12 | "devDependencies": {
13 | "axios": "^0.16.2",
14 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
15 | "bootstrap": "^4.0.0-beta",
16 | "cross-env": "^5.0.1",
17 | "csspin": "^1.1.4",
18 | "jquery": "^3.1.1",
19 | "laravel-mix": "^1.0",
20 | "less": "^2.7.2",
21 | "lodash": "^4.17.4",
22 | "node-sass": "^4.5.3",
23 | "popper.js": "^1.12.5",
24 | "pusher-js": "^4.3.0",
25 | "vue": "^2.1.10",
26 | "vuex": "^2.4.0",
27 | "webpack-livereload-plugin": "^0.11.0"
28 | },
29 | "dependencies": {}
30 | }
31 |
--------------------------------------------------------------------------------
/src/package/Support/Executor.php:
--------------------------------------------------------------------------------
1 | setTimeout($timeout);
32 |
33 | $this->startedAt = Carbon::now();
34 |
35 | $process->run($callback);
36 |
37 | $this->endedAt = Carbon::now();
38 |
39 | return $process;
40 | }
41 |
42 | /**
43 | * Get the elapsed time formatted for humans.
44 | *
45 | * @return mixed
46 | */
47 | public function elapsedForHumans()
48 | {
49 | return $this->endedAt->diffForHumans($this->startedAt);
50 | }
51 |
52 | /**
53 | * Execute a shell command.
54 | *
55 | * @param $command
56 | *
57 | * @return mixed
58 | */
59 | public function shellExec($command)
60 | {
61 | return shell_exec($command);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/database/migrations/2014_11_14_400000_create_tddd_suites_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 |
19 | $table->string('name');
20 |
21 | $table->integer('project_id')->unsigned();
22 |
23 | $table->integer('tester_id')->unsigned();
24 |
25 | $table->string('tests_path');
26 |
27 | $table->string('file_mask');
28 |
29 | $table->string('command_options');
30 |
31 | $table->integer('retries')->default(0);
32 |
33 | $table->timestamps();
34 | });
35 |
36 | Schema::table('tddd_suites', function (Blueprint $table) {
37 | $table->foreign('project_id')
38 | ->references('id')
39 | ->on('tddd_projects')
40 | ->onDelete('cascade')
41 | ->onUpdate('cascade');
42 | });
43 | }
44 |
45 | /**
46 | * Reverse the migrations.
47 | *
48 | * @return void
49 | */
50 | public function migrateDown()
51 | {
52 | Schema::drop('tddd_suites');
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/package/Data/Repositories/Support/Testers.php:
--------------------------------------------------------------------------------
1 | $data['code']],
19 | [
20 | 'command' => $data['command'],
21 | 'output_folder' => array_get($data, 'output_folder'),
22 | 'output_html_fail_extension' => array_get($data, 'output_html_fail_extension'),
23 | 'output_png_fail_extension' => array_get($data, 'output_png_fail_extension'),
24 | 'pipers' => array_get($data, 'pipers'),
25 | 'error_pattern' => array_get($data, 'error_pattern'),
26 | 'env' => array_get($data, 'env'),
27 | ]
28 | );
29 | }
30 |
31 | /**
32 | * Delete unavailable testers.
33 | *
34 | * @param $testers
35 | */
36 | public function deleteMissingTesters($testers)
37 | {
38 | foreach (Tester::all() as $tester) {
39 | if (!in_array($tester->name, $testers)) {
40 | $tester->delete();
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/package/Notifications/Channels/BaseChannel.php:
--------------------------------------------------------------------------------
1 | getActionMessage($item);
34 | }
35 |
36 | /**
37 | * @return string
38 | */
39 | protected function getActionLink()
40 | {
41 | return route(config('tddd.notifications.routes.dashboard'));
42 | }
43 |
44 | protected function makeActionTitle($test)
45 | {
46 | return "{$test['project_name']} - {$test['name']}";
47 | }
48 |
49 | protected function makeActionLink($test)
50 | {
51 | return route(
52 | 'tests-watcher.dashboard',
53 | [
54 | 'test_id' => $test['id'],
55 | 'project_id' => $test['project_id'],
56 | ]
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/package/Notifications/Status.php:
--------------------------------------------------------------------------------
1 | item = $item;
30 |
31 | $this->channel = $channel;
32 | }
33 |
34 | /**
35 | * @param $name
36 | *
37 | * @return \Illuminate\Foundation\Application|mixed
38 | */
39 | private function getSenderInstance($name)
40 | {
41 | $name = substr($name, 2);
42 |
43 | return instantiate(config('tddd.notifications.channels.'.strtolower($name).'.sender'));
44 | }
45 |
46 | /**
47 | * Get the notification's delivery channels.
48 | *
49 | * @return array
50 | */
51 | public function via()
52 | {
53 | return [$this->channel];
54 | }
55 |
56 | /**
57 | * @param $name
58 | * @param $parameters
59 | *
60 | * @return mixed
61 | */
62 | public function __call($name, $parameters)
63 | {
64 | $parameters[] = $this->item;
65 |
66 | return call_user_func_array(
67 | [
68 | $this->getSenderInstance($name),
69 | 'send',
70 | ],
71 | $parameters
72 | );
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/package/Http/Controllers/Projects.php:
--------------------------------------------------------------------------------
1 | dataRepository->enableProjects($enable, $project_id);
20 |
21 | return $this->success(['enabled' => $enabled]);
22 | }
23 |
24 | /**
25 | * Notify users.
26 | *
27 | * @param $project_id
28 | *
29 | * @return mixed
30 | */
31 | public function notify($project_id)
32 | {
33 | $this->dataRepository->notify($project_id);
34 |
35 | return $this->success();
36 | }
37 |
38 | /**
39 | * Run project tests.
40 | *
41 | * @return mixed
42 | */
43 | public function run(Request $request)
44 | {
45 | $this->dataRepository->runProjectTests($request->get('projects'));
46 |
47 | return $this->success();
48 | }
49 |
50 | /**
51 | * Reset projects tests states.
52 | *
53 | * @param Request $request
54 | *
55 | * @return mixed
56 | */
57 | public function reset(Request $request)
58 | {
59 | $this->dataRepository->reset($request->get('projects'));
60 |
61 | return $this->success();
62 | }
63 |
64 | /**
65 | * Toggle the enabled state of all projects.
66 | *
67 | * @return mixed
68 | */
69 | public function toggleAll()
70 | {
71 | $this->dataRepository->toggleAll();
72 |
73 | return $this->success();
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/webpack.mix.js:
--------------------------------------------------------------------------------
1 | let mix = require('laravel-mix');
2 |
3 | // var webpack = require('webpack');
4 |
5 | // var LiveReloadPlugin = require('webpack-livereload-plugin');
6 |
7 | mix.js('src/resources/assets/js/app.js', 'src/public/js')
8 | .sass('src/resources/assets/sass/app.scss', 'src/public/css');
9 |
10 | // mix.webpackConfig({
11 | // plugins: [
12 | // // new LiveReloadPlugin(),
13 | //
14 | // // new webpack.ProvidePlugin({
15 | // // $: "jquery",
16 | // // jQuery: "jquery",
17 | // // "window.jQuery": "jquery",
18 | // // Tether: "tether",
19 | // // "window.Tether": "tether",
20 | // // Alert: "exports-loader?Alert!bootstrap/js/dist/alert",
21 | // // Button: "exports-loader?Button!bootstrap/js/dist/button",
22 | // // Carousel: "exports-loader?Carousel!bootstrap/js/dist/carousel",
23 | // // Collapse: "exports-loader?Collapse!bootstrap/js/dist/collapse",
24 | // // Dropdown: "exports-loader?Dropdown!bootstrap/js/dist/dropdown",
25 | // // Modal: "exports-loader?Modal!bootstrap/js/dist/modal",
26 | // // Popover: "exports-loader?Popover!bootstrap/js/dist/popover",
27 | // // Scrollspy: "exports-loader?Scrollspy!bootstrap/js/dist/scrollspy",
28 | // // Tab: "exports-loader?Tab!bootstrap/js/dist/tab",
29 | // // Tooltip: "exports-loader?Tooltip!bootstrap/js/dist/tooltip",
30 | // // Util: "exports-loader?Util!bootstrap/js/dist/util",
31 | // // })
32 | // ]
33 | // });
34 |
35 | mix.webpackConfig({
36 | node: {
37 | console: false,
38 | fs: 'empty',
39 | net: 'empty',
40 | tls: 'empty'
41 | }
42 | });
43 |
44 |
45 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | app['config']->set("ci.{$key}", $value);
16 | }
17 |
18 | return $this->app['config']->get("ci.{$key}");
19 | }
20 |
21 | private function configureDatabase()
22 | {
23 | if (!file_exists($path = __DIR__.'/databases')) {
24 | mkdir($path);
25 | }
26 |
27 | touch($this->database = tempnam($path, 'database.sqlite.'));
28 |
29 | app()->config->set(
30 | 'database.connections.testbench',
31 | [
32 | 'driver' => 'sqlite',
33 | 'database' => $this->database,
34 | 'prefix' => '',
35 | ]
36 | );
37 | }
38 |
39 | private function deleteDatabase()
40 | {
41 | @unlink($this->database);
42 | }
43 |
44 | protected function setUp()
45 | {
46 | dd('configure');
47 | parent::setUp();
48 |
49 | $this->configureDatabase();
50 |
51 | $this->artisan('migrate:fresh', ['--database' => 'testbench']);
52 | }
53 |
54 | protected function tearDown()
55 | {
56 | parent::tearDown();
57 |
58 | $this->deleteDatabase();
59 | }
60 |
61 | protected function getPackageProviders($app)
62 | {
63 | $this->app = $app;
64 |
65 | return [
66 | TdddServiceProvider::class,
67 | ];
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/package/Support/Notifier.php:
--------------------------------------------------------------------------------
1 | enabled()) {
32 | return false;
33 | }
34 |
35 | collect(config('tddd.notifications.channels'))->each(function ($value, $channel) use ($tests) {
36 | event(new TestsFailed($tests, $channel));
37 |
38 | event(new UserNotifiedOfFailure($tests));
39 | });
40 | }
41 |
42 | /**
43 | * Send a notification.
44 | *
45 | * @param $title
46 | * @param $body
47 | * @param null $icon
48 | *
49 | * @return bool
50 | */
51 | public function notifyViaDesktop($title, $body, $icon = null)
52 | {
53 | if (!static::enabled()) {
54 | return false;
55 | }
56 |
57 | $notifier = NotifierFactory::create();
58 |
59 | $notification =
60 | (new Notification())
61 | ->setTitle($title)
62 | ->setBody($body);
63 |
64 | if (!is_null($icon)) {
65 | $notification->setIcon('http://vjeantet.fr/images/logo.png');
66 | }
67 |
68 | $notifier->send($notification);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/package/Support/jasonlewis/resource-watcher/src/JasonLewis/ResourceWatcher/Event.php:
--------------------------------------------------------------------------------
1 | resource = $resource;
55 | $this->code = $code;
56 | }
57 |
58 | /**
59 | * Get the resource event code.
60 | *
61 | * @return int
62 | */
63 | public function getCode()
64 | {
65 | return $this->code;
66 | }
67 |
68 | /**
69 | * Get the resource.
70 | *
71 | * @return \JasonLewis\ResourceWatcher\Resource\ResourceInterface
72 | */
73 | public function getResource()
74 | {
75 | return $this->resource;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pragmarx/tddd",
3 |
4 | "description": "A Self-Hosted TDD Dashboard & Tests Watcher",
5 |
6 | "keywords": [
7 | "laravel", "ci", "continuous integration", "test",
8 | "testing", "test-driven development", "tdd", "bdd",
9 | "watcher", "phpunit", "dusk", "behat", "phpspec",
10 | "codeception", "atoum", "tester"
11 | ],
12 |
13 | "license": "MIT",
14 |
15 | "authors": [
16 | {
17 | "name": "Antonio Carlos Ribeiro",
18 | "email": "acr@antoniocarlosribeiro.com",
19 | "role": "Creator & Designer"
20 | }
21 | ],
22 |
23 | "require": {
24 | "php": ">=7.0",
25 | "laravel/framework": ">=5.5",
26 | "pragmarx/support": "~0.8",
27 | "sensiolabs/ansi-to-html": "~1",
28 | "symfony/process": "~3",
29 | "guzzlehttp/guzzle": "~6.3",
30 | "jolicode/jolinotif": "~1.2",
31 | "doctrine/dbal": "~2.5",
32 | "symfony/yaml": "~3.2",
33 | "pusher/pusher-php-server": "~3.0"
34 | },
35 |
36 | "require-dev": {
37 | "phpunit/phpunit": "^6.5",
38 | "orchestra/testbench": "~3.5"
39 | },
40 |
41 | "autoload": {
42 | "psr-4": {
43 | "PragmaRX\\Tddd\\Package\\": "src/package",
44 | "PragmaRX\\Tddd\\Tests\\": "tests/"
45 | },
46 | "psr-0": {
47 | "JasonLewis\\ResourceWatcher": "src/package/Support/jasonlewis/resource-watcher/src"
48 | },
49 | "files": [
50 | "src/package/Support/helpers.php"
51 | ]
52 | },
53 |
54 | "extra": {
55 | "component": "package",
56 | "laravel": {
57 | "providers": [
58 | "PragmaRX\\Tddd\\Package\\ServiceProvider"
59 | ]
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/package/Data/Models/Test.php:
--------------------------------------------------------------------------------
1 | path, $this->name]);
27 | }
28 |
29 | /**
30 | * Suite relation.
31 | *
32 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
33 | */
34 | public function suite()
35 | {
36 | return $this->belongsTo('PragmaRX\Tddd\Package\Data\Models\Suite');
37 | }
38 |
39 | /**
40 | * Get the test command.
41 | *
42 | * @param $value
43 | *
44 | * @return string
45 | */
46 | public function getTestCommandAttribute($value)
47 | {
48 | $command = $this->suite->testCommand;
49 |
50 | return $command.' '.$this->fullPath;
51 | }
52 |
53 | /**
54 | * Runs relation.
55 | *
56 | * @return \Illuminate\Database\Eloquent\Relations\HasMany
57 | */
58 | public function runs()
59 | {
60 | return $this->hasMany('PragmaRX\Tddd\Package\Data\Models\Run');
61 | }
62 |
63 | /**
64 | * Update test sha1.
65 | */
66 | public function updateSha1()
67 | {
68 | $this->sha1 = @sha1_file($this->fullPath);
69 |
70 | $this->save();
71 | }
72 |
73 | /**
74 | * Check if the sha1 changed.
75 | *
76 | * @return bool
77 | */
78 | public function sha1Changed()
79 | {
80 | return $this->sha1 !== @sha1_file($this->fullPath);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/resources/views/dashboard.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ config('tddd.root.names.dashboard') }}
6 |
7 |
8 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
22 |
23 |
40 |
41 |
44 |
45 | @if(config('app.env') == 'local')
46 |
47 | @endif
48 |
49 |
50 |
--------------------------------------------------------------------------------
/src/routes/web.php:
--------------------------------------------------------------------------------
1 | config('tddd.routes.prefixes.dashboard')], function () {
6 | Route::get('/', ['as' => 'tests-watcher.dashboard', 'uses' => 'Dashboard@index']);
7 |
8 | Route::get('/data', ['as' => 'tests-watcher.dashboard.data', 'uses' => 'Dashboard@data']);
9 | });
10 |
11 | Route::group(['prefix' => config('tddd.routes.prefixes.tests')], function () {
12 | Route::get('/reset/{project_id}', ['as' => 'tests-watcher.tests.reset', 'uses' => 'Tests@reset']);
13 |
14 | Route::get('/run/{test_id?}', ['as' => 'tests-watcher.tests.run', 'uses' => 'Tests@run']);
15 |
16 | Route::get('/{project_id}/{test_id}/enable/{enable}', ['as' => 'tests-watcher.tests.enable', 'uses' => 'Tests@enable']);
17 | });
18 |
19 | Route::group(['prefix' => config('tddd.routes.prefixes.projects')], function () {
20 | Route::get('/{project_id}/enable/{enable}', ['as' => 'tests-watcher.projects.enable', 'uses' => 'Projects@enable']);
21 |
22 | Route::get('/{project_id}/notify', ['as' => 'tests-watcher.tests.notify', 'uses' => 'Projects@notify']);
23 |
24 | Route::post('/reset', ['as' => 'tests-watcher.projects.reset', 'uses' => 'Projects@reset']);
25 |
26 | Route::post('/run', ['as' => 'tests-watcher.projects.run.all', 'uses' => 'Projects@run']);
27 |
28 | Route::get('/toggle-all', ['as' => 'tests-watcher.projects.toggle-all', 'uses' => 'Projects@toggleAll']);
29 | });
30 |
31 | Route::group(['prefix' => config('tddd.routes.prefixes.files')], function () {
32 | Route::get('/edit/{filename}/{suite_id}/{line?}', ['as' => 'tests-watcher.file.edit', 'uses' => 'Files@editFile']);
33 |
34 | Route::get('/{filename}/download', ['as' => 'tests-watcher.image.download', 'uses' => 'Files@imageDownload']);
35 | });
36 |
37 | Route::group(['prefix' => config('tddd.routes.prefixes.html')], function () {
38 | Route::get('/', ['as' => 'tests-watcher.html.view', 'uses' => 'Html@view']);
39 | });
40 |
--------------------------------------------------------------------------------
/src/package/Data/Models/Suite.php:
--------------------------------------------------------------------------------
1 | project->tests_full_path,
35 | $this->tests_path,
36 | ]
37 | );
38 | }
39 |
40 | /**
41 | * Project relation.
42 | *
43 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
44 | */
45 | public function project()
46 | {
47 | return $this->belongsTo('PragmaRX\Tddd\Package\Data\Models\Project');
48 | }
49 |
50 | /**
51 | * Tester relation.
52 | *
53 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
54 | */
55 | public function tester()
56 | {
57 | return $this->belongsTo('PragmaRX\Tddd\Package\Data\Models\Tester');
58 | }
59 |
60 | /**
61 | * Tests relation.
62 | *
63 | * @return \Illuminate\Database\Eloquent\Relations\HasMany
64 | */
65 | public function tests()
66 | {
67 | return $this->hasMany('PragmaRX\Tddd\Package\Data\Models\Test');
68 | }
69 |
70 | /**
71 | * Get the test command.
72 | *
73 | * @return mixed
74 | */
75 | public function getTestCommandAttribute()
76 | {
77 | $command = $this->tester->command.' '.$this->command_options;
78 |
79 | return replace_suite_paths($this, $command);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/resources/assets/sass/_variables.scss:
--------------------------------------------------------------------------------
1 | // Brands
2 | $brand-primary: #2654b2;
3 | $brand-info: #4d80b2;
4 | $brand-success: #6DA34D;
5 | $brand-warning: #EA844D;
6 | $brand-danger: #931621;
7 | $brand-clear: #fdfdfd;
8 | $brand-secondary: #e9ecee;
9 | $brand-gray: #e9ecee;
10 | $brand-bluish: #73dbff;
11 | $brand-darker: #444444;
12 | $brand-darkest: #000000;
13 | $brand-html: #220e03;
14 | $brand-pill: #fffa00;
15 | $brand-white: #fff;
16 |
17 | $body-bg: $brand-clear;
18 | $brand-gray-darker: darken($brand-gray, 10%);
19 | $brand-secondary-darker: darken($brand-secondary, 10%);
20 | $brand-pill-darker: darken($brand-pill, 2%);
21 | $brand-clear-darker: darken($brand-clear, 2%);
22 | $brand-white-darker: darken($brand-white, 2%);
23 |
24 | // Brand lighter
25 | $brand-primary-light: lighten($brand-primary, 20%);
26 | $brand-info-light: lighten($brand-info, 20%);
27 | $brand-success-light: lighten($brand-success, 20%);
28 | $brand-warning-light: lighten($brand-warning, 20%);
29 | $brand-danger-light: lighten($brand-danger, 30%);
30 | $brand-clear-light: lighten($brand-clear, 20%);
31 | $brand-gray-light: lighten($brand-gray, 20%);
32 | $brand-bluish-light: lighten($brand-bluish, 20%);
33 | $brand-darker-light: lighten($brand-darker, 40%);
34 | $brand-darkest-light: lighten($brand-darkest, 20%);
35 | $brand-pill-light: lighten($brand-pill, 20%);
36 |
37 | $brand-failed: $brand-danger-light;
38 | $brand-passed: $brand-success-light;
39 |
40 | // Typography
41 | $icon-font-path: "~bootstrap-sass/assets/fonts/bootstrap/";
42 | $font-family-sans-serif: "Arsenal", sans-serif;
43 | $font-size-base: 0.9rem;
44 | $line-height-base: 1.6;
45 | $text-color: $brand-darkest;
46 |
47 | // Navbar
48 | $navbar-default-bg: #fff;
49 |
50 | // Buttons
51 | $btn-default-color: $text-color;
52 |
53 | // Inputs
54 | $input-border: lighten($text-color, 40%);
55 | $input-border-focus: lighten($brand-primary, 25%);
56 | $input-color-placeholder: lighten($text-color, 30%);
57 |
58 | // Panels
59 | $panel-default-heading-bg: $brand-clear;
60 |
61 | $fa-font-path: "~font-awesome/fonts";
62 |
63 |
--------------------------------------------------------------------------------
/src/package/Data/Repositories/Support/Runs.php:
--------------------------------------------------------------------------------
1 | $test->id,
28 | 'log' => '',
29 | 'was_ok' => false,
30 | ]);
31 | }
32 |
33 | /**
34 | * Get test info.
35 | *
36 | * @param $test
37 | *
38 | * @return array
39 | */
40 | protected function getTestInfo($test)
41 | {
42 | $run = Run::where('test_id', $test->id)->orderBy('created_at', 'desc')->first();
43 |
44 | return [
45 | 'id' => $test->id,
46 | 'suite_name' => $test->suite->name,
47 | 'project_name' => $test->suite->project->name,
48 | 'project_id' => $test->suite->project->id,
49 | 'path' => $test->path.DIRECTORY_SEPARATOR,
50 | 'name' => $test->name,
51 | 'edit_file_url' => $this->makeEditFileUrl($test),
52 | 'updated_at' => $test->updated_at->diffForHumans(null, false, true),
53 | 'state' => $test->state,
54 | 'enabled' => $test->enabled,
55 | 'editor_name' => $this->getEditor($test->suite)['name'],
56 | 'coverage' => ['enabled' => $test->suite->coverage_enabled, 'index' => $test->suite->coverage_index],
57 |
58 | 'run' => $run,
59 | 'notified_at' => is_null($run) ? null : $run->notified_at,
60 | 'log' => is_null($run) ? null : $run->log,
61 | 'html' => is_null($run) ? null : $run->html,
62 | 'image' => is_null($run) ? null : $run->png,
63 | 'time' => is_null($run) ? '' : (is_null($run->started_at) ? '' : $this->removeBefore($run->started_at->diffForHumans($run->ended_at))),
64 | ];
65 | }
66 |
67 | /**
68 | * Update the run log.
69 | *
70 | * @param $run
71 | * @param $output
72 | */
73 | public function updateRunLog($run, $output)
74 | {
75 | $run->log = $output;
76 |
77 | $run->save();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [1.0.0] - 2018-08-28
4 | ### Added
5 | - Pusher broadcasting
6 | - Ability to disble pooling
7 | ### Breaking Changes
8 | - Add PUSHER_CLUSTER= to your .env file
9 | - Update your root.yml according to config/tddd/root.yml, regarding the pooling key
10 |
11 | ## [0.9.9] - 2017-12-08
12 | ### Added
13 | - Toggle enabled/disabled projects
14 |
15 | ## [0.9.8] - 2017-12-08
16 | ### Added
17 | - Always display log show button
18 |
19 | ## [0.9.7] - 2017-12-07
20 | ### Changed
21 | - Removed clear artisan command
22 |
23 | ## [0.9.6] - 2017-12-07
24 | ### Added
25 | - Tests coverage tab on tests log view
26 | ### Changed
27 | - Config files are now in yaml format
28 | - Renamed package to TDDD - Test Driven Development Dashboard
29 |
30 | ## [0.9.5] - 2017-10-16
31 | ### Added
32 | - MySQL Support
33 | - Option to add environment variables to tester script
34 | - Config for different editors and a default editor
35 | - User can now set one different editor for each suite
36 | - Config for PHPStorm editor
37 | - Config for Sublime Text 3 editor
38 | - Config for Visual Studio Code editor
39 | - Option to disable project on Dashboard
40 | - Projects can now be disbled
41 | - No need to refresh page when rebooting watcher anymore
42 | - Input to filter projects
43 | - Option to configure the poll interval (defaults to 1500ms)
44 | - Show spinner on running project
45 | - Show badge (passed/failed) for each project
46 | - Button to run all tests on all (filtered) projects
47 | - Added AVA tester
48 | - Test state to log modal
49 | - Option to run test from the log modal
50 | - Option to reset state of all projects
51 | - Watch and automatically reload config
52 | - Display tester log in real-time
53 | ### Changed
54 | - Allow better configuration of editor's binary
55 | - Moved Laravel related classes out from Vendor\Laravel
56 | - Completely restructure package directory
57 | - License is now MIT
58 | - Improved regex matcher of editable source files (and lines)
59 | ### Fixed
60 | - Abending when tester used in suite does not exists
61 | - Piper script not being
62 | - Test subfolders not stored correctly
63 |
64 | ## [0.9.4] - 2017-10-11
65 | ### Added
66 | - Show Jest snapshots in dashboard log modal
67 | - Show exclustions in terminal log
68 |
69 | ## [0.9.3] - 2017-10-10
70 | ### Added
71 | - Support for Javascript testing (Jest)
72 | ### Changed
73 | - Ignore abstract PHP classes
74 |
75 | ## [0.9.2] - 2017-09-10
76 | ### Changed
77 | - Bug fixes
78 |
79 | ## [0.9.1] - 2017-08-10
80 | ### Changed
81 | - Bug fixes
82 |
83 | ## [0.9.0] - 2017-07-10
84 | ### Changed
85 | - Complete redesign of dashboard
86 | - Moved from ReactJS to VueJS
87 |
88 | ## [0.5.0] - 2015-03-10
89 | ### Added
90 | - Support Laravel 5
91 |
92 | ## [0.1.0] - 2014-07-06
93 | ### Added
94 | - First version
95 |
--------------------------------------------------------------------------------
/src/package/Services/Base.php:
--------------------------------------------------------------------------------
1 | command->{$type}($line);
48 | }
49 |
50 | /**
51 | * Show a comment in terminal.
52 | *
53 | * @param $comment
54 | */
55 | public function showComment($comment)
56 | {
57 | $this->command->comment($comment);
58 | }
59 |
60 | /**
61 | * Get the event name.
62 | *
63 | * @param $eventCode
64 | *
65 | * @return string
66 | */
67 | protected function getEventName($eventCode)
68 | {
69 | $event = '(unknown event)';
70 |
71 | switch ($eventCode) {
72 | case Event::RESOURCE_DELETED:
73 | $event = 'deleted';
74 | break;
75 | case Event::RESOURCE_CREATED:
76 | $event = 'created';
77 | break;
78 | case Event::RESOURCE_MODIFIED:
79 | $event = 'modified';
80 | break;
81 | }
82 |
83 | return $event;
84 | }
85 |
86 | /**
87 | * Display messages in terminal.
88 | *
89 | * @param $messages
90 | */
91 | protected function displayMessages($messages)
92 | {
93 | $fatal = $messages->reduce(function ($carry, $message) {
94 | $prefix = $message['type'] == 'error' ? 'FATAL ERROR: ' : '';
95 |
96 | $this->command->{$message['type']}($prefix.$message['body']);
97 |
98 | if ($message['type'] == 'error') {
99 | return true;
100 | }
101 |
102 | return $carry;
103 | });
104 |
105 | if ($fatal == true) {
106 | die;
107 | }
108 | }
109 |
110 | /**
111 | * Set the command.
112 | *
113 | * @param $command
114 | */
115 | protected function setCommand($command)
116 | {
117 | $this->command = $command;
118 |
119 | if (!is_null($this->loader)) {
120 | $this->loader->setCommand($this->command);
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/package/Services/Config.php:
--------------------------------------------------------------------------------
1 | yaml = $yaml;
32 | }
33 |
34 | /**
35 | * Check if the config is valid.
36 | *
37 | * @return bool
38 | */
39 | protected function configIsValid()
40 | {
41 | return is_array($this->config) && count($this->config) > 0;
42 | }
43 |
44 | /**
45 | * Get a configuration key.
46 | *
47 | * @param $key
48 | * @param mixed|null $default
49 | *
50 | * @throws \Exception
51 | *
52 | * @return mixed
53 | */
54 | public function get($key, $default = null)
55 | {
56 | $this->loadConfig();
57 |
58 | return config("tddd.{$key}", $default);
59 | }
60 |
61 | /**
62 | * Load the config.
63 | */
64 | public function loadConfig()
65 | {
66 | if ($this->configIsValid()) {
67 | return;
68 | }
69 |
70 | $this->yaml->loadToConfig($this->getConfigPath(), 'tddd', true)->toArray();
71 | }
72 |
73 | /**
74 | * Force the config to be reloaded.
75 | */
76 | public function reloadConfig()
77 | {
78 | $this->invalidateConfig();
79 |
80 | $this->loadConfig();
81 | }
82 |
83 | /**
84 | * Get a list of all config files.
85 | *
86 | * @return Collection
87 | */
88 | public function getConfigFiles()
89 | {
90 | return $this->yaml->listFiles($this->getConfigPath())->flatten();
91 | }
92 |
93 | /**
94 | * Get the config path.
95 | *
96 | * @return \Illuminate\Config\Repository|mixed
97 | */
98 | public function getConfigPath()
99 | {
100 | if (is_null($this->configPath)) {
101 | $this->configPath = replace_laravel_paths(config('tddd-base.path'));
102 | }
103 |
104 | return $this->configPath;
105 | }
106 |
107 | /**
108 | * Set the config item.
109 | *
110 | * @param $data
111 | */
112 | public function set($data)
113 | {
114 | $this->config = array_merge($data, $this->config);
115 |
116 | $this->mergeWithLaravelConfig();
117 | }
118 |
119 | /**
120 | * Invalidate the current config.
121 | */
122 | public function invalidateConfig()
123 | {
124 | $this->config = [];
125 | }
126 |
127 | /**
128 | * Check if a file is a config file.
129 | *
130 | * @param $file
131 | *
132 | * @return bool
133 | */
134 | public function isConfigFile($file)
135 | {
136 | return $this->getConfigFiles()->contains($file);
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/package/Support/jasonlewis/resource-watcher/src/JasonLewis/ResourceWatcher/Tracker.php:
--------------------------------------------------------------------------------
1 | tracked[$resource->getKey()] = [$resource, $listener];
27 | }
28 |
29 | /**
30 | * Determine if a resource is tracked.
31 | *
32 | * @param \JasonLewis\ResourceWatcher\Resource\ResourceInterface $resource
33 | */
34 | public function isTracked(ResourceInterface $resource)
35 | {
36 | return isset($this->tracked[$resource->getKey()]);
37 | }
38 |
39 | /**
40 | * Get the tracked resources.
41 | *
42 | * @return array
43 | */
44 | public function getTracked()
45 | {
46 | return $this->tracked;
47 | }
48 |
49 | /**
50 | * Detect any changes on the tracked resources.
51 | *
52 | * @return void
53 | */
54 | public function checkTrackings()
55 | {
56 | foreach ($this->tracked as $name => $tracked) {
57 | list($resource, $listener) = $tracked;
58 |
59 | if (!$events = $resource->detectChanges()) {
60 | continue;
61 | }
62 |
63 | foreach ($events as $event) {
64 | if ($event instanceof Event) {
65 | $this->callListenerBindings($listener, $event);
66 | }
67 | }
68 | }
69 | }
70 |
71 | /**
72 | * Call the bindings on the listener for a given event.
73 | *
74 | * @param \JasonLewis\ResourceWatcher\Listener $listener
75 | * @param \JasonLewis\ResourceWatcher\Event $event
76 | *
77 | * @return void
78 | */
79 | protected function callListenerBindings(Listener $listener, Event $event)
80 | {
81 | $binding = $listener->determineEventBinding($event);
82 |
83 | if ($listener->hasBinding($binding)) {
84 | foreach ($listener->getBindings($binding) as $callback) {
85 | $resource = $event->getResource();
86 |
87 | call_user_func($callback, $resource, $resource->getPath());
88 | }
89 | }
90 |
91 | // If a listener has a binding for anything we'll also spin through
92 | // them and call each of them.
93 | if ($listener->hasBinding('*')) {
94 | foreach ($listener->getBindings('*') as $callback) {
95 | $resource = $event->getResource();
96 |
97 | call_user_func($callback, $event, $resource, $resource->getPath());
98 | }
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/package/Support/jasonlewis/resource-watcher/src/JasonLewis/ResourceWatcher/Resource/DirectoryResource.php:
--------------------------------------------------------------------------------
1 | descendants = $this->detectDirectoryDescendants();
26 | }
27 |
28 | /**
29 | * Detect any changes to the resource.
30 | *
31 | * @return array
32 | */
33 | public function detectChanges()
34 | {
35 | $events = parent::detectChanges();
36 |
37 | // When a descendant file is created or deleted a modified event is fired on the
38 | // directory. This is the only way a directory will receive a modified event and
39 | // will thus result in two events being fired for a single descendant modification
40 | // within the directory. This will clear the events if we got a modified event.
41 | if ($events and $events[0]->getCode() == Event::RESOURCE_MODIFIED) {
42 | $events = [];
43 | }
44 |
45 | foreach ($this->descendants as $key => $descendant) {
46 | $descendantEvents = $descendant->detectChanges();
47 |
48 | foreach ($descendantEvents as $event) {
49 | if ($event instanceof Event and $event->getCode() == Event::RESOURCE_DELETED) {
50 | unset($this->descendants[$key]);
51 | }
52 | }
53 |
54 | $events = array_merge($events, $descendantEvents);
55 | }
56 |
57 | // If this directory still exists we'll check the directories descendants again for any
58 | // new descendants.
59 | if ($this->exists) {
60 | foreach ($this->detectDirectoryDescendants() as $key => $descendant) {
61 | if (!isset($this->descendants[$key])) {
62 | $this->descendants[$key] = $descendant;
63 |
64 | $events[] = new Event($descendant, Event::RESOURCE_CREATED);
65 | }
66 | }
67 | }
68 |
69 | return $events;
70 | }
71 |
72 | /**
73 | * Detect the descendant resources of the directory.
74 | *
75 | * @return array
76 | */
77 | protected function detectDirectoryDescendants()
78 | {
79 | $descendants = [];
80 |
81 | foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->getPath())) as $file) {
82 | if ($file->isDir() and !in_array($file->getBasename(), ['.', '..'])) {
83 | $resource = new self($file, $this->files);
84 |
85 | $descendants[$resource->getKey()] = $resource;
86 | } elseif ($file->isFile()) {
87 | $resource = new FileResource($file, $this->files);
88 |
89 | $descendants[$resource->getKey()] = $resource;
90 | }
91 | }
92 |
93 | return $descendants;
94 | }
95 |
96 | /**
97 | * Get the descendants of the directory.
98 | *
99 | * @return array
100 | */
101 | public function getDescendants()
102 | {
103 | return $this->descendants;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/package/Data/Repositories/Support/Queue.php:
--------------------------------------------------------------------------------
1 | state == Constants::STATE_QUEUED
22 | &&
23 | QueueModel::where('test_id', $test->id)->first();
24 | }
25 |
26 | /**
27 | * Queue all tests.
28 | */
29 | public function queueAllTests()
30 | {
31 | $this->showProgress('QUEUE: adding tests to queue...');
32 |
33 | foreach (Test::all() as $test) {
34 | $this->addTestToQueue($test);
35 | }
36 | }
37 |
38 | /**
39 | * Queue all tests from a particular suite.
40 | *
41 | * @param $suite_id
42 | */
43 | public function queueTestsForSuite($suite_id)
44 | {
45 | $tests = Test::where('suite_id', $suite_id)->get();
46 |
47 | foreach ($tests as $test) {
48 | $this->addTestToQueue($test);
49 | }
50 | }
51 |
52 | /**
53 | * Add a test to the queue.
54 | *
55 | * @param $test
56 | * @param bool $force
57 | */
58 | public function addTestToQueue($test, $force = false)
59 | {
60 | if ($test->enabled && $test->suite->project->enabled && !$this->isEnqueued($test)) {
61 | $test->updateSha1();
62 |
63 | QueueModel::updateOrCreate(['test_id' => $test->id]);
64 |
65 | // After queueing, if it's the only one, it may take the test and run it right away,
66 | // so we must wait a little for it to happen
67 | sleep(1);
68 |
69 | // We then get a fresh model, which may have a different state now
70 | $test = $test->fresh();
71 |
72 | if ($force || !in_array($test->state, [Constants::STATE_RUNNING, Constants::STATE_QUEUED])) {
73 | $test->state = Constants::STATE_QUEUED;
74 |
75 | $test->timestamps = false;
76 |
77 | $test->save();
78 | }
79 | }
80 | }
81 |
82 | /**
83 | * Get a test from the queue.
84 | *
85 | * @return \PragmaRX\Tddd\Package\Data\Models\Test|null
86 | */
87 | public function getNextTestFromQueue()
88 | {
89 | $query = QueueModel::join('tddd_tests', 'tddd_tests.id', '=', 'tddd_queue.test_id')
90 | ->where('tddd_tests.enabled', true)
91 | ->where('tddd_tests.state', '!=', Constants::STATE_RUNNING);
92 |
93 | if (!$queue = $query->first()) {
94 | return;
95 | }
96 |
97 | return $queue->test;
98 | }
99 |
100 | /**
101 | * Remove test from que run queue.
102 | *
103 | * @param $test
104 | *
105 | * @return mixed
106 | */
107 | protected function removeTestFromQueue($test)
108 | {
109 | QueueModel::where('test_id', $test->id)->delete();
110 |
111 | return $test;
112 | }
113 |
114 | /**
115 | * Reset a test to idle state.
116 | *
117 | * @param $test
118 | */
119 | protected function resetTest($test)
120 | {
121 | QueueModel::where('test_id', $test->id)->delete();
122 |
123 | $test->state = Constants::STATE_IDLE;
124 |
125 | $test->timestamps = false;
126 |
127 | $test->save();
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/package/Support/jasonlewis/resource-watcher/src/JasonLewis/ResourceWatcher/Resource/FileResource.php:
--------------------------------------------------------------------------------
1 | resource = $resource;
57 | $this->path = $resource->getRealPath();
58 | $this->files = $files;
59 | $this->exists = $this->files->exists($this->path);
60 | $this->lastModified = !$this->exists ?: $this->files->lastModified($this->path);
61 | }
62 |
63 | /**
64 | * Detect any changes to the resource.
65 | *
66 | * @return array
67 | */
68 | public function detectChanges()
69 | {
70 | clearstatcache(true, $this->path);
71 |
72 | if (!$this->exists and $this->files->exists($this->path)) {
73 | $this->lastModified = $this->files->lastModified($this->path);
74 | $this->exists = true;
75 |
76 | return [new Event($this, Event::RESOURCE_CREATED)];
77 | } elseif ($this->exists and !$this->files->exists($this->path)) {
78 | $this->exists = false;
79 |
80 | return [new Event($this, Event::RESOURCE_DELETED)];
81 | } elseif ($this->exists and $this->isModified()) {
82 | $this->lastModified = $this->files->lastModified($this->path);
83 |
84 | return [new Event($this, Event::RESOURCE_MODIFIED)];
85 | }
86 |
87 | return [];
88 | }
89 |
90 | /**
91 | * Determine if the resource has been modified.
92 | *
93 | * @return bool
94 | */
95 | public function isModified()
96 | {
97 | return $this->lastModified < $this->files->lastModified($this->path);
98 | }
99 |
100 | /**
101 | * Get the resource key.
102 | *
103 | * @return string
104 | */
105 | public function getKey()
106 | {
107 | return md5($this->path);
108 | }
109 |
110 | /**
111 | * Get the path of the resource.
112 | *
113 | * @return string
114 | */
115 | public function getPath()
116 | {
117 | return $this->path;
118 | }
119 |
120 | /**
121 | * Get the resource SplFileInfo.
122 | *
123 | * @return \SplFileInfo
124 | */
125 | public function getSplFileInfo()
126 | {
127 | return $this->resource;
128 | }
129 |
130 | /**
131 | * Get the resources last modified timestamp.
132 | *
133 | * @return int
134 | */
135 | public function getLastModified()
136 | {
137 | return $this->lastModified;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/package/Support/jasonlewis/resource-watcher/src/JasonLewis/ResourceWatcher/Watcher.php:
--------------------------------------------------------------------------------
1 | tracker = $tracker;
46 | $this->files = $files;
47 | }
48 |
49 | /**
50 | * Register a resource to be watched.
51 | *
52 | * @param string $resource
53 | *
54 | * @return \JasonLewis\ResourceWatcher\Listener
55 | */
56 | public function watch($resource)
57 | {
58 | if (!$this->files->exists($resource)) {
59 | throw new RuntimeException('Resource must exist before you can watch it.');
60 | } elseif ($this->files->isDirectory($resource)) {
61 | $resource = new DirectoryResource(new SplFileInfo($resource), $this->files);
62 |
63 | $resource->setupDirectory();
64 | } else {
65 | $resource = new FileResource(new SplFileInfo($resource), $this->files);
66 | }
67 |
68 | // The listener gives users the ability to bind listeners on the events
69 | // created when watching a file or directory. We'll give the listener
70 | // to the tracker so the tracker can fire any bound listeners.
71 | $listener = new Listener();
72 |
73 | $this->tracker->register($resource, $listener);
74 |
75 | return $listener;
76 | }
77 |
78 | /**
79 | * Start watching for a given interval. The interval and timeout and measured
80 | * in microseconds, so 1,000,000 microseconds is equal to 1 second.
81 | *
82 | * @param int $interval
83 | * @param int $timeout
84 | * @param \Closure $callback
85 | *
86 | * @return void
87 | */
88 | public function startWatch($interval = 1000000, $timeout = null, Closure $callback = null)
89 | {
90 | $this->watching = true;
91 |
92 | $timeWatching = 0;
93 |
94 | while ($this->watching) {
95 | if (is_callable($callback)) {
96 | call_user_func($callback, $this);
97 | }
98 |
99 | usleep($interval);
100 |
101 | $this->tracker->checkTrackings();
102 |
103 | $timeWatching += $interval;
104 |
105 | if (!is_null($timeout) and $timeWatching >= $timeout) {
106 | $this->stopWatch();
107 | }
108 | }
109 | }
110 |
111 | /**
112 | * Alias of startWatch.
113 | *
114 | * @param int $interval
115 | * @param int $timeout
116 | * @param \Closure $callback
117 | *
118 | * @return void
119 | */
120 | public function start($interval = 1000000, $timeout = null, Closure $callback = null)
121 | {
122 | $this->startWatch($interval, $timeout, $callback);
123 | }
124 |
125 | /**
126 | * Get the tracker instance.
127 | *
128 | * @return \JasonLewis\ResourceWatcher\Tracker
129 | */
130 | public function getTracker()
131 | {
132 | return $this->tracker;
133 | }
134 |
135 | /**
136 | * Stop watching.
137 | *
138 | * @return void
139 | */
140 | public function stopWatch()
141 | {
142 | $this->watching = false;
143 | }
144 |
145 | /**
146 | * Alias of stopWatch.
147 | *
148 | * @return void
149 | */
150 | public function stop()
151 | {
152 | $this->stopWatch();
153 | }
154 |
155 | /**
156 | * Determine if watcher is watching.
157 | *
158 | * @return bool
159 | */
160 | public function isWatching()
161 | {
162 | return $this->watching;
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/src/package/Support/jasonlewis/resource-watcher/README.md:
--------------------------------------------------------------------------------
1 | # Resource Watcher
2 |
3 | A resource watcher allows you to watch a resource for any changes. This means you can watch a directory and then listen for any changes to files within that directory or to the directory itself.
4 |
5 | [](https://travis-ci.org/jasonlewis/resource-watcher)
6 |
7 | ## Installation
8 |
9 | To install Resource Watcher add it to the `requires` key of your `composer.json` file.
10 |
11 | ```
12 | "jasonlewis/resource-watcher": "1.1.*"
13 | ```
14 |
15 | Then update your project with `composer update`.
16 |
17 | ## Usage
18 |
19 | The Resource Watcher is best used from a console. An example of a console command can be found in the `watcher` file. This file is commented to give you
20 | an idea of how to configure and use a resource watcher. Once you've customized the command to your liking you can run it from your console.
21 |
22 | ```
23 | $ php watcher
24 | ```
25 |
26 | Any changes you make to the resource will be outputted to the console.
27 |
28 | ## Quick Overview
29 |
30 | To watch resources you first need an instance of `JasonLewis\ResourceWatcher\Watcher`. This class has a few dependencies (`JasonLewis\ResourceWatcher\Tracker` and `Illuminate\Filesystem\Filesystem`) that must also be instantiated.
31 |
32 | ```php
33 | $files = new Illuminate\Filesystem\Filesystem;
34 | $tracker = new JasonLewis\ResourceWatcher\Tracker;
35 |
36 | $watcher = new JasonLewis\ResourceWatcher\Watcher($tracker, $files);
37 | ```
38 |
39 | Now that we have our watcher we can create a listener for a given resource.
40 |
41 | ```php
42 | $listener = $watcher->watch('path/to/resource');
43 | ```
44 |
45 | When you watch a resource an instance of `JasonLewis\ResourceWatcher\Listener` is returned. With this we can now listen for certain events on a resource.
46 |
47 | There are three events we can listen for: `modify`, `create`, and `delete`. The callback you give to the listener receives two parameters, the first being an implementation of `JasonLewis\ResourceWatcher\Resource\ResourceInterface` and the second being the absolute path to the resource.
48 |
49 | ```php
50 | $listener->modify(function($resource, $path)
51 | {
52 | echo "{$path} has been modified.".PHP_EOL;
53 | });
54 | ```
55 |
56 | You can use the alias methods as well.
57 |
58 | ```php
59 | $listener->onModify(function($resource, $path)
60 | {
61 | echo "{$path} has been modified.".PHP_EOL;
62 | });
63 | ```
64 |
65 | You can also listen for any of these events. This time the callback receives a different set of parameters, the first being an instance of `JasonLewis\ResourceWatcher\Event` and the remaining two being the same as before.
66 |
67 | ```php
68 | $listener->anything(function($event, $resource, $path)
69 | {
70 |
71 | });
72 | ```
73 |
74 | > Remember that each call to `$watcher->watch()` will return an instance of `JasonLewis\ResourceWatcher\Listener`, so be sure you attach listeners to the right one!
75 |
76 | Once you're watching some resources and have your listeners set up you can start the watching process.
77 |
78 | ```php
79 | $watcher->start();
80 | ```
81 |
82 | By default the watcher will poll for changes every second. You can adjust this by passing in an optional first parameter to the `start` method. The polling interval is given in microseconds, so 1,000,000 microseconds is 1 second. The watch will continue until such time that it's aborted from the console. To set a timeout pass in the number of microseconds before the watch will abort as the second parameter.
83 |
84 | The `start` method can also be given a callback as an optional third parameter. This callback will be fired before checking for any changes to resources.
85 |
86 | ```php
87 | $watcher->start(1000000, null, function($watcher)
88 | {
89 | // Perhaps perform some other check and then stop the watch.
90 | $watcher->stop();
91 | });
92 | ```
93 |
94 | ## Framework Integration
95 |
96 | ### Laravel 4
97 |
98 | Included is a service provider for the Laravel 4 framework. This service provider will bind an instance of `JasonLewis\ResourceWatcher\Watcher` to the application container under the `watcher` key.
99 |
100 | ```php
101 | $listener = $app['watcher']->watch('path/to/resource');
102 |
103 | // Or if you don't have access to an instance of the application container.
104 | $listener = app('tddd.watcher')->watch('path/to/resource');
105 | ```
106 |
107 | Register `JasonLewis\ResourceWatcher\Integration\LaravelServiceProvider` in the array of providers in `app/config/app.php`.
108 |
109 | ## License
110 |
111 | Resource Watcher is released under the 2-clause BSD license. See the `LICENSE` for more details.
112 |
--------------------------------------------------------------------------------
/src/package/Support/jasonlewis/resource-watcher/src/JasonLewis/ResourceWatcher/Listener.php:
--------------------------------------------------------------------------------
1 | registerBinding($event, $callback);
34 | }
35 |
36 | /**
37 | * Bind to anything.
38 | *
39 | * @param \Closure $callback
40 | *
41 | * @return void
42 | */
43 | public function onAnything(Closure $callback)
44 | {
45 | $this->on('*', $callback);
46 | }
47 |
48 | /**
49 | * Alias of the onAnything event.
50 | *
51 | * @param \Closure $callback
52 | *
53 | * @return void
54 | */
55 | public function anything(Closure $callback)
56 | {
57 | $this->on('*', $callback);
58 | }
59 |
60 | /**
61 | * Bind to a modify event.
62 | *
63 | * @param \Closure $callback
64 | *
65 | * @return void
66 | */
67 | public function onModify(Closure $callback)
68 | {
69 | $this->on('modify', $callback);
70 | }
71 |
72 | /**
73 | * Alias of the onModify method.
74 | *
75 | * @param \Closure $callback
76 | *
77 | * @return void
78 | */
79 | public function modify(Closure $callback)
80 | {
81 | $this->on('modify', $callback);
82 | }
83 |
84 | /**
85 | * Bind to a delete event.
86 | *
87 | * @param \Closure $callback
88 | *
89 | * @return void
90 | */
91 | public function onDelete(Closure $callback)
92 | {
93 | $this->on('delete', $callback);
94 | }
95 |
96 | /**
97 | * Alias of the onDelete method.
98 | *
99 | * @param \Closure $callback
100 | *
101 | * @return void
102 | */
103 | public function delete(Closure $callback)
104 | {
105 | $this->on('delete', $callback);
106 | }
107 |
108 | /**
109 | * Bind to a create event.
110 | *
111 | * @param \Closure $callback
112 | *
113 | * @return void
114 | */
115 | public function onCreate(Closure $callback)
116 | {
117 | $this->on('create', $callback);
118 | }
119 |
120 | /**
121 | * Alias of the onCreate method.
122 | *
123 | * @param \Closure $callback
124 | *
125 | * @return void
126 | */
127 | public function create(Closure $callback)
128 | {
129 | $this->on('create', $callback);
130 | }
131 |
132 | /**
133 | * Register a binding.
134 | *
135 | * @param string $binding
136 | * @param \Closure $callback
137 | *
138 | * @return void
139 | */
140 | protected function registerBinding($binding, Closure $callback)
141 | {
142 | $this->bindings[$binding][] = $callback;
143 | }
144 |
145 | /**
146 | * Determine if a binding is bound to the listener.
147 | *
148 | * @param string $binding
149 | *
150 | * @return bool
151 | */
152 | public function hasBinding($binding)
153 | {
154 | return isset($this->bindings[$binding]);
155 | }
156 |
157 | /**
158 | * Get the bindings or a specific array of bindings.
159 | *
160 | * @param string $binding
161 | *
162 | * @return array
163 | */
164 | public function getBindings($binding = null)
165 | {
166 | if (is_null($binding)) {
167 | return $this->bindings;
168 | }
169 |
170 | return $this->bindings[$binding];
171 | }
172 |
173 | /**
174 | * Determine the binding for a given event.
175 | *
176 | * @param \JasonLewis\ResourceWatcher\Event $event
177 | *
178 | * @return string
179 | */
180 | public function determineEventBinding(Event $event)
181 | {
182 | switch ($event->getCode()) {
183 | case Event::RESOURCE_DELETED:
184 | return 'delete';
185 | break;
186 | case Event::RESOURCE_CREATED:
187 | return 'create';
188 | break;
189 | case Event::RESOURCE_MODIFIED:
190 | return 'modify';
191 | break;
192 | }
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/src/package/Services/Loader.php:
--------------------------------------------------------------------------------
1 | dataRepository = $dataRepository;
39 | }
40 |
41 | /**
42 | * Create or update the suite.
43 | *
44 | * @param $suite_name
45 | * @param $project
46 | * @param $suite_data
47 | */
48 | private function createSuite($suite_name, $project, $suite_data)
49 | {
50 | $this->showProgress(" -- suite '{$suite_name}'");
51 |
52 | if (!$this->dataRepository->createOrUpdateSuite($suite_name, $project->id, $suite_data)) {
53 | $this->displayMessages($this->dataRepository->getMessages());
54 | die;
55 | }
56 | }
57 |
58 | /**
59 | * Read configuration and load testers, projects, suites...
60 | */
61 | public function loadEverything($showTests = false)
62 | {
63 | $this->showProgress('Config loaded from '.Config::getConfigPath());
64 |
65 | $this->loadTesters();
66 |
67 | $this->loadProjects();
68 |
69 | $this->loadTests($showTests);
70 | }
71 |
72 | /**
73 | * Load all testers to database.
74 | */
75 | public function loadTesters()
76 | {
77 | $this->showProgress('Loading testers...', 'info');
78 |
79 | if (!is_arrayable($testers = $this->config('testers')) or count($testers) == 0) {
80 | $this->showProgress('No testers found.', 'error');
81 |
82 | return;
83 | }
84 |
85 | foreach ($testers as $data) {
86 | $this->showProgress("TESTER: {$data['name']}");
87 |
88 | $this->dataRepository->createOrUpdateTester($data);
89 | }
90 |
91 | $this->dataRepository->deleteMissingTesters(array_keys($testers));
92 | }
93 |
94 | /**
95 | * Load all projects to database.
96 | */
97 | public function loadProjects()
98 | {
99 | $this->showProgress('Loading projects and suites...', 'info');
100 |
101 | if (!is_arrayable($projects = $this->config('projects')) or count($projects) == 0) {
102 | $this->showProgress('No projects found.', 'error');
103 |
104 | return;
105 | }
106 |
107 | foreach ($projects as $data) {
108 | $this->showProgress("Project '{$data['name']}'", 'comment');
109 |
110 | $project = $this->dataRepository->createOrUpdateProject($data['name'], $data['path'], $data['tests_path']);
111 |
112 | $this->refreshProjectSuites($data, $project);
113 |
114 | $this->addToWatchFolders($data['path'], $data['watch_folders']);
115 |
116 | $this->addToExclusions($data['path'], $data['exclude']);
117 | }
118 |
119 | $this->dataRepository->deleteMissingProjects(collect($this->config('projects'))->pluck('name')->toArray());
120 | }
121 |
122 | /**
123 | * Load all test files to database.
124 | */
125 | public function loadTests($showTests)
126 | {
127 | $this->showProgress('Loading tests...', 'info');
128 |
129 | $this->dataRepository->syncTests($this->exclusions, $showTests);
130 |
131 | $this->displayMessages($this->dataRepository->getMessages());
132 | }
133 |
134 | /**
135 | * Add folders to the watch list.
136 | *
137 | * @param $path
138 | * @param $watch_folders
139 | */
140 | public function addToWatchFolders($path, $watch_folders)
141 | {
142 | collect($watch_folders)->each(function ($folder) use ($path) {
143 | $this->watchFolders[] = !file_exists($new = make_path([$path, $folder])) && file_exists($folder)
144 | ? $folder
145 | : $new;
146 | });
147 | }
148 |
149 | /**
150 | * Add path to exclusions list.
151 | *
152 | * @param $path
153 | * @param $exclude
154 | */
155 | public function addToExclusions($path, $exclude)
156 | {
157 | collect($exclude)->each(function ($folder) use ($path) {
158 | $this->exclusions[] = $excluded = make_path([$path, $folder]);
159 |
160 | $this->showProgress("EXCLUDED: {$excluded}");
161 | });
162 | }
163 |
164 | /**
165 | * Refresh all suites for a project.
166 | *
167 | * @param $data
168 | * @param $project
169 | */
170 | private function refreshProjectSuites($data, $project)
171 | {
172 | $this->dataRepository->removeMissingSuites($suites = $data['suites'], $project);
173 |
174 | collect($suites)->map(function ($data, $name) use ($project) {
175 | $this->createSuite($name, $project, $data);
176 | });
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/src/package/Data/Repositories/Support/Suites.php:
--------------------------------------------------------------------------------
1 | id : $project_id;
24 |
25 | if (is_null($tester = Tester::where('name', $suite_data['tester'])->first())) {
26 | $this->addMessage("Tester {$suite_data['tester']} not found.", 'error');
27 |
28 | return false;
29 | }
30 |
31 | return Suite::updateOrCreate(
32 | [
33 | 'name' => $name,
34 | 'project_id' => $project_id,
35 | ],
36 | [
37 | 'tester_id' => $tester->id,
38 | 'tests_path' => array_get($suite_data, 'tests_path'),
39 | 'command_options' => array_get($suite_data, 'command_options'),
40 | 'file_mask' => array_get($suite_data, 'file_mask'),
41 | 'retries' => array_get($suite_data, 'retries'),
42 | 'editor' => array_get($suite_data, 'editor'),
43 | 'coverage_enabled' => array_get($suite_data, 'coverage.enabled', false),
44 | 'coverage_index' => array_get($suite_data, 'coverage.index'),
45 | ]
46 | );
47 | }
48 |
49 | /**
50 | * Find suite by project and name.
51 | *
52 | * @param $name
53 | * @param $project_id
54 | *
55 | * @return \PragmaRX\Tddd\Package\Data\Models\Suite|null
56 | */
57 | public function findSuiteByNameAndProject($name, $project_id)
58 | {
59 | return Suite::where('name', $name)
60 | ->where('project_id', $project_id)
61 | ->first();
62 | }
63 |
64 | /**
65 | * Get all suites.
66 | *
67 | * @return \Illuminate\Database\Eloquent\Collection|static[]
68 | */
69 | public function getSuites()
70 | {
71 | return Suite::all();
72 | }
73 |
74 | /**
75 | * Find suite by id.
76 | *
77 | * @return \PragmaRX\Tddd\Package\Data\Models\Suite|null
78 | */
79 | public function findSuiteById($id)
80 | {
81 | return Suite::find($id);
82 | }
83 |
84 | /**
85 | * Remove suites that are not in present in config.
86 | *
87 | * @param $suites
88 | * @param $project
89 | */
90 | public function removeMissingSuites($suites, $project)
91 | {
92 | Suite::where('project_id', $project->id)->whereNotIn('name', collect($suites)->keys())->each(function ($suite) {
93 | $suite->delete();
94 | });
95 | }
96 |
97 | /**
98 | * Sync all tests for a particular suite.
99 | *
100 | * @param $suite
101 | * @param $exclusions
102 | */
103 | protected function syncSuiteTests($suite, $exclusions, $showTests)
104 | {
105 | $files = $this->getAllFilesFromSuite($suite);
106 |
107 | foreach ($files as $file) {
108 | if (!$this->isExcluded($exclusions, null, $file) && $this->isTestable($file->getRealPath())) {
109 | $this->createOrUpdateTest($file, $suite);
110 |
111 | if ($showTests) {
112 | $this->addMessage('NEW TEST: '.$file->getRealPath());
113 | }
114 | } else {
115 | // If the test already exists, delete it.
116 | //
117 | if ($test = $this->findTestByNameAndSuite($file, $suite)) {
118 | $test->delete();
119 | }
120 | }
121 | }
122 |
123 | foreach ($suite->tests as $test) {
124 | if (!file_exists($path = $test->fullPath)) {
125 | $test->delete();
126 | }
127 | }
128 | }
129 |
130 | /**
131 | * Get all files from a suite.
132 | *
133 | * @param $suite
134 | *
135 | * @return array
136 | */
137 | protected function getAllFilesFromSuite($suite)
138 | {
139 | if (!file_exists($suite->testsFullPath)) {
140 | die('FATAL ERROR: directory not found: '.$suite->testsFullPath.'.');
141 | }
142 |
143 | $files = Finder::create()->files()->in($suite->testsFullPath);
144 |
145 | if ($suite->file_mask) {
146 | $files->name($suite->file_mask);
147 | }
148 |
149 | return iterator_to_array($files, false);
150 | }
151 |
152 | /**
153 | * Get all suites for a path.
154 | *
155 | * @param $path
156 | *
157 | * @return mixed
158 | */
159 | public function getSuitesForPath($path)
160 | {
161 | $projects = $this->getProjects();
162 |
163 | // Reduce the collection of projects by those whose path properties
164 | // (should be only 1) are contained in the fullpath of our
165 | // changed file
166 | $filtered_projects = $projects->filter(function ($project) use ($path) {
167 | return substr_count($path, $project->path) > 0;
168 | });
169 |
170 | // Get filtered projects dependencies
171 | $depends = $projects->filter(function ($project) use ($filtered_projects) {
172 | if (!is_null($depends = config("tddd.projects.{$project->name}.depends"))) {
173 | return collect($depends)->filter(function ($item) use ($filtered_projects) {
174 | return !is_null($filtered_projects->where('name', $item)->first());
175 | });
176 | }
177 |
178 | return false;
179 | });
180 |
181 | // At this point we have (hopefully only 1) project. Now we need
182 | // the suite(s) associated with the project.
183 | return Suite::whereIn('project_id', $filtered_projects->merge($depends)->pluck('id'))
184 | ->get();
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/resources/assets/sass/app.scss:
--------------------------------------------------------------------------------
1 | // Fonts
2 | @import url("https://fonts.googleapis.com/css?family=Arsenal:400");
3 |
4 | // Variables
5 | @import "variables";
6 |
7 | // Bootstrap
8 | @import "~bootstrap/scss/bootstrap";
9 |
10 | // Bootstrap
11 | @import "~csspin/csspin";
12 |
13 | body {
14 | color: $brand-darker;
15 | font-family: 'Arial';
16 | font-weight: 500;
17 | }
18 |
19 | /*
20 | * Global add-ons
21 | */
22 |
23 | .container-fluid {
24 | padding-top: 40px;
25 | }
26 |
27 | /*
28 | * Sidebar
29 | */
30 |
31 | /* Hide for mobile, show later */
32 | .sidebar {
33 | display: none;
34 | }
35 |
36 | /*
37 | * Tables
38 | */
39 |
40 | .table-header {
41 | vertical-align: middle;
42 | margin-bottom: 15px;
43 | font-weight: 800;
44 | }
45 |
46 | .table-header .title {
47 | font-size: 1.8em;
48 | }
49 |
50 | .dim {
51 | filter: alpha(opacity=40); /* internet explorer */
52 | -khtml-opacity: 0.4; /* khtml, old safari */
53 | -moz-opacity: 0.4; /* mozilla, netscape */
54 | opacity: 0.4; /* fx, safari, opera */
55 | }
56 |
57 | .pale {
58 | filter: alpha(opacity=10); /* internet explorer */
59 | -khtml-opacity: 0.1; /* khtml, old safari */
60 | -moz-opacity: 0.1; /* mozilla, netscape */
61 | opacity: 0.1; /* fx, safari, opera */
62 | }
63 |
64 | th {
65 | font-size: 1.3em;
66 | }
67 |
68 | .table > tbody > tr > td {
69 | vertical-align: middle;
70 | }
71 |
72 | /*
73 | * Links
74 | */
75 | a.file {
76 | color: $brand-danger-light;
77 | }
78 |
79 | a:hover.file {
80 | color: $brand-bluish;
81 | }
82 |
83 | /*
84 | * Projects
85 | */
86 | .project-title {
87 | color: $brand-clear;
88 | text-transform: lowercase;
89 | font-size: 1.8em;
90 | margin-top: -7px;
91 | margin-bottom: 7px;
92 | font-weight: 800;
93 | }
94 |
95 | .search-project {
96 | color: #f8f9fa;
97 | background-color: #444;
98 | border: 1px solid #818182;
99 | }
100 |
101 | .search-project:focus {
102 | background-color: #444;
103 | border-color: #818182;
104 | color: #f8f9fa;
105 | }
106 |
107 | .card-projects {
108 | margin-bottom: 15px;
109 | background-color: $brand-darker;
110 | padding: 10px;
111 | }
112 |
113 | /**
114 | * Project list
115 | */
116 |
117 | .list-group-item {
118 | cursor: pointer;
119 | border: 1px solid rgba(0, 0, 0, 0.125);
120 | }
121 |
122 | .list-group-item.active {
123 | background-color: $brand-pill;
124 | color: $brand-darkest;
125 | border: 1px solid rgba(0, 0, 0, 0.125);
126 | }
127 |
128 | list-group-item.active, .list-group-item.active:hover, .list-group-item.active:focus {
129 | background-color: $brand-pill-darker;
130 | }
131 |
132 | list-group-item, .list-group-item:hover, .list-group-item:focus {
133 | background-color: $brand-gray;
134 | }
135 |
136 |
137 | /*
138 | * Modals
139 | */
140 | .modal-lg {
141 | max-width: 80% !important;
142 | max-height: 80% !important;
143 | }
144 |
145 | .modal-scroll {
146 | height: 650px;
147 | overflow-y: auto;
148 | margin-top: 15px;
149 | padding: 15px;
150 | }
151 |
152 | .terminal {
153 | background-color: $brand-darkest;
154 | }
155 |
156 | /*
157 | * Buttons
158 | */
159 | .btn {
160 | padding: 3px 6px;
161 | cursor: pointer;
162 | }
163 |
164 | .btn-secondary {
165 | color: $brand-darkest !important;
166 | background-color: $brand-secondary !important;
167 | border-color: $brand-secondary-darker !important;
168 | }
169 |
170 |
171 | /*
172 | * Navbar
173 | */
174 | .navbar {
175 | border-radius: 0px !important;
176 | margin-bottom: 0px !important;
177 | }
178 |
179 | .navbar-inverse {
180 | background-color: $brand-primary;
181 | border-color: $brand-primary;
182 | }
183 |
184 | .navbar-inverse .navbar-brand {
185 | color: $brand-clear;
186 | }
187 |
188 | /*
189 | * Toolbar
190 | */
191 | .toolbar {
192 | background-color: $brand-gray;
193 | height: 60px;
194 | padding: 15px;
195 | vertical-align: middle;
196 | border: 1px;
197 | border-color: $brand-gray-darker;
198 | border-style: solid;
199 | }
200 |
201 | /*
202 | * Toolbar
203 | */
204 | .btn-square {
205 | height: 30px;
206 | width: auto;
207 | padding: 5px;
208 | vertical-align: middle;
209 | padding-left: 10px;
210 | }
211 |
212 | /*
213 | * Search
214 | */
215 | .search-group {
216 | padding: 5px;
217 | margin-top: -9px;
218 | width: 100%;
219 | }
220 |
221 | /*
222 | * Placeholder
223 | */
224 | @mixin placeholder {
225 | ::-webkit-input-placeholder {@content}
226 | :-moz-placeholder {@content}
227 | ::-moz-placeholder {@content}
228 | :-ms-input-placeholder {@content}
229 | }
230 |
231 | @include placeholder {
232 | color: white;
233 | opacity: 0.3;
234 | }
235 |
236 | /*
237 | * State
238 | */
239 |
240 | table > tbody > tr > td.state.state-failed {
241 |
242 | }
243 |
244 | .state {
245 | color: $brand-clear;
246 | }
247 |
248 | .state-failed {
249 | background-color: $brand-danger-light;
250 | }
251 |
252 | .state-running {
253 | background-color: $brand-info-light;
254 | }
255 |
256 | .state-ok {
257 | background-color: $brand-success-light;
258 | }
259 |
260 | .state-disabled {
261 | background-color: $brand-clear-light;
262 | color: $brand-darker;
263 | }
264 |
265 | .state-idle {
266 | background-color: $brand-darker-light;
267 | }
268 |
269 | .state-queued {
270 | background-color: $brand-gray-light;
271 | color: $brand-darker;
272 | }
273 |
274 | .badge {
275 | padding: 0.6em .8em .7em;
276 | }
277 |
278 | .project-state {
279 | font-size: 1.1em;
280 | }
281 |
282 | .project-state-passed {
283 | color: $brand-passed;
284 | }
285 |
286 | .project-state-failed {
287 | color: $brand-failed;
288 | }
289 |
290 | .screenshot {
291 | width: 98%;
292 | }
293 |
294 | .table-test-name {
295 | font-weight: 700 !important;
296 | color: $brand-darkest;
297 | }
298 |
299 | .table-test-path {
300 | font-size: 0.7rem;
301 | font-weight: 100 !important;
302 | color: $brand-gray-darker;
303 | }
304 |
305 | .table-link {
306 | cursor: pointer;
307 | }
308 |
309 | .html {
310 | color: $brand-clear-light;
311 | background-color: $brand-html;
312 | font-size: 1.2rem;
313 | font-weight: 500 !important;
314 | }
315 |
316 | .project-checkbox {
317 | margin-right: 5px;
318 | }
319 |
320 | .cursor-pointer {
321 | cursor: pointer;
322 | }
323 |
324 | h1, h2, h3 {
325 | font-weight: 800;
326 | }
327 |
328 | .app {
329 | margin-top: 40px;
330 | }
331 |
--------------------------------------------------------------------------------
/src/package/Data/Repositories/Support/Projects.php:
--------------------------------------------------------------------------------
1 | $name], ['path' => $path, 'tests_path' => $tests_path]);
23 | }
24 |
25 | /**
26 | * Find project by id.
27 | *
28 | * @return \PragmaRX\Tddd\Package\Data\Models\Project|null
29 | */
30 | public function findProjectById($id)
31 | {
32 | return Project::find($id);
33 | }
34 |
35 | /**
36 | * Delete unavailable projects.
37 | *
38 | * @param $projects
39 | */
40 | public function deleteMissingProjects($projects)
41 | {
42 | foreach (Project::all() as $project) {
43 | if (!in_array($project->name, $projects)) {
44 | $project->delete();
45 | }
46 | }
47 | }
48 |
49 | /**
50 | * Get all tests.
51 | *
52 | * @param null $project_id
53 | *
54 | * @return array
55 | */
56 | public function getProjectTests($project_id = null)
57 | {
58 | $order = "(case
59 | when state = 'running' then 1
60 | when state = 'failed' then 2
61 | when state = 'queued' then 3
62 | when state = 'ok' then 4
63 | when state = 'idle' then 5
64 | end) asc,
65 |
66 | updated_at desc";
67 |
68 | $query = Test::select('tddd_tests.*')
69 | ->join('tddd_suites', 'tddd_suites.id', '=', 'suite_id')
70 | ->orderByRaw($order);
71 |
72 | if ($project_id) {
73 | $query->where('project_id', $project_id);
74 | }
75 |
76 | return collect($query->get())->map(function ($test) {
77 | return $this->getTestInfo($test);
78 | });
79 | }
80 |
81 | /**
82 | * Get all projects.
83 | *
84 | * @return \Illuminate\Database\Eloquent\Collection
85 | */
86 | public function getProjectsAndCacheResult()
87 | {
88 | $projects = $this->getProjects();
89 |
90 | app('tddd.cache')->put(Constants::CACHE_PROJECTS_KEY, sha1($projects->toJson()));
91 |
92 | return $projects;
93 | }
94 |
95 | /**
96 | * Get all projects.
97 | *
98 | * @return \Illuminate\Database\Eloquent\Collection
99 | */
100 | public function getProjects()
101 | {
102 | return Project::all()->map(function ($item) {
103 | $item['tests'] = $this->getProjectTests($item->id);
104 |
105 | $item['state'] = $this->getProjectState(collect($item['tests']));
106 |
107 | return $item;
108 | });
109 | }
110 |
111 | /**
112 | * Get a SHA1 for all projects.
113 | *
114 | * @return bool
115 | */
116 | public function projectSha1HasChanged()
117 | {
118 | $currentSha1 = sha1($projectsJson = $this->getProjects()->toJson());
119 |
120 | $oldSha1 = app('tddd.cache')->get(Constants::CACHE_PROJECTS_KEY);
121 |
122 | if ($hasChanged = $currentSha1 != $oldSha1) {
123 | app('tddd.cache')->put(Constants::CACHE_PROJECTS_KEY, sha1($projectsJson));
124 | }
125 |
126 | return $hasChanged;
127 | }
128 |
129 | /**
130 | * Get a SHA1 for all projects.
131 | *
132 | * @return \Illuminate\Database\Eloquent\Collection
133 | */
134 | public function getProjectsSha1()
135 | {
136 | return sha1($this->getProjects()->toJson());
137 | }
138 |
139 | /**
140 | * The the project state.
141 | *
142 | * @param \Illuminate\Support\Collection $tests
143 | *
144 | * @return string
145 | */
146 | public function getProjectState($tests)
147 | {
148 | if ($tests->contains('state', 'running')) {
149 | return 'running';
150 | }
151 |
152 | if ($tests->contains('state', 'queued')) {
153 | return 'queued';
154 | }
155 |
156 | if ($tests->contains('state', 'failed')) {
157 | return 'failed';
158 | }
159 |
160 | if ($tests->every('state', 'ok')) {
161 | return 'ok';
162 | }
163 |
164 | return 'idle';
165 | }
166 |
167 | /**
168 | * Enable tests.
169 | *
170 | * @param $enable
171 | * @param $project_id
172 | *
173 | * @return bool
174 | */
175 | public function enableProjects($enable, $project_id)
176 | {
177 | $enable = $enable === 'true';
178 |
179 | $projects = $project_id == 'all'
180 | ? Project::all()
181 | : Project::where('id', $project_id)->get();
182 |
183 | foreach ($projects as $test) {
184 | $this->enableProject($enable, $test);
185 | }
186 |
187 | return $enable;
188 | }
189 |
190 | /**
191 | * Enable a test.
192 | *
193 | * @param $enable
194 | * @param \PragmaRX\Tddd\Package\Data\Models\Project $project
195 | */
196 | protected function enableProject($enable, $project)
197 | {
198 | $project->timestamps = false;
199 |
200 | $project->enabled = $enable;
201 |
202 | $project->save();
203 | }
204 |
205 | /**
206 | * Run all tests or projects tests.
207 | *
208 | * @param null $project_id
209 | */
210 | public function runProjectTests($project_id = null)
211 | {
212 | $tests = $this->queryTests($project_id)->get();
213 |
214 | foreach ($tests as $test) {
215 | $this->enableTest(true, $test);
216 |
217 | // Force test to the queue
218 | $this->runTest($test, true);
219 | }
220 | }
221 |
222 | /**
223 | * Run all tests or projects tests.
224 | *
225 | * @param null $project_id
226 | */
227 | public function reset($project_id = null)
228 | {
229 | foreach ($this->queryTests($project_id)->get() as $test) {
230 | $this->resetTest($test);
231 | }
232 | }
233 |
234 | /**
235 | * Toggle the enabled state of all projects.
236 | */
237 | public function toggleAll()
238 | {
239 | Project::all()->each(function ($project) {
240 | $this->enableProject(!$project->enabled, $project);
241 | });
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/src/resources/lang/en/validation.php:
--------------------------------------------------------------------------------
1 | 'The :attribute must be accepted.',
17 | 'active_url' => 'The :attribute is not a valid URL.',
18 | 'after' => 'The :attribute must be a date after :date.',
19 | 'after_or_equal' => 'The :attribute must be a date after or equal to :date.',
20 | 'alpha' => 'The :attribute may only contain letters.',
21 | 'alpha_dash' => 'The :attribute may only contain letters, numbers, and dashes.',
22 | 'alpha_num' => 'The :attribute may only contain letters and numbers.',
23 | 'array' => 'The :attribute must be an array.',
24 | 'before' => 'The :attribute must be a date before :date.',
25 | 'before_or_equal' => 'The :attribute must be a date before or equal to :date.',
26 | 'between' => [
27 | 'numeric' => 'The :attribute must be between :min and :max.',
28 | 'file' => 'The :attribute must be between :min and :max kilobytes.',
29 | 'string' => 'The :attribute must be between :min and :max characters.',
30 | 'array' => 'The :attribute must have between :min and :max items.',
31 | ],
32 | 'boolean' => 'The :attribute field must be true or false.',
33 | 'confirmed' => 'The :attribute confirmation does not match.',
34 | 'date' => 'The :attribute is not a valid date.',
35 | 'date_format' => 'The :attribute does not match the format :format.',
36 | 'different' => 'The :attribute and :other must be different.',
37 | 'digits' => 'The :attribute must be :digits digits.',
38 | 'digits_between' => 'The :attribute must be between :min and :max digits.',
39 | 'dimensions' => 'The :attribute has invalid image dimensions.',
40 | 'distinct' => 'The :attribute field has a duplicate value.',
41 | 'email' => 'The :attribute must be a valid email address.',
42 | 'exists' => 'The selected :attribute is invalid.',
43 | 'file' => 'The :attribute must be a file.',
44 | 'filled' => 'The :attribute field must have a value.',
45 | 'image' => 'The :attribute must be an image.',
46 | 'in' => 'The selected :attribute is invalid.',
47 | 'in_array' => 'The :attribute field does not exist in :other.',
48 | 'integer' => 'The :attribute must be an integer.',
49 | 'ip' => 'The :attribute must be a valid IP address.',
50 | 'ipv4' => 'The :attribute must be a valid IPv4 address.',
51 | 'ipv6' => 'The :attribute must be a valid IPv6 address.',
52 | 'json' => 'The :attribute must be a valid JSON string.',
53 | 'max' => [
54 | 'numeric' => 'The :attribute may not be greater than :max.',
55 | 'file' => 'The :attribute may not be greater than :max kilobytes.',
56 | 'string' => 'The :attribute may not be greater than :max characters.',
57 | 'array' => 'The :attribute may not have more than :max items.',
58 | ],
59 | 'mimes' => 'The :attribute must be a file of type: :values.',
60 | 'mimetypes' => 'The :attribute must be a file of type: :values.',
61 | 'min' => [
62 | 'numeric' => 'The :attribute must be at least :min.',
63 | 'file' => 'The :attribute must be at least :min kilobytes.',
64 | 'string' => 'The :attribute must be at least :min characters.',
65 | 'array' => 'The :attribute must have at least :min items.',
66 | ],
67 | 'not_in' => 'The selected :attribute is invalid.',
68 | 'numeric' => 'The :attribute must be a number.',
69 | 'present' => 'The :attribute field must be present.',
70 | 'regex' => 'The :attribute format is invalid.',
71 | 'required' => 'The :attribute field is required.',
72 | 'required_if' => 'The :attribute field is required when :other is :value.',
73 | 'required_unless' => 'The :attribute field is required unless :other is in :values.',
74 | 'required_with' => 'The :attribute field is required when :values is present.',
75 | 'required_with_all' => 'The :attribute field is required when :values is present.',
76 | 'required_without' => 'The :attribute field is required when :values is not present.',
77 | 'required_without_all' => 'The :attribute field is required when none of :values are present.',
78 | 'same' => 'The :attribute and :other must match.',
79 | 'size' => [
80 | 'numeric' => 'The :attribute must be :size.',
81 | 'file' => 'The :attribute must be :size kilobytes.',
82 | 'string' => 'The :attribute must be :size characters.',
83 | 'array' => 'The :attribute must contain :size items.',
84 | ],
85 | 'string' => 'The :attribute must be a string.',
86 | 'timezone' => 'The :attribute must be a valid zone.',
87 | 'unique' => 'The :attribute has already been taken.',
88 | 'uploaded' => 'The :attribute failed to upload.',
89 | 'url' => 'The :attribute format is invalid.',
90 |
91 | /*
92 | |--------------------------------------------------------------------------
93 | | Custom Validation Language Lines
94 | |--------------------------------------------------------------------------
95 | |
96 | | Here you may specify custom validation messages for attributes using the
97 | | convention "attribute.rule" to name the lines. This makes it quick to
98 | | specify a specific custom language line for a given attribute rule.
99 | |
100 | */
101 |
102 | 'custom' => [
103 | 'attribute-name' => [
104 | 'rule-name' => 'custom-message',
105 | ],
106 | ],
107 |
108 | /*
109 | |--------------------------------------------------------------------------
110 | | Custom Validation Attributes
111 | |--------------------------------------------------------------------------
112 | |
113 | | The following language lines are used to swap attribute place-holders
114 | | with something more reader friendly such as E-Mail Address instead
115 | | of "email". This simply helps us make messages a little cleaner.
116 | |
117 | */
118 |
119 | 'attributes' => [],
120 |
121 | ];
122 |
--------------------------------------------------------------------------------
/src/package/ServiceProvider.php:
--------------------------------------------------------------------------------
1 | publishConfiguration();
40 |
41 | $this->loadConfig();
42 |
43 | $this->loadMigrations();
44 |
45 | $this->loadRoutes();
46 |
47 | $this->loadViews();
48 | }
49 |
50 | /**
51 | * Load config files to Laravel config.
52 | */
53 | protected function loadConfig()
54 | {
55 | $this->config->loadConfig();
56 | }
57 |
58 | /**
59 | * Configure migrations path.
60 | */
61 | protected function loadMigrations()
62 | {
63 | $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
64 | }
65 |
66 | /**
67 | * Configure views path.
68 | */
69 | protected function loadViews()
70 | {
71 | $this->loadViewsFrom(__DIR__.'/../resources/views', 'pragmarx/tddd');
72 | }
73 |
74 | /**
75 | * Configure config path.
76 | */
77 | protected function publishConfiguration()
78 | {
79 | $this->publishes([
80 | __DIR__.'/../config' => config_path(),
81 | ]);
82 | }
83 |
84 | /**
85 | * Register the service provider.
86 | *
87 | * @return void
88 | */
89 | public function register()
90 | {
91 | if (!defined('TDDD_PATH')) {
92 | define('TDDD_PATH', realpath(__DIR__.'/../../'));
93 | }
94 |
95 | $this->registerResourceWatcher();
96 |
97 | $this->registerService();
98 |
99 | $this->registerWatcher();
100 |
101 | $this->registerTester();
102 |
103 | $this->registerConfig();
104 |
105 | $this->registerCache();
106 |
107 | $this->registerWatchCommand();
108 |
109 | $this->registerTestCommand();
110 |
111 | $this->registerNotifier();
112 |
113 | $this->registerEventListeners();
114 | }
115 |
116 | /**
117 | * Get the services provided by the provider.
118 | *
119 | * @return array
120 | */
121 | public function provides()
122 | {
123 | return ['tddd'];
124 | }
125 |
126 | /**
127 | * Register event listeners.
128 | */
129 | protected function registerEventListeners()
130 | {
131 | Event::listen(TestsFailed::class, Notify::class);
132 |
133 | Event::listen(UserNotifiedOfFailure::class, MarkAsNotified::class);
134 | }
135 |
136 | /**
137 | * Register the watch command.
138 | */
139 | protected function registerNotifier()
140 | {
141 | $this->app->singleton('tddd.notifier', function () {
142 | return new Notifier();
143 | });
144 | }
145 |
146 | /**
147 | * Register the watch command.
148 | */
149 | protected function registerWatchCommand()
150 | {
151 | $this->app->singleton('tddd.watch.command', function () {
152 | return new WatchCommand();
153 | });
154 |
155 | $this->commands('tddd.watch.command');
156 | }
157 |
158 | /**
159 | * Register the test command.
160 | */
161 | protected function registerTestCommand()
162 | {
163 | $this->app->singleton('tddd.test.command', function () {
164 | return new TestCommand();
165 | });
166 |
167 | $this->commands('tddd.test.command');
168 | }
169 |
170 | /**
171 | * Register service service.
172 | */
173 | protected function registerService()
174 | {
175 | $this->app->singleton('tddd', function () {
176 | return app('PragmaRX\Tddd\Package\Service');
177 | });
178 | }
179 |
180 | /**
181 | * Register service watcher.
182 | */
183 | protected function registerWatcher()
184 | {
185 | $this->app->singleton('tddd.watcher', function () {
186 | return app('PragmaRX\Tddd\Package\Services\Watcher');
187 | });
188 | }
189 |
190 | /**
191 | * Register service tester.
192 | */
193 | protected function registerTester()
194 | {
195 | $this->app->singleton('tddd.tester', function () {
196 | return app('PragmaRX\Tddd\Package\Services\Tester');
197 | });
198 | }
199 |
200 | /**
201 | * Register service tester.
202 | */
203 | protected function registerCache()
204 | {
205 | $this->app->singleton('tddd.cache', function () {
206 | return new Cache();
207 | });
208 | }
209 |
210 | /**
211 | * Register service tester.
212 | */
213 | protected function registerConfig()
214 | {
215 | $config = $this->config = app('PragmaRX\Tddd\Package\Services\Config');
216 |
217 | $this->app->singleton('tddd.config', function () use ($config) {
218 | return $config;
219 | });
220 | }
221 |
222 | /**
223 | * Register the resource watcher.
224 | */
225 | protected function registerResourceWatcher()
226 | {
227 | $this->app->register('JasonLewis\ResourceWatcher\Integration\LaravelServiceProvider');
228 | }
229 |
230 | /**
231 | * Register all routes.
232 | */
233 | protected function loadRoutes()
234 | {
235 | Route::group([
236 | 'prefix' => config('tddd.routes.prefixes.global'),
237 | 'namespace' => 'PragmaRX\Tddd\Package\Http\Controllers',
238 | 'middleware' => 'web',
239 | ], function () {
240 | $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
241 | });
242 | }
243 |
244 | /**
245 | * Get the root directory for this ServiceProvider.
246 | *
247 | * @return string
248 | */
249 | public function getRootDirectory()
250 | {
251 | return __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'..';
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/src/resources/assets/js/components/Projects.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
24 |
25 |
26 | toggle
27 |
28 |
29 | run
30 |
31 |
32 | reset
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
NO PROJECTS FOUND
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | -
57 |
58 |
59 |
65 |
66 | {{ project.name }}
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
171 |
--------------------------------------------------------------------------------
/src/resources/assets/js/components/Log.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
20 |
21 |
22 |
23 | command output
24 |
25 |
26 |
27 | screenshots
28 |
29 |
30 |
31 | coverage
32 |
33 |
34 |
35 | {{ getHtmlPaneName() }}
36 |
37 |
38 |
39 |
40 |
43 |
44 |
45 | open in {{ selectedTest.editor_name }}
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
{{ String(screenshot).substring(screenshot.lastIndexOf('/') + 1) }}
57 |
![]()
58 |
59 |
60 |
61 |
62 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
81 |
82 |
83 |
84 |
85 |
86 |
159 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TDDD - Test Driven Development Dashboard
2 | ### A Self-Hosted TDD Dashboard & Tests Watcher
3 |
4 | [](https://packagist.org/packages/pragmarx/tddd)
5 | [](LICENSE.md)
6 | [](https://packagist.org/packages/pragmarx/tddd)
7 | [](https://scrutinizer-tddd.com/g/antonioribeiro/tddd/?branch=master)
8 | [](https://scrutinizer-tddd.com/g/antonioribeiro/tddd/?branch=master)
9 | [](https://styleci.io/repos/27037779)
10 |
11 | ## What is it?
12 |
13 | TDD Dashboard, is an app (built as a Laravel PHP package) to watch and run all your tests during development. It supports any test framework working on terminal, and comes with some testers (PHPUnit, phpspec, behat, Jest, AVA...) preconfigured, but you can easily add yours, just tell it where the executable is and it's done. It also shows the progress of your tests, let you run a single test or all of them, and open your favorite code editor (PHPStorm, VSCode, Sublime Text, etc.) going right to the failing line of your test. If your test framework generate screenshots, it is also able to show it in the log page, with all the reds and greens you are used to see in your terminal.
14 |
15 | It uses Laravel as motor, but supports (and has been tested with) many languages, frameworks and testing frameworks:
16 |
17 | * [PHPUnit](https://phpunit.de/)
18 | * [Laravel & Laravel Dusk](https://laravel.com/docs/5.5/dusk)
19 | * [Codeception](http://codeception.com/)
20 | * [phpspec](http://www.phpspec.net/en/stable/)
21 | * [Behat](http://behat.org/en/latest/)
22 | * [atoum](http://atoum.org/)
23 | * [Jest](https://facebook.github.io/jest/)
24 | * [AVA](https://github.com/avajs/ava)
25 | * [React](https://reactjs.org/)
26 | * [Ruby on Rails](http://guides.rubyonrails.org/testing.html)
27 | * [Nette Tester](https://tester.nette.org/)
28 | * [Symfony](https://symfony.com/doc/current/testing.html)
29 |
30 | ## Features
31 |
32 | * Project List: click a project link to see all its tests.
33 | * Open files directly in your source code editor (PHPStorm, Sublime Text...).
34 | * Error log with source code linked, go strait to the error line in your source code.
35 | * Enable/disable a test. Once disabled if the watcher catches a change in resources, that test will not fire.
36 | * Real time test state: "idle", "running", "queued", "ok" and "failed".
37 | * "Show" button, to display the error log of failed tests.
38 | * Highly configurable, watch anything and test everything!
39 |
40 | ### Videos
41 |
42 | - [Preview](https://www.youtube.com/watch?v=sO_aDf3xCgE)
43 | - [Installing](https://youtu.be/AgkKCLNiV8w)
44 | - [VueJS Preview](https://youtu.be/HAdfLYArk_A)
45 | - [Laravel Dusk Preview](https://youtu.be/ooF4oLD9U7Q)
46 |
47 | ### Screenshots
48 |
49 | #### Dashboard
50 |
51 | 
52 |
53 | #### Error Log
54 | 
55 |
56 | 
57 |
58 | 
59 |
60 | ## Command Line Interface
61 |
62 | The Artisan commands **Watcher** and **Tester** are responsible for watching resources and firing tests, respectively:
63 |
64 | ### Watcher
65 |
66 | Keep track of your files and enqueue your tests every time a project or test file is changed. If a project file changes, it will enqueue all your tests, if a test file changes, it will enqueue only that particular test. This is how you run it:
67 |
68 | ``` bash
69 | php artisan tddd:watch
70 | ```
71 |
72 | ### Tester
73 |
74 | Responsible for taking tests from the run queue, execute it and log the results. Tester will only execute enabled tests. This is how you run it:
75 |
76 | ``` bash
77 | php artisan tddd:test
78 | ```
79 |
80 | ### Notifications
81 |
82 | It uses JoliNotif, so if it's not working on macOS, you can try installing terminal-notifier:
83 |
84 | ``` bash
85 | brew install terminal-notifier
86 | ```
87 |
88 | ## Test Framework Compatibility
89 |
90 | This package was tested and is known to be compatible with
91 |
92 | * [Codeception](http://codeception.com/)
93 | * [PHPUnit](https://phpunit.de/)
94 | * [phpspec](http://www.phpspec.net/)
95 | * [behat](http://docs.behat.org/)
96 | * [atoum](https://github.com/atoum/atoum)
97 | * [Nette Tester](http://tester.nette.org/en/)
98 |
99 | ## Installing
100 |
101 | #### TL;DR
102 |
103 | ``` bash
104 | laravel new tddd
105 | cd tddd
106 | composer require pragmarx/tddd
107 | php artisan vendor:publish --provider="PragmaRX\Tddd\Package\ServiceProvider"
108 | valet link tddd
109 | # configure database on your .env
110 | php artisan migrate
111 | php artisan tddd:watch & php artisan tddd:work &
112 | open http://tddd.dev/tests-watcher/dashboard
113 | ```
114 |
115 | ### Examples & Starter App
116 |
117 | For lots of examples, check [this starter app](https://github.com/antonioribeiro/tests-watcher-starter), which will also help you create an independent dashboard for your tests.
118 |
119 | ### The long version
120 |
121 | Require it with [Composer](http://getcomposer.org/):
122 |
123 | ``` bash
124 | composer require pragmarx/tddd
125 | ```
126 |
127 | Create a database, configure on your Laravel app and migrate it
128 |
129 | ``` bash
130 | php artisan migrate
131 | ```
132 |
133 | Publish Ci configuration:
134 |
135 | On Laravel 4.*
136 |
137 | Add the service provider to your app/config/app.php:
138 |
139 | ``` php
140 | 'PragmaRX\Tddd\Package\ServiceProvider',
141 | ```
142 |
143 | ``` bash
144 | php artisan config:publish pragmarx/tddd
145 | ```
146 |
147 | On Laravel 5.*
148 |
149 | ``` bash
150 | php artisan vendor:publish --provider="PragmaRX\Tddd\Package\ServiceProvider"
151 | ```
152 |
153 | ## Example of projects
154 |
155 | ### Laravel Dusk
156 |
157 | ``` php
158 | 'project bar (dusk)' => [
159 | 'path' => $basePath,
160 | 'watch_folders' => [
161 | 'app',
162 | 'tests/Browser'
163 | ],
164 | 'exclude' => [
165 | 'tests/Browser/console/',
166 | 'tests/Browser/screenshots/',
167 | ],
168 | 'depends' => [],
169 | 'tests_path' => 'tests',
170 | 'suites' => [
171 | 'browser' => [
172 | 'tester' => 'dusk',
173 | 'tests_path' => 'Browser',
174 | 'command_options' => '',
175 | 'file_mask' => '*Test.php',
176 | 'retries' => 0,
177 | ],
178 | ],
179 | ],
180 | ```
181 |
182 | ## Troubleshooting
183 |
184 | #### Tests are running fine in terminal but failing in the dashboard?
185 |
186 | You have first to remember they are being executed in isolation, and, also, the environment is not exactly the same, so things like a cache and session may affect your results.
187 |
188 | ## Requirements
189 |
190 | - Laravel 4.1+ or 5
191 | - PHP 5.3.7+
192 |
193 | ## Author
194 |
195 | [Antonio Carlos Ribeiro](http://twitter.com/iantonioribeiro)
196 |
197 | ## License
198 |
199 | Laravel Ci is licensed under the BSD 3-Clause License - see the `LICENSE` file for details
200 |
201 | ## Contributing
202 |
203 | Pull requests and issues are welcome.
204 |
205 |
206 |
207 |
208 |
--------------------------------------------------------------------------------
/src/package/Data/Repositories/Support/Tests.php:
--------------------------------------------------------------------------------
1 | getRelativePathname())
24 | ->where('suite_id', $suite->id)
25 | ->first();
26 |
27 | return $exists;
28 | }
29 |
30 | /**
31 | * Create or update a test.
32 | *
33 | * @param \Symfony\Component\Finder\SplFileInfo $file
34 | * @param \PragmaRX\Tddd\Package\Data\Models\Suite $suite
35 | *
36 | * @return bool
37 | */
38 | public function createOrUpdateTest($file, $suite)
39 | {
40 | $test = Test::where('path', $path = $this->normalizePath($file->getPath()))
41 | ->where('name', $name = trim($file->getFilename()))
42 | ->first();
43 |
44 | if (is_null($test)) {
45 | $test = Test::create([
46 | 'sha1' => sha1("$path/$name"),
47 | 'path' => $path,
48 | 'name' => $name,
49 | 'suite_id' => $suite->id,
50 | ]);
51 | }
52 |
53 | if ($test->wasRecentlyCreated && $this->findTestByFileAndSuite($file, $suite)) {
54 | $this->addTestToQueue($test);
55 | }
56 |
57 | return $test->wasRecentlyCreated;
58 | }
59 |
60 | /**
61 | * Sync all tests.
62 | *
63 | * @param $exclusions
64 | */
65 | public function syncTests($exclusions, $showTests)
66 | {
67 | foreach ($this->getSuites() as $suite) {
68 | $this->syncSuiteTests($suite, $exclusions, $showTests);
69 | }
70 | }
71 |
72 | /**
73 | * Check if a file is a test file.
74 | *
75 | * @param $path
76 | *
77 | * @return \___PHPSTORM_HELPERS\static|bool|mixed
78 | */
79 | public function isTestFile($path)
80 | {
81 | if (file_exists($path)) {
82 | foreach (Test::all() as $test) {
83 | if ($test->fullPath == $path) {
84 | return $test;
85 | }
86 | }
87 | }
88 |
89 | return false;
90 | }
91 |
92 | /**
93 | * Store the test result.
94 | *
95 | * @param $run
96 | * @param $test
97 | * @param $lines
98 | * @param $ok
99 | * @param $startedAt
100 | * @param $endedAt
101 | *
102 | * @return mixed
103 | */
104 | public function storeTestResult($run, $test, $lines, $ok, $startedAt, $endedAt)
105 | {
106 | if (!$this->testExists($test)) {
107 | return false;
108 | }
109 |
110 | $run = $this->updateRun($run, $test, $lines, $ok, $startedAt, $endedAt);
111 |
112 | $test->state = $ok ? Constants::STATE_OK : Constants::STATE_FAILED;
113 |
114 | $test->last_run_id = $run->id;
115 |
116 | $test->save();
117 |
118 | $this->removeTestFromQueue($test);
119 |
120 | return $ok;
121 | }
122 |
123 | /**
124 | * Mark a test as being running.
125 | *
126 | * @param $test
127 | *
128 | * @return mixed
129 | */
130 | public function markTestAsRunning($test)
131 | {
132 | $test->state = Constants::STATE_RUNNING;
133 |
134 | $test->save();
135 |
136 | return $this->createNewRunForTest($test);
137 | }
138 |
139 | /**
140 | * Find a test by name and suite.
141 | *
142 | * @param $suite
143 | * @param $file
144 | *
145 | * @return mixed
146 | */
147 | protected function findTestByNameAndSuite($file, $suite)
148 | {
149 | return Test::where('name', $file->getRelativePathname())->where('suite_id', $suite->id)->first();
150 | }
151 |
152 | /**
153 | * Enable tests.
154 | *
155 | * @param $enable
156 | * @param $project_id
157 | * @param null $test_id
158 | *
159 | * @return bool
160 | */
161 | public function enableTests($enable, $project_id, $test_id)
162 | {
163 | $enable = is_bool($enable) ? $enable : ($enable === 'true');
164 |
165 | $tests = $this->queryTests($project_id, $test_id == 'all' ? null : $test_id)->get();
166 |
167 | foreach ($tests as $test) {
168 | $this->enableTest($enable, $test);
169 | }
170 |
171 | return $enable;
172 | }
173 |
174 | /**
175 | * Run a test.
176 | *
177 | * @param $test
178 | * @param bool $force
179 | */
180 | public function runTest($test, $force = false)
181 | {
182 | if (!$test instanceof Test) {
183 | $test = Test::find($test);
184 | }
185 |
186 | $this->addTestToQueue($test, $force);
187 | }
188 |
189 | /**
190 | * Enable a test.
191 | *
192 | * @param $enable
193 | * @param \PragmaRX\Tddd\Package\Data\Models\Test $test
194 | */
195 | protected function enableTest($enable, $test)
196 | {
197 | $test->timestamps = false;
198 |
199 | $test->enabled = $enable;
200 |
201 | $test->save();
202 |
203 | if (!$enable) {
204 | $this->removeTestFromQueue($test);
205 |
206 | return;
207 | }
208 |
209 | if ($test->state !== Constants::STATE_OK) {
210 | $this->addTestToQueue($test);
211 | }
212 | }
213 |
214 | /**
215 | * Query tests.
216 | *
217 | * @param $test_id
218 | *
219 | * @return mixed
220 | */
221 | protected function queryTests($projects, $test_id = null)
222 | {
223 | $projects = (array) $projects;
224 |
225 | $query = Test::select('tddd_tests.*')
226 | ->join('tddd_suites', 'tddd_suites.id', '=', 'tddd_tests.suite_id');
227 |
228 | if ($projects && $projects != 'all') {
229 | $query->whereIn('tddd_suites.project_id', $projects);
230 | }
231 |
232 | if ($test_id && $test_id != 'all') {
233 | $query->where('tddd_tests.id', $test_id);
234 | }
235 |
236 | return $query;
237 | }
238 |
239 | /**
240 | * Mark tests as notified.
241 | *
242 | * @param $tests
243 | */
244 | public function markTestsAsNotified($tests)
245 | {
246 | $tests->each(function ($test) {
247 | $test['run']->notified_at = Carbon::now();
248 |
249 | $test['run']->save();
250 | });
251 | }
252 |
253 | /**
254 | * Check if the test exists.
255 | *
256 | * @param $test
257 | *
258 | * @return bool
259 | */
260 | protected function testExists($test)
261 | {
262 | return !is_null(Test::find($test->id));
263 | }
264 |
265 | /**
266 | * Update the run.
267 | *
268 | * @param $run
269 | * @param $test
270 | * @param $lines
271 | * @param $ok
272 | * @param $startedAt
273 | * @param $endedAt
274 | *
275 | * @return mixed
276 | */
277 | private function updateRun($run, $test, $lines, $ok, $startedAt, $endedAt)
278 | {
279 | $run->test_id = $test->id;
280 | $run->was_ok = $ok;
281 | $run->log = $this->formatLog($lines, $test) ?: '(empty)';
282 | $run->html = $this->getOutput($test, $test->suite->tester->output_folder,
283 | $test->suite->tester->output_html_fail_extension);
284 | $run->screenshots = $this->getScreenshots($test, $lines);
285 | $run->started_at = $startedAt;
286 | $run->ended_at = $endedAt;
287 |
288 | $run->save();
289 |
290 | return $run;
291 | }
292 | }
293 |
--------------------------------------------------------------------------------