diff --git a/tests/Unit/Libraries/Cms/Form/Filter/IntarrayFilterTest.php b/tests/Unit/Libraries/Cms/Form/Filter/IntarrayFilterTest.php new file mode 100644 index 0000000000000..41f80f4493d44 --- /dev/null +++ b/tests/Unit/Libraries/Cms/Form/Filter/IntarrayFilterTest.php @@ -0,0 +1,111 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Tests\Unit\Libraries\Cms\Form\Filter; + +use Joomla\CMS\Form\Filter\IntarrayFilter; +use Joomla\Tests\Unit\UnitTestCase; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDox; + +/** + * Test class for IntarrayFilter. + * + * The filter casts the value to an array of integers. Only the element is read, and only to raise + * the deprecation for the legacy `INT_ARRAY` filter name, so the group, input registry and form are + * passed as null. + * + * @package Joomla.UnitTest + * @subpackage Form + * @since __DEPLOY_VERSION__ + */ +#[TestDox('The intarray form filter')] +class IntarrayFilterTest extends UnitTestCase +{ + /** + * Test data for the testFilter method. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public static function dataFilter(): array + { + return [ + 'numeric string is wrapped' => [[5], '5'], + 'array of numeric strings' => [[1, 2], ['1', '2']], + 'non numeric member becomes 0' => [[1, 2, 0], ['1', '2', 'x']], + 'empty array stays empty' => [[], []], + 'string keys are preserved' => [['x' => 7], ['x' => '7']], + 'non numeric string becomes 0' => [[0], 'abc'], + 'null becomes zero' => [[0], null], + 'float is truncated' => [[3], 3.9], + 'boolean true' => [[1], true], + ]; + } + + /** + * Tests that the value is cast to an array of integers. + * + * @param array $expected The expected integer array + * @param mixed $value The raw form field value + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[DataProvider('dataFilter')] + #[TestDox('casts the value to an array of integers')] + public function testFilter(array $expected, $value): void + { + $filter = new IntarrayFilter(); + $element = new \SimpleXMLElement(''); + + $this->assertSame($expected, $filter->filter($element, $value, null, null, null)); + } + + /** + * Tests that an object is converted through its public properties, keeping their names as keys. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('converts an object through its public properties, keeping the property names as keys')] + public function testObjectKeepsItsPropertyNamesAsKeys(): void + { + $filter = new IntarrayFilter(); + $element = new \SimpleXMLElement(''); + + $this->assertSame( + ['a' => 3, 'b' => 4], + $filter->filter($element, (object) ['a' => '3', 'b' => '4'], null, null, null) + ); + } + + /** + * Tests that the deprecated INT_ARRAY filter name still filters the value. + * + * The deprecation itself is raised with a suppressed trigger_error() and is therefore not + * observable from the test, so only the filtering is asserted here. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('still filters when the deprecated INT_ARRAY filter name is used')] + public function testDeprecatedFilterNameStillFilters(): void + { + $filter = new IntarrayFilter(); + $element = new \SimpleXMLElement(''); + + $this->assertSame([1, 2], $filter->filter($element, ['1', '2'], null, null, null)); + } +} diff --git a/tests/Unit/Libraries/Cms/Form/Filter/RawFilterTest.php b/tests/Unit/Libraries/Cms/Form/Filter/RawFilterTest.php new file mode 100644 index 0000000000000..ce1e8bdd1a849 --- /dev/null +++ b/tests/Unit/Libraries/Cms/Form/Filter/RawFilterTest.php @@ -0,0 +1,86 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Tests\Unit\Libraries\Cms\Form\Filter; + +use Joomla\CMS\Form\Filter\RawFilter; +use Joomla\Tests\Unit\UnitTestCase; +use PHPUnit\Framework\Attributes\TestDox; + +/** + * Test class for RawFilter. + * + * The filter hands the value straight back. The element, group, input registry and form are not + * read, so they are all passed as null. + * + * @package Joomla.UnitTest + * @subpackage Form + * @since __DEPLOY_VERSION__ + */ +#[TestDox('The raw form filter')] +class RawFilterTest extends UnitTestCase +{ + /** + * Tests that scalar values are handed back untouched. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('returns scalar values untouched')] + public function testScalarValuesArePassedThrough(): void + { + $filter = new RawFilter(); + $element = new \SimpleXMLElement(''); + + $this->assertSame('markup', $filter->filter($element, 'markup', null, null, null)); + $this->assertSame('', $filter->filter($element, '', null, null, null)); + $this->assertSame(0, $filter->filter($element, 0, null, null, null)); + $this->assertNull($filter->filter($element, null, null, null, null)); + $this->assertFalse($filter->filter($element, false, null, null, null)); + } + + /** + * Tests that arrays are handed back untouched, including nested ones. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('returns arrays untouched, including nested ones')] + public function testArraysArePassedThrough(): void + { + $filter = new RawFilter(); + $element = new \SimpleXMLElement(''); + + $this->assertSame([], $filter->filter($element, [], null, null, null)); + + $nested = ['a' => ['b' => '']]; + + $this->assertSame($nested, $filter->filter($element, $nested, null, null, null)); + } + + /** + * Tests that an object is handed back as the very same instance rather than a copy. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('returns the very same object instance, not a copy')] + public function testObjectIsNotCopied(): void + { + $filter = new RawFilter(); + $element = new \SimpleXMLElement(''); + $object = (object) ['title' => 'Test']; + + $this->assertSame($object, $filter->filter($element, $object, null, null, null)); + } +} diff --git a/tests/Unit/Libraries/Cms/Form/Filter/RulesFilterTest.php b/tests/Unit/Libraries/Cms/Form/Filter/RulesFilterTest.php new file mode 100644 index 0000000000000..3ed30ba1f6ef1 --- /dev/null +++ b/tests/Unit/Libraries/Cms/Form/Filter/RulesFilterTest.php @@ -0,0 +1,196 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Tests\Unit\Libraries\Cms\Form\Filter; + +use Joomla\CMS\Form\Filter\RulesFilter; +use Joomla\Tests\Unit\UnitTestCase; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDox; + +/** + * Test class for RulesFilter. + * + * The filter turns the permissions grid posted by the rules form field into the nested array of + * booleans a Rules object is built from. The element, group, input registry and form are not read, + * so they are all passed as null. + * + * @package Joomla.UnitTest + * @subpackage Form + * @since __DEPLOY_VERSION__ + */ +#[TestDox('The rules form filter')] +class RulesFilterTest extends UnitTestCase +{ + /** + * Test data for the testPermissionIsCastToBoolean method. + * + * Note that the check is a loose `$p == '1' || $p === 'true'`, so anything which loosely equals + * 1 is an allow and everything else which is not an empty string is a deny. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public static function dataPermissions(): array + { + return [ + 'string one is allow' => [true, '1'], + 'string true is allow' => [true, 'true'], + 'integer one is allow' => [true, 1], + 'boolean true is allow' => [true, true], + 'leading zero is allow' => [true, '01'], + 'string zero is deny' => [false, '0'], + 'string false is deny' => [false, 'false'], + 'arbitrary string is deny' => [false, 'nonsense'], + 'minus one is deny' => [false, '-1'], + ]; + } + + /** + * Tests how a single posted permission value is cast. + * + * @param boolean $expected The boolean the value must be cast to + * @param mixed $value The posted permission value + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[DataProvider('dataPermissions')] + #[TestDox('casts a posted permission to a boolean')] + public function testPermissionIsCastToBoolean(bool $expected, $value): void + { + $filter = new RulesFilter(); + $element = new \SimpleXMLElement(''); + + $result = $filter->filter($element, ['core.edit' => ['6' => $value]], null, null, null); + + $this->assertSame(['core.edit' => [6 => $expected]], $result); + } + + /** + * Tests that an inherited permission, posted as an empty string, is dropped. + * + * The identity is left out of the action entirely rather than being stored as false, which is + * what makes the difference between "inherit" and "deny" in the saved rules. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('drops an identity whose permission was left as inherited')] + public function testInheritedPermissionIsDropped(): void + { + $filter = new RulesFilter(); + $element = new \SimpleXMLElement(''); + + $result = $filter->filter( + $element, + ['core.edit' => ['6' => '1', '7' => '', '8' => '0']], + null, + null, + null + ); + + $this->assertSame(['core.edit' => [6 => true, 8 => false]], $result); + } + + /** + * Tests that an action all of whose identities are inherited survives as an empty array. + * + * The action key itself is created before the identities are walked, so it is kept even when + * nothing is stored under it. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('keeps an action whose identities were all inherited, as an empty array')] + public function testActionWithOnlyInheritedPermissionsSurvivesEmpty(): void + { + $filter = new RulesFilter(); + $element = new \SimpleXMLElement(''); + + $result = $filter->filter($element, ['core.edit' => ['6' => '', '7' => '']], null, null, null); + + $this->assertSame(['core.edit' => []], $result); + } + + /** + * Tests that several actions are all filtered. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('filters every action in the grid')] + public function testEveryActionIsFiltered(): void + { + $filter = new RulesFilter(); + $element = new \SimpleXMLElement(''); + + $result = $filter->filter( + $element, + [ + 'core.edit' => ['6' => '1'], + 'core.delete' => ['6' => '0'], + 'core.edit.state' => ['6' => 'true', '7' => 'false'], + ], + null, + null, + null + ); + + $this->assertSame( + [ + 'core.edit' => [6 => true], + 'core.delete' => [6 => false], + 'core.edit.state' => [6 => true, 7 => false], + ], + $result + ); + } + + /** + * Tests that an empty value produces an empty rules array. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('returns an empty array when nothing was posted')] + public function testEmptyValueProducesAnEmptyArray(): void + { + $filter = new RulesFilter(); + $element = new \SimpleXMLElement(''); + + $this->assertSame([], $filter->filter($element, null, null, null, null)); + $this->assertSame([], $filter->filter($element, [], null, null, null)); + } + + /** + * Tests that an object is accepted, since the value is cast to an array first. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('accepts the grid as an object')] + public function testObjectIsAccepted(): void + { + $filter = new RulesFilter(); + $element = new \SimpleXMLElement(''); + + $result = $filter->filter($element, (object) ['core.edit' => ['6' => '1']], null, null, null); + + $this->assertSame(['core.edit' => [6 => true]], $result); + } +} diff --git a/tests/Unit/Libraries/Cms/Form/Filter/SafehtmlFilterTest.php b/tests/Unit/Libraries/Cms/Form/Filter/SafehtmlFilterTest.php new file mode 100644 index 0000000000000..fe5635c63e100 --- /dev/null +++ b/tests/Unit/Libraries/Cms/Form/Filter/SafehtmlFilterTest.php @@ -0,0 +1,124 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Tests\Unit\Libraries\Cms\Form\Filter; + +use Joomla\CMS\Filter\InputFilter; +use Joomla\CMS\Form\Filter\SafehtmlFilter; +use Joomla\Tests\Unit\UnitTestCase; +use PHPUnit\Framework\Attributes\TestDox; + +/** + * Test class for SafehtmlFilter. + * + * The filter delegates to InputFilter in its block-defined-tags mode. Only the delegation and the + * handful of behaviours the form layer depends on are asserted here, the exhaustive cleaning matrix + * belongs to the tests of the filter package itself. The element, group, input registry and form + * are not read, so they are all passed as null. + * + * @package Joomla.UnitTest + * @subpackage Form + * @since __DEPLOY_VERSION__ + */ +#[TestDox('The safehtml form filter')] +class SafehtmlFilterTest extends UnitTestCase +{ + /** + * Resets the InputFilter instance cache, which is keyed by the constructor arguments and is + * otherwise shared with every other test in the run. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected function setUp(): void + { + parent::setUp(); + + $this->setStaticProperty(InputFilter::class, 'instances', []); + } + + /** + * Tests that a script element is removed. + * + * Note the element is removed but its text content is kept, which is what InputFilter does in + * this mode. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('removes a script element')] + public function testScriptElementIsRemoved(): void + { + $filter = new SafehtmlFilter(); + $element = new \SimpleXMLElement(''); + + $result = $filter->filter($element, '

ok

', null, null, null); + + $this->assertStringNotContainsString('assertStringContainsString('

ok

', $result); + } + + /** + * Tests that an event handler attribute is removed. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('removes an event handler attribute')] + public function testEventHandlerAttributeIsRemoved(): void + { + $filter = new SafehtmlFilter(); + $element = new \SimpleXMLElement(''); + + $result = $filter->filter($element, '

ok

', null, null, null); + + $this->assertStringNotContainsString('onclick', $result); + $this->assertStringContainsString('ok', $result); + } + + /** + * Tests that ordinary formatting markup survives. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('keeps ordinary formatting markup')] + public function testFormattingMarkupIsKept(): void + { + $filter = new SafehtmlFilter(); + $element = new \SimpleXMLElement(''); + + $this->assertSame( + '

bold

', + $filter->filter($element, '

bold

', null, null, null) + ); + } + + /** + * Tests that text without markup and an empty string are handed back unchanged. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('leaves plain text and an empty string alone')] + public function testPlainTextIsUnchanged(): void + { + $filter = new SafehtmlFilter(); + $element = new \SimpleXMLElement(''); + + $this->assertSame('plain text', $filter->filter($element, 'plain text', null, null, null)); + $this->assertSame('', $filter->filter($element, '', null, null, null)); + } +} diff --git a/tests/Unit/Libraries/Cms/Form/Filter/TelFilterTest.php b/tests/Unit/Libraries/Cms/Form/Filter/TelFilterTest.php new file mode 100644 index 0000000000000..cffe94745bb56 --- /dev/null +++ b/tests/Unit/Libraries/Cms/Form/Filter/TelFilterTest.php @@ -0,0 +1,139 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Tests\Unit\Libraries\Cms\Form\Filter; + +use Joomla\CMS\Form\Filter\TelFilter; +use Joomla\Tests\Unit\UnitTestCase; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDox; + +/** + * Test class for TelFilter. + * + * The filter normalises a telephone number into the "countrycode.number" shape the telephone form + * field stores. It tries the NANP, ITU-T and EPP patterns in turn and falls back to reducing the + * value to its digits. The element, group, input registry and form are not read, so they are all + * passed as null. + * + * @package Joomla.UnitTest + * @subpackage Form + * @since __DEPLOY_VERSION__ + */ +#[TestDox('The telephone form filter')] +class TelFilterTest extends UnitTestCase +{ + /** + * Test data for the testFilter method. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public static function dataFilter(): array + { + return [ + // North American Numbering Plan, in each of the shapes the pattern accepts. + 'NANP with brackets and spaces' => ['1.2025550125', '(202) 555-0125'], + 'NANP with dots' => ['1.2025550125', '202.555.0125'], + 'NANP with hyphens' => ['1.2025550125', '202-555-0125'], + 'NANP with a leading +1' => ['1.2025550125', '+1 202 555 0125'], + 'NANP with a leading 1' => ['1.2025550125', '1-202-555-0125'], + 'NANP with surrounding spaces' => ['1.2025550125', ' (202) 555-0125 '], + + // ITU-T, which splits the country code off at the first space. + 'ITU-T' => ['44.2079460000', '+44 2079460000'], + + // EPP, which keeps the country code and drops any extension. + 'EPP' => ['44.2079460000', '+44.2079460000'], + 'EPP with an extension' => ['44.2079460000', '+44.2079460000x123'], + + // Already in the stored shape. + 'already normalised' => ['44.2079460000', '44.2079460000'], + + // Fallback: reduce to digits, then split a country code off the front when there are + // more than twelve of them. + 'seven digits stay local' => ['.5550125', '5550125'], + 'twelve digits stay local' => ['.123456789012', '123456789012'], + 'thirteen digits gain a code' => ['1.234567890123', '1234567890123'], + 'fifteen digits gain a code' => ['123.456789012345', '123456789012345'], + + // Anything the fallback cannot use is discarded. + 'sixteen digits are discarded' => ['', '1234567890123456'], + 'letters only' => ['', 'not a number'], + 'empty string' => ['', ''], + ]; + } + + /** + * Tests that a telephone number is normalised to the expected shape. + * + * @param string $expected The expected filtered value + * @param string $value The raw form field value + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[DataProvider('dataFilter')] + #[TestDox('normalises a telephone number')] + public function testFilter(string $expected, string $value): void + { + $filter = new TelFilter(); + $element = new \SimpleXMLElement(''); + + $this->assertSame($expected, $filter->filter($element, $value, null, null, null)); + } + + /** + * Tests an ITU-T number written without the space the branch relies on. + * + * The country code is split off at the first space, so a number which matches the ITU-T pattern + * but contains no space loses everything and is stored as a bare separator. This pins the + * current behaviour, it is not an endorsement of it. + * + * @return void + * + * @since __DEPLOY_VERSION__ + * + * @todo An ITU-T number without a space should keep its digits. + */ + #[TestDox('reduces a spaceless ITU-T number to a bare separator (known defect)')] + public function testSpacelessItuNumberIsReducedToASeparator(): void + { + $filter = new TelFilter(); + $element = new \SimpleXMLElement(''); + + $this->assertSame('.', $filter->filter($element, '+442079460000', null, null, null)); + } + + /** + * Tests that the element, group, input registry and form are not read. + * + * This pins the fact that no Form needs to be built to exercise this filter. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('ignores the element, group, input and form arguments')] + public function testContextArgumentsAreIgnored(): void + { + $filter = new TelFilter(); + + $bare = new \SimpleXMLElement(''); + $decorated = new \SimpleXMLElement(''); + + $this->assertSame( + $filter->filter($bare, '+44 2079460000', null, null, null), + $filter->filter($decorated, '+44 2079460000', 'somegroup', null, null) + ); + } +} diff --git a/tests/Unit/Libraries/Cms/Form/Filter/UnsetFilterTest.php b/tests/Unit/Libraries/Cms/Form/Filter/UnsetFilterTest.php new file mode 100644 index 0000000000000..e93fcd08786eb --- /dev/null +++ b/tests/Unit/Libraries/Cms/Form/Filter/UnsetFilterTest.php @@ -0,0 +1,68 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Tests\Unit\Libraries\Cms\Form\Filter; + +use Joomla\CMS\Form\Filter\UnsetFilter; +use Joomla\Tests\Unit\UnitTestCase; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDox; + +/** + * Test class for UnsetFilter. + * + * The filter always discards the value. The element, group, input registry and form are not read, + * so they are all passed as null. + * + * @package Joomla.UnitTest + * @subpackage Form + * @since __DEPLOY_VERSION__ + */ +#[TestDox('The unset form filter')] +class UnsetFilterTest extends UnitTestCase +{ + /** + * Values which must all be discarded. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public static function dataFilter(): array + { + return [ + 'string' => ['a value'], + 'empty' => [''], + 'zero' => [0], + 'null' => [null], + 'array' => [['a', 'b']], + 'object' => [(object) ['title' => 'Test']], + ]; + } + + /** + * Tests that the value is always discarded. + * + * @param mixed $value The value handed to the filter + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[DataProvider('dataFilter')] + #[TestDox('discards the value whatever it is')] + public function testValueIsAlwaysDiscarded($value): void + { + $filter = new UnsetFilter(); + $element = new \SimpleXMLElement(''); + + $this->assertNull($filter->filter($element, $value, null, null, null)); + } +} diff --git a/tests/Unit/Libraries/Cms/Form/Filter/UrlFilterTest.php b/tests/Unit/Libraries/Cms/Form/Filter/UrlFilterTest.php new file mode 100644 index 0000000000000..300dc4e3183b9 --- /dev/null +++ b/tests/Unit/Libraries/Cms/Form/Filter/UrlFilterTest.php @@ -0,0 +1,332 @@ + + * @license GNU General Public License version 2 or later; see LICENSE.txt + */ + +namespace Joomla\Tests\Unit\Libraries\Cms\Form\Filter; + +use Joomla\CMS\Filter\InputFilter; +use Joomla\CMS\Form\Filter\UrlFilter; +use Joomla\CMS\Uri\Uri; +use Joomla\Tests\Unit\UnitTestCase; +use PHPUnit\Framework\Attributes\BackupGlobals; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestDox; + +/** + * Test class for UrlFilter. + * + * This is the only filter in the group which reads its element and needs an environment: the + * branches which build an internal link resolve Uri::root(), which needs both the server variables + * and a container holding the configuration. The group, input registry and form are still not read, + * so they are passed as null. + * + * @package Joomla.UnitTest + * @subpackage Form + * @since __DEPLOY_VERSION__ + */ +#[BackupGlobals(true)] +#[TestDox('The url form filter')] +class UrlFilterTest extends UnitTestCase +{ + /** + * Sets up the server variables and the container Uri::root() resolves through. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected function setUp(): void + { + parent::setUp(); + + $this->setUpContainer(); + $this->setStaticProperty(InputFilter::class, 'instances', []); + + Uri::reset(); + + $_SERVER['HTTP_HOST'] = 'www.example.com:80'; + $_SERVER['SCRIPT_NAME'] = '/joomla/index.php'; + $_SERVER['PHP_SELF'] = '/joomla/index.php'; + $_SERVER['REQUEST_URI'] = '/joomla/index.php?var=value'; + } + + /** + * Resets the static Uri caches, which are not covered by the Factory snapshot. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + protected function tearDown(): void + { + Uri::reset(); + + parent::tearDown(); + } + + /** + * Returns a url type field element. + * + * @param string $attributes Extra attributes for the element + * + * @return \SimpleXMLElement + * + * @since __DEPLOY_VERSION__ + */ + private function urlElement(string $attributes = ''): \SimpleXMLElement + { + return new \SimpleXMLElement(''); + } + + /** + * Test data for the testValueWithAProtocolIsKept method. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public static function dataWithProtocol(): array + { + return [ + 'https with path, query and fragment' => ['https://example.com/path?q=1#frag'], + 'http' => ['http://example.com'], + 'already punycoded' => ['https://xn--mller-kva.de/pfad'], + ]; + } + + /** + * Tests that a value which already carries a protocol is handed back unchanged. + * + * @param string $value The raw form field value + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[DataProvider('dataWithProtocol')] + #[TestDox('keeps a value which already carries a protocol')] + public function testValueWithAProtocolIsKept(string $value): void + { + $filter = new UrlFilter(); + + $this->assertSame($value, $filter->filter($this->urlElement(), $value, null, null, null)); + } + + /** + * Test data for the testEmptyValueIsRejected method. + * + * @return array + * + * @since __DEPLOY_VERSION__ + */ + public static function dataEmpty(): array + { + return [ + 'empty string' => [''], + 'null' => [null], + 'string zero' => ['0'], + ]; + } + + /** + * Tests that an empty value is rejected as boolean false rather than an empty string. + * + * Note that the check is empty(), so the string "0" is rejected too. + * + * @param mixed $value The raw form field value + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[DataProvider('dataEmpty')] + #[TestDox('rejects an empty value as boolean false')] + public function testEmptyValueIsRejected($value): void + { + $filter = new UrlFilter(); + + $this->assertFalse($filter->filter($this->urlElement(), $value, null, null, null)); + } + + /** + * Tests that a value without a protocol is treated as an external link. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('prepends http to a value without a protocol')] + public function testValueWithoutAProtocolBecomesExternal(): void + { + $filter = new UrlFilter(); + + $this->assertSame( + 'http://www.example.com', + $filter->filter($this->urlElement(), 'www.example.com', null, null, null) + ); + $this->assertSame( + 'http://example.com/path', + $filter->filter($this->urlElement(), 'example.com/path', null, null, null) + ); + } + + /** + * Tests that a value which looks like an internal link gets the site root prepended. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('prepends the site root to a link which starts with index.php')] + public function testInternalLinkGetsTheSiteRoot(): void + { + $filter = new UrlFilter(); + + $this->assertSame( + Uri::root() . 'index.php?option=com_content', + $filter->filter($this->urlElement(), 'index.php?option=com_content', null, null, null) + ); + } + + /** + * Tests that the characters which are never valid in a uri are removed. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + #[TestDox('removes the characters which are never valid in a uri')] + public function testInvalidUriCharactersAreRemoved(): void + { + $filter = new UrlFilter(); + + $this->assertSame( + 'https://example.com/ab', + $filter->filter($this->urlElement(), 'https://example.com/a"b', null, null, null) + ); + $this->assertSame( + 'https://example.com', + $filter->filter($this->urlElement(), '