From 8193ce818a14fa4b531134f18132e490ae32e476 Mon Sep 17 00:00:00 2001 From: David Fencl Date: Fri, 5 Jun 2026 23:08:44 +0200 Subject: [PATCH] feat: implement tests and coverage reports for KpiCards and scrapeLakes functionality --- coverage/base.css | 224 ++++ coverage/block-navigation.js | 87 ++ coverage/clover.xml | 133 ++ coverage/coverage-final.json | 5 + coverage/favicon.png | Bin 0 -> 445 bytes coverage/index.html | 146 +++ coverage/prettify.css | 1 + coverage/prettify.js | 2 + coverage/scripts/index.html | 131 ++ coverage/scripts/lakesConfig.ts.html | 160 +++ coverage/scripts/scrapeLakes.ts.html | 616 +++++++++ coverage/sort-arrow-sprite.png | Bin 0 -> 138 bytes coverage/sorter.js | 210 ++++ coverage/src/components/KpiCards.tsx.html | 466 +++++++ coverage/src/components/index.html | 116 ++ coverage/src/index.html | 116 ++ coverage/src/translations.ts.html | 385 ++++++ package-lock.json | 1306 +++++++++++++++++++- package.json | 13 +- public/data/MARI.json | 9 + public/data/MZHR.json | 11 +- public/data/VLHN.json | 9 + public/data/VLKO.json | 9 + public/data/VLL1.json | 11 +- public/data/VLL2.json | 9 + public/data/VLOR.json | 9 + public/data/VLSL.json | 9 + public/data/VLST.json | 9 + public/data/lakes_index.json | 8 +- scripts/__tests__/scrapeLakes.test.ts | 25 + scripts/buildIndex.ts | 2 +- scripts/scrapeLakes.ts | 8 +- src/__tests__/translations.test.ts | 34 + src/components/LakeDetail.tsx | 13 +- src/components/__tests__/KpiCards.test.tsx | 41 + src/test/setup.ts | 1 + vite.config.ts | 6 + 37 files changed, 4309 insertions(+), 31 deletions(-) create mode 100644 coverage/base.css create mode 100644 coverage/block-navigation.js create mode 100644 coverage/clover.xml create mode 100644 coverage/coverage-final.json create mode 100644 coverage/favicon.png create mode 100644 coverage/index.html create mode 100644 coverage/prettify.css create mode 100644 coverage/prettify.js create mode 100644 coverage/scripts/index.html create mode 100644 coverage/scripts/lakesConfig.ts.html create mode 100644 coverage/scripts/scrapeLakes.ts.html create mode 100644 coverage/sort-arrow-sprite.png create mode 100644 coverage/sorter.js create mode 100644 coverage/src/components/KpiCards.tsx.html create mode 100644 coverage/src/components/index.html create mode 100644 coverage/src/index.html create mode 100644 coverage/src/translations.ts.html create mode 100644 scripts/__tests__/scrapeLakes.test.ts create mode 100644 src/__tests__/translations.test.ts create mode 100644 src/components/__tests__/KpiCards.test.tsx create mode 100644 src/test/setup.ts diff --git a/coverage/base.css b/coverage/base.css new file mode 100644 index 0000000..f418035 --- /dev/null +++ b/coverage/base.css @@ -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; +} diff --git a/coverage/block-navigation.js b/coverage/block-navigation.js new file mode 100644 index 0000000..530d1ed --- /dev/null +++ b/coverage/block-navigation.js @@ -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); diff --git a/coverage/clover.xml b/coverage/clover.xml new file mode 100644 index 0000000..f3b6e18 --- /dev/null +++ b/coverage/clover.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/coverage/coverage-final.json b/coverage/coverage-final.json new file mode 100644 index 0000000..9655c78 --- /dev/null +++ b/coverage/coverage-final.json @@ -0,0 +1,5 @@ +{"/Users/davis/WebstormProjects/davisfe.cz/scripts/lakesConfig.ts": {"path":"/Users/davis/WebstormProjects/davisfe.cz/scripts/lakesConfig.ts","statementMap":{"0":{"start":{"line":12,"column":41},"end":{"line":25,"column":null}}},"fnMap":{},"branchMap":{},"s":{"0":1},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":1,"seen":{"s:12:41:25:Infinity":0}}} +,"/Users/davis/WebstormProjects/davisfe.cz/scripts/scrapeLakes.ts": {"path":"/Users/davis/WebstormProjects/davisfe.cz/scripts/scrapeLakes.ts","statementMap":{"0":{"start":{"line":20,"column":2},"end":{"line":37,"column":null}},"1":{"start":{"line":21,"column":4},"end":{"line":21,"column":null}},"2":{"start":{"line":21,"column":44},"end":{"line":21,"column":null}},"3":{"start":{"line":22,"column":33},"end":{"line":22,"column":null}},"4":{"start":{"line":23,"column":31},"end":{"line":23,"column":null}},"5":{"start":{"line":24,"column":29},"end":{"line":24,"column":null}},"6":{"start":{"line":26,"column":4},"end":{"line":26,"column":null}},"7":{"start":{"line":26,"column":25},"end":{"line":26,"column":null}},"8":{"start":{"line":28,"column":14},"end":{"line":28,"column":null}},"9":{"start":{"line":29,"column":14},"end":{"line":29,"column":null}},"10":{"start":{"line":30,"column":17},"end":{"line":30,"column":null}},"11":{"start":{"line":31,"column":14},"end":{"line":31,"column":null}},"12":{"start":{"line":32,"column":4},"end":{"line":32,"column":null}},"13":{"start":{"line":32,"column":28},"end":{"line":32,"column":null}},"14":{"start":{"line":33,"column":4},"end":{"line":33,"column":null}},"15":{"start":{"line":33,"column":77},"end":{"line":33,"column":null}},"16":{"start":{"line":34,"column":4},"end":{"line":34,"column":null}},"17":{"start":{"line":36,"column":4},"end":{"line":36,"column":null}},"18":{"start":{"line":41,"column":14},"end":{"line":41,"column":null}},"19":{"start":{"line":42,"column":20},"end":{"line":42,"column":null}},"20":{"start":{"line":44,"column":2},"end":{"line":160,"column":null}},"21":{"start":{"line":45,"column":18},"end":{"line":45,"column":null}},"22":{"start":{"line":46,"column":21},"end":{"line":51,"column":null}},"23":{"start":{"line":53,"column":14},"end":{"line":53,"column":null}},"24":{"start":{"line":55,"column":24},"end":{"line":55,"column":null}},"25":{"start":{"line":56,"column":24},"end":{"line":56,"column":null}},"26":{"start":{"line":57,"column":37},"end":{"line":57,"column":null}},"27":{"start":{"line":58,"column":39},"end":{"line":58,"column":null}},"28":{"start":{"line":60,"column":4},"end":{"line":78,"column":null}},"29":{"start":{"line":61,"column":19},"end":{"line":61,"column":null}},"30":{"start":{"line":62,"column":6},"end":{"line":77,"column":null}},"31":{"start":{"line":63,"column":8},"end":{"line":76,"column":null}},"32":{"start":{"line":64,"column":24},"end":{"line":64,"column":null}},"33":{"start":{"line":65,"column":25},"end":{"line":65,"column":null}},"34":{"start":{"line":66,"column":10},"end":{"line":66,"column":null}},"35":{"start":{"line":66,"column":40},"end":{"line":66,"column":null}},"36":{"start":{"line":67,"column":10},"end":{"line":67,"column":null}},"37":{"start":{"line":67,"column":39},"end":{"line":67,"column":null}},"38":{"start":{"line":68,"column":10},"end":{"line":71,"column":null}},"39":{"start":{"line":69,"column":22},"end":{"line":69,"column":null}},"40":{"start":{"line":70,"column":12},"end":{"line":70,"column":null}},"41":{"start":{"line":70,"column":27},"end":{"line":70,"column":null}},"42":{"start":{"line":72,"column":10},"end":{"line":75,"column":null}},"43":{"start":{"line":73,"column":22},"end":{"line":73,"column":null}},"44":{"start":{"line":74,"column":12},"end":{"line":74,"column":null}},"45":{"start":{"line":74,"column":27},"end":{"line":74,"column":null}},"46":{"start":{"line":80,"column":34},"end":{"line":80,"column":null}},"47":{"start":{"line":81,"column":20},"end":{"line":81,"column":null}},"48":{"start":{"line":82,"column":4},"end":{"line":86,"column":null}},"49":{"start":{"line":83,"column":6},"end":{"line":85,"column":null}},"50":{"start":{"line":84,"column":8},"end":{"line":84,"column":null}},"51":{"start":{"line":88,"column":4},"end":{"line":112,"column":null}},"52":{"start":{"line":89,"column":6},"end":{"line":111,"column":null}},"53":{"start":{"line":90,"column":8},"end":{"line":90,"column":null}},"54":{"start":{"line":90,"column":21},"end":{"line":90,"column":null}},"55":{"start":{"line":91,"column":21},"end":{"line":91,"column":null}},"56":{"start":{"line":92,"column":8},"end":{"line":110,"column":null}},"57":{"start":{"line":93,"column":26},"end":{"line":93,"column":null}},"58":{"start":{"line":94,"column":27},"end":{"line":94,"column":null}},"59":{"start":{"line":95,"column":24},"end":{"line":95,"column":null}},"60":{"start":{"line":96,"column":10},"end":{"line":98,"column":null}},"61":{"start":{"line":97,"column":12},"end":{"line":97,"column":null}},"62":{"start":{"line":100,"column":32},"end":{"line":100,"column":null}},"63":{"start":{"line":101,"column":10},"end":{"line":109,"column":null}},"64":{"start":{"line":102,"column":12},"end":{"line":108,"column":null}},"65":{"start":{"line":114,"column":4},"end":{"line":120,"column":null}},"66":{"start":{"line":116,"column":6},"end":{"line":116,"column":null}},"67":{"start":{"line":117,"column":6},"end":{"line":117,"column":null}},"68":{"start":{"line":118,"column":6},"end":{"line":118,"column":null}},"69":{"start":{"line":118,"column":32},"end":{"line":118,"column":null}},"70":{"start":{"line":119,"column":6},"end":{"line":119,"column":null}},"71":{"start":{"line":119,"column":34},"end":{"line":119,"column":null}},"72":{"start":{"line":122,"column":37},"end":{"line":122,"column":null}},"73":{"start":{"line":123,"column":4},"end":{"line":126,"column":null}},"74":{"start":{"line":124,"column":26},"end":{"line":124,"column":null}},"75":{"start":{"line":125,"column":6},"end":{"line":125,"column":null}},"76":{"start":{"line":128,"column":20},"end":{"line":128,"column":null}},"77":{"start":{"line":129,"column":4},"end":{"line":129,"column":null}},"78":{"start":{"line":129,"column":33},"end":{"line":129,"column":66}},"79":{"start":{"line":130,"column":4},"end":{"line":130,"column":null}},"80":{"start":{"line":130,"column":28},"end":{"line":130,"column":61}},"81":{"start":{"line":132,"column":23},"end":{"line":134,"column":null}},"82":{"start":{"line":133,"column":6},"end":{"line":133,"column":null}},"83":{"start":{"line":137,"column":39},"end":{"line":137,"column":null}},"84":{"start":{"line":138,"column":41},"end":{"line":138,"column":null}},"85":{"start":{"line":139,"column":4},"end":{"line":151,"column":null}},"86":{"start":{"line":140,"column":6},"end":{"line":144,"column":null}},"87":{"start":{"line":141,"column":8},"end":{"line":141,"column":null}},"88":{"start":{"line":142,"column":6},"end":{"line":144,"column":null}},"89":{"start":{"line":143,"column":8},"end":{"line":143,"column":null}},"90":{"start":{"line":146,"column":6},"end":{"line":150,"column":null}},"91":{"start":{"line":147,"column":8},"end":{"line":147,"column":null}},"92":{"start":{"line":148,"column":6},"end":{"line":150,"column":null}},"93":{"start":{"line":149,"column":8},"end":{"line":149,"column":null}},"94":{"start":{"line":153,"column":4},"end":{"line":153,"column":null}},"95":{"start":{"line":154,"column":4},"end":{"line":154,"column":null}},"96":{"start":{"line":156,"column":4},"end":{"line":156,"column":null}},"97":{"start":{"line":159,"column":4},"end":{"line":159,"column":null}},"98":{"start":{"line":164,"column":2},"end":{"line":164,"column":null}},"99":{"start":{"line":166,"column":2},"end":{"line":172,"column":null}},"100":{"start":{"line":168,"column":30},"end":{"line":168,"column":null}},"101":{"start":{"line":169,"column":4},"end":{"line":169,"column":null}},"102":{"start":{"line":171,"column":4},"end":{"line":171,"column":null}},"103":{"start":{"line":171,"column":33},"end":{"line":171,"column":57}},"104":{"start":{"line":174,"column":2},"end":{"line":174,"column":null}},"105":{"start":{"line":177,"column":0},"end":{"line":177,"column":null}}},"fnMap":{"0":{"name":"parseDateString","decl":{"start":{"line":19,"column":16},"end":{"line":19,"column":32}},"loc":{"start":{"line":19,"column":64},"end":{"line":38,"column":null}},"line":19},"1":{"name":"scrapeLake","decl":{"start":{"line":40,"column":15},"end":{"line":40,"column":26}},"loc":{"start":{"line":40,"column":75},"end":{"line":161,"column":null}},"line":40},"2":{"name":"(anonymous_2)","decl":{"start":{"line":60,"column":20},"end":{"line":60,"column":21}},"loc":{"start":{"line":60,"column":32},"end":{"line":78,"column":5}},"line":60},"3":{"name":"(anonymous_3)","decl":{"start":{"line":63,"column":31},"end":{"line":63,"column":32}},"loc":{"start":{"line":63,"column":41},"end":{"line":76,"column":9}},"line":63},"4":{"name":"(anonymous_4)","decl":{"start":{"line":82,"column":20},"end":{"line":82,"column":21}},"loc":{"start":{"line":82,"column":32},"end":{"line":86,"column":5}},"line":82},"5":{"name":"(anonymous_5)","decl":{"start":{"line":89,"column":32},"end":{"line":89,"column":33}},"loc":{"start":{"line":89,"column":44},"end":{"line":111,"column":7}},"line":89},"6":{"name":"(anonymous_6)","decl":{"start":{"line":129,"column":25},"end":{"line":129,"column":33}},"loc":{"start":{"line":129,"column":33},"end":{"line":129,"column":66}},"line":129},"7":{"name":"(anonymous_7)","decl":{"start":{"line":130,"column":20},"end":{"line":130,"column":28}},"loc":{"start":{"line":130,"column":28},"end":{"line":130,"column":61}},"line":130},"8":{"name":"(anonymous_8)","decl":{"start":{"line":132,"column":57},"end":{"line":132,"column":58}},"loc":{"start":{"line":132,"column":67},"end":{"line":134,"column":5}},"line":132},"9":{"name":"(anonymous_9)","decl":{"start":{"line":139,"column":23},"end":{"line":139,"column":31}},"loc":{"start":{"line":139,"column":31},"end":{"line":151,"column":5}},"line":139},"10":{"name":"runScraper","decl":{"start":{"line":163,"column":15},"end":{"line":163,"column":28}},"loc":{"start":{"line":163,"column":28},"end":{"line":175,"column":null}},"line":163},"11":{"name":"(anonymous_11)","decl":{"start":{"line":171,"column":22},"end":{"line":171,"column":33}},"loc":{"start":{"line":171,"column":33},"end":{"line":171,"column":57}},"line":171}},"branchMap":{"0":{"loc":{"start":{"line":21,"column":4},"end":{"line":21,"column":null}},"type":"if","locations":[{"start":{"line":21,"column":4},"end":{"line":21,"column":null}},{"start":{},"end":{}}],"line":21},"1":{"loc":{"start":{"line":21,"column":8},"end":{"line":21,"column":44}},"type":"binary-expr","locations":[{"start":{"line":21,"column":8},"end":{"line":21,"column":20}},{"start":{"line":21,"column":20},"end":{"line":21,"column":44}}],"line":21},"2":{"loc":{"start":{"line":26,"column":4},"end":{"line":26,"column":null}},"type":"if","locations":[{"start":{"line":26,"column":4},"end":{"line":26,"column":null}},{"start":{},"end":{}}],"line":26},"3":{"loc":{"start":{"line":26,"column":8},"end":{"line":26,"column":25}},"type":"binary-expr","locations":[{"start":{"line":26,"column":8},"end":{"line":26,"column":17}},{"start":{"line":26,"column":17},"end":{"line":26,"column":25}}],"line":26},"4":{"loc":{"start":{"line":32,"column":4},"end":{"line":32,"column":null}},"type":"if","locations":[{"start":{"line":32,"column":4},"end":{"line":32,"column":null}},{"start":{},"end":{}}],"line":32},"5":{"loc":{"start":{"line":33,"column":4},"end":{"line":33,"column":null}},"type":"if","locations":[{"start":{"line":33,"column":4},"end":{"line":33,"column":null}},{"start":{},"end":{}}],"line":33},"6":{"loc":{"start":{"line":33,"column":8},"end":{"line":33,"column":77}},"type":"binary-expr","locations":[{"start":{"line":33,"column":8},"end":{"line":33,"column":33}},{"start":{"line":33,"column":33},"end":{"line":33,"column":55}},{"start":{"line":33,"column":55},"end":{"line":33,"column":77}}],"line":33},"7":{"loc":{"start":{"line":62,"column":6},"end":{"line":77,"column":null}},"type":"if","locations":[{"start":{"line":62,"column":6},"end":{"line":77,"column":null}},{"start":{},"end":{}}],"line":62},"8":{"loc":{"start":{"line":62,"column":10},"end":{"line":62,"column":72}},"type":"binary-expr","locations":[{"start":{"line":62,"column":10},"end":{"line":62,"column":47}},{"start":{"line":62,"column":47},"end":{"line":62,"column":72}}],"line":62},"9":{"loc":{"start":{"line":66,"column":10},"end":{"line":66,"column":null}},"type":"if","locations":[{"start":{"line":66,"column":10},"end":{"line":66,"column":null}},{"start":{},"end":{}}],"line":66},"10":{"loc":{"start":{"line":66,"column":56},"end":{"line":66,"column":null}},"type":"binary-expr","locations":[{"start":{"line":66,"column":56},"end":{"line":66,"column":78}},{"start":{"line":66,"column":78},"end":{"line":66,"column":null}}],"line":66},"11":{"loc":{"start":{"line":67,"column":10},"end":{"line":67,"column":null}},"type":"if","locations":[{"start":{"line":67,"column":10},"end":{"line":67,"column":null}},{"start":{},"end":{}}],"line":67},"12":{"loc":{"start":{"line":67,"column":55},"end":{"line":67,"column":null}},"type":"binary-expr","locations":[{"start":{"line":67,"column":55},"end":{"line":67,"column":77}},{"start":{"line":67,"column":77},"end":{"line":67,"column":null}}],"line":67},"13":{"loc":{"start":{"line":68,"column":10},"end":{"line":71,"column":null}},"type":"if","locations":[{"start":{"line":68,"column":10},"end":{"line":71,"column":null}},{"start":{},"end":{}}],"line":68},"14":{"loc":{"start":{"line":70,"column":12},"end":{"line":70,"column":null}},"type":"if","locations":[{"start":{"line":70,"column":12},"end":{"line":70,"column":null}},{"start":{},"end":{}}],"line":70},"15":{"loc":{"start":{"line":72,"column":10},"end":{"line":75,"column":null}},"type":"if","locations":[{"start":{"line":72,"column":10},"end":{"line":75,"column":null}},{"start":{},"end":{}}],"line":72},"16":{"loc":{"start":{"line":74,"column":12},"end":{"line":74,"column":null}},"type":"if","locations":[{"start":{"line":74,"column":12},"end":{"line":74,"column":null}},{"start":{},"end":{}}],"line":74},"17":{"loc":{"start":{"line":83,"column":6},"end":{"line":85,"column":null}},"type":"if","locations":[{"start":{"line":83,"column":6},"end":{"line":85,"column":null}},{"start":{},"end":{}}],"line":83},"18":{"loc":{"start":{"line":83,"column":10},"end":{"line":83,"column":78}},"type":"binary-expr","locations":[{"start":{"line":83,"column":10},"end":{"line":83,"column":45}},{"start":{"line":83,"column":45},"end":{"line":83,"column":78}}],"line":83},"19":{"loc":{"start":{"line":88,"column":4},"end":{"line":112,"column":null}},"type":"if","locations":[{"start":{"line":88,"column":4},"end":{"line":112,"column":null}},{"start":{},"end":{}}],"line":88},"20":{"loc":{"start":{"line":90,"column":8},"end":{"line":90,"column":null}},"type":"if","locations":[{"start":{"line":90,"column":8},"end":{"line":90,"column":null}},{"start":{},"end":{}}],"line":90},"21":{"loc":{"start":{"line":92,"column":8},"end":{"line":110,"column":null}},"type":"if","locations":[{"start":{"line":92,"column":8},"end":{"line":110,"column":null}},{"start":{},"end":{}}],"line":92},"22":{"loc":{"start":{"line":96,"column":10},"end":{"line":98,"column":null}},"type":"if","locations":[{"start":{"line":96,"column":10},"end":{"line":98,"column":null}},{"start":{},"end":{}}],"line":96},"23":{"loc":{"start":{"line":96,"column":14},"end":{"line":96,"column":50}},"type":"binary-expr","locations":[{"start":{"line":96,"column":14},"end":{"line":96,"column":32}},{"start":{"line":96,"column":32},"end":{"line":96,"column":50}}],"line":96},"24":{"loc":{"start":{"line":101,"column":10},"end":{"line":109,"column":null}},"type":"if","locations":[{"start":{"line":101,"column":10},"end":{"line":109,"column":null}},{"start":{},"end":{}}],"line":101},"25":{"loc":{"start":{"line":104,"column":21},"end":{"line":104,"column":null}},"type":"binary-expr","locations":[{"start":{"line":104,"column":21},"end":{"line":104,"column":45}},{"start":{"line":104,"column":45},"end":{"line":104,"column":null}}],"line":104},"26":{"loc":{"start":{"line":105,"column":20},"end":{"line":105,"column":null}},"type":"binary-expr","locations":[{"start":{"line":105,"column":20},"end":{"line":105,"column":43}},{"start":{"line":105,"column":43},"end":{"line":105,"column":null}}],"line":105},"27":{"loc":{"start":{"line":114,"column":4},"end":{"line":120,"column":null}},"type":"if","locations":[{"start":{"line":114,"column":4},"end":{"line":120,"column":null}},{"start":{},"end":{}}],"line":114},"28":{"loc":{"start":{"line":118,"column":6},"end":{"line":118,"column":null}},"type":"if","locations":[{"start":{"line":118,"column":6},"end":{"line":118,"column":null}},{"start":{},"end":{}}],"line":118},"29":{"loc":{"start":{"line":119,"column":6},"end":{"line":119,"column":null}},"type":"if","locations":[{"start":{"line":119,"column":6},"end":{"line":119,"column":null}},{"start":{},"end":{}}],"line":119},"30":{"loc":{"start":{"line":123,"column":4},"end":{"line":126,"column":null}},"type":"if","locations":[{"start":{"line":123,"column":4},"end":{"line":126,"column":null}},{"start":{},"end":{}}],"line":123},"31":{"loc":{"start":{"line":140,"column":6},"end":{"line":144,"column":null}},"type":"if","locations":[{"start":{"line":140,"column":6},"end":{"line":144,"column":null}},{"start":{"line":142,"column":6},"end":{"line":144,"column":null}}],"line":140},"32":{"loc":{"start":{"line":140,"column":10},"end":{"line":140,"column":71}},"type":"binary-expr","locations":[{"start":{"line":140,"column":10},"end":{"line":140,"column":44}},{"start":{"line":140,"column":44},"end":{"line":140,"column":71}}],"line":140},"33":{"loc":{"start":{"line":142,"column":6},"end":{"line":144,"column":null}},"type":"if","locations":[{"start":{"line":142,"column":6},"end":{"line":144,"column":null}},{"start":{},"end":{}}],"line":142},"34":{"loc":{"start":{"line":146,"column":6},"end":{"line":150,"column":null}},"type":"if","locations":[{"start":{"line":146,"column":6},"end":{"line":150,"column":null}},{"start":{"line":148,"column":6},"end":{"line":150,"column":null}}],"line":146},"35":{"loc":{"start":{"line":146,"column":10},"end":{"line":146,"column":75}},"type":"binary-expr","locations":[{"start":{"line":146,"column":10},"end":{"line":146,"column":46}},{"start":{"line":146,"column":46},"end":{"line":146,"column":75}}],"line":146},"36":{"loc":{"start":{"line":148,"column":6},"end":{"line":150,"column":null}},"type":"if","locations":[{"start":{"line":148,"column":6},"end":{"line":150,"column":null}},{"start":{},"end":{}}],"line":148}},"s":{"0":7,"1":7,"2":3,"3":4,"4":4,"5":4,"6":4,"7":2,"8":2,"9":2,"10":2,"11":2,"12":2,"13":0,"14":2,"15":1,"16":1,"17":0,"18":1,"19":1,"20":1,"21":1,"22":1,"23":0,"24":0,"25":0,"26":0,"27":0,"28":0,"29":0,"30":0,"31":0,"32":0,"33":0,"34":0,"35":0,"36":0,"37":0,"38":0,"39":0,"40":0,"41":0,"42":0,"43":0,"44":0,"45":0,"46":0,"47":0,"48":0,"49":0,"50":0,"51":0,"52":0,"53":0,"54":0,"55":0,"56":0,"57":0,"58":0,"59":0,"60":0,"61":0,"62":0,"63":0,"64":0,"65":0,"66":0,"67":0,"68":0,"69":0,"70":0,"71":0,"72":0,"73":0,"74":0,"75":0,"76":0,"77":0,"78":0,"79":0,"80":0,"81":0,"82":0,"83":0,"84":0,"85":0,"86":0,"87":0,"88":0,"89":0,"90":0,"91":0,"92":0,"93":0,"94":0,"95":0,"96":0,"97":0,"98":1,"99":1,"100":1,"101":1,"102":0,"103":0,"104":0,"105":1},"f":{"0":7,"1":1,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0,"10":1,"11":0},"b":{"0":[3,4],"1":[7,6],"2":[2,2],"3":[4,2],"4":[0,2],"5":[1,1],"6":[2,1,1],"7":[0,0],"8":[0,0],"9":[0,0],"10":[0,0],"11":[0,0],"12":[0,0],"13":[0,0],"14":[0,0],"15":[0,0],"16":[0,0],"17":[0,0],"18":[0,0],"19":[0,0],"20":[0,0],"21":[0,0],"22":[0,0],"23":[0,0],"24":[0,0],"25":[0,0],"26":[0,0],"27":[0,0],"28":[0,0],"29":[0,0],"30":[0,0],"31":[0,0],"32":[0,0],"33":[0,0],"34":[0,0],"35":[0,0],"36":[0,0]},"meta":{"lastBranch":37,"lastFunction":12,"lastStatement":106,"seen":{"f:19:16:19:32":0,"s:20:2:37:Infinity":0,"b:21:4:21:Infinity:undefined:undefined:undefined:undefined":0,"s:21:4:21:Infinity":1,"b:21:8:21:20:21:20:21:44":1,"s:21:44:21:Infinity":2,"s:22:33:22:Infinity":3,"s:23:31:23:Infinity":4,"s:24:29:24:Infinity":5,"b:26:4:26:Infinity:undefined:undefined:undefined:undefined":2,"s:26:4:26:Infinity":6,"b:26:8:26:17:26:17:26:25":3,"s:26:25:26:Infinity":7,"s:28:14:28:Infinity":8,"s:29:14:29:Infinity":9,"s:30:17:30:Infinity":10,"s:31:14:31:Infinity":11,"b:32:4:32:Infinity:undefined:undefined:undefined:undefined":4,"s:32:4:32:Infinity":12,"s:32:28:32:Infinity":13,"b:33:4:33:Infinity:undefined:undefined:undefined:undefined":5,"s:33:4:33:Infinity":14,"b:33:8:33:33:33:33:33:55:33:55:33:77":6,"s:33:77:33:Infinity":15,"s:34:4:34:Infinity":16,"s:36:4:36:Infinity":17,"f:40:15:40:26":1,"s:41:14:41:Infinity":18,"s:42:20:42:Infinity":19,"s:44:2:160:Infinity":20,"s:45:18:45:Infinity":21,"s:46:21:51:Infinity":22,"s:53:14:53:Infinity":23,"s:55:24:55:Infinity":24,"s:56:24:56:Infinity":25,"s:57:37:57:Infinity":26,"s:58:39:58:Infinity":27,"s:60:4:78:Infinity":28,"f:60:20:60:21":2,"s:61:19:61:Infinity":29,"b:62:6:77:Infinity:undefined:undefined:undefined:undefined":7,"s:62:6:77:Infinity":30,"b:62:10:62:47:62:47:62:72":8,"s:63:8:76:Infinity":31,"f:63:31:63:32":3,"s:64:24:64:Infinity":32,"s:65:25:65:Infinity":33,"b:66:10:66:Infinity:undefined:undefined:undefined:undefined":9,"s:66:10:66:Infinity":34,"s:66:40:66:Infinity":35,"b:66:56:66:78:66:78:66:Infinity":10,"b:67:10:67:Infinity:undefined:undefined:undefined:undefined":11,"s:67:10:67:Infinity":36,"s:67:39:67:Infinity":37,"b:67:55:67:77:67:77:67:Infinity":12,"b:68:10:71:Infinity:undefined:undefined:undefined:undefined":13,"s:68:10:71:Infinity":38,"s:69:22:69:Infinity":39,"b:70:12:70:Infinity:undefined:undefined:undefined:undefined":14,"s:70:12:70:Infinity":40,"s:70:27:70:Infinity":41,"b:72:10:75:Infinity:undefined:undefined:undefined:undefined":15,"s:72:10:75:Infinity":42,"s:73:22:73:Infinity":43,"b:74:12:74:Infinity:undefined:undefined:undefined:undefined":16,"s:74:12:74:Infinity":44,"s:74:27:74:Infinity":45,"s:80:34:80:Infinity":46,"s:81:20:81:Infinity":47,"s:82:4:86:Infinity":48,"f:82:20:82:21":4,"b:83:6:85:Infinity:undefined:undefined:undefined:undefined":17,"s:83:6:85:Infinity":49,"b:83:10:83:45:83:45:83:78":18,"s:84:8:84:Infinity":50,"b:88:4:112:Infinity:undefined:undefined:undefined:undefined":19,"s:88:4:112:Infinity":51,"s:89:6:111:Infinity":52,"f:89:32:89:33":5,"b:90:8:90:Infinity:undefined:undefined:undefined:undefined":20,"s:90:8:90:Infinity":53,"s:90:21:90:Infinity":54,"s:91:21:91:Infinity":55,"b:92:8:110:Infinity:undefined:undefined:undefined:undefined":21,"s:92:8:110:Infinity":56,"s:93:26:93:Infinity":57,"s:94:27:94:Infinity":58,"s:95:24:95:Infinity":59,"b:96:10:98:Infinity:undefined:undefined:undefined:undefined":22,"s:96:10:98:Infinity":60,"b:96:14:96:32:96:32:96:50":23,"s:97:12:97:Infinity":61,"s:100:32:100:Infinity":62,"b:101:10:109:Infinity:undefined:undefined:undefined:undefined":24,"s:101:10:109:Infinity":63,"s:102:12:108:Infinity":64,"b:104:21:104:45:104:45:104:Infinity":25,"b:105:20:105:43:105:43:105:Infinity":26,"b:114:4:120:Infinity:undefined:undefined:undefined:undefined":27,"s:114:4:120:Infinity":65,"s:116:6:116:Infinity":66,"s:117:6:117:Infinity":67,"b:118:6:118:Infinity:undefined:undefined:undefined:undefined":28,"s:118:6:118:Infinity":68,"s:118:32:118:Infinity":69,"b:119:6:119:Infinity:undefined:undefined:undefined:undefined":29,"s:119:6:119:Infinity":70,"s:119:34:119:Infinity":71,"s:122:37:122:Infinity":72,"b:123:4:126:Infinity:undefined:undefined:undefined:undefined":30,"s:123:4:126:Infinity":73,"s:124:26:124:Infinity":74,"s:125:6:125:Infinity":75,"s:128:20:128:Infinity":76,"s:129:4:129:Infinity":77,"f:129:25:129:33":6,"s:129:33:129:66":78,"s:130:4:130:Infinity":79,"f:130:20:130:28":7,"s:130:28:130:61":80,"s:132:23:134:Infinity":81,"f:132:57:132:58":8,"s:133:6:133:Infinity":82,"s:137:39:137:Infinity":83,"s:138:41:138:Infinity":84,"s:139:4:151:Infinity":85,"f:139:23:139:31":9,"b:140:6:144:Infinity:142:6:144:Infinity":31,"s:140:6:144:Infinity":86,"b:140:10:140:44:140:44:140:71":32,"s:141:8:141:Infinity":87,"b:142:6:144:Infinity:undefined:undefined:undefined:undefined":33,"s:142:6:144:Infinity":88,"s:143:8:143:Infinity":89,"b:146:6:150:Infinity:148:6:150:Infinity":34,"s:146:6:150:Infinity":90,"b:146:10:146:46:146:46:146:75":35,"s:147:8:147:Infinity":91,"b:148:6:150:Infinity:undefined:undefined:undefined:undefined":36,"s:148:6:150:Infinity":92,"s:149:8:149:Infinity":93,"s:153:4:153:Infinity":94,"s:154:4:154:Infinity":95,"s:156:4:156:Infinity":96,"s:159:4:159:Infinity":97,"f:163:15:163:28":10,"s:164:2:164:Infinity":98,"s:166:2:172:Infinity":99,"s:168:30:168:Infinity":100,"s:169:4:169:Infinity":101,"s:171:4:171:Infinity":102,"f:171:22:171:33":11,"s:171:33:171:57":103,"s:174:2:174:Infinity":104,"s:177:0:177:Infinity":105}}} +,"/Users/davis/WebstormProjects/davisfe.cz/src/translations.ts": {"path":"/Users/davis/WebstormProjects/davisfe.cz/src/translations.ts","statementMap":{"0":{"start":{"line":3,"column":17},"end":{"line":100,"column":null}}},"fnMap":{},"branchMap":{},"s":{"0":2},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":1,"seen":{"s:3:17:100:Infinity":0}}} +,"/Users/davis/WebstormProjects/davisfe.cz/src/components/KpiCards.tsx": {"path":"/Users/davis/WebstormProjects/davisfe.cz/src/components/KpiCards.tsx","statementMap":{"0":{"start":{"line":21,"column":17},"end":{"line":125,"column":null}},"1":{"start":{"line":22,"column":36},"end":{"line":22,"column":null}},"2":{"start":{"line":23,"column":15},"end":{"line":23,"column":null}},"3":{"start":{"line":24,"column":19},"end":{"line":24,"column":null}},"4":{"start":{"line":26,"column":2},"end":{"line":33,"column":null}},"5":{"start":{"line":27,"column":4},"end":{"line":32,"column":null}},"6":{"start":{"line":28,"column":20},"end":{"line":30,"column":null}},"7":{"start":{"line":29,"column":8},"end":{"line":29,"column":null}},"8":{"start":{"line":31,"column":6},"end":{"line":31,"column":null}},"9":{"start":{"line":31,"column":19},"end":{"line":31,"column":null}},"10":{"start":{"line":35,"column":2},"end":{"line":123,"column":null}},"11":{"start":{"line":86,"column":27},"end":{"line":86,"column":null}},"12":{"start":{"line":93,"column":29},"end":{"line":93,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":21,"column":17},"end":{"line":21,"column":18}},"loc":{"start":{"line":21,"column":70},"end":{"line":125,"column":null}},"line":21},"1":{"name":"(anonymous_1)","decl":{"start":{"line":26,"column":12},"end":{"line":26,"column":18}},"loc":{"start":{"line":26,"column":18},"end":{"line":33,"column":5}},"line":26},"2":{"name":"(anonymous_2)","decl":{"start":{"line":28,"column":31},"end":{"line":28,"column":37}},"loc":{"start":{"line":28,"column":37},"end":{"line":30,"column":9}},"line":28},"3":{"name":"(anonymous_3)","decl":{"start":{"line":31,"column":13},"end":{"line":31,"column":19}},"loc":{"start":{"line":31,"column":19},"end":{"line":31,"column":null}},"line":31},"4":{"name":"(anonymous_4)","decl":{"start":{"line":86,"column":21},"end":{"line":86,"column":27}},"loc":{"start":{"line":86,"column":27},"end":{"line":86,"column":null}},"line":86},"5":{"name":"(anonymous_5)","decl":{"start":{"line":93,"column":23},"end":{"line":93,"column":29}},"loc":{"start":{"line":93,"column":29},"end":{"line":93,"column":null}},"line":93}},"branchMap":{"0":{"loc":{"start":{"line":21,"column":36},"end":{"line":21,"column":57}},"type":"default-arg","locations":[{"start":{"line":21,"column":47},"end":{"line":21,"column":57}}],"line":21},"1":{"loc":{"start":{"line":27,"column":4},"end":{"line":32,"column":null}},"type":"if","locations":[{"start":{"line":27,"column":4},"end":{"line":32,"column":null}},{"start":{},"end":{}}],"line":27},"2":{"loc":{"start":{"line":67,"column":15},"end":{"line":67,"column":null}},"type":"cond-expr","locations":[{"start":{"line":67,"column":30},"end":{"line":67,"column":73}},{"start":{"line":67,"column":73},"end":{"line":67,"column":null}}],"line":67},"3":{"loc":{"start":{"line":67,"column":73},"end":{"line":67,"column":null}},"type":"cond-expr","locations":[{"start":{"line":67,"column":88},"end":{"line":67,"column":131}},{"start":{"line":67,"column":131},"end":{"line":67,"column":null}}],"line":67},"4":{"loc":{"start":{"line":91,"column":11},"end":{"line":113,"column":null}},"type":"binary-expr","locations":[{"start":{"line":91,"column":11},"end":{"line":91,"column":null}},{"start":{"line":92,"column":12},"end":{"line":113,"column":null}}],"line":91},"5":{"loc":{"start":{"line":112,"column":15},"end":{"line":112,"column":null}},"type":"cond-expr","locations":[{"start":{"line":112,"column":35},"end":{"line":112,"column":135}},{"start":{"line":112,"column":135},"end":{"line":112,"column":null}}],"line":112},"6":{"loc":{"start":{"line":116,"column":106},"end":{"line":116,"column":190}},"type":"cond-expr","locations":[{"start":{"line":116,"column":149},"end":{"line":116,"column":170}},{"start":{"line":116,"column":170},"end":{"line":116,"column":190}}],"line":116},"7":{"loc":{"start":{"line":116,"column":106},"end":{"line":116,"column":149}},"type":"binary-expr","locations":[{"start":{"line":116,"column":106},"end":{"line":116,"column":126}},{"start":{"line":116,"column":126},"end":{"line":116,"column":149}}],"line":116},"8":{"loc":{"start":{"line":117,"column":11},"end":{"line":117,"column":null}},"type":"cond-expr","locations":[{"start":{"line":117,"column":71},"end":{"line":117,"column":171}},{"start":{"line":117,"column":171},"end":{"line":117,"column":null}}],"line":117},"9":{"loc":{"start":{"line":117,"column":11},"end":{"line":117,"column":71}},"type":"binary-expr","locations":[{"start":{"line":117,"column":11},"end":{"line":117,"column":45}},{"start":{"line":117,"column":45},"end":{"line":117,"column":71}}],"line":117},"10":{"loc":{"start":{"line":117,"column":71},"end":{"line":117,"column":171}},"type":"cond-expr","locations":[{"start":{"line":117,"column":94},"end":{"line":117,"column":132}},{"start":{"line":117,"column":132},"end":{"line":117,"column":171}}],"line":117},"11":{"loc":{"start":{"line":117,"column":171},"end":{"line":117,"column":null}},"type":"cond-expr","locations":[{"start":{"line":117,"column":191},"end":{"line":117,"column":224}},{"start":{"line":117,"column":224},"end":{"line":117,"column":null}}],"line":117}},"s":{"0":1,"1":3,"2":3,"3":3,"4":3,"5":3,"6":0,"7":0,"8":0,"9":0,"10":3,"11":0,"12":0},"f":{"0":3,"1":3,"2":0,"3":0,"4":0,"5":0},"b":{"0":[3],"1":[0,3],"2":[3,0],"3":[0,0],"4":[3,0],"5":[0,0],"6":[1,2],"7":[3,2],"8":[2,1],"9":[3,3],"10":[1,1],"11":[1,0]},"meta":{"lastBranch":12,"lastFunction":6,"lastStatement":13,"seen":{"s:21:17:125:Infinity":0,"f:21:17:21:18":0,"b:21:47:21:57":0,"s:22:36:22:Infinity":1,"s:23:15:23:Infinity":2,"s:24:19:24:Infinity":3,"s:26:2:33:Infinity":4,"f:26:12:26:18":1,"b:27:4:32:Infinity:undefined:undefined:undefined:undefined":1,"s:27:4:32:Infinity":5,"s:28:20:30:Infinity":6,"f:28:31:28:37":2,"s:29:8:29:Infinity":7,"s:31:6:31:Infinity":8,"f:31:13:31:19":3,"s:31:19:31:Infinity":9,"s:35:2:123:Infinity":10,"b:67:30:67:73:67:73:67:Infinity":2,"b:67:88:67:131:67:131:67:Infinity":3,"f:86:21:86:27":4,"s:86:27:86:Infinity":11,"b:91:11:91:Infinity:92:12:113:Infinity":4,"f:93:23:93:29":5,"s:93:29:93:Infinity":12,"b:112:35:112:135:112:135:112:Infinity":5,"b:116:149:116:170:116:170:116:190":6,"b:116:106:116:126:116:126:116:149":7,"b:117:71:117:171:117:171:117:Infinity":8,"b:117:11:117:45:117:45:117:71":9,"b:117:94:117:132:117:132:117:171":10,"b:117:191:117:224:117:224:117:Infinity":11}}} +} diff --git a/coverage/favicon.png b/coverage/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c1525b811a167671e9de1fa78aab9f5c0b61cef7 GIT binary patch literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> + + + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ 28.92% + Statements + 35/121 +
+ + +
+ 29.59% + Branches + 29/98 +
+ + +
+ 27.77% + Functions + 5/18 +
+ + +
+ 30.18% + Lines + 32/106 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
scripts +
+
25.23%27/10718.66%14/7525%3/1225.8%24/93
src +
+
100%1/1100%0/0100%0/0100%1/1
src/components +
+
53.84%7/1365.21%15/2333.33%2/658.33%7/12
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/prettify.css b/coverage/prettify.css new file mode 100644 index 0000000..b317a7c --- /dev/null +++ b/coverage/prettify.css @@ -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} diff --git a/coverage/prettify.js b/coverage/prettify.js new file mode 100644 index 0000000..b322523 --- /dev/null +++ b/coverage/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/coverage/scripts/index.html b/coverage/scripts/index.html new file mode 100644 index 0000000..9630c47 --- /dev/null +++ b/coverage/scripts/index.html @@ -0,0 +1,131 @@ + + + + + + Code coverage report for scripts + + + + + + + + + +
+
+

All files scripts

+
+ +
+ 25.23% + Statements + 27/107 +
+ + +
+ 18.66% + Branches + 14/75 +
+ + +
+ 25% + Functions + 3/12 +
+ + +
+ 25.8% + Lines + 24/93 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
lakesConfig.ts +
+
100%1/1100%0/0100%0/0100%1/1
scrapeLakes.ts +
+
24.52%26/10618.66%14/7525%3/1225%23/92
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/scripts/lakesConfig.ts.html b/coverage/scripts/lakesConfig.ts.html new file mode 100644 index 0000000..7aa48e0 --- /dev/null +++ b/coverage/scripts/lakesConfig.ts.html @@ -0,0 +1,160 @@ + + + + + + Code coverage report for scripts/lakesConfig.ts + + + + + + + + + +
+
+

All files / scripts lakesConfig.ts

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 0/0 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  + 
export interface LakeConfig {
+  id: string;
+  text: string;
+  priority?: boolean;
+  coords: [number, number];
+  maxVolume?: number;
+  minLevel?: number;
+  maxLevel?: number;
+  storageLevel?: number;
+}
+ 
+export const lakesConfig: LakeConfig[] = [
+  { id: "VLL1|1", text: "VD Lipno 1 - Vltava", priority: true, coords: [48.6322, 14.2215], maxVolume: 306.0, minLevel: 715.00, maxLevel: 725.60, storageLevel: 724.9 },
+  { id: "VLL2|1", text: "VD Lipno II - Vltava", coords: [48.6250, 14.3180], maxVolume: 1.5, minLevel: 510.0, maxLevel: 511.5, storageLevel: 511.5 },
+  { id: "VLHN|1", text: "VD Hněvkovice - Vltava", priority: true, coords: [49.1830, 14.4440], maxVolume: 21.1, minLevel: 365.0, maxLevel: 370.5, storageLevel: 370.1 },
+  { id: "VLKO|1", text: "VD Kořensko - Vltava", coords: [49.2550, 14.3980], maxVolume: 2.8, minLevel: 352.0, maxLevel: 353.5, storageLevel: 352.6 },
+  { id: "VLOR|2", text: "VD Orlík - Vltava", priority: true, coords: [49.6060, 14.1700], maxVolume: 716.5, minLevel: 330.00, maxLevel: 354.00, storageLevel: 349.9 },
+  { id: "UHKA|2", text: "VD Kamýk - Vltava", coords: [49.6360, 14.2540], maxVolume: 12.8, minLevel: 283.0, maxLevel: 285.6, storageLevel: 285.6 },
+  { id: "VLSL|2", text: "VD Slapy - Vltava", priority: true, coords: [49.8220, 14.4360], maxVolume: 269.3, minLevel: 265.50, maxLevel: 271.10, storageLevel: 270.6 },
+  { id: "VLST|2", text: "VD Štěchovice - Vltava", coords: [49.8450, 14.4120], maxVolume: 11.2, minLevel: 217.0, maxLevel: 219.5, storageLevel: 219.4 },
+  { id: "VRSN|2", text: "VD Vrané - Vltava", coords: [49.9340, 14.3850], maxVolume: 11.1, minLevel: 198.5, maxLevel: 200.5, storageLevel: 200.5 },
+  { id: "SVKR|2", text: "VD Švihov - Želivka", priority: true, coords: [49.7180, 15.1060], maxVolume: 266.6, minLevel: 343.0, maxLevel: 377.0, storageLevel: 377.0 },
+  { id: "MARI|1", text: "VD Římov - Malše", priority: true, coords: [48.8470, 14.4870], maxVolume: 33.8, minLevel: 458.0, maxLevel: 471.0, storageLevel: 470.65 },
+  { id: "MZHR|3", text: "VD Hracholusky - Mže", priority: true, coords: [49.7890, 13.1550], maxVolume: 56.7, minLevel: 360.0, maxLevel: 373.0, storageLevel: 354.1 }
+];
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/scripts/scrapeLakes.ts.html b/coverage/scripts/scrapeLakes.ts.html new file mode 100644 index 0000000..73d8fe7 --- /dev/null +++ b/coverage/scripts/scrapeLakes.ts.html @@ -0,0 +1,616 @@ + + + + + + Code coverage report for scripts/scrapeLakes.ts + + + + + + + + + +
+
+

All files / scripts scrapeLakes.ts

+
+ +
+ 24.52% + Statements + 26/106 +
+ + +
+ 18.66% + Branches + 14/75 +
+ + +
+ 25% + Functions + 3/12 +
+ + +
+ 25% + Lines + 23/92 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +7x +7x +4x +4x +4x +  +4x +  +2x +2x +2x +2x +2x +2x +1x +  +  +  +  +  +  +1x +1x +  +1x +1x +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +1x +  +1x +1x +  +  +  +  +  +  +  +1x + 
import * as fs from 'fs';
+import * as path from 'path';
+import * as cheerio from 'cheerio';
+import axios from 'axios';
+import https from 'https';
+import { lakesConfig } from './lakesConfig';
+ 
+interface DataRecord {
+  timestamp: string;
+  level: number;
+  flow: number;
+  inflow?: number;
+  volume?: number;
+  temperature?: number | null;
+  precipitation?: number | null;
+}
+ 
+// Parse date from DD.MM.YYYY HH:MM to ISO
+export function parseDateString(dateStr: string): string | null {
+  try {
+    if (!dateStr || !dateStr.includes(' ')) return null;
+    const [datePart, timePart] = dateStr.trim().split(' ');
+    const [day, month, year] = datePart.split('.');
+    const [hours, minutes] = timePart.split(':');
+    
+    if (!year || !hours) return null;
+    
+    const y = parseInt(year);
+    const m = parseInt(month) - 1;
+    const dDay = parseInt(day);
+    const d = new Date(y, m, dDay, parseInt(hours), parseInt(minutes));
+    Iif (isNaN(d.getTime())) return null;
+    if (d.getFullYear() !== y || d.getMonth() !== m || d.getDate() !== dDay) return null;
+    return d.toISOString();
+  } catch (e) {
+    return null;
+  }
+}
+ 
+async function scrapeLake(lakeId: string, oid: string, internalId: string) {
+  const URL = `https://www.pvl.cz/portal/nadrze/cz/pc/Mereni.aspx?oid=${oid}&id=${internalId}`;
+  const DATA_FILE = path.resolve(`public/data/${internalId}.json`);
+ 
+  try {
+    const agent = new https.Agent({ rejectUnauthorized: false });
+    const response = await axios.get(URL, {
+      httpsAgent: agent,
+      headers: {
+        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
+      }
+    });
+ 
+    const $ = cheerio.load(response.data);
+      
+    let currentInflow = 0;
+    let currentVolume = 0;
+    let currentTemp: number | null = null;
+    let currentPrecip: number | null = null;
+    
+    $('table').each((i, tbl) => {
+      const text = $(tbl).text();
+      if (text.includes('Aktuální hodnoty') && text.includes('Přítok')) {
+        $(tbl).find('tr').each((j, r) => {
+          const label = $(r).find('td').eq(0).text().trim();
+          const valStr = $(r).find('td').eq(1).text().trim().replace(/\s/g, '').replace(',', '.');
+          if (label.includes('Přítok')) currentInflow = parseFloat(valStr) || 0;
+          if (label.includes('Objem')) currentVolume = parseFloat(valStr) || 0;
+          if (label.includes('Teplota')) {
+            const v = parseFloat(valStr);
+            if (!isNaN(v)) currentTemp = v;
+          }
+          if (label.includes('Srážky')) {
+            const v = parseFloat(valStr);
+            if (!isNaN(v)) currentPrecip = v;
+          }
+        });
+      }
+    });
+ 
+    const records: DataRecord[] = [];
+    let dataTable = null;
+    $('table').each((i, tbl) => {
+      if ($(tbl).text().includes('Datum') && $(tbl).text().includes('Odtok')) {
+        dataTable = $(tbl);
+      }
+    });
+ 
+    if (dataTable) {
+      dataTable.find('tr').each((i, row) => {
+        if (i === 0) return; // skip header
+        const cols = $(row).find('td');
+        if (cols.length >= 3) {
+          const rawDate = $(cols[0]).text().trim(); 
+          const levelStr = $(cols[1]).text().trim().replace(',', '.');
+          let flowStr = $(cols[2]).text().trim().replace(',', '.');
+          if (flowStr === '' && cols.length >= 4) {
+            flowStr = $(cols[3]).text().trim().replace(',', '.');
+          }
+ 
+          const parsedDateStr = parseDateString(rawDate);
+          if (parsedDateStr) {
+            records.push({
+              timestamp: parsedDateStr,
+              level: parseFloat(levelStr) || 0,
+              flow: parseFloat(flowStr) || 0,
+              inflow: 0,
+              volume: 0
+            });
+          }
+        }
+      });
+    }
+ 
+    if (records.length > 0) {
+      // Apply current values to the latest record
+      records[0].inflow = currentInflow;
+      records[0].volume = currentVolume;
+      if (currentTemp !== null) records[0].temperature = currentTemp;
+      if (currentPrecip !== null) records[0].precipitation = currentPrecip;
+    }
+ 
+    let existingData: DataRecord[] = [];
+    if (fs.existsSync(DATA_FILE)) {
+      const fileContent = fs.readFileSync(DATA_FILE, 'utf-8');
+      existingData = JSON.parse(fileContent);
+    }
+ 
+    const dataMap = new Map<string, DataRecord>();
+    existingData.forEach(item => dataMap.set(item.timestamp, item));
+    records.forEach(item => dataMap.set(item.timestamp, item));
+ 
+    const mergedData = Array.from(dataMap.values()).sort((a, b) => {
+      return new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime();
+    });
+ 
+    // Propagate previous values if missing (user requested)
+    let lastKnownTemp: number | null = null;
+    let lastKnownPrecip: number | null = null;
+    mergedData.forEach(item => {
+      if (item.temperature !== undefined && item.temperature !== null) {
+        lastKnownTemp = item.temperature;
+      } else if (lastKnownTemp !== null) {
+        item.temperature = lastKnownTemp;
+      }
+ 
+      if (item.precipitation !== undefined && item.precipitation !== null) {
+        lastKnownPrecip = item.precipitation;
+      } else if (lastKnownPrecip !== null) {
+        item.precipitation = lastKnownPrecip;
+      }
+    });
+ 
+    fs.mkdirSync(path.dirname(DATA_FILE), { recursive: true });
+    fs.writeFileSync(DATA_FILE, JSON.stringify(mergedData, null, 2), 'utf-8');
+    
+    console.log(`[${internalId}] Scraped ${records.length} records. DB total: ${mergedData.length}`);
+ 
+  } catch (error: any) {
+    console.error(`[${internalId}] Error scraping data:`, error.message);
+  }
+}
+ 
+async function runScraper() {
+  console.log(`Starting bulk scraper for ${lakesConfig.length} lakes...`);
+  
+  for (const lake of lakesConfig) {
+    // ID format: VLL1|1 -> internalId=VLL1, oid=1
+    const [internalId, oid] = lake.id.split('|');
+    await scrapeLake(lake.id, oid, internalId);
+    // Add small delay to not hammer the server
+    await new Promise(resolve => setTimeout(resolve, 500));
+  }
+  
+  console.log('Bulk scraping finished.');
+}
+ 
+runScraper();
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/sort-arrow-sprite.png b/coverage/sort-arrow-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed68316eb3f65dec9063332d2f69bf3093bbfab GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc literal 0 HcmV?d00001 diff --git a/coverage/sorter.js b/coverage/sorter.js new file mode 100644 index 0000000..4ed70ae --- /dev/null +++ b/coverage/sorter.js @@ -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 + ''; + } + } + 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); diff --git a/coverage/src/components/KpiCards.tsx.html b/coverage/src/components/KpiCards.tsx.html new file mode 100644 index 0000000..4289925 --- /dev/null +++ b/coverage/src/components/KpiCards.tsx.html @@ -0,0 +1,466 @@ + + + + + + Code coverage report for src/components/KpiCards.tsx + + + + + + + + + +
+
+

All files / src/components KpiCards.tsx

+
+ +
+ 53.84% + Statements + 7/13 +
+ + +
+ 65.21% + Branches + 15/23 +
+ + +
+ 33.33% + Functions + 2/6 +
+ + +
+ 58.33% + Lines + 7/12 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +3x +3x +3x +  +3x +3x +  +  +  +  +  +  +  +3x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { FiArrowUp, FiArrowDown } from 'react-icons/fi';
+import { type Language, t } from '../translations';
+import { useState, useEffect } from 'react';
+ 
+interface KpiData {
+  level: number;
+  inflow: number;
+  outflow: number;
+  outflow: number;
+  volume: number;
+  fullness: number;
+  storageDiff?: number;
+}
+ 
+interface Props {
+  data: KpiData;
+  language: Language;
+  lakeName?: string;
+}
+ 
+const KpiCards = ({ data, language, lakeName = 'Lipno 1' }: Props) => {
+  const [showTooltip, setShowTooltip] = useState(false);
+  const dict = t[language].kpi;
+  const flowDiff = data.inflow - data.outflow;
+ 
+  useEffect(() => {
+    Iif (showTooltip) {
+      const timer = setTimeout(() => {
+        setShowTooltip(false);
+      }, 3500);
+      return () => clearTimeout(timer);
+    }
+  }, [showTooltip]);
+ 
+  return (
+    <div className="kpi-grid-container">
+      {/* CARD 1: HLADINA */}
+      <div className="kpi-card">
+        <div style={{ fontSize: '1rem', color: 'var(--text-muted)', marginBottom: '0.5rem' }}>
+          {dict.level} {lakeName}
+        </div>
+        <div style={{ fontSize: '2.5rem', fontWeight: 'bold', color: 'var(--color-cyan)', lineHeight: 1, marginBottom: '0.5rem' }}>
+          {data.level.toFixed(2)} <span style={{ fontSize: '1rem', color: 'var(--text-muted)', fontWeight: 'normal' }}>m n. m.</span>
+        </div>
+        <div style={{ fontSize: '0.85rem', color: 'var(--color-green)' }}>
+          (+0.02 m / 24h)
+        </div>
+        
+        {/* Decorative Circle for Level */}
+        <div style={{ position: 'absolute', right: '1.5rem', top: '1.5rem', width: '40px', height: '40px', borderRadius: '50%', border: '4px solid rgba(0, 195, 255, 0.2)', borderTopColor: 'var(--color-cyan)', transform: 'rotate(45deg)' }}></div>
+      </div>
+ 
+      {/* CARD 2: PRŮTOK */}
+      <div className="kpi-card">
+        <div style={{ fontSize: '1rem', color: 'var(--text-muted)', marginBottom: '0.5rem' }}>
+          {dict.flow}
+        </div>
+        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', height: '100%' }}>
+          <div style={{ display: 'flex', flexDirection: 'column', gap: '0.25rem', fontSize: '0.85rem' }}>
+            <div style={{ fontSize: '0.85rem', color: 'var(--text-muted)', display: 'flex', alignItems: 'center' }}>
+              <span style={{ display: 'inline-block', width: '8px', height: '8px', borderRadius: '50%', backgroundColor: '#8b5cf6', marginRight: '6px' }}></span>
+              {dict.inflow}: <span style={{ fontWeight: 'bold', color: 'var(--text-main)', marginLeft: '4px' }}>{data.inflow.toFixed(1)} m³/s</span>
+            </div>
+            <div style={{ fontSize: '0.85rem', color: 'var(--text-muted)', marginTop: '0.25rem', display: 'flex', alignItems: 'center', gap: '0.25rem' }}>
+              <span style={{ display: 'inline-block', width: '8px', height: '8px', borderRadius: '50%', backgroundColor: 'var(--color-orange)', marginRight: '2px' }}></span>
+              {dict.outflow}: <span style={{ fontWeight: 'bold', color: 'var(--text-main)' }}>{data.outflow.toFixed(1)} m³/s</span>
+              {flowDiff > 0 ? <FiArrowUp color="var(--color-green)" /> : flowDiff < 0 ? <FiArrowDown color="var(--color-red)" /> : null}
+            </div>
+          </div>
+          
+          {/* Flow Circle */}
+          <div style={{ width: '40px', height: '40px', borderRadius: '50%', border: '4px solid rgba(0, 195, 255, 0.2)', borderTopColor: 'var(--color-cyan)', borderRightColor: 'var(--color-cyan)', display: 'flex', alignItems: 'center', justifyContent: 'center', transform: 'rotate(-45deg)' }}>
+            <span style={{ fontSize: '0.65rem', transform: 'rotate(45deg)', color: 'var(--text-main)', fontWeight: 'bold' }}>
+              <div style={{ lineHeight: 1 }}>{data.outflow.toFixed(1)}</div>
+              <div style={{ fontSize: '0.45rem', opacity: 0.7 }}>m³/s</div>
+            </span>
+          </div>
+        </div>
+      </div>
+ 
+      {/* CARD 3: NAPLNĚNOST */}
+      <div className="kpi-card">
+        <div style={{ fontSize: '1rem', color: 'var(--text-muted)', marginBottom: '0.5rem', display: 'flex', alignItems: 'center', gap: '0.4rem', position: 'relative' }}>
+          {dict.fullness}
+          <span 
+            onClick={() => setShowTooltip(!showTooltip)}
+            style={{ cursor: 'pointer', fontSize: '0.85rem', opacity: 0.6, padding: '0 4px' }}
+          >
+            ⓘ
+          </span>
+          {showTooltip && (
+            <div 
+              onClick={() => setShowTooltip(false)}
+              style={{
+                position: 'absolute',
+                bottom: '100%',
+                left: '50%',
+                transform: 'translateX(-50%)',
+                marginBottom: '8px',
+                backgroundColor: 'var(--bg-card)',
+                border: '1px solid var(--border-color)',
+                padding: '0.75rem',
+                borderRadius: '8px',
+                width: '250px',
+                zIndex: 100,
+                boxShadow: '0 4px 12px rgba(0,0,0,0.5)',
+                color: 'var(--text-main)',
+                fontSize: '0.85rem',
+                lineHeight: 1.4,
+                cursor: 'pointer'
+              }}>
+              {language === 'cs' ? "Rozdíl mezi aktuální hladinou a hladinou zásobního prostoru (důležité pro jachtaře a rekreaci)." : "Difference between current water level and storage space level (important for sailing and recreation)."}
+            </div>
+          )}
+        </div>
+        <div style={{ fontSize: '2rem', fontWeight: 'bold', lineHeight: 1, marginBottom: '0.5rem', color: data.storageDiff && data.storageDiff < 0 ? 'var(--color-red)' : 'var(--color-cyan)' }}>
+          {data.storageDiff !== undefined && data.storageDiff !== 0 ? (data.storageDiff > 0 ? `+${data.storageDiff.toFixed(2)} m` : `${data.storageDiff.toFixed(2)} m`) : (data.fullness > 0 ? `${data.fullness.toFixed(1)}%` : 'N/A')}
+        </div>
+        <div style={{ fontSize: '0.85rem', color: 'var(--text-muted)' }}>
+          {dict.volume}: {data.volume.toFixed(1)} mil. m³
+        </div>
+      </div>
+    </div>
+  );
+};
+ 
+export default KpiCards;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/index.html b/coverage/src/components/index.html new file mode 100644 index 0000000..4186b73 --- /dev/null +++ b/coverage/src/components/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for src/components + + + + + + + + + +
+
+

All files src/components

+
+ +
+ 53.84% + Statements + 7/13 +
+ + +
+ 65.21% + Branches + 15/23 +
+ + +
+ 33.33% + Functions + 2/6 +
+ + +
+ 58.33% + Lines + 7/12 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
KpiCards.tsx +
+
53.84%7/1365.21%15/2333.33%2/658.33%7/12
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/index.html b/coverage/src/index.html new file mode 100644 index 0000000..06e41ba --- /dev/null +++ b/coverage/src/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for src + + + + + + + + + +
+
+

All files src

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 0/0 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
translations.ts +
+
100%1/1100%0/0100%0/0100%1/1
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/translations.ts.html b/coverage/src/translations.ts.html new file mode 100644 index 0000000..a0395ee --- /dev/null +++ b/coverage/src/translations.ts.html @@ -0,0 +1,385 @@ + + + + + + Code coverage report for src/translations.ts + + + + + + + + + +
+
+

All files / src translations.ts

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 0/0 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
export type Language = 'en' | 'cs';
+ 
+export const t = {
+  en: {
+    sidebar: {
+      favorites: 'Favorites',
+      lakes: 'Lakes',
+      map: 'Map',
+      settings: 'Settings'
+    },
+    topbar: {
+      search: 'Search river or reservoir (e.g. Lipno)...',
+      updated: 'Last updated:'
+    },
+    kpi: {
+      level: 'WATER LEVEL',
+      flow: 'FLOW RATE',
+      inflow: 'Inflow',
+      outflow: 'Outflow',
+      fullness: 'STORAGE LEVEL',
+      volume: 'Volume'
+    },
+    chart: {
+      title: 'Long-term development',
+      timeframe: 'Timeframe',
+      timeframeMobile: 'Time',
+      view: 'View',
+      raw: 'Raw data',
+      smoothed: 'Smoothed',
+      calendar: 'Calendar',
+      all: 'All',
+      year: 'Year',
+      level: 'Water level',
+      inflow: 'Inflow',
+      outflow: 'Outflow',
+      maxLevel: 'Max retention level',
+      storageLevel: 'Storage space level',
+      dataSources: 'Data sources:',
+      createdIn: 'Created with ♥ in the Czech Republic'
+    },
+    settings: {
+      title: 'Settings',
+      theme: 'Theme',
+      dark: 'Dark',
+      light: 'Light',
+      language: 'Language',
+      english: 'English',
+      czech: 'Čeština',
+      buyCoffee: 'Buy Me a Coffee'
+    }
+  },
+  cs: {
+    sidebar: {
+      favorites: 'Oblíbené',
+      lakes: 'Jezera',
+      map: 'Mapa',
+      settings: 'Nastavení'
+    },
+    topbar: {
+      search: 'Hledat tok nebo nádrž (např. Lipno)...',
+      updated: 'Aktualizováno:'
+    },
+    kpi: {
+      level: 'HLADINA',
+      flow: 'PRŮTOK',
+      inflow: 'Přítok',
+      outflow: 'Odtok',
+      fullness: 'ZÁSOBNÍ PROSTOR',
+      volume: 'Objem'
+    },
+    chart: {
+      title: 'Dlouhodobý vývoj',
+      timeframe: 'Časové období',
+      timeframeMobile: 'Časové',
+      view: 'Zobrazení',
+      raw: 'Syrová data',
+      smoothed: 'Vyhlazená',
+      calendar: 'Kalendář',
+      all: 'Vše',
+      year: 'Rok',
+      level: 'Hladina',
+      inflow: 'Přítok',
+      outflow: 'Odtok',
+      maxLevel: 'Max. retenční hladina',
+      storageLevel: 'Hladina zásobního prostoru',
+      dataSources: 'Zdroje dat:',
+      createdIn: 'Vytvořeno s ♥ v České republice'
+    },
+    settings: {
+      title: 'Nastavení',
+      theme: 'Vzhled',
+      dark: 'Tmavý',
+      light: 'Světlý',
+      language: 'Jazyk',
+      english: 'English',
+      czech: 'Čeština',
+      buyCoffee: 'Kup mi kávu'
+    }
+  }
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7a6cc55..b51e268 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,21 +21,85 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", "@types/leaflet": "^1.9.21", "@types/node": "^24.10.1", "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", + "@vitest/coverage-v8": "^4.1.8", "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "jsdom": "^29.1.1", "tsx": "^4.22.4", "typescript": "~5.9.3", "typescript-eslint": "^8.46.4", - "vite": "^7.2.4" + "vite": "^7.2.4", + "vitest": "^4.1.8" } }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -179,9 +243,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -189,9 +253,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -223,13 +287,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -270,6 +334,16 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -305,19 +379,182 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", + "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.1.tgz", + "integrity": "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.2.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.5.tgz", + "integrity": "sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -917,6 +1154,24 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1393,6 +1648,88 @@ "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", "license": "MIT" }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1438,6 +1775,17 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", @@ -1501,6 +1849,13 @@ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT" }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1859,6 +2214,150 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.8.tgz", + "integrity": "sha512-lt3kovsyHwYe00wq4D1ti0Z974fWj4NLp6siqiyEufUpyFwK9Yhi7rBhac9JL5aA0zoMrJqc4vYPZRUnI7l7nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.8", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.8", + "vitest": "4.1.8" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.8.tgz", + "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.8.tgz", + "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.8.tgz", + "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.8.tgz", + "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.8", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.8.tgz", + "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "@vitest/utils": "4.1.8", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.8.tgz", + "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.8.tgz", + "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -1911,6 +2410,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1934,6 +2443,45 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.3.tgz", + "integrity": "sha512-jCMQ6ZylLPudp0CDfBmQBZUsrh1/8psbmu9ibeVWKuHWD0YrH9YABwlKu5kVEFoT0GCQQW9Z/SxfuEbbkGQCRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1969,6 +2517,16 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -2064,6 +2622,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2222,6 +2790,20 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/css-what": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", @@ -2234,6 +2816,13 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -2362,6 +2951,30 @@ "node": ">=12" } }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/date-fns": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.4.0.tgz", @@ -2389,6 +3002,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/decimal.js-light": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", @@ -2411,6 +3031,23 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -2530,6 +3167,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", @@ -2806,6 +3450,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2822,6 +3476,16 @@ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3130,6 +3794,26 @@ "hermes-estree": "0.25.1" } }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/htmlparser2": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", @@ -3233,6 +3917,16 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/internmap": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", @@ -3265,6 +3959,13 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3272,6 +3973,45 @@ "dev": true, "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3292,6 +4032,93 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/jsdom/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -3402,6 +4229,67 @@ "yallist": "^3.0.2" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.2.tgz", + "integrity": "sha512-c8jsqUZm3omBOI66G90z1Dyw5z622G8oLG+omfsHBJf3CWQTlOcwOjvOG6wtiNfW6anKm/eA39LMwMtMez2TiQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -3411,6 +4299,13 @@ "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -3432,6 +4327,16 @@ "node": ">= 0.6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3496,6 +4401,20 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, + "node_modules/obug": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.2.tgz", + "integrity": "sha512-AWGB9WFcRXOQs48Z/udjI5ZcZMHXwX8XPByNpOydgcGsDLIzjGizhoMWJyKAWze7AVW/2W1i+/gPX4YtKe5cyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3628,6 +4547,13 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3687,6 +4613,41 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", @@ -3858,6 +4819,20 @@ "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", @@ -3873,6 +4848,16 @@ "redux": "^5.0.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/reselect": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", @@ -3937,6 +4922,19 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -3982,6 +4980,13 @@ "node": ">=8" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3992,6 +4997,33 @@ "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4018,12 +5050,36 @@ "node": ">=8" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -4041,6 +5097,62 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.2.tgz", + "integrity": "sha512-kCwffuaH8ntKtygnWe1b4BJKWiCUH30n5KfoTr6IchcXOwR7chAOFJxFrH3vjANafUYrIA4a7SDL+nn7SiR4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.2" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.2.tgz", + "integrity": "sha512-nwEyF4vl4RSJjwSjBUmOSxc3BFPoIFdlRthJ6e+5v9P3bHNsoD06UjuqMUspqp7vsEZ1beaHi1km+optiE17yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -4771,6 +5883,119 @@ } } }, + "node_modules/vitest": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.8.tgz", + "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.8", + "@vitest/mocker": "4.1.8", + "@vitest/pretty-format": "4.1.8", + "@vitest/runner": "4.1.8", + "@vitest/snapshot": "4.1.8", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.8", + "@vitest/browser-preview": "4.1.8", + "@vitest/browser-webdriverio": "4.1.8", + "@vitest/coverage-istanbul": "4.1.8", + "@vitest/coverage-v8": "4.1.8", + "@vitest/ui": "4.1.8", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", @@ -4793,6 +6018,21 @@ "node": ">=18" } }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4809,6 +6049,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -4819,6 +6076,23 @@ "node": ">=0.10.0" } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index 5492a0f..32c5c90 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,10 @@ "scrape": "tsx scripts/scrapeLakes.ts", "build-index": "tsx scripts/buildIndex.ts", "data:update": "npm run scrape && npm run build-index", - "data:watch": "tsx scripts/watchData.ts" + "data:watch": "tsx scripts/watchData.ts", + "test": "vitest run", + "test:watch": "vitest", + "coverage": "vitest run --coverage" }, "dependencies": { "axios": "^1.17.0", @@ -28,18 +31,24 @@ }, "devDependencies": { "@eslint/js": "^9.39.1", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", "@types/leaflet": "^1.9.21", "@types/node": "^24.10.1", "@types/react": "^19.2.5", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^5.1.1", + "@vitest/coverage-v8": "^4.1.8", "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", + "jsdom": "^29.1.1", "tsx": "^4.22.4", "typescript": "~5.9.3", "typescript-eslint": "^8.46.4", - "vite": "^7.2.4" + "vite": "^7.2.4", + "vitest": "^4.1.8" } } diff --git a/public/data/MARI.json b/public/data/MARI.json index 85b6d75..d08de29 100644 --- a/public/data/MARI.json +++ b/public/data/MARI.json @@ -6793,5 +6793,14 @@ "volume": 26.49, "temperature": 14.4, "precipitation": 0 + }, + { + "timestamp": "2026-06-05T21:00:00.000Z", + "level": 467.72, + "flow": 0, + "inflow": 2.88, + "volume": 26.49, + "temperature": 13.1, + "precipitation": 0 } ] \ No newline at end of file diff --git a/public/data/MZHR.json b/public/data/MZHR.json index ff06ffb..0e637aa 100644 --- a/public/data/MZHR.json +++ b/public/data/MZHR.json @@ -6788,10 +6788,19 @@ { "timestamp": "2026-06-05T20:50:00.000Z", "level": 352.85, - "flow": 0, + "flow": 2.53, "inflow": 1.46, "volume": 32.35, "temperature": 12.1, "precipitation": 5.7 + }, + { + "timestamp": "2026-06-05T21:00:00.000Z", + "level": 352.86, + "flow": 0, + "inflow": 1.46, + "volume": 32.37, + "temperature": 12.1, + "precipitation": 5.7 } ] \ No newline at end of file diff --git a/public/data/VLHN.json b/public/data/VLHN.json index 95b493b..df72824 100644 --- a/public/data/VLHN.json +++ b/public/data/VLHN.json @@ -6793,5 +6793,14 @@ "volume": 20.2, "temperature": 18.234727964853622, "precipitation": 0 + }, + { + "timestamp": "2026-06-05T21:00:00.000Z", + "level": 369.78, + "flow": 1.25, + "inflow": 10.82, + "volume": 20.24, + "temperature": 18.234727964853622, + "precipitation": 0 } ] \ No newline at end of file diff --git a/public/data/VLKO.json b/public/data/VLKO.json index 2fece5e..b45e58d 100644 --- a/public/data/VLKO.json +++ b/public/data/VLKO.json @@ -6793,5 +6793,14 @@ "volume": 2.75, "temperature": 12.4, "precipitation": 0 + }, + { + "timestamp": "2026-06-05T21:00:00.000Z", + "level": 352.44, + "flow": 19.01, + "inflow": 14.13, + "volume": 2.74, + "temperature": 12.4, + "precipitation": 0 } ] \ No newline at end of file diff --git a/public/data/VLL1.json b/public/data/VLL1.json index ad2f305..45f1d65 100644 --- a/public/data/VLL1.json +++ b/public/data/VLL1.json @@ -6788,10 +6788,19 @@ { "timestamp": "2026-06-05T20:50:00.000Z", "level": 723.09, - "flow": 0, + "flow": 1.51, "inflow": 2.51, "volume": 199.27, "temperature": 18.62002326908434, "precipitation": 0 + }, + { + "timestamp": "2026-06-05T21:00:00.000Z", + "level": 723.09, + "flow": 0, + "inflow": 2.51, + "volume": 199.67, + "temperature": 18.62002326908434, + "precipitation": 0 } ] \ No newline at end of file diff --git a/public/data/VLL2.json b/public/data/VLL2.json index 13865a3..fd1a38d 100644 --- a/public/data/VLL2.json +++ b/public/data/VLL2.json @@ -6793,5 +6793,14 @@ "volume": 0.62, "temperature": 17.97824695485787, "precipitation": 0 + }, + { + "timestamp": "2026-06-05T21:00:00.000Z", + "level": 559.91, + "flow": 0, + "inflow": 3.71, + "volume": 0.68, + "temperature": 17.97824695485787, + "precipitation": 0 } ] \ No newline at end of file diff --git a/public/data/VLOR.json b/public/data/VLOR.json index 95ee150..3a78173 100644 --- a/public/data/VLOR.json +++ b/public/data/VLOR.json @@ -6793,5 +6793,14 @@ "volume": 523.52, "temperature": 18.70045888971512, "precipitation": 0 + }, + { + "timestamp": "2026-06-05T21:00:00.000Z", + "level": 345.26, + "flow": 377.67, + "inflow": 23.84, + "volume": 522.12, + "temperature": 18.70045888971512, + "precipitation": 0 } ] \ No newline at end of file diff --git a/public/data/VLSL.json b/public/data/VLSL.json index f718b10..ce1b542 100644 --- a/public/data/VLSL.json +++ b/public/data/VLSL.json @@ -6793,5 +6793,14 @@ "volume": 259.76, "temperature": 16.3, "precipitation": 0 + }, + { + "timestamp": "2026-06-05T21:00:00.000Z", + "level": 269.8, + "flow": 217.32, + "inflow": 46.5, + "volume": 260.21, + "temperature": 16.3, + "precipitation": 0 } ] \ No newline at end of file diff --git a/public/data/VLST.json b/public/data/VLST.json index 739ca5d..1654282 100644 --- a/public/data/VLST.json +++ b/public/data/VLST.json @@ -6793,5 +6793,14 @@ "volume": 8.96, "temperature": 18.450684013836877, "precipitation": 0 + }, + { + "timestamp": "2026-06-05T21:00:00.000Z", + "level": 218.59, + "flow": 85.17, + "inflow": 19.85, + "volume": 9.68, + "temperature": 18.450684013836877, + "precipitation": 0 } ] \ No newline at end of file diff --git a/public/data/lakes_index.json b/public/data/lakes_index.json index 9e67a27..b6a8df8 100644 --- a/public/data/lakes_index.json +++ b/public/data/lakes_index.json @@ -25,7 +25,7 @@ 14.02, 1.51, 1.51, - 0 + 1.51 ] }, { @@ -37,7 +37,7 @@ "capacity": 100, "storageDiff": 48.41, "inflow": "3.7", - "outflow": "7.2", + "outflow": "0.0", "volume": 0.62, "maxVolume": 1.5, "lat": 48.625, @@ -298,7 +298,7 @@ "capacity": 74.8, "storageDiff": -2.93, "inflow": "2.9", - "outflow": "0.7", + "outflow": "0.0", "volume": 26.49, "maxVolume": 33.8, "lat": 48.847, @@ -344,7 +344,7 @@ 2.53, 2.53, 2.53, - 0 + 2.53 ] } ] \ No newline at end of file diff --git a/scripts/__tests__/scrapeLakes.test.ts b/scripts/__tests__/scrapeLakes.test.ts new file mode 100644 index 0000000..991a1f0 --- /dev/null +++ b/scripts/__tests__/scrapeLakes.test.ts @@ -0,0 +1,25 @@ +import { describe, it, expect } from 'vitest'; +import { parseDateString } from '../scrapeLakes'; + +describe('scrapeLakes - parseDateString', () => { + it('should parse valid date strings correctly', () => { + // Note: JS Date parsing uses local timezone, so the output ISO string depends on where the test runs. + // To make it deterministic, we just check if it returns a string and is not null. + const result = parseDateString('05.06.2026 22:30'); + expect(result).not.toBeNull(); + // Assuming standard parsing, it should contain 2026 + expect(result).toContain('2026-06-05'); + }); + + it('should return null for invalid formats', () => { + expect(parseDateString('')).toBeNull(); + expect(parseDateString('invalid date string')).toBeNull(); + expect(parseDateString('05.06.2026')).toBeNull(); // Missing time + expect(parseDateString('22:30')).toBeNull(); // Missing date + }); + + it('should return null for malformed parts', () => { + expect(parseDateString('99.99.9999 99:99')).toBeNull(); // JS Date might parse this as valid overflow, but let's check + expect(parseDateString('abc def ghi')).toBeNull(); + }); +}); diff --git a/scripts/buildIndex.ts b/scripts/buildIndex.ts index 9d17415..e5abea7 100644 --- a/scripts/buildIndex.ts +++ b/scripts/buildIndex.ts @@ -26,7 +26,7 @@ const lakes = lakesConfig.map(lake => { if (data.length > 0) { // Find latest valid record or just the last record const lastValidLevelData = [...data].reverse().find(d => d.level !== null && !isNaN(d.level)); - const lastValidFlowData = [...data].reverse().find(d => d.flow !== null && !isNaN(d.flow) && d.flow > 0); + const lastValidFlowData = [...data].reverse().find(d => d.flow !== null && !isNaN(d.flow) && d.flow >= 0); currentLevel = lastValidLevelData ? lastValidLevelData.level : 0; currentFlow = lastValidFlowData ? lastValidFlowData.flow : 0; diff --git a/scripts/scrapeLakes.ts b/scripts/scrapeLakes.ts index cfbe72d..a252a61 100644 --- a/scripts/scrapeLakes.ts +++ b/scripts/scrapeLakes.ts @@ -16,7 +16,7 @@ interface DataRecord { } // Parse date from DD.MM.YYYY HH:MM to ISO -function parseDateString(dateStr: string): string | null { +export function parseDateString(dateStr: string): string | null { try { if (!dateStr || !dateStr.includes(' ')) return null; const [datePart, timePart] = dateStr.trim().split(' '); @@ -25,8 +25,12 @@ function parseDateString(dateStr: string): string | null { if (!year || !hours) return null; - const d = new Date(parseInt(year), parseInt(month) - 1, parseInt(day), parseInt(hours), parseInt(minutes)); + const y = parseInt(year); + const m = parseInt(month) - 1; + const dDay = parseInt(day); + const d = new Date(y, m, dDay, parseInt(hours), parseInt(minutes)); if (isNaN(d.getTime())) return null; + if (d.getFullYear() !== y || d.getMonth() !== m || d.getDate() !== dDay) return null; return d.toISOString(); } catch (e) { return null; diff --git a/src/__tests__/translations.test.ts b/src/__tests__/translations.test.ts new file mode 100644 index 0000000..c60c598 --- /dev/null +++ b/src/__tests__/translations.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect } from 'vitest'; +import { t } from '../translations'; + +describe('Translations', () => { + it('should have exactly the same keys in English and Czech', () => { + const enKeys = Object.keys(t.en).sort(); + const csKeys = Object.keys(t.cs).sort(); + + expect(enKeys).toEqual(csKeys); + + // Deep check for nested keys + for (const key of enKeys) { + const enSubKeys = Object.keys((t.en as any)[key]).sort(); + const csSubKeys = Object.keys((t.cs as any)[key]).sort(); + + expect(enSubKeys).toEqual(csSubKeys); + } + }); + + it('should not have empty translation strings', () => { + const checkEmpty = (obj: any) => { + for (const val of Object.values(obj)) { + if (typeof val === 'string') { + expect(val.length).toBeGreaterThan(0); + } else if (typeof val === 'object') { + checkEmpty(val); + } + } + }; + + checkEmpty(t.en); + checkEmpty(t.cs); + }); +}); diff --git a/src/components/LakeDetail.tsx b/src/components/LakeDetail.tsx index 75ff4e2..4c205df 100644 --- a/src/components/LakeDetail.tsx +++ b/src/components/LakeDetail.tsx @@ -41,7 +41,12 @@ const CustomTooltip = ({ active, payload, label, language, isWeather }: any) => return (

{label}

- {payload.map((entry: any, index: number) => { + {[...payload].sort((a: any, b: any) => { + const order = ['level', 'inflow', 'outflow']; + const indexA = order.indexOf(a.dataKey); + const indexB = order.indexOf(b.dataKey); + return (indexA === -1 ? 99 : indexA) - (indexB === -1 ? 99 : indexB); + }).map((entry: any, index: number) => { let labelStr = ''; let unit = ''; let color = ''; @@ -49,7 +54,7 @@ const CustomTooltip = ({ active, payload, label, language, isWeather }: any) => else if (entry.dataKey === 'outflow') { labelStr = dict.outflow; unit = 'm³/s'; color = 'var(--color-orange)'; } else if (entry.dataKey === 'inflow') { labelStr = dict.inflow; unit = 'm³/s'; color = '#8b5cf6'; } - if (!labelStr || (entry.dataKey === 'inflow' && entry.value === 0)) return null; + if (!labelStr || entry.value === null || entry.value === undefined) return null; return (
@@ -125,8 +130,8 @@ const LakeDetail = ({ language, lakeId }: Props) => { const latestData = data[data.length - 1] || { level: 0, inflow: 0, outflow: 0, volume: 0, fullness: 0 }; const curveType = isSmoothed ? 'monotone' : 'linear'; - // Find the last record that actually has flow data (often the very last record is incomplete on PVL) - const lastValidFlowData = [...data].reverse().find(d => d.outflow > 0) || latestData; + // Find last valid values for KPIs, including 0 + const lastValidFlowData = [...data].reverse().find(d => d.outflow !== null && !isNaN(d.outflow) && d.outflow >= 0) || latestData; const now = new Date().getTime(); const getCutoff = () => { diff --git a/src/components/__tests__/KpiCards.test.tsx b/src/components/__tests__/KpiCards.test.tsx new file mode 100644 index 0000000..315a403 --- /dev/null +++ b/src/components/__tests__/KpiCards.test.tsx @@ -0,0 +1,41 @@ +import { render, screen } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; +import KpiCards from '../KpiCards'; + +describe('KpiCards Component', () => { + const mockData = { + level: 723.10, + inflow: 5.5, + outflow: 2.5, + volume: 150, + fullness: 80, + storageDiff: -1.81 + }; + + it('renders correctly with negative storageDiff (red)', () => { + const { container } = render(); + + // ZÁSOBNÍ PROSTOR card should show -1.81 m + expect(screen.getByText('-1.81 m')).toBeInTheDocument(); + + // Because it is negative, it should have the red color style applied + const diffElement = screen.getByText('-1.81 m'); + expect(diffElement.parentElement?.outerHTML).toContain('var(--color-red)'); + }); + + it('renders correctly with positive storageDiff (cyan)', () => { + const positiveData = { ...mockData, storageDiff: 0.5 }; + render(); + + expect(screen.getByText('+0.50 m')).toBeInTheDocument(); + const diffElement = screen.getByText('+0.50 m'); + expect(diffElement.parentElement?.outerHTML).toContain('var(--color-cyan)'); + }); + + it('falls back to percentage fullness if storageDiff is zero/undefined', () => { + const noDiffData = { ...mockData, storageDiff: 0, fullness: 85.5 }; + render(); + + expect(screen.getByText('85.5%')).toBeInTheDocument(); + }); +}); diff --git a/src/test/setup.ts b/src/test/setup.ts new file mode 100644 index 0000000..7b0828b --- /dev/null +++ b/src/test/setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-dom'; diff --git a/vite.config.ts b/vite.config.ts index 8b0f57b..aa458c6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,7 +1,13 @@ +/// import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' // https://vite.dev/config/ export default defineConfig({ plugins: [react()], + test: { + globals: true, + environment: 'jsdom', + setupFiles: './src/test/setup.ts', + }, })