diff --git a/CHANGELOG.md b/CHANGELOG.md index e52110b..7a96e59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,7 @@ All notable changes to `phalcon/debugbar` are documented here. The format is bas - Pin `pie.phar` to a release with a sha256 check, build the PHP 8.1 Docker image, declare `ext-mbstring`, and list the Phalcon version that each feature needs. [#104](https://github.com/phalcon/debugbar/issues/104) - Name the collapse toggle and the tabs for screen readers, close the open panel or the history with Escape, and fix two `Phalcon\Debug` docblocks. [#106](https://github.com/phalcon/debugbar/issues/106) - Open the ADR debug page only for an exception logged at `error` level or higher, and show a lower-level exception only in the `exceptions` panel. [#107](https://github.com/phalcon/debugbar/issues/107) +- Share one renderer and one CSS block for the exceptions and logs panels, and show an exception with no trace as a plain line. [#108](https://github.com/phalcon/debugbar/issues/108) - Test HTML writes, the collapse state and `localStorage` errors in the JS tests, and keep every listener in the fake DOM. [#103](https://github.com/phalcon/debugbar/issues/103) ## [0.4.0](https://github.com/phalcon/debugbar/releases/tag/v0.4.0) (2026-07-14) diff --git a/resources/assets/debugbar.css b/resources/assets/debugbar.css index 3478e60..8020879 100644 --- a/resources/assets/debugbar.css +++ b/resources/assets/debugbar.css @@ -440,49 +440,33 @@ font-style: italic; } -#phalcon-debugbar .phalcon-debugbar-exception { +#phalcon-debugbar .phalcon-debugbar-exception, +#phalcon-debugbar .phalcon-debugbar-log { padding: 3px 0; border-bottom: 1px solid #23233440; } -#phalcon-debugbar .phalcon-debugbar-exception-summary { +#phalcon-debugbar .phalcon-debugbar-exception-summary, +#phalcon-debugbar .phalcon-debugbar-log-summary { padding: 4px 0; cursor: pointer; } -#phalcon-debugbar .phalcon-debugbar-exception-label { +#phalcon-debugbar .phalcon-debugbar-exception-label, +#phalcon-debugbar .phalcon-debugbar-log-label { margin-right: 8px; - color: #ff6b81; font-weight: 600; } -#phalcon-debugbar .phalcon-debugbar-exception-trace { - margin: 6px 0 10px 16px; - padding: 8px 10px; - background: #0f0f17; - border-radius: 4px; - color: #b9b9d4; - font-size: 11px; - white-space: pre-wrap; - word-break: break-word; -} - -#phalcon-debugbar .phalcon-debugbar-log { - padding: 3px 0; - border-bottom: 1px solid #23233440; -} - -#phalcon-debugbar .phalcon-debugbar-log-summary { - padding: 4px 0; - cursor: pointer; +#phalcon-debugbar .phalcon-debugbar-exception-label { + color: #ff6b81; } #phalcon-debugbar .phalcon-debugbar-log-label { - margin-right: 8px; color: #7cc4ff; - font-weight: 600; } +#phalcon-debugbar .phalcon-debugbar-exception-trace, #phalcon-debugbar .phalcon-debugbar-log-context { margin: 6px 0 10px 16px; padding: 8px 10px; diff --git a/resources/assets/debugbar.js b/resources/assets/debugbar.js index 586b2ed..e3e6ee3 100644 --- a/resources/assets/debugbar.js +++ b/resources/assets/debugbar.js @@ -273,45 +273,35 @@ return node; } - function renderExceptions(panel) { + /* + * The exceptions and logs panels: a row with a detail (the trace or the + * context) opens to show it, and a row with no detail is a plain line. + * `kind` names the classes: phalcon-debugbar-exception-*, phalcon-debugbar-log-*. + */ + function renderEntries(panel, kind, detailKey) { if (!Array.isArray(panel) || !panel.length) { return el('div', 'phalcon-debugbar-empty', 'No data'); } - var wrap = el('div', 'phalcon-debugbar-exceptions'); + var prefix = 'phalcon-debugbar-' + kind; + var wrap = el('div', prefix + 's'); panel.forEach(function (row) { row = row || {}; - var details = el('details', 'phalcon-debugbar-exception'); - var summary = el('summary', 'phalcon-debugbar-exception-summary'); - summary.appendChild(el('span', 'phalcon-debugbar-exception-label', scalar(row.label))); - summary.appendChild(el('span', 'phalcon-debugbar-exception-message', scalar(row.message))); - details.appendChild(summary); - details.appendChild(el('pre', 'phalcon-debugbar-exception-trace', scalar(row.trace))); - wrap.appendChild(details); - }); - return wrap; - } - - function renderLogs(panel) { - if (!Array.isArray(panel) || !panel.length) { - return el('div', 'phalcon-debugbar-empty', 'No data'); - } - var wrap = el('div', 'phalcon-debugbar-logs'); - panel.forEach(function (row) { - row = row || {}; - var context = scalar(row.context); - if (context === '') { - var line = el('div', 'phalcon-debugbar-log'); - line.appendChild(el('span', 'phalcon-debugbar-log-label', scalar(row.label))); - line.appendChild(el('span', 'phalcon-debugbar-log-message', scalar(row.message))); + var detail = scalar(row[detailKey]); + var label = el('span', prefix + '-label', scalar(row.label)); + var message = el('span', prefix + '-message', scalar(row.message)); + if (detail === '') { + var line = el('div', prefix); + line.appendChild(label); + line.appendChild(message); wrap.appendChild(line); return; } - var details = el('details', 'phalcon-debugbar-log'); - var summary = el('summary', 'phalcon-debugbar-log-summary'); - summary.appendChild(el('span', 'phalcon-debugbar-log-label', scalar(row.label))); - summary.appendChild(el('span', 'phalcon-debugbar-log-message', scalar(row.message))); + var details = el('details', prefix); + var summary = el('summary', prefix + '-summary'); + summary.appendChild(label); + summary.appendChild(message); details.appendChild(summary); - details.appendChild(el('pre', 'phalcon-debugbar-log-context', context)); + details.appendChild(el('pre', prefix + '-' + detailKey, detail)); wrap.appendChild(details); }); return wrap; @@ -326,9 +316,9 @@ case 'html': return renderHtml(panel); case 'exceptions': - return renderExceptions(panel); + return renderEntries(panel, 'exception', 'trace'); case 'logs': - return renderLogs(panel); + return renderEntries(panel, 'log', 'context'); case 'grid': default: return renderGrid(panel); diff --git a/resources/assets/debugbar.min.css b/resources/assets/debugbar.min.css index 6b6d4c9..6999c10 100644 --- a/resources/assets/debugbar.min.css +++ b/resources/assets/debugbar.min.css @@ -1 +1 @@ -#phalcon-debugbar{position:fixed;left:0;right:0;bottom:0;z-index:2147483000;font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;font-size:12px;line-height:1.5;color:#e2e2ef}#phalcon-debugbar button{margin:0;border-radius:0;box-shadow:none;text-transform:none}#phalcon-debugbar.is-collapsed{left:auto}#phalcon-debugbar.is-collapsed .phalcon-debugbar-body,#phalcon-debugbar.is-collapsed .phalcon-debugbar-history-browser,#phalcon-debugbar.is-collapsed .phalcon-debugbar-tabs,#phalcon-debugbar.is-collapsed .phalcon-debugbar-indicators{display:none}#phalcon-debugbar *,#phalcon-debugbar *::before,#phalcon-debugbar *::after{box-sizing:border-box}#phalcon-debugbar .phalcon-debugbar-bar{display:flex;flex-direction:column;background:#1c1c28;border-top:2px solid #7c3aed;box-shadow:0 -2px 14px rgb(0 0 0 / .45)}#phalcon-debugbar .phalcon-debugbar-row{display:flex;align-items:stretch}#phalcon-debugbar .phalcon-debugbar-tabs{display:flex;flex:1 1 auto;align-items:stretch;overflow-x:auto}#phalcon-debugbar .phalcon-debugbar-tab{display:inline-flex;align-items:center;gap:7px;padding:7px 14px;background:#fff0;border:0;border-right:1px solid #2b2b40;color:#b9b9d4;font:inherit;white-space:nowrap;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-tab:hover{background:#26263a;color:#fff}#phalcon-debugbar .phalcon-debugbar-tab.is-active{background:#7c3aed;color:#fff}#phalcon-debugbar .phalcon-debugbar-badge{padding:1px 7px;border-radius:9px;background:rgb(255 255 255 / .16);font-size:11px}#phalcon-debugbar .phalcon-debugbar-tab.is-active .phalcon-debugbar-badge{background:rgb(0 0 0 / .25)}#phalcon-debugbar .phalcon-debugbar-indicators{display:flex;flex:0 1 auto;min-width:0;align-items:stretch}#phalcon-debugbar .phalcon-debugbar-indicator{display:inline-flex;min-width:0;align-items:center;gap:6px;padding:7px 10px;border-left:1px solid #2b2b40;color:#b9b9d4;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-indicator-icon{width:15px;height:15px;flex:0 0 auto;fill:none;stroke:#a78bfa;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}#phalcon-debugbar .phalcon-debugbar-indicator-value{color:#fff;font-weight:700}#phalcon-debugbar .phalcon-debugbar-request-control{max-width:25vw}#phalcon-debugbar button.phalcon-debugbar-request-control,#phalcon-debugbar button.phalcon-debugbar-metric{background:#fff0;border:0;border-left:1px solid #2b2b40;font:inherit;cursor:pointer}#phalcon-debugbar button.phalcon-debugbar-request-control:hover,#phalcon-debugbar button.phalcon-debugbar-request-control.is-active,#phalcon-debugbar button.phalcon-debugbar-metric:hover{background:#26263a}#phalcon-debugbar button.phalcon-debugbar-metric.is-active{background:#7c3aed}#phalcon-debugbar button.phalcon-debugbar-metric.is-active .phalcon-debugbar-indicator-icon{stroke:#fff}#phalcon-debugbar .phalcon-debugbar-request-method{color:#a78bfa;font-weight:700}#phalcon-debugbar .phalcon-debugbar-request-uri{overflow:hidden;text-overflow:ellipsis}#phalcon-debugbar .phalcon-debugbar-toggle{flex:0 0 auto;padding:7px 12px;background:#7c3aed;border:0;color:#fff;font:inherit;font-size:14px;line-height:1;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-toggle:hover{background:#8b4ff5}#phalcon-debugbar .phalcon-debugbar-body{max-height:42vh;overflow:auto;padding:10px 14px;background:#14141d;border-bottom:1px solid #2b2b40}#phalcon-debugbar .phalcon-debugbar-summary{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px}#phalcon-debugbar .phalcon-debugbar-summary-metric{display:flex;min-width:130px;align-items:baseline;justify-content:space-between;gap:14px;padding:7px 10px;border:1px solid #303047;border-radius:4px;background:#20202e}#phalcon-debugbar .phalcon-debugbar-summary-label{color:#9a9ac8}#phalcon-debugbar .phalcon-debugbar-summary-value{color:#fff;font-size:13px}#phalcon-debugbar .phalcon-debugbar-history-browser{display:none;max-height:210px;overflow:auto;background:#101018;border-bottom:1px solid #2b2b40}#phalcon-debugbar .phalcon-debugbar-history-toolbar{position:sticky;top:0;z-index:1;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:7px 14px;background:#181824;border-bottom:1px solid #2b2b40}#phalcon-debugbar .phalcon-debugbar-history-title{color:#d5d5e8;font-size:12px}#phalcon-debugbar .phalcon-debugbar-history-actions{display:flex;gap:6px}#phalcon-debugbar .phalcon-debugbar-history-action{padding:3px 9px;border:1px solid #454563;border-radius:3px;background:#29293d;color:#d5d5e8;font:inherit;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-history-action:hover{background:#353550;color:#fff}#phalcon-debugbar .phalcon-debugbar-history-action.is-danger{border-color:#7f3445;color:#ff9aac}#phalcon-debugbar .phalcon-debugbar-history-action.is-danger:hover{background:#542532}#phalcon-debugbar .phalcon-debugbar-history-action:disabled{opacity:.5;cursor:default}#phalcon-debugbar .phalcon-debugbar-history-list{display:flex;flex-direction:column}#phalcon-debugbar .phalcon-debugbar-history-request{display:grid;grid-template-columns:58px minmax(180px,1fr) 48px 210px;gap:10px;align-items:center;padding:5px 14px;background:#fff0;border:0;border-bottom:1px solid #232334;color:#b9b9d4;font:inherit;text-align:left;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-history-request:hover{background:#26263a;color:#fff}#phalcon-debugbar .phalcon-debugbar-history-request.is-selected{background:#26263a;color:#fff;box-shadow:inset 3px 0 #7c3aed}#phalcon-debugbar .phalcon-debugbar-history-request-error{display:none;grid-column:1 / -1;color:#ff6b81}#phalcon-debugbar .phalcon-debugbar-history-request.is-error .phalcon-debugbar-history-request-error{display:block}#phalcon-debugbar .phalcon-debugbar-history-method{color:#a78bfa;font-weight:700}#phalcon-debugbar .phalcon-debugbar-history-uri{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-history-status,#phalcon-debugbar .phalcon-debugbar-history-time{color:#8f8faa}#phalcon-debugbar .phalcon-debugbar-history-loading,#phalcon-debugbar .phalcon-debugbar-history-empty{padding:8px 14px;color:#8f8faa}#phalcon-debugbar .phalcon-debugbar-history-error{padding:8px 14px;color:#ff6b81}@media (max-width:760px){#phalcon-debugbar .phalcon-debugbar-indicator{padding-right:7px;padding-left:7px}#phalcon-debugbar .phalcon-debugbar-request-control{max-width:32vw}#phalcon-debugbar .phalcon-debugbar-history-request{grid-template-columns:52px minmax(120px,1fr) 42px}#phalcon-debugbar .phalcon-debugbar-history-time{display:none}}#phalcon-debugbar table,#phalcon-debugbar tr,#phalcon-debugbar th,#phalcon-debugbar td,#phalcon-debugbar pre{background:#fff0;color:inherit}#phalcon-debugbar table{width:100%;border-collapse:collapse}#phalcon-debugbar th,#phalcon-debugbar td{padding:4px 10px;text-align:left;vertical-align:top;border-bottom:1px solid #23233440;word-break:break-word}#phalcon-debugbar .phalcon-debugbar-grid th,#phalcon-debugbar .phalcon-debugbar-key{width:1%;color:#9a9ac8;font-weight:600;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-value{color:#e2e2ef}#phalcon-debugbar .phalcon-debugbar-list tr.is-duplicate{background:#2a2118}#phalcon-debugbar .phalcon-debugbar-duplicate-count{display:inline-block;margin-left:10px;padding:1px 6px;border-radius:999px;background:#8a4b08;color:#fff1d6;font-size:11px;font-weight:700;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-code{margin:0;white-space:pre-wrap;word-break:break-word}#phalcon-debugbar .phalcon-debugbar-empty{padding:6px 2px;color:#6b6b8c;font-style:italic}#phalcon-debugbar .phalcon-debugbar-exception{padding:3px 0;border-bottom:1px solid #23233440}#phalcon-debugbar .phalcon-debugbar-exception-summary{padding:4px 0;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-exception-label{margin-right:8px;color:#ff6b81;font-weight:600}#phalcon-debugbar .phalcon-debugbar-exception-trace{margin:6px 0 10px 16px;padding:8px 10px;background:#0f0f17;border-radius:4px;color:#b9b9d4;font-size:11px;white-space:pre-wrap;word-break:break-word}#phalcon-debugbar .phalcon-debugbar-log{padding:3px 0;border-bottom:1px solid #23233440}#phalcon-debugbar .phalcon-debugbar-log-summary{padding:4px 0;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-log-label{margin-right:8px;color:#7cc4ff;font-weight:600}#phalcon-debugbar .phalcon-debugbar-log-context{margin:6px 0 10px 16px;padding:8px 10px;background:#0f0f17;border-radius:4px;color:#b9b9d4;font-size:11px;white-space:pre-wrap;word-break:break-word} \ No newline at end of file +#phalcon-debugbar{position:fixed;left:0;right:0;bottom:0;z-index:2147483000;font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;font-size:12px;line-height:1.5;color:#e2e2ef}#phalcon-debugbar button{margin:0;border-radius:0;box-shadow:none;text-transform:none}#phalcon-debugbar.is-collapsed{left:auto}#phalcon-debugbar.is-collapsed .phalcon-debugbar-body,#phalcon-debugbar.is-collapsed .phalcon-debugbar-history-browser,#phalcon-debugbar.is-collapsed .phalcon-debugbar-tabs,#phalcon-debugbar.is-collapsed .phalcon-debugbar-indicators{display:none}#phalcon-debugbar *,#phalcon-debugbar *::before,#phalcon-debugbar *::after{box-sizing:border-box}#phalcon-debugbar .phalcon-debugbar-bar{display:flex;flex-direction:column;background:#1c1c28;border-top:2px solid #7c3aed;box-shadow:0 -2px 14px rgb(0 0 0 / .45)}#phalcon-debugbar .phalcon-debugbar-row{display:flex;align-items:stretch}#phalcon-debugbar .phalcon-debugbar-tabs{display:flex;flex:1 1 auto;align-items:stretch;overflow-x:auto}#phalcon-debugbar .phalcon-debugbar-tab{display:inline-flex;align-items:center;gap:7px;padding:7px 14px;background:#fff0;border:0;border-right:1px solid #2b2b40;color:#b9b9d4;font:inherit;white-space:nowrap;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-tab:hover{background:#26263a;color:#fff}#phalcon-debugbar .phalcon-debugbar-tab.is-active{background:#7c3aed;color:#fff}#phalcon-debugbar .phalcon-debugbar-badge{padding:1px 7px;border-radius:9px;background:rgb(255 255 255 / .16);font-size:11px}#phalcon-debugbar .phalcon-debugbar-tab.is-active .phalcon-debugbar-badge{background:rgb(0 0 0 / .25)}#phalcon-debugbar .phalcon-debugbar-indicators{display:flex;flex:0 1 auto;min-width:0;align-items:stretch}#phalcon-debugbar .phalcon-debugbar-indicator{display:inline-flex;min-width:0;align-items:center;gap:6px;padding:7px 10px;border-left:1px solid #2b2b40;color:#b9b9d4;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-indicator-icon{width:15px;height:15px;flex:0 0 auto;fill:none;stroke:#a78bfa;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}#phalcon-debugbar .phalcon-debugbar-indicator-value{color:#fff;font-weight:700}#phalcon-debugbar .phalcon-debugbar-request-control{max-width:25vw}#phalcon-debugbar button.phalcon-debugbar-request-control,#phalcon-debugbar button.phalcon-debugbar-metric{background:#fff0;border:0;border-left:1px solid #2b2b40;font:inherit;cursor:pointer}#phalcon-debugbar button.phalcon-debugbar-request-control:hover,#phalcon-debugbar button.phalcon-debugbar-request-control.is-active,#phalcon-debugbar button.phalcon-debugbar-metric:hover{background:#26263a}#phalcon-debugbar button.phalcon-debugbar-metric.is-active{background:#7c3aed}#phalcon-debugbar button.phalcon-debugbar-metric.is-active .phalcon-debugbar-indicator-icon{stroke:#fff}#phalcon-debugbar .phalcon-debugbar-request-method{color:#a78bfa;font-weight:700}#phalcon-debugbar .phalcon-debugbar-request-uri{overflow:hidden;text-overflow:ellipsis}#phalcon-debugbar .phalcon-debugbar-toggle{flex:0 0 auto;padding:7px 12px;background:#7c3aed;border:0;color:#fff;font:inherit;font-size:14px;line-height:1;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-toggle:hover{background:#8b4ff5}#phalcon-debugbar .phalcon-debugbar-body{max-height:42vh;overflow:auto;padding:10px 14px;background:#14141d;border-bottom:1px solid #2b2b40}#phalcon-debugbar .phalcon-debugbar-summary{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px}#phalcon-debugbar .phalcon-debugbar-summary-metric{display:flex;min-width:130px;align-items:baseline;justify-content:space-between;gap:14px;padding:7px 10px;border:1px solid #303047;border-radius:4px;background:#20202e}#phalcon-debugbar .phalcon-debugbar-summary-label{color:#9a9ac8}#phalcon-debugbar .phalcon-debugbar-summary-value{color:#fff;font-size:13px}#phalcon-debugbar .phalcon-debugbar-history-browser{display:none;max-height:210px;overflow:auto;background:#101018;border-bottom:1px solid #2b2b40}#phalcon-debugbar .phalcon-debugbar-history-toolbar{position:sticky;top:0;z-index:1;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:7px 14px;background:#181824;border-bottom:1px solid #2b2b40}#phalcon-debugbar .phalcon-debugbar-history-title{color:#d5d5e8;font-size:12px}#phalcon-debugbar .phalcon-debugbar-history-actions{display:flex;gap:6px}#phalcon-debugbar .phalcon-debugbar-history-action{padding:3px 9px;border:1px solid #454563;border-radius:3px;background:#29293d;color:#d5d5e8;font:inherit;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-history-action:hover{background:#353550;color:#fff}#phalcon-debugbar .phalcon-debugbar-history-action.is-danger{border-color:#7f3445;color:#ff9aac}#phalcon-debugbar .phalcon-debugbar-history-action.is-danger:hover{background:#542532}#phalcon-debugbar .phalcon-debugbar-history-action:disabled{opacity:.5;cursor:default}#phalcon-debugbar .phalcon-debugbar-history-list{display:flex;flex-direction:column}#phalcon-debugbar .phalcon-debugbar-history-request{display:grid;grid-template-columns:58px minmax(180px,1fr) 48px 210px;gap:10px;align-items:center;padding:5px 14px;background:#fff0;border:0;border-bottom:1px solid #232334;color:#b9b9d4;font:inherit;text-align:left;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-history-request:hover{background:#26263a;color:#fff}#phalcon-debugbar .phalcon-debugbar-history-request.is-selected{background:#26263a;color:#fff;box-shadow:inset 3px 0 #7c3aed}#phalcon-debugbar .phalcon-debugbar-history-request-error{display:none;grid-column:1 / -1;color:#ff6b81}#phalcon-debugbar .phalcon-debugbar-history-request.is-error .phalcon-debugbar-history-request-error{display:block}#phalcon-debugbar .phalcon-debugbar-history-method{color:#a78bfa;font-weight:700}#phalcon-debugbar .phalcon-debugbar-history-uri{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-history-status,#phalcon-debugbar .phalcon-debugbar-history-time{color:#8f8faa}#phalcon-debugbar .phalcon-debugbar-history-loading,#phalcon-debugbar .phalcon-debugbar-history-empty{padding:8px 14px;color:#8f8faa}#phalcon-debugbar .phalcon-debugbar-history-error{padding:8px 14px;color:#ff6b81}@media (max-width:760px){#phalcon-debugbar .phalcon-debugbar-indicator{padding-right:7px;padding-left:7px}#phalcon-debugbar .phalcon-debugbar-request-control{max-width:32vw}#phalcon-debugbar .phalcon-debugbar-history-request{grid-template-columns:52px minmax(120px,1fr) 42px}#phalcon-debugbar .phalcon-debugbar-history-time{display:none}}#phalcon-debugbar table,#phalcon-debugbar tr,#phalcon-debugbar th,#phalcon-debugbar td,#phalcon-debugbar pre{background:#fff0;color:inherit}#phalcon-debugbar table{width:100%;border-collapse:collapse}#phalcon-debugbar th,#phalcon-debugbar td{padding:4px 10px;text-align:left;vertical-align:top;border-bottom:1px solid #23233440;word-break:break-word}#phalcon-debugbar .phalcon-debugbar-grid th,#phalcon-debugbar .phalcon-debugbar-key{width:1%;color:#9a9ac8;font-weight:600;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-value{color:#e2e2ef}#phalcon-debugbar .phalcon-debugbar-list tr.is-duplicate{background:#2a2118}#phalcon-debugbar .phalcon-debugbar-duplicate-count{display:inline-block;margin-left:10px;padding:1px 6px;border-radius:999px;background:#8a4b08;color:#fff1d6;font-size:11px;font-weight:700;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-code{margin:0;white-space:pre-wrap;word-break:break-word}#phalcon-debugbar .phalcon-debugbar-empty{padding:6px 2px;color:#6b6b8c;font-style:italic}#phalcon-debugbar .phalcon-debugbar-exception,#phalcon-debugbar .phalcon-debugbar-log{padding:3px 0;border-bottom:1px solid #23233440}#phalcon-debugbar .phalcon-debugbar-exception-summary,#phalcon-debugbar .phalcon-debugbar-log-summary{padding:4px 0;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-exception-label,#phalcon-debugbar .phalcon-debugbar-log-label{margin-right:8px;font-weight:600}#phalcon-debugbar .phalcon-debugbar-exception-label{color:#ff6b81}#phalcon-debugbar .phalcon-debugbar-log-label{color:#7cc4ff}#phalcon-debugbar .phalcon-debugbar-exception-trace,#phalcon-debugbar .phalcon-debugbar-log-context{margin:6px 0 10px 16px;padding:8px 10px;background:#0f0f17;border-radius:4px;color:#b9b9d4;font-size:11px;white-space:pre-wrap;word-break:break-word} \ No newline at end of file diff --git a/resources/assets/debugbar.min.js b/resources/assets/debugbar.min.js index eec6897..19b840f 100644 --- a/resources/assets/debugbar.min.js +++ b/resources/assets/debugbar.min.js @@ -34,12 +34,10 @@ tr.appendChild(value);table.appendChild(tr)});return table} function renderCode(panel){var source='';if(typeof panel==='string'){source=panel}else if(panel&&panel.source!==undefined){source=panel.source} return el('pre','phalcon-debugbar-code',scalar(source))} function renderHtml(panel){var node=el('div','phalcon-debugbar-html');node.innerHTML=typeof panel==='string'?panel:'';return node} -function renderExceptions(panel){if(!Array.isArray(panel)||!panel.length){return el('div','phalcon-debugbar-empty','No data')} -var wrap=el('div','phalcon-debugbar-exceptions');panel.forEach(function(row){row=row||{};var details=el('details','phalcon-debugbar-exception');var summary=el('summary','phalcon-debugbar-exception-summary');summary.appendChild(el('span','phalcon-debugbar-exception-label',scalar(row.label)));summary.appendChild(el('span','phalcon-debugbar-exception-message',scalar(row.message)));details.appendChild(summary);details.appendChild(el('pre','phalcon-debugbar-exception-trace',scalar(row.trace)));wrap.appendChild(details)});return wrap} -function renderLogs(panel){if(!Array.isArray(panel)||!panel.length){return el('div','phalcon-debugbar-empty','No data')} -var wrap=el('div','phalcon-debugbar-logs');panel.forEach(function(row){row=row||{};var context=scalar(row.context);if(context===''){var line=el('div','phalcon-debugbar-log');line.appendChild(el('span','phalcon-debugbar-log-label',scalar(row.label)));line.appendChild(el('span','phalcon-debugbar-log-message',scalar(row.message)));wrap.appendChild(line);return} -var details=el('details','phalcon-debugbar-log');var summary=el('summary','phalcon-debugbar-log-summary');summary.appendChild(el('span','phalcon-debugbar-log-label',scalar(row.label)));summary.appendChild(el('span','phalcon-debugbar-log-message',scalar(row.message)));details.appendChild(summary);details.appendChild(el('pre','phalcon-debugbar-log-context',context));wrap.appendChild(details)});return wrap} -function renderPanel(type,panel){switch(type){case 'list':return renderList(panel);case 'code':return renderCode(panel);case 'html':return renderHtml(panel);case 'exceptions':return renderExceptions(panel);case 'logs':return renderLogs(panel);case 'grid':default:return renderGrid(panel)}} +function renderEntries(panel,kind,detailKey){if(!Array.isArray(panel)||!panel.length){return el('div','phalcon-debugbar-empty','No data')} +var prefix='phalcon-debugbar-'+kind;var wrap=el('div',prefix+'s');panel.forEach(function(row){row=row||{};var detail=scalar(row[detailKey]);var label=el('span',prefix+'-label',scalar(row.label));var message=el('span',prefix+'-message',scalar(row.message));if(detail===''){var line=el('div',prefix);line.appendChild(label);line.appendChild(message);wrap.appendChild(line);return} +var details=el('details',prefix);var summary=el('summary',prefix+'-summary');summary.appendChild(label);summary.appendChild(message);details.appendChild(summary);details.appendChild(el('pre',prefix+'-'+detailKey,detail));wrap.appendChild(details)});return wrap} +function renderPanel(type,panel){switch(type){case 'list':return renderList(panel);case 'code':return renderCode(panel);case 'html':return renderHtml(panel);case 'exceptions':return renderEntries(panel,'exception','trace');case 'logs':return renderEntries(panel,'log','context');case 'grid':default:return renderGrid(panel)}} function renderSummary(summary){if(!Array.isArray(summary)||!summary.length){return null} var wrap=el('div','phalcon-debugbar-summary');summary.forEach(function(item){item=item||{};var metric=el('div','phalcon-debugbar-summary-metric');metric.appendChild(el('span','phalcon-debugbar-summary-label',titleize(item.label)));metric.appendChild(el('strong','phalcon-debugbar-summary-value',scalar(item.value)));wrap.appendChild(metric)});return wrap} function hasBadge(badge){return badge!==null&&badge!==undefined&&badge!==''&&badge!==0} diff --git a/tests/JavaScript/debugbar.test.js b/tests/JavaScript/debugbar.test.js index b6055bf..3e53ad3 100644 --- a/tests/JavaScript/debugbar.test.js +++ b/tests/JavaScript/debugbar.test.js @@ -648,9 +648,7 @@ test('request history reports clear failures and restores controls', async funct }); test('indicator collectors render once on the right and open their panels', function () { - var dataNode = new dom.TestElement('script'); - var mount = new dom.TestElement('div'); - dataNode.textContent = JSON.stringify({ + var mount = renderPayload({ data: { messages: {panel: []}, time: {panel: [], badge: '12.34ms'}, @@ -691,20 +689,6 @@ test('indicator collectors render once on the right and open their panels', func } } }); - global.document = dom.createDocument({ - 'phalcon-debugbar': mount, - 'phalcon-debugbar-data': dataNode - }); - global.window = { - localStorage: { - getItem: function () { - return null; - }, - setItem: function () {} - } - }; - - require(debugbarPath); var labels = mount.querySelectorAll('.phalcon-debugbar-tab-label').map(function (label) { return label.textContent; @@ -778,23 +762,7 @@ test('the icon names in PHP match the drawings in the client', function () { }); test('the client renders the output of the real collectors', function () { - var dataNode = new dom.TestElement('script'); - var mount = new dom.TestElement('div'); - dataNode.textContent = JSON.stringify(panelContract().payload); - global.document = dom.createDocument({ - 'phalcon-debugbar': mount, - 'phalcon-debugbar-data': dataNode - }); - global.window = { - localStorage: { - getItem: function () { - return null; - }, - setItem: function () {} - } - }; - - require(debugbarPath); + var mount = renderPayload(panelContract().payload); var tabs = mount.querySelectorAll('.phalcon-debugbar-tab'); var labels = tabs.map(function (tab) { @@ -939,9 +907,7 @@ test('the client accepts the stored entry of the real history code', async funct }); test('request metrics use widget metadata when the collector has a different name', function () { - var dataNode = new dom.TestElement('script'); - var mount = new dom.TestElement('div'); - dataNode.textContent = JSON.stringify({ + var mount = renderPayload({ data: { time: {panel: [], badge: '12.34ms'}, http: { @@ -964,20 +930,6 @@ test('request metrics use widget metadata when the collector has a different nam } } }); - global.document = dom.createDocument({ - 'phalcon-debugbar': mount, - 'phalcon-debugbar-data': dataNode - }); - global.window = { - localStorage: { - getItem: function () { - return null; - }, - setItem: function () {} - } - }; - - require(debugbarPath); var labels = mount.querySelectorAll('.phalcon-debugbar-tab-label').map(function (label) { return label.textContent; @@ -990,9 +942,7 @@ test('request metrics use widget metadata when the collector has a different nam }); test('request control replaces the History tab and opens request history', async function () { - var dataNode = new dom.TestElement('script'); - var mount = new dom.TestElement('div'); - dataNode.textContent = JSON.stringify({ + var mount = renderPayload({ data: { messages: {panel: []}, request: { @@ -1012,19 +962,11 @@ test('request control replaces the History tab and opens request history', async history: {label: 'History', panel: 'history'} } } - }); - global.document = dom.createDocument({ - 'phalcon-debugbar': mount, - 'phalcon-debugbar-data': dataNode - }); - global.window = { + }, { fetch: function () { return Promise.resolve(response({requests: []})); } - }; - - delete require.cache[debugbarPath]; - require(debugbarPath); + }); var tabs = dom.findByClass(mount, 'phalcon-debugbar-tabs'); var requestControl = dom.findByClass(mount, 'phalcon-debugbar-request-control'); @@ -1059,8 +1001,6 @@ test('request control replaces the History tab and opens request history', async }); test('selecting stored data closes history and uses metadata without a request collector', async function () { - var dataNode = new dom.TestElement('script'); - var mount = new dom.TestElement('div'); var historyPanel = {url: '/_debugbar/open'}; var widgets = { history: {label: 'History', panel: 'history'}, @@ -1075,7 +1015,8 @@ test('selecting stored data closes history and uses metadata without a request c request: {method: ['metrics', 'method'], uri: ['metrics', 'uri']} } }; - dataNode.textContent = JSON.stringify({ + var calls = 0; + var mount = renderPayload({ data: { history: {panel: historyPanel}, time: {panel: [{label: 'Request', message: '10ms'}], badge: '10ms'}, @@ -1085,13 +1026,7 @@ test('selecting stored data closes history and uses metadata without a request c } }, meta: {widgets: widgets} - }); - global.document = dom.createDocument({ - 'phalcon-debugbar': mount, - 'phalcon-debugbar-data': dataNode - }); - var calls = 0; - global.window = { + }, { fetch: function () { calls++; if (calls === 1) { @@ -1114,9 +1049,7 @@ test('selecting stored data closes history and uses metadata without a request c } })); } - }; - - require(debugbarPath); + }); var timeIndicator = dom.findByClass(mount, 'phalcon-debugbar-metric'); timeIndicator.click(); assert.equal(timeIndicator.classList.contains('is-active'), true); @@ -1141,20 +1074,13 @@ test('selecting stored data closes history and uses metadata without a request c }); test('stored data with no widget that defines the request uses the request metadata', async function () { - var dataNode = new dom.TestElement('script'); - var mount = new dom.TestElement('div'); var historyPanel = {url: '/_debugbar/open'}; var widgets = {history: {label: 'History', panel: 'history'}}; - dataNode.textContent = JSON.stringify({ + var calls = 0; + var mount = renderPayload({ data: {history: {panel: historyPanel}}, meta: {widgets: widgets} - }); - global.document = dom.createDocument({ - 'phalcon-debugbar': mount, - 'phalcon-debugbar-data': dataNode - }); - var calls = 0; - global.window = { + }, { fetch: function () { calls++; if (calls === 1) { @@ -1171,9 +1097,7 @@ test('stored data with no widget that defines the request uses the request metad } })); } - }; - - require(debugbarPath); + }); dom.findByClass(mount, 'phalcon-debugbar-request-control').click(); await dom.flushPromises(); dom.findByClass(mount, 'phalcon-debugbar-history-request').click(); @@ -1184,8 +1108,6 @@ test('stored data with no widget that defines the request uses the request metad }); test('selecting stored data reopens the collector tab active before history', async function () { - var dataNode = new dom.TestElement('script'); - var mount = new dom.TestElement('div'); var historyPanel = {url: '/_debugbar/open'}; var widgets = { database: {label: 'Database', panel: 'list'}, @@ -1196,20 +1118,15 @@ test('selecting stored data reopens the collector tab active before history', as request: {method: ['metrics', 'method'], uri: ['metrics', 'uri']} } }; - dataNode.textContent = JSON.stringify({ + var calls = 0; + var mount = renderPayload({ data: { database: {panel: [{label: 'Current', message: 'SELECT 1'}]}, history: {panel: historyPanel}, request: {metrics: {method: 'GET', uri: '/current'}, panel: {}} }, meta: {widgets: widgets} - }); - global.document = dom.createDocument({ - 'phalcon-debugbar': mount, - 'phalcon-debugbar-data': dataNode - }); - var calls = 0; - global.window = { + }, { fetch: function () { calls++; if (calls === 1) { @@ -1233,9 +1150,7 @@ test('selecting stored data reopens the collector tab active before history', as } })); } - }; - - require(debugbarPath); + }); var databaseTab = mount.querySelectorAll('.phalcon-debugbar-tab').filter(function (tab) { return tab.textContent === 'Database'; })[0]; @@ -1387,3 +1302,72 @@ test('Escape in the bar closes the request history', async function () { assert.equal(dom.findByClass(mount, 'phalcon-debugbar-history-browser').style.display, 'none'); assert.equal(dom.findByClass(mount, 'phalcon-debugbar-request-control').getAttribute('aria-expanded'), 'false'); }); + +// Opens the one tab of a panel of the given type and returns the rendered panel. +function entriesPanel(type, rows) { + var mount = renderPayload({ + data: {entries: {panel: rows}}, + meta: {widgets: {entries: {label: 'Entries', panel: type}}} + }); + dom.findByClass(mount, 'phalcon-debugbar-tab').click(); + + return dom.findByClass(mount, 'phalcon-debugbar-body').children[0]; +} + +// The markup as [tag, class, text] for a leaf and [tag, class, children] for the others. +function shape(node) { + return node.children.length + ? [node.tagName, node.className, node.children.map(shape)] + : [node.tagName, node.className, node.textContent]; +} + +test('the logs panel shows a line for a log with no context and a details block for one with context', function () { + var panel = entriesPanel('logs', [ + {label: 'info', message: 'login', context: '{"id":7}'}, + {label: 'debug', message: 'ping', context: ''} + ]); + + assert.deepEqual(shape(panel), ['DIV', 'phalcon-debugbar-logs', [ + ['DETAILS', 'phalcon-debugbar-log', [ + ['SUMMARY', 'phalcon-debugbar-log-summary', [ + ['SPAN', 'phalcon-debugbar-log-label', 'info'], + ['SPAN', 'phalcon-debugbar-log-message', 'login'] + ]], + ['PRE', 'phalcon-debugbar-log-context', '{"id":7}'] + ]], + ['DIV', 'phalcon-debugbar-log', [ + ['SPAN', 'phalcon-debugbar-log-label', 'debug'], + ['SPAN', 'phalcon-debugbar-log-message', 'ping'] + ]] + ]]); +}); + +test('the exceptions panel shows a details block with the trace', function () { + var panel = entriesPanel('exceptions', [ + {label: 'RuntimeException', message: 'boom (file.php:1)', trace: '#0 {main}'} + ]); + + assert.deepEqual(shape(panel), ['DIV', 'phalcon-debugbar-exceptions', [ + ['DETAILS', 'phalcon-debugbar-exception', [ + ['SUMMARY', 'phalcon-debugbar-exception-summary', [ + ['SPAN', 'phalcon-debugbar-exception-label', 'RuntimeException'], + ['SPAN', 'phalcon-debugbar-exception-message', 'boom (file.php:1)'] + ]], + ['PRE', 'phalcon-debugbar-exception-trace', '#0 {main}'] + ]] + ]]); +}); + +test('an exception with no trace shows as a line, as a log with no context does', function () { + // The "rows dropped" row of the row limit has no trace. + var panel = entriesPanel('exceptions', [ + {label: 'dropped', message: '2 rows dropped', trace: ''} + ]); + + assert.deepEqual(shape(panel), ['DIV', 'phalcon-debugbar-exceptions', [ + ['DIV', 'phalcon-debugbar-exception', [ + ['SPAN', 'phalcon-debugbar-exception-label', 'dropped'], + ['SPAN', 'phalcon-debugbar-exception-message', '2 rows dropped'] + ]] + ]]); +});