Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
6695467
Add Test wrapper
wazabii8 Mar 25, 2025
20d4512
Merge branch 'bugfix/dto' into develop
wazabii8 Mar 25, 2025
5cd7418
Add mocking capabillities
wazabii8 Mar 30, 2025
9f9fd4b
Mocker and structure improvements
wazabii8 Apr 1, 2025
f3c05db
Prompt semantics
wazabii8 Apr 2, 2025
a1b38c9
Add mocking capabilities
wazabii8 Apr 13, 2025
b8a81d8
Add mock validations to method params
wazabii8 Apr 14, 2025
1543977
Code quality improvements
wazabii8 Apr 14, 2025
7ab63d6
Add more validations
wazabii8 Apr 15, 2025
fed2920
Add method parameter validation
wazabii8 Apr 17, 2025
06b5c48
Add default variable values to method generator
wazabii8 Apr 27, 2025
23ed4ba
Change validation class name
wazabii8 Apr 28, 2025
9f6cade
List validation error names collected from closure
wazabii8 Apr 30, 2025
725ea9b
Code quality improvements
wazabii8 May 1, 2025
19c373b
Code quality improvements
wazabii8 May 1, 2025
27d8dde
Code quality improvements
wazabii8 May 1, 2025
365ffb1
Code quality improvements
wazabii8 May 1, 2025
a9b89df
Add mocking improvements
wazabii8 May 2, 2025
1f240f1
Allow setting mock constructor args
wazabii8 May 2, 2025
eeb68e2
Fix validation count for mock
wazabii8 May 3, 2025
84fc6ea
Add Default data type mock
wazabii8 May 4, 2025
4c84d09
Improve mocking and error handling
wazabii8 May 4, 2025
9e8abaf
Add DTO traverse to value in validate
wazabii8 May 5, 2025
bbb5c12
Fix Mocking and MethodPool inheritance
wazabii8 May 7, 2025
8ba1ce9
refactor: replace function null checks with strict comparisons
wazabii8 May 9, 2025
2c811db
refactor: restructure project layout and improve file naming for clarity
wazabii8 May 14, 2025
9b1b242
Add assert support
wazabii8 May 17, 2025
7723592
refactor: remove closure binding from group
wazabii8 May 19, 2025
1f472d3
Add more dynamic test search
wazabii8 May 20, 2025
24f8a8a
Alow both absolute and relative argv path when root dir is present
wazabii8 May 20, 2025
555fd20
Improve CLI styling
wazabii8 May 21, 2025
702070f
Mock class identifiers
wazabii8 May 23, 2025
fea5132
Add unique mock identifier
wazabii8 May 23, 2025
0b3ab93
Pass meta data to mocked and keep orignial
wazabii8 May 25, 2025
26f8180
Add interface support for mocking
wazabii8 May 25, 2025
823c9d6
feat: Add argument check to mocker
wazabii8 May 25, 2025
289f38c
Add will throw method
wazabii8 May 27, 2025
2a67a48
Add throwable validations to Expect
wazabii8 May 29, 2025
636d3b6
bugfix: mocker method argument inheritance
wazabii8 May 29, 2025
e8d5a94
Data type and code quality improvements
wazabii8 May 30, 2025
97b4983
Code quality imporvements
wazabii8 May 30, 2025
50e89c7
Add comment block to files
wazabii8 May 30, 2025
5849439
Creating TestItem class
wazabii8 Jun 1, 2025
f058d7d
Structure improvements
wazabii8 Jun 1, 2025
b44c84d
Add test to test
wazabii8 Jun 1, 2025
cb9e455
Fix skip count
wazabii8 Jun 1, 2025
c448385
Testing mocking idea
wazabii8 Jun 6, 2025
465df4a
Minor refactoring
wazabii8 Jun 8, 2025
66a2003
Add warning if trying to mock final methods
wazabii8 Jun 8, 2025
364bc20
Adjust test
wazabii8 Jun 8, 2025
391e778
Add more descriptive code commments
wazabii8 Jun 15, 2025
bd0b6b1
feat: Start building code coverage functionality
wazabii8 Jun 21, 2025
9a871f5
feat: Introduce initial MVC setup for CLI
wazabii8 Jun 22, 2025
c89fc39
Add coverage
wazabii8 Jun 26, 2025
a8c4414
Adding MVC and dependency injection
danielRConsid Jul 14, 2025
9ccc208
Improving the MVC, DI and Middleware setup
danielRConsid Jul 16, 2025
e5f01d6
Refactor and structure improvements
danielRConsid Jul 20, 2025
1b59632
refactor: Code structure improvements
danielRConsid Jul 21, 2025
282043e
refactor: Organize and restructure code
danielRConsid Aug 10, 2025
e5be6bd
update: Add ConfigProps, defines the set of allowed configuration pro…
danielRConsid Aug 12, 2025
a49e444
refactor: Organization and re-naming of files and directories
danielRConsid Aug 13, 2025
2c297de
refactor: Organize code after solid principles
danielRConsid Aug 14, 2025
711f214
update: Config props hydration and inheritance
danielRConsid Aug 14, 2025
20f92bd
fix: redirect E_USER_WARNING to PHP internal error handling
danielRConsid Aug 15, 2025
c6f35ce
update: Add test discovery pattern to config file
danielRConsid Aug 16, 2025
2abc4cb
fix: Add more config options
danielRConsid Aug 17, 2025
6e93be0
fix: Add code coverage functionality to the CLI MVC
danielRConsid Aug 20, 2025
4c42c9f
chore: code quality improvements
danielRConsid Aug 20, 2025
2e011d7
style: apply coding standards with php-cs-fixer
danielRConsid Aug 20, 2025
e54cf01
fix: add track errors and exceptions
danielRConsid Aug 21, 2025
fc09bf5
fix: error and exception management
danielRConsid Aug 25, 2025
cf8f4f9
refactor: unify kernel and request handler logic for clearer structure
danielRConsid Aug 26, 2025
b8140b3
feat: support configurable locale and timezone
danielRConsid Aug 26, 2025
0c0f62b
update: start adding junit xml render
danielRConsid Aug 27, 2025
f825f1f
update: junit xml structure
danielRConsid Aug 28, 2025
55d60c7
fix: add middleware to router
danielRConsid Aug 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
383 changes: 98 additions & 285 deletions README.md

Large diffs are not rendered by default.

49 changes: 20 additions & 29 deletions bin/unitary
Original file line number Diff line number Diff line change
@@ -1,40 +1,31 @@
#!/usr/bin/env php
<?php
/**
* MaplePHP Unitary unit testing library
* @example php unitary --path=fullDirPath --exclude="dir1/dir2/
* MaplePHP Unitary bin file
*/

require $GLOBALS['_composer_autoload_path'];
use MaplePHP\Blunder\Handlers\CliHandler;
use MaplePHP\Unitary\Console\Application;

use MaplePHP\Http\Environment;
use MaplePHP\Http\ServerRequest;
use MaplePHP\Http\Uri;
use MaplePHP\Prompts\Command;
use MaplePHP\Unitary\FileIterator;

$command = new Command();
$env = new Environment();
$request = new ServerRequest(new Uri($env->getUriParts([
"argv" => $argv
])), $env);
$autoload = __DIR__ . '/../../../../vendor/autoload.php';
$autoload = is_file($autoload) ? $autoload : __DIR__ . '/../vendor/autoload.php';
$autoload = realpath($autoload);

$data = $request->getCliArgs();
$defaultPath = (defined("UNITARY_PATH") ? UNITARY_PATH : "./");

try {
$path = ($data['path'] ?? $defaultPath);
if(!isset($path)) {
throw new Exception("Path not specified: --path=path/to/dir");
if (!$autoload || !is_file($autoload)) {
if (!empty($GLOBALS['_composer_autoload_path'])) {
$autoload = $GLOBALS['_composer_autoload_path'];
} else {
fwrite(STDERR, "Autoloader not found. Run `composer install`.\n");
exit(1);
}
}

$testDir = realpath($path);
if(!is_dir($testDir)) {
throw new Exception("Test directory '$testDir' does not exist");
}
$unit = new FileIterator($data);
$unit->executeAll($testDir);
require $autoload;

} catch (Exception $e) {
$command->error($e->getMessage());
}
$app = (new Application())
->withErrorHandler(new CliHandler())
->boot([
"argv" => $argv,
"dir" => getcwd()
]);
4 changes: 4 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
"maplephp/prompts": "^1.0"
},
"autoload": {
"files": [
"src/Setup/assert-polyfill.php",
"src/Support/functions.php"
],
"psr-4": {
"MaplePHP\\Unitary\\": "src"
}
Expand Down
92 changes: 92 additions & 0 deletions src/Config/ConfigProps.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

declare(strict_types=1);

namespace MaplePHP\Unitary\Config;

use InvalidArgumentException;
use MaplePHP\Emitron\AbstractConfigProps;

/**
* Defines the set of allowed configuration properties and CLI arguments.
*
* CLI arguments with matching property names will override configuration file values.
*
* Note:
* - All properties are nullable, indicating they have not been explicitly set.
* - Null values allow the system to distinguish between "not provided" and "intentionally set".
* - Do not use array values or multiple data types
*/
class ConfigProps extends AbstractConfigProps
{
public ?string $path = null;
public ?string $discoverPattern = null;
public ?string $exclude = null;
public ?string $show = null;
public ?string $timezone = null;
public ?string $local = null;
public ?int $exitCode = null;
public ?bool $verbose = null;
public ?bool $alwaysShowFiles = null;
public ?bool $errorsOnly = null;
public ?bool $smartSearch = null;
public ?bool $failFast = null;


/**
* Hydrate the properties/object with expected data, and handle unexpected data
*
* @param string $key
* @param mixed $value
* @return void
*/
protected function propsHydration(string $key, mixed $value): void
{
switch ($key) {
case 'path':
$this->path = (!is_string($value) || $value === '') ? null : $value;
break;
case 'discoverPattern':
$this->discoverPattern = (!is_string($value) || $value === '') ? null : $value;
break;
case 'exclude':
$this->exclude = (!is_string($value) || $value === '') ? null : $value;
break;
case 'show':
$this->show = (!is_string($value) || $value === '') ? null : $value;
break;
case 'timezone':
// The default timezone is 'CET'
$this->timezone = (!is_string($value) || $value === '') ? 'Europe/Stockholm' : $value;
break;
case 'local':
// The default timezone is 'CET'
$this->local = (!is_string($value) || $value === '') ? 'en_US' : $value;
if(!$this->isValidLocale($this->local)) {
throw new InvalidArgumentException(
"Invalid locale '{$this->local}'. Expected format like 'en_US' (language_COUNTRY)."
);
}
break;
case 'exitCode':
$this->exitCode = ($value === null) ? null : (int)$value;
break;
case 'verbose':
$this->verbose = $this->dataToBool($value);
break;
case 'alwaysShowFiles':
$this->alwaysShowFiles = $this->dataToBool($value);
break;
case 'smartSearch':
$this->smartSearch = $this->dataToBool($value);
break;
case 'errorsOnly':
$this->errorsOnly = $this->dataToBool($value);
break;
case 'failFast':
$this->failFast = $this->dataToBool($value);
break;
}
}

}
93 changes: 93 additions & 0 deletions src/Config/TestConfig.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

/**
* TestConfig — Part of the MaplePHP Unitary Testing Library
*
* @package: MaplePHP\Unitary
* @author: Daniel Ronkainen
* @licence: Apache-2.0 license, Copyright © Daniel Ronkainen
* Don't delete this comment, it's part of the license.
*/
declare(strict_types=1);

namespace MaplePHP\Unitary\Config;

final class TestConfig
{
public ?string $message;
public bool $skip = false;
public string $select = "";
private bool $updatedSubject = false;

public function __construct(string $message)
{
$this->message = $message;
}

/**
* Statically make instance.
*
* @param string $message
* @return self
*/
public static function make(string $message = "Validating"): self
{
return new self($message);
}

/**
* Sets the select state for the current instance.
*
* @param string $key The key to set.
* @return self
*/
public function withName(string $key): self
{
$inst = clone $this;
$inst->select = $key;
return $inst;
}

// Alias for setName()
public function setSelect(string $key): self
{
return $this->withName($key);
}

/**
* Sets the message for the current instance.
*
* @param string $subject The message to set.
* @return self
*/
public function withSubject(string $subject): self
{
$inst = clone $this;
$inst->updatedSubject = true;
$inst->message = $subject;
return $inst;
}

/**
* Check if a subject has been added in `withSubject`
*
* @return bool
*/
public function hasSubject(): bool
{
return $this->updatedSubject;
}

/**
* Sets the skip state for the current instance.
*
* @param bool $bool Optional. The value to set for the skip state. Defaults to true.
* @return self
*/
public function withSkip(bool $bool = true): self
{
$inst = clone $this;
$inst->skip = $bool;
return $inst;
}
}
58 changes: 58 additions & 0 deletions src/Console/Application.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
namespace MaplePHP\Unitary\Console;

use MaplePHP\Blunder\Interfaces\AbstractHandlerInterface;
use MaplePHP\Blunder\Run;
use MaplePHP\Container\Container;
use MaplePHP\Http\Environment;
use MaplePHP\Http\ServerRequest;
use MaplePHP\Http\Uri;
use MaplePHP\Unitary\Console\Middlewares\{
AddCommandMiddleware,
CliInitMiddleware,
ConfigPropsMiddleware,
LocalMiddleware
};

final class Application
{

/**
* Default error handler boot
* @param AbstractHandlerInterface $handler
* @return $this
*/
public function withErrorHandler(AbstractHandlerInterface $handler): self
{
$inst = clone $this;
$run = new Run($handler);
$run->severity()
->excludeSeverityLevels([E_USER_WARNING, E_NOTICE, E_USER_NOTICE, E_DEPRECATED, E_USER_DEPRECATED])
->redirectTo(function () {
// Let PHP’s default error handler process excluded severities
return false;
});
$run->setExitCode(1);
$run->load();
return $inst;
}

/**
* @param array $parts
* @return Kernel
* @throws \Exception
*/
public function boot(array $parts): Kernel
{
$env = new Environment();
$request = new ServerRequest(new Uri($env->getUriParts($parts)), $env);
$kernel = new Kernel(new Container(), [
AddCommandMiddleware::class,
ConfigPropsMiddleware::class,
LocalMiddleware::class,
CliInitMiddleware::class
]);
$kernel->run($request);
return $kernel;
}
}
13 changes: 13 additions & 0 deletions src/Console/ConsoleRouter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

use MaplePHP\Unitary\Console\Controllers\CoverageController;
use MaplePHP\Unitary\Console\Controllers\RunTestController;
use MaplePHP\Unitary\Console\Controllers\TemplateController;
use MaplePHP\Unitary\Console\Middlewares\TestMiddleware;

return $router
->map("coverage", [CoverageController::class, "run"])
->map("template", [TemplateController::class, "run"])
->map("junit", [RunTestController::class, "runJUnit"])
->map(["", "test", "run"], [RunTestController::class, "run"])->with(TestMiddleware::class)
->map(["__404", "help"], [RunTestController::class, "help"]);
51 changes: 51 additions & 0 deletions src/Console/Controllers/CoverageController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace MaplePHP\Unitary\Console\Controllers;

use MaplePHP\Http\Interfaces\ResponseInterface;
use MaplePHP\Prompts\Themes\Blocks;
use MaplePHP\Unitary\Console\Services\RunTestService;
use MaplePHP\Unitary\Renders\SilentRender;
use MaplePHP\Unitary\Support\TestUtils\CodeCoverage;

class CoverageController extends DefaultController
{
/**
* Code Coverage Controller
*/
public function run(RunTestService $service): ResponseInterface
{
$coverage = new CodeCoverage();
$coverage->start();
$handler = new SilentRender();
$response = $service->run($handler);
$coverage->end();

$result = $coverage->getResponse();
if ($result !== false) {
$this->outputBody($result);
} else {
$this->command->error("Error: Code coverage is not reachable");
$this->command->error("Reason: " . $coverage->getIssue()->message());
}
$this->command->message("");

return $response;
}

/**
* Will output the main body response in CLI
*
* @param array $result
* @return void
*/
private function outputBody(array $result): void
{
$block = new Blocks($this->command);
$block->addSection("Code coverage", function (Blocks $block) use ($result) {
return $block->addList("Total lines:", $result['totalLines'])
->addList("Executed lines:", $result['executedLines'])
->addList("Code coverage percent:", $result['percent'] . "%");
});
}
}
Loading