<?php
/*
 * This file is part of Chevere.
 *
 * (c) Rodolfo Berrios < [email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
declare(strict_types=1);
namespace Chevere\Tests;
use ArgumentCountError;
use Chevere\Parameter\ArrayParameter;
use Chevere\Parameter\Interfaces\IntParameterInterface;
use Chevere\Parameter\Interfaces\StringParameterInterface;
use Chevere\Parameter\Interfaces\UnionParameterInterface;
use InvalidArgumentException;
use OutOfBoundsException;
use PHPUnit\Framework\TestCase;
use TypeError;
use function Chevere\Parameter\arrayp;
use function Chevere\Parameter\assertArray;
use function Chevere\Parameter\float;
use function Chevere\Parameter\int;
use function Chevere\Parameter\null;
use function Chevere\Parameter\string;
use function Chevere\Parameter\union;
final class ArrayParameterTest extends TestCase
{
    public function testConstruct(): void
    {
        $parameter = new ArrayParameter();
        $this->assertSame(null, $parameter->default());
        $this->assertCount(0, $parameter->parameters());
        $this->assertSame([
            'type' => 'array#map',
            'description' => '',
            'default' => null,
            'parameters' => [],
        ], $parameter->schema());
    }
    public function testWithDefault(): void
    {
        $parameter = new ArrayParameter();
        $int = int(min: 1);
        $parameter = $parameter->withRequired(test: $int);
        $default = [
            'test' => 1,
        ];
        $with = $parameter->withDefault($default);
        $this->assertNotSame($parameter, $with);
        $this->assertSame($default, $with->default());
        (new ParameterHelper())->testWithParameterDefault(
            primitive: 'array',
            parameter: $parameter,
            default: $default,
            parameterWithDefault: $with
        );
        $this->assertSame([
            'type' => 'array#map',
            'description' => '',
            'default' => $default,
            'parameters' => [
                'test' => [
                    'required' => true,
                ] + $int->schema(),
            ],
        ], $with->schema());
        $this->expectException(ArgumentCountError::class);
        $this->expectExceptionMessage('Missing required argument(s): `test`');
        $parameter->withDefault([]);
    }
    public function testWithRequired(): void
    {
        $string = string();
        $int = int();
        $parameter = new ArrayParameter();
        $withRequired = $parameter->withRequired(
            one: $string,
            two: $int
        );
        $this->assertTrue($withRequired->parameters()->has('one', 'two'));
        $this->assertNotSame($parameter, $withRequired);
        $this->assertCount(2, $withRequired->parameters());
        $this->assertInstanceOf(
            StringParameterInterface::class,
            $withRequired->parameters()->get('one')
        );
        $this->assertInstanceOf(
            IntParameterInterface::class,
            $withRequired->parameters()->get('two')
        );
        $this->assertSame([
            'type' => 'array#map',
            'description' => '',
            'default' => null,
            'parameters' => [
                'one' => [
                    'required' => true,
                ] + $string->schema(),
                'two' => [
                    'required' => true,
                ] + $int->schema(),
            ],
        ], $withRequired->schema());
        $withRequired = $withRequired->withRequired(
            one: $int,
            three: $int
        );
        $this->assertTrue($withRequired->parameters()->has('one', 'two', 'three'));
        $this->assertInstanceOf(
            IntParameterInterface::class,
            $withRequired->parameters()->get('one')
        );
        $this->assertInstanceOf(
            IntParameterInterface::class,
            $withRequired->parameters()->get('three')
        );
    }
    public function testWithOptional(): void
    {
        $string = string();
        $union = union(null(), int());
        $parameter = new ArrayParameter();
        $with = $parameter->withOptional(
            one: $string,
            two: $union
        );
        $assert = assertArray($with, []);
        $this->assertSame([], $assert);
        $expected = [
            'two' => null,
        ];
        $assert = assertArray($with, $expected);
        $this->assertSame($expected, $assert);
        $expected = [
            'two' => 123,
        ];
        $assert = assertArray($with, $expected);
        $this->assertSame($expected, $assert);
        $this->assertTrue($with->parameters()->has('one', 'two'));
        $this->assertNotSame($parameter, $with);
        $this->assertCount(2, $with->parameters());
        $this->assertInstanceOf(
            StringParameterInterface::class,
            $with->parameters()->get('one')
        );
        $this->assertInstanceOf(
            UnionParameterInterface::class,
            $with->parameters()->get('two')
        );
        $with = $with->withOptional(
            one: $union,
            three: $union
        );
        $this->assertTrue($with->parameters()->has('one', 'two', 'three'));
        $this->assertInstanceOf(
            UnionParameterInterface::class,
            $with->parameters()->get('one')
        );
        $this->assertInstanceOf(
            UnionParameterInterface::class,
            $with->parameters()->get('three')
        );
    }
    public function testWithOut(): void
    {
        $string = string();
        $int = int();
        $parameter = (new ArrayParameter())->withRequired(
            one: $string,
            two: $int
        );
        $with = $parameter->without('one');
        $this->assertNotSame($parameter, $with);
        $this->assertCount(1, $with->parameters());
    }
    public function testAssertCompatible(): void
    {
        $string = string();
        $int = int();
        $parameter = (new ArrayParameter())->withRequired(
            one: $string,
        );
        $altString = string();
        $compatible = (new ArrayParameter())->withRequired(
            one: $altString,
        );
        $parameter->assertCompatible($compatible);
        $compatible->assertCompatible($parameter);
        $notCompatible = (new ArrayParameter())->withRequired(
            one: $int,
        );
        $expectedType = $string::class;
        $failType = $int::class;
        $this->expectExceptionMessage(
            <<<STRING
            Parameter `one` of type `{$expectedType}` is not compatible with type `{$failType}`
            STRING
        );
        $this->expectException(InvalidArgumentException::class);
        $parameter->assertCompatible($notCompatible);
    }
    public function testAssertCompatibleMissingKey(): void
    {
        $string = string();
        $parameter = (new ArrayParameter())->withRequired(
            one: $string,
        );
        $notCompatible = (new ArrayParameter())->withRequired(
            two: $string,
        );
        $this->expectException(OutOfBoundsException::class);
        $parameter->assertCompatible($notCompatible);
    }
    public function testIsList(): void
    {
        $string = string();
        $int = int();
        $parameter = (new ArrayParameter())->withRequired(
            a: $string,
            b: $int,
        );
        $this->assertFalse($parameter->isList());
        $this->assertTrue($parameter->isMap());
        $this->assertSame('array#map', $parameter->typeSchema());
        $parameter = (new ArrayParameter())->withRequired($string, $int);
        $this->assertTrue($parameter->isList());
        $this->assertFalse($parameter->isMap());
        $this->assertSame('array#list', $parameter->typeSchema());
    }
    public function testWithOptionalMinimum(): void
    {
        $array = (new ArrayParameter())
            ->withOptional(
                foo: string(),
                bar: string(),
            );
        $arrayWith = $array->withOptionalMinimum(1);
        $this->assertNotSame($array, $arrayWith);
        $this->assertSame(1, $arrayWith->parameters()->optionalMinimum());
    }
    public function testWithMakeOptional(): void
    {
        $array = (new ArrayParameter())
            ->withRequired(
                foo: string(),
                bar: string(),
            );
        $arrayWith = $array->withMakeOptional('foo');
        $this->assertNotSame($array, $arrayWith);
        $this->assertTrue($arrayWith->parameters()->optionalKeys()->contains('foo'));
        $this->assertTrue($arrayWith->parameters()->requiredKeys()->contains('bar'));
    }
    public function testWithMakeRequired(): void
    {
        $array = (new ArrayParameter())
            ->withOptional(
                foo: string(),
                bar: string(),
            );
        $arrayWith = $array->withMakeRequired('bar');
        $this->assertNotSame($array, $arrayWith);
        $this->assertTrue($arrayWith->parameters()->optionalKeys()->contains('foo'));
        $this->assertTrue($arrayWith->parameters()->requiredKeys()->contains('bar'));
    }
    public function testMakeOptionalNoParams(): void
    {
        $array = (new ArrayParameter())
            ->withRequired(
                foo: string(),
                bar: string(),
            );
        $arrayWith = $array->withMakeOptional();
        $this->assertNotSame($array, $arrayWith);
        $this->assertSame([], $arrayWith->parameters()->requiredKeys()->toArray());
        $this->assertTrue($arrayWith->parameters()->optionalKeys()->contains('foo', 'bar'));
    }
    public function testWithMakeRequiredNoParams(): void
    {
        $array = (new ArrayParameter())
            ->withOptional(
                foo: string(),
                bar: string(),
            );
        $arrayWith = $array->withMakeRequired();
        $this->assertNotSame($array, $arrayWith);
        $this->assertSame([], $arrayWith->parameters()->optionalKeys()->toArray());
        $this->assertTrue($arrayWith->parameters()->requiredKeys()->contains('foo', 'bar'));
    }
    public function testWithModify(): void
    {
        $float = float();
        $int = int();
        $array = (new ArrayParameter())
            ->withRequired(
                foo: string(),
            )
            ->withOptional(
                bar: string(),
            );
        $arrayWith = $array->withModify(
            foo: $float,
            bar: $int,
        );
        $this->assertNotSame($array, $arrayWith);
        $this->assertSame(
            $float,
            $arrayWith->parameters()->get('foo'),
        );
        $this->assertSame(
            $int,
            $arrayWith->parameters()->get('bar'),
        );
        $this->assertSame(['foo'], $arrayWith->parameters()->requiredKeys()->toArray());
        $this->assertSame(['bar'], $arrayWith->parameters()->optionalKeys()->toArray());
        $this->expectException(OutOfBoundsException::class);
        $this->expectExceptionMessage('Missing key(s) `0, 1`');
        $arrayWith->withModify(...[
            0 => $float,
            1 => $int,
        ]);
    }
    public function testInvoke(): void
    {
        $value = [
            'name' => 'Rodolfo',
            'id' => 10,
        ];
        $parameter = arrayp(
            name: string(),
            id: int()
        );
        $parameter($value);
        $this->expectException(TypeError::class);
        $parameter(null);
    }
    public function testMultipleErrors(): void
    {
        $parameter = arrayp(
            string('/^foo$/'),
            int(min: 1)
        );
        $parameter(['foo', 1]);
        $this->expectException(InvalidArgumentException::class);
        $this->expectExceptionMessage(
            <<<PLAIN
            [0]: Argument must be of type Stringable|string, int given
            [1]: Argument must be of type int, string given
            PLAIN
        );
        $parameter([1, 'foo']);
    }
}
 
  |