Skip to content

Commit 76c561b

Browse files
committed
feat(history)!: add the Status column styles and uncaptured pill, hide IP and Mail up to 1600 px, and drop HistorySummary::$statusCodeFilter.
1 parent 4499689 commit 76c561b

10 files changed

Lines changed: 116 additions & 150 deletions

File tree

‎e2e/history-layout.spec.js‎

Lines changed: 75 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ function columns(query, mail) {
2020
? [["Mail", "yii-debug-col-num yii-debug-col-mail", "mailCount"]]
2121
: []),
2222
["Method", "", "method"],
23+
["Status", "yii-debug-col-status", "statusCode"],
2324
["AJAX", "", "ajax"],
2425
["URL", "", "url"],
2526
];
@@ -52,6 +53,7 @@ function rows(definitions, count) {
5253
Query: "3",
5354
Mail: "0",
5455
Method: "COMMAND",
56+
Status: '<span class="yii-debug-badge yii-debug-status-2xx">200</span>',
5557
AJAX: "Yes",
5658
URL: `<span class="yii-debug-url-cell" title="${url}">${url}</span>`,
5759
};
@@ -65,19 +67,32 @@ function rows(definitions, count) {
6567
}).join("");
6668
}
6769

70+
// IP and Mail hide up to 1600 px; the explicit widths pin both sides of that breakpoint on the desktop project.
6871
for (const [adapter, query, mail] of [
6972
["Yii2", true, true],
7073
["Yii2 without Mail", true, false],
7174
["Yii2 without Database", false, true],
7275
["Yii3", false, false],
7376
]) {
7477
for (const theme of ["light", "dark"]) {
75-
test(`${adapter} History keeps column widths stable in ${theme} theme`, async ({
76-
page,
77-
}) => {
78-
const definitions = columns(query, mail);
78+
for (const width of [null, 1600, 1680]) {
79+
const at = width ? ` at ${width} px` : "";
80+
81+
test(`${adapter} History keeps column widths stable in ${theme} theme${at}`, async ({
82+
page,
83+
}, testInfo) => {
84+
test.skip(
85+
width !== null && testInfo.project.name !== "desktop-1440",
86+
"Explicit breakpoint widths run on the desktop project only.",
87+
);
88+
89+
if (width) {
90+
await page.setViewportSize({ width, height: 900 });
91+
}
92+
93+
const definitions = columns(query, mail);
7994

80-
await page.setContent(`
95+
await page.setContent(`
8196
<!doctype html>
8297
<html lang="en" data-yii-debug-theme="${theme}">
8398
<body class="yii-debug">
@@ -99,50 +114,61 @@ for (const [adapter, query, mail] of [
99114
</body>
100115
</html>
101116
`);
102-
await page.addStyleTag({ path: stylesheet.pathname });
103-
await waitForStableUI(page);
104-
105-
const headers = page.locator("thead th");
106-
const widths = () =>
107-
headers.evaluateAll((cells) =>
108-
cells.map((cell) => cell.getBoundingClientRect().width),
109-
);
110-
const baseline = await widths();
111-
const viewport = page.viewportSize();
112-
113-
expect(baseline.slice(0, 5)).toEqual([36, 276, 80, 100, 100]);
114-
expect(baseline[5]).toBe(viewport.width <= 1366 ? 0 : 112);
115-
expect(baseline.slice(-3, -1)).toEqual([128, 84]);
116-
117-
// Replacing the result rows models server-rendered filtering and paging.
118-
for (const count of [50, 1, 0, 10]) {
119-
await page.locator("tbody").evaluate(
120-
(body, html) => {
121-
body.innerHTML = html;
122-
},
123-
count
124-
? rows(definitions, count)
125-
: `<tr><td colspan="${definitions.length}">No results found.</td></tr>`,
126-
);
127-
expect(
128-
await widths(),
129-
`${count} results must preserve every column`,
130-
).toEqual(baseline);
131-
132-
const overflow = await page.evaluate(() => ({
133-
viewport: document.documentElement.clientWidth,
134-
document: document.documentElement.scrollWidth,
135-
previews: [...document.querySelectorAll(".yii-debug-url-cell")].every(
136-
(element) =>
137-
element.getBoundingClientRect().right <=
138-
element.parentElement.getBoundingClientRect().right,
139-
),
140-
}));
141-
142-
expect(overflow.document).toBeLessThanOrEqual(overflow.viewport);
143-
expect(overflow.previews).toBe(true);
144-
}
145-
});
117+
await page.addStyleTag({ path: stylesheet.pathname });
118+
await waitForStableUI(page);
119+
120+
const headers = page.locator("thead th");
121+
const widths = () =>
122+
headers.evaluateAll((cells) =>
123+
cells.map((cell) => cell.getBoundingClientRect().width),
124+
);
125+
const baseline = await widths();
126+
const viewport = page.viewportSize();
127+
128+
expect(baseline.slice(0, 5)).toEqual([36, 276, 80, 100, 100]);
129+
const hidden = viewport.width <= 1600;
130+
131+
expect(baseline[5], "IP width").toBe(hidden ? 0 : 112);
132+
if (mail) {
133+
expect(
134+
baseline[definitions.findIndex(([label]) => label === "Mail")],
135+
"Mail width",
136+
).toBe(hidden ? 0 : 56);
137+
}
138+
expect(baseline.slice(-4, -1)).toEqual([128, 72, 84]);
139+
140+
// Replacing the result rows models server-rendered filtering and paging.
141+
for (const count of [50, 1, 0, 10]) {
142+
await page.locator("tbody").evaluate(
143+
(body, html) => {
144+
body.innerHTML = html;
145+
},
146+
count
147+
? rows(definitions, count)
148+
: `<tr><td colspan="${definitions.length}">No results found.</td></tr>`,
149+
);
150+
expect(
151+
await widths(),
152+
`${count} results must preserve every column`,
153+
).toEqual(baseline);
154+
155+
const overflow = await page.evaluate(() => ({
156+
viewport: document.documentElement.clientWidth,
157+
document: document.documentElement.scrollWidth,
158+
previews: [
159+
...document.querySelectorAll(".yii-debug-url-cell"),
160+
].every(
161+
(element) =>
162+
element.getBoundingClientRect().right <=
163+
element.parentElement.getBoundingClientRect().right,
164+
),
165+
}));
166+
167+
expect(overflow.document).toBeLessThanOrEqual(overflow.viewport);
168+
expect(overflow.previews).toBe(true);
169+
}
170+
});
171+
}
146172
}
147173
}
148174

‎resources/assets/dist/css/debug.min.css‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎resources/src/styles/main.css‎

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3214,13 +3214,14 @@
32143214
* no wrapping keep every capture scannable at a glance. Fixed instrument
32153215
* widths keep filters, pagination, and empty results from moving columns;
32163216
* URL alone takes the remaining space. Both adapters share the first six
3217-
* and last three columns, with optional counters between IP and Method.
3217+
* and last four columns (Method, Status, AJAX, URL), with optional
3218+
* counters between IP and Method.
32183219
*/
32193220
.yii-debug-grid-history .yii-debug-table {
32203221
--yii-debug-history-query-width: 0px;
32213222
--yii-debug-history-mail-width: 0px;
32223223
--yii-debug-history-fixed-width: calc(
3223-
916px + var(--yii-debug-history-query-width) + var(--yii-debug-history-mail-width)
3224+
988px + var(--yii-debug-history-query-width) + var(--yii-debug-history-mail-width)
32243225
);
32253226

32263227
min-width: calc(var(--yii-debug-history-fixed-width) + 180px);
@@ -3264,10 +3265,14 @@
32643265
width: 56px;
32653266
}
32663267

3267-
> th:nth-last-child(3) {
3268+
> th:nth-last-child(4) {
32683269
width: 128px;
32693270
}
32703271

3272+
> th.yii-debug-col-status {
3273+
width: 72px;
3274+
}
3275+
32713276
> th:nth-last-child(2) {
32723277
width: 84px;
32733278
}
@@ -3309,14 +3314,15 @@
33093314

33103315
/**
33113316
* IP and MAIL are low-signal on laptop viewports (IP reads `::1` for
3312-
* every local capture, MAIL is almost always `0`) — hiding them at mid
3313-
* widths leaves more room for the URL. Narrow screens retain horizontal
3314-
* scrolling inside the table wrap. Both columns return on wide desktops.
3317+
* every local capture, MAIL is almost always `0`) — hiding them up to
3318+
* 1600 px leaves the URL readable beside the Status column. Narrow
3319+
* screens retain horizontal scrolling inside the table wrap. Both
3320+
* columns return above 1600 px.
33153321
*/
3316-
@media (width <= 1366px) {
3322+
@media (width <= 1600px) {
33173323
.yii-debug-grid-history .yii-debug-table {
33183324
--yii-debug-history-fixed-width: calc(
3319-
804px + var(--yii-debug-history-query-width)
3325+
876px + var(--yii-debug-history-query-width)
33203326
);
33213327
}
33223328

‎src/Comparison/SummaryMetricComparison.php‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public static function between(RequestSummary $baseline, RequestSummary $target)
4646
{
4747
return [
4848
self::textMetric(
49-
'Status',
49+
ViewMessage::STATUS->value,
5050
self::status($baseline->statusCode),
5151
self::status($target->statusCode),
5252
),

‎src/View/History/HistoryCellRenderer.php‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,15 +161,13 @@ public static function renderSqlCountCell(
161161
}
162162

163163
/**
164-
* Renders the status-code badge cell; an uncaptured (`0`) code displays as a successful `200`.
164+
* Renders the status-code badge cell; an uncaptured (`0`) code renders the muted dash pill, never a `200`.
165165
*/
166166
public static function renderStatusCell(HistoryRow $row): string
167167
{
168-
$statusCode = $row->statusCode === 0 ? 200 : $row->statusCode;
169-
170168
return Span::tag()
171-
->class(Css::BADGE . ' ' . Css::status(Vocabulary::statusClass($statusCode)))
172-
->content((string) $statusCode)
169+
->class(Css::BADGE . ' ' . Css::status(Vocabulary::statusClass($row->statusCode)))
170+
->content($row->statusCode > 0 ? (string) $row->statusCode : '–')
173171
->render();
174172
}
175173

‎src/View/History/HistorySummary.php‎

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use PHPForge\Debug\Storage\RequestSummary;
88

99
use function count;
10-
use function ksort;
1110

1211
/**
1312
* Typed aggregate view-model for the History index summary header.
@@ -25,13 +24,6 @@ public function __construct(
2524
* @var list<HistoryStatusBucket>
2625
*/
2726
public array $statusBuckets,
28-
/**
29-
* Unique status-code map (`code => code`) consumed by the grid's status filter dropdown; `null` when the
30-
* manifest has no captured statuses (the dropdown collapses to a text input).
31-
*
32-
* @var array<int|string, int|string>|null
33-
*/
34-
public array|null $statusCodeFilter,
3527
) {}
3628

3729
/**
@@ -45,15 +37,10 @@ public static function fromManifest(array $manifest): self
4537

4638
$buckets = ['2xx' => 0, '3xx' => 0, '4xx' => 0, '5xx' => 0];
4739
$sample = [];
48-
$codes = [];
4940

5041
foreach ($manifest as $entry) {
5142
$statusCode = $entry->statusCode;
5243

53-
if ($statusCode > 0) {
54-
$codes[$statusCode] = $statusCode;
55-
}
56-
5744
if ($statusCode < 200 || $statusCode >= 600) {
5845
continue;
5946
}
@@ -84,12 +71,6 @@ public static function fromManifest(array $manifest): self
8471
);
8572
}
8673

87-
ksort($codes);
88-
89-
return new self(
90-
totalRequests: $totalRequests,
91-
statusBuckets: $statusBuckets,
92-
statusCodeFilter: $codes === [] ? null : $codes,
93-
);
74+
return new self(totalRequests: $totalRequests, statusBuckets: $statusBuckets);
9475
}
9576
}

‎src/View/ViewMessage.php‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,11 @@ enum ViewMessage: string
129129
*/
130130
case QUERY = 'Query';
131131

132+
/**
133+
* Header of the HTTP status-code column, also used as its filter label and as a comparison metric label.
134+
*/
135+
case STATUS = 'Status';
136+
132137
/**
133138
* Label of the target selector in the comparison form.
134139
*/

‎tests/View/History/HistoryCellRendererTest.php‎

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -338,15 +338,6 @@ public function testRenderSqlCountCellSingularizesSingleExcessiveCaller(): void
338338
);
339339
}
340340

341-
public function testRenderStatusCellMapsCommandWithZeroToSuccess(): void
342-
{
343-
self::assertSame(
344-
'<span class="yii-debug-badge yii-debug-status-2xx">200</span>',
345-
HistoryCellRenderer::renderStatusCell(self::row(['method' => 'COMMAND', 'statusCode' => 0])),
346-
"COMMAND with status '0' must display as status '200'.",
347-
);
348-
}
349-
350341
public function testRenderStatusCellMapsRangeToStatusClass(): void
351342
{
352343
self::assertSame(
@@ -379,6 +370,17 @@ public function testRenderStatusCellMapsRangeToStatusClass(): void
379370
);
380371
}
381372

373+
public function testRenderStatusCellRendersUncapturedPillForZeroCode(): void
374+
{
375+
self::assertSame(
376+
<<<HTML
377+
<span class="yii-debug-badge yii-debug-status-none">–</span>
378+
HTML,
379+
HistoryCellRenderer::renderStatusCell(self::row(['statusCode' => 0])),
380+
'Uncaptured code must render a muted dash, not `200`.',
381+
);
382+
}
383+
382384
public function testRenderSummaryEchoesBucketPills(): void
383385
{
384386
$summary = new HistorySummary(
@@ -387,7 +389,6 @@ public function testRenderSummaryEchoesBucketPills(): void
387389
new HistoryStatusBucket(label: '2xx', count: 4, sampleCode: 200, variant: '2xx'),
388390
new HistoryStatusBucket(label: '4xx', count: 1, sampleCode: 404, variant: '4xx'),
389391
],
390-
statusCodeFilter: null,
391392
);
392393
$html = HistoryCellRenderer::renderSummary(
393394
$summary,
@@ -411,7 +412,7 @@ public function testRenderSummaryEchoesBucketPills(): void
411412

412413
public function testRenderSummaryReturnsEmptyWhenNoRequestsCaptured(): void
413414
{
414-
$summary = new HistorySummary(totalRequests: 0, statusBuckets: [], statusCodeFilter: null);
415+
$summary = new HistorySummary(totalRequests: 0, statusBuckets: []);
415416

416417
self::assertSame(
417418
'',
@@ -422,7 +423,7 @@ public function testRenderSummaryReturnsEmptyWhenNoRequestsCaptured(): void
422423

423424
public function testRenderSummaryUsesSingularLabelForOneRequest(): void
424425
{
425-
$summary = new HistorySummary(totalRequests: 1, statusBuckets: [], statusCodeFilter: null);
426+
$summary = new HistorySummary(totalRequests: 1, statusBuckets: []);
426427

427428
self::assertSame(
428429
<<<HTML

0 commit comments

Comments
 (0)