Passed
Push — 1.x ( 82cd9a...b759cf )
by Kevin
01:14
created

test_function()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 0
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 2
rs 10
1
<?php
2
3
namespace Zenstruck\Callback\Tests;
4
5
use PHPUnit\Framework\TestCase;
6
use Zenstruck\Callback;
7
use Zenstruck\Callback\Exception\UnresolveableArgument;
8
use Zenstruck\Callback\Parameter;
9
10
/**
11
 * @author Kevin Bond <[email protected]>
12
 */
13
final class CallbackTest extends TestCase
14
{
15
    /**
16
     * @test
17
     */
18
    public function create_must_be_callable(): void
19
    {
20
        $this->expectException(\InvalidArgumentException::class);
21
22
        Callback::createFor('not a callable');
23
    }
24
25
    /**
26
     * @test
27
     */
28
    public function invoke_all_can_enforce_min_arguments(): void
29
    {
30
        $callback = Callback::createFor(function() { return 'ret'; });
31
32
        $this->expectException(\ArgumentCountError::class);
33
34
        $callback->invokeAll(Parameter::untyped('foo'), 1);
35
    }
36
37
    /**
38
     * @test
39
     */
40
    public function invoke_all_with_no_arguments(): void
41
    {
42
        $actual = Callback::createFor(function() { return 'ret'; })
43
            ->invokeAll(Parameter::untyped('foo'))
44
        ;
45
46
        $this->assertSame('ret', $actual);
47
    }
48
49
    /**
50
     * @test
51
     */
52
    public function invoke_all_with_string_callable(): void
53
    {
54
        $actual = Callback::createFor('strtoupper')
55
            ->invokeAll(Parameter::union(
56
                Parameter::untyped('foobar'),
57
                Parameter::typed('string', 'foobar')
58
            )
59
        )
60
        ;
61
62
        $this->assertSame('FOOBAR', $actual);
63
    }
64
65
    /**
66
     * @test
67
     */
68
    public function invoke_all_untyped_argument(): void
69
    {
70
        $actual = Callback::createFor(function($string) { return \mb_strtoupper($string); })
71
            ->invokeAll(Parameter::untyped('foobar'))
72
        ;
73
74
        $this->assertSame('FOOBAR', $actual);
75
    }
76
77
    /**
78
     * @test
79
     */
80
    public function invoke_all_primitive_typed_argument(): void
81
    {
82
        $actual = Callback::createFor(function(string $string) { return \mb_strtoupper($string); })
83
            ->invokeAll(Parameter::typed('string', 'foobar'))
84
        ;
85
86
        $this->assertSame('FOOBAR', $actual);
87
    }
88
89
    /**
90
     * @test
91
     */
92
    public function invoke_all_class_arguments(): void
93
    {
94
        $object = new Object2();
95
        $function = static function(Object1 $object1, Object2 $object2, $object3) {
96
            return [
97
                'object1' => $object1,
98
                'object2' => $object2,
99
                'object3' => $object3,
100
            ];
101
        };
102
103
        $actual = Callback::createFor($function)
104
            ->invokeAll(Parameter::union(
105
                Parameter::untyped($object),
106
                Parameter::typed(Object1::class, $object)
107
            ))
108
        ;
109
110
        $this->assertSame(
111
            [
112
                'object1' => $object,
113
                'object2' => $object,
114
                'object3' => $object,
115
            ],
116
            $actual
117
        );
118
    }
119
120
    /**
121
     * @test
122
     */
123
    public function invoke_all_class_arguments_value_factories(): void
124
    {
125
        $function = static function(Object1 $object1, Object2 $object2, $object3) {
126
            return [
127
                'object1' => $object1,
128
                'object2' => $object2,
129
                'object3' => $object3,
130
            ];
131
        };
132
        $factoryArgs = [];
133
        $factory = Parameter::factory(static function($arg) use (&$factoryArgs) {
134
            $factoryArgs[] = $arg;
135
136
            if ($arg) {
137
                return new $arg();
138
            }
139
140
            return new Object1();
141
        });
142
143
        $ret = Callback::createFor($function)
144
            ->invokeAll(Parameter::union(
145
                Parameter::untyped($factory),
146
                Parameter::typed(Object1::class, $factory)
147
            ))
148
        ;
149
150
        $this->assertSame(['object1', 'object2', 'object3'], \array_keys($ret));
151
        $this->assertInstanceOf(Object1::class, $ret['object1']);
152
        $this->assertInstanceOf(Object2::class, $ret['object2']);
153
        $this->assertInstanceOf(Object1::class, $ret['object3']);
154
        $this->assertSame(
155
            [Object1::class, Object2::class, null],
156
            $factoryArgs
157
        );
158
    }
159
160
    /**
161
     * @test
162
     */
163
    public function invoke_all_unresolvable_parameter(): void
164
    {
165
        $callback = Callback::createFor(static function(Object1 $object1, Object2 $object2, Object3 $object3) {});
0 ignored issues
show
Unused Code introduced by
The parameter $object3 is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

165
        $callback = Callback::createFor(static function(Object1 $object1, Object2 $object2, /** @scrutinizer ignore-unused */ Object3 $object3) {});

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $object2 is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

165
        $callback = Callback::createFor(static function(Object1 $object1, /** @scrutinizer ignore-unused */ Object2 $object2, Object3 $object3) {});

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $object1 is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

165
        $callback = Callback::createFor(static function(/** @scrutinizer ignore-unused */ Object1 $object1, Object2 $object2, Object3 $object3) {});

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
166
167
        $this->expectException(UnresolveableArgument::class);
168
        $this->expectExceptionMessage('Unable to resolve argument 3 for callback. Expected type: "mixed|Zenstruck\Callback\Tests\Object1"');
169
170
        $callback->invokeAll(Parameter::union(
171
            Parameter::untyped(new Object1()),
172
            Parameter::typed(Object1::class, new Object1())
173
        ));
174
    }
175
176
    /**
177
     * @test
178
     */
179
    public function invoke_with_no_args(): void
180
    {
181
        $actual = Callback::createFor(function() { return 'ret'; })
182
            ->invoke()
183
        ;
184
185
        $this->assertSame('ret', $actual);
186
    }
187
188
    /**
189
     * @test
190
     */
191
    public function invoke_with_resolvable_args(): void
192
    {
193
        $object = new Object2();
194
        $function = static function(Object1 $object1, Object2 $object2, $object3, $extra) {
195
            return [
196
                'object1' => $object1,
197
                'object2' => $object2,
198
                'object3' => $object3,
199
                'extra' => $extra,
200
            ];
201
        };
202
203
        $actual = Callback::createFor($function)
204
            ->invoke(
205
                Parameter::typed(Object1::class, $object),
206
                Parameter::typed(Object2::class, $object),
207
                Parameter::untyped($object),
208
                'value'
209
            )
210
        ;
211
212
        $this->assertSame(
213
            [
214
                'object1' => $object,
215
                'object2' => $object,
216
                'object3' => $object,
217
                'extra' => 'value',
218
            ],
219
            $actual
220
        );
221
    }
222
223
    /**
224
     * @test
225
     */
226
    public function invoke_with_unresolvable_argument(): void
227
    {
228
        $object = new Object2();
229
        $function = static function(Object1 $object1, $object2, $object3, $extra) {};
0 ignored issues
show
Unused Code introduced by
The parameter $object2 is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

229
        $function = static function(Object1 $object1, /** @scrutinizer ignore-unused */ $object2, $object3, $extra) {};

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $object1 is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

229
        $function = static function(/** @scrutinizer ignore-unused */ Object1 $object1, $object2, $object3, $extra) {};

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $object3 is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

229
        $function = static function(Object1 $object1, $object2, /** @scrutinizer ignore-unused */ $object3, $extra) {};

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $extra is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

229
        $function = static function(Object1 $object1, $object2, $object3, /** @scrutinizer ignore-unused */ $extra) {};

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
230
231
        $this->expectException(UnresolveableArgument::class);
232
        $this->expectExceptionMessage('Unable to resolve argument 2 for callback. Expected type: "Zenstruck\Callback\Tests\Object2"');
233
234
        Callback::createFor($function)
235
            ->invoke(
236
                Parameter::typed(Object1::class, $object),
237
                Parameter::typed(Object2::class, $object),
238
                Parameter::untyped($object),
239
                'value'
240
            )
241
        ;
242
    }
243
244
    /**
245
     * @test
246
     */
247
    public function invoke_with_not_enough_required_arguments(): void
248
    {
249
        $object = new Object2();
250
        $function = static function(Object1 $object1) {};
0 ignored issues
show
Unused Code introduced by
The parameter $object1 is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

250
        $function = static function(/** @scrutinizer ignore-unused */ Object1 $object1) {};

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
251
252
        $this->expectException(\ArgumentCountError::class);
253
        $this->expectExceptionMessage('No argument 2 for callable. Expected type: "Zenstruck\Callback\Tests\Object2"');
254
255
        Callback::createFor($function)
256
            ->invoke(
257
                Parameter::typed(Object1::class, $object),
258
                Parameter::typed(Object2::class, $object),
259
                Parameter::untyped($object),
260
                'value'
261
            )
262
        ;
263
    }
264
265
    /**
266
     * @test
267
     */
268
    public function can_mark_invoke_parameter_arguments_as_optional(): void
269
    {
270
        $actual = Callback::createFor(static function() { return 'ret'; })
271
            ->invoke(Parameter::typed('string', 'foobar')->optional())
272
        ;
273
274
        $this->assertSame('ret', $actual);
275
276
        $actual = Callback::createFor(static function(string $v) { return $v; })
277
            ->invoke(Parameter::typed('string', 'foobar')->optional())
278
        ;
279
280
        $this->assertSame('foobar', $actual);
281
    }
282
283
    /**
284
     * @test
285
     */
286
    public function is_stringable(): void
287
    {
288
        $this->assertStringMatchesFormat(__CLASS__.':%d', (string) Callback::createFor(function() {}));
289
        $this->assertStringMatchesFormat(__CLASS__.':%d', (string) Callback::createFor([$this, __METHOD__]));
290
        $this->assertStringMatchesFormat(Object4::class.':%d', (string) Callback::createFor(new Object4()));
291
        $this->assertStringMatchesFormat(Object4::class.':%d', (string) Callback::createFor([Object4::class, 'staticMethod']));
292
        $this->assertSame(__NAMESPACE__.'\test_function', (string) Callback::createFor(__NAMESPACE__.'\test_function'));
293
    }
294
}
295
296
class Object1
297
{
298
}
299
300
class Object2 extends Object1
301
{
302
}
303
304
class Object3
305
{
306
}
307
308
class Object4
309
{
310
    public function __invoke()
311
    {
312
    }
313
314
    public static function staticMethod()
315
    {
316
    }
317
}
318
319
function test_function()
320
{
321
}
322