• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

codeigniter4 / CodeIgniter4 / 8677009716

13 Apr 2024 11:45PM UTC coverage: 84.44% (-2.2%) from 86.607%
8677009716

push

github

web-flow
Merge pull request #8776 from kenjis/fix-findQualifiedNameFromPath-Cannot-declare-class-v3

fix: Cannot declare class CodeIgniter\Config\Services, because the name is already in use

0 of 3 new or added lines in 1 file covered. (0.0%)

478 existing lines in 72 files now uncovered.

20318 of 24062 relevant lines covered (84.44%)

188.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.3
/system/Test/DOMParser.php
1
<?php
2

3
declare(strict_types=1);
4

5
/**
6
 * This file is part of CodeIgniter 4 framework.
7
 *
8
 * (c) CodeIgniter Foundation <admin@codeigniter.com>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE file that was distributed with this source code.
12
 */
13

14
namespace CodeIgniter\Test;
15

16
use BadMethodCallException;
17
use DOMDocument;
18
use DOMNodeList;
19
use DOMXPath;
20
use InvalidArgumentException;
21

22
/**
23
 * Load a response into a DOMDocument for testing assertions based on that
24
 *
25
 * @see \CodeIgniter\Test\DOMParserTest
26
 */
27
class DOMParser
28
{
29
    /**
30
     * DOM for the body,
31
     *
32
     * @var DOMDocument
33
     */
34
    protected $dom;
35

36
    /**
37
     * Constructor.
38
     *
39
     * @throws BadMethodCallException
40
     */
41
    public function __construct()
42
    {
43
        if (! extension_loaded('DOM')) {
146✔
UNCOV
44
            throw new BadMethodCallException('DOM extension is required, but not currently loaded.'); // @codeCoverageIgnore
×
45
        }
46

47
        $this->dom = new DOMDocument('1.0', 'utf-8');
146✔
48
    }
49

50
    /**
51
     * Returns the body of the current document.
52
     */
53
    public function getBody(): string
54
    {
55
        return $this->dom->saveHTML();
6✔
56
    }
57

58
    /**
59
     * Sets a string as the body that we want to work with.
60
     *
61
     * @return $this
62
     */
63
    public function withString(string $content)
64
    {
65
        // DOMDocument::loadHTML() will treat your string as being in ISO-8859-1
66
        // (the HTTP/1.1 default character set) unless you tell it otherwise.
67
        // https://stackoverflow.com/a/8218649
68
        // So encode characters to HTML numeric string references.
69
        $content = mb_encode_numericentity($content, [0x80, 0x10FFFF, 0, 0x1FFFFF], 'UTF-8');
127✔
70

71
        // turning off some errors
72
        libxml_use_internal_errors(true);
127✔
73

74
        if (! $this->dom->loadHTML($content)) {
127✔
75
            // unclear how we would get here, given that we are trapping libxml errors
76
            // @codeCoverageIgnoreStart
UNCOV
77
            libxml_clear_errors();
×
78

UNCOV
79
            throw new BadMethodCallException('Invalid HTML');
×
80
            // @codeCoverageIgnoreEnd
81
        }
82

83
        // ignore the whitespace.
84
        $this->dom->preserveWhiteSpace = false;
127✔
85

86
        return $this;
127✔
87
    }
88

89
    /**
90
     * Loads the contents of a file as a string
91
     * so that we can work with it.
92
     *
93
     * @return $this
94
     */
95
    public function withFile(string $path)
96
    {
97
        if (! is_file($path)) {
2✔
98
            throw new InvalidArgumentException(basename($path) . ' is not a valid file.');
1✔
99
        }
100

101
        $content = file_get_contents($path);
1✔
102

103
        return $this->withString($content);
1✔
104
    }
105

106
    /**
107
     * Checks to see if the text is found within the result.
108
     */
109
    public function see(?string $search = null, ?string $element = null): bool
110
    {
111
        // If Element is null, we're just scanning for text
112
        if ($element === null) {
49✔
113
            $content = $this->dom->saveHTML($this->dom->documentElement);
23✔
114

115
            return mb_strpos($content, $search) !== false;
23✔
116
        }
117

118
        $result = $this->doXPath($search, $element);
28✔
119

120
        return (bool) $result->length;
28✔
121
    }
122

123
    /**
124
     * Checks to see if the text is NOT found within the result.
125
     */
126
    public function dontSee(?string $search = null, ?string $element = null): bool
127
    {
128
        return ! $this->see($search, $element);
5✔
129
    }
130

131
    /**
132
     * Checks to see if an element with the matching CSS specifier
133
     * is found within the current DOM.
134
     */
135
    public function seeElement(string $element): bool
136
    {
137
        return $this->see(null, $element);
4✔
138
    }
139

140
    /**
141
     * Checks to see if the element is available within the result.
142
     */
143
    public function dontSeeElement(string $element): bool
144
    {
145
        return $this->dontSee(null, $element);
3✔
146
    }
147

148
    /**
149
     * Determines if a link with the specified text is found
150
     * within the results.
151
     */
152
    public function seeLink(string $text, ?string $details = null): bool
153
    {
154
        return $this->see($text, 'a' . $details);
5✔
155
    }
156

157
    /**
158
     * Checks for an input named $field with a value of $value.
159
     */
160
    public function seeInField(string $field, string $value): bool
161
    {
162
        $result = $this->doXPath(null, 'input', ["[@value=\"{$value}\"][@name=\"{$field}\"]"]);
4✔
163

164
        return (bool) $result->length;
4✔
165
    }
166

167
    /**
168
     * Checks for checkboxes that are currently checked.
169
     */
170
    public function seeCheckboxIsChecked(string $element): bool
171
    {
172
        $result = $this->doXPath(null, 'input' . $element, [
3✔
173
            '[@type="checkbox"]',
3✔
174
            '[@checked="checked"]',
3✔
175
        ]);
3✔
176

177
        return (bool) $result->length;
3✔
178
    }
179

180
    /**
181
     * Checks to see if the XPath can be found.
182
     */
183
    public function seeXPath(string $path): bool
184
    {
185
        $xpath = new DOMXPath($this->dom);
4✔
186

187
        return (bool) $xpath->query($path)->length;
4✔
188
    }
189

190
    /**
191
     * Checks to see if the XPath can't be found.
192
     */
193
    public function dontSeeXPath(string $path): bool
194
    {
195
        return ! $this->seeXPath($path);
2✔
196
    }
197

198
    /**
199
     * Search the DOM using an XPath expression.
200
     *
201
     * @return DOMNodeList|false
202
     */
203
    protected function doXPath(?string $search, string $element, array $paths = [])
204
    {
205
        // Otherwise, grab any elements that match
206
        // the selector
207
        $selector = $this->parseSelector($element);
35✔
208

209
        $path = '';
35✔
210

211
        // By ID
212
        if (isset($selector['id'])) {
35✔
213
            $path = ($selector['tag'] === '')
11✔
214
                ? "id(\"{$selector['id']}\")"
7✔
215
                : "//{$selector['tag']}[@id=\"{$selector['id']}\"]";
4✔
216
        }
217
        // By Class
218
        elseif (isset($selector['class'])) {
24✔
219
            $path = ($selector['tag'] === '')
9✔
220
                ? "//*[@class=\"{$selector['class']}\"]"
3✔
221
                : "//{$selector['tag']}[@class=\"{$selector['class']}\"]";
7✔
222
        }
223
        // By tag only
224
        elseif ($selector['tag'] !== '') {
17✔
225
            $path = "//{$selector['tag']}";
17✔
226
        }
227

228
        if (isset($selector['attr'])) {
35✔
229
            foreach ($selector['attr'] as $key => $value) {
1✔
230
                $path .= "[@{$key}=\"{$value}\"]";
1✔
231
            }
232
        }
233

234
        // $paths might contain a number of different
235
        // ready to go xpath portions to tack on.
236
        if ($paths !== [] && is_array($paths)) {
35✔
237
            foreach ($paths as $extra) {
7✔
238
                $path .= $extra;
7✔
239
            }
240
        }
241

242
        if ($search !== null) {
35✔
243
            $path .= "[contains(., \"{$search}\")]";
21✔
244
        }
245

246
        $xpath = new DOMXPath($this->dom);
35✔
247

248
        return $xpath->query($path);
35✔
249
    }
250

251
    /**
252
     * Look for the a selector  in the passed text.
253
     *
254
     * @return array{tag: string, id: string|null, class: string|null, attr: array<string, string>|null}
255
     */
256
    public function parseSelector(string $selector)
257
    {
258
        $id    = null;
39✔
259
        $class = null;
39✔
260
        $attr  = null;
39✔
261

262
        // ID?
263
        if (str_contains($selector, '#')) {
39✔
264
            [$tag, $id] = explode('#', $selector);
12✔
265
        }
266
        // Attribute
267
        elseif (str_contains($selector, '[') && str_contains($selector, ']')) {
27✔
268
            $open  = strpos($selector, '[');
2✔
269
            $close = strpos($selector, ']');
2✔
270

271
            $tag  = substr($selector, 0, $open);
2✔
272
            $text = substr($selector, $open + 1, $close - 2);
2✔
273

274
            // We only support a single attribute currently
275
            $text = explode(',', $text);
2✔
276
            $text = trim(array_shift($text));
2✔
277

278
            [$name, $value] = explode('=', $text);
2✔
279

280
            $name  = trim($name);
2✔
281
            $value = trim($value);
2✔
282
            $attr  = [$name => trim($value, '] ')];
2✔
283
        }
284
        // Class?
285
        elseif (str_contains($selector, '.')) {
25✔
286
            [$tag, $class] = explode('.', $selector);
11✔
287
        }
288
        // Otherwise, assume the entire string is our tag
289
        else {
290
            $tag = $selector;
16✔
291
        }
292

293
        return [
39✔
294
            'tag'   => $tag,
39✔
295
            'id'    => $id,
39✔
296
            'class' => $class,
39✔
297
            'attr'  => $attr,
39✔
298
        ];
39✔
299
    }
300
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc