Skip to content

Commit 82a056c

Browse files
authored
Add late output error test (#1719)
1 parent 17a4107 commit 82a056c

File tree

11 files changed

+224
-89
lines changed

11 files changed

+224
-89
lines changed

demos/_unit-test/callback_2.php renamed to demos/_unit-test/callback-nested.php

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
use Atk4\Ui\Button;
1212
use Atk4\Ui\Crud;
13+
use Atk4\Ui\Exception;
1314
use Atk4\Ui\Header;
1415
use Atk4\Ui\Loader;
1516
use Atk4\Ui\UserAction\ExecutorFactory;
@@ -25,26 +26,39 @@
2526
);
2627

2728
$loader = Loader::addTo($app);
29+
$loader->cb->setUrlTrigger('trigger_main_loader');
2830
$loader->loadEvent = false;
2931

30-
$loader->set(function ($p) use ($m) {
31-
$loader_1 = Loader::addTo($p);
32-
$loader_1->loadEvent = false;
33-
32+
$loader->set(function (Loader $p) use ($m) {
3433
Header::addTo($p, ['Loader-1', 'size' => 4]);
3534

36-
$loader_1->set(function ($p) use ($m) {
35+
if (isset($_GET['err_main_loader'])) {
36+
throw new Exception('Exception from Main Loader');
37+
}
38+
39+
$loaderSub = Loader::addTo($p);
40+
$loaderSub->cb->setUrlTrigger('trigger_sub_loader');
41+
$loaderSub->loadEvent = false;
42+
43+
$loaderSub->set(function (Loader $p) use ($m) {
3744
Header::addTo($p, ['Loader-2', 'size' => 4]);
38-
$loader_3 = Loader::addTo($p);
3945

40-
$loader_3->set(function ($p) use ($m) {
46+
if (isset($_GET['err_sub_loader'])) {
47+
throw new Exception('Exception from Sub Loader');
48+
} elseif (isset($_GET['err_sub_loader2'])) {
49+
throw new \Error('Exception II from Sub Loader');
50+
}
51+
52+
$loaderSubSub = Loader::addTo($p);
53+
54+
$loaderSubSub->set(function (Loader $p) use ($m) {
4155
Header::addTo($p, ['Loader-3', 'size' => 4]);
4256

4357
$c = Crud::addTo($p, ['ipp' => 4]);
4458
$c->setModel($m, [$m->fieldName()->name]);
4559
});
4660
});
47-
\Atk4\Ui\Button::addTo($p, ['Load2'])->js('click', $loader_1->jsLoad());
61+
\Atk4\Ui\Button::addTo($p, ['Load2'])->js('click', $loaderSub->jsLoad());
4862
});
4963

5064
\Atk4\Ui\Button::addTo($app, ['Load1'])->js('click', $loader->jsLoad());

demos/_unit-test/exception.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
$modal->name = 'm_test';
1919

2020
$modal->set(function () {
21-
throw new \Exception('TEST!');
21+
throw new \Exception('Test throw exception!');
2222
});
2323

2424
$button = \Atk4\Ui\Button::addTo($app, ['Test modal exception']);
@@ -28,7 +28,7 @@
2828
$modal2 = \Atk4\Ui\Modal::addTo($app, ['cb' => $cb1]);
2929

3030
$modal2->set(function () {
31-
trigger_error('error triggered');
31+
trigger_error('Test trigger error!');
3232
});
3333

3434
$button2 = \Atk4\Ui\Button::addTo($app, ['Test modal error']);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Atk4\Ui\Demos;
6+
7+
use Atk4\Ui\CallbackLater;
8+
9+
/** @var \Atk4\Ui\App $app */
10+
require_once __DIR__ . '/../init-app.php';
11+
12+
$cb = CallbackLater::addTo($app);
13+
$cb->setUrlTrigger('err_headers_already_sent');
14+
15+
$modal = \Atk4\Ui\Modal::addTo($app, ['cb' => $cb]);
16+
$modal->set(function () {
17+
header('x-unmanaged-header: test');
18+
flush();
19+
});
20+
21+
$cb2 = CallbackLater::addTo($app);
22+
$cb2->setUrlTrigger('err_unexpected_output_detected');
23+
24+
$modal2 = \Atk4\Ui\Modal::addTo($app, ['cb' => $cb2]);
25+
$modal2->set(function () {
26+
// unexpected output can be detected only when output buffering is enabled and not flushed
27+
if (ob_get_level() === 0) {
28+
ob_start();
29+
}
30+
echo 'unmanaged output';
31+
});
32+
33+
$button = \Atk4\Ui\Button::addTo($app, ['Test LateOutputError: Headers already sent']);
34+
$button->on('click', $modal->show());
35+
36+
$button2 = \Atk4\Ui\Button::addTo($app, ['Test LateOutputError: Unexpected output detected']);
37+
$button2->on('click', $modal2->show());

phpstan.neon.dist

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -700,9 +700,6 @@ parameters:
700700
-
701701
path: 'src/View.php'
702702
message: '~^If condition is always true\.$~'
703-
-
704-
path: 'tests/DemosTest.php'
705-
message: '~^Else branch is unreachable because ternary operator condition is always true\.$~'
706703

707704
# TODO these rules are generated, this ignores should be fixed in the code
708705
# for level = 5
@@ -2038,27 +2035,6 @@ parameters:
20382035
-
20392036
path: 'src/Wizard.php'
20402037
message: '~^Method Atk4\\Ui\\Wizard::addFinish\(\) has no return type specified\.$~'
2041-
-
2042-
path: 'tests/CallbackTest.php'
2043-
message: '~^Property Atk4\\Ui\\Tests\\CallbackTest::\$var has no type specified\.$~'
2044-
-
2045-
path: 'tests/CallbackTest.php'
2046-
message: '~^Method Atk4\\Ui\\Tests\\CallbackTest::callPull230\(\) has no return type specified\.$~'
2047-
-
2048-
path: 'tests/DemosHttpTest.php'
2049-
message: '~^Property Atk4\\Ui\\Tests\\DemosHttpTest::\$_process has no type specified\.$~'
2050-
-
2051-
path: 'tests/DemosHttpTest.php'
2052-
message: '~^Property Atk4\\Ui\\Tests\\DemosHttpTest::\$_processSessionDir has no type specified\.$~'
2053-
-
2054-
path: 'tests/DemosHttpTest.php'
2055-
message: '~^Property Atk4\\Ui\\Tests\\DemosHttpTest::\$host has no type specified\.$~'
2056-
-
2057-
path: 'tests/DemosHttpTest.php'
2058-
message: '~^Property Atk4\\Ui\\Tests\\DemosHttpTest::\$port has no type specified\.$~'
2059-
-
2060-
path: 'tests/DemosTest.php'
2061-
message: '~^Strict comparison using \!\=\= between bool and null will always evaluate to true\.$~'
20622038
-
20632039
path: 'tests/PaginatorTest.php'
20642040
message: '~^Property Atk4\\Ui\\Tests\\PaginatorTest::\$p has no type specified\.$~'

src/App.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ class App
123123

124124
/** @var string[] Extra HTTP headers to send on exit. */
125125
protected $response_headers = [
126+
self::HEADER_STATUS_CODE => '200',
126127
'cache-control' => 'no-store', // disable caching by default
127128
];
128129

@@ -216,6 +217,7 @@ static function (int $severity, string $msg, string $file, int $line): bool {
216217
},
217218
\E_ALL
218219
);
220+
$this->outputResponseUnsafe('', [self::HEADER_STATUS_CODE => 500]);
219221
}
220222

221223
// Always run app on shutdown

src/Loader.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class Loader extends View
2121
public $shim;
2222

2323
/**
24-
* Specify which event will cause Loader to begen fetching it's actual data. In some cases
24+
* Specify which event will cause Loader to begin fetching it's actual data. In some cases
2525
* you would want to wait. You can set a custom JavaScript event name then trigger() it.
2626
*
2727
* Default value is `true` which means loading will take place as soon as possible. Setting this
@@ -35,7 +35,7 @@ class Loader extends View
3535
public $ui = 'ui segment';
3636

3737
/** @var Callback for triggering */
38-
protected $cb;
38+
public $cb;
3939

4040
/** @var array Url arguments. */
4141
public $urlArgs = [];

tests-behat/callback.feature

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Feature: Callback
1010
Then I should not see "TestName"
1111

1212
Scenario:
13-
Given I am on "_unit-test/callback_2.php"
13+
Given I am on "_unit-test/callback-nested.php"
1414
Then I press button "Load1"
1515
Then I should see "Loader-1"
1616
Then I press button "Load2"

tests/CallbackTest.php

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -225,23 +225,21 @@ public function testVirtualPageCustomTrigger(): void
225225
$this->assertSame(25, $var);
226226
}
227227

228-
public $var;
229-
230-
public function callPull230()
231-
{
232-
$this->var = 26;
233-
}
228+
/** @var int */
229+
private $varPull230;
234230

235231
public function testPull230(): void
236232
{
237233
$vp = VirtualPage::addTo($this->app);
238234

239235
$this->simulateCallbackTriggering($vp);
240236

241-
$vp->set(\Closure::fromCallable([$this, 'callPull230']));
237+
$vp->set(function () {
238+
$this->varPull230 = 26;
239+
});
242240

243241
$this->expectOutputRegex('/^..DOCTYPE/');
244242
$this->app->run();
245-
$this->assertSame(26, $this->var);
243+
$this->assertSame(26, $this->varPull230);
246244
}
247245
}

tests/DemosHttpTest.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Atk4\Ui\Tests;
66

7+
use Atk4\Ui\Callback;
78
use GuzzleHttp\Client;
89
use Symfony\Component\Process\Process;
910

@@ -14,7 +15,9 @@
1415
*/
1516
class DemosHttpTest extends DemosTest
1617
{
18+
/** @var Process|null */
1719
private static $_process;
20+
/** @var string|null */
1821
private static $_processSessionDir;
1922

2023
/** @var bool set the app->call_exit in demo */
@@ -23,7 +26,9 @@ class DemosHttpTest extends DemosTest
2326
/** @var bool set the app->catch_exceptions in demo */
2427
protected $app_catch_exceptions = true;
2528

29+
/** @var string */
2630
protected $host = '127.0.0.1';
31+
/** @var int */
2732
protected $port = 9687;
2833

2934
public static function tearDownAfterClass(): void
@@ -90,4 +95,33 @@ protected function getPathWithAppVars(string $path): string
9095

9196
return parent::getPathWithAppVars($path);
9297
}
98+
99+
private function getLateOutputErrorPath(string $trigger): string
100+
{
101+
return '_unit-test/late-output-error.php?' . Callback::URL_QUERY_TRIGGER_PREFIX . $trigger . '=ajax&'
102+
. Callback::URL_QUERY_TARGET . '=' . $trigger . '&__atk_json=1';
103+
}
104+
105+
public function testDemoLateOutputErrorHeadersAlreadySent(): void
106+
{
107+
$response = $this->getResponseFromRequest5xx($this->getLateOutputErrorPath('err_headers_already_sent'));
108+
109+
$this->assertSame(500, $response->getStatusCode());
110+
$this->assertSame(
111+
"\n" . '!! FATAL UI ERROR: Headers already sent, more headers cannot be set at this stage !!' . "\n",
112+
$response->getBody()->getContents()
113+
);
114+
}
115+
116+
public function testDemoLateOutputErrorUnexpectedOutputDetected(): void
117+
{
118+
$response = $this->getResponseFromRequest5xx($this->getLateOutputErrorPath('err_unexpected_output_detected'));
119+
120+
$this->assertSame(500, $response->getStatusCode());
121+
$this->assertSame(
122+
'unmanaged output'
123+
. "\n" . '!! FATAL UI ERROR: Unexpected output detected !!' . "\n",
124+
$response->getBody()->getContents()
125+
);
126+
}
93127
}

0 commit comments

Comments
 (0)