diff --git a/src/server/report/app.js b/src/server/report/app.js
index f0abe727..46fcd2a8 100644
--- a/src/server/report/app.js
+++ b/src/server/report/app.js
@@ -1,7 +1,6 @@
import './test.js';
import { css, html, LitElement, nothing } from 'lit';
import { FILTER_STATUS, FULL_MODE, LAYOUTS } from './common.js';
-import { classMap } from 'lit/directives/class-map.js';
import data from './data.js';
import page from 'page';
@@ -42,6 +41,7 @@ class App extends LitElement {
table {
background-color: #ffffff;
border-collapse: collapse;
+ width: 100%;
}
td, th {
border: 1px solid #dfe6ef;
@@ -120,10 +120,15 @@ class App extends LitElement {
} else {
hasPadding = false;
view = html`
-
-
-
-
+
`;
}
}
@@ -134,6 +139,9 @@ class App extends LitElement {
view = this._files.map(f => this._renderFile(f));
}
}
+ if (hasPadding) {
+ view = html`${view}
`;
+ }
return html`
- ${view}
+ ${view}
`;
}
- _goHome() {
- this._updateSearchParams({ file: undefined, test: undefined });
- }
_handleFilterBrowserChange(e) {
const browsers = data.browsers.map(b => b.name).filter(b => {
if (b === e.target.value) {
@@ -162,6 +167,13 @@ class App extends LitElement {
_handleFilterStatusChange(e) {
this._updateSearchParams({ status: e.target.value });
}
+ _handleNavigation(e) {
+ switch (e.detail.location) {
+ case 'home':
+ this._updateSearchParams({ file: undefined, test: undefined });
+ break;
+ }
+ }
_handleSettingChange(e) {
this[`_${e.detail.name}`] = e.detail.value;
}
@@ -217,15 +229,18 @@ class App extends LitElement {
`;
};
+ const browserFilter = data.browsers.length > 1 ? html`
+ ` : nothing;
+
return html`
-
+ ${browserFilter}
`;
}
diff --git a/src/server/report/button.js b/src/server/report/button.js
new file mode 100644
index 00000000..3df083c3
--- /dev/null
+++ b/src/server/report/button.js
@@ -0,0 +1,40 @@
+import { css, html, LitElement } from 'lit';
+
+class Button extends LitElement {
+ static properties = {
+ text: { type: String }
+ };
+ static styles = [css`
+ :host {
+ display: inline-block;
+ }
+ button {
+ align-items: center;
+ background-color: #ffffff;
+ border: 1px solid #cdd5dc;
+ border-radius: 5px;
+ cursor: pointer;
+ display: flex;
+ gap: 5px;
+ line-height: 24px;
+ margin: 0;
+ outline: none;
+ padding: 10px;
+ user-select: none;
+ }
+ button:hover,
+ button:focus {
+ background-color: #007bff;
+ color: #ffffff;
+ }
+ button:focus-visible {
+ border-color: #007bff;
+ box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #007bff;
+ }
+ `];
+ render() {
+ return html``;
+ }
+}
+
+customElements.define('d2l-vdiff-report-button', Button);
diff --git a/src/server/report/icons.js b/src/server/report/icons.js
index 56cb0b2e..47b9b53d 100644
--- a/src/server/report/icons.js
+++ b/src/server/report/icons.js
@@ -1,5 +1,11 @@
import { svg } from 'lit';
+export const ICON_HOME = svg`
+ `;
+
export const ICON_FULL = svg`
`;
+
+export const ICON_BROWSERS = {
+ Chromium: svg``,
+ Firefox: svg``,
+ Webkit: svg``
+};
diff --git a/src/server/report/test-result.js b/src/server/report/test-result.js
index 045ae85b..8f085d0c 100644
--- a/src/server/report/test-result.js
+++ b/src/server/report/test-result.js
@@ -12,6 +12,10 @@ class TestResult extends LitElement {
test: { type: String },
};
static styles = [css`
+ :host {
+ display: block;
+ padding: 20px;
+ }
.split {
flex-direction: row;
flex-wrap: nowrap;
@@ -39,7 +43,6 @@ class TestResult extends LitElement {
position: absolute;
top: 0;
left: 0;
- z-index: 1;
}
.no-changes {
border: 1px solid #cdd5dc;
@@ -48,11 +51,10 @@ class TestResult extends LitElement {
`];
render() {
- const { browserData, fileData, resultData, testData } = this._fetchData();
- if (!browserData || !fileData || !resultData || !testData) return nothing;
+ const resultData = this._fetchData();
+ if (!resultData) return nothing;
return html`
- ${resultData.name} v${browserData.version} (${resultData.duration}ms)
${this._renderBody(resultData)}
`;
@@ -71,7 +73,7 @@ class TestResult extends LitElement {
const browserData = data.browsers.find(b => b.name === this.browser);
if (!browserData) return {};
- return { browserData, fileData, resultData, testData };
+ return resultData;
}
_renderBody(resultData) {
diff --git a/src/server/report/test.js b/src/server/report/test.js
index da1ffca5..25cedd06 100644
--- a/src/server/report/test.js
+++ b/src/server/report/test.js
@@ -1,32 +1,85 @@
+import './button.js';
import './test-result.js';
import { css, html, LitElement, nothing } from 'lit';
import { FULL_MODE, getId, LAYOUTS } from './common.js';
+import { ICON_BROWSERS, ICON_HOME } from './icons.js';
+import { classMap } from 'lit/directives/class-map.js';
import data from './data.js';
class Test extends LitElement {
static properties = {
+ browsers: { type: String },
file: { type: String },
fullMode: { attribute: 'full-mode', type: String },
layout: { type: String },
showOverlay: { attribute: 'show-overlay', type: Boolean },
test: { type: String },
+ _selectedBrowserIndex: { state: true }
};
static styles = [css`
+ :host {
+ display: grid;
+ grid-template-rows: auto 1fr auto;
+ grid-template-areas:
+ 'header'
+ 'content'
+ 'footer';
+ height: 100vh;
+ }
.header {
+ border-bottom: 1px solid #cdd5dc;
+ grid-area: header;
+ }
+ .tab-panels {
+ grid-area: content;
+ overflow: auto;
+ }
+ .footer {
+ align-items: center;
+ border-top: 1px solid #cdd5dc;
+ display: flex;
+ gap: 10px;
+ grid-area: footer;
+ padding: 20px;
+ }
+ .footer > svg {
+ flex: 0 0 auto;
+ height: 50px;
+ width: 50px;
+ }
+ .footer-info {
+ flex: 1 0 auto;
+ }
+ .footer-browser-name {
+ font-size: 1.2rem;
+ font-weight: bold;
+ }
+ .footer-timing {
+ flex: 0 0 auto;
+ font-size: 2rem;
+ font-weight: bold;
+ }
+ .header, .footer {
background-color: #f0f0f0;
- border-bottom: 1px solid #e6e6e6;
box-shadow: 0 0 6px rgba(0,0,0,.07);
- position: sticky;
- top: 0;
- z-index: 2;
+ }
+ .title {
+ display: flex;
+ padding: 20px 20px 0 20px;
}
.title h2 {
margin: 0;
}
+ .title-info {
+ flex: 1 0 auto;
+ }
+ .title-navigation {
+ flex: 0 0 auto;
+ }
.settings {
align-items: center;
display: flex;
- padding-top: 20px;
+ padding: 20px;
gap: 20px;
}
.settings-box {
@@ -37,9 +90,6 @@ class Test extends LitElement {
padding: 10px;
user-select: none;
}
- .header, .results {
- padding: 20px;
- }
.pill-box {
border-radius: 5px;
display: flex;
@@ -84,10 +134,66 @@ class Test extends LitElement {
border-color: #007bff;
box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px #007bff;
}
+ [role="tablist"] {
+ align-items: stretch;
+ border-top: 1px solid #cdd5dc;
+ display: flex;
+ flex: 0 0 auto;
+ flex-wrap: nowrap;
+ }
+ [role="tab"] {
+ background: none;
+ border: none;
+ border-right: 1px solid #cdd5dc;
+ cursor: pointer;
+ flex: 1 0 auto;
+ margin: 0;
+ outline: none;
+ padding: 10px 15px;
+ position: relative;
+ user-select: none;
+ }
+ [role="tab"]:last-child {
+ border-right: none;
+ }
+ [role="tab"] > span {
+ display: inline-block;
+ padding: 5px;
+ }
+ [role="tab"]:focus-visible > span {
+ border: 2px solid #007bff;
+ border-radius: 3px;
+ padding: 3px;
+ }
+ [role="tab"]:hover > span {
+ color: #007bff;
+ }
+ .tab-selected-indicator {
+ border-block-start: 4px solid #007bff;
+ border-start-start-radius: 4px;
+ border-start-end-radius: 4px;
+ bottom: 0;
+ position: absolute;
+ width: calc(100% - 30px);
+ }
+ .pass {
+ color: #46a661;
+ }
+ .error {
+ color: #cd2026;
+ }
+ .warning {
+ color: #e87511;
+ }
`];
+ constructor() {
+ super();
+ this.browsers = [];
+ this._selectedBrowserIndex = -1;
+ }
render() {
- const { fileData, testData } = this._fetchData();
+ const { browsers, fileData, testData } = this._fetchData();
if (!fileData || !testData) return nothing;
let fullMode = nothing;
@@ -95,21 +201,30 @@ class Test extends LitElement {
fullMode = this._renderPillbox('fullMode', this.fullMode, [FULL_MODE.GOLDEN, FULL_MODE.NEW]);
}
+ const selectedBrowser = this._getSelectedBrowser(browsers, testData);
+ const selectedResult = testData.results.find(r => r.name === selectedBrowser.name);
+ const tabButtons = browsers.length > 1 ? this._renderTabButtons(browsers, selectedBrowser, testData) : nothing;
+
return html`
-
- ${testData.results.map(r => html``)}
-
+ ${this._renderTabPanels(browsers, selectedBrowser, fileData, testData)}
+ ${this._renderFooter(selectedBrowser, selectedResult)}
`;
}
@@ -121,15 +236,45 @@ class Test extends LitElement {
const testData = fileData.tests.find(t => t.name === this.test);
if (!testData) return {};
- return { fileData, testData };
+ const filteredBrowsers = this.browsers.split(',');
+ const browsers = data.browsers.filter(b => filteredBrowsers.includes(b.name));
+
+ return { browsers, fileData, testData };
}
+ _getSelectedBrowser(browsers, { results }) {
+ return browsers[this._selectedBrowserIndex] ||
+ browsers.find(b => results.find(r => r.name === b.name && !r.passed)) ||
+ browsers[0];
+ }
+ _handleBackClick() {
+ this._triggerNavigation('home');
+ }
_handleOverlayChange(e) {
this._triggerChange('overlay', e.target.checked);
}
_handlePillboxChange(e) {
this._triggerChange(e.target.name, e.target.value);
}
+ _renderFooter(selectedBrowser, selectedResult) {
+ const duration = selectedResult.duration;
+ const durationClass = {
+ 'error': duration >= 1000,
+ 'footer-timing': true,
+ 'pass': duration < 500,
+ 'warning': duration >= 500 && duration < 1000
+ };
+ return html`
+
+ `;
+ }
_renderPillbox(name, selectedValue, items) {
const renderItem = (i) => {
const id = getId();
@@ -142,6 +287,79 @@ class Test extends LitElement {
};
return html`${items.map(i => renderItem(i))}
`;
}
+ _renderTabButtons(browsers, selectedBrowser, testData) {
+
+ const onKeyDown = (e) => {
+ let focusOn;
+ switch (e.key) {
+ case 'ArrowRight':
+ focusOn = e.target.nextElementSibling || e.target.parentNode.firstElementChild;
+ break;
+ case 'ArrowLeft':
+ focusOn = e.target.previousElementSibling || e.target.parentNode.lastElementChild;
+ break;
+ case 'Home':
+ focusOn = e.target.parentNode.firstElementChild;
+ break;
+ case 'End':
+ focusOn = e.target.parentNode.lastElementChild;
+ break;
+ }
+ if (focusOn) focusOn.focus();
+ };
+
+ const renderTabButton = (browser, index) => {
+ const result = testData.results.find(r => r.name === browser);
+ const selected = (browser === selectedBrowser.name);
+ const status = result.passed ? 'passed' : 'failed';
+ const onClick = () => {
+ return () => this._selectedBrowserIndex = index;
+ };
+ const statusClass = {
+ pass: result.passed,
+ error: !result.passed
+ };
+ return html`
+ `;
+ };
+
+ return html`
+
+ ${browsers.map((b, i) => renderTabButton(b.name, i))}
+
+ `;
+ }
+ _renderTabPanels(browsers, selectedBrowser, fileData, testData) {
+
+ const renderTabPanel = (browser) => {
+ return html`
+
+
+
+ `;
+ };
+
+ return html`${browsers.map(b => renderTabPanel(b.name))}
`;
+
+ }
_triggerChange(name, value) {
this.dispatchEvent(new CustomEvent(
'setting-change', {
@@ -154,6 +372,17 @@ class Test extends LitElement {
}
));
}
+ _triggerNavigation(location) {
+ this.dispatchEvent(new CustomEvent(
+ 'navigation', {
+ bubbles: false,
+ composed: false,
+ detail: {
+ location: location
+ }
+ }
+ ));
+ }
}
customElements.define('d2l-vdiff-report-test', Test);