code cov/source code view

This commit is contained in:
Theis Pieter Hollebeek 2024-12-06 11:57:26 +01:00
parent a115cdf78c
commit 5d029967ed
7 changed files with 171 additions and 587 deletions

1
web/public/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
dist/

View File

@ -1,6 +1,11 @@
{
"tasks": {
"bundle": "deno run -A bundle.ts",
"dev": "deno run --watch -A bundle.ts"
},
"compilerOptions": {
"lib": ["dom", "dom.iterable", "dom.asynciterable"]
"checkJs": false,
"lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"]
},
"fmt": {
"indentWidth": 4

View File

@ -1,27 +1,32 @@
// src/index.ts
var codeCoverageDiv = document.querySelector("#code-coverage");
var flameGraphDiv = document.querySelector("#flame-graph");
function drawText(text, codeCoverageData) {
const tooltip = document.getElementById("covers-tooltip");
function loadCodeCoverage(text, codeCoverageData, codeCoverageDiv) {
const tooltip = document.createElement("span");
tooltip.id = "covers-tooltip";
codeCoverageDiv.append(tooltip);
const entries = codeCoverageData.toSorted((a, b) => b.index - a.index);
const charEntries = {};
const elements = [];
let line = 1;
let col = 1;
for (let index = 0; index < text.length; ++index) {
if (text[index] == "\n") {
if (text[index] === "\n") {
col = 1;
line += 1;
elements.push("\n");
const newlineSpan = document.createElement("span");
newlineSpan.innerText = "\n";
elements.push(newlineSpan);
continue;
}
const entry = entries.find((entry2) => index >= entry2.index);
if (!entry) {
throw new Error("unreachable");
}
charEntries[`${line}-${col}`] = entry;
const color = (ratio) => `rgba(${255 - 255 * ratio}, ${255 * ratio}, 125, 0.5)`;
const span = document.createElement("span");
span.style.backgroundColor = color(Math.min(entry.covers / 25, 1));
span.innerText = text[index];
span.dataset.covers = entry.covers;
span.dataset.covers = entry.covers.toString();
elements.push(span);
col += 1;
}
@ -30,16 +35,18 @@ function drawText(text, codeCoverageData) {
const outside = x < boundingRect.left || x >= boundingRect.right || y < boundingRect.top || y >= boundingRect.bottom;
return !outside;
}
testytestytesty.append(...elements);
codeCoverageDiv.append(...elements);
document.addEventListener("mousemove", (event) => {
const [x, y] = [event.clientX, event.clientY];
const outerBox = testytestytesty.getBoundingClientRect();
const outerBox = codeCoverageDiv.getBoundingClientRect();
if (!positionInBox([x, y], outerBox)) {
console.log("+");
return;
}
const element = elements.find((element2) => {
if (!element2.dataset?.covers) {
if (typeof element2 === "string") {
return false;
}
if (!element2.dataset.covers) {
return false;
}
const isIn = positionInBox([x, y], element2.getBoundingClientRect());
@ -56,127 +63,8 @@ function drawText(text, codeCoverageData) {
tooltip.innerText = `Ran ${covers} time${covers !== 1 ? "s" : ""}`;
});
}
function loadCodeCoverage(text, codeCoverageData) {
codeCoverageDiv.innerHTML = `
<canvas id="code-coverage-canvas"></canvas>
<pre><code>${text}</code></pre>
<span id="covers-tooltip" hidden></span>
`;
const canvas = document.querySelector("#code-coverage-canvas");
canvas.width = 1e3;
canvas.height = 500;
const ctx = canvas.getContext("2d");
ctx.font = "20px monospace";
const { width: chWidth } = ctx.measureText("-");
const chHeight = 23;
const color = (ratio) => `rgba(${255 - 255 * ratio}, ${255 * ratio}, 125, 0.5)`;
const entries = codeCoverageData.toSorted((a, b) => b.index - a.index);
const charEntries = {};
let line = 1;
let col = 1;
for (let index = 0; index < text.length; ++index) {
if (text[index] == "\n") {
col = 1;
line += 1;
continue;
}
const entry = entries.find((entry2) => index >= entry2.index);
charEntries[`${line}-${col}`] = entry;
ctx.fillStyle = color(Math.min(entry.covers / 25, 1));
ctx.fillRect(
(col - 1) * chWidth,
(line - 1) * chHeight,
chWidth,
chHeight
);
col += 1;
}
const tooltip = document.getElementById("covers-tooltip");
canvas.addEventListener("mousemove", (e) => {
const col2 = Math.floor(e.offsetX / chWidth + 1);
const line2 = Math.floor(e.offsetY / chHeight + 1);
const key = `${line2}-${col2}`;
if (!(key in charEntries)) {
tooltip.hidden = true;
return;
}
const entry = charEntries[key];
tooltip.innerText = `Ran ${entry.covers} time${entry.covers !== 1 ? "s" : ""}`;
tooltip.style.left = `${e.clientX + 20}px`;
tooltip.style.top = `${e.clientY + 20}px`;
tooltip.hidden = false;
});
canvas.addEventListener("mouseleave", () => {
tooltip.hidden = true;
});
}
function loadFlameGraph(flameGraphData, fnNames) {
flameGraphDiv.innerHTML = `
<canvas id="flame-graph-canvas"></canvas>
<span id="flame-graph-tooltip" hidden></span>
`;
const canvas = document.querySelector("#flame-graph-canvas");
canvas.width = 1e3;
canvas.height = 500;
const ctx = canvas.getContext("2d");
ctx.font = "16px monospace";
const nodes = [];
function calculateNodeRects(node, depth, totalAcc, offsetAcc) {
const x = offsetAcc / totalAcc * canvas.width;
const y = canvas.height - 30 * depth - 30;
const w = (node.acc + 1) / totalAcc * canvas.width;
const h = 30;
const title = fnNames[node.fn];
const percent = `${(node.acc / totalAcc * 100).toFixed(1)}%`;
nodes.push({ x, y, w, h, title, percent });
const totalChildrenAcc = node.children.reduce(
(acc, child) => acc + child.acc,
0
);
let newOffsetAcc = offsetAcc + (node.acc - totalChildrenAcc) / 2;
for (const child of node.children) {
calculateNodeRects(child, depth + 1, totalAcc, newOffsetAcc);
newOffsetAcc += child.acc;
}
}
calculateNodeRects(flameGraphData, 0, flameGraphData.acc, 0);
for (const node of nodes) {
const { x, y, w, h, title } = node;
ctx.fillStyle = "rgb(255, 125, 0)";
ctx.fillRect(
x + 1,
y + 1,
w - 2,
h - 2
);
ctx.fillStyle = "black";
ctx.fillText(
title,
x + (w - 10) / 2 - ctx.measureText(title).width / 2 + 5,
y + 20
);
}
const tooltip = document.getElementById("flame-graph-tooltip");
canvas.addEventListener("mousemove", (e) => {
const x = e.offsetX;
const y = e.offsetY;
const node = nodes.find(
(node2) => x >= node2.x && x < node2.x + node2.w && y >= node2.y && y < node2.y + node2.h
);
if (!node) {
tooltip.hidden = true;
return;
}
tooltip.innerText = `${node.title} ${node.percent}`;
tooltip.style.left = `${e.clientX + 20}px`;
tooltip.style.top = `${e.clientY + 20}px`;
tooltip.hidden = false;
});
canvas.addEventListener("mouseleave", () => {
tooltip.hidden = true;
});
}
var codeData = `fn add(a, b) {
function main() {
const codeData = `fn add(a, b) {
+ a b
}
@ -190,25 +78,41 @@ loop {
i = + i 1;
}
`;
function main() {
const codeCoverageData = JSON.parse(
`[{"index":0,"line":1,"col":1,"covers":2},{"index":28,"line":5,"col":1,"covers":1},{"index":44,"line":6,"col":1,"covers":1},{"index":55,"line":7,"col":1,"covers":1},{"index":66,"line":8,"col":5,"covers":11},{"index":104,"line":11,"col":5,"covers":10},{"index":19,"line":2,"col":5,"covers":10},{"index":133,"line":12,"col":5,"covers":10},{"index":87,"line":9,"col":9,"covers":1}]`
);
const flameGraphData = JSON.parse(
`{"fn":0,"acc":257,"parent":0,"children":[{"fn":18,"acc":251,"parent":0,"children":[{"fn":12,"acc":30,"parent":1,"children":[]}]}]}`
);
const viewRadios = document.querySelectorAll('input[name="views"]');
const view = document.querySelector("#view");
const renderFunctions = {
"source-code": () => {
const code = document.createElement("pre");
code.innerHTML = codeData;
view.replaceChildren(code);
},
"code-coverage": () => {
const codeCoverageElement = document.createElement("pre");
loadCodeCoverage(codeData, codeCoverageData, codeCoverageElement);
const view2 = document.querySelector("#view");
view2.replaceChildren(codeCoverageElement);
},
"flame-graph": () => {
}
};
const viewRadios = document.querySelectorAll(
'input[name="views"]'
);
for (const input of viewRadios) {
input.addEventListener("input", (ev) => {
console.log(ev);
const target = ev.target;
const value = target.value;
renderFunctions[value]();
});
if (input.checked) {
const value = input.value;
renderFunctions[value]();
}
}
loadCodeCoverage(codeData, codeCoverageData);
loadFlameGraph(flameGraphData, {
0: "<entry>",
12: "add",
18: "main"
});
drawText(codeData, codeCoverageData);
}
main();

View File

@ -1,318 +0,0 @@
// https://deno.land/std@0.185.0/bytes/bytes_list.ts
var BytesList = class {
#len = 0;
#chunks = [];
constructor() {
}
/**
* Total size of bytes
*/
size() {
return this.#len;
}
/**
* Push bytes with given offset infos
*/
add(value, start = 0, end = value.byteLength) {
if (value.byteLength === 0 || end - start === 0) {
return;
}
checkRange(start, end, value.byteLength);
this.#chunks.push({
value,
end,
start,
offset: this.#len
});
this.#len += end - start;
}
/**
* Drop head `n` bytes.
*/
shift(n) {
if (n === 0) {
return;
}
if (this.#len <= n) {
this.#chunks = [];
this.#len = 0;
return;
}
const idx = this.getChunkIndex(n);
this.#chunks.splice(0, idx);
const [chunk] = this.#chunks;
if (chunk) {
const diff = n - chunk.offset;
chunk.start += diff;
}
let offset = 0;
for (const chunk2 of this.#chunks) {
chunk2.offset = offset;
offset += chunk2.end - chunk2.start;
}
this.#len = offset;
}
/**
* Find chunk index in which `pos` locates by binary-search
* returns -1 if out of range
*/
getChunkIndex(pos) {
let max = this.#chunks.length;
let min = 0;
while (true) {
const i = min + Math.floor((max - min) / 2);
if (i < 0 || this.#chunks.length <= i) {
return -1;
}
const { offset, start, end } = this.#chunks[i];
const len = end - start;
if (offset <= pos && pos < offset + len) {
return i;
} else if (offset + len <= pos) {
min = i + 1;
} else {
max = i - 1;
}
}
}
/**
* Get indexed byte from chunks
*/
get(i) {
if (i < 0 || this.#len <= i) {
throw new Error("out of range");
}
const idx = this.getChunkIndex(i);
const { value, offset, start } = this.#chunks[idx];
return value[start + i - offset];
}
/**
* Iterator of bytes from given position
*/
*iterator(start = 0) {
const startIdx = this.getChunkIndex(start);
if (startIdx < 0)
return;
const first = this.#chunks[startIdx];
let firstOffset = start - first.offset;
for (let i = startIdx; i < this.#chunks.length; i++) {
const chunk = this.#chunks[i];
for (let j = chunk.start + firstOffset; j < chunk.end; j++) {
yield chunk.value[j];
}
firstOffset = 0;
}
}
/**
* Returns subset of bytes copied
*/
slice(start, end = this.#len) {
if (end === start) {
return new Uint8Array();
}
checkRange(start, end, this.#len);
const result = new Uint8Array(end - start);
const startIdx = this.getChunkIndex(start);
const endIdx = this.getChunkIndex(end - 1);
let written = 0;
for (let i = startIdx; i <= endIdx; i++) {
const {
value: chunkValue,
start: chunkStart,
end: chunkEnd,
offset: chunkOffset
} = this.#chunks[i];
const readStart = chunkStart + (i === startIdx ? start - chunkOffset : 0);
const readEnd = i === endIdx ? end - chunkOffset + chunkStart : chunkEnd;
const len = readEnd - readStart;
result.set(chunkValue.subarray(readStart, readEnd), written);
written += len;
}
return result;
}
/**
* Concatenate chunks into single Uint8Array copied.
*/
concat() {
const result = new Uint8Array(this.#len);
let sum = 0;
for (const { value, start, end } of this.#chunks) {
result.set(value.subarray(start, end), sum);
sum += end - start;
}
return result;
}
};
function checkRange(start, end, len) {
if (start < 0 || len < start || end < 0 || len < end || end < start) {
throw new Error("invalid range");
}
}
// https://deno.land/std@0.185.0/bytes/concat.ts
function concat(...buf) {
let length = 0;
for (const b of buf) {
length += b.length;
}
const output = new Uint8Array(length);
let index = 0;
for (const b of buf) {
output.set(b, index);
index += b.length;
}
return output;
}
// https://deno.land/std@0.185.0/bytes/copy.ts
function copy(src, dst, off = 0) {
off = Math.max(0, Math.min(off, dst.byteLength));
const dstBytesAvailable = dst.byteLength - off;
if (src.byteLength > dstBytesAvailable) {
src = src.subarray(0, dstBytesAvailable);
}
dst.set(src, off);
return src.byteLength;
}
// https://deno.land/std@0.185.0/bytes/ends_with.ts
function endsWith(source, suffix) {
for (let srci = source.length - 1, sfxi = suffix.length - 1; sfxi >= 0; srci--, sfxi--) {
if (source[srci] !== suffix[sfxi])
return false;
}
return true;
}
// https://deno.land/std@0.185.0/bytes/equals.ts
function equalsNaive(a, b) {
for (let i = 0; i < b.length; i++) {
if (a[i] !== b[i])
return false;
}
return true;
}
function equals32Bit(a, b) {
const len = a.length;
const compressable = Math.floor(len / 4);
const compressedA = new Uint32Array(a.buffer, 0, compressable);
const compressedB = new Uint32Array(b.buffer, 0, compressable);
for (let i = compressable * 4; i < len; i++) {
if (a[i] !== b[i])
return false;
}
for (let i = 0; i < compressedA.length; i++) {
if (compressedA[i] !== compressedB[i])
return false;
}
return true;
}
function equals(a, b) {
if (a.length !== b.length) {
return false;
}
return a.length < 1e3 ? equalsNaive(a, b) : equals32Bit(a, b);
}
// https://deno.land/std@0.185.0/bytes/index_of_needle.ts
function indexOfNeedle(source, needle, start = 0) {
if (start >= source.length) {
return -1;
}
if (start < 0) {
start = Math.max(0, source.length + start);
}
const s = needle[0];
for (let i = start; i < source.length; i++) {
if (source[i] !== s)
continue;
const pin = i;
let matched = 1;
let j = i;
while (matched < needle.length) {
j++;
if (source[j] !== needle[j - pin]) {
break;
}
matched++;
}
if (matched === needle.length) {
return pin;
}
}
return -1;
}
// https://deno.land/std@0.185.0/bytes/includes_needle.ts
function includesNeedle(source, needle, start = 0) {
return indexOfNeedle(source, needle, start) !== -1;
}
// https://deno.land/std@0.185.0/bytes/last_index_of_needle.ts
function lastIndexOfNeedle(source, needle, start = source.length - 1) {
if (start < 0) {
return -1;
}
if (start >= source.length) {
start = source.length - 1;
}
const e = needle[needle.length - 1];
for (let i = start; i >= 0; i--) {
if (source[i] !== e)
continue;
const pin = i;
let matched = 1;
let j = i;
while (matched < needle.length) {
j--;
if (source[j] !== needle[needle.length - 1 - (pin - j)]) {
break;
}
matched++;
}
if (matched === needle.length) {
return pin - needle.length + 1;
}
}
return -1;
}
// https://deno.land/std@0.185.0/bytes/repeat.ts
function repeat(source, count) {
if (count === 0) {
return new Uint8Array();
}
if (count < 0) {
throw new RangeError("bytes: negative repeat count");
}
if (!Number.isInteger(count)) {
throw new Error("bytes: repeat count must be an integer");
}
const nb = new Uint8Array(source.length * count);
let bp = copy(source, nb);
for (; bp < nb.length; bp *= 2) {
copy(nb.slice(0, bp), nb, bp);
}
return nb;
}
// https://deno.land/std@0.185.0/bytes/starts_with.ts
function startsWith(source, prefix) {
for (let i = 0, max = prefix.length; i < max; i++) {
if (source[i] !== prefix[i])
return false;
}
return true;
}
export {
BytesList,
concat,
copy,
endsWith,
equals,
includesNeedle,
indexOfNeedle,
lastIndexOfNeedle,
repeat,
startsWith
};

View File

@ -26,11 +26,11 @@
</div>
</nav>
<main id="view">
<div id="code-coverage"></div>
<div id="flame-graph"></div>
<pre id="testytestytesty"></pre>
<pre id="code-coverage"></pre>
<div id="cover">Process is currently running</div>
</main>
<span id="covers-tooltip"></span>
</div>
</div>
</body>

View File

@ -1,59 +1,78 @@
const codeCoverageDiv = document.querySelector("#code-coverage");
const flameGraphDiv = document.querySelector("#flame-graph");
type CodeCovEntry = {
index: number;
line: number;
col: number;
covers: number;
};
function drawText(text: string, codeCoverageData: CodeCovEntry[]) {
const tooltip = document.getElementById("covers-tooltip");
function loadCodeCoverage(
text: string,
codeCoverageData: CodeCovEntry[],
codeCoverageDiv: HTMLPreElement,
) {
const tooltip = document.createElement("span");
tooltip.id = "covers-tooltip";
codeCoverageDiv.append(tooltip);
const entries = codeCoverageData.toSorted((
a: CodeCovEntry,
b: CodeCovEntry,
) => b.index - a.index);
const charEntries = {};
const elements = [];
const charEntries: { [key: string]: CodeCovEntry } = {};
const elements: HTMLElement[] = [];
let line = 1;
let col = 1;
for (let index = 0; index < text.length; ++index) {
if (text[index] == "\n") {
if (text[index] === "\n") {
col = 1;
line += 1;
elements.push("\n");
const newlineSpan = document.createElement("span");
newlineSpan.innerText = "\n";
elements.push(newlineSpan);
continue;
}
const entry = entries.find((entry) => index >= entry.index);
if (!entry) {
throw new Error("unreachable");
}
charEntries[`${line}-${col}`] = entry;
const color = (ratio) =>
const color = (ratio: number) =>
`rgba(${255 - 255 * ratio}, ${255 * ratio}, 125, 0.5)`;
const span = document.createElement("span");
span.style.backgroundColor = color(Math.min(entry.covers / 25, 1));
span.innerText = text[index];
span.dataset.covers = entry.covers;
span.dataset.covers = entry.covers.toString();
elements.push(span);
col += 1;
}
function positionInBox(position, boundingRect) {
function positionInBox(
position: [number, number],
boundingRect: {
left: number;
top: number;
right: number;
bottom: number;
},
) {
const [x, y] = position;
const outside = x < boundingRect.left ||
x >= boundingRect.right || y < boundingRect.top ||
y >= boundingRect.bottom;
return !outside;
}
testytestytesty.append(...elements);
codeCoverageDiv.append(...elements);
document.addEventListener("mousemove", (event) => {
const [x, y] = [event.clientX, event.clientY];
const outerBox = testytestytesty.getBoundingClientRect();
const outerBox = codeCoverageDiv.getBoundingClientRect();
if (!positionInBox([x, y], outerBox)) {
console.log("+");
return;
}
const element = elements.find((element) => {
if (!element.dataset?.covers) {
if (typeof element === "string") {
return false;
}
if (!element.dataset.covers) {
return false;
}
const isIn = positionInBox([x, y], element.getBoundingClientRect());
@ -71,91 +90,34 @@ function drawText(text: string, codeCoverageData: CodeCovEntry[]) {
});
}
function loadCodeCoverage(text, codeCoverageData) {
codeCoverageDiv.innerHTML = `
<canvas id="code-coverage-canvas"></canvas>
<pre><code>${text}</code></pre>
<span id="covers-tooltip" hidden></span>
`;
/** @type { HTMLCanvasElement } */
const canvas = document.querySelector("#code-coverage-canvas");
canvas.width = 1000;
canvas.height = 500;
const ctx = canvas.getContext("2d");
ctx.font = "20px monospace";
const { width: chWidth } = ctx.measureText("-");
const chHeight = 23;
const color = (ratio) =>
`rgba(${255 - 255 * ratio}, ${255 * ratio}, 125, 0.5)`;
const entries = codeCoverageData.toSorted((a, b) => b.index - a.index);
const charEntries = {};
let line = 1;
let col = 1;
for (let index = 0; index < text.length; ++index) {
if (text[index] == "\n") {
col = 1;
line += 1;
continue;
}
const entry = entries.find((entry) => index >= entry.index);
charEntries[`${line}-${col}`] = entry;
ctx.fillStyle = color(Math.min(entry.covers / 25, 1));
ctx.fillRect(
(col - 1) * chWidth,
(line - 1) * chHeight,
chWidth,
chHeight,
);
col += 1;
}
const tooltip = document.getElementById("covers-tooltip");
canvas.addEventListener("mousemove", (e) => {
const col = Math.floor(e.offsetX / chWidth + 1);
const line = Math.floor(e.offsetY / chHeight + 1);
const key = `${line}-${col}`;
if (!(key in charEntries)) {
tooltip.hidden = true;
return;
}
const entry = charEntries[key];
tooltip.innerText = `Ran ${entry.covers} time${
entry.covers !== 1 ? "s" : ""
}`;
tooltip.style.left = `${e.clientX + 20}px`;
tooltip.style.top = `${e.clientY + 20}px`;
tooltip.hidden = false;
});
canvas.addEventListener("mouseleave", () => {
tooltip.hidden = true;
});
}
function loadFlameGraph(flameGraphData, fnNames) {
// @ts-ignore: unsure of relevant types
function loadFlameGraph(flameGraphData, fnNames, flameGraphDiv) {
flameGraphDiv.innerHTML = `
<canvas id="flame-graph-canvas"></canvas>
<span id="flame-graph-tooltip" hidden></span>
`;
/** @type { HTMLCanvasElement } */
const canvas = document.querySelector("#flame-graph-canvas");
const canvas = document.querySelector<HTMLCanvasElement>(
"#flame-graph-canvas",
)!;
canvas.width = 1000;
canvas.height = 500;
const ctx = canvas.getContext("2d");
const ctx = canvas.getContext("2d")!;
ctx.font = "16px monospace";
const nodes = [];
type Node = {
x: number;
y: number;
w: number;
h: number;
title: string;
percent: string;
};
const nodes: Node[] = [];
// @ts-ignore: unsure of relevant types
function calculateNodeRects(node, depth, totalAcc, offsetAcc) {
const x = (offsetAcc / totalAcc) * canvas.width;
const y = canvas.height - 30 * depth - 30;
@ -167,6 +129,7 @@ function loadFlameGraph(flameGraphData, fnNames) {
nodes.push({ x, y, w, h, title, percent });
const totalChildrenAcc = node.children.reduce(
// @ts-ignore: unsure of relevant types
(acc, child) => acc + child.acc,
0,
);
@ -195,7 +158,7 @@ function loadFlameGraph(flameGraphData, fnNames) {
);
}
const tooltip = document.getElementById("flame-graph-tooltip");
const tooltip = document.getElementById("flame-graph-tooltip")!;
canvas.addEventListener("mousemove", (e) => {
const x = e.offsetX;
@ -219,6 +182,7 @@ function loadFlameGraph(flameGraphData, fnNames) {
});
}
function main() {
const codeData = `\
fn add(a, b) {
+ a b
@ -235,7 +199,6 @@ loop {
}
`;
function main() {
const codeCoverageData = JSON.parse(
`[{"index":0,"line":1,"col":1,"covers":2},{"index":28,"line":5,"col":1,"covers":1},{"index":44,"line":6,"col":1,"covers":1},{"index":55,"line":7,"col":1,"covers":1},{"index":66,"line":8,"col":5,"covers":11},{"index":104,"line":11,"col":5,"covers":10},{"index":19,"line":2,"col":5,"covers":10},{"index":133,"line":12,"col":5,"covers":10},{"index":87,"line":9,"col":9,"covers":1}]`,
);
@ -244,20 +207,49 @@ function main() {
`{"fn":0,"acc":257,"parent":0,"children":[{"fn":18,"acc":251,"parent":0,"children":[{"fn":12,"acc":30,"parent":1,"children":[]}]}]}`,
);
const viewRadios = document.querySelectorAll('input[name="views"]');
type RenderFns = {
"source-code": () => void;
"code-coverage": () => void;
"flame-graph": () => void;
};
const view = document.querySelector("#view")!;
const renderFunctions: RenderFns = {
"source-code": () => {
const code = document.createElement("pre");
code.innerHTML = codeData;
view.replaceChildren(code);
},
"code-coverage": () => {
const codeCoverageElement = document.createElement("pre");
loadCodeCoverage(codeData, codeCoverageData, codeCoverageElement);
const view = document.querySelector("#view")!;
view.replaceChildren(codeCoverageElement);
},
"flame-graph": () => {},
};
const viewRadios: NodeListOf<HTMLInputElement> = document.querySelectorAll(
'input[name="views"]',
);
for (const input of viewRadios) {
input.addEventListener("input", (ev) => {
console.log(ev);
const target = ev.target as HTMLInputElement;
const value = target.value as keyof RenderFns;
renderFunctions[value]();
});
if (input.checked) {
const value = input.value as keyof RenderFns;
renderFunctions[value]();
}
}
loadCodeCoverage(codeData, codeCoverageData);
loadFlameGraph(flameGraphData, {
0: "<entry>",
12: "add",
18: "main",
});
drawText(codeData, codeCoverageData);
// loadCodeCoverage(codeData, codeCoverageData);
// loadFlameGraph(flameGraphData, {
// 0: "<entry>",
// 12: "add",
// 18: "main",
// });
}
main();

View File

@ -77,6 +77,27 @@ main #cover {
gap: 0.5rem;
}
#views-nav input {
display: none;
}
#views-nav label {
display: inline-block;
padding: 0.4em;
padding-bottom: 0.2em;
cursor: pointer;
}
#view pre {
font-size: 1rem;
}
#views-nav input:checked + label {
background-color: var(--code-status);
color: var(--black);
}
#views-layout {
display: flex;
margin: 0 auto;
@ -85,28 +106,7 @@ main #cover {
max-width: 1500px;
}
#code-coverage {
width: 1000px;
height: 500px;
background-color: rgb(240, 220, 200);
}
#code-coverage pre {
background-color: none;
}
#code-coverage code {
font-family: monospace;
color: black;
font-weight: 600;
font-size: 20px;
}
#code-coverage canvas {
z-index: 1;
width: 1000px;
height: 500px;
position: absolute;
image-rendering: pixelated;
}
#code-coverage #covers-tooltip {
#covers-tooltip {
z-index: 2;
position: fixed;
top: 0;