Passed
Pull Request — 1.x (#74)
by Kevin
02:19
created

BrowserKitBrowser::useParameters()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 1
nop 0
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Zenstruck\Browser;
4
5
use Symfony\Component\BrowserKit\AbstractBrowser;
6
use Symfony\Component\BrowserKit\CookieJar;
7
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
8
use Symfony\Component\HttpKernel\Profiler\Profile;
9
use Zenstruck\Assert;
10
use Zenstruck\Browser;
11
use Zenstruck\Browser\Mink\BrowserKitDriver;
12
use Zenstruck\Callback\Parameter;
13
14
/**
15
 * @author Kevin Bond <[email protected]>
16
 *
17
 * @template B of AbstractBrowser
18
 */
19
abstract class BrowserKitBrowser extends Browser
20
{
21
    /** @var B */
22
    private AbstractBrowser $inner;
23
    private ?HttpOptions $defaultHttpOptions = null;
24
25
    /**
26
     * @param B $inner
27
     */
28
    public function __construct(AbstractBrowser $inner)
29
    {
30
        $this->inner = $inner;
0 ignored issues
show
Documentation Bug introduced by
It seems like $inner of type Symfony\Component\BrowserKit\AbstractBrowser is incompatible with the declared type Zenstruck\Browser\B of property $inner.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
31
32
        parent::__construct(new BrowserKitDriver($inner));
33
    }
34
35
    /**
36
     * @return static
37
     */
38
    final public function interceptRedirects(): self
39
    {
40
        $this->inner->followRedirects(false);
41
42
        return $this;
43
    }
44
45
    /**
46
     * @return static
47
     */
48
    final public function followRedirects(): self
49
    {
50
        $this->inner->followRedirects(true);
51
52
        if ($this->minkSession()->isStarted() && $this->response()->isRedirect()) {
53
            $this->followRedirect();
54
        }
55
56
        return $this;
57
    }
58
59
    /**
60
     * @param int $max The maximum number of redirects to follow (defaults to "infinite")
61
     *
62
     * @return static
63
     */
64
    final public function followRedirect(int $max = \PHP_INT_MAX): self
65
    {
66
        for ($i = 0; $i < $max; ++$i) {
67
            if (!$this->response()->isRedirect()) {
68
                break;
69
            }
70
71
            $this->inner->followRedirect();
72
        }
73
74
        return $this;
75
    }
76
77
    /**
78
     * @param int $max The maximum number of redirects to follow (defaults to "infinite")
79
     *
80
     * @return static
81
     */
82
    final public function assertRedirectedTo(string $expected, int $max = \PHP_INT_MAX): self
83
    {
84
        $this->assertRedirected();
85
        $this->followRedirect($max);
86
        $this->assertOn($expected);
87
88
        return $this;
89
    }
90
91
    /**
92
     * @param HttpOptions|array $options
93
     *
94
     * @return static
95
     */
96
    final public function setDefaultHttpOptions($options): self
97
    {
98
        $this->defaultHttpOptions = HttpOptions::create($options);
99
100
        return $this;
101
    }
102
103
    /**
104
     * @param HttpOptions|array $options HttpOptions::DEFAULT_OPTIONS
105
     *
106
     * @return static
107
     */
108
    final public function request(string $method, string $url, $options = []): self
109
    {
110
        if ($this->defaultHttpOptions) {
111
            $options = $this->defaultHttpOptions->merge($options);
112
        }
113
114
        $options = HttpOptions::create($options);
115
116
        $this->inner->request(
117
            $method,
118
            $options->addQueryToUrl($url),
119
            $options->parameters(),
120
            $options->files(),
121
            $options->server(),
122
            $options->body()
123
        );
124
125
        return $this;
126
    }
127
128
    /**
129
     * @see request()
130
     *
131
     * @param HttpOptions|array $options
132
     *
133
     * @return static
134
     */
135
    final public function get(string $url, $options = []): self
136
    {
137
        return $this->request('GET', $url, $options);
138
    }
139
140
    /**
141
     * @see request()
142
     *
143
     * @param HttpOptions|array $options
144
     *
145
     * @return static
146
     */
147
    final public function post(string $url, $options = []): self
148
    {
149
        return $this->request('POST', $url, $options);
150
    }
151
152
    /**
153
     * @see request()
154
     *
155
     * @param HttpOptions|array $options
156
     *
157
     * @return static
158
     */
159
    final public function put(string $url, $options = []): self
160
    {
161
        return $this->request('PUT', $url, $options);
162
    }
163
164
    /**
165
     * @see request()
166
     *
167
     * @param HttpOptions|array $options
168
     *
169
     * @return static
170
     */
171
    final public function delete(string $url, $options = []): self
172
    {
173
        return $this->request('DELETE', $url, $options);
174
    }
175
176
    /**
177
     * @see request()
178
     *
179
     * @param HttpOptions|array $options
180
     *
181
     * @return static
182
     */
183
    final public function patch(string $url, $options = []): self
184
    {
185
        return $this->request('PATCH', $url, $options);
186
    }
187
188
    /**
189
     * @return static
190
     */
191
    final public function assertStatus(int $expected): self
192
    {
193
        return $this->wrapMinkExpectation(
194
            fn() => $this->webAssert()->statusCodeEquals($expected)
195
        );
196
    }
197
198
    /**
199
     * @return static
200
     */
201
    final public function assertSuccessful(): self
202
    {
203
        Assert::true($this->response()->isSuccessful(), 'Expected successful status code (2xx) but got {actual}.', [
204
            'actual' => $this->response()->statusCode(),
205
        ]);
206
207
        return $this;
208
    }
209
210
    /**
211
     * @return static
212
     */
213
    final public function assertRedirected(): self
214
    {
215
        if ($this->inner->isFollowingRedirects()) {
216
            throw new \RuntimeException('Cannot assert redirected if not intercepting redirects. Call ->interceptRedirects() before making the request.');
217
        }
218
219
        Assert::true($this->response()->isRedirect(), 'Expected redirect status code (3xx) but got {actual}.', [
220
            'actual' => $this->response()->statusCode(),
221
        ]);
222
223
        return $this;
224
    }
225
226
    /**
227
     * @return static
228
     */
229
    final public function assertHeaderEquals(string $header, string $expected): self
230
    {
231
        return $this->wrapMinkExpectation(
232
            fn() => $this->webAssert()->responseHeaderEquals($header, $expected)
233
        );
234
    }
235
236
    /**
237
     * @return static
238
     */
239
    final public function assertHeaderContains(string $header, string $expected): self
240
    {
241
        return $this->wrapMinkExpectation(
242
            fn() => $this->webAssert()->responseHeaderContains($header, $expected)
243
        );
244
    }
245
246
    /**
247
     * @return static
248
     */
249
    final public function assertJson(string $expectedContentType = 'json'): self
250
    {
251
        return $this->assertHeaderContains('Content-Type', $expectedContentType);
252
    }
253
254
    /**
255
     * @param string $expression JMESPath expression
256
     * @param mixed  $expected
257
     *
258
     * @return static
259
     */
260
    final public function assertJsonMatches(string $expression, $expected): self
261
    {
262
        Assert::that($this->response()->assertJson()->search($expression))->is($expected);
263
264
        return $this;
265
    }
266
267
    abstract public function profile(): Profile;
268
269
    /**
270
     * @return B
271
     */
272
    final protected function inner(): AbstractBrowser
273
    {
274
        return $this->inner;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->inner returns the type Symfony\Component\BrowserKit\AbstractBrowser which is incompatible with the documented return type Zenstruck\Browser\B.
Loading history...
275
    }
276
277
    protected function useParameters(): array
278
    {
279
        return [
0 ignored issues
show
introduced by
The expression return array(parent::use...n(...) { /* ... */ }))) returns an array which contains values of type array<integer,Zenstruck\...meter\UntypedParameter> which are incompatible with the return type Zenstruck\Callback\Parameter mandated by Zenstruck\Browser::useParameters().
Loading history...
280
            ...parent::useParameters(),
281
            Parameter::typed(CookieJar::class, Parameter::factory(fn() => $this->inner->getCookieJar())),
282
            Parameter::typed(DataCollectorInterface::class, Parameter::factory(function(string $class) {
283
                foreach ($this->profile()->getCollectors() as $collector) {
284
                    if ($class === \get_class($collector)) {
285
                        return $collector;
286
                    }
287
                }
288
289
                Assert::fail('DataCollector %s is not available for this request.', [$class]);
290
            })),
291
        ];
292
    }
293
}
294