improve back and frontend testing

This commit is contained in:
2026-03-29 19:58:06 -07:00
parent 29eee8a42c
commit e9215a64ff
70 changed files with 13441 additions and 134 deletions

View File

@@ -0,0 +1,316 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for angleMeasureGeometry.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> angleMeasureGeometry.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">97.4% </span>
<span class="quiet">Statements</span>
<span class='fraction'>75/77</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">53.33% </span>
<span class="quiet">Branches</span>
<span class='fraction'>8/15</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>6/6</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">97.4% </span>
<span class="quiet">Lines</span>
<span class='fraction'>75/77</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">18x</span>
<span class="cline-any cline-yes">18x</span>
<span class="cline-any cline-yes">18x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">function clamp01(value) {
return Math.max(0, Math.min(1, Number(value) || 0));
}
&nbsp;
export function round3(value) {
return Number.parseFloat(Number(value).toFixed(3));
}
&nbsp;
export function getAngleLabelBasePosition(x1, y1, xm, ym, x2, y2) {
const va = { x: Number(x1) - Number(xm), y: Number(y1) - Number(ym) };
const vb = { x: Number(x2) - Number(xm), y: Number(y2) - Number(ym) };
const lenA = Math.hypot(va.x, va.y);
const lenB = Math.hypot(vb.x, vb.y);
&nbsp;
if (lenA &lt;= 1e-6 || lenB &lt;= 1e-6) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
<span class="cstat-no" title="statement not covered" > return { x: clamp01(xm), y: clamp01(Number(ym) - 0.14) };</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
const unit = {
x: (va.x / lenA) + (vb.x / lenB),
y: (va.y / lenA) + (vb.y / lenB),
};
const unitLength = Math.hypot(unit.x, unit.y);
const bisector = unitLength &lt;= 1e-6
<span class="branch-0 cbranch-no" title="branch not covered" > ? { x: 0, y: -1 }</span>
: { x: unit.x / unitLength, y: unit.y / unitLength };
&nbsp;
return {
x: clamp01(Number(xm) + bisector.x * 0.14),
y: clamp01(Number(ym) + bisector.y * 0.14),
};
}
&nbsp;
export function getAngleLabelPosition(points, labelDx = 0, labelDy = 0) {
const base = getAngleLabelBasePosition(points.x1, points.y1, points.xm, points.ym, points.x2, points.y2);
return {
x: clamp01(base.x + (Number(labelDx) <span class="branch-0 cbranch-no" title="branch not covered" >|| 0)</span>),
y: clamp01(base.y + (Number(labelDy) <span class="branch-0 cbranch-no" title="branch not covered" >|| 0)</span>),
};
}
&nbsp;
export function moveAngleWidget(points, dx, dy) {
const nextDx = Number(dx) <span class="branch-0 cbranch-no" title="branch not covered" >|| 0;</span>
const nextDy = Number(dy) <span class="branch-0 cbranch-no" title="branch not covered" >|| 0;</span>
const xs = [points.x1, points.xm, points.x2];
const ys = [points.y1, points.ym, points.y2];
const minX = Math.min(...xs);
const maxX = Math.max(...xs);
const minY = Math.min(...ys);
const maxY = Math.max(...ys);
const clampedDx = Math.max(-minX, Math.min(1 - maxX, nextDx));
const clampedDy = Math.max(-minY, Math.min(1 - maxY, nextDy));
&nbsp;
return {
x1: round3(clamp01(points.x1 + clampedDx)),
y1: round3(clamp01(points.y1 + clampedDy)),
xm: round3(clamp01(points.xm + clampedDx)),
ym: round3(clamp01(points.ym + clampedDy)),
x2: round3(clamp01(points.x2 + clampedDx)),
y2: round3(clamp01(points.y2 + clampedDy)),
};
}
&nbsp;
export function measureAngleDegrees(x1, y1, xm, ym, x2, y2) {
const ax = Number(x1) - Number(xm);
const ay = Number(y1) - Number(ym);
const bx = Number(x2) - Number(xm);
const by = Number(y2) - Number(ym);
const lenA = Math.hypot(ax, ay);
const lenB = Math.hypot(bx, by);
&nbsp;
if (lenA &lt;= 1e-12 || lenB &lt;= 1e-12) <span class="branch-0 cbranch-no" title="branch not covered" >return 0;</span>
&nbsp;
const cosTheta = ((ax * bx) + (ay * by)) / (lenA * lenB);
const clamped = Math.max(-1, Math.min(1, cosTheta));
return Math.acos(clamped) * (180 / Math.PI);
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,224 @@
body, html {
margin:0; padding: 0;
height: 100%;
}
body {
font-family: Helvetica Neue, Helvetica, Arial;
font-size: 14px;
color:#333;
}
.small { font-size: 12px; }
*, *:after, *:before {
-webkit-box-sizing:border-box;
-moz-box-sizing:border-box;
box-sizing:border-box;
}
h1 { font-size: 20px; margin: 0;}
h2 { font-size: 14px; }
pre {
font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
margin: 0;
padding: 0;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
}
a { color:#0074D9; text-decoration:none; }
a:hover { text-decoration:underline; }
.strong { font-weight: bold; }
.space-top1 { padding: 10px 0 0 0; }
.pad2y { padding: 20px 0; }
.pad1y { padding: 10px 0; }
.pad2x { padding: 0 20px; }
.pad2 { padding: 20px; }
.pad1 { padding: 10px; }
.space-left2 { padding-left:55px; }
.space-right2 { padding-right:20px; }
.center { text-align:center; }
.clearfix { display:block; }
.clearfix:after {
content:'';
display:block;
height:0;
clear:both;
visibility:hidden;
}
.fl { float: left; }
@media only screen and (max-width:640px) {
.col3 { width:100%; max-width:100%; }
.hide-mobile { display:none!important; }
}
.quiet {
color: #7f7f7f;
color: rgba(0,0,0,0.5);
}
.quiet a { opacity: 0.7; }
.fraction {
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 10px;
color: #555;
background: #E8E8E8;
padding: 4px 5px;
border-radius: 3px;
vertical-align: middle;
}
div.path a:link, div.path a:visited { color: #333; }
table.coverage {
border-collapse: collapse;
margin: 10px 0 0 0;
padding: 0;
}
table.coverage td {
margin: 0;
padding: 0;
vertical-align: top;
}
table.coverage td.line-count {
text-align: right;
padding: 0 5px 0 20px;
}
table.coverage td.line-coverage {
text-align: right;
padding-right: 10px;
min-width:20px;
}
table.coverage td span.cline-any {
display: inline-block;
padding: 0 5px;
width: 100%;
}
.missing-if-branch {
display: inline-block;
margin-right: 5px;
border-radius: 3px;
position: relative;
padding: 0 4px;
background: #333;
color: yellow;
}
.skip-if-branch {
display: none;
margin-right: 10px;
position: relative;
padding: 0 4px;
background: #ccc;
color: white;
}
.missing-if-branch .typ, .skip-if-branch .typ {
color: inherit !important;
}
.coverage-summary {
border-collapse: collapse;
width: 100%;
}
.coverage-summary tr { border-bottom: 1px solid #bbb; }
.keyline-all { border: 1px solid #ddd; }
.coverage-summary td, .coverage-summary th { padding: 10px; }
.coverage-summary tbody { border: 1px solid #bbb; }
.coverage-summary td { border-right: 1px solid #bbb; }
.coverage-summary td:last-child { border-right: none; }
.coverage-summary th {
text-align: left;
font-weight: normal;
white-space: nowrap;
}
.coverage-summary th.file { border-right: none !important; }
.coverage-summary th.pct { }
.coverage-summary th.pic,
.coverage-summary th.abs,
.coverage-summary td.pct,
.coverage-summary td.abs { text-align: right; }
.coverage-summary td.file { white-space: nowrap; }
.coverage-summary td.pic { min-width: 120px !important; }
.coverage-summary tfoot td { }
.coverage-summary .sorter {
height: 10px;
width: 7px;
display: inline-block;
margin-left: 0.5em;
background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
}
.coverage-summary .sorted .sorter {
background-position: 0 -20px;
}
.coverage-summary .sorted-desc .sorter {
background-position: 0 -10px;
}
.status-line { height: 10px; }
/* yellow */
.cbranch-no { background: yellow !important; color: #111; }
/* dark red */
.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
.low .chart { border:1px solid #C21F39 }
.highlighted,
.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
background: #C21F39 !important;
}
/* medium red */
.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
/* light red */
.low, .cline-no { background:#FCE1E5 }
/* light green */
.high, .cline-yes { background:rgb(230,245,208) }
/* medium green */
.cstat-yes { background:rgb(161,215,106) }
/* dark green */
.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
.high .chart { border:1px solid rgb(77,146,33) }
/* dark yellow (gold) */
.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
.medium .chart { border:1px solid #f9cd0b; }
/* light yellow */
.medium { background: #fff4c2; }
.cstat-skip { background: #ddd; color: #111; }
.fstat-skip { background: #ddd; color: #111 !important; }
.cbranch-skip { background: #ddd !important; color: #111; }
span.cline-neutral { background: #eaeaea; }
.coverage-summary td.empty {
opacity: .5;
padding-top: 4px;
padding-bottom: 4px;
line-height: 1;
color: #888;
}
.cover-fill, .cover-empty {
display:inline-block;
height: 12px;
}
.chart {
line-height: 0;
}
.cover-empty {
background: white;
}
.cover-full {
border-right: none !important;
}
pre.prettyprint {
border: none !important;
padding: 0 !important;
margin: 0 !important;
}
.com { color: #999 !important; }
.ignore-none { color: #999; font-weight: normal; }
.wrapper {
min-height: 100%;
height: auto !important;
height: 100%;
margin: 0 auto -48px;
}
.footer, .push {
height: 48px;
}

View File

@@ -0,0 +1,87 @@
/* eslint-disable */
var jumpToCode = (function init() {
// Classes of code we would like to highlight in the file view
var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
// Elements to highlight in the file listing view
var fileListingElements = ['td.pct.low'];
// We don't want to select elements that are direct descendants of another match
var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
// Selector that finds elements on the page to which we can jump
var selector =
fileListingElements.join(', ') +
', ' +
notSelector +
missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
// The NodeList of matching elements
var missingCoverageElements = document.querySelectorAll(selector);
var currentIndex;
function toggleClass(index) {
missingCoverageElements
.item(currentIndex)
.classList.remove('highlighted');
missingCoverageElements.item(index).classList.add('highlighted');
}
function makeCurrent(index) {
toggleClass(index);
currentIndex = index;
missingCoverageElements.item(index).scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center'
});
}
function goToPrevious() {
var nextIndex = 0;
if (typeof currentIndex !== 'number' || currentIndex === 0) {
nextIndex = missingCoverageElements.length - 1;
} else if (missingCoverageElements.length > 1) {
nextIndex = currentIndex - 1;
}
makeCurrent(nextIndex);
}
function goToNext() {
var nextIndex = 0;
if (
typeof currentIndex === 'number' &&
currentIndex < missingCoverageElements.length - 1
) {
nextIndex = currentIndex + 1;
}
makeCurrent(nextIndex);
}
return function jump(event) {
if (
document.getElementById('fileSearch') === document.activeElement &&
document.activeElement != null
) {
// if we're currently focused on the search input, we don't want to navigate
return;
}
switch (event.which) {
case 78: // n
case 74: // j
goToNext();
break;
case 66: // b
case 75: // k
case 80: // p
goToPrevious();
break;
}
};
})();
window.addEventListener('keydown', jumpToCode);

View File

@@ -0,0 +1,232 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for canvasInteractionTargets.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> canvasInteractionTargets.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">89.79% </span>
<span class="quiet">Statements</span>
<span class='fraction'>44/49</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">68% </span>
<span class="quiet">Branches</span>
<span class='fraction'>17/25</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>7/7</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">89.79% </span>
<span class="quiet">Lines</span>
<span class='fraction'>44/49</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">42x</span>
<span class="cline-any cline-yes">42x</span>
<span class="cline-any cline-yes">42x</span>
<span class="cline-any cline-yes">42x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">42x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">const EXCLUDED_CANVAS_TARGETS = '.context-menu, .react-flow__node, .react-flow__edge, .react-flow__controls, .react-flow__minimap, .surface-view-container';
const CANVAS_AREA_TARGETS = '.react-flow, .react-flow__renderer, .react-flow__viewport, .react-flow__pane, .react-flow__background, .react-flow__selectionpane';
&nbsp;
function getTargetElement(target) {
if (!target) <span class="branch-0 cbranch-no" title="branch not covered" >return null;</span>
if (typeof target.closest === 'function') return target;
<span class="branch-0 cbranch-no" title="branch not covered" > if (target.parentElement &amp;&amp; typeof target.parentElement.closest === 'function')</span> <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
<span class="cstat-no" title="statement not covered" > return target.parentElement;</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > return null;</span>
}
&nbsp;
function supportsClosest(target) {
return !!getTargetElement(target);
}
&nbsp;
function matchesClosest(target, selector) {
const element = getTargetElement(target);
return !!element &amp;&amp; element.closest(selector) !== null;
}
&nbsp;
export function isEditableInteractionTarget(target) {
if (!supportsClosest(target)) <span class="branch-0 cbranch-no" title="branch not covered" >return false;</span>
if (matchesClosest(target, 'input, textarea, select')) return true;
return matchesClosest(target, '[contenteditable="true"]');
}
&nbsp;
export function canStartCanvasRightDragZoomTarget(target) {
if (!supportsClosest(target)) <span class="branch-0 cbranch-no" title="branch not covered" >return false;</span>
if (isEditableInteractionTarget(target)) return false;
if (matchesClosest(target, EXCLUDED_CANVAS_TARGETS)) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
<span class="cstat-no" title="statement not covered" > return false;</span>
<span class="cstat-no" title="statement not covered" > }</span>
return matchesClosest(target, CANVAS_AREA_TARGETS);
}
&nbsp;
export function canOpenCanvasContextMenuTarget(target) {
if (!supportsClosest(target)) <span class="branch-0 cbranch-no" title="branch not covered" >return false;</span>
if (isEditableInteractionTarget(target)) return false;
if (matchesClosest(target, EXCLUDED_CANVAS_TARGETS)) {
return false;
}
return matchesClosest(target, CANVAS_AREA_TARGETS);
}
&nbsp;
export function isSecondaryCanvasContextEvent(event) {
if (!event || typeof event.button !== 'number') <span class="branch-0 cbranch-no" title="branch not covered" >return false;</span>
return event.button === 2 || (event.button === 0 &amp;&amp; !!event.ctrlKey);
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,451 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for connectionUtils.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> connectionUtils.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>122/122</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">93.93% </span>
<span class="quiet">Branches</span>
<span class='fraction'>62/66</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>12/12</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>122/122</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-yes">20x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">33x</span>
<span class="cline-any cline-yes">33x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">33x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">33x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">14x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">// ── Connection utility functions ───────────────────────────────────────
// Pure functions extracted from App.jsx so they can be independently tested.
&nbsp;
import { socketSpecAcceptsType } from './constants.js';
&nbsp;
// ── Handle ID helpers ─────────────────────────────────────────────────
&nbsp;
export function getHandleType(handleId) {
return handleId.split('::')[2];
}
&nbsp;
export function getInputName(handleId) {
return handleId.split('::')[1];
}
&nbsp;
export function getOutputSlot(handleId) {
return parseInt(handleId.split('::')[1], 10);
}
&nbsp;
export function encodeProxyHandleRef(handleId) {
return encodeURIComponent(String(handleId || ''));
}
&nbsp;
export function decodeProxyHandleRef(encoded) {
try {
return decodeURIComponent(String(encoded || ''));
} catch {
return String(encoded <span class="branch-0 cbranch-no" title="branch not covered" >|| '')</span>;
}
}
&nbsp;
export function parseGroupProxyHandle(handleId) {
const text = String(handleId || '');
if (!text.startsWith('group-proxy::')) return null;
const parts = text.split('::');
if (parts.length &lt; 5) return null;
return {
direction: parts[1],
nodeId: parts[2],
type: parts[3],
realHandle: decodeProxyHandleRef(parts.slice(4).join('::')),
};
}
&nbsp;
export function getConnectionHandleType(handleId) {
const proxy = parseGroupProxyHandle(handleId);
return proxy?.type || getHandleType(handleId);
}
&nbsp;
export function getResolvedHandleRef(nodeId, handleId) {
const proxy = parseGroupProxyHandle(handleId);
return {
nodeId: proxy?.nodeId || nodeId,
handleId: proxy?.realHandle || handleId,
type: proxy?.type || getHandleType(handleId),
};
}
&nbsp;
export function getNodeInputSpecForHandle(node, handleId) {
const definition = node?.data?.definition;
if (!definition?.input) return null;
const inputName = getInputName(handleId);
return definition.input.required?.[inputName]
|| definition.input.optional?.[inputName]
|| null;
}
&nbsp;
// ── Type compatibility ────────────────────────────────────────────────
&nbsp;
export function outputTypeCanConnectToTarget(outputType, targetSpecOrType, outputAcceptedTypes = []) {
if (socketSpecAcceptsType(outputType, targetSpecOrType)) {
return true;
}
// Polymorphic output: the output socket declares it can also produce the target type
if (outputAcceptedTypes.length &gt; 0) {
const targetType = Array.isArray(targetSpecOrType) ? targetSpecOrType[0] : targetSpecOrType;
if (outputAcceptedTypes.includes(targetType)) return true;
}
return outputType === 'ANNOTATION_SOURCE'
&amp;&amp; !socketSpecAcceptsType('ANNOTATION_SOURCE', targetSpecOrType)
&amp;&amp; (
socketSpecAcceptsType('DATA_FIELD', targetSpecOrType)
|| socketSpecAcceptsType('IMAGE', targetSpecOrType)
);
}
&nbsp;
export function resolveOutputTypeForTarget(outputType, targetSpecOrType) {
if (outputType !== 'ANNOTATION_SOURCE') {
return outputType;
}
if (socketSpecAcceptsType('ANNOTATION_SOURCE', targetSpecOrType)) {
return 'ANNOTATION_SOURCE';
}
if (socketSpecAcceptsType('DATA_FIELD', targetSpecOrType)) {
return 'DATA_FIELD';
}
if (socketSpecAcceptsType('IMAGE', targetSpecOrType)) {
return 'IMAGE';
}
return 'ANNOTATION_SOURCE';
}
&nbsp;
// ── Pure connection validation ────────────────────────────────────────
// Extracted from the isValidConnection useCallback so it can be unit-tested
// without a ReactFlow context. Pass a `getNodeFn` that mirrors reactFlow.getNode.
&nbsp;
export function checkConnectionValid(connection, getNodeFn) {
const srcType = getConnectionHandleType(connection.sourceHandle);
const resolvedTarget = getResolvedHandleRef(connection.target, connection.targetHandle);
const targetNode = getNodeFn(resolvedTarget.nodeId);
const targetSpec = getNodeInputSpecForHandle(targetNode, resolvedTarget.handleId) || resolvedTarget.type;
if (socketSpecAcceptsType(srcType, targetSpec)) return true;
// Polymorphic output: check if the source output declares it can produce the target type
const srcProxy = parseGroupProxyHandle(connection.sourceHandle);
const srcNodeId = srcProxy <span class="branch-0 cbranch-no" title="branch not covered" >? srcProxy.nodeId </span>: connection.source;
const srcHandleId = srcProxy <span class="branch-0 cbranch-no" title="branch not covered" >? srcProxy.realHandle </span>: connection.sourceHandle;
const srcNode = getNodeFn(srcNodeId);
const srcSlot = getOutputSlot(srcHandleId);
const srcAcceptedTypes = srcNode?.data?.definition?.output_accepted_types?.[srcSlot] || [];
const targetType = Array.isArray(targetSpec) ? targetSpec[0] <span class="branch-0 cbranch-no" title="branch not covered" >: targetSpec;</span>
return Array.isArray(srcAcceptedTypes) &amp;&amp; srcAcceptedTypes.includes(targetType);
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,391 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for constants.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> constants.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">97.05% </span>
<span class="quiet">Statements</span>
<span class='fraction'>99/102</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">76.47% </span>
<span class="quiet">Branches</span>
<span class='fraction'>13/17</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>5/5</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">97.05% </span>
<span class="quiet">Lines</span>
<span class='fraction'>99/102</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">60x</span>
<span class="cline-any cline-yes">60x</span>
<span class="cline-any cline-yes">60x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">60x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">23x</span>
<span class="cline-any cline-yes">23x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">23x</span>
<span class="cline-any cline-yes">23x</span>
<span class="cline-any cline-yes">23x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">17x</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-yes">50x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">49x</span>
<span class="cline-any cline-yes">49x</span>
<span class="cline-any cline-yes">49x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">// ── Shared type &amp; color constants ─────────────────────────────────────
&nbsp;
export const DATA_TYPES = new Set([
'DATA_FIELD', 'IMAGE', 'LINE', 'RECORD_TABLE', 'DATA_TABLE',
'COORD', 'ANNOTATION_SOURCE', 'COLORMAP',
'MESH_MODEL', 'FONT', 'FILE_PATH', 'DIRECTORY', 'COORDPAIR',
]);
&nbsp;
export const SOCKET_WIDGET_TYPES = new Set(['FLOAT', 'INT']);
&nbsp;
export const TYPE_COLORS = {
DATA_FIELD: '#3a7abf',
IMAGE: '#00ff08a0',
LINE: '#ffbe5c',
RECORD_TABLE: '#35e2fd',
DATA_TABLE: '#ff7474',
COORD: '#e91ed1',
COORDPAIR: '#5cb861',
FLOAT: '#ab3197',
INT: '#ffffff',
ANNOTATION_SOURCE: '#06b6d4',
COLORMAP: '#f472b6',
MESH_MODEL: '#14b8a6',
FONT: '#fb7185',
FILE_PATH: '#f59e0b',
DIRECTORY: '#f97316',
};
&nbsp;
export const CAT_COLORS = {
Input: '#37474f',
Display: '#212121',
Overlay: '#0f766e',
Geometry: '#0d9488',
Filter: '#1a237e',
Spectral: '#4c1d95',
'Level &amp; Correct': '#1b5e20',
Measure: '#4a148c',
Mask: '#7c2d12',
Grains: '#bf360c',
};
&nbsp;
export const SOCKET_COMPATIBILITY = {
FLOAT: new Set(['INT']),
INT: new Set(['FLOAT']),
LINE: new Set(['COORDPAIR']),
};
&nbsp;
const EMPTY_SOCKET_TYPE_SET = new Set();
&nbsp;
export function getSpecTypeAndOptions(spec) {
if (Array.isArray(spec)) {
return [spec[0], spec[1] <span class="branch-0 cbranch-no" title="branch not covered" >|| {}]</span>;
}
<span class="cstat-no" title="statement not covered" ><span class="branch-0 cbranch-no" title="branch not covered" > return [spec, {}];</span></span>
}
&nbsp;
export function isDataSocketType(type) {
return typeof type === 'string' &amp;&amp; DATA_TYPES.has(type);
}
&nbsp;
export function isDataSocketSpec(spec) {
const [type] = getSpecTypeAndOptions(spec);
return isDataSocketType(type);
}
&nbsp;
export function getAcceptedSocketTypes(specOrType) {
const [type, opts] = Array.isArray(specOrType)
? getSpecTypeAndOptions(specOrType)
: [specOrType, {}];
if (typeof type !== 'string') <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
<span class="cstat-no" title="statement not covered" > return EMPTY_SOCKET_TYPE_SET;</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
const accepted = new Set([type]);
const explicitAccepted = Array.isArray(opts?.accepted_types) ? opts.accepted_types : [];
for (const acceptedType of explicitAccepted) {
if (typeof acceptedType === 'string' &amp;&amp; acceptedType) {
accepted.add(acceptedType);
}
}
&nbsp;
const fallbackAccepted = SOCKET_COMPATIBILITY[type];
if (fallbackAccepted) {
for (const acceptedType of fallbackAccepted) {
accepted.add(acceptedType);
}
}
&nbsp;
return accepted;
}
&nbsp;
export function socketSpecAcceptsType(sourceType, targetSpecOrType) {
if (typeof sourceType !== 'string' || !sourceType) <span class="branch-0 cbranch-no" title="branch not covered" >return false;</span>
return getAcceptedSocketTypes(targetSpecOrType).has(sourceType);
}
&nbsp;
// Colors used in Canvas 2D / toBlob contexts where CSS var() is unavailable.
export const CANVAS_COLORS = {
bgDeep: '#0f172a',
maskStroke: '#ffffff',
maskOverlay: 'rgba(255, 59, 59, 0.16)',
};
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,253 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for defaultWorkflow.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> defaultWorkflow.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">89.28% </span>
<span class="quiet">Statements</span>
<span class='fraction'>50/56</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">86.36% </span>
<span class="quiet">Branches</span>
<span class='fraction'>19/22</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>2/2</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">89.28% </span>
<span class="quiet">Lines</span>
<span class='fraction'>50/56</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { extractWorkflow } from './pngMetadata.js';
&nbsp;
const DEFAULT_WORKFLOW_CANDIDATES = [
{ path: '/default-workflow.json', type: 'json' },
{ path: '/default-workflow.png', type: 'png' },
];
&nbsp;
async function loadCandidate(candidate, fetchImpl, extractWorkflowFn) {
let response;
try {
response = await fetchImpl(candidate.path, { cache: 'no-store' });
} catch {
return null;
}
&nbsp;
const contentType = response.headers?.get?.('content-type') || '';
const isHtmlFallback = typeof contentType === 'string' &amp;&amp; contentType.toLowerCase().includes('text/html');
&nbsp;
if (!response.ok) {
if (response.status === 404 || response.status === 0) return null;
<span class="cstat-no" title="statement not covered" ><span class="branch-0 cbranch-no" title="branch not covered" > throw new Error(`Failed to load ${candidate.path} (${response.status})`);</span></span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
if (candidate.type === 'json') {
if (isHtmlFallback) return null;
try {
return await response.json();
} <span class="branch-0 cbranch-no" title="branch not covered" >catch {</span>
<span class="cstat-no" title="statement not covered" > throw new Error(`${candidate.path} is not valid JSON`);</span>
<span class="cstat-no" title="statement not covered" > }</span>
}
&nbsp;
if (isHtmlFallback) return null;
const workflow = await extractWorkflowFn(await response.blob());
if (!workflow) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
<span class="cstat-no" title="statement not covered" > throw new Error(`${candidate.path} does not contain embedded workflow metadata`);</span>
<span class="cstat-no" title="statement not covered" > }</span>
return workflow;
}
&nbsp;
export async function loadDefaultWorkflowAsset({
fetchImpl = fetch,
extractWorkflowFn = extractWorkflow,
} = {}) {
for (const candidate of DEFAULT_WORKFLOW_CANDIDATES) {
const workflow = await loadCandidate(candidate, fetchImpl, extractWorkflowFn);
if (workflow) {
return {
source: candidate.path,
format: candidate.type,
workflow,
};
}
}
return null;
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,559 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for executionGraph.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> executionGraph.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">99.36% </span>
<span class="quiet">Statements</span>
<span class='fraction'>157/158</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">79.48% </span>
<span class="quiet">Branches</span>
<span class='fraction'>62/78</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>10/10</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">99.36% </span>
<span class="quiet">Lines</span>
<span class='fraction'>157/158</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">22x</span>
<span class="cline-any cline-yes">22x</span>
<span class="cline-any cline-yes">22x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">22x</span>
<span class="cline-any cline-yes">22x</span>
<span class="cline-any cline-yes">22x</span>
<span class="cline-any cline-yes">22x</span>
<span class="cline-any cline-yes">22x</span>
<span class="cline-any cline-yes">22x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">25x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">18x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">18x</span>
<span class="cline-any cline-yes">18x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">18x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">16x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { getSpecTypeAndOptions, isDataSocketSpec } from './constants.js';
&nbsp;
const OMITTED_WIDGET_INPUTS_BY_CLASS = {
View3D: new Set([
'camera_azimuth',
'camera_polar',
'camera_distance',
'camera_target_x',
'camera_target_y',
'camera_target_z',
]),
};
&nbsp;
function getInputName(handleId) {
return handleId.split('::')[1];
}
&nbsp;
function getOutputSlot(handleId) {
return parseInt(handleId.split('::')[1], 10);
}
&nbsp;
function resolveExecutionEdge(edge) {
const original = edge?.data?.groupProxyOriginal;
if (!original) return edge;
return {
...edge,
source: original.source || edge.source,
sourceHandle: original.sourceHandle || edge.sourceHandle,
target: original.target <span class="branch-0 cbranch-no" title="branch not covered" >|| edge.target,</span>
targetHandle: original.targetHandle <span class="branch-0 cbranch-no" title="branch not covered" >|| edge.targetHandle,</span>
};
}
&nbsp;
export function getConnectedNodeIds(edges) {
const connectedNodeIds = new Set();
for (const edge of edges) {
const resolved = resolveExecutionEdge(edge);
connectedNodeIds.add(resolved.source);
connectedNodeIds.add(resolved.target);
}
return connectedNodeIds;
}
&nbsp;
function isPreviewLoadNode(node) {
return ['Image', 'ImageDemo'].includes(node?.data?.className);
}
&nbsp;
function hasPreviewLoadSelection(node) {
if (node?.data?.className === 'Image') {
return !!String(node.data?.widgetValues?.filename || '').trim();
}
if (node?.data?.className === 'ImageDemo') {
return !!String(node.data?.widgetValues?.name <span class="branch-0 cbranch-no" title="branch not covered" >|| '')</span>.trim();
}
<span class="cstat-no" title="statement not covered" ><span class="branch-0 cbranch-no" title="branch not covered" > return false;</span></span>
}
&nbsp;
function getRunnableNodeIds(nodes, edges) {
const connectedNodeIds = getConnectedNodeIds(edges);
&nbsp;
const runnableNodeIds = new Set(connectedNodeIds);
for (const node of nodes) {
if (connectedNodeIds.has(node.id)) continue;
if (isPreviewLoadNode(node) &amp;&amp; hasPreviewLoadSelection(node)) {
runnableNodeIds.add(node.id);
}
}
&nbsp;
return runnableNodeIds;
}
&nbsp;
export function serializeExecutionGraph(nodes, edges, { excludeManualTrigger = false } = {}) {
const runnableNodeIds = getRunnableNodeIds(nodes, edges);
const prompt = {};
&nbsp;
for (const node of nodes) {
if (!runnableNodeIds.has(node.id)) continue;
&nbsp;
const { className, definition, widgetValues, runtimeValues } = node.data;
if (className === 'Group') <span class="branch-0 cbranch-no" title="branch not covered" >continue;</span>
if (!definition) <span class="branch-0 cbranch-no" title="branch not covered" >continue;</span>
if (excludeManualTrigger <span class="branch-0 cbranch-no" title="branch not covered" >&amp;&amp; definition.manual_trigger)</span> <span class="branch-0 cbranch-no" title="branch not covered" >continue;</span>
&nbsp;
const inputs = {};
const valueBag = { ...(widgetValues <span class="branch-0 cbranch-no" title="branch not covered" >|| {})</span>, ...(runtimeValues || {}) };
const omittedInputs = OMITTED_WIDGET_INPUTS_BY_CLASS[className] || null;
&nbsp;
const allWidgets = {
...(definition.input.required <span class="branch-0 cbranch-no" title="branch not covered" >|| {})</span>,
...(definition.input.optional <span class="branch-0 cbranch-no" title="branch not covered" >|| {})</span>,
};
for (const [name, spec] of Object.entries(allWidgets)) {
if (omittedInputs?.has(name)) continue;
const [type] = getSpecTypeAndOptions(spec);
if (isDataSocketSpec(spec)) continue;
if (type === 'BUTTON') <span class="branch-0 cbranch-no" title="branch not covered" >continue;</span>
if (valueBag[name] !== undefined) {
inputs[name] = valueBag[name];
}
}
&nbsp;
const incoming = edges
.map(resolveExecutionEdge)
.filter((edge) =&gt; edge.target === node.id);
for (const edge of incoming) {
const inputName = getInputName(edge.targetHandle);
const outputSlot = getOutputSlot(edge.sourceHandle);
inputs[inputName] = [edge.source, outputSlot];
}
&nbsp;
prompt[node.id] = { class_type: className, inputs };
}
&nbsp;
return prompt;
}
&nbsp;
export function getAutoRunnableNodes(nodes, edges) {
const runnableNodeIds = getRunnableNodeIds(nodes, edges);
return nodes.filter((node) =&gt; runnableNodeIds.has(node.id));
}
&nbsp;
export function hasBlockingAutoRunInput(node, edges) {
const def = node.data?.definition;
if (!def || def.manual_trigger) <span class="branch-0 cbranch-no" title="branch not covered" >return false;</span>
&nbsp;
const required = def.input.required <span class="branch-0 cbranch-no" title="branch not covered" >|| {};</span>
for (const [name, spec] of Object.entries(required)) {
const [type, opts] = getSpecTypeAndOptions(spec);
const hiddenByConnectedInput = (() =&gt; {
const raw = opts?.hide_when_input_connected;
if (!raw) return false;
const inputs = Array.isArray(raw) <span class="branch-0 cbranch-no" title="branch not covered" >? raw </span>: [raw];
return inputs.some((inputName) =&gt; edges.some(
(edge) =&gt; {
const resolved = resolveExecutionEdge(edge);
return resolved.target === node.id &amp;&amp; getInputName(resolved.targetHandle) === String(inputName);
}
));
})();
&nbsp;
if (hiddenByConnectedInput) continue;
&nbsp;
if (type === 'FILE_PICKER' || type === 'FOLDER_PICKER') {
if (!node.data.widgetValues?.[name]) return true;
continue;
}
if (!isDataSocketSpec(spec)) <span class="branch-0 cbranch-no" title="branch not covered" >continue;</span>
const hasEdge = edges.some(
(edge) =&gt; {
const resolved = resolveExecutionEdge(edge);
return resolved.target === node.id &amp;&amp; getInputName(resolved.targetHandle) === name;
}
);
if (!hasEdge) return true;
}
&nbsp;
return false;
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

View File

@@ -0,0 +1,139 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for groupDrag.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> groupDrag.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>18/18</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">55.55% </span>
<span class="quiet">Branches</span>
<span class='fraction'>5/9</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>2/2</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>18/18</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">export const GROUP_DRAG_RELEASE_DISTANCE = 18;
&nbsp;
export function getPointDistanceOutsideRect(rect, point) {
if (!rect || !point) <span class="branch-0 cbranch-no" title="branch not covered" >return Infinity;</span>
&nbsp;
const dx = point.x &lt; rect.left
<span class="branch-0 cbranch-no" title="branch not covered" > ? rect.left - point.x</span>
: (point.x &gt; rect.right ? point.x - rect.right : 0);
const dy = point.y &lt; rect.top
<span class="branch-0 cbranch-no" title="branch not covered" > ? rect.top - point.y</span>
: (point.y &gt; rect.bottom <span class="branch-0 cbranch-no" title="branch not covered" >? point.y - rect.bottom </span>: 0);
&nbsp;
return Math.hypot(dx, dy);
}
&nbsp;
export function shouldReleaseFromGroup(rect, point, threshold = GROUP_DRAG_RELEASE_DISTANCE) {
return getPointDistanceOutsideRect(rect, point) &gt;= threshold;
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,190 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for groupSizing.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> groupSizing.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">97.14% </span>
<span class="quiet">Statements</span>
<span class='fraction'>34/35</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">35.71% </span>
<span class="quiet">Branches</span>
<span class='fraction'>5/14</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>2/2</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">97.14% </span>
<span class="quiet">Lines</span>
<span class='fraction'>34/35</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">const DEFAULT_CHILD_WIDTH = 200;
const DEFAULT_CHILD_HEIGHT = 120;
&nbsp;
function getNodeSize(node, axis) {
const fallback = axis === 'width' ? DEFAULT_CHILD_WIDTH : DEFAULT_CHILD_HEIGHT;
const measured = Number(node?.measured<span class="branch-0 cbranch-no" title="branch not covered" >?.[axis])</span>;
if (Number.isFinite(measured) <span class="branch-0 cbranch-no" title="branch not covered" >&amp;&amp; measured &gt; 0)</span> <span class="branch-0 cbranch-no" title="branch not covered" >return measured;</span>
const direct = Number(node?.[axis]);
if (Number.isFinite(direct) <span class="branch-0 cbranch-no" title="branch not covered" >&amp;&amp; direct &gt; 0)</span> <span class="branch-0 cbranch-no" title="branch not covered" >return direct;</span>
const styled = Number(node?.style?.[axis]);
if (Number.isFinite(styled) &amp;&amp; styled &gt; 0) return styled;
<span class="cstat-no" title="statement not covered" ><span class="branch-0 cbranch-no" title="branch not covered" > return fallback;</span></span>
}
&nbsp;
export function getGroupMinimumSize(memberNodes, {
minWidth = 260,
minHeight = 180,
paddingX = 24,
paddingY = 24,
} = {}) {
let maxRight = 0;
let maxBottom = 0;
&nbsp;
for (const node of memberNodes <span class="branch-0 cbranch-no" title="branch not covered" >|| [])</span> {
const x = Number(node?.position?.x) <span class="branch-0 cbranch-no" title="branch not covered" >|| 0;</span>
const y = Number(node?.position?.y) <span class="branch-0 cbranch-no" title="branch not covered" >|| 0;</span>
maxRight = Math.max(maxRight, x + getNodeSize(node, 'width'));
maxBottom = Math.max(maxBottom, y + getNodeSize(node, 'height'));
}
&nbsp;
return {
width: Math.max(minWidth, Math.ceil(maxRight + paddingX)),
height: Math.max(minHeight, Math.ceil(maxBottom + paddingY)),
};
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,401 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for All files</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1>All files</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">88.19% </span>
<span class="quiet">Statements</span>
<span class='fraction'>1681/1906</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">68.87% </span>
<span class="quiet">Branches</span>
<span class='fraction'>447/649</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">89.34% </span>
<span class="quiet">Functions</span>
<span class='fraction'>109/122</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">88.19% </span>
<span class="quiet">Lines</span>
<span class='fraction'>1681/1906</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<div class="pad1">
<table class="coverage-summary">
<thead>
<tr>
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
</tr>
</thead>
<tbody><tr>
<td class="file high" data-value="angleMeasureGeometry.js"><a href="angleMeasureGeometry.js.html">angleMeasureGeometry.js</a></td>
<td data-value="97.4" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 97%"></div><div class="cover-empty" style="width: 3%"></div></div>
</td>
<td data-value="97.4" class="pct high">97.4%</td>
<td data-value="77" class="abs high">75/77</td>
<td data-value="53.33" class="pct medium">53.33%</td>
<td data-value="15" class="abs medium">8/15</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="6" class="abs high">6/6</td>
<td data-value="97.4" class="pct high">97.4%</td>
<td data-value="77" class="abs high">75/77</td>
</tr>
<tr>
<td class="file high" data-value="canvasInteractionTargets.js"><a href="canvasInteractionTargets.js.html">canvasInteractionTargets.js</a></td>
<td data-value="89.79" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 89%"></div><div class="cover-empty" style="width: 11%"></div></div>
</td>
<td data-value="89.79" class="pct high">89.79%</td>
<td data-value="49" class="abs high">44/49</td>
<td data-value="68" class="pct medium">68%</td>
<td data-value="25" class="abs medium">17/25</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="7" class="abs high">7/7</td>
<td data-value="89.79" class="pct high">89.79%</td>
<td data-value="49" class="abs high">44/49</td>
</tr>
<tr>
<td class="file high" data-value="connectionUtils.js"><a href="connectionUtils.js.html">connectionUtils.js</a></td>
<td data-value="100" class="pic high">
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="122" class="abs high">122/122</td>
<td data-value="93.93" class="pct high">93.93%</td>
<td data-value="66" class="abs high">62/66</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="12" class="abs high">12/12</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="122" class="abs high">122/122</td>
</tr>
<tr>
<td class="file high" data-value="constants.js"><a href="constants.js.html">constants.js</a></td>
<td data-value="97.05" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 97%"></div><div class="cover-empty" style="width: 3%"></div></div>
</td>
<td data-value="97.05" class="pct high">97.05%</td>
<td data-value="102" class="abs high">99/102</td>
<td data-value="76.47" class="pct medium">76.47%</td>
<td data-value="17" class="abs medium">13/17</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="5" class="abs high">5/5</td>
<td data-value="97.05" class="pct high">97.05%</td>
<td data-value="102" class="abs high">99/102</td>
</tr>
<tr>
<td class="file high" data-value="defaultWorkflow.js"><a href="defaultWorkflow.js.html">defaultWorkflow.js</a></td>
<td data-value="89.28" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 89%"></div><div class="cover-empty" style="width: 11%"></div></div>
</td>
<td data-value="89.28" class="pct high">89.28%</td>
<td data-value="56" class="abs high">50/56</td>
<td data-value="86.36" class="pct high">86.36%</td>
<td data-value="22" class="abs high">19/22</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="2" class="abs high">2/2</td>
<td data-value="89.28" class="pct high">89.28%</td>
<td data-value="56" class="abs high">50/56</td>
</tr>
<tr>
<td class="file high" data-value="executionGraph.js"><a href="executionGraph.js.html">executionGraph.js</a></td>
<td data-value="99.36" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 99%"></div><div class="cover-empty" style="width: 1%"></div></div>
</td>
<td data-value="99.36" class="pct high">99.36%</td>
<td data-value="158" class="abs high">157/158</td>
<td data-value="79.48" class="pct medium">79.48%</td>
<td data-value="78" class="abs medium">62/78</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="10" class="abs high">10/10</td>
<td data-value="99.36" class="pct high">99.36%</td>
<td data-value="158" class="abs high">157/158</td>
</tr>
<tr>
<td class="file high" data-value="groupDrag.js"><a href="groupDrag.js.html">groupDrag.js</a></td>
<td data-value="100" class="pic high">
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="18" class="abs high">18/18</td>
<td data-value="55.55" class="pct medium">55.55%</td>
<td data-value="9" class="abs medium">5/9</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="2" class="abs high">2/2</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="18" class="abs high">18/18</td>
</tr>
<tr>
<td class="file high" data-value="groupSizing.js"><a href="groupSizing.js.html">groupSizing.js</a></td>
<td data-value="97.14" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 97%"></div><div class="cover-empty" style="width: 3%"></div></div>
</td>
<td data-value="97.14" class="pct high">97.14%</td>
<td data-value="35" class="abs high">34/35</td>
<td data-value="35.71" class="pct low">35.71%</td>
<td data-value="14" class="abs low">5/14</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="2" class="abs high">2/2</td>
<td data-value="97.14" class="pct high">97.14%</td>
<td data-value="35" class="abs high">34/35</td>
</tr>
<tr>
<td class="file high" data-value="loadNodeOutputs.js"><a href="loadNodeOutputs.js.html">loadNodeOutputs.js</a></td>
<td data-value="90" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 90%"></div><div class="cover-empty" style="width: 10%"></div></div>
</td>
<td data-value="90" class="pct high">90%</td>
<td data-value="30" class="abs high">27/30</td>
<td data-value="66.66" class="pct medium">66.66%</td>
<td data-value="15" class="abs medium">10/15</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="3" class="abs high">3/3</td>
<td data-value="90" class="pct high">90%</td>
<td data-value="30" class="abs high">27/30</td>
</tr>
<tr>
<td class="file medium" data-value="markupShapeGeometry.js"><a href="markupShapeGeometry.js.html">markupShapeGeometry.js</a></td>
<td data-value="76.53" class="pic medium">
<div class="chart"><div class="cover-fill" style="width: 76%"></div><div class="cover-empty" style="width: 24%"></div></div>
</td>
<td data-value="76.53" class="pct medium">76.53%</td>
<td data-value="98" class="abs medium">75/98</td>
<td data-value="44.44" class="pct low">44.44%</td>
<td data-value="18" class="abs low">8/18</td>
<td data-value="83.33" class="pct high">83.33%</td>
<td data-value="6" class="abs high">5/6</td>
<td data-value="76.53" class="pct medium">76.53%</td>
<td data-value="98" class="abs medium">75/98</td>
</tr>
<tr>
<td class="file high" data-value="nodeClipboard.js"><a href="nodeClipboard.js.html">nodeClipboard.js</a></td>
<td data-value="94.37" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 94%"></div><div class="cover-empty" style="width: 6%"></div></div>
</td>
<td data-value="94.37" class="pct high">94.37%</td>
<td data-value="320" class="abs high">302/320</td>
<td data-value="59.67" class="pct medium">59.67%</td>
<td data-value="124" class="abs medium">74/124</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="17" class="abs high">17/17</td>
<td data-value="94.37" class="pct high">94.37%</td>
<td data-value="320" class="abs high">302/320</td>
</tr>
<tr>
<td class="file high" data-value="nodeHierarchy.js"><a href="nodeHierarchy.js.html">nodeHierarchy.js</a></td>
<td data-value="100" class="pic high">
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="28" class="abs high">28/28</td>
<td data-value="86.66" class="pct high">86.66%</td>
<td data-value="15" class="abs high">13/15</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="2" class="abs high">2/2</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="28" class="abs high">28/28</td>
</tr>
<tr>
<td class="file high" data-value="nodeWidgetDefaults.js"><a href="nodeWidgetDefaults.js.html">nodeWidgetDefaults.js</a></td>
<td data-value="92.3" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 92%"></div><div class="cover-empty" style="width: 8%"></div></div>
</td>
<td data-value="92.3" class="pct high">92.3%</td>
<td data-value="26" class="abs high">24/26</td>
<td data-value="69.23" class="pct medium">69.23%</td>
<td data-value="13" class="abs medium">9/13</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="2" class="abs high">2/2</td>
<td data-value="92.3" class="pct high">92.3%</td>
<td data-value="26" class="abs high">24/26</td>
</tr>
<tr>
<td class="file high" data-value="nodeWidgetLayout.js"><a href="nodeWidgetLayout.js.html">nodeWidgetLayout.js</a></td>
<td data-value="100" class="pic high">
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="49" class="abs high">49/49</td>
<td data-value="73.07" class="pct medium">73.07%</td>
<td data-value="26" class="abs medium">19/26</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="4" class="abs high">4/4</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="49" class="abs high">49/49</td>
</tr>
<tr>
<td class="file high" data-value="pngMetadata.js"><a href="pngMetadata.js.html">pngMetadata.js</a></td>
<td data-value="98.78" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 98%"></div><div class="cover-empty" style="width: 2%"></div></div>
</td>
<td data-value="98.78" class="pct high">98.78%</td>
<td data-value="164" class="abs high">162/164</td>
<td data-value="61.11" class="pct medium">61.11%</td>
<td data-value="36" class="abs medium">22/36</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="8" class="abs high">8/8</td>
<td data-value="98.78" class="pct high">98.78%</td>
<td data-value="164" class="abs high">162/164</td>
</tr>
<tr>
<td class="file high" data-value="runtimeValuePersistence.js"><a href="runtimeValuePersistence.js.html">runtimeValuePersistence.js</a></td>
<td data-value="90" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 90%"></div><div class="cover-empty" style="width: 10%"></div></div>
</td>
<td data-value="90" class="pct high">90%</td>
<td data-value="10" class="abs high">9/10</td>
<td data-value="87.5" class="pct high">87.5%</td>
<td data-value="8" class="abs high">7/8</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="1" class="abs high">1/1</td>
<td data-value="90" class="pct high">90%</td>
<td data-value="10" class="abs high">9/10</td>
</tr>
<tr>
<td class="file high" data-value="valueFormatting.js"><a href="valueFormatting.js.html">valueFormatting.js</a></td>
<td data-value="80.35" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 80%"></div><div class="cover-empty" style="width: 20%"></div></div>
</td>
<td data-value="80.35" class="pct high">80.35%</td>
<td data-value="224" class="abs high">180/224</td>
<td data-value="65.57" class="pct medium">65.57%</td>
<td data-value="61" class="abs medium">40/61</td>
<td data-value="83.33" class="pct high">83.33%</td>
<td data-value="12" class="abs high">10/12</td>
<td data-value="80.35" class="pct high">80.35%</td>
<td data-value="224" class="abs high">180/224</td>
</tr>
<tr>
<td class="file low" data-value="workflowCapture.js"><a href="workflowCapture.js.html">workflowCapture.js</a></td>
<td data-value="40.93" class="pic low">
<div class="chart"><div class="cover-fill" style="width: 40%"></div><div class="cover-empty" style="width: 60%"></div></div>
</td>
<td data-value="40.93" class="pct low">40.93%</td>
<td data-value="193" class="abs low">79/193</td>
<td data-value="36.84" class="pct low">36.84%</td>
<td data-value="19" class="abs low">7/19</td>
<td data-value="16.66" class="pct low">16.66%</td>
<td data-value="12" class="abs low">2/12</td>
<td data-value="40.93" class="pct low">40.93%</td>
<td data-value="193" class="abs low">79/193</td>
</tr>
<tr>
<td class="file high" data-value="workflowHydration.js"><a href="workflowHydration.js.html">workflowHydration.js</a></td>
<td data-value="100" class="pic high">
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="82" class="abs high">82/82</td>
<td data-value="73.52" class="pct medium">73.52%</td>
<td data-value="34" class="abs medium">25/34</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="5" class="abs high">5/5</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="82" class="abs high">82/82</td>
</tr>
<tr>
<td class="file high" data-value="workflowSerialization.js"><a href="workflowSerialization.js.html">workflowSerialization.js</a></td>
<td data-value="100" class="pic high">
<div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="65" class="abs high">65/65</td>
<td data-value="64.7" class="pct medium">64.7%</td>
<td data-value="34" class="abs medium">22/34</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="4" class="abs high">4/4</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="65" class="abs high">65/65</td>
</tr>
</tbody>
</table>
</div>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,175 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for loadNodeOutputs.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> loadNodeOutputs.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">90% </span>
<span class="quiet">Statements</span>
<span class='fraction'>27/30</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">66.66% </span>
<span class="quiet">Branches</span>
<span class='fraction'>10/15</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>3/3</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">90% </span>
<span class="quiet">Lines</span>
<span class='fraction'>27/30</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">export function resolveLoadNodeChannelPath({
explicitPath = null,
resolvedPathInput = null,
className = '',
widgetValues = {},
} = {}) {
if (typeof explicitPath === 'string' &amp;&amp; explicitPath) {
return explicitPath;
}
if (typeof resolvedPathInput === 'string' <span class="branch-0 cbranch-no" title="branch not covered" >&amp;&amp; resolvedPathInput)</span> <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
<span class="cstat-no" title="statement not covered" > return resolvedPathInput;</span>
<span class="cstat-no" title="statement not covered" > }</span>
if (className === 'Image') {
return String(widgetValues?.filename <span class="branch-0 cbranch-no" title="branch not covered" >|| '')</span>;
}
if (className === 'ImageDemo') {
return String(widgetValues?.name <span class="branch-0 cbranch-no" title="branch not covered" >|| '')</span>;
}
<span class="cstat-no" title="statement not covered" ><span class="branch-0 cbranch-no" title="branch not covered" > return '';</span></span>
}
&nbsp;
export function beginTrackedNodeRequest(requestVersions, nodeId) {
const nextVersion = (requestVersions.get(nodeId) || 0) + 1;
requestVersions.set(nodeId, nextVersion);
return nextVersion;
}
&nbsp;
export function isTrackedNodeRequestCurrent(requestVersions, nodeId, version) {
return requestVersions.get(nodeId) === version;
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,379 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for markupShapeGeometry.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> markupShapeGeometry.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">76.53% </span>
<span class="quiet">Statements</span>
<span class='fraction'>75/98</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">44.44% </span>
<span class="quiet">Branches</span>
<span class='fraction'>8/18</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">83.33% </span>
<span class="quiet">Functions</span>
<span class='fraction'>5/6</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">76.53% </span>
<span class="quiet">Lines</span>
<span class='fraction'>75/98</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line medium'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">export const MARKUP_DEFAULT_SHAPE = 'arrow';
export const MARKUP_DEFAULT_COLOR = '#ff0000';
export const MARKUP_PREVIEW_REFERENCE_DIM = 512;
&nbsp;
function clampFraction(value) {
const numeric = Number(value);
if (!Number.isFinite(numeric)) <span class="branch-0 cbranch-no" title="branch not covered" >return 0;</span>
return Math.max(0, Math.min(1, numeric));
}
&nbsp;
export function sanitizeMarkupColor(color, fallback = MARKUP_DEFAULT_COLOR) {
if (typeof color !== 'string') return fallback;
const value = color.trim();
return /^#[0-9a-fA-F]{6}$/.test(value) <span class="branch-0 cbranch-no" title="branch not covered" >? value.toLowerCase() </span>: fallback;
}
&nbsp;
export function sanitizeMarkupShape(
shape,
fallbackShape = MARKUP_DEFAULT_SHAPE,
fallbackColor = MARKUP_DEFAULT_COLOR,
fallbackWidth = 3,
) {
if (!shape || typeof shape !== 'object') <span class="branch-0 cbranch-no" title="branch not covered" >return null;</span>
const kind = ['line', 'rectangle', 'circle', 'arrow'].includes(shape.kind) <span class="branch-0 cbranch-no" title="branch not covered" >? shape.kind </span>: fallbackShape;
const x1 = clampFraction(shape.x1);
const y1 = clampFraction(shape.y1);
const x2 = clampFraction(shape.x2);
const y2 = clampFraction(shape.y2);
const width = Math.max(1, Math.min(64, Math.round(Number(shape.width) <span class="branch-0 cbranch-no" title="branch not covered" >|| fallbackWidth <span class="branch-0 cbranch-no" title="branch not covered" ></span>|| 1)</span>));
return {
kind,
x1: Number(x1.toFixed(4)),
y1: Number(y1.toFixed(4)),
x2: Number(x2.toFixed(4)),
y2: Number(y2.toFixed(4)),
width,
color: sanitizeMarkupColor(shape.color, fallbackColor),
};
}
&nbsp;
export <span class="fstat-no" title="function not covered" >function parseMarkupShapes(</span>
<span class="cstat-no" title="statement not covered" > markupShapes,</span>
<span class="cstat-no" title="statement not covered" > fallbackShape = MARKUP_DEFAULT_SHAPE,</span>
<span class="cstat-no" title="statement not covered" > fallbackColor = MARKUP_DEFAULT_COLOR,</span>
<span class="cstat-no" title="statement not covered" > fallbackWidth = 3,</span>
<span class="cstat-no" title="statement not covered" >) {</span>
<span class="cstat-no" title="statement not covered" > if (Array.isArray(markupShapes)) {</span>
<span class="cstat-no" title="statement not covered" > return markupShapes</span>
<span class="cstat-no" title="statement not covered" > .map((shape) =&gt; sanitizeMarkupShape(shape, fallbackShape, fallbackColor, fallbackWidth))</span>
<span class="cstat-no" title="statement not covered" > .filter(Boolean);</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" ></span>
<span class="cstat-no" title="statement not covered" > if (typeof markupShapes !== 'string' || !markupShapes.trim()) return [];</span>
<span class="cstat-no" title="statement not covered" ></span>
<span class="cstat-no" title="statement not covered" > try {</span>
<span class="cstat-no" title="statement not covered" > const parsed = JSON.parse(markupShapes);</span>
<span class="cstat-no" title="statement not covered" > if (!Array.isArray(parsed)) return [];</span>
<span class="cstat-no" title="statement not covered" > return parsed</span>
<span class="cstat-no" title="statement not covered" > .map((shape) =&gt; sanitizeMarkupShape(shape, fallbackShape, fallbackColor, fallbackWidth))</span>
<span class="cstat-no" title="statement not covered" > .filter(Boolean);</span>
<span class="cstat-no" title="statement not covered" > } catch {</span>
<span class="cstat-no" title="statement not covered" > return [];</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
export function getArrowGeometry(shape, imageWidth, imageHeight) {
const x1 = shape.x1 * imageWidth;
const y1 = shape.y1 * imageHeight;
const x2 = shape.x2 * imageWidth;
const y2 = shape.y2 * imageHeight;
const dx = x2 - x1;
const dy = y2 - y1;
const length = Math.hypot(dx, dy) <span class="branch-0 cbranch-no" title="branch not covered" >|| 1;</span>
const ux = dx / length;
const uy = dy / length;
const strokeWidth = Math.max(1, shape.width);
const headLength = Math.max(10, strokeWidth * 4);
const headWidth = Math.max(8, strokeWidth * 3);
const shaftX = x2 - ux * headLength;
const shaftY = y2 - uy * headLength;
const px = -uy;
const py = ux;
const leftX = shaftX + px * headWidth * 0.5;
const leftY = shaftY + py * headWidth * 0.5;
const rightX = shaftX - px * headWidth * 0.5;
const rightY = shaftY - py * headWidth * 0.5;
return {
line: `${x1},${y1} ${shaftX},${shaftY}`,
head: `${x2},${y2} ${leftX},${leftY} ${rightX},${rightY}`,
};
}
&nbsp;
export function getMarkupPreviewStrokeWidth(width, imageWidth, imageHeight) {
const normalizedWidth = Math.max(1, Math.round(Number(width) <span class="branch-0 cbranch-no" title="branch not covered" >|| 1)</span>);
const longestDim = Math.max(1, Number(imageWidth) <span class="branch-0 cbranch-no" title="branch not covered" >|| 0,</span> Number(imageHeight) <span class="branch-0 cbranch-no" title="branch not covered" >|| 0)</span>;
const scale = Math.max(1, longestDim / MARKUP_PREVIEW_REFERENCE_DIM);
return Math.max(1, Math.round(normalizedWidth * scale));
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,169 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for nodeHierarchy.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> nodeHierarchy.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>28/28</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">86.66% </span>
<span class="quiet">Branches</span>
<span class='fraction'>13/15</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>2/2</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>28/28</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">24x</span>
<span class="cline-any cline-yes">24x</span>
<span class="cline-any cline-yes">24x</span>
<span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-yes">24x</span>
<span class="cline-any cline-yes">24x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-yes">19x</span>
<span class="cline-any cline-yes">24x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">export function sortNodesForParentOrder(nodes) {
const list = Array.isArray(nodes) ? nodes.filter(Boolean) <span class="branch-0 cbranch-no" title="branch not covered" >: [];</span>
const entries = list.map((node) =&gt; ({ id: String(node.id), node }));
const byId = new Map(entries.map((entry) =&gt; [entry.id, entry]));
const visiting = new Set();
const visited = new Set();
const ordered = [];
&nbsp;
function visit(entry) {
if (!entry) <span class="branch-0 cbranch-no" title="branch not covered" >return;</span>
const { id, node } = entry;
if (visited.has(id) || visiting.has(id)) return;
&nbsp;
visiting.add(id);
&nbsp;
const parentId = node?.parentId ? String(node.parentId) : null;
if (parentId) {
visit(byId.get(parentId));
}
&nbsp;
visiting.delete(id);
visited.add(id);
ordered.push(node);
}
&nbsp;
entries.forEach((entry) =&gt; visit(entry));
return ordered;
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,163 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for nodeWidgetDefaults.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> nodeWidgetDefaults.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">92.3% </span>
<span class="quiet">Statements</span>
<span class='fraction'>24/26</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">69.23% </span>
<span class="quiet">Branches</span>
<span class='fraction'>9/13</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>2/2</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">92.3% </span>
<span class="quiet">Lines</span>
<span class='fraction'>24/26</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { getSpecTypeAndOptions, isDataSocketSpec } from './constants.js';
&nbsp;
export function getDefaultWidgetValue(spec) {
const [type, opts] = getSpecTypeAndOptions(spec);
if (isDataSocketSpec(spec)) return undefined;
if (type === 'BUTTON') <span class="branch-0 cbranch-no" title="branch not covered" >return undefined;</span>
if (Array.isArray(type)) {
if (typeof opts?.default === 'string' &amp;&amp; type.includes(opts.default)) {
return opts.default;
}
<span class="cstat-no" title="statement not covered" ><span class="branch-0 cbranch-no" title="branch not covered" > return type[0];</span></span>
<span class="cstat-no" title="statement not covered" > }</span>
return opts?.default <span class="branch-0 cbranch-no" title="branch not covered" >?? '';</span>
}
&nbsp;
export function buildDefaultWidgetValues(definition) {
const widgetValues = {};
const required = definition?.input?.required <span class="branch-0 cbranch-no" title="branch not covered" >|| {};</span>
for (const [name, spec] of Object.entries(required)) {
const value = getDefaultWidgetValue(spec);
if (value !== undefined) {
widgetValues[name] = value;
}
}
return widgetValues;
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,232 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for nodeWidgetLayout.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> nodeWidgetLayout.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>49/49</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">73.07% </span>
<span class="quiet">Branches</span>
<span class='fraction'>19/26</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>4/4</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>49/49</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">export function formatUiLabel(text) {
return String(text <span class="branch-0 cbranch-no" title="branch not covered" >?? '')</span>
.replace(/_/g, ' ')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
}
&nbsp;
function normalizeInputNames(raw) {
if (!raw) return [];
return (Array.isArray(raw) <span class="branch-0 cbranch-no" title="branch not covered" >? raw </span>: [raw])
.map((value) =&gt; String(value))
.filter((value) =&gt; value.length &gt; 0);
}
&nbsp;
export function getWidgetCombinedInputName(widget, dataInputByName) {
const explicitInputName = normalizeInputNames(widget?.opts?.top_socket_input)[0];
if (explicitInputName &amp;&amp; dataInputByName?.has(explicitInputName)) {
return explicitInputName;
}
&nbsp;
const widgetLabel = formatUiLabel(widget?.opts?.label || widget?.name);
if (!widgetLabel) <span class="branch-0 cbranch-no" title="branch not covered" >return null;</span>
&nbsp;
for (const inputName of normalizeInputNames(widget?.opts?.hide_when_input_connected)) {
const input = dataInputByName?.get(inputName);
if (!input) <span class="branch-0 cbranch-no" title="branch not covered" >continue;</span>
const inputLabel = formatUiLabel(input.label <span class="branch-0 cbranch-no" title="branch not covered" >|| input.name)</span>;
if (inputLabel === widgetLabel) {
return input.name;
}
}
&nbsp;
return null;
}
&nbsp;
export function buildCombinedInputNameByWidgetName(widgets, dataInputs) {
const dataInputByName = new Map((dataInputs <span class="branch-0 cbranch-no" title="branch not covered" >|| [])</span>.map((input) =&gt; [input.name, input]));
const combinedInputNameByWidgetName = new Map();
&nbsp;
for (const widget of widgets <span class="branch-0 cbranch-no" title="branch not covered" >|| [])</span> {
const combinedInputName = getWidgetCombinedInputName(widget, dataInputByName);
if (combinedInputName) {
combinedInputNameByWidgetName.set(widget.name, combinedInputName);
}
}
&nbsp;
return combinedInputNameByWidgetName;
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,577 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for pngMetadata.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> pngMetadata.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">98.78% </span>
<span class="quiet">Statements</span>
<span class='fraction'>162/164</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">61.11% </span>
<span class="quiet">Branches</span>
<span class='fraction'>22/36</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>8/8</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">98.78% </span>
<span class="quiet">Lines</span>
<span class='fraction'>162/164</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">512x</span>
<span class="cline-any cline-yes">512x</span>
<span class="cline-any cline-yes">4096x</span>
<span class="cline-any cline-yes">4096x</span>
<span class="cline-any cline-yes">512x</span>
<span class="cline-any cline-yes">512x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">382x</span>
<span class="cline-any cline-yes">382x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">56x</span>
<span class="cline-any cline-yes">56x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">26x</span>
<span class="cline-any cline-yes">26x</span>
<span class="cline-any cline-yes">26x</span>
<span class="cline-any cline-yes">26x</span>
<span class="cline-any cline-yes">26x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">26x</span>
<span class="cline-any cline-yes">26x</span>
<span class="cline-any cline-yes">26x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">13x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
* PNG text chunk utilities for embedding/extracting workflow metadata.
*
* PNG files are composed of chunks: [4-byte length][4-byte type][data][4-byte CRC].
* We add an iTXt chunk with key "workflow" containing the JSON-serialised graph,
* inserted just before the IEND chunk.
*/
&nbsp;
// ── CRC32 (PNG uses CRC-32/ISO 3309) ────────────────────────────────
&nbsp;
const crcTable = new Uint32Array(256);
for (let i = 0; i &lt; 256; i++) {
let c = i;
for (let j = 0; j &lt; 8; j++) {
c = (c &amp; 1) ? (0xEDB88320 ^ (c &gt;&gt;&gt; 1)) : (c &gt;&gt;&gt; 1);
}
crcTable[i] = c;
}
&nbsp;
function crc32(bytes) {
let crc = 0xFFFFFFFF;
for (let i = 0; i &lt; bytes.length; i++) {
crc = crcTable[(crc ^ bytes[i]) &amp; 0xFF] ^ (crc &gt;&gt;&gt; 8);
}
return (crc ^ 0xFFFFFFFF) &gt;&gt;&gt; 0;
}
&nbsp;
// ── Helpers ──────────────────────────────────────────────────────────
&nbsp;
const PNG_SIG = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
&nbsp;
function isPng(data) {
if (data.length &lt; 8) <span class="branch-0 cbranch-no" title="branch not covered" >return false;</span>
for (let i = 0; i &lt; 8; i++) {
if (data[i] !== PNG_SIG[i]) <span class="branch-0 cbranch-no" title="branch not covered" >return false;</span>
}
return true;
}
&nbsp;
function chunkType(data, offset) {
return String.fromCharCode(
data[offset + 4], data[offset + 5], data[offset + 6], data[offset + 7],
);
}
&nbsp;
function readUint32(data, offset) {
return new DataView(data.buffer, data.byteOffset + offset, 4).getUint32(0);
}
&nbsp;
function buildChunk(type, payload) {
const encoder = new TextEncoder();
const typeBytes = encoder.encode(type);
const forCrc = new Uint8Array(4 + payload.length);
forCrc.set(typeBytes, 0);
forCrc.set(payload, 4);
&nbsp;
const chunk = new Uint8Array(12 + payload.length);
const view = new DataView(chunk.buffer);
view.setUint32(0, payload.length);
chunk.set(typeBytes, 4);
chunk.set(payload, 8);
view.setUint32(8 + payload.length, crc32(forCrc));
return chunk;
}
&nbsp;
function parseTextChunk(type, chunkData) {
const decoder = new TextDecoder();
const keywordEnd = chunkData.indexOf(0);
if (keywordEnd === -1) <span class="branch-0 cbranch-no" title="branch not covered" >return null;</span>
&nbsp;
const keyword = decoder.decode(chunkData.subarray(0, keywordEnd));
if (keyword !== 'workflow') <span class="branch-0 cbranch-no" title="branch not covered" >return null;</span>
&nbsp;
if (type !== 'iTXt') <span class="branch-0 cbranch-no" title="branch not covered" >return null;</span>
&nbsp;
const compressionFlagIdx = keywordEnd + 1;
const compressionMethodIdx = keywordEnd + 2;
if (compressionMethodIdx &gt;= chunkData.length) <span class="branch-0 cbranch-no" title="branch not covered" >return null;</span>
&nbsp;
const compressionFlag = chunkData[compressionFlagIdx];
if (compressionFlag !== 0) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
<span class="cstat-no" title="statement not covered" > throw new Error('Compressed PNG workflow metadata is not supported');</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
let offset = compressionMethodIdx + 1;
const languageEnd = chunkData.indexOf(0, offset);
if (languageEnd === -1) <span class="branch-0 cbranch-no" title="branch not covered" >return null;</span>
&nbsp;
offset = languageEnd + 1;
const translatedEnd = chunkData.indexOf(0, offset);
if (translatedEnd === -1) <span class="branch-0 cbranch-no" title="branch not covered" >return null;</span>
&nbsp;
return JSON.parse(decoder.decode(chunkData.subarray(translatedEnd + 1)));
}
&nbsp;
// ── Public API ───────────────────────────────────────────────────────
&nbsp;
/**
* Embed a workflow object into a PNG blob as an iTXt chunk.
* Returns a new Blob with the metadata inserted before IEND.
*/
export async function embedWorkflow(pngBlob, workflow) {
const data = new Uint8Array(await pngBlob.arrayBuffer());
if (!isPng(data)) <span class="branch-0 cbranch-no" title="branch not covered" >throw new Error('Not a valid PNG file');</span>
&nbsp;
const encoder = new TextEncoder();
&nbsp;
// Build iTXt payload:
// keyword \0 compression-flag compression-method language-tag \0 translated-keyword \0 text
const key = encoder.encode('workflow');
const val = encoder.encode(JSON.stringify(workflow));
const payload = new Uint8Array(key.length + 5 + val.length);
payload.set(key, 0);
payload.set(val, key.length + 5);
const chunk = buildChunk('iTXt', payload);
&nbsp;
// Locate IEND
let pos = 8;
let iendPos = data.length;
while (pos &lt; data.length) {
if (pos + 8 &gt; data.length) <span class="branch-0 cbranch-no" title="branch not covered" >break;</span>
const len = readUint32(data, pos);
if (pos + 12 + len &gt; data.length) <span class="branch-0 cbranch-no" title="branch not covered" >break;</span>
if (chunkType(data, pos) === 'IEND') { iendPos = pos; break; }
pos += 12 + len;
}
&nbsp;
// Splice: [before IEND] + [tEXt chunk] + [IEND]
const result = new Uint8Array(data.length + chunk.length);
result.set(data.subarray(0, iendPos), 0);
result.set(chunk, iendPos);
result.set(data.subarray(iendPos), iendPos + chunk.length);
&nbsp;
return new Blob([result], { type: 'image/png' });
}
&nbsp;
/**
* Extract the workflow object from a PNG blob's iTXt chunks.
* Returns the parsed object, or null if no "workflow" key is found.
*/
export async function extractWorkflow(pngBlob) {
const data = new Uint8Array(await pngBlob.arrayBuffer());
if (!isPng(data)) <span class="branch-0 cbranch-no" title="branch not covered" >return null;</span>
&nbsp;
let pos = 8;
let found = null;
&nbsp;
while (pos + 8 &lt;= data.length) {
const len = readUint32(data, pos);
if (pos + 12 + len &gt; data.length) <span class="branch-0 cbranch-no" title="branch not covered" >break;</span>
const type = chunkType(data, pos);
&nbsp;
if (type === 'iTXt') {
const chunkData = data.subarray(pos + 8, pos + 8 + len);
const parsed = parseTextChunk(type, chunkData);
if (parsed) found = parsed;
}
&nbsp;
if (type === 'IEND') break;
pos += 12 + len;
}
&nbsp;
return found;
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1 @@
.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,115 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for runtimeValuePersistence.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> runtimeValuePersistence.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">90% </span>
<span class="quiet">Statements</span>
<span class='fraction'>9/10</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">87.5% </span>
<span class="quiet">Branches</span>
<span class='fraction'>7/8</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>1/1</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">90% </span>
<span class="quiet">Lines</span>
<span class='fraction'>9/10</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">11x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">12x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">export function sanitizeRuntimeValuesForPersistence(className, runtimeValues) {
if (!runtimeValues || typeof runtimeValues !== 'object' || Array.isArray(runtimeValues)) {
return {};
}
&nbsp;
if (className === 'View3D') {
return {};
}
<span class="cstat-no" title="statement not covered" ><span class="branch-0 cbranch-no" title="branch not covered" > return { ...runtimeValues };</span></span>
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

View File

@@ -0,0 +1,210 @@
/* eslint-disable */
var addSorting = (function() {
'use strict';
var cols,
currentSort = {
index: 0,
desc: false
};
// returns the summary table element
function getTable() {
return document.querySelector('.coverage-summary');
}
// returns the thead element of the summary table
function getTableHeader() {
return getTable().querySelector('thead tr');
}
// returns the tbody element of the summary table
function getTableBody() {
return getTable().querySelector('tbody');
}
// returns the th element for nth column
function getNthColumn(n) {
return getTableHeader().querySelectorAll('th')[n];
}
function onFilterInput() {
const searchValue = document.getElementById('fileSearch').value;
const rows = document.getElementsByTagName('tbody')[0].children;
// Try to create a RegExp from the searchValue. If it fails (invalid regex),
// it will be treated as a plain text search
let searchRegex;
try {
searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive
} catch (error) {
searchRegex = null;
}
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
let isMatch = false;
if (searchRegex) {
// If a valid regex was created, use it for matching
isMatch = searchRegex.test(row.textContent);
} else {
// Otherwise, fall back to the original plain text search
isMatch = row.textContent
.toLowerCase()
.includes(searchValue.toLowerCase());
}
row.style.display = isMatch ? '' : 'none';
}
}
// loads the search box
function addSearchBox() {
var template = document.getElementById('filterTemplate');
var templateClone = template.content.cloneNode(true);
templateClone.getElementById('fileSearch').oninput = onFilterInput;
template.parentElement.appendChild(templateClone);
}
// loads all columns
function loadColumns() {
var colNodes = getTableHeader().querySelectorAll('th'),
colNode,
cols = [],
col,
i;
for (i = 0; i < colNodes.length; i += 1) {
colNode = colNodes[i];
col = {
key: colNode.getAttribute('data-col'),
sortable: !colNode.getAttribute('data-nosort'),
type: colNode.getAttribute('data-type') || 'string'
};
cols.push(col);
if (col.sortable) {
col.defaultDescSort = col.type === 'number';
colNode.innerHTML =
colNode.innerHTML + '<span class="sorter"></span>';
}
}
return cols;
}
// attaches a data attribute to every tr element with an object
// of data values keyed by column name
function loadRowData(tableRow) {
var tableCols = tableRow.querySelectorAll('td'),
colNode,
col,
data = {},
i,
val;
for (i = 0; i < tableCols.length; i += 1) {
colNode = tableCols[i];
col = cols[i];
val = colNode.getAttribute('data-value');
if (col.type === 'number') {
val = Number(val);
}
data[col.key] = val;
}
return data;
}
// loads all row data
function loadData() {
var rows = getTableBody().querySelectorAll('tr'),
i;
for (i = 0; i < rows.length; i += 1) {
rows[i].data = loadRowData(rows[i]);
}
}
// sorts the table using the data for the ith column
function sortByIndex(index, desc) {
var key = cols[index].key,
sorter = function(a, b) {
a = a.data[key];
b = b.data[key];
return a < b ? -1 : a > b ? 1 : 0;
},
finalSorter = sorter,
tableBody = document.querySelector('.coverage-summary tbody'),
rowNodes = tableBody.querySelectorAll('tr'),
rows = [],
i;
if (desc) {
finalSorter = function(a, b) {
return -1 * sorter(a, b);
};
}
for (i = 0; i < rowNodes.length; i += 1) {
rows.push(rowNodes[i]);
tableBody.removeChild(rowNodes[i]);
}
rows.sort(finalSorter);
for (i = 0; i < rows.length; i += 1) {
tableBody.appendChild(rows[i]);
}
}
// removes sort indicators for current column being sorted
function removeSortIndicators() {
var col = getNthColumn(currentSort.index),
cls = col.className;
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
col.className = cls;
}
// adds sort indicators for current column being sorted
function addSortIndicators() {
getNthColumn(currentSort.index).className += currentSort.desc
? ' sorted-desc'
: ' sorted';
}
// adds event listeners for all sorter widgets
function enableUI() {
var i,
el,
ithSorter = function ithSorter(i) {
var col = cols[i];
return function() {
var desc = col.defaultDescSort;
if (currentSort.index === i) {
desc = !currentSort.desc;
}
sortByIndex(i, desc);
removeSortIndicators();
currentSort.index = i;
currentSort.desc = desc;
addSortIndicators();
};
};
for (i = 0; i < cols.length; i += 1) {
if (cols[i].sortable) {
// add the click event handler on the th so users
// dont have to click on those tiny arrows
el = getNthColumn(i).querySelector('.sorter').parentElement;
if (el.addEventListener) {
el.addEventListener('click', ithSorter(i));
} else {
el.attachEvent('onclick', ithSorter(i));
}
}
}
}
// adds sorting functionality to the UI
return function() {
if (!getTable()) {
return;
}
cols = loadColumns();
loadData();
addSearchBox();
addSortIndicators();
enableUI();
};
})();
window.addEventListener('load', addSorting);

View File

@@ -0,0 +1,757 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for valueFormatting.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> valueFormatting.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">80.35% </span>
<span class="quiet">Statements</span>
<span class='fraction'>180/224</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">65.57% </span>
<span class="quiet">Branches</span>
<span class='fraction'>40/61</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">83.33% </span>
<span class="quiet">Functions</span>
<span class='fraction'>10/12</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">80.35% </span>
<span class="quiet">Lines</span>
<span class='fraction'>180/224</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a>
<a name='L195'></a><a href='#L195'>195</a>
<a name='L196'></a><a href='#L196'>196</a>
<a name='L197'></a><a href='#L197'>197</a>
<a name='L198'></a><a href='#L198'>198</a>
<a name='L199'></a><a href='#L199'>199</a>
<a name='L200'></a><a href='#L200'>200</a>
<a name='L201'></a><a href='#L201'>201</a>
<a name='L202'></a><a href='#L202'>202</a>
<a name='L203'></a><a href='#L203'>203</a>
<a name='L204'></a><a href='#L204'>204</a>
<a name='L205'></a><a href='#L205'>205</a>
<a name='L206'></a><a href='#L206'>206</a>
<a name='L207'></a><a href='#L207'>207</a>
<a name='L208'></a><a href='#L208'>208</a>
<a name='L209'></a><a href='#L209'>209</a>
<a name='L210'></a><a href='#L210'>210</a>
<a name='L211'></a><a href='#L211'>211</a>
<a name='L212'></a><a href='#L212'>212</a>
<a name='L213'></a><a href='#L213'>213</a>
<a name='L214'></a><a href='#L214'>214</a>
<a name='L215'></a><a href='#L215'>215</a>
<a name='L216'></a><a href='#L216'>216</a>
<a name='L217'></a><a href='#L217'>217</a>
<a name='L218'></a><a href='#L218'>218</a>
<a name='L219'></a><a href='#L219'>219</a>
<a name='L220'></a><a href='#L220'>220</a>
<a name='L221'></a><a href='#L221'>221</a>
<a name='L222'></a><a href='#L222'>222</a>
<a name='L223'></a><a href='#L223'>223</a>
<a name='L224'></a><a href='#L224'>224</a>
<a name='L225'></a><a href='#L225'>225</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">68x</span>
<span class="cline-any cline-yes">68x</span>
<span class="cline-any cline-yes">68x</span>
<span class="cline-any cline-yes">68x</span>
<span class="cline-any cline-yes">68x</span>
<span class="cline-any cline-yes">68x</span>
<span class="cline-any cline-yes">68x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">const SI_PREFIX_MULTIPLIERS = {
Y: 1e24, Z: 1e21, E: 1e18, P: 1e15, T: 1e12,
G: 1e9, M: 1e6, k: 1e3,
m: 1e-3, u: 1e-6, µ: 1e-6, n: 1e-9, p: 1e-12,
f: 1e-15, a: 1e-18, z: 1e-21, y: 1e-24,
};
&nbsp;
const NUMBER_WITH_UNIT_RE = /^([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)\s*(.*)?$/;
&nbsp;
/**
* Parse a string like "1.5 nm" into { numeric: 1.5e-9, unit: "m" }.
* Returns null if the string does not start with a valid number.
* The numeric value is scaled to the base SI unit via the prefix.
*/
export <span class="fstat-no" title="function not covered" >function parseNumberWithUnit(text) {</span>
<span class="cstat-no" title="statement not covered" > const s = String(text ?? '').trim();</span>
<span class="cstat-no" title="statement not covered" > if (!s) return { numeric: 0, unit: '' };</span>
<span class="cstat-no" title="statement not covered" ></span>
<span class="cstat-no" title="statement not covered" > const m = s.match(NUMBER_WITH_UNIT_RE);</span>
<span class="cstat-no" title="statement not covered" > if (!m) return null;</span>
<span class="cstat-no" title="statement not covered" ></span>
<span class="cstat-no" title="statement not covered" > const numeric = parseFloat(m[1]);</span>
<span class="cstat-no" title="statement not covered" > const unitStr = (m[2] ?? '').trim();</span>
<span class="cstat-no" title="statement not covered" ></span>
<span class="cstat-no" title="statement not covered" > if (!unitStr) return { numeric, unit: '' };</span>
<span class="cstat-no" title="statement not covered" ></span>
<span class="cstat-no" title="statement not covered" > if (unitStr.length &gt;= 2) {</span>
<span class="cstat-no" title="statement not covered" > const prefix = unitStr[0];</span>
<span class="cstat-no" title="statement not covered" > const rest = unitStr.slice(1);</span>
<span class="cstat-no" title="statement not covered" > const multiplier = SI_PREFIX_MULTIPLIERS[prefix];</span>
<span class="cstat-no" title="statement not covered" > if (multiplier !== undefined &amp;&amp; PREFIXABLE_UNITS.has(rest)) {</span>
<span class="cstat-no" title="statement not covered" > return { numeric: numeric * multiplier, unit: rest };</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" ></span>
<span class="cstat-no" title="statement not covered" > return { numeric, unit: unitStr };</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
const SI_PREFIXES = [
{ exp: -24, prefix: 'y' },
{ exp: -21, prefix: 'z' },
{ exp: -18, prefix: 'a' },
{ exp: -15, prefix: 'f' },
{ exp: -12, prefix: 'p' },
{ exp: -9, prefix: 'n' },
{ exp: -6, prefix: 'u' },
{ exp: -3, prefix: 'm' },
{ exp: 0, prefix: '' },
{ exp: 3, prefix: 'k' },
{ exp: 6, prefix: 'M' },
{ exp: 9, prefix: 'G' },
{ exp: 12, prefix: 'T' },
{ exp: 15, prefix: 'P' },
{ exp: 18, prefix: 'E' },
{ exp: 21, prefix: 'Z' },
{ exp: 24, prefix: 'Y' },
];
&nbsp;
const PREFIXABLE_UNITS = new Set([
'm', 's', 'A', 'V', 'W', 'Hz', 'F', 'C', 'J', 'N', 'Pa', 'T', 'H', 'S', 'g', 'K', 'Ohm', 'ohm', 'Ω',
]);
&nbsp;
const SUPERSCRIPT_DIGITS = {
'-': '⁻',
'0': '⁰',
'1': '¹',
'2': '²',
'3': '³',
'4': '⁴',
'5': '⁵',
'6': '⁶',
'7': '⁷',
'8': '⁸',
'9': '⁹',
};
&nbsp;
export function formatNumericCell(value) {
if (value == null) <span class="branch-0 cbranch-no" title="branch not covered" >return '';</span>
if (typeof value === 'number') {
if (!Number.isFinite(value)) <span class="branch-0 cbranch-no" title="branch not covered" >return String(value);</span>
const abs = Math.abs(value);
if (Number.isInteger(value) &amp;&amp; abs &lt; 1e6) return String(value);
if ((abs &gt; 0 &amp;&amp; abs &lt; 1e-3) || abs &gt;= 1e4) <span class="branch-0 cbranch-no" title="branch not covered" >return value.toExponential(3);</span>
return value.toFixed(4).replace(/\.?0+$/, '');
}
<span class="cstat-no" title="statement not covered" ><span class="branch-0 cbranch-no" title="branch not covered" > if (Array.isArray(value)) return value.join(', ');</span></span>
<span class="cstat-no" title="statement not covered" > return String(value);</span>
}
&nbsp;
function toSuperscript(text) {
return String(text)
.split('')
.map((char) =&gt; SUPERSCRIPT_DIGITS[char] <span class="branch-0 cbranch-no" title="branch not covered" >|| char)</span>
.join('');
}
&nbsp;
export function formatDisplayUnit(unit) {
return String(unit <span class="branch-0 cbranch-no" title="branch not covered" >?? '')</span>.replace(/\^(-?\d+)/g, (_, exponent) =&gt; toSuperscript(exponent));
}
&nbsp;
function parsePrefixableUnit(unit) {
const text = String(unit <span class="branch-0 cbranch-no" title="branch not covered" >?? '')</span>.trim();
if (!text) <span class="branch-0 cbranch-no" title="branch not covered" >return null;</span>
&nbsp;
const poweredMatch = text.match(/^([A-Za-zΩ]+)\^([1-9]\d*)$/);
if (poweredMatch) {
const [, baseUnit, powerText] = poweredMatch;
if (!PREFIXABLE_UNITS.has(baseUnit)) return null;
return { baseUnit, power: Number.parseInt(powerText, 10) };
}
&nbsp;
if (!PREFIXABLE_UNITS.has(text)) <span class="branch-0 cbranch-no" title="branch not covered" >return null;</span>
return { baseUnit: text, power: 1 };
}
&nbsp;
function formatPrefixedUnit(baseUnit, prefix, power) {
return power === 1 ? `${prefix}${baseUnit}` : `${prefix}${baseUnit}${toSuperscript(power)}`;
}
&nbsp;
function choosePrefixExponent(value, power) {
const abs = Math.abs(value);
const candidates = SI_PREFIXES.map(({ exp, prefix }) =&gt; {
const scaled = value / (10 ** (exp * power));
return {
exp,
prefix,
scaled,
absScaled: Math.abs(scaled),
};
});
&nbsp;
const inRange = candidates.filter(({ absScaled }) =&gt; absScaled &gt;= 1 &amp;&amp; absScaled &lt; 999.5);
if (inRange.length &gt; 0) {
return inRange.reduce((best, candidate) =&gt; (candidate.absScaled &lt; best.absScaled ? candidate : best));
}
&nbsp;
const aboveOne = candidates.filter(({ absScaled }) =&gt; absScaled &gt;= 1);
if (aboveOne.length &gt; 0) {
return aboveOne.reduce((best, candidate) =&gt; (candidate.absScaled &lt; best.absScaled ? candidate <span class="branch-0 cbranch-no" title="branch not covered" >: best)</span>);
}
<span class="cstat-no" title="statement not covered" ><span class="branch-0 cbranch-no" title="branch not covered" ></span></span>
<span class="cstat-no" title="statement not covered" > return candidates.reduce((best, candidate) =&gt; (candidate.absScaled &gt; best.absScaled ? candidate : best));</span>
}
&nbsp;
/**
* Given a representative axis value and a unit string, returns the scale factor
* and prefixed unit label to use for a whole axis.
* All tick values should be divided by `scale` before display, and `unitLabel` shown once.
*/
export <span class="fstat-no" title="function not covered" >function getAxisScale(representativeValue, unit) {</span>
<span class="cstat-no" title="statement not covered" > if (!unit || typeof representativeValue !== 'number' || !Number.isFinite(representativeValue) || representativeValue === 0) {</span>
<span class="cstat-no" title="statement not covered" > return { scale: 1, unitLabel: unit || '' };</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > const { valueText, unitText } = applySIPrefix(representativeValue, unit);</span>
<span class="cstat-no" title="statement not covered" > const scaled = parseFloat(valueText);</span>
<span class="cstat-no" title="statement not covered" > if (!Number.isFinite(scaled) || scaled === 0) return { scale: 1, unitLabel: unit };</span>
<span class="cstat-no" title="statement not covered" > return { scale: representativeValue / scaled, unitLabel: unitText };</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
export function applySIPrefix(value, unit) {
const formattedUnit = formatDisplayUnit(unit);
if (typeof value !== 'number' || !Number.isFinite(value)) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
<span class="cstat-no" title="statement not covered" > return { valueText: formatNumericCell(value), unitText: formattedUnit };</span>
<span class="cstat-no" title="statement not covered" > }</span>
if (value === 0) <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
<span class="cstat-no" title="statement not covered" > return { valueText: '0', unitText: formattedUnit };</span>
<span class="cstat-no" title="statement not covered" > }</span>
&nbsp;
const parsedUnit = parsePrefixableUnit(unit);
if (!parsedUnit) {
return { valueText: formatNumericCell(value), unitText: formattedUnit };
}
&nbsp;
const chosenPrefix = choosePrefixExponent(value, parsedUnit.power);
return {
valueText: formatNumericCell(chosenPrefix.scaled),
unitText: formatPrefixedUnit(parsedUnit.baseUnit, chosenPrefix.prefix, parsedUnit.power),
};
}
&nbsp;
function getCompanionUnitColumn(column, row) {
if (!row || typeof row !== 'object' || typeof column !== 'string' || column === 'unit') <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
<span class="cstat-no" title="statement not covered" > return null;</span>
<span class="cstat-no" title="statement not covered" > }</span>
const unitColumn = `${column}_unit`;
return typeof row?.[unitColumn] === 'string' ? unitColumn <span class="branch-0 cbranch-no" title="branch not covered" >: null;</span>
}
&nbsp;
export function getTableColumns(rows) {
const columns = [];
const hiddenColumns = new Set();
&nbsp;
for (const row of rows <span class="branch-0 cbranch-no" title="branch not covered" >|| [])</span> {
if (!row || typeof row !== 'object') <span class="branch-0 cbranch-no" title="branch not covered" >continue;</span>
for (const key of Object.keys(row)) {
if (
key.endsWith('_unit')
&amp;&amp; key.length &gt; 5
&amp;&amp; Object.prototype.hasOwnProperty.call(row, key.slice(0, -5))
) {
hiddenColumns.add(key);
continue;
}
if (!columns.includes(key)) columns.push(key);
}
}
&nbsp;
return columns.filter((column) =&gt; !hiddenColumns.has(column));
}
&nbsp;
export function formatTableRowCell(row, column) {
const companionUnitColumn = getCompanionUnitColumn(column, row);
if (companionUnitColumn) {
const formatted = applySIPrefix(row?.[column], row?.[companionUnitColumn]);
return formatted.unitText ? `${formatted.valueText} ${formatted.unitText}` <span class="branch-0 cbranch-no" title="branch not covered" >: formatted.valueText;</span>
}
<span class="branch-0 cbranch-no" title="branch not covered" > if (column === 'value' &amp;&amp; typeof row?.unit === 'string')</span> <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
<span class="cstat-no" title="statement not covered" > return applySIPrefix(row?.value, row.unit).valueText;</span>
<span class="cstat-no" title="statement not covered" > }</span>
if (column === 'unit' &amp;&amp; typeof row?.unit === 'string') <span class="branch-0 cbranch-no" title="branch not covered" >{</span>
<span class="cstat-no" title="statement not covered" > return applySIPrefix(row?.value, row.unit).unitText;</span>
<span class="cstat-no" title="statement not covered" > }</span>
return formatNumericCell(row?.[column]);
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,664 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for workflowCapture.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> workflowCapture.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">40.93% </span>
<span class="quiet">Statements</span>
<span class='fraction'>79/193</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">36.84% </span>
<span class="quiet">Branches</span>
<span class='fraction'>7/19</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">16.66% </span>
<span class="quiet">Functions</span>
<span class='fraction'>2/12</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">40.93% </span>
<span class="quiet">Lines</span>
<span class='fraction'>79/193</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line low'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { toBlob } from 'html-to-image';
import { CANVAS_COLORS } from './constants.js';
&nbsp;
export const OVERLAY_CAPTURE_SELECTORS = [
'.lineplot-overlay',
'.cs-overlay',
'.crop-overlay',
];
&nbsp;
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function encodeBase64(bytes) {</span></span>
<span class="cstat-no" title="statement not covered" > if (typeof Buffer !== 'undefined') {</span>
<span class="cstat-no" title="statement not covered" > return Buffer.from(bytes).toString('base64');</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" ></span>
<span class="cstat-no" title="statement not covered" > let binary = '';</span>
<span class="cstat-no" title="statement not covered" > for (let i = 0; i &lt; bytes.length; i += 1) {</span>
<span class="cstat-no" title="statement not covered" > binary += String.fromCharCode(bytes[i]);</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > return btoa(binary);</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >async function blobToDataUrl(blob) {</span></span>
<span class="cstat-no" title="statement not covered" > if (!blob) return null;</span>
<span class="cstat-no" title="statement not covered" > const bytes = new Uint8Array(await blob.arrayBuffer());</span>
<span class="cstat-no" title="statement not covered" > return `data:${blob.type || 'image/png'};base64,${encodeBase64(bytes)}`;</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function getElementSize(el) {</span></span>
<span class="cstat-no" title="statement not covered" > const rect = el.getBoundingClientRect?.() ?? { width: 0, height: 0 };</span>
<span class="cstat-no" title="statement not covered" > return {</span>
<span class="cstat-no" title="statement not covered" > width: Math.max(1, Math.round(el.clientWidth || rect.width || 0)),</span>
<span class="cstat-no" title="statement not covered" > height: Math.max(1, Math.round(el.clientHeight || rect.height || 0)),</span>
<span class="cstat-no" title="statement not covered" > };</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
export <span class="fstat-no" title="function not covered" >async function waitForImageElement(img) {</span>
<span class="cstat-no" title="statement not covered" > if (img.complete &amp;&amp; img.naturalWidth &gt; 0) return;</span>
<span class="cstat-no" title="statement not covered" > if (typeof img.decode === 'function') {</span>
<span class="cstat-no" title="statement not covered" > try {</span>
<span class="cstat-no" title="statement not covered" > await img.decode();</span>
<span class="cstat-no" title="statement not covered" > return;</span>
<span class="cstat-no" title="statement not covered" > } catch {</span>
<span class="cstat-no" title="statement not covered" > // Fall back to load/error listeners below.</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" > await new Promise((resolve) =&gt; {</span>
<span class="cstat-no" title="statement not covered" > const done = () =&gt; {</span>
<span class="cstat-no" title="statement not covered" > img.removeEventListener('load', done);</span>
<span class="cstat-no" title="statement not covered" > img.removeEventListener('error', done);</span>
<span class="cstat-no" title="statement not covered" > resolve();</span>
<span class="cstat-no" title="statement not covered" > };</span>
<span class="cstat-no" title="statement not covered" > img.addEventListener('load', done, { once: true });</span>
<span class="cstat-no" title="statement not covered" > img.addEventListener('error', done, { once: true });</span>
<span class="cstat-no" title="statement not covered" > });</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
export <span class="fstat-no" title="function not covered" >async function getCaptureImageDataUrl(img) {</span>
<span class="cstat-no" title="statement not covered" > const src = img.currentSrc || img.src;</span>
<span class="cstat-no" title="statement not covered" > if (!src) return null;</span>
<span class="cstat-no" title="statement not covered" > if (!src.startsWith('data:')) return src;</span>
<span class="cstat-no" title="statement not covered" ></span>
<span class="cstat-no" title="statement not covered" > const { width, height } = getElementSize(img);</span>
<span class="cstat-no" title="statement not covered" > const scale = Math.min(2, globalThis.window?.devicePixelRatio || 1);</span>
<span class="cstat-no" title="statement not covered" ></span>
<span class="cstat-no" title="statement not covered" > const canvas = globalThis.document.createElement('canvas');</span>
<span class="cstat-no" title="statement not covered" > canvas.width = Math.max(1, Math.round(width * scale));</span>
<span class="cstat-no" title="statement not covered" > canvas.height = Math.max(1, Math.round(height * scale));</span>
<span class="cstat-no" title="statement not covered" ></span>
<span class="cstat-no" title="statement not covered" > const ctx = canvas.getContext('2d');</span>
<span class="cstat-no" title="statement not covered" > if (!ctx) return src;</span>
<span class="cstat-no" title="statement not covered" ></span>
<span class="cstat-no" title="statement not covered" > try {</span>
<span class="cstat-no" title="statement not covered" > ctx.drawImage(img, 0, 0, canvas.width, canvas.height);</span>
<span class="cstat-no" title="statement not covered" > return canvas.toDataURL('image/png');</span>
<span class="cstat-no" title="statement not covered" > } catch {</span>
<span class="cstat-no" title="statement not covered" > return src;</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
export <span class="fstat-no" title="function not covered" >function createCapturePlaceholder(</span>
<span class="cstat-no" title="statement not covered" > el,</span>
<span class="cstat-no" title="statement not covered" > dataUrl,</span>
<span class="cstat-no" title="statement not covered" > { stretch = false, documentRef = globalThis.document, getComputedStyleFn = globalThis.window?.getComputedStyle?.bind(globalThis.window) } = {},</span>
<span class="cstat-no" title="statement not covered" >) {</span>
<span class="cstat-no" title="statement not covered" > const rect = el.getBoundingClientRect();</span>
<span class="cstat-no" title="statement not covered" > const style = getComputedStyleFn(el);</span>
<span class="cstat-no" title="statement not covered" > const placeholder = documentRef.createElement('div');</span>
<span class="cstat-no" title="statement not covered" ></span>
<span class="cstat-no" title="statement not covered" > placeholder.style.display = style.display === 'inline' ? 'inline-block' : style.display;</span>
<span class="cstat-no" title="statement not covered" > placeholder.style.width = `${el.clientWidth || rect.width}px`;</span>
<span class="cstat-no" title="statement not covered" > placeholder.style.height = `${el.clientHeight || rect.height}px`;</span>
<span class="cstat-no" title="statement not covered" > placeholder.style.maxWidth = style.maxWidth;</span>
<span class="cstat-no" title="statement not covered" > placeholder.style.maxHeight = style.maxHeight;</span>
<span class="cstat-no" title="statement not covered" > placeholder.style.minWidth = style.minWidth;</span>
<span class="cstat-no" title="statement not covered" > placeholder.style.minHeight = style.minHeight;</span>
<span class="cstat-no" title="statement not covered" > placeholder.style.borderRadius = style.borderRadius;</span>
<span class="cstat-no" title="statement not covered" > placeholder.style.backgroundImage = `url("${dataUrl}")`;</span>
<span class="cstat-no" title="statement not covered" > placeholder.style.backgroundRepeat = 'no-repeat';</span>
<span class="cstat-no" title="statement not covered" > placeholder.style.backgroundPosition = 'center';</span>
<span class="cstat-no" title="statement not covered" > placeholder.style.backgroundSize = stretch ? '100% 100%' : 'contain';</span>
<span class="cstat-no" title="statement not covered" > placeholder.style.flexShrink = style.flexShrink;</span>
<span class="cstat-no" title="statement not covered" ></span>
<span class="cstat-no" title="statement not covered" > return placeholder;</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >async function renderCanvasToDataUrl(canvas) {</span></span>
<span class="cstat-no" title="statement not covered" > try {</span>
<span class="cstat-no" title="statement not covered" > return canvas.toDataURL('image/png');</span>
<span class="cstat-no" title="statement not covered" > } catch {</span>
<span class="cstat-no" title="statement not covered" > return null;</span>
<span class="cstat-no" title="statement not covered" > }</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >async function renderElementToDataUrl(el, toBlobImpl) {</span></span>
<span class="cstat-no" title="statement not covered" > const { width, height } = getElementSize(el);</span>
<span class="cstat-no" title="statement not covered" > const blob = await toBlobImpl(el, {</span>
<span class="cstat-no" title="statement not covered" > width,</span>
<span class="cstat-no" title="statement not covered" > height,</span>
<span class="cstat-no" title="statement not covered" > backgroundColor: CANVAS_COLORS.bgDeep,</span>
<span class="cstat-no" title="statement not covered" > style: {</span>
<span class="cstat-no" title="statement not covered" > width: `${width}px`,</span>
<span class="cstat-no" title="statement not covered" > height: `${height}px`,</span>
<span class="cstat-no" title="statement not covered" > transform: 'none',</span>
<span class="cstat-no" title="statement not covered" > },</span>
<span class="cstat-no" title="statement not covered" > });</span>
<span class="cstat-no" title="statement not covered" > return blobToDataUrl(blob);</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
async function replaceElementsWithPlaceholders(elements, renderDataUrl, createPlaceholderFn, stretch) {
const restorers = [];
&nbsp;
for (const el of elements) {
if (!el?.parentNode) <span class="branch-0 cbranch-no" title="branch not covered" >continue;</span>
const dataUrl = await renderDataUrl(el);
if (!dataUrl) <span class="branch-0 cbranch-no" title="branch not covered" >continue;</span>
&nbsp;
const placeholder = createPlaceholderFn(el, dataUrl, { stretch });
el.parentNode.replaceChild(placeholder, el);
restorers.push(() =&gt; {
if (placeholder.parentNode) {
placeholder.parentNode.replaceChild(el, placeholder);
}
});
}
&nbsp;
return restorers;
}
&nbsp;
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function defaultQueryAll(root, selector) {</span></span>
<span class="cstat-no" title="statement not covered" > return Array.from(root.querySelectorAll(selector));</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
<span class="cstat-no" title="statement not covered" ><span class="fstat-no" title="function not covered" >function defaultNextFrame() {</span></span>
<span class="cstat-no" title="statement not covered" > return new Promise((resolve) =&gt; requestAnimationFrame(() =&gt; resolve()));</span>
<span class="cstat-no" title="statement not covered" >}</span>
&nbsp;
export async function captureViewportBlob(viewportEl, options, deps = {}) {
const queryAll = deps.queryAll <span class="branch-0 cbranch-no" title="branch not covered" >?? defaultQueryAll;</span>
const toBlobImpl = deps.toBlobImpl <span class="branch-0 cbranch-no" title="branch not covered" >?? toBlob;</span>
const waitForImage = deps.waitForImageElement <span class="branch-0 cbranch-no" title="branch not covered" >?? waitForImageElement;</span>
const renderImage = deps.renderImageToDataUrl <span class="branch-0 cbranch-no" title="branch not covered" >?? getCaptureImageDataUrl;</span>
const renderCanvas = deps.renderCanvasToDataUrl <span class="branch-0 cbranch-no" title="branch not covered" >?? renderCanvasToDataUrl;</span>
const renderOverlay = deps.renderOverlayToDataUrl <span class="branch-0 cbranch-no" title="branch not covered" >?? (<span class="branch-0 cbranch-no" title="branch not covered" >(el) =&gt; renderElementToDataUrl(el, toBlobImpl))</span>;</span>
const createPlaceholderFn = deps.createPlaceholder <span class="branch-0 cbranch-no" title="branch not covered" >?? createCapturePlaceholder;</span>
const nextFrame = deps.nextFrame <span class="branch-0 cbranch-no" title="branch not covered" >?? defaultNextFrame;</span>
const overlaySelectors = deps.overlaySelectors ?? OVERLAY_CAPTURE_SELECTORS;
const restorers = [];
&nbsp;
const overlays = [];
const seen = new Set();
for (const selector of overlaySelectors) {
for (const el of queryAll(viewportEl, selector)) {
if (seen.has(el)) <span class="branch-0 cbranch-no" title="branch not covered" >continue;</span>
seen.add(el);
overlays.push(el);
}
}
&nbsp;
const images = queryAll(viewportEl, 'img');
await Promise.all(images.map(waitForImage));
&nbsp;
try {
restorers.push(...await replaceElementsWithPlaceholders(overlays, renderOverlay, createPlaceholderFn, true));
restorers.push(...await replaceElementsWithPlaceholders(queryAll(viewportEl, 'img'), renderImage, createPlaceholderFn, false));
restorers.push(...await replaceElementsWithPlaceholders(queryAll(viewportEl, 'canvas'), renderCanvas, createPlaceholderFn, true));
&nbsp;
await nextFrame();
await nextFrame();
return await toBlobImpl(viewportEl, options);
} finally {
restorers.reverse().forEach((restore) =&gt; restore());
}
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,331 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for workflowHydration.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> workflowHydration.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>82/82</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">73.52% </span>
<span class="quiet">Branches</span>
<span class='fraction'>25/34</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>5/5</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>82/82</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">3x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { sortNodesForParentOrder } from './nodeHierarchy.js';
import { sanitizeRuntimeValuesForPersistence } from './runtimeValuePersistence.js';
&nbsp;
function mergeDefinition(nodeData, defs) {
const savedData = nodeData <span class="branch-0 cbranch-no" title="branch not covered" >|| {};</span>
const registryDefinition = savedData.className ? defs[savedData.className] <span class="branch-0 cbranch-no" title="branch not covered" >: null;</span>
return registryDefinition || null;
}
&nbsp;
function getSocketType(inputDef) {
if (!inputDef) <span class="branch-0 cbranch-no" title="branch not covered" >return null;</span>
const [type] = Array.isArray(inputDef) ? inputDef <span class="branch-0 cbranch-no" title="branch not covered" >: [inputDef];</span>
return Array.isArray(type) ? type[0] : type;
}
&nbsp;
function getInputEntries(definition) {
return [
...Object.entries(definition?.input?.required || {}),
...Object.entries(definition?.input?.optional || {}),
];
}
&nbsp;
function sanitizeWidgetValues(widgetValues, definition) {
const nextValues = { ...(widgetValues <span class="branch-0 cbranch-no" title="branch not covered" >|| {})</span> };
&nbsp;
getInputEntries(definition).forEach(([inputName, inputDef]) =&gt; {
const type = getSocketType(inputDef);
if (type === 'FILE_PICKER' || type === 'FOLDER_PICKER') {
nextValues[inputName] = '';
}
});
&nbsp;
return nextValues;
}
&nbsp;
export function hydrateWorkflowState(data, defs = {}) {
const loadedNodes = Array.isArray(data?.nodes) ? data.nodes <span class="branch-0 cbranch-no" title="branch not covered" >: [];</span>
const loadedEdges = Array.isArray(data?.edges) ? data.edges <span class="branch-0 cbranch-no" title="branch not covered" >: [];</span>
&nbsp;
const nodes = sortNodesForParentOrder(loadedNodes.map((node) =&gt; {
const definition = mergeDefinition(node.data, defs);
&nbsp;
return {
...node,
type: node.type || 'custom',
className: node.className,
parentId: node.parentId,
extent: node.extent,
hidden: !!node.hidden,
style: node.style,
dragHandle: node.dragHandle || '.drag-handle',
data: {
...node.data,
label: node.data?.label || node.data?.className <span class="branch-0 cbranch-no" title="branch not covered" >|| 'Node',</span>
widgetValues: sanitizeWidgetValues(node.data?.widgetValues, definition),
runtimeValues: sanitizeRuntimeValuesForPersistence(
node.data?.className,
node.data?.runtimeValues,
),
...(node.data?.extraData || {}),
definition,
previewImage: null,
tableRows: null,
meshData: null,
overlay: null,
scalarValue: null,
processingTimeMs: null,
warning: null,
},
};
}));
&nbsp;
const edges = loadedEdges.map((edge) =&gt; ({ ...edge }));
&nbsp;
const nextNodeId = Math.max(0, ...loadedNodes.map((node) =&gt; parseInt(node.id, 10) <span class="branch-0 cbranch-no" title="branch not covered" >|| 0)</span>) + 1;
&nbsp;
return {
nodes,
edges,
nextNodeId,
};
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

View File

@@ -0,0 +1,280 @@
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for workflowSerialization.js</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> workflowSerialization.js</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Statements</span>
<span class='fraction'>65/65</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">64.7% </span>
<span class="quiet">Branches</span>
<span class='fraction'>22/34</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>4/4</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Lines</span>
<span class='fraction'>65/65</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">10x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">21x</span>
<span class="cline-any cline-yes">21x</span>
<span class="cline-any cline-yes">21x</span>
<span class="cline-any cline-yes">21x</span>
<span class="cline-any cline-yes">21x</span>
<span class="cline-any cline-yes">21x</span>
<span class="cline-any cline-yes">21x</span>
<span class="cline-any cline-yes">21x</span>
<span class="cline-any cline-yes">21x</span>
<span class="cline-any cline-yes">21x</span>
<span class="cline-any cline-yes">21x</span>
<span class="cline-any cline-yes">21x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">5x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-yes">4x</span>
<span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import { sanitizeRuntimeValuesForPersistence } from './runtimeValuePersistence.js';
&nbsp;
export function serializeWorkflowState(nodes, edges) {
const compactObject = (value) =&gt; {
if (!value || typeof value !== 'object') <span class="branch-0 cbranch-no" title="branch not covered" >return null;</span>
const entries = Object.entries(value);
return entries.length &gt; 0 <span class="branch-0 cbranch-no" title="branch not covered" >? Object.fromEntries(entries) </span>: null;
};
const getExtraData = (data) =&gt; compactObject(Object.fromEntries(
Object.entries(data <span class="branch-0 cbranch-no" title="branch not covered" >|| {})</span>.filter(([key]) =&gt; ![
'label',
'className',
'widgetValues',
'runtimeValues',
'definition',
'previewImage',
'tableRows',
'meshData',
'overlay',
'scalarValue',
'processingTimeMs',
'warning',
].includes(key))
));
const getRuntimeValues = (node) =&gt; compactObject(
sanitizeRuntimeValuesForPersistence(node.data?.className, node.data?.runtimeValues),
);
&nbsp;
return {
version: 1,
nodes: nodes.map((node) =&gt; {
const runtimeValues = getRuntimeValues(node);
return {
id: node.id,
type: node.type || 'custom',
position: node.position,
...(node.className ? { className: node.className } : {}),
...(node.parentId <span class="branch-0 cbranch-no" title="branch not covered" >? { parentId: node.parentId } </span>: {}),
...(node.extent <span class="branch-0 cbranch-no" title="branch not covered" >? { extent: node.extent } </span>: {}),
...(node.hidden <span class="branch-0 cbranch-no" title="branch not covered" >? { hidden: true } </span>: {}),
...(node.style ? { style: node.style } : {}),
dragHandle: node.dragHandle || '.drag-handle',
data: {
label: node.data?.label || node.data?.className <span class="branch-0 cbranch-no" title="branch not covered" >|| 'Node',</span>
className: node.data?.className <span class="branch-0 cbranch-no" title="branch not covered" >|| '',</span>
widgetValues: node.data?.widgetValues || {},
...(runtimeValues <span class="branch-0 cbranch-no" title="branch not covered" >? { runtimeValues } </span>: {}),
...(getExtraData(node.data) <span class="branch-0 cbranch-no" title="branch not covered" >? { extraData: getExtraData(node.data) } </span>: {}),
output: node.data?.definition?.output || [],
output_name: node.data?.definition?.output_name || [],
},
};
}),
edges: edges.map((edge) =&gt; ({
id: edge.id,
source: edge.source,
sourceHandle: edge.sourceHandle,
target: edge.target,
targetHandle: edge.targetHandle,
...(edge.style ? { style: edge.style } : {}),
...(edge.hidden <span class="branch-0 cbranch-no" title="branch not covered" >? { hidden: true } </span>: {}),
...(edge.data <span class="branch-0 cbranch-no" title="branch not covered" >? { data: edge.data } </span>: {}),
})),
};
}
&nbsp;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2026-03-30T02:57:17.889Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>

2979
frontend/coverage/lcov.info Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,8 @@
"dev": "vite --force",
"build": "vite build --emptyOutDir",
"preview": "vite preview",
"test": "node --test tests/**/*.test.mjs"
"test": "node --test tests/**/*.test.mjs",
"test:coverage": "c8 --reporter=text --reporter=lcov node --test tests/**/*.test.mjs"
},
"dependencies": {
"@xyflow/react": "^12.0.0",
@@ -21,6 +22,7 @@
},
"devDependencies": {
"@vitejs/plugin-react": "^4.3.0",
"c8": "^10.1.3",
"vite": "^5.4.0"
}
}

View File

@@ -36,6 +36,19 @@ import {
resolveLoadNodeChannelPath,
} from './loadNodeOutputs.js';
import { buildDefaultWidgetValues } from './nodeWidgetDefaults.js';
import {
getHandleType,
getInputName,
getOutputSlot,
encodeProxyHandleRef,
parseGroupProxyHandle,
getConnectionHandleType,
getResolvedHandleRef,
getNodeInputSpecForHandle,
outputTypeCanConnectToTarget,
resolveOutputTypeForTarget,
checkConnectionValid,
} from './connectionUtils.js';
import {
getSpecTypeAndOptions,
@@ -58,50 +71,6 @@ const CANVAS_MAX_ZOOM = 4;
const CANVAS_RIGHT_DRAG_ZOOM_SENSITIVITY = 0.0065;
const CANVAS_RIGHT_DRAG_ZOOM_THRESHOLD = 5;
// ── Handle ID helpers ─────────────────────────────────────────────────
function getHandleType(handleId) {
return handleId.split('::')[2];
}
function getInputName(handleId) {
return handleId.split('::')[1];
}
function getOutputSlot(handleId) {
return parseInt(handleId.split('::')[1], 10);
}
function encodeProxyHandleRef(handleId) {
return encodeURIComponent(String(handleId || ''));
}
function decodeProxyHandleRef(encoded) {
try {
return decodeURIComponent(String(encoded || ''));
} catch {
return String(encoded || '');
}
}
function parseGroupProxyHandle(handleId) {
const text = String(handleId || '');
if (!text.startsWith('group-proxy::')) return null;
const parts = text.split('::');
if (parts.length < 5) return null;
return {
direction: parts[1],
nodeId: parts[2],
type: parts[3],
realHandle: decodeProxyHandleRef(parts.slice(4).join('::')),
};
}
function getConnectionHandleType(handleId) {
const proxy = parseGroupProxyHandle(handleId);
return proxy?.type || getHandleType(handleId);
}
function getNodeDimension(node, axis) {
if (axis === 'width') return node.measured?.width || node.style?.width || node.width || 200;
return node.measured?.height || node.style?.height || node.height || 120;
@@ -432,61 +401,6 @@ function compareMenuCategories(a, b) {
return String(a?.name || '').localeCompare(String(b?.name || ''));
}
function getResolvedHandleRef(nodeId, handleId) {
const proxy = parseGroupProxyHandle(handleId);
return {
nodeId: proxy?.nodeId || nodeId,
handleId: proxy?.realHandle || handleId,
type: proxy?.type || getHandleType(handleId),
};
}
function getNodeInputSpecForHandle(node, handleId) {
const definition = node?.data?.definition;
if (!definition?.input) return null;
const inputName = getInputName(handleId);
return definition.input.required?.[inputName]
|| definition.input.optional?.[inputName]
|| null;
}
function socketTypesCompatible(sourceType, targetSpecOrType) {
return socketSpecAcceptsType(sourceType, targetSpecOrType);
}
function outputTypeCanConnectToTarget(outputType, targetSpecOrType, outputAcceptedTypes = []) {
if (socketTypesCompatible(outputType, targetSpecOrType)) {
return true;
}
// Polymorphic output: the output socket declares it can also produce the target type
if (outputAcceptedTypes.length > 0) {
const targetType = Array.isArray(targetSpecOrType) ? targetSpecOrType[0] : targetSpecOrType;
if (outputAcceptedTypes.includes(targetType)) return true;
}
return outputType === 'ANNOTATION_SOURCE'
&& !socketTypesCompatible('ANNOTATION_SOURCE', targetSpecOrType)
&& (
socketTypesCompatible('DATA_FIELD', targetSpecOrType)
|| socketTypesCompatible('IMAGE', targetSpecOrType)
);
}
function resolveOutputTypeForTarget(outputType, targetSpecOrType) {
if (outputType !== 'ANNOTATION_SOURCE') {
return outputType;
}
if (socketTypesCompatible('ANNOTATION_SOURCE', targetSpecOrType)) {
return 'ANNOTATION_SOURCE';
}
if (socketTypesCompatible('DATA_FIELD', targetSpecOrType)) {
return 'DATA_FIELD';
}
if (socketTypesCompatible('IMAGE', targetSpecOrType)) {
return 'IMAGE';
}
return 'ANNOTATION_SOURCE';
}
function getRenderedNodeBounds(nodes) {
let minX = Infinity;
let minY = Infinity;
@@ -675,7 +589,7 @@ function ContextMenu({
const opt = def.input.optional || {};
const allInputs = { ...req, ...opt };
const hasMatch = Object.values(allInputs).some((spec) => {
return socketTypesCompatible(filterType, spec);
return socketSpecAcceptsType(filterType, spec);
});
if (!hasMatch) continue;
} else {
@@ -1328,7 +1242,7 @@ function Flow() {
const resolvedTarget = getResolvedHandleRef(edge.target, edge.targetHandle);
const targetNode = reactFlow.getNode(resolvedTarget.nodeId);
const targetSpec = getNodeInputSpecForHandle(targetNode, resolvedTarget.handleId) || resolvedTarget.type;
return socketTypesCompatible(outputType, targetSpec);
return socketSpecAcceptsType(outputType, targetSpec);
}));
}, [reactFlow, setEdges, setNodeOutputs]);
@@ -1392,22 +1306,10 @@ function Flow() {
// ── Connection handling ─────────────────────────────────────────────
const isValidConnection = useCallback((connection) => {
const srcType = getConnectionHandleType(connection.sourceHandle);
const resolvedTarget = getResolvedHandleRef(connection.target, connection.targetHandle);
const targetNode = reactFlow.getNode(resolvedTarget.nodeId);
const targetSpec = getNodeInputSpecForHandle(targetNode, resolvedTarget.handleId) || resolvedTarget.type;
if (socketTypesCompatible(srcType, targetSpec)) return true;
// Polymorphic output: check if the source output declares it can produce the target type
const srcProxy = parseGroupProxyHandle(connection.sourceHandle);
const srcNodeId = srcProxy ? srcProxy.nodeId : connection.source;
const srcHandleId = srcProxy ? srcProxy.realHandle : connection.sourceHandle;
const srcNode = reactFlow.getNode(srcNodeId);
const srcSlot = getOutputSlot(srcHandleId);
const srcAcceptedTypes = srcNode?.data?.definition?.output_accepted_types?.[srcSlot] || [];
const targetType = Array.isArray(targetSpec) ? targetSpec[0] : targetSpec;
return Array.isArray(srcAcceptedTypes) && srcAcceptedTypes.includes(targetType);
}, [reactFlow]);
const isValidConnection = useCallback(
(connection) => checkConnectionValid(connection, (id) => reactFlow.getNode(id)),
[reactFlow],
);
const onConnect = useCallback((params) => {
const sourceProxy = parseGroupProxyHandle(params.sourceHandle);
@@ -1759,7 +1661,7 @@ function Flow() {
// Dragged from an output → connect to the first matching input on the new node
const allInputs = { ...(def.input.required || {}), ...(def.input.optional || {}) };
const inputName = Object.entries(allInputs).find(([, spec]) => {
return socketTypesCompatible(filterType, spec);
return socketSpecAcceptsType(filterType, spec);
})?.[0];
if (inputName) {
const targetType = (() => {

View File

@@ -0,0 +1,122 @@
// ── Connection utility functions ───────────────────────────────────────
// Pure functions extracted from App.jsx so they can be independently tested.
import { socketSpecAcceptsType } from './constants.js';
// ── Handle ID helpers ─────────────────────────────────────────────────
export function getHandleType(handleId) {
return handleId.split('::')[2];
}
export function getInputName(handleId) {
return handleId.split('::')[1];
}
export function getOutputSlot(handleId) {
return parseInt(handleId.split('::')[1], 10);
}
export function encodeProxyHandleRef(handleId) {
return encodeURIComponent(String(handleId || ''));
}
export function decodeProxyHandleRef(encoded) {
try {
return decodeURIComponent(String(encoded || ''));
} catch {
return String(encoded || '');
}
}
export function parseGroupProxyHandle(handleId) {
const text = String(handleId || '');
if (!text.startsWith('group-proxy::')) return null;
const parts = text.split('::');
if (parts.length < 5) return null;
return {
direction: parts[1],
nodeId: parts[2],
type: parts[3],
realHandle: decodeProxyHandleRef(parts.slice(4).join('::')),
};
}
export function getConnectionHandleType(handleId) {
const proxy = parseGroupProxyHandle(handleId);
return proxy?.type || getHandleType(handleId);
}
export function getResolvedHandleRef(nodeId, handleId) {
const proxy = parseGroupProxyHandle(handleId);
return {
nodeId: proxy?.nodeId || nodeId,
handleId: proxy?.realHandle || handleId,
type: proxy?.type || getHandleType(handleId),
};
}
export function getNodeInputSpecForHandle(node, handleId) {
const definition = node?.data?.definition;
if (!definition?.input) return null;
const inputName = getInputName(handleId);
return definition.input.required?.[inputName]
|| definition.input.optional?.[inputName]
|| null;
}
// ── Type compatibility ────────────────────────────────────────────────
export function outputTypeCanConnectToTarget(outputType, targetSpecOrType, outputAcceptedTypes = []) {
if (socketSpecAcceptsType(outputType, targetSpecOrType)) {
return true;
}
// Polymorphic output: the output socket declares it can also produce the target type
if (outputAcceptedTypes.length > 0) {
const targetType = Array.isArray(targetSpecOrType) ? targetSpecOrType[0] : targetSpecOrType;
if (outputAcceptedTypes.includes(targetType)) return true;
}
return outputType === 'ANNOTATION_SOURCE'
&& !socketSpecAcceptsType('ANNOTATION_SOURCE', targetSpecOrType)
&& (
socketSpecAcceptsType('DATA_FIELD', targetSpecOrType)
|| socketSpecAcceptsType('IMAGE', targetSpecOrType)
);
}
export function resolveOutputTypeForTarget(outputType, targetSpecOrType) {
if (outputType !== 'ANNOTATION_SOURCE') {
return outputType;
}
if (socketSpecAcceptsType('ANNOTATION_SOURCE', targetSpecOrType)) {
return 'ANNOTATION_SOURCE';
}
if (socketSpecAcceptsType('DATA_FIELD', targetSpecOrType)) {
return 'DATA_FIELD';
}
if (socketSpecAcceptsType('IMAGE', targetSpecOrType)) {
return 'IMAGE';
}
return 'ANNOTATION_SOURCE';
}
// ── Pure connection validation ────────────────────────────────────────
// Extracted from the isValidConnection useCallback so it can be unit-tested
// without a ReactFlow context. Pass a `getNodeFn` that mirrors reactFlow.getNode.
export function checkConnectionValid(connection, getNodeFn) {
const srcType = getConnectionHandleType(connection.sourceHandle);
const resolvedTarget = getResolvedHandleRef(connection.target, connection.targetHandle);
const targetNode = getNodeFn(resolvedTarget.nodeId);
const targetSpec = getNodeInputSpecForHandle(targetNode, resolvedTarget.handleId) || resolvedTarget.type;
if (socketSpecAcceptsType(srcType, targetSpec)) return true;
// Polymorphic output: check if the source output declares it can produce the target type
const srcProxy = parseGroupProxyHandle(connection.sourceHandle);
const srcNodeId = srcProxy ? srcProxy.nodeId : connection.source;
const srcHandleId = srcProxy ? srcProxy.realHandle : connection.sourceHandle;
const srcNode = getNodeFn(srcNodeId);
const srcSlot = getOutputSlot(srcHandleId);
const srcAcceptedTypes = srcNode?.data?.definition?.output_accepted_types?.[srcSlot] || [];
const targetType = Array.isArray(targetSpec) ? targetSpec[0] : targetSpec;
return Array.isArray(srcAcceptedTypes) && srcAcceptedTypes.includes(targetType);
}

View File

@@ -0,0 +1,371 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {
getHandleType,
getInputName,
getOutputSlot,
encodeProxyHandleRef,
decodeProxyHandleRef,
parseGroupProxyHandle,
getConnectionHandleType,
getResolvedHandleRef,
getNodeInputSpecForHandle,
outputTypeCanConnectToTarget,
resolveOutputTypeForTarget,
checkConnectionValid,
} from '../src/connectionUtils.js';
// ── Handle ID helpers ─────────────────────────────────────────────────
test('getHandleType extracts the type segment from a handle ID', () => {
assert.equal(getHandleType('output::0::DATA_FIELD'), 'DATA_FIELD');
assert.equal(getHandleType('input::field::DATA_FIELD'), 'DATA_FIELD');
assert.equal(getHandleType('output::2::LINE'), 'LINE');
});
test('getInputName extracts the name segment from an input handle ID', () => {
assert.equal(getInputName('input::field::DATA_FIELD'), 'field');
assert.equal(getInputName('input::colormap::COLORMAP'), 'colormap');
});
test('getOutputSlot extracts the slot index from an output handle ID', () => {
assert.equal(getOutputSlot('output::0::DATA_FIELD'), 0);
assert.equal(getOutputSlot('output::3::LINE'), 3);
});
test('encodeProxyHandleRef round-trips with decodeProxyHandleRef', () => {
const handle = 'output::0::DATA_FIELD';
assert.equal(decodeProxyHandleRef(encodeProxyHandleRef(handle)), handle);
});
test('encodeProxyHandleRef handles null/undefined gracefully', () => {
assert.equal(decodeProxyHandleRef(encodeProxyHandleRef(null)), '');
assert.equal(decodeProxyHandleRef(encodeProxyHandleRef(undefined)), '');
});
test('decodeProxyHandleRef falls back for malformed percent-encoding', () => {
// '%zz' is not valid percent-encoding; should return the raw string
const bad = '%zz';
const result = decodeProxyHandleRef(bad);
assert.equal(result, bad);
});
test('decodeProxyHandleRef handles a handle with colons preserved after round-trip', () => {
const handle = 'output::1::RECORD_TABLE';
const encoded = encodeProxyHandleRef(handle);
assert.ok(!encoded.includes('::'), 'colons should be encoded');
assert.equal(decodeProxyHandleRef(encoded), handle);
});
// ── parseGroupProxyHandle ─────────────────────────────────────────────
test('parseGroupProxyHandle returns null for regular handles', () => {
assert.equal(parseGroupProxyHandle('output::0::DATA_FIELD'), null);
assert.equal(parseGroupProxyHandle('input::field::DATA_FIELD'), null);
assert.equal(parseGroupProxyHandle(''), null);
assert.equal(parseGroupProxyHandle(null), null);
});
test('parseGroupProxyHandle returns null for too-short proxy handles', () => {
assert.equal(parseGroupProxyHandle('group-proxy::out::nodeA'), null);
assert.equal(parseGroupProxyHandle('group-proxy::out::nodeA::DATA_FIELD'), null);
});
test('parseGroupProxyHandle parses a valid outbound proxy handle', () => {
const inner = 'output::0::DATA_FIELD';
const encoded = encodeProxyHandleRef(inner);
const proxy = parseGroupProxyHandle(`group-proxy::out::node42::DATA_FIELD::${encoded}`);
assert.ok(proxy !== null);
assert.equal(proxy.direction, 'out');
assert.equal(proxy.nodeId, 'node42');
assert.equal(proxy.type, 'DATA_FIELD');
assert.equal(proxy.realHandle, inner);
});
test('parseGroupProxyHandle parses a valid inbound proxy handle', () => {
const inner = 'input::field::DATA_FIELD';
const encoded = encodeProxyHandleRef(inner);
const proxy = parseGroupProxyHandle(`group-proxy::in::node7::DATA_FIELD::${encoded}`);
assert.ok(proxy !== null);
assert.equal(proxy.direction, 'in');
assert.equal(proxy.nodeId, 'node7');
assert.equal(proxy.realHandle, inner);
});
test('parseGroupProxyHandle handles colons inside the encoded real handle', () => {
// The real handle contains '::' which gets encoded; the join should reconstruct correctly
const inner = 'output::2::LINE';
const encoded = encodeProxyHandleRef(inner);
const proxyId = `group-proxy::out::n1::LINE::${encoded}`;
const proxy = parseGroupProxyHandle(proxyId);
assert.equal(proxy.realHandle, inner);
});
// ── getConnectionHandleType ───────────────────────────────────────────
test('getConnectionHandleType returns type from a regular handle', () => {
assert.equal(getConnectionHandleType('output::0::DATA_FIELD'), 'DATA_FIELD');
assert.equal(getConnectionHandleType('input::x::LINE'), 'LINE');
});
test('getConnectionHandleType returns type from a proxy handle', () => {
const encoded = encodeProxyHandleRef('output::0::IMAGE');
const proxy = `group-proxy::out::nodeX::IMAGE::${encoded}`;
assert.equal(getConnectionHandleType(proxy), 'IMAGE');
});
// ── getResolvedHandleRef ──────────────────────────────────────────────
test('getResolvedHandleRef returns identity for a regular handle', () => {
const ref = getResolvedHandleRef('node1', 'input::field::DATA_FIELD');
assert.equal(ref.nodeId, 'node1');
assert.equal(ref.handleId, 'input::field::DATA_FIELD');
assert.equal(ref.type, 'DATA_FIELD');
});
test('getResolvedHandleRef unwraps a proxy handle to the real node and handle', () => {
const inner = 'input::field::DATA_FIELD';
const encoded = encodeProxyHandleRef(inner);
const proxyId = `group-proxy::in::realNode::DATA_FIELD::${encoded}`;
const ref = getResolvedHandleRef('groupNode', proxyId);
assert.equal(ref.nodeId, 'realNode');
assert.equal(ref.handleId, inner);
assert.equal(ref.type, 'DATA_FIELD');
});
// ── getNodeInputSpecForHandle ─────────────────────────────────────────
test('getNodeInputSpecForHandle returns null for missing node', () => {
assert.equal(getNodeInputSpecForHandle(null, 'input::field::DATA_FIELD'), null);
assert.equal(getNodeInputSpecForHandle(undefined, 'input::field::DATA_FIELD'), null);
});
test('getNodeInputSpecForHandle returns null when definition has no input', () => {
const node = { data: { definition: {} } };
assert.equal(getNodeInputSpecForHandle(node, 'input::field::DATA_FIELD'), null);
});
test('getNodeInputSpecForHandle finds a required input spec', () => {
const spec = ['DATA_FIELD', {}];
const node = { data: { definition: { input: { required: { field: spec }, optional: {} } } } };
assert.deepEqual(getNodeInputSpecForHandle(node, 'input::field::DATA_FIELD'), spec);
});
test('getNodeInputSpecForHandle finds an optional input spec', () => {
const spec = ['LINE', { accepted_types: ['DATA_FIELD'] }];
const node = { data: { definition: { input: { required: {}, optional: { profile: spec } } } } };
assert.deepEqual(getNodeInputSpecForHandle(node, 'input::profile::LINE'), spec);
});
test('getNodeInputSpecForHandle returns null for unknown input name', () => {
const node = { data: { definition: { input: { required: { field: ['DATA_FIELD', {}] }, optional: {} } } } };
assert.equal(getNodeInputSpecForHandle(node, 'input::nonexistent::DATA_FIELD'), null);
});
// ── outputTypeCanConnectToTarget ──────────────────────────────────────
test('outputTypeCanConnectToTarget allows exact type match', () => {
assert.equal(outputTypeCanConnectToTarget('DATA_FIELD', 'DATA_FIELD'), true);
assert.equal(outputTypeCanConnectToTarget('LINE', 'LINE'), true);
assert.equal(outputTypeCanConnectToTarget('FLOAT', 'FLOAT'), true);
});
test('outputTypeCanConnectToTarget allows INT↔FLOAT via socket compatibility', () => {
assert.equal(outputTypeCanConnectToTarget('INT', 'FLOAT'), true);
assert.equal(outputTypeCanConnectToTarget('FLOAT', 'INT'), true);
});
test('outputTypeCanConnectToTarget rejects incompatible types with no accepted_types', () => {
assert.equal(outputTypeCanConnectToTarget('DATA_FIELD', 'LINE'), false);
assert.equal(outputTypeCanConnectToTarget('LINE', 'IMAGE'), false);
assert.equal(outputTypeCanConnectToTarget('FLOAT', 'DATA_FIELD'), false);
});
test('outputTypeCanConnectToTarget allows polymorphic match via outputAcceptedTypes', () => {
// Output socket declares LINE but can also emit DATA_FIELD
assert.equal(outputTypeCanConnectToTarget('LINE', 'DATA_FIELD', ['DATA_FIELD']), true);
assert.equal(outputTypeCanConnectToTarget('LINE', 'IMAGE', ['DATA_FIELD']), false);
});
test('outputTypeCanConnectToTarget allows polymorphic match from spec array', () => {
const spec = ['DATA_FIELD', {}];
assert.equal(outputTypeCanConnectToTarget('LINE', spec, ['DATA_FIELD']), true);
});
test('outputTypeCanConnectToTarget: ANNOTATION_SOURCE connects to DATA_FIELD and IMAGE targets', () => {
assert.equal(outputTypeCanConnectToTarget('ANNOTATION_SOURCE', 'DATA_FIELD'), true);
assert.equal(outputTypeCanConnectToTarget('ANNOTATION_SOURCE', 'IMAGE'), true);
});
test('outputTypeCanConnectToTarget: ANNOTATION_SOURCE connects to ANNOTATION_SOURCE target', () => {
assert.equal(outputTypeCanConnectToTarget('ANNOTATION_SOURCE', 'ANNOTATION_SOURCE'), true);
});
test('outputTypeCanConnectToTarget: ANNOTATION_SOURCE does not connect to LINE', () => {
assert.equal(outputTypeCanConnectToTarget('ANNOTATION_SOURCE', 'LINE'), false);
});
test('outputTypeCanConnectToTarget: empty outputAcceptedTypes does not affect result', () => {
assert.equal(outputTypeCanConnectToTarget('DATA_FIELD', 'LINE', []), false);
});
test('outputTypeCanConnectToTarget: accepts target given as spec array', () => {
assert.equal(
outputTypeCanConnectToTarget('DATA_FIELD', ['DATA_FIELD', { label: 'input' }]),
true,
);
});
// ── resolveOutputTypeForTarget ────────────────────────────────────────
test('resolveOutputTypeForTarget returns non-ANNOTATION_SOURCE types unchanged', () => {
assert.equal(resolveOutputTypeForTarget('DATA_FIELD', 'DATA_FIELD'), 'DATA_FIELD');
assert.equal(resolveOutputTypeForTarget('LINE', 'LINE'), 'LINE');
assert.equal(resolveOutputTypeForTarget('IMAGE', 'IMAGE'), 'IMAGE');
});
test('resolveOutputTypeForTarget keeps ANNOTATION_SOURCE when target accepts it', () => {
assert.equal(resolveOutputTypeForTarget('ANNOTATION_SOURCE', 'ANNOTATION_SOURCE'), 'ANNOTATION_SOURCE');
});
test('resolveOutputTypeForTarget resolves ANNOTATION_SOURCE to DATA_FIELD when target is DATA_FIELD', () => {
assert.equal(resolveOutputTypeForTarget('ANNOTATION_SOURCE', 'DATA_FIELD'), 'DATA_FIELD');
});
test('resolveOutputTypeForTarget resolves ANNOTATION_SOURCE to IMAGE when target is IMAGE', () => {
assert.equal(resolveOutputTypeForTarget('ANNOTATION_SOURCE', 'IMAGE'), 'IMAGE');
});
test('resolveOutputTypeForTarget falls back to ANNOTATION_SOURCE for unknown target', () => {
assert.equal(resolveOutputTypeForTarget('ANNOTATION_SOURCE', 'LINE'), 'ANNOTATION_SOURCE');
assert.equal(resolveOutputTypeForTarget('ANNOTATION_SOURCE', 'RECORD_TABLE'), 'ANNOTATION_SOURCE');
});
// ── checkConnectionValid ──────────────────────────────────────────────
function makeNode(id, inputs, outputAcceptedTypes = []) {
return {
id,
data: {
definition: {
input: inputs,
output_accepted_types: outputAcceptedTypes,
},
},
};
}
function makeGetNode(nodes) {
const map = Object.fromEntries(nodes.map((n) => [n.id, n]));
return (id) => map[id] ?? null;
}
test('checkConnectionValid accepts a direct type match', () => {
const srcNode = makeNode('src', {});
const tgtNode = makeNode('tgt', { required: { field: ['DATA_FIELD', {}] } });
const getNode = makeGetNode([srcNode, tgtNode]);
const conn = {
source: 'src',
sourceHandle: 'output::0::DATA_FIELD',
target: 'tgt',
targetHandle: 'input::field::DATA_FIELD',
};
assert.equal(checkConnectionValid(conn, getNode), true);
});
test('checkConnectionValid rejects a type mismatch with no accepted_types', () => {
const srcNode = makeNode('src', {});
const tgtNode = makeNode('tgt', { required: { field: ['LINE', {}] } });
const getNode = makeGetNode([srcNode, tgtNode]);
const conn = {
source: 'src',
sourceHandle: 'output::0::DATA_FIELD',
target: 'tgt',
targetHandle: 'input::field::LINE',
};
assert.equal(checkConnectionValid(conn, getNode), false);
});
test('checkConnectionValid accepts INT→FLOAT via intrinsic compatibility', () => {
const srcNode = makeNode('src', {});
const tgtNode = makeNode('tgt', { required: { v: ['FLOAT', {}] } });
const getNode = makeGetNode([srcNode, tgtNode]);
const conn = {
source: 'src',
sourceHandle: 'output::0::INT',
target: 'tgt',
targetHandle: 'input::v::FLOAT',
};
assert.equal(checkConnectionValid(conn, getNode), true);
});
test('checkConnectionValid accepts polymorphic output_accepted_types match', () => {
// Source slot 0 declares it can also produce DATA_FIELD (its primary type is LINE)
const srcNode = makeNode('src', {}, [['DATA_FIELD']]);
const tgtNode = makeNode('tgt', { required: { field: ['DATA_FIELD', {}] } });
const getNode = makeGetNode([srcNode, tgtNode]);
const conn = {
source: 'src',
sourceHandle: 'output::0::LINE',
target: 'tgt',
targetHandle: 'input::field::DATA_FIELD',
};
assert.equal(checkConnectionValid(conn, getNode), true);
});
test('checkConnectionValid rejects when accepted_types does not include target type', () => {
const srcNode = makeNode('src', {}, [['IMAGE']]);
const tgtNode = makeNode('tgt', { required: { field: ['DATA_FIELD', {}] } });
const getNode = makeGetNode([srcNode, tgtNode]);
const conn = {
source: 'src',
sourceHandle: 'output::0::LINE',
target: 'tgt',
targetHandle: 'input::field::DATA_FIELD',
};
assert.equal(checkConnectionValid(conn, getNode), false);
});
test('checkConnectionValid falls back to handle type when target node is missing', () => {
const srcNode = makeNode('src', {});
const getNode = makeGetNode([srcNode]);
const conn = {
source: 'src',
sourceHandle: 'output::0::DATA_FIELD',
target: 'missing',
targetHandle: 'input::field::DATA_FIELD',
};
// resolvedTarget.type will be DATA_FIELD, which matches the source
assert.equal(checkConnectionValid(conn, getNode), true);
});
test('checkConnectionValid handles group proxy source handles', () => {
const realSrcNode = makeNode('realSrc', {}, [[]]);
const tgtNode = makeNode('tgt', { required: { field: ['DATA_FIELD', {}] } });
const getNode = makeGetNode([realSrcNode, tgtNode]);
const realHandle = 'output::0::DATA_FIELD';
const proxyHandle = `group-proxy::out::realSrc::DATA_FIELD::${encodeProxyHandleRef(realHandle)}`;
const conn = {
source: 'groupNode',
sourceHandle: proxyHandle,
target: 'tgt',
targetHandle: 'input::field::DATA_FIELD',
};
assert.equal(checkConnectionValid(conn, getNode), true);
});
test('checkConnectionValid handles group proxy target handles', () => {
const srcNode = makeNode('src', {});
const realTgtNode = makeNode('realTgt', { required: { field: ['DATA_FIELD', {}] } });
const getNode = makeGetNode([srcNode, realTgtNode]);
const realHandle = 'input::field::DATA_FIELD';
const proxyHandle = `group-proxy::in::realTgt::DATA_FIELD::${encodeProxyHandleRef(realHandle)}`;
const conn = {
source: 'src',
sourceHandle: 'output::0::DATA_FIELD',
target: 'groupNode',
targetHandle: proxyHandle,
};
assert.equal(checkConnectionValid(conn, getNode), true);
});

View File

@@ -24,7 +24,7 @@ test('accepted_types extend canonical socket compatibility without reintroducing
assert.equal(isDataSocketSpec(spec), true);
assert.deepEqual(
Array.from(getAcceptedSocketTypes(spec)).sort(),
['RECORD_TABLE', 'DATA_TABLE'],
['DATA_TABLE', 'RECORD_TABLE'],
);
assert.equal(socketSpecAcceptsType('DATA_TABLE', spec), true);
assert.equal(socketSpecAcceptsType('LINE', spec), false);