baremaps-renderer/assets/report-template.html (296 lines of code) (raw):
<!--
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the License for the specific language governing permissions and limitations under
the License.
-->
<!DOCTYPE html>
<html>
<head>
<title>Baremaps Renderer</title>
<style>
:root {
--color-pass: rgb(40, 156, 40);
--color-pass-transparent: rgba(40, 156, 40, 0.05);
--color-error: rgb(255, 59, 59);
--color-error-transparent: rgba(255, 59, 59, 0.05);
}
body {
font-family: sans-serif;
padding: 4rem 8rem;
}
@media (max-width: 768px) {
body {
padding: 4rem 0.5rem;
}
.results {
gap: 0.5rem !important;
}
}
.title {
margin-bottom: 3rem;
}
h1 {
font-size: 3rem;
margin-top: 0;
margin-bottom: 1rem;
}
h2 {
font-size: 1.5rem;
margin-top: 0;
margin-bottom: 1rem;
text-transform: uppercase;
font-weight: 500;
}
h3 {
font-size: 2rem;
margin: 1rem 0;
}
h4 {
font-size: 1.25rem;
margin-bottom: 0;
}
h4.fail {
margin-top: 0;
text-transform: uppercase;
color: var(--color-error);
}
h4.pass {
margin-top: 0;
text-transform: uppercase;
color: var(--color-pass);
}
a {
position: relative;
text-decoration: underline;
color: inherit;
}
pre {
font-size: 1.25rem;
}
button {
border: none;
background-color: transparent;
cursor: pointer;
font-size: 1rem;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.075);
}
.sort {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 1rem;
}
.sort p {
margin-right: 0.5rem;
}
.sort button.active {
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
background-color: rgba(0, 0, 0, 0.075);
}
.results {
display: flex;
flex-direction: column;
gap: 2rem;
}
.summary {
border-left: 3px solid black;
padding: 1rem 2rem;
background-color: rgba(0, 0, 0, 0.05);
}
.summary h4 {
text-transform: uppercase;
margin-top: 1rem;
}
.p-pass {
color: var(--color-pass);
font-weight: bold;
}
.p-fail {
color: var(--color-error);
font-weight: bold;
}
.result {
padding: 1rem 2rem;
}
.result.fail {
border-left: 3px solid var(--color-error);
background-color: var(--color-error-transparent);
}
.result.pass {
border-left: 3px solid var(--color-pass);
background-color: var(--color-pass-transparent);
}
pre {
margin: 0;
}
.images {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 1rem;
}
img {
width: 100%;
aspect-ratio: 1;
}
</style>
</head>
<body>
<div class="title">
<h1>Baremaps Renderer</h1>
<h2>Integration Testing Report</h2>
</div>
<div>
<!-- Sort by test status, test diff, test name -->
<div class="sort">
<p><strong>Sort by:</strong></p>
<button onClick="sortBy(this, 'status')">Test status</button>
<button onClick="sortBy(this, 'diff')">Test diff</button>
<button onClick="sortBy(this, 'name')">Test name</button>
</div>
</div>
<!-- Test results container (incl. the summary) -->
<div class="results" id="testResults"></div>
</body>
<script>
/** Template for the test summary */
const SUMMARY_TEMPLATE = `<div class="summary">
<h4>Summary</h4>
<p>Out of <strong>{{ TOT_TESTS }}</strong> tests:</p>
<ul>
<li><span class="p-pass">{{ TOT_PASS_TESTS }} tests passed</span></li>
<li><span class="p-fail">{{ TOT_FAIL_TESTS }} tests failed</span></li>
</ul>
</div>`;
/**
* Helper function to get the test template
*
* @param {boolean} passed - Whether the test passed or not
* @param {number} index - The index of the test
* @returns {string} - The test template
*/
const getTestTemplate = (passed, index) => {
const loading = index > 2 ? 'lazy' : 'eager';
return `<div class="result ${passed ? 'pass' : 'fail'}">
<h4 class="${passed ? 'pass' : 'fail'}">${passed ? 'PASSED' : 'FAILED'}</h4>
<h3>{{ TEST_NAME }}</h3>
<p>
{{ TEST_PATH }}
</p>
<p>
<a href="{{ MAP_URL }}" target="_bank">
<strong>{{ MAP_URL }}</strong>
</a>
</p>
<pre>
<code>
{{ METADATA }}</code>
</pre>
<div class="images">
<h4>Expected</h4>
<h4>Actual</h4>
<h4>Difference ({{ DIFF }})</h4>
<img src="{{ EXPECTED_IMG_PATH }}" loading="${loading}" />
<img src="{{ ACTUAL_IMG_PATH }}" loading="${loading}" />
<img src="{{ DIFF_IMG_PATH }}" loading="${loading}" />
</div>
</div>`;
};
const testData = JSON.parse(`{{ TESTS_DATA }}`);
/**
* Updates the sort parameters and re-renders the test results
*
* @param {HTMLElement} el - The element that was clicked
* @param {string} key - The key to sort by
*/
const sortBy = (el, key) => {
if (!sortedBy[key].active) {
el.classList.add('active');
el.innerText = `${el.innerText} ▲`;
sortedBy[key].asc = true;
sortedBy[key].active = true;
} else if (sortedBy[key].active && sortedBy[key].asc) {
el.classList.remove('asc');
el.innerText = `${el.innerText.replace(' ▲', '')} ▼`;
sortedBy[key].asc = false;
} else if (sortedBy[key].active && !sortedBy[key].asc) {
el.classList.remove('active');
el.innerText = el.innerText.replace(' ▼', '');
sortedBy[key].active = false;
}
render();
};
let sortedBy = {
status: {
asc: false,
active: false,
},
diff: {
asc: false,
active: false,
},
name: {
asc: false,
active: false,
},
};
// prevents the browser from caching the images
const ts = new Date().getTime();
/** Render the test results */
const render = () => {
const testResultsDiv = document.getElementById('testResults');
testResultsDiv.innerHTML = '';
const summary = document.createElement('div');
summary.innerHTML = SUMMARY_TEMPLATE.replace(
'{{ TOT_TESTS }}',
testData.length,
)
.replace(
'{{ TOT_PASS_TESTS }}',
testData.filter((test) => test.success).length,
)
.replace(
'{{ TOT_FAIL_TESTS }}',
testData.filter((test) => !test.success).length,
);
testResultsDiv.appendChild(summary);
const sortedTestData = [...testData];
// sort the test data
if (sortedBy.name.active) {
sortedBy.name.asc
? sortedTestData.sort((a, b) => (a.name > b.name ? 1 : -1))
: sortedTestData.sort((a, b) => (a.name < b.name ? 1 : -1));
}
if (sortedBy.diff.active) {
sortedBy.diff.asc
? sortedTestData.sort((a, b) => a.diff - b.diff)
: sortedTestData.sort((a, b) => b.diff - a.diff);
}
if (sortedBy.status.active) {
sortedBy.status.asc
? sortedTestData.sort((a, b) =>
a.success === b.success ? 0 : a.success ? -1 : 1,
)
: sortedTestData.sort((a, b) =>
a.success === b.success ? 0 : a.success ? 1 : -1,
);
}
sortedTestData.forEach((test, index) => {
const testDiv = document.createElement('div');
testDiv.innerHTML = getTestTemplate(test.success, index);
testDiv.innerHTML = testDiv.innerHTML
.replace('{{ TEST_NAME }}', test.name)
.replace('{{ TEST_PATH }}', test.path)
.replace(
// replace all occurrences of the map url
new RegExp('{{ MAP_URL }}', 'g'),
`https://demo.baremaps.com/#${test.metadata.zoom}/${test.metadata.center[1]}/${test.metadata.center[0]}`,
)
.replace('{{ METADATA }}', JSON.stringify(test.metadata, null, 4))
.replace('{{ DIFF }}', test.diff)
.replace(
'{{ EXPECTED_IMG_PATH }}',
test.expectedImagePath + `?ts=${ts}`,
)
.replace('{{ ACTUAL_IMG_PATH }}', test.actualImagePath + `?ts=${ts}`)
.replace('{{ DIFF_IMG_PATH }}', test.diffImagePath + `?ts=${ts}`);
testResultsDiv.appendChild(testDiv);
});
};
render();
</script>
</html>