diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index 3839fbaa0..8a92a2094 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -58,5 +58,8 @@ jobs:
- name: Run Check configuration
run: php data/bin/check_configuration.php
- - name: Run Tests
+ - name: Run Lime Tests
run: php data/bin/symfony symfony:test --trace
+
+ - name: Run PHPUnit Tests
+ run: php vendor/bin/phpunit
diff --git a/.gitignore b/.gitignore
index 3c17d4767..0644b20d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,6 @@ lib/plugins/sfDoctrinePlugin/test/functional/fixtures/log/
/vendor
/composer.lock
.php-cs-fixer.cache
+.phpunit.result.cache
+phpunit.xml
+/tests/fixtures/symfony/log
diff --git a/composer.json b/composer.json
index 096711cad..61b1397b6 100755
--- a/composer.json
+++ b/composer.json
@@ -8,7 +8,9 @@
"swiftmailer/swiftmailer": "^5.2 || ^6.0"
},
"require-dev": {
- "psr/log": "*"
+ "psr/log": "*",
+ "phpunit/phpunit": "^9.6",
+ "symfony/phpunit-bridge": "^7.0"
},
"autoload": {
"files": ["autoload.php"]
@@ -26,5 +28,13 @@
"replace": {
"lexpress/symfony1": "^1.5"
},
+ "scripts": {
+ "test": [
+ "@test:lime",
+ "@test:phpunit"
+ ],
+ "test:lime": "php data/bin/symfony symfony:test --trace",
+ "test:phpunit": "phpunit"
+ },
"bin": ["data/bin/symfony"]
}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 000000000..99dc45452
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+ ./tests/
+
+
+
+
+
+ ./lib/
+
+
+
diff --git a/tests/action/sfComponentTest.php b/tests/action/sfComponentTest.php
new file mode 100644
index 000000000..2f151c54c
--- /dev/null
+++ b/tests/action/sfComponentTest.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once __DIR__.'/../fixtures/myComponent.php';
+require_once __DIR__.'/../sfContext.class.php';
+require_once __DIR__.'/../sfNoRouting.class.php';
+require_once __DIR__.'/../sfEventDispatcherTestCase.php';
+
+/**
+ * @internal
+ */
+class sfComponentTest extends sfEventDispatcherTestCase
+{
+ private sfContext $context;
+
+ public function setUp(): void
+ {
+ $this->context = sfContext::getInstance(array(
+ 'routing' => 'sfNoRouting',
+ 'request' => 'sfWebRequest',
+ ));
+
+ $this->testObject = new myComponent($this->context, 'module', 'action');
+ $this->dispatcher = $this->context->getEventDispatcher();
+ $this->class = 'component';
+ }
+
+ public function testInitialize()
+ {
+ $component = new myComponent($this->context, 'module', 'action');
+ $this->assertSame($this->context, $component->getContext(), '->initialize() takes a sfContext object as its first argument');
+ $component->initialize($this->context, 'module', 'action');
+ $this->assertSame($this->context, $component->getContext(), '->initialize() takes a sfContext object as its first argument');
+ }
+
+ public function testGetContext()
+ {
+ $component = new myComponent($this->context, 'module', 'action');
+
+ $component->initialize($this->context, 'module', 'action');
+ $this->assertSame($this->context, $component->getContext(), '->getContext() returns the current context');
+ }
+
+ public function testGetRequest()
+ {
+ $component = new myComponent($this->context, 'module', 'action');
+
+ $component->initialize($this->context, 'module', 'action');
+ $this->assertSame($this->context->getRequest(), $component->getRequest(), '->getRequest() returns the current request');
+ }
+
+ public function testGetResponse()
+ {
+ $component = new myComponent($this->context, 'module', 'action');
+
+ $component->initialize($this->context, 'module', 'action');
+ $this->assertSame($this->context->getResponse(), $component->getResponse(), '->getResponse() returns the current response');
+ }
+
+ public function testSetter()
+ {
+ $component = new myComponent($this->context, 'module', 'action');
+
+ $component->foo = array();
+ $component->foo[] = 'bar';
+ $this->assertSame(array('bar'), $component->foo, '__set() populates component variables');
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 000000000..f362d1471
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,15 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Bridge\PhpUnit\DeprecationErrorHandler;
+
+if ('disabled' !== getenv('SYMFONY_DEPRECATIONS_HELPER')) {
+ DeprecationErrorHandler::register(getenv('SYMFONY_DEPRECATIONS_HELPER'));
+}
diff --git a/tests/fixtures/myComponent.php b/tests/fixtures/myComponent.php
new file mode 100644
index 000000000..008695803
--- /dev/null
+++ b/tests/fixtures/myComponent.php
@@ -0,0 +1,8 @@
+setReturnValue($arguments[0]);
+
+ return true;
+ }
+ }
+}
diff --git a/tests/sfContext.class.php b/tests/sfContext.class.php
new file mode 100644
index 000000000..8fead9ad3
--- /dev/null
+++ b/tests/sfContext.class.php
@@ -0,0 +1,123 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+class sfContext
+{
+ protected static ?sfContext $instance = null;
+
+ public $configuration;
+ public $request;
+ public $response;
+ public $controller;
+ public $routing;
+ public $user;
+ public $storage;
+
+ protected string $sessionPath = '';
+
+ protected sfEventDispatcher $dispatcher;
+
+ public static function getInstance($factories = array(), $force = false): self
+ {
+ if (!isset(self::$instance) || $force) {
+ self::$instance = new sfContext();
+
+ self::$instance->sessionPath = sys_get_temp_dir().'/sessions_'.rand(11111, 99999);
+ self::$instance->storage = new sfSessionTestStorage(array('session_path' => self::$instance->sessionPath));
+
+ self::$instance->dispatcher = new sfEventDispatcher();
+
+ foreach ($factories as $type => $class) {
+ self::$instance->inject($type, $class);
+ }
+ }
+
+ return self::$instance;
+ }
+
+ public function __destruct()
+ {
+ sfToolkit::clearDirectory($this->sessionPath);
+ }
+
+ public static function hasInstance()/*: true*/
+ {
+ return true;
+ }
+
+ public function getEventDispatcher(): sfEventDispatcher
+ {
+ return self::$instance->dispatcher;
+ }
+
+ public function getModuleName(): string
+ {
+ return 'module';
+ }
+
+ public function getActionName(): string
+ {
+ return 'action';
+ }
+
+ public function getConfiguration()
+ {
+ return $this->configuration;
+ }
+
+ public function getRequest()
+ {
+ return $this->request;
+ }
+
+ public function getResponse()
+ {
+ return $this->response;
+ }
+
+ public function getRouting()
+ {
+ return $this->routing;
+ }
+
+ public function getStorage()
+ {
+ return $this->storage;
+ }
+
+ public function getUser()
+ {
+ return $this->user;
+ }
+
+ public function getController()
+ {
+ return $this->controller;
+ }
+
+ public function inject($type, $class, $parameters = array())
+ {
+ switch ($type) {
+ case 'routing':
+ $object = new $class($this->dispatcher, null, $parameters);
+ break;
+ case 'response':
+ $object = new $class($this->dispatcher, $parameters);
+ break;
+ case 'request':
+ $object = new $class($this->dispatcher, $this->routing, $parameters);
+ break;
+ default:
+ $object = new $class($this, $parameters);
+ }
+
+ $this->{$type} = $object;
+ }
+}
diff --git a/tests/sfEventDispatcherTestCase.php b/tests/sfEventDispatcherTestCase.php
new file mode 100644
index 000000000..3136fc4f9
--- /dev/null
+++ b/tests/sfEventDispatcherTestCase.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use PHPUnit\Framework\TestCase;
+
+require_once __DIR__.'/fixtures/myEventDispatcherTest.php';
+
+/**
+ * @internal
+ *
+ * @coversNothing
+ */
+class sfEventDispatcherTestCase extends TestCase
+{
+ protected $testObject;
+ protected $dispatcher;
+ protected $class;
+
+ public function testExistedMethod()
+ {
+ $this->dispatcher->connect($this->class.'.method_not_found', array(myEventDispatcherTest::class, 'newMethod'));
+
+ $this->assertSame('ok', $this->testObject->newMethod('ok'), '__call() accepts new methods via sfEventDispatcher');
+ }
+
+ public function testNonExistantMethod()
+ {
+ $this->expectException(sfException::class);
+
+ $this->testObject->nonexistantmethodname();
+ }
+}
diff --git a/tests/sfNoRouting.class.php b/tests/sfNoRouting.class.php
new file mode 100644
index 000000000..23b8a5942
--- /dev/null
+++ b/tests/sfNoRouting.class.php
@@ -0,0 +1,107 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+/**
+ * sfNoRouting class is a very simple routing class that uses GET parameters.
+ *
+ * @author Fabien Potencier
+ *
+ * @version SVN: $Id: sfNoRouting.class.php 20566 2009-07-29 07:04:01Z fabien $
+ */
+class sfNoRouting extends sfRouting
+{
+ /**
+ * @see sfRouting
+ */
+ public function getCurrentInternalUri($with_route_name = false)
+ {
+ $parameters = $this->mergeArrays($this->defaultParameters, $_GET);
+ $action = sprintf('%s/%s', $parameters['module'], $parameters['action']);
+
+ // other parameters
+ unset($parameters['module'], $parameters['action']);
+ ksort($parameters);
+ $parameters = count($parameters) ? '?'.http_build_query($parameters, '', '&') : '';
+
+ return sprintf('%s%s', $action, $parameters);
+ }
+
+ /**
+ * @see sfRouting
+ */
+ public function generate($name, $params = array(), $absolute = false)
+ {
+ $parameters = $this->mergeArrays($this->defaultParameters, $params);
+ if ($this->getDefaultParameter('module') == $parameters['module']) {
+ unset($parameters['module']);
+ }
+ if ($this->getDefaultParameter('action') == $parameters['action']) {
+ unset($parameters['action']);
+ }
+
+ $parameters = http_build_query($parameters, '', '&');
+
+ return $this->fixGeneratedUrl('/'.($parameters ? '?'.$parameters : ''), $absolute);
+ }
+
+ /**
+ * @see sfRouting
+ */
+ public function parse($url)
+ {
+ return array();
+ }
+
+ /**
+ * @see sfRouting
+ */
+ public function getRoutes()
+ {
+ return array();
+ }
+
+ /**
+ * @see sfRouting
+ */
+ public function getRoute($name)
+ {
+ return null;
+ }
+
+ /**
+ * @see sfRouting
+ */
+ public function setRoutes($routes)
+ {
+ return array();
+ }
+
+ /**
+ * @see sfRouting
+ */
+ public function hasRoutes()
+ {
+ return false;
+ }
+
+ /**
+ * @see sfRouting
+ */
+ public function clearRoutes() {}
+
+ protected function mergeArrays($arr1, $arr2)
+ {
+ foreach ($arr2 as $key => $value) {
+ $arr1[$key] = $value;
+ }
+
+ return $arr1;
+ }
+}