Skip to content

Commit 7fa5806

Browse files
authored
feat: add NoAssertSeeRule to flag assertSee assertions in tests
Signed-off-by: Kevin Ullyott <kevin.ullyott@canyongbs.com>
1 parent d02d214 commit 7fa5806

7 files changed

Lines changed: 405 additions & 0 deletions

File tree

phpstan-extension.neon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ services:
4141
class: CanyonGBS\Common\Rules\MultipleMigrationChangesWrappedInTransaction\MultipleMigrationChangesWrappedInTransactionRule
4242
tags:
4343
- phpstan.rules.rule
44+
-
45+
class: CanyonGBS\Common\Rules\NoAssertSee\NoAssertSeeRule
46+
tags:
47+
- phpstan.rules.rule
4448
-
4549
class: CanyonGBS\Common\Rules\NoBlueprintAfterGrouping\NoBlueprintAfterGroupingRule
4650
tags:
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<?php
2+
3+
/*
4+
<COPYRIGHT>
5+
6+
Copyright © 2016-2026, Canyon GBS LLC. All rights reserved.
7+
8+
Canyon GBS Common is licensed under the Elastic License 2.0. For more details,
9+
see https://github.com/canyongbs/common/blob/main/LICENSE.
10+
11+
Notice:
12+
13+
- You may not provide the software to third parties as a hosted or managed
14+
service, where the service provides users with access to any substantial set of
15+
the features or functionality of the software.
16+
- You may not move, change, disable, or circumvent the license key functionality
17+
in the software, and you may not remove or obscure any functionality in the
18+
software that is protected by the license key.
19+
- You may not alter, remove, or obscure any licensing, copyright, or other notices
20+
of the licensor in the software. Any use of the licensor’s trademarks is subject
21+
to applicable law.
22+
- Canyon GBS LLC respects the intellectual property rights of others and expects the
23+
same in return. Canyon GBS™ and Canyon GBS Common are registered trademarks of
24+
Canyon GBS LLC, and we are committed to enforcing and protecting our trademarks
25+
vigorously.
26+
- The software solution, including services, infrastructure, and code, is offered as a
27+
Software as a Service (SaaS) by Canyon GBS LLC.
28+
- Use of this software implies agreement to the license terms and conditions as stated
29+
in the Elastic License 2.0.
30+
31+
For more information or inquiries please visit our website at
32+
https://www.canyongbs.com or contact us via email at legal@canyongbs.com.
33+
34+
</COPYRIGHT>
35+
*/
36+
37+
namespace CanyonGBS\Common\Rules\NoAssertSee;
38+
39+
use Illuminate\Mail\Mailable;
40+
use Illuminate\Testing\TestComponent;
41+
use Illuminate\Testing\TestResponse;
42+
use Illuminate\Testing\TestView;
43+
use Livewire\Component;
44+
use PhpParser\Node;
45+
use PhpParser\Node\Expr\MethodCall;
46+
use PhpParser\Node\Identifier;
47+
use PHPStan\Analyser\Scope;
48+
use PHPStan\Rules\Rule;
49+
use PHPStan\Rules\RuleError;
50+
use PHPStan\Rules\RuleErrorBuilder;
51+
use PHPStan\Type\ObjectType;
52+
use PHPStan\Type\TypeCombinator;
53+
54+
/**
55+
* Flags calls to the assertSee family of test assertions (Livewire's testable, Laravel's
56+
* TestResponse/TestView/TestComponent, and Mailable). These only prove the given text appears
57+
* somewhere in the rendered output, not that it appears where the test expects, so they can pass
58+
* even when the feature under test is broken. Prefer a more precise assertion (e.g. asserting
59+
* component/view state, a scoped selector, or the specific structure produced). If you are
60+
* certain you specifically need one of these assertions, the rule can be silenced with a
61+
* specific inline ignore.
62+
*
63+
* Only flags calls made on a receiver known to actually carry these testing assertions, so
64+
* unrelated classes/namespaces that happen to define a same-named method are left alone.
65+
*
66+
* @implements Rule<MethodCall>
67+
*/
68+
class NoAssertSeeRule implements Rule
69+
{
70+
public const string ERROR_MESSAGE = 'Avoid assertSee() and its alternatives: they only prove the text appears somewhere in the rendered output, not that it appears where the test actually expects it, so they can pass even when the feature under test is broken. Prefer a more precise assertion (e.g. asserting component/view state, a scoped selector, or the specific structure produced). If you are certain you specifically need this assertion, add an inline ignore for this rule (// @phpstan-ignore Common.noAssertSee).';
71+
72+
/**
73+
* @var list<string>
74+
*/
75+
private const array BANNED_METHODS = [
76+
'assertSee',
77+
'assertSeeText',
78+
'assertSeeHtml',
79+
'assertSeeHtmlInOrder',
80+
'assertSeeInOrder',
81+
'assertSeeTextInOrder',
82+
'assertDontSee',
83+
'assertDontSeeText',
84+
'assertDontSeeHtml',
85+
'assertSeeIn',
86+
'assertDontSeeIn',
87+
'assertSeeInHtml',
88+
'assertDontSeeInHtml',
89+
'assertSeeInText',
90+
'assertDontSeeInText',
91+
'assertSeeInOrderInHtml',
92+
'assertSeeInOrderInText',
93+
];
94+
95+
/**
96+
* Classes that actually carry the assertSee-family assertions we ban. `Laravel\Dusk\Browser`
97+
* is referenced as a plain string since Dusk is not a dependency of every consuming app; the
98+
* class does not need to exist for an ObjectType comparison against it to work.
99+
*
100+
* @var list<string>
101+
*/
102+
private const array ALLOWED_RECEIVER_CLASSES = [
103+
TestResponse::class,
104+
TestView::class,
105+
TestComponent::class,
106+
Mailable::class,
107+
Component::class,
108+
'Laravel\Dusk\Browser',
109+
];
110+
111+
/**
112+
* @return class-string<Node>
113+
*/
114+
public function getNodeType(): string
115+
{
116+
return MethodCall::class;
117+
}
118+
119+
/**
120+
* @param MethodCall $node
121+
*
122+
* @return array<RuleError>
123+
*/
124+
public function processNode(Node $node, Scope $scope): array
125+
{
126+
if (! $node->name instanceof Identifier) {
127+
return [];
128+
}
129+
130+
if (! in_array($node->name->toString(), self::BANNED_METHODS, true)) {
131+
return [];
132+
}
133+
134+
if (! $this->isKnownAssertionReceiver($node, $scope)) {
135+
return [];
136+
}
137+
138+
return [
139+
RuleErrorBuilder::message(self::ERROR_MESSAGE)
140+
->identifier('Common.noAssertSee')
141+
->build(),
142+
];
143+
}
144+
145+
private function isKnownAssertionReceiver(MethodCall $node, Scope $scope): bool
146+
{
147+
$calledOnType = $scope->getType($node->var);
148+
149+
$allowedType = TypeCombinator::union(
150+
...array_map(static fn (string $class): ObjectType => new ObjectType($class), self::ALLOWED_RECEIVER_CLASSES)
151+
);
152+
153+
return $allowedType->isSuperTypeOf($calledOnType)->yes();
154+
}
155+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
parameters:
2+
level: 6
3+
ignoreErrors:
4+
- identifier: missingType.generics
5+
6+
services:
7+
-
8+
class: CanyonGBS\Common\Rules\NoAssertSee\NoAssertSeeRule
9+
tags:
10+
- phpstan.rules.rule
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
/*
4+
<COPYRIGHT>
5+
6+
Copyright © 2016-2026, Canyon GBS LLC. All rights reserved.
7+
8+
Canyon GBS Common is licensed under the Elastic License 2.0. For more details,
9+
see https://github.com/canyongbs/common/blob/main/LICENSE.
10+
11+
Notice:
12+
13+
- You may not provide the software to third parties as a hosted or managed
14+
service, where the service provides users with access to any substantial set of
15+
the features or functionality of the software.
16+
- You may not move, change, disable, or circumvent the license key functionality
17+
in the software, and you may not remove or obscure any functionality in the
18+
software that is protected by the license key.
19+
- You may not alter, remove, or obscure any licensing, copyright, or other notices
20+
of the licensor in the software. Any use of the licensor’s trademarks is subject
21+
to applicable law.
22+
- Canyon GBS LLC respects the intellectual property rights of others and expects the
23+
same in return. Canyon GBS™ and Canyon GBS Common are registered trademarks of
24+
Canyon GBS LLC, and we are committed to enforcing and protecting our trademarks
25+
vigorously.
26+
- The software solution, including services, infrastructure, and code, is offered as a
27+
Software as a Service (SaaS) by Canyon GBS LLC.
28+
- Use of this software implies agreement to the license terms and conditions as stated
29+
in the Elastic License 2.0.
30+
31+
For more information or inquiries please visit our website at
32+
https://www.canyongbs.com or contact us via email at legal@canyongbs.com.
33+
34+
</COPYRIGHT>
35+
*/
36+
37+
class UnrelatedClassWithAssertSeeMethod
38+
{
39+
public function assertSee(string $value): void
40+
{
41+
// Not a test assertion, just a same-named method on an unrelated class.
42+
}
43+
}
44+
45+
class AssertSeeAllowedFixtureTest
46+
{
47+
public function test(\Illuminate\Testing\TestResponse $response, UnrelatedClassWithAssertSeeMethod $unrelated): void
48+
{
49+
$response->assertOk();
50+
$response->assertViewHas('posts');
51+
52+
$unrelated->assertSee('foo');
53+
}
54+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
/*
4+
<COPYRIGHT>
5+
6+
Copyright © 2016-2026, Canyon GBS LLC. All rights reserved.
7+
8+
Canyon GBS Common is licensed under the Elastic License 2.0. For more details,
9+
see https://github.com/canyongbs/common/blob/main/LICENSE.
10+
11+
Notice:
12+
13+
- You may not provide the software to third parties as a hosted or managed
14+
service, where the service provides users with access to any substantial set of
15+
the features or functionality of the software.
16+
- You may not move, change, disable, or circumvent the license key functionality
17+
in the software, and you may not remove or obscure any functionality in the
18+
software that is protected by the license key.
19+
- You may not alter, remove, or obscure any licensing, copyright, or other notices
20+
of the licensor in the software. Any use of the licensor’s trademarks is subject
21+
to applicable law.
22+
- Canyon GBS LLC respects the intellectual property rights of others and expects the
23+
same in return. Canyon GBS™ and Canyon GBS Common are registered trademarks of
24+
Canyon GBS LLC, and we are committed to enforcing and protecting our trademarks
25+
vigorously.
26+
- The software solution, including services, infrastructure, and code, is offered as a
27+
Software as a Service (SaaS) by Canyon GBS LLC.
28+
- Use of this software implies agreement to the license terms and conditions as stated
29+
in the Elastic License 2.0.
30+
31+
For more information or inquiries please visit our website at
32+
https://www.canyongbs.com or contact us via email at legal@canyongbs.com.
33+
34+
</COPYRIGHT>
35+
*/
36+
37+
class AssertSeeFixtureTest
38+
{
39+
public function test(
40+
\Illuminate\Testing\TestResponse $response,
41+
\Illuminate\Mail\Mailable $mailable,
42+
\Livewire\Component $component,
43+
): void {
44+
$response->assertSee('foo');
45+
$response->assertSeeText('foo');
46+
$response->assertSeeHtml('<p>foo</p>');
47+
$response->assertSeeHtmlInOrder(['<p>foo</p>', '<p>bar</p>']);
48+
$response->assertSeeInOrder(['foo', 'bar']);
49+
$response->assertSeeTextInOrder(['foo', 'bar']);
50+
$response->assertDontSee('foo');
51+
$response->assertDontSeeText('foo');
52+
$response->assertDontSeeHtml('<p>foo</p>');
53+
54+
$mailable->assertSeeInHtml('foo');
55+
$mailable->assertDontSeeInHtml('foo');
56+
$mailable->assertSeeInText('foo');
57+
$mailable->assertDontSeeInText('foo');
58+
$mailable->assertSeeInOrderInHtml(['foo', 'bar']);
59+
$mailable->assertSeeInOrderInText(['foo', 'bar']);
60+
61+
$component->assertSeeIn('@foo', 'bar');
62+
$component->assertDontSeeIn('@foo', 'bar');
63+
}
64+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
/*
4+
<COPYRIGHT>
5+
6+
Copyright © 2016-2026, Canyon GBS LLC. All rights reserved.
7+
8+
Canyon GBS Common is licensed under the Elastic License 2.0. For more details,
9+
see https://github.com/canyongbs/common/blob/main/LICENSE.
10+
11+
Notice:
12+
13+
- You may not provide the software to third parties as a hosted or managed
14+
service, where the service provides users with access to any substantial set of
15+
the features or functionality of the software.
16+
- You may not move, change, disable, or circumvent the license key functionality
17+
in the software, and you may not remove or obscure any functionality in the
18+
software that is protected by the license key.
19+
- You may not alter, remove, or obscure any licensing, copyright, or other notices
20+
of the licensor in the software. Any use of the licensor’s trademarks is subject
21+
to applicable law.
22+
- Canyon GBS LLC respects the intellectual property rights of others and expects the
23+
same in return. Canyon GBS™ and Canyon GBS Common are registered trademarks of
24+
Canyon GBS LLC, and we are committed to enforcing and protecting our trademarks
25+
vigorously.
26+
- The software solution, including services, infrastructure, and code, is offered as a
27+
Software as a Service (SaaS) by Canyon GBS LLC.
28+
- Use of this software implies agreement to the license terms and conditions as stated
29+
in the Elastic License 2.0.
30+
31+
For more information or inquiries please visit our website at
32+
https://www.canyongbs.com or contact us via email at legal@canyongbs.com.
33+
34+
</COPYRIGHT>
35+
*/
36+
37+
class AssertSeeIgnoredFixtureTest
38+
{
39+
public function test(\Illuminate\Testing\TestResponse $response): void
40+
{
41+
// @phpstan-ignore Common.noAssertSee
42+
$response->assertSee('foo');
43+
}
44+
}

0 commit comments

Comments
 (0)