Passed
Push — 1.x ( d1dbfd...3c16d6 )
by Kevin
02:18
created

BrowserKitBrowser::patch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Zenstruck\Browser;
4
5
use Symfony\Component\BrowserKit\AbstractBrowser;
6
use Symfony\Component\HttpKernel\Profiler\Profile;
7
use Zenstruck\Assert;
8
use Zenstruck\Browser;
9
use Zenstruck\Browser\Mink\BrowserKitDriver;
10
11
/**
12
 * @author Kevin Bond <[email protected]>
13
 *
14
 * @template B of AbstractBrowser
15
 */
16
abstract class BrowserKitBrowser extends Browser
17
{
18
    /** @var B */
19
    private AbstractBrowser $inner;
20
    private ?HttpOptions $defaultHttpOptions = null;
21
22
    /**
23
     * @param B $inner
24
     */
25
    public function __construct(AbstractBrowser $inner)
26
    {
27
        $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...
28
29
        parent::__construct(new BrowserKitDriver($inner));
30
    }
31
32
    /**
33
     * @return static
34
     */
35
    final public function interceptRedirects(): self
36
    {
37
        $this->inner->followRedirects(false);
38
39
        return $this;
40
    }
41
42
    /**
43
     * @return static
44
     */
45
    final public function followRedirects(): self
46
    {
47
        $this->inner->followRedirects(true);
48
49
        if ($this->minkSession()->isStarted() && $this->response()->isRedirect()) {
50
            $this->followRedirect();
51
        }
52
53
        return $this;
54
    }
55
56
    /**
57
     * @param int $max The maximum number of redirects to follow (defaults to "infinite")
58
     *
59
     * @return static
60
     */
61
    final public function followRedirect(int $max = \PHP_INT_MAX): self
62
    {
63
        for ($i = 0; $i < $max; ++$i) {
64
            if (!$this->response()->isRedirect()) {
65
                break;
66
            }
67
68
            $this->inner->followRedirect();
69
        }
70
71
        return $this;
72
    }
73
74
    /**
75
     * @param int $max The maximum number of redirects to follow (defaults to "infinite")
76
     *
77
     * @return static
78
     */
79
    final public function assertRedirectedTo(string $expected, int $max = \PHP_INT_MAX): self
80
    {
81
        $this->assertRedirected();
82
        $this->followRedirect($max);
83
        $this->assertOn($expected);
84
85
        return $this;
86
    }
87
88
    /**
89
     * @param HttpOptions|array $options
90
     *
91
     * @return static
92
     */
93
    final public function setDefaultHttpOptions($options): self
94
    {
95
        $this->defaultHttpOptions = HttpOptions::create($options);
96
97
        return $this;
98
    }
99
100
    /**
101
     * @param HttpOptions|array $options HttpOptions::DEFAULT_OPTIONS
102
     *
103
     * @return static
104
     */
105
    final public function request(string $method, string $url, $options = []): self
106
    {
107
        if ($this->defaultHttpOptions) {
108
            $options = $this->defaultHttpOptions->merge($options);
109
        }
110
111
        $options = HttpOptions::create($options);
112
113
        $this->inner->request(
114
            $method,
115
            $options->addQueryToUrl($url),
116
            $options->parameters(),
117
            $options->files(),
118
            $options->server(),
119
            $options->body()
120
        );
121
122
        return $this;
123
    }
124
125
    /**
126
     * @see request()
127
     *
128
     * @param HttpOptions|array $options
129
     *
130
     * @return static
131
     */
132
    final public function get(string $url, $options = []): self
133
    {
134
        return $this->request('GET', $url, $options);
135
    }
136
137
    /**
138
     * @see request()
139
     *
140
     * @param HttpOptions|array $options
141
     *
142
     * @return static
143
     */
144
    final public function post(string $url, $options = []): self
145
    {
146
        return $this->request('POST', $url, $options);
147
    }
148
149
    /**
150
     * @see request()
151
     *
152
     * @param HttpOptions|array $options
153
     *
154
     * @return static
155
     */
156
    final public function put(string $url, $options = []): self
157
    {
158
        return $this->request('PUT', $url, $options);
159
    }
160
161
    /**
162
     * @see request()
163
     *
164
     * @param HttpOptions|array $options
165
     *
166
     * @return static
167
     */
168
    final public function delete(string $url, $options = []): self
169
    {
170
        return $this->request('DELETE', $url, $options);
171
    }
172
173
    /**
174
     * @see request()
175
     *
176
     * @param HttpOptions|array $options
177
     *
178
     * @return static
179
     */
180
    final public function patch(string $url, $options = []): self
181
    {
182
        return $this->request('PATCH', $url, $options);
183
    }
184
185
    /**
186
     * @return static
187
     */
188
    final public function assertStatus(int $expected): self
189
    {
190
        return $this->wrapMinkExpectation(
191
            fn() => $this->webAssert()->statusCodeEquals($expected)
192
        );
193
    }
194
195
    /**
196
     * @return static
197
     */
198
    final public function assertSuccessful(): self
199
    {
200
        Assert::true($this->response()->isSuccessful(), 'Expected successful status code (2xx) but got {actual}.', [
201
            'actual' => $this->response()->statusCode(),
202
        ]);
203
204
        return $this;
205
    }
206
207
    /**
208
     * @return static
209
     */
210
    final public function assertRedirected(): self
211
    {
212
        if ($this->inner->isFollowingRedirects()) {
213
            throw new \RuntimeException('Cannot assert redirected if not intercepting redirects. Call ->interceptRedirects() before making the request.');
214
        }
215
216
        Assert::true($this->response()->isRedirect(), 'Expected redirect status code (3xx) but got {actual}.', [
217
            'actual' => $this->response()->statusCode(),
218
        ]);
219
220
        return $this;
221
    }
222
223
    /**
224
     * @return static
225
     */
226
    final public function assertHeaderEquals(string $header, string $expected): self
227
    {
228
        return $this->wrapMinkExpectation(
229
            fn() => $this->webAssert()->responseHeaderEquals($header, $expected)
230
        );
231
    }
232
233
    /**
234
     * @return static
235
     */
236
    final public function assertHeaderContains(string $header, string $expected): self
237
    {
238
        return $this->wrapMinkExpectation(
239
            fn() => $this->webAssert()->responseHeaderContains($header, $expected)
240
        );
241
    }
242
243
    /**
244
     * @return static
245
     */
246
    final public function assertJson(string $expectedContentType = 'json'): self
247
    {
248
        return $this->assertHeaderContains('Content-Type', $expectedContentType);
249
    }
250
251
    /**
252
     * @param string $expression JMESPath expression
253
     * @param mixed  $expected
254
     *
255
     * @return static
256
     */
257
    final public function assertJsonMatches(string $expression, $expected): self
258
    {
259
        Assert::that($this->response()->assertJson()->search($expression))->is($expected);
260
261
        return $this;
262
    }
263
264
    abstract public function profile(): Profile;
265
266
    /**
267
     * @return B
268
     */
269
    final protected function inner(): AbstractBrowser
270
    {
271
        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...
272
    }
273
}
274