From e1b907e806b37bbdf726996f64f61550d3776c82 Mon Sep 17 00:00:00 2001 From: Kilian Ciuffolo Date: Sun, 19 May 2024 17:15:12 -0700 Subject: [PATCH] chore: fix coverage colors for same line statements --- assets/index.css | 370 ++++++++++++++++++++++------------------------- assets/index.js | 148 +++++++++++++------ 2 files changed, 274 insertions(+), 244 deletions(-) diff --git a/assets/index.css b/assets/index.css index c1eeb86..96f22de 100644 --- a/assets/index.css +++ b/assets/index.css @@ -2,6 +2,7 @@ --nav-gap: 15px; --covered: green; --uncovered: #bf616a; + --mixed: #ddaa00; --background: #ffffff; --topbar-background: #f6f8fa; --topbar-border: #d0d7de; @@ -16,6 +17,7 @@ :root.dark { --covered: rgb(71, 210, 71); --uncovered: #bf616a; + --mixed: #ffcc00; --background: #242931; --topbar-background: #2e3440; --topbar-border: #4c566a; @@ -28,22 +30,19 @@ * { font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, - "Liberation Mono", monospace !important; + "Liberation Mono", monospace; font-size: 12px; margin: 0; padding: 0; } body { + background: var(--background); + transition: all 0.1s ease-in-out; min-height: 100vh; overflow-y: scroll; } -body { - background: var(--background) !important; - transition: all 0.1s ease-in-out; -} - #topbar { position: sticky; top: 0; @@ -55,205 +54,176 @@ body { border-color: var(--topbar-border); border-bottom: 1px solid var(--topbar-border); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); -} - -#legend { - display: flex; - align-items: center; - margin-right: auto; -} - -#nav, -#legend span { - margin-left: var(--nav-gap); - color: var(--topbar-color) !important; -} - -#nav { - position: relative; - display: inline-block; -} - -#nav::after { - content: "▼"; - position: absolute; - right: 1.2em; - top: 50%; - transform: translateY(-50%); - pointer-events: none; - font-size: 0.75em; -} - -select { - display: block; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - background-color: var(--select-background); - color: var(--select-color); - border: 1px solid var(--select-border); - border-radius: 3px; - padding: 4px 24px 4px 8px; -} - -#legend > span { - padding: 2px 4px; - border-radius: 2px; -} - -a.incremental { - display: flex; - background: var(--topbar-hover-color); - padding: 2px 4px; - border-radius: 2px; - box-shadow: inset 0 0 0 1px var(--topbar-hover-color); - color: var(--topbar-color); - text-decoration: none; - align-items: center; -} - -a.incremental:hover { - background: transparent; - box-shadow: inset 0 0 0 1px var(--topbar-hover-color); -} - -a.incremental:active { - transform: translateY(1px); -} - -input[type="checkbox"] { - height: 0; - width: 0; - visibility: hidden; - margin-left: var(--nav-gap); -} -label { - cursor: pointer; - height: 20px; - width: 50px; - border-radius: 15px; - background: #afb8c133; - display: block; - position: relative; - margin-right: 15px; - margin-right: var(--nav-gap); -} - -label:after { - content: ""; - position: absolute; - top: 0; - left: 0; - height: 100%; - aspect-ratio: 1 / 1; - background: #fff; - border-radius: 50%; - transform: scale(0.9); - transition: 0.3s; -} - -input:checked + label { - background: #00000033; -} - -input:checked + label:after { - left: 100%; - transform: scale(0.9) translateX(-110%); + span { + margin-left: var(--nav-gap); + color: var(--topbar-color); + } + + #legend { + display: flex; + align-items: center; + margin-right: auto; + + span { + padding: 2px 4px; + border-radius: 2px; + } + } + + #nav { + position: relative; + display: inline-block; + + &::after { + content: "▼"; + position: absolute; + right: 1.2em; + top: 50%; + transform: translateY(-50%); + pointer-events: none; + font-size: 0.75em; + } + + select { + display: block; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--select-background); + color: var(--select-color); + border: 1px solid var(--select-border); + border-radius: 3px; + padding: 4px 24px 4px 8px; + } + } + + a.incremental { + display: flex; + background: var(--topbar-hover-color); + padding: 2px 4px; + border-radius: 2px; + box-shadow: inset 0 0 0 1px var(--topbar-hover-color); + color: var(--topbar-color); + text-decoration: none; + align-items: center; + + &:hover { + background: transparent; + box-shadow: inset 0 0 0 1px var(--topbar-hover-color); + } + + &:active { + transform: translateY(1px); + } + } + + input[type="checkbox"] { + height: 0; + width: 0; + visibility: hidden; + margin-left: var(--nav-gap); + + &:checked + label { + background: #00000033; + } + + &:checked + label:after { + left: 100%; + transform: scale(0.9) translateX(-110%); + } + } + + label { + cursor: pointer; + height: 20px; + width: 50px; + border-radius: 15px; + background: #afb8c133; + display: block; + position: relative; + margin-right: 15px; + margin-right: var(--nav-gap); + + &:after { + content: ""; + position: absolute; + top: 0; + left: 0; + height: 100%; + aspect-ratio: 1 / 1; + background: #fff; + border-radius: 50%; + transform: scale(0.9); + transition: 0.3s; + } + } } #content { padding: 15px 0; line-height: 1.67em; -} - -pre { - position: relative; - background: transparent !important; -} - -pre .code, -pre .coverage { - display: flex; - flex-direction: row; - gap: 10px; -} - -pre .coverage { - color: transparent !important; - background: transparent; -} - -pre .gutter { - display: flex; - flex-direction: column; - text-align: right; - color: var(--gutter-color); - min-width: 72px; -} - -pre .code .gutter { - opacity: 0; -} - -pre .gutter div { - padding: 0 16px; -} - -pre .editor { - background: transparent !important; -} - -.cov0 { - color: rgba(0, 0, 0, 0) !important; - background-color: color-mix( - in srgb, - var(--uncovered) 10%, - transparent - ) !important; -} - -.cov0 .ln { - color: var(--uncovered) !important; - background-color: color-mix( - in srgb, - var(--uncovered) 10%, - transparent - ) !important; -} - -.cov1, -.cov2, -.cov3, -.cov4, -.cov5, -.cov6, -.cov7, -.cov8, -.cov9, -.cov10 { - color: rgba(0, 0, 0, 0) !important; - background-color: color-mix( - in srgb, - var(--covered) 10%, - transparent - ) !important; -} -.cov1 .ln, -.cov2 .ln, -.cov3 .ln, -.cov4 .ln, -.cov5 .ln, -.cov6 .ln, -.cov7 .ln, -.cov8 .ln, -.cov9 .ln, -.cov10 .ln { - color: green !important; - background-color: color-mix( - in srgb, - var(--covered) 10%, - transparent - ) !important; + pre { + position: relative; + background: transparent !important; + + .code, + .coverage { + display: flex; + flex-direction: row; + gap: 10px; + } + + .code { + .gutter { + opacity: 0; + } + + .editor { + background: transparent !important; + } + } + + .coverage { + color: transparent !important; + background: transparent; + + .editor { + .cov { + display: inline-block; + transform: scale(1.05, 1); + border-radius: 2px; + color: transparent; + } + } + } + + .gutter { + display: flex; + flex-direction: column; + /* min-width: 72px; */ + color: var(--gutter-color); + + .ln { + text-align: right; + padding: 0 16px; + } + } + + .cov-covered { + color: var(--covered); + background-color: color-mix(in srgb, var(--covered) 10%, transparent); + } + + .cov-uncovered { + color: var(--uncovered); + background-color: color-mix(in srgb, var(--uncovered) 20%, transparent); + } + + .cov-mixed { + color: var(--mixed); + background-color: color-mix(in srgb, var(--mixed) 10%, transparent); + } + } } diff --git a/assets/index.js b/assets/index.js index 6952b03..9671519 100644 --- a/assets/index.js +++ b/assets/index.js @@ -5,6 +5,7 @@ let loading = load([ "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css", "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css", "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js", + "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/go.min.js", "../index.css?" + document.querySelector('script[src*="index.js"]').src.split('?').pop(), ]); @@ -26,18 +27,54 @@ function main() { return } - configureIncrementalButton() - configureSelectFix() - configureTheme() + // layout + configureFileSelect() + addIncrementalButton() + addThemeButton() + configureCodeBlocks() - highlight('.code .editor') + configureSyntaxHighlight('pre .code .editor') + addCoverageSpans('pre .coverage span') addLineNumbers() // setup complete, restore the page visibility document.documentElement.style.setProperty('opacity', '1') } -function configureIncrementalButton() { +function addCoverageSpans(cssSelector) { + let spans = Array.from(document.querySelectorAll(cssSelector)) + + spans.forEach((span) => { + let html = span.innerHTML + let lines = html.split('\n') + let covClass = span.classList[0] === 'cov0' ? 'cov-uncovered' : 'cov-covered' + + for (let i = 0; i < lines.length; i++) { + let trimmed = lines[i].trim() + let [start, end] = lines[i].split(trimmed) + + if (trimmed[0] === '{' || trimmed[0] === '}') { + trimmed = trimmed.slice(1).trim() + start = lines[i].replace(trimmed, '') + } + + if (trimmed[trimmed.length - 1] === '{' || trimmed[trimmed.length - 1] === '}') { + trimmed = trimmed.slice(0, -1).trim() + end = lines[i].split(trimmed)[1] + } + + if (trimmed === '') { + lines[i] = `${start || ''}${trimmed}${end || ''}` + } else { + lines[i] = `${start || ''}${trimmed}${end || ''}` + } + } + + span.innerHTML = lines.join('\n') + }) +} + +function addIncrementalButton() { let url = window.location.href let isInc = url.includes('-inc.html') @@ -62,7 +99,7 @@ function configureIncrementalButton() { document.querySelector('#topbar').appendChild(link) } -function configureSelectFix() { +function configureFileSelect() { let files = document.getElementById('files') files.addEventListener('change', (e) => { @@ -74,7 +111,7 @@ function configureSelectFix() { files.dispatchEvent(new Event('change')) } -function configureTheme() { +function addThemeButton() { let isDark = localStorage.getItem('dark') === 'true' let switchInput = document.createElement('input') @@ -113,24 +150,23 @@ function toggleDarkMode() { } function configureCodeBlocks() { - let pres = Array.from(document.querySelectorAll('#content pre')) - - pres.forEach((pre) => { + document.querySelectorAll('#content pre').forEach((pre) => { let gutter = document.createElement('div') gutter.classList.add('gutter') let editor = document.createElement('div') - editor.classList.add('editor') - editor.innerHTML = pre.innerHTML + editor.classList.add('editor', 'language-go') + editor.innerHTML = pre.innerHTML.replaceAll(' ', ' ') let code = document.createElement('div') - code.classList = 'code' code.appendChild(gutter) code.appendChild(editor) let coverage = code.cloneNode(true) coverage.classList = 'coverage' + editor.innerHTML = editor.textContent + code.classList = 'code' code.style.setProperty('position', 'absolute') code.style.setProperty('top', '0') code.style.setProperty('left', '0') @@ -141,49 +177,73 @@ function configureCodeBlocks() { }) } -function highlight(cssSelector) { +function configureSyntaxHighlight(cssSelector) { hljs.configure({ cssSelector, ignoreUnescapedHTML: true }) hljs.highlightAll() } function addLineNumbers() { - let containers = Array.from(document.querySelectorAll('#content pre > div')) - - containers.forEach((container) => { - let gutter = container.querySelector('.gutter') - let editor = container.querySelector('.editor') - let code = editor.innerHTML.replaceAll(' ', ' ') - let lines = code.split('\n') - let linesCount = lines.length - let gutterHtml = '' - - editor.innerHTML = lines - .map((line) => `${line}`) - .join('\n') - - let lineStarts = Array.from(editor.querySelectorAll('.line-start')) - - for (let i = 0; i < linesCount; i++) { - let backgroundColor = window.getComputedStyle( - lineStarts[i].parentElement, - ).backgroundColor - let textColor = backgroundColor.replace(' / 0.1', ' / 1') - - if (textColor === 'rgba(0, 0, 0, 0)') { - gutterHtml += `
${i + 1}
` - } else { - gutterHtml += `
${i + 1}
` - } + let code = document.querySelector('#content pre > div.coverage') + let gutter = code.querySelector('.gutter') + let editor = code.querySelector('.editor') + let lines = editor.innerHTML.split('\n') + let gutterHtml = '' + + // this function has two goals: + // 1. add line numbers to the gutter + // 2. assign a color to the line number based on the coverage of the line + // + // first, we add a .line-start span to each line in the editor. + // this allows us to group the spans in the editor by line. + // if a line has only one span, we assign the color of the span to the line number in the gutter. + // if a line has more than one span and they have different background colors, + // we can assume that the line has multiple statements with multiple coverage states + // and we assign a yellow-ish color to the line number in the gutter. + + editor.innerHTML = lines + .map((line) => `${line}`) + .join('\n') + + let lineNumber = 1 + let spansInLine + let spans = Array.from(editor.querySelectorAll('span')) + + for (let i = 0; i < spans.length; i++) { + let currentSpan = spans[i] + let nextSpan = spans[i + 1] + + if (currentSpan.classList.contains('line-start')) { + spansInLine = [] } - gutter.innerHTML = gutterHtml - }) + if (nextSpan?.classList?.contains('cov')) { + spansInLine.push(nextSpan) + continue + } + + if (!nextSpan?.classList.contains('line-start')) { + continue + } + + let classes = new Set(spansInLine.map((el) => el.classList[1])) + let className = classes.size > 1 ? 'cov-mixed' : classes.values().next().value || '' + + gutterHtml += `
${lineNumber}
` + + lineNumber++ + } + + gutterHtml += `
${lineNumber}
` + gutter.innerHTML = gutterHtml + + // add line numbers to the coverage gutter + document.querySelector('#content pre > div.code > .gutter').innerHTML = gutterHtml } function loadScript(src, state) { let script = document.createElement('script') script.src = src - script.async = true + script.async = false script.onload = () => { console.info(`loaded: ${src}`) state.loaded++