From e3c379d069ffa9661561d25cdbf2f5894a2f8ee8 Mon Sep 17 00:00:00 2001 From: Adam Mathes Date: Sat, 14 Feb 2026 08:58:38 -0800 Subject: Refactor: project structure, implement dependency injection, and align v2 UI with v1 --- frontend/.prettierrc | 7 + frontend/README.md | 8 +- frontend/coverage/base.css | 384 +- frontend/coverage/block-navigation.js | 133 +- frontend/coverage/coverage-final.json | 2251 +- frontend/coverage/index.html | 238 +- frontend/coverage/prettify.css | 102 +- frontend/coverage/prettify.js | 939 +- frontend/coverage/sorter.js | 379 +- frontend/coverage/src/App.css.html | 128 +- frontend/coverage/src/App.tsx.html | 128 +- frontend/coverage/src/components/FeedItem.css.html | 133 +- frontend/coverage/src/components/FeedItem.tsx.html | 133 +- .../coverage/src/components/FeedItems.css.html | 133 +- .../coverage/src/components/FeedItems.tsx.html | 133 +- frontend/coverage/src/components/FeedList.css.html | 133 +- frontend/coverage/src/components/FeedList.tsx.html | 133 +- frontend/coverage/src/components/Login.css.html | 133 +- frontend/coverage/src/components/Login.tsx.html | 133 +- frontend/coverage/src/components/Settings.css.html | 133 +- frontend/coverage/src/components/Settings.tsx.html | 133 +- frontend/coverage/src/components/index.html | 504 +- frontend/coverage/src/index.html | 236 +- frontend/eslint.config.js | 34 +- frontend/package-lock.json | 108 + frontend/package.json | 8 +- frontend/playwright-report/index.html | 21860 ++++++++++++++++++- frontend/playwright.config.ts | 40 +- frontend/src/App.css | 4 +- frontend/src/App.test.tsx | 79 +- frontend/src/App.tsx | 42 +- frontend/src/components/FeedItem.css | 124 +- frontend/src/components/FeedItem.test.tsx | 103 +- frontend/src/components/FeedItem.tsx | 146 +- frontend/src/components/FeedItems.css | 23 +- frontend/src/components/FeedItems.test.tsx | 431 +- frontend/src/components/FeedItems.tsx | 423 +- frontend/src/components/FeedList.css | 182 +- frontend/src/components/FeedList.test.tsx | 200 +- frontend/src/components/FeedList.tsx | 246 +- frontend/src/components/Login.css | 4 +- frontend/src/components/Login.test.tsx | 101 +- frontend/src/components/Login.tsx | 84 +- frontend/src/components/Settings.css | 87 +- frontend/src/components/Settings.test.tsx | 145 +- frontend/src/components/Settings.tsx | 214 +- frontend/src/components/TagView.test.tsx | 128 +- frontend/src/index.css | 10 +- frontend/src/main.tsx | 12 +- frontend/src/setupTests.ts | 48 +- frontend/src/types.ts | 34 +- frontend/test-results/.last-run.json | 2 +- frontend/tests/e2e.spec.ts | 114 +- frontend/tsconfig.app.json | 21 +- frontend/tsconfig.json | 5 +- frontend/tsconfig.node.json | 15 +- frontend/vite.config.ts | 8 +- frontend/vitest.config.ts | 16 +- 58 files changed, 28609 insertions(+), 3259 deletions(-) create mode 100644 frontend/.prettierrc (limited to 'frontend') diff --git a/frontend/.prettierrc b/frontend/.prettierrc new file mode 100644 index 0000000..1f4c4bb --- /dev/null +++ b/frontend/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "printWidth": 100 +} diff --git a/frontend/README.md b/frontend/README.md index d2e7761..c987b94 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -40,15 +40,15 @@ export default defineConfig([ // other options... }, }, -]) +]); ``` You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: ```js // eslint.config.js -import reactX from 'eslint-plugin-react-x' -import reactDom from 'eslint-plugin-react-dom' +import reactX from 'eslint-plugin-react-x'; +import reactDom from 'eslint-plugin-react-dom'; export default defineConfig([ globalIgnores(['dist']), @@ -69,5 +69,5 @@ export default defineConfig([ // other options... }, }, -]) +]); ``` diff --git a/frontend/coverage/base.css b/frontend/coverage/base.css index f418035..8cd9700 100644 --- a/frontend/coverage/base.css +++ b/frontend/coverage/base.css @@ -1,71 +1,129 @@ -body, html { - margin:0; padding: 0; +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; } + 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; } + 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; + 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; } -.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); + color: rgba(0, 0, 0, 0.5); +} +.quiet a { + opacity: 0.7; } -.quiet a { opacity: 0.7; } .fraction { font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; font-size: 10px; color: #555; - background: #E8E8E8; + background: #e8e8e8; padding: 4px 5px; border-radius: 3px; vertical-align: middle; } -div.path a:link, div.path a:visited { color: #333; } +div.path a:link, +div.path a:visited { + color: #333; +} table.coverage { border-collapse: collapse; margin: 10px 0 0 0; @@ -78,140 +136,219 @@ table.coverage td { vertical-align: top; } table.coverage td.line-count { - text-align: right; - padding: 0 5px 0 20px; + text-align: right; + padding: 0 5px 0 20px; } table.coverage td.line-coverage { - text-align: right; - padding-right: 10px; - min-width:20px; + text-align: right; + padding-right: 10px; + min-width: 20px; } table.coverage td span.cline-any { - display: inline-block; - padding: 0 5px; - width: 100%; + 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; + 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; + 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; +.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 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.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 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; + 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; + background-position: 0 -20px; } .coverage-summary .sorted-desc .sorter { - background-position: 0 -10px; + background-position: 0 -10px; +} +.status-line { + height: 10px; } -.status-line { height: 10px; } /* yellow */ -.cbranch-no { background: yellow !important; color: #111; } +.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 } +.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; +.highlighted .cstat-no, +.highlighted .fstat-no, +.highlighted .cbranch-no { + background: #c21f39 !important; } /* medium red */ -.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +.cstat-no, +.fstat-no, +.cbranch-no, +.cbranch-no { + background: #f6c6ce; +} /* light red */ -.low, .cline-no { background:#FCE1E5 } +.low, +.cline-no { + background: #fce1e5; +} /* light green */ -.high, .cline-yes { background:rgb(230,245,208) } +.high, +.cline-yes { + background: rgb(230, 245, 208); +} /* medium green */ -.cstat-yes { background:rgb(161,215,106) } +.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) } +.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; } +.status-line.medium, +.medium .cover-fill { + background: #f9cd0b; +} +.medium .chart { + border: 1px solid #f9cd0b; +} /* light yellow */ -.medium { background: #fff4c2; } +.medium { + background: #fff4c2; +} -.cstat-skip { background: #ddd; color: #111; } -.fstat-skip { background: #ddd; color: #111 !important; } -.cbranch-skip { background: #ddd !important; color: #111; } +.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; } +span.cline-neutral { + background: #eaeaea; +} .coverage-summary td.empty { - opacity: .5; - padding-top: 4px; - padding-bottom: 4px; - line-height: 1; - color: #888; + opacity: 0.5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; } -.cover-fill, .cover-empty { - display:inline-block; +.cover-fill, +.cover-empty { + display: inline-block; height: 12px; } .chart { line-height: 0; } .cover-empty { - background: white; + background: white; } .cover-full { - border-right: none !important; + border-right: none !important; } pre.prettyprint { - border: none !important; - padding: 0 !important; - margin: 0 !important; + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { + color: #999 !important; +} +.ignore-none { + color: #999; + font-weight: normal; } -.com { color: #999 !important; } -.ignore-none { color: #999; font-weight: normal; } .wrapper { min-height: 100%; @@ -219,6 +356,7 @@ pre.prettyprint { height: 100%; margin: 0 auto -48px; } -.footer, .push { +.footer, +.push { height: 48px; } diff --git a/frontend/coverage/block-navigation.js b/frontend/coverage/block-navigation.js index 530d1ed..05f7569 100644 --- a/frontend/coverage/block-navigation.js +++ b/frontend/coverage/block-navigation.js @@ -1,87 +1,82 @@ -/* 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']; + // 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']; + // 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) > ` + // 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` + // 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); + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); - var currentIndex; + 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 toggleClass(index) { + missingCoverageElements.item(currentIndex).classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } - function goToPrevious() { - var nextIndex = 0; - if (typeof currentIndex !== 'number' || currentIndex === 0) { - nextIndex = missingCoverageElements.length - 1; - } else if (missingCoverageElements.length > 1) { - nextIndex = currentIndex - 1; - } + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center', + }); + } - makeCurrent(nextIndex); + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; } - function goToNext() { - var nextIndex = 0; + makeCurrent(nextIndex); + } - if ( - typeof currentIndex === 'number' && - currentIndex < missingCoverageElements.length - 1 - ) { - nextIndex = currentIndex + 1; - } + function goToNext() { + var nextIndex = 0; - makeCurrent(nextIndex); + if (typeof currentIndex === 'number' && currentIndex < missingCoverageElements.length - 1) { + nextIndex = currentIndex + 1; } - 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; - } + makeCurrent(nextIndex); + } - switch (event.which) { - case 78: // n - case 74: // j - goToNext(); - break; - case 66: // b - case 75: // k - case 80: // p - goToPrevious(); - break; - } - }; + 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/frontend/coverage/coverage-final.json b/frontend/coverage/coverage-final.json index 80fb28a..e46821b 100644 --- a/frontend/coverage/coverage-final.json +++ b/frontend/coverage/coverage-final.json @@ -1,13 +1,2240 @@ -{"/Users/adam/workspace/vibecode/neko/frontend/src/App.css": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/App.css","statementMap":{},"fnMap":{},"branchMap":{},"s":{},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":0,"seen":{}}} -,"/Users/adam/workspace/vibecode/neko/frontend/src/App.tsx": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/App.tsx","statementMap":{"0":{"start":{"line":8,"column":22},"end":{"line":8,"column":null}},"1":{"start":{"line":9,"column":8},"end":{"line":9,"column":null}},"2":{"start":{"line":11,"column":2},"end":{"line":21,"column":null}},"3":{"start":{"line":12,"column":4},"end":{"line":20,"column":null}},"4":{"start":{"line":14,"column":8},"end":{"line":18,"column":null}},"5":{"start":{"line":15,"column":10},"end":{"line":15,"column":null}},"6":{"start":{"line":17,"column":10},"end":{"line":17,"column":null}},"7":{"start":{"line":20,"column":19},"end":{"line":20,"column":33}},"8":{"start":{"line":23,"column":2},"end":{"line":25,"column":null}},"9":{"start":{"line":24,"column":4},"end":{"line":24,"column":null}},"10":{"start":{"line":27,"column":2},"end":{"line":29,"column":null}},"11":{"start":{"line":28,"column":4},"end":{"line":28,"column":null}},"12":{"start":{"line":31,"column":2},"end":{"line":31,"column":null}},"13":{"start":{"line":39,"column":8},"end":{"line":39,"column":null}},"14":{"start":{"line":40,"column":2},"end":{"line":68,"column":null}},"15":{"start":{"line":45,"column":33},"end":{"line":45,"column":56}},"16":{"start":{"line":48,"column":12},"end":{"line":49,"column":null}},"17":{"start":{"line":49,"column":26},"end":{"line":49,"column":60}},"18":{"start":{"line":73,"column":2},"end":{"line":86,"column":null}}},"fnMap":{"0":{"name":"RequireAuth","decl":{"start":{"line":7,"column":9},"end":{"line":7,"column":21}},"loc":{"start":{"line":7,"column":69},"end":{"line":32,"column":null}},"line":7},"1":{"name":"(anonymous_1)","decl":{"start":{"line":11,"column":12},"end":{"line":11,"column":18}},"loc":{"start":{"line":11,"column":18},"end":{"line":21,"column":5}},"line":11},"2":{"name":"(anonymous_2)","decl":{"start":{"line":13,"column":12},"end":{"line":13,"column":13}},"loc":{"start":{"line":13,"column":21},"end":{"line":19,"column":7}},"line":13},"3":{"name":"(anonymous_3)","decl":{"start":{"line":20,"column":13},"end":{"line":20,"column":19}},"loc":{"start":{"line":20,"column":19},"end":{"line":20,"column":33}},"line":20},"4":{"name":"Dashboard","decl":{"start":{"line":38,"column":9},"end":{"line":38,"column":21}},"loc":{"start":{"line":38,"column":21},"end":{"line":70,"column":null}},"line":38},"5":{"name":"(anonymous_5)","decl":{"start":{"line":45,"column":27},"end":{"line":45,"column":33}},"loc":{"start":{"line":45,"column":33},"end":{"line":45,"column":56}},"line":45},"6":{"name":"(anonymous_6)","decl":{"start":{"line":47,"column":27},"end":{"line":47,"column":33}},"loc":{"start":{"line":47,"column":33},"end":{"line":50,"column":13}},"line":47},"7":{"name":"(anonymous_7)","decl":{"start":{"line":49,"column":20},"end":{"line":49,"column":26}},"loc":{"start":{"line":49,"column":26},"end":{"line":49,"column":60}},"line":49},"8":{"name":"App","decl":{"start":{"line":72,"column":9},"end":{"line":72,"column":15}},"loc":{"start":{"line":72,"column":15},"end":{"line":88,"column":null}},"line":72}},"branchMap":{"0":{"loc":{"start":{"line":14,"column":8},"end":{"line":18,"column":null}},"type":"if","locations":[{"start":{"line":14,"column":8},"end":{"line":18,"column":null}},{"start":{"line":16,"column":15},"end":{"line":18,"column":null}}],"line":14},"1":{"loc":{"start":{"line":23,"column":2},"end":{"line":25,"column":null}},"type":"if","locations":[{"start":{"line":23,"column":2},"end":{"line":25,"column":null}},{"start":{},"end":{}}],"line":23},"2":{"loc":{"start":{"line":27,"column":2},"end":{"line":29,"column":null}},"type":"if","locations":[{"start":{"line":27,"column":2},"end":{"line":29,"column":null}},{"start":{},"end":{}}],"line":27}},"s":{"0":2,"1":2,"2":2,"3":1,"4":1,"5":1,"6":0,"7":0,"8":2,"9":1,"10":1,"11":0,"12":1,"13":1,"14":1,"15":0,"16":1,"17":1,"18":2},"f":{"0":2,"1":1,"2":1,"3":0,"4":1,"5":0,"6":1,"7":1,"8":2},"b":{"0":[1,0],"1":[1,1],"2":[0,1]},"meta":{"lastBranch":3,"lastFunction":9,"lastStatement":19,"seen":{"f:7:9:7:21":0,"s:8:22:8:Infinity":0,"s:9:8:9:Infinity":1,"s:11:2:21:Infinity":2,"f:11:12:11:18":1,"s:12:4:20:Infinity":3,"f:13:12:13:13":2,"b:14:8:18:Infinity:16:15:18:Infinity":0,"s:14:8:18:Infinity":4,"s:15:10:15:Infinity":5,"s:17:10:17:Infinity":6,"f:20:13:20:19":3,"s:20:19:20:33":7,"b:23:2:25:Infinity:undefined:undefined:undefined:undefined":1,"s:23:2:25:Infinity":8,"s:24:4:24:Infinity":9,"b:27:2:29:Infinity:undefined:undefined:undefined:undefined":2,"s:27:2:29:Infinity":10,"s:28:4:28:Infinity":11,"s:31:2:31:Infinity":12,"f:38:9:38:21":4,"s:39:8:39:Infinity":13,"s:40:2:68:Infinity":14,"f:45:27:45:33":5,"s:45:33:45:56":15,"f:47:27:47:33":6,"s:48:12:49:Infinity":16,"f:49:20:49:26":7,"s:49:26:49:60":17,"f:72:9:72:15":8,"s:73:2:86:Infinity":18}}} -,"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItem.css": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItem.css","statementMap":{},"fnMap":{},"branchMap":{},"s":{},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":0,"seen":{}}} -,"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItem.tsx": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItem.tsx","statementMap":{"0":{"start":{"line":10,"column":24},"end":{"line":10,"column":null}},"1":{"start":{"line":11,"column":30},"end":{"line":11,"column":null}},"2":{"start":{"line":14,"column":23},"end":{"line":16,"column":null}},"3":{"start":{"line":15,"column":8},"end":{"line":15,"column":null}},"4":{"start":{"line":18,"column":23},"end":{"line":52,"column":null}},"5":{"start":{"line":19,"column":8},"end":{"line":19,"column":null}},"6":{"start":{"line":21,"column":29},"end":{"line":21,"column":null}},"7":{"start":{"line":22,"column":8},"end":{"line":22,"column":null}},"8":{"start":{"line":24,"column":8},"end":{"line":51,"column":null}},"9":{"start":{"line":36,"column":16},"end":{"line":38,"column":null}},"10":{"start":{"line":37,"column":20},"end":{"line":37,"column":null}},"11":{"start":{"line":39,"column":16},"end":{"line":39,"column":null}},"12":{"start":{"line":44,"column":16},"end":{"line":44,"column":null}},"13":{"start":{"line":47,"column":16},"end":{"line":47,"column":null}},"14":{"start":{"line":49,"column":16},"end":{"line":49,"column":null}},"15":{"start":{"line":50,"column":16},"end":{"line":50,"column":null}},"16":{"start":{"line":54,"column":4},"end":{"line":82,"column":null}},"17":{"start":{"line":59,"column":24},"end":{"line":59,"column":null}},"18":{"start":{"line":60,"column":24},"end":{"line":60,"column":null}}},"fnMap":{"0":{"name":"FeedItem","decl":{"start":{"line":9,"column":24},"end":{"line":9,"column":33}},"loc":{"start":{"line":9,"column":71},"end":{"line":84,"column":null}},"line":9},"1":{"name":"(anonymous_1)","decl":{"start":{"line":14,"column":23},"end":{"line":14,"column":29}},"loc":{"start":{"line":14,"column":29},"end":{"line":16,"column":null}},"line":14},"2":{"name":"(anonymous_2)","decl":{"start":{"line":18,"column":23},"end":{"line":18,"column":24}},"loc":{"start":{"line":18,"column":42},"end":{"line":52,"column":null}},"line":18},"3":{"name":"(anonymous_3)","decl":{"start":{"line":35,"column":18},"end":{"line":35,"column":19}},"loc":{"start":{"line":35,"column":27},"end":{"line":40,"column":13}},"line":35},"4":{"name":"(anonymous_4)","decl":{"start":{"line":41,"column":18},"end":{"line":41,"column":24}},"loc":{"start":{"line":41,"column":24},"end":{"line":45,"column":13}},"line":41},"5":{"name":"(anonymous_5)","decl":{"start":{"line":46,"column":19},"end":{"line":46,"column":20}},"loc":{"start":{"line":46,"column":28},"end":{"line":51,"column":13}},"line":46},"6":{"name":"(anonymous_6)","decl":{"start":{"line":58,"column":29},"end":{"line":58,"column":30}},"loc":{"start":{"line":58,"column":36},"end":{"line":61,"column":null}},"line":58}},"branchMap":{"0":{"loc":{"start":{"line":36,"column":16},"end":{"line":38,"column":null}},"type":"if","locations":[{"start":{"line":36,"column":16},"end":{"line":38,"column":null}},{"start":{},"end":{}}],"line":36},"1":{"loc":{"start":{"line":55,"column":36},"end":{"line":55,"column":65}},"type":"cond-expr","locations":[{"start":{"line":55,"column":48},"end":{"line":55,"column":57}},{"start":{"line":55,"column":57},"end":{"line":55,"column":65}}],"line":55},"2":{"loc":{"start":{"line":55,"column":69},"end":{"line":55,"column":93}},"type":"cond-expr","locations":[{"start":{"line":55,"column":79},"end":{"line":55,"column":91}},{"start":{"line":55,"column":91},"end":{"line":55,"column":93}}],"line":55},"3":{"loc":{"start":{"line":62,"column":43},"end":{"line":62,"column":87}},"type":"cond-expr","locations":[{"start":{"line":62,"column":58},"end":{"line":62,"column":73}},{"start":{"line":62,"column":73},"end":{"line":62,"column":87}}],"line":62},"4":{"loc":{"start":{"line":63,"column":27},"end":{"line":63,"column":null}},"type":"cond-expr","locations":[{"start":{"line":63,"column":42},"end":{"line":63,"column":53}},{"start":{"line":63,"column":53},"end":{"line":63,"column":null}}],"line":63},"5":{"loc":{"start":{"line":65,"column":21},"end":{"line":65,"column":null}},"type":"cond-expr","locations":[{"start":{"line":65,"column":36},"end":{"line":65,"column":42}},{"start":{"line":65,"column":42},"end":{"line":65,"column":null}}],"line":65},"6":{"loc":{"start":{"line":68,"column":21},"end":{"line":68,"column":null}},"type":"binary-expr","locations":[{"start":{"line":68,"column":21},"end":{"line":68,"column":35}},{"start":{"line":68,"column":35},"end":{"line":68,"column":null}}],"line":68},"7":{"loc":{"start":{"line":74,"column":21},"end":{"line":74,"column":null}},"type":"binary-expr","locations":[{"start":{"line":74,"column":21},"end":{"line":74,"column":40}},{"start":{"line":74,"column":40},"end":{"line":74,"column":null}}],"line":74},"8":{"loc":{"start":{"line":79,"column":13},"end":{"line":80,"column":null}},"type":"binary-expr","locations":[{"start":{"line":79,"column":13},"end":{"line":79,"column":null}},{"start":{"line":80,"column":16},"end":{"line":80,"column":null}}],"line":79}},"s":{"0":21,"1":21,"2":21,"3":1,"4":21,"5":1,"6":1,"7":1,"8":1,"9":1,"10":0,"11":1,"12":1,"13":0,"14":0,"15":0,"16":21,"17":1,"18":1},"f":{"0":21,"1":1,"2":1,"3":1,"4":1,"5":0,"6":1},"b":{"0":[0,1],"1":[9,12],"2":[1,20],"3":[2,19],"4":[2,19],"5":[2,19],"6":[21,0],"7":[21,5],"8":[21,4]},"meta":{"lastBranch":9,"lastFunction":7,"lastStatement":19,"seen":{"f:9:24:9:33":0,"s:10:24:10:Infinity":0,"s:11:30:11:Infinity":1,"s:14:23:16:Infinity":2,"f:14:23:14:29":1,"s:15:8:15:Infinity":3,"s:18:23:52:Infinity":4,"f:18:23:18:24":2,"s:19:8:19:Infinity":5,"s:21:29:21:Infinity":6,"s:22:8:22:Infinity":7,"s:24:8:51:Infinity":8,"f:35:18:35:19":3,"b:36:16:38:Infinity:undefined:undefined:undefined:undefined":0,"s:36:16:38:Infinity":9,"s:37:20:37:Infinity":10,"s:39:16:39:Infinity":11,"f:41:18:41:24":4,"s:44:16:44:Infinity":12,"f:46:19:46:20":5,"s:47:16:47:Infinity":13,"s:49:16:49:Infinity":14,"s:50:16:50:Infinity":15,"s:54:4:82:Infinity":16,"b:55:48:55:57:55:57:55:65":1,"b:55:79:55:91:55:91:55:93":2,"f:58:29:58:30":6,"s:59:24:59:Infinity":17,"s:60:24:60:Infinity":18,"b:62:58:62:73:62:73:62:87":3,"b:63:42:63:53:63:53:63:Infinity":4,"b:65:36:65:42:65:42:65:Infinity":5,"b:68:21:68:35:68:35:68:Infinity":6,"b:74:21:74:40:74:40:74:Infinity":7,"b:79:13:79:Infinity:80:16:80:Infinity":8}}} -,"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItems.css": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItems.css","statementMap":{},"fnMap":{},"branchMap":{},"s":{},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":0,"seen":{}}} -,"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItems.tsx": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItems.tsx","statementMap":{"0":{"start":{"line":8,"column":28},"end":{"line":8,"column":null}},"1":{"start":{"line":9,"column":23},"end":{"line":9,"column":null}},"2":{"start":{"line":10,"column":21},"end":{"line":10,"column":null}},"3":{"start":{"line":12,"column":26},"end":{"line":12,"column":null}},"4":{"start":{"line":13,"column":30},"end":{"line":13,"column":null}},"5":{"start":{"line":14,"column":38},"end":{"line":14,"column":null}},"6":{"start":{"line":15,"column":30},"end":{"line":15,"column":null}},"7":{"start":{"line":16,"column":26},"end":{"line":16,"column":null}},"8":{"start":{"line":18,"column":23},"end":{"line":78,"column":null}},"9":{"start":{"line":19,"column":8},"end":{"line":24,"column":null}},"10":{"start":{"line":20,"column":12},"end":{"line":20,"column":null}},"11":{"start":{"line":22,"column":12},"end":{"line":22,"column":null}},"12":{"start":{"line":23,"column":12},"end":{"line":23,"column":null}},"13":{"start":{"line":25,"column":8},"end":{"line":25,"column":null}},"14":{"start":{"line":27,"column":18},"end":{"line":27,"column":null}},"15":{"start":{"line":28,"column":23},"end":{"line":28,"column":null}},"16":{"start":{"line":30,"column":8},"end":{"line":34,"column":null}},"17":{"start":{"line":31,"column":12},"end":{"line":31,"column":null}},"18":{"start":{"line":32,"column":8},"end":{"line":34,"column":null}},"19":{"start":{"line":33,"column":12},"end":{"line":33,"column":null}},"20":{"start":{"line":36,"column":8},"end":{"line":38,"column":null}},"21":{"start":{"line":37,"column":12},"end":{"line":37,"column":null}},"22":{"start":{"line":41,"column":8},"end":{"line":49,"column":null}},"23":{"start":{"line":42,"column":12},"end":{"line":42,"column":null}},"24":{"start":{"line":43,"column":8},"end":{"line":49,"column":null}},"25":{"start":{"line":44,"column":12},"end":{"line":44,"column":null}},"26":{"start":{"line":45,"column":12},"end":{"line":45,"column":null}},"27":{"start":{"line":48,"column":12},"end":{"line":48,"column":null}},"28":{"start":{"line":51,"column":28},"end":{"line":51,"column":null}},"29":{"start":{"line":52,"column":8},"end":{"line":54,"column":null}},"30":{"start":{"line":53,"column":12},"end":{"line":53,"column":null}},"31":{"start":{"line":56,"column":8},"end":{"line":77,"column":null}},"32":{"start":{"line":58,"column":16},"end":{"line":60,"column":null}},"33":{"start":{"line":59,"column":20},"end":{"line":59,"column":null}},"34":{"start":{"line":61,"column":16},"end":{"line":61,"column":null}},"35":{"start":{"line":64,"column":16},"end":{"line":68,"column":null}},"36":{"start":{"line":65,"column":20},"end":{"line":65,"column":null}},"37":{"start":{"line":65,"column":39},"end":{"line":65,"column":57}},"38":{"start":{"line":67,"column":20},"end":{"line":67,"column":null}},"39":{"start":{"line":69,"column":16},"end":{"line":69,"column":null}},"40":{"start":{"line":70,"column":16},"end":{"line":70,"column":null}},"41":{"start":{"line":71,"column":16},"end":{"line":71,"column":null}},"42":{"start":{"line":74,"column":16},"end":{"line":74,"column":null}},"43":{"start":{"line":75,"column":16},"end":{"line":75,"column":null}},"44":{"start":{"line":76,"column":16},"end":{"line":76,"column":null}},"45":{"start":{"line":80,"column":4},"end":{"line":82,"column":null}},"46":{"start":{"line":81,"column":8},"end":{"line":81,"column":null}},"47":{"start":{"line":84,"column":42},"end":{"line":84,"column":null}},"48":{"start":{"line":86,"column":4},"end":{"line":122,"column":null}},"49":{"start":{"line":87,"column":30},"end":{"line":118,"column":null}},"50":{"start":{"line":88,"column":12},"end":{"line":88,"column":null}},"51":{"start":{"line":88,"column":36},"end":{"line":88,"column":null}},"52":{"start":{"line":90,"column":12},"end":{"line":117,"column":null}},"53":{"start":{"line":91,"column":16},"end":{"line":101,"column":null}},"54":{"start":{"line":92,"column":38},"end":{"line":92,"column":null}},"55":{"start":{"line":93,"column":20},"end":{"line":99,"column":null}},"56":{"start":{"line":94,"column":37},"end":{"line":94,"column":null}},"57":{"start":{"line":95,"column":24},"end":{"line":97,"column":null}},"58":{"start":{"line":96,"column":28},"end":{"line":96,"column":null}},"59":{"start":{"line":98,"column":24},"end":{"line":98,"column":null}},"60":{"start":{"line":100,"column":20},"end":{"line":100,"column":null}},"61":{"start":{"line":102,"column":12},"end":{"line":117,"column":null}},"62":{"start":{"line":103,"column":16},"end":{"line":109,"column":null}},"63":{"start":{"line":104,"column":38},"end":{"line":104,"column":null}},"64":{"start":{"line":105,"column":20},"end":{"line":107,"column":null}},"65":{"start":{"line":106,"column":24},"end":{"line":106,"column":null}},"66":{"start":{"line":108,"column":20},"end":{"line":108,"column":null}},"67":{"start":{"line":110,"column":12},"end":{"line":117,"column":null}},"68":{"start":{"line":111,"column":16},"end":{"line":116,"column":null}},"69":{"start":{"line":112,"column":20},"end":{"line":114,"column":null}},"70":{"start":{"line":113,"column":24},"end":{"line":113,"column":null}},"71":{"start":{"line":115,"column":20},"end":{"line":115,"column":null}},"72":{"start":{"line":120,"column":8},"end":{"line":120,"column":null}},"73":{"start":{"line":121,"column":8},"end":{"line":121,"column":null}},"74":{"start":{"line":121,"column":21},"end":{"line":121,"column":null}},"75":{"start":{"line":124,"column":25},"end":{"line":129,"column":null}},"76":{"start":{"line":125,"column":24},"end":{"line":125,"column":null}},"77":{"start":{"line":126,"column":8},"end":{"line":128,"column":null}},"78":{"start":{"line":127,"column":12},"end":{"line":127,"column":null}},"79":{"start":{"line":131,"column":23},"end":{"line":141,"column":null}},"80":{"start":{"line":132,"column":28},"end":{"line":132,"column":null}},"81":{"start":{"line":134,"column":8},"end":{"line":134,"column":null}},"82":{"start":{"line":134,"column":32},"end":{"line":134,"column":92}},"83":{"start":{"line":134,"column":54},"end":{"line":134,"column":91}},"84":{"start":{"line":136,"column":8},"end":{"line":140,"column":null}},"85":{"start":{"line":140,"column":26},"end":{"line":140,"column":67}},"86":{"start":{"line":143,"column":23},"end":{"line":153,"column":null}},"87":{"start":{"line":144,"column":28},"end":{"line":144,"column":null}},"88":{"start":{"line":146,"column":8},"end":{"line":146,"column":null}},"89":{"start":{"line":146,"column":32},"end":{"line":146,"column":92}},"90":{"start":{"line":146,"column":54},"end":{"line":146,"column":91}},"91":{"start":{"line":148,"column":8},"end":{"line":152,"column":null}},"92":{"start":{"line":152,"column":26},"end":{"line":152,"column":69}},"93":{"start":{"line":155,"column":4},"end":{"line":191,"column":null}},"94":{"start":{"line":156,"column":25},"end":{"line":180,"column":null}},"95":{"start":{"line":158,"column":16},"end":{"line":177,"column":null}},"96":{"start":{"line":160,"column":20},"end":{"line":165,"column":null}},"97":{"start":{"line":161,"column":24},"end":{"line":163,"column":null}},"98":{"start":{"line":162,"column":28},"end":{"line":162,"column":null}},"99":{"start":{"line":164,"column":24},"end":{"line":164,"column":null}},"100":{"start":{"line":168,"column":20},"end":{"line":176,"column":null}},"101":{"start":{"line":169,"column":38},"end":{"line":169,"column":null}},"102":{"start":{"line":170,"column":24},"end":{"line":175,"column":null}},"103":{"start":{"line":171,"column":41},"end":{"line":171,"column":null}},"104":{"start":{"line":172,"column":28},"end":{"line":174,"column":null}},"105":{"start":{"line":173,"column":32},"end":{"line":173,"column":null}},"106":{"start":{"line":182,"column":8},"end":{"line":185,"column":null}},"107":{"start":{"line":183,"column":23},"end":{"line":183,"column":null}},"108":{"start":{"line":184,"column":12},"end":{"line":184,"column":null}},"109":{"start":{"line":184,"column":20},"end":{"line":184,"column":null}},"110":{"start":{"line":187,"column":25},"end":{"line":187,"column":null}},"111":{"start":{"line":188,"column":8},"end":{"line":188,"column":null}},"112":{"start":{"line":188,"column":22},"end":{"line":188,"column":null}},"113":{"start":{"line":190,"column":8},"end":{"line":190,"column":null}},"114":{"start":{"line":190,"column":21},"end":{"line":190,"column":null}},"115":{"start":{"line":193,"column":4},"end":{"line":193,"column":null}},"116":{"start":{"line":193,"column":17},"end":{"line":193,"column":null}},"117":{"start":{"line":194,"column":4},"end":{"line":194,"column":null}},"118":{"start":{"line":194,"column":15},"end":{"line":194,"column":null}},"119":{"start":{"line":197,"column":4},"end":{"line":221,"column":null}},"120":{"start":{"line":204,"column":24},"end":{"line":212,"column":null}},"121":{"start":{"line":209,"column":43},"end":{"line":209,"column":null}}},"fnMap":{"0":{"name":"FeedItems","decl":{"start":{"line":7,"column":24},"end":{"line":7,"column":36}},"loc":{"start":{"line":7,"column":36},"end":{"line":223,"column":null}},"line":7},"1":{"name":"(anonymous_1)","decl":{"start":{"line":18,"column":23},"end":{"line":18,"column":24}},"loc":{"start":{"line":18,"column":43},"end":{"line":78,"column":null}},"line":18},"2":{"name":"(anonymous_2)","decl":{"start":{"line":57,"column":18},"end":{"line":57,"column":19}},"loc":{"start":{"line":57,"column":27},"end":{"line":62,"column":13}},"line":57},"3":{"name":"(anonymous_3)","decl":{"start":{"line":63,"column":18},"end":{"line":63,"column":19}},"loc":{"start":{"line":63,"column":28},"end":{"line":72,"column":13}},"line":63},"4":{"name":"(anonymous_4)","decl":{"start":{"line":65,"column":29},"end":{"line":65,"column":30}},"loc":{"start":{"line":65,"column":39},"end":{"line":65,"column":57}},"line":65},"5":{"name":"(anonymous_5)","decl":{"start":{"line":73,"column":19},"end":{"line":73,"column":20}},"loc":{"start":{"line":73,"column":28},"end":{"line":77,"column":13}},"line":73},"6":{"name":"(anonymous_6)","decl":{"start":{"line":80,"column":14},"end":{"line":80,"column":20}},"loc":{"start":{"line":80,"column":20},"end":{"line":82,"column":7}},"line":80},"7":{"name":"(anonymous_7)","decl":{"start":{"line":86,"column":14},"end":{"line":86,"column":20}},"loc":{"start":{"line":86,"column":20},"end":{"line":122,"column":7}},"line":86},"8":{"name":"(anonymous_8)","decl":{"start":{"line":87,"column":30},"end":{"line":87,"column":31}},"loc":{"start":{"line":87,"column":52},"end":{"line":118,"column":null}},"line":87},"9":{"name":"(anonymous_9)","decl":{"start":{"line":91,"column":33},"end":{"line":91,"column":34}},"loc":{"start":{"line":91,"column":43},"end":{"line":101,"column":17}},"line":91},"10":{"name":"(anonymous_10)","decl":{"start":{"line":103,"column":33},"end":{"line":103,"column":34}},"loc":{"start":{"line":103,"column":43},"end":{"line":109,"column":17}},"line":103},"11":{"name":"(anonymous_11)","decl":{"start":{"line":111,"column":33},"end":{"line":111,"column":34}},"loc":{"start":{"line":111,"column":51},"end":{"line":116,"column":17}},"line":111},"12":{"name":"(anonymous_12)","decl":{"start":{"line":121,"column":15},"end":{"line":121,"column":21}},"loc":{"start":{"line":121,"column":21},"end":{"line":121,"column":null}},"line":121},"13":{"name":"(anonymous_13)","decl":{"start":{"line":124,"column":25},"end":{"line":124,"column":26}},"loc":{"start":{"line":124,"column":44},"end":{"line":129,"column":null}},"line":124},"14":{"name":"(anonymous_14)","decl":{"start":{"line":131,"column":23},"end":{"line":131,"column":24}},"loc":{"start":{"line":131,"column":39},"end":{"line":141,"column":null}},"line":131},"15":{"name":"(anonymous_15)","decl":{"start":{"line":134,"column":17},"end":{"line":134,"column":18}},"loc":{"start":{"line":134,"column":32},"end":{"line":134,"column":92}},"line":134},"16":{"name":"(anonymous_16)","decl":{"start":{"line":134,"column":46},"end":{"line":134,"column":47}},"loc":{"start":{"line":134,"column":54},"end":{"line":134,"column":91}},"line":134},"17":{"name":"(anonymous_17)","decl":{"start":{"line":140,"column":17},"end":{"line":140,"column":18}},"loc":{"start":{"line":140,"column":26},"end":{"line":140,"column":67}},"line":140},"18":{"name":"(anonymous_18)","decl":{"start":{"line":143,"column":23},"end":{"line":143,"column":24}},"loc":{"start":{"line":143,"column":39},"end":{"line":153,"column":null}},"line":143},"19":{"name":"(anonymous_19)","decl":{"start":{"line":146,"column":17},"end":{"line":146,"column":18}},"loc":{"start":{"line":146,"column":32},"end":{"line":146,"column":92}},"line":146},"20":{"name":"(anonymous_20)","decl":{"start":{"line":146,"column":46},"end":{"line":146,"column":47}},"loc":{"start":{"line":146,"column":54},"end":{"line":146,"column":91}},"line":146},"21":{"name":"(anonymous_21)","decl":{"start":{"line":152,"column":17},"end":{"line":152,"column":18}},"loc":{"start":{"line":152,"column":26},"end":{"line":152,"column":69}},"line":152},"22":{"name":"(anonymous_22)","decl":{"start":{"line":155,"column":14},"end":{"line":155,"column":20}},"loc":{"start":{"line":155,"column":20},"end":{"line":191,"column":7}},"line":155},"23":{"name":"(anonymous_23)","decl":{"start":{"line":157,"column":12},"end":{"line":157,"column":13}},"loc":{"start":{"line":157,"column":25},"end":{"line":178,"column":null}},"line":157},"24":{"name":"(anonymous_24)","decl":{"start":{"line":158,"column":32},"end":{"line":158,"column":33}},"loc":{"start":{"line":158,"column":43},"end":{"line":177,"column":17}},"line":158},"25":{"name":"(anonymous_25)","decl":{"start":{"line":182,"column":22},"end":{"line":182,"column":23}},"loc":{"start":{"line":182,"column":36},"end":{"line":185,"column":9}},"line":182},"26":{"name":"(anonymous_26)","decl":{"start":{"line":190,"column":15},"end":{"line":190,"column":21}},"loc":{"start":{"line":190,"column":21},"end":{"line":190,"column":null}},"line":190},"27":{"name":"(anonymous_27)","decl":{"start":{"line":203,"column":31},"end":{"line":203,"column":32}},"loc":{"start":{"line":204,"column":24},"end":{"line":212,"column":null}},"line":204},"28":{"name":"(anonymous_28)","decl":{"start":{"line":209,"column":37},"end":{"line":209,"column":43}},"loc":{"start":{"line":209,"column":43},"end":{"line":209,"column":null}},"line":209}},"branchMap":{"0":{"loc":{"start":{"line":10,"column":21},"end":{"line":10,"column":null}},"type":"binary-expr","locations":[{"start":{"line":10,"column":21},"end":{"line":10,"column":51}},{"start":{"line":10,"column":51},"end":{"line":10,"column":null}}],"line":10},"1":{"loc":{"start":{"line":19,"column":8},"end":{"line":24,"column":null}},"type":"if","locations":[{"start":{"line":19,"column":8},"end":{"line":24,"column":null}},{"start":{"line":21,"column":15},"end":{"line":24,"column":null}}],"line":19},"2":{"loc":{"start":{"line":30,"column":8},"end":{"line":34,"column":null}},"type":"if","locations":[{"start":{"line":30,"column":8},"end":{"line":34,"column":null}},{"start":{"line":32,"column":8},"end":{"line":34,"column":null}}],"line":30},"3":{"loc":{"start":{"line":32,"column":8},"end":{"line":34,"column":null}},"type":"if","locations":[{"start":{"line":32,"column":8},"end":{"line":34,"column":null}},{"start":{},"end":{}}],"line":32},"4":{"loc":{"start":{"line":36,"column":8},"end":{"line":38,"column":null}},"type":"if","locations":[{"start":{"line":36,"column":8},"end":{"line":38,"column":null}},{"start":{},"end":{}}],"line":36},"5":{"loc":{"start":{"line":41,"column":8},"end":{"line":49,"column":null}},"type":"if","locations":[{"start":{"line":41,"column":8},"end":{"line":49,"column":null}},{"start":{"line":43,"column":8},"end":{"line":49,"column":null}}],"line":41},"6":{"loc":{"start":{"line":43,"column":8},"end":{"line":49,"column":null}},"type":"if","locations":[{"start":{"line":43,"column":8},"end":{"line":49,"column":null}},{"start":{"line":46,"column":15},"end":{"line":49,"column":null}}],"line":43},"7":{"loc":{"start":{"line":52,"column":8},"end":{"line":54,"column":null}},"type":"if","locations":[{"start":{"line":52,"column":8},"end":{"line":54,"column":null}},{"start":{},"end":{}}],"line":52},"8":{"loc":{"start":{"line":58,"column":16},"end":{"line":60,"column":null}},"type":"if","locations":[{"start":{"line":58,"column":16},"end":{"line":60,"column":null}},{"start":{},"end":{}}],"line":58},"9":{"loc":{"start":{"line":64,"column":16},"end":{"line":68,"column":null}},"type":"if","locations":[{"start":{"line":64,"column":16},"end":{"line":68,"column":null}},{"start":{"line":66,"column":23},"end":{"line":68,"column":null}}],"line":64},"10":{"loc":{"start":{"line":88,"column":12},"end":{"line":88,"column":null}},"type":"if","locations":[{"start":{"line":88,"column":12},"end":{"line":88,"column":null}},{"start":{},"end":{}}],"line":88},"11":{"loc":{"start":{"line":90,"column":12},"end":{"line":117,"column":null}},"type":"if","locations":[{"start":{"line":90,"column":12},"end":{"line":117,"column":null}},{"start":{"line":102,"column":12},"end":{"line":117,"column":null}}],"line":90},"12":{"loc":{"start":{"line":93,"column":20},"end":{"line":99,"column":null}},"type":"if","locations":[{"start":{"line":93,"column":20},"end":{"line":99,"column":null}},{"start":{},"end":{}}],"line":93},"13":{"loc":{"start":{"line":95,"column":24},"end":{"line":97,"column":null}},"type":"if","locations":[{"start":{"line":95,"column":24},"end":{"line":97,"column":null}},{"start":{},"end":{}}],"line":95},"14":{"loc":{"start":{"line":102,"column":12},"end":{"line":117,"column":null}},"type":"if","locations":[{"start":{"line":102,"column":12},"end":{"line":117,"column":null}},{"start":{"line":110,"column":12},"end":{"line":117,"column":null}}],"line":102},"15":{"loc":{"start":{"line":105,"column":20},"end":{"line":107,"column":null}},"type":"if","locations":[{"start":{"line":105,"column":20},"end":{"line":107,"column":null}},{"start":{},"end":{}}],"line":105},"16":{"loc":{"start":{"line":110,"column":12},"end":{"line":117,"column":null}},"type":"if","locations":[{"start":{"line":110,"column":12},"end":{"line":117,"column":null}},{"start":{},"end":{}}],"line":110},"17":{"loc":{"start":{"line":112,"column":20},"end":{"line":114,"column":null}},"type":"if","locations":[{"start":{"line":112,"column":20},"end":{"line":114,"column":null}},{"start":{},"end":{}}],"line":112},"18":{"loc":{"start":{"line":112,"column":24},"end":{"line":112,"column":74}},"type":"binary-expr","locations":[{"start":{"line":112,"column":24},"end":{"line":112,"column":45}},{"start":{"line":112,"column":45},"end":{"line":112,"column":74}}],"line":112},"19":{"loc":{"start":{"line":126,"column":8},"end":{"line":128,"column":null}},"type":"if","locations":[{"start":{"line":126,"column":8},"end":{"line":128,"column":null}},{"start":{},"end":{}}],"line":126},"20":{"loc":{"start":{"line":134,"column":54},"end":{"line":134,"column":91}},"type":"cond-expr","locations":[{"start":{"line":134,"column":75},"end":{"line":134,"column":89}},{"start":{"line":134,"column":89},"end":{"line":134,"column":91}}],"line":134},"21":{"loc":{"start":{"line":146,"column":54},"end":{"line":146,"column":91}},"type":"cond-expr","locations":[{"start":{"line":146,"column":75},"end":{"line":146,"column":89}},{"start":{"line":146,"column":89},"end":{"line":146,"column":91}}],"line":146},"22":{"loc":{"start":{"line":160,"column":20},"end":{"line":165,"column":null}},"type":"if","locations":[{"start":{"line":160,"column":20},"end":{"line":165,"column":null}},{"start":{},"end":{}}],"line":160},"23":{"loc":{"start":{"line":161,"column":24},"end":{"line":163,"column":null}},"type":"if","locations":[{"start":{"line":161,"column":24},"end":{"line":163,"column":null}},{"start":{},"end":{}}],"line":161},"24":{"loc":{"start":{"line":161,"column":28},"end":{"line":161,"column":97}},"type":"binary-expr","locations":[{"start":{"line":161,"column":28},"end":{"line":161,"column":52}},{"start":{"line":161,"column":52},"end":{"line":161,"column":68}},{"start":{"line":161,"column":68},"end":{"line":161,"column":79}},{"start":{"line":161,"column":79},"end":{"line":161,"column":97}}],"line":161},"25":{"loc":{"start":{"line":168,"column":20},"end":{"line":176,"column":null}},"type":"if","locations":[{"start":{"line":168,"column":20},"end":{"line":176,"column":null}},{"start":{},"end":{}}],"line":168},"26":{"loc":{"start":{"line":168,"column":24},"end":{"line":168,"column":83}},"type":"binary-expr","locations":[{"start":{"line":168,"column":24},"end":{"line":168,"column":49}},{"start":{"line":168,"column":49},"end":{"line":168,"column":83}}],"line":168},"27":{"loc":{"start":{"line":170,"column":24},"end":{"line":175,"column":null}},"type":"if","locations":[{"start":{"line":170,"column":24},"end":{"line":175,"column":null}},{"start":{},"end":{}}],"line":170},"28":{"loc":{"start":{"line":170,"column":28},"end":{"line":170,"column":81}},"type":"binary-expr","locations":[{"start":{"line":170,"column":28},"end":{"line":170,"column":45}},{"start":{"line":170,"column":45},"end":{"line":170,"column":59}},{"start":{"line":170,"column":59},"end":{"line":170,"column":81}}],"line":170},"29":{"loc":{"start":{"line":172,"column":28},"end":{"line":174,"column":null}},"type":"if","locations":[{"start":{"line":172,"column":28},"end":{"line":174,"column":null}},{"start":{},"end":{}}],"line":172},"30":{"loc":{"start":{"line":184,"column":12},"end":{"line":184,"column":null}},"type":"if","locations":[{"start":{"line":184,"column":12},"end":{"line":184,"column":null}},{"start":{},"end":{}}],"line":184},"31":{"loc":{"start":{"line":188,"column":8},"end":{"line":188,"column":null}},"type":"if","locations":[{"start":{"line":188,"column":8},"end":{"line":188,"column":null}},{"start":{},"end":{}}],"line":188},"32":{"loc":{"start":{"line":193,"column":4},"end":{"line":193,"column":null}},"type":"if","locations":[{"start":{"line":193,"column":4},"end":{"line":193,"column":null}},{"start":{},"end":{}}],"line":193},"33":{"loc":{"start":{"line":194,"column":4},"end":{"line":194,"column":null}},"type":"if","locations":[{"start":{"line":194,"column":4},"end":{"line":194,"column":null}},{"start":{},"end":{}}],"line":194},"34":{"loc":{"start":{"line":199,"column":13},"end":{"line":219,"column":null}},"type":"cond-expr","locations":[{"start":{"line":200,"column":16},"end":{"line":200,"column":null}},{"start":{"line":202,"column":16},"end":{"line":219,"column":null}}],"line":199},"35":{"loc":{"start":{"line":208,"column":39},"end":{"line":208,"column":null}},"type":"cond-expr","locations":[{"start":{"line":208,"column":65},"end":{"line":208,"column":93}},{"start":{"line":208,"column":93},"end":{"line":208,"column":null}}],"line":208},"36":{"loc":{"start":{"line":214,"column":21},"end":{"line":217,"column":null}},"type":"binary-expr","locations":[{"start":{"line":214,"column":21},"end":{"line":214,"column":null}},{"start":{"line":215,"column":24},"end":{"line":217,"column":null}}],"line":214},"37":{"loc":{"start":{"line":216,"column":29},"end":{"line":216,"column":null}},"type":"cond-expr","locations":[{"start":{"line":216,"column":43},"end":{"line":216,"column":63}},{"start":{"line":216,"column":63},"end":{"line":216,"column":null}}],"line":216}},"s":{"0":27,"1":27,"2":27,"3":27,"4":27,"5":27,"6":27,"7":27,"8":27,"9":8,"10":1,"11":7,"12":7,"13":8,"14":8,"15":8,"16":8,"17":2,"18":6,"19":1,"20":8,"21":1,"22":8,"23":0,"24":8,"25":0,"26":0,"27":8,"28":8,"29":8,"30":8,"31":8,"32":7,"33":0,"34":7,"35":6,"36":1,"37":1,"38":5,"39":6,"40":6,"41":6,"42":1,"43":1,"44":1,"45":27,"46":7,"47":27,"48":27,"49":23,"50":3,"51":0,"52":3,"53":2,"54":2,"55":2,"56":2,"57":2,"58":1,"59":2,"60":2,"61":1,"62":0,"63":0,"64":0,"65":0,"66":0,"67":1,"68":1,"69":1,"70":1,"71":1,"72":23,"73":23,"74":23,"75":27,"76":2,"77":2,"78":2,"79":27,"80":2,"81":2,"82":2,"83":3,"84":2,"85":0,"86":27,"87":1,"88":1,"89":1,"90":2,"91":1,"92":0,"93":27,"94":24,"95":2,"96":2,"97":1,"98":1,"99":1,"100":1,"101":1,"102":1,"103":1,"104":1,"105":1,"106":24,"107":15,"108":15,"109":15,"110":24,"111":24,"112":10,"113":24,"114":24,"115":27,"116":13,"117":14,"118":14,"119":13,"120":21,"121":0},"f":{"0":27,"1":8,"2":7,"3":6,"4":1,"5":1,"6":7,"7":23,"8":3,"9":2,"10":0,"11":1,"12":23,"13":2,"14":2,"15":2,"16":3,"17":0,"18":1,"19":1,"20":2,"21":0,"22":24,"23":2,"24":2,"25":15,"26":24,"27":21,"28":0},"b":{"0":[27,27],"1":[1,7],"2":[2,6],"3":[1,5],"4":[1,7],"5":[0,8],"6":[0,8],"7":[8,0],"8":[0,7],"9":[1,5],"10":[0,3],"11":[2,1],"12":[2,0],"13":[1,1],"14":[0,1],"15":[0,0],"16":[1,0],"17":[1,0],"18":[1,1],"19":[2,0],"20":[2,1],"21":[1,1],"22":[1,1],"23":[1,0],"24":[1,1,1,1],"25":[1,0],"26":[1,1],"27":[1,0],"28":[1,1,1],"29":[1,0],"30":[15,0],"31":[10,14],"32":[13,14],"33":[1,13],"34":[0,13],"35":[5,16],"36":[13,13],"37":[1,12]},"meta":{"lastBranch":38,"lastFunction":29,"lastStatement":122,"seen":{"f:7:24:7:36":0,"s:8:28:8:Infinity":0,"s:9:23:9:Infinity":1,"s:10:21:10:Infinity":2,"b:10:21:10:51:10:51:10:Infinity":0,"s:12:26:12:Infinity":3,"s:13:30:13:Infinity":4,"s:14:38:14:Infinity":5,"s:15:30:15:Infinity":6,"s:16:26:16:Infinity":7,"s:18:23:78:Infinity":8,"f:18:23:18:24":1,"b:19:8:24:Infinity:21:15:24:Infinity":1,"s:19:8:24:Infinity":9,"s:20:12:20:Infinity":10,"s:22:12:22:Infinity":11,"s:23:12:23:Infinity":12,"s:25:8:25:Infinity":13,"s:27:18:27:Infinity":14,"s:28:23:28:Infinity":15,"b:30:8:34:Infinity:32:8:34:Infinity":2,"s:30:8:34:Infinity":16,"s:31:12:31:Infinity":17,"b:32:8:34:Infinity:undefined:undefined:undefined:undefined":3,"s:32:8:34:Infinity":18,"s:33:12:33:Infinity":19,"b:36:8:38:Infinity:undefined:undefined:undefined:undefined":4,"s:36:8:38:Infinity":20,"s:37:12:37:Infinity":21,"b:41:8:49:Infinity:43:8:49:Infinity":5,"s:41:8:49:Infinity":22,"s:42:12:42:Infinity":23,"b:43:8:49:Infinity:46:15:49:Infinity":6,"s:43:8:49:Infinity":24,"s:44:12:44:Infinity":25,"s:45:12:45:Infinity":26,"s:48:12:48:Infinity":27,"s:51:28:51:Infinity":28,"b:52:8:54:Infinity:undefined:undefined:undefined:undefined":7,"s:52:8:54:Infinity":29,"s:53:12:53:Infinity":30,"s:56:8:77:Infinity":31,"f:57:18:57:19":2,"b:58:16:60:Infinity:undefined:undefined:undefined:undefined":8,"s:58:16:60:Infinity":32,"s:59:20:59:Infinity":33,"s:61:16:61:Infinity":34,"f:63:18:63:19":3,"b:64:16:68:Infinity:66:23:68:Infinity":9,"s:64:16:68:Infinity":35,"s:65:20:65:Infinity":36,"f:65:29:65:30":4,"s:65:39:65:57":37,"s:67:20:67:Infinity":38,"s:69:16:69:Infinity":39,"s:70:16:70:Infinity":40,"s:71:16:71:Infinity":41,"f:73:19:73:20":5,"s:74:16:74:Infinity":42,"s:75:16:75:Infinity":43,"s:76:16:76:Infinity":44,"s:80:4:82:Infinity":45,"f:80:14:80:20":6,"s:81:8:81:Infinity":46,"s:84:42:84:Infinity":47,"s:86:4:122:Infinity":48,"f:86:14:86:20":7,"s:87:30:118:Infinity":49,"f:87:30:87:31":8,"b:88:12:88:Infinity:undefined:undefined:undefined:undefined":10,"s:88:12:88:Infinity":50,"s:88:36:88:Infinity":51,"b:90:12:117:Infinity:102:12:117:Infinity":11,"s:90:12:117:Infinity":52,"s:91:16:101:Infinity":53,"f:91:33:91:34":9,"s:92:38:92:Infinity":54,"b:93:20:99:Infinity:undefined:undefined:undefined:undefined":12,"s:93:20:99:Infinity":55,"s:94:37:94:Infinity":56,"b:95:24:97:Infinity:undefined:undefined:undefined:undefined":13,"s:95:24:97:Infinity":57,"s:96:28:96:Infinity":58,"s:98:24:98:Infinity":59,"s:100:20:100:Infinity":60,"b:102:12:117:Infinity:110:12:117:Infinity":14,"s:102:12:117:Infinity":61,"s:103:16:109:Infinity":62,"f:103:33:103:34":10,"s:104:38:104:Infinity":63,"b:105:20:107:Infinity:undefined:undefined:undefined:undefined":15,"s:105:20:107:Infinity":64,"s:106:24:106:Infinity":65,"s:108:20:108:Infinity":66,"b:110:12:117:Infinity:undefined:undefined:undefined:undefined":16,"s:110:12:117:Infinity":67,"s:111:16:116:Infinity":68,"f:111:33:111:34":11,"b:112:20:114:Infinity:undefined:undefined:undefined:undefined":17,"s:112:20:114:Infinity":69,"b:112:24:112:45:112:45:112:74":18,"s:113:24:113:Infinity":70,"s:115:20:115:Infinity":71,"s:120:8:120:Infinity":72,"s:121:8:121:Infinity":73,"f:121:15:121:21":12,"s:121:21:121:Infinity":74,"s:124:25:129:Infinity":75,"f:124:25:124:26":13,"s:125:24:125:Infinity":76,"b:126:8:128:Infinity:undefined:undefined:undefined:undefined":19,"s:126:8:128:Infinity":77,"s:127:12:127:Infinity":78,"s:131:23:141:Infinity":79,"f:131:23:131:24":14,"s:132:28:132:Infinity":80,"s:134:8:134:Infinity":81,"f:134:17:134:18":15,"s:134:32:134:92":82,"f:134:46:134:47":16,"s:134:54:134:91":83,"b:134:75:134:89:134:89:134:91":20,"s:136:8:140:Infinity":84,"f:140:17:140:18":17,"s:140:26:140:67":85,"s:143:23:153:Infinity":86,"f:143:23:143:24":18,"s:144:28:144:Infinity":87,"s:146:8:146:Infinity":88,"f:146:17:146:18":19,"s:146:32:146:92":89,"f:146:46:146:47":20,"s:146:54:146:91":90,"b:146:75:146:89:146:89:146:91":21,"s:148:8:152:Infinity":91,"f:152:17:152:18":21,"s:152:26:152:69":92,"s:155:4:191:Infinity":93,"f:155:14:155:20":22,"s:156:25:180:Infinity":94,"f:157:12:157:13":23,"s:158:16:177:Infinity":95,"f:158:32:158:33":24,"b:160:20:165:Infinity:undefined:undefined:undefined:undefined":22,"s:160:20:165:Infinity":96,"b:161:24:163:Infinity:undefined:undefined:undefined:undefined":23,"s:161:24:163:Infinity":97,"b:161:28:161:52:161:52:161:68:161:68:161:79:161:79:161:97":24,"s:162:28:162:Infinity":98,"s:164:24:164:Infinity":99,"b:168:20:176:Infinity:undefined:undefined:undefined:undefined":25,"s:168:20:176:Infinity":100,"b:168:24:168:49:168:49:168:83":26,"s:169:38:169:Infinity":101,"b:170:24:175:Infinity:undefined:undefined:undefined:undefined":27,"s:170:24:175:Infinity":102,"b:170:28:170:45:170:45:170:59:170:59:170:81":28,"s:171:41:171:Infinity":103,"b:172:28:174:Infinity:undefined:undefined:undefined:undefined":29,"s:172:28:174:Infinity":104,"s:173:32:173:Infinity":105,"s:182:8:185:Infinity":106,"f:182:22:182:23":25,"s:183:23:183:Infinity":107,"b:184:12:184:Infinity:undefined:undefined:undefined:undefined":30,"s:184:12:184:Infinity":108,"s:184:20:184:Infinity":109,"s:187:25:187:Infinity":110,"b:188:8:188:Infinity:undefined:undefined:undefined:undefined":31,"s:188:8:188:Infinity":111,"s:188:22:188:Infinity":112,"s:190:8:190:Infinity":113,"f:190:15:190:21":26,"s:190:21:190:Infinity":114,"b:193:4:193:Infinity:undefined:undefined:undefined:undefined":32,"s:193:4:193:Infinity":115,"s:193:17:193:Infinity":116,"b:194:4:194:Infinity:undefined:undefined:undefined:undefined":33,"s:194:4:194:Infinity":117,"s:194:15:194:Infinity":118,"s:197:4:221:Infinity":119,"b:200:16:200:Infinity:202:16:219:Infinity":34,"f:203:31:203:32":27,"s:204:24:212:Infinity":120,"b:208:65:208:93:208:93:208:Infinity":35,"f:209:37:209:43":28,"s:209:43:209:Infinity":121,"b:214:21:214:Infinity:215:24:217:Infinity":36,"b:216:43:216:63:216:63:216:Infinity":37}}} -,"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedList.css": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedList.css","statementMap":{},"fnMap":{},"branchMap":{},"s":{},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":0,"seen":{}}} -,"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedList.tsx": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedList.tsx","statementMap":{"0":{"start":{"line":7,"column":26},"end":{"line":7,"column":null}},"1":{"start":{"line":8,"column":24},"end":{"line":8,"column":null}},"2":{"start":{"line":9,"column":30},"end":{"line":9,"column":null}},"3":{"start":{"line":10,"column":26},"end":{"line":10,"column":null}},"4":{"start":{"line":12,"column":4},"end":{"line":32,"column":null}},"5":{"start":{"line":13,"column":8},"end":{"line":31,"column":null}},"6":{"start":{"line":15,"column":16},"end":{"line":15,"column":null}},"7":{"start":{"line":15,"column":29},"end":{"line":15,"column":null}},"8":{"start":{"line":16,"column":16},"end":{"line":16,"column":null}},"9":{"start":{"line":19,"column":16},"end":{"line":19,"column":null}},"10":{"start":{"line":19,"column":29},"end":{"line":19,"column":null}},"11":{"start":{"line":20,"column":16},"end":{"line":20,"column":null}},"12":{"start":{"line":24,"column":16},"end":{"line":24,"column":null}},"13":{"start":{"line":25,"column":16},"end":{"line":25,"column":null}},"14":{"start":{"line":26,"column":16},"end":{"line":26,"column":null}},"15":{"start":{"line":29,"column":16},"end":{"line":29,"column":null}},"16":{"start":{"line":30,"column":16},"end":{"line":30,"column":null}},"17":{"start":{"line":34,"column":4},"end":{"line":34,"column":null}},"18":{"start":{"line":34,"column":17},"end":{"line":34,"column":null}},"19":{"start":{"line":35,"column":4},"end":{"line":35,"column":null}},"20":{"start":{"line":35,"column":15},"end":{"line":35,"column":null}},"21":{"start":{"line":37,"column":4},"end":{"line":78,"column":null}},"22":{"start":{"line":53,"column":28},"end":{"line":58,"column":null}},"23":{"start":{"line":69,"column":28},"end":{"line":73,"column":null}}},"fnMap":{"0":{"name":"FeedList","decl":{"start":{"line":6,"column":24},"end":{"line":6,"column":35}},"loc":{"start":{"line":6,"column":35},"end":{"line":80,"column":null}},"line":6},"1":{"name":"(anonymous_1)","decl":{"start":{"line":12,"column":14},"end":{"line":12,"column":20}},"loc":{"start":{"line":12,"column":20},"end":{"line":32,"column":7}},"line":12},"2":{"name":"(anonymous_2)","decl":{"start":{"line":14,"column":37},"end":{"line":14,"column":44}},"loc":{"start":{"line":14,"column":44},"end":{"line":17,"column":13}},"line":14},"3":{"name":"(anonymous_3)","decl":{"start":{"line":18,"column":35},"end":{"line":18,"column":42}},"loc":{"start":{"line":18,"column":42},"end":{"line":21,"column":13}},"line":18},"4":{"name":"(anonymous_4)","decl":{"start":{"line":23,"column":18},"end":{"line":23,"column":19}},"loc":{"start":{"line":23,"column":45},"end":{"line":27,"column":13}},"line":23},"5":{"name":"(anonymous_5)","decl":{"start":{"line":28,"column":19},"end":{"line":28,"column":20}},"loc":{"start":{"line":28,"column":28},"end":{"line":31,"column":13}},"line":28},"6":{"name":"(anonymous_6)","decl":{"start":{"line":52,"column":35},"end":{"line":52,"column":36}},"loc":{"start":{"line":53,"column":28},"end":{"line":58,"column":null}},"line":53},"7":{"name":"(anonymous_7)","decl":{"start":{"line":68,"column":34},"end":{"line":68,"column":35}},"loc":{"start":{"line":69,"column":28},"end":{"line":73,"column":null}},"line":69}},"branchMap":{"0":{"loc":{"start":{"line":15,"column":16},"end":{"line":15,"column":null}},"type":"if","locations":[{"start":{"line":15,"column":16},"end":{"line":15,"column":null}},{"start":{},"end":{}}],"line":15},"1":{"loc":{"start":{"line":19,"column":16},"end":{"line":19,"column":null}},"type":"if","locations":[{"start":{"line":19,"column":16},"end":{"line":19,"column":null}},{"start":{},"end":{}}],"line":19},"2":{"loc":{"start":{"line":34,"column":4},"end":{"line":34,"column":null}},"type":"if","locations":[{"start":{"line":34,"column":4},"end":{"line":34,"column":null}},{"start":{},"end":{}}],"line":34},"3":{"loc":{"start":{"line":35,"column":4},"end":{"line":35,"column":null}},"type":"if","locations":[{"start":{"line":35,"column":4},"end":{"line":35,"column":null}},{"start":{},"end":{}}],"line":35},"4":{"loc":{"start":{"line":48,"column":17},"end":{"line":60,"column":null}},"type":"cond-expr","locations":[{"start":{"line":49,"column":20},"end":{"line":49,"column":null}},{"start":{"line":51,"column":20},"end":{"line":60,"column":null}}],"line":48},"5":{"loc":{"start":{"line":55,"column":37},"end":{"line":55,"column":null}},"type":"binary-expr","locations":[{"start":{"line":55,"column":37},"end":{"line":55,"column":51}},{"start":{"line":55,"column":51},"end":{"line":55,"column":null}}],"line":55},"6":{"loc":{"start":{"line":57,"column":33},"end":{"line":57,"column":null}},"type":"binary-expr","locations":[{"start":{"line":57,"column":33},"end":{"line":57,"column":50}},{"start":{"line":57,"column":50},"end":{"line":57,"column":null}}],"line":57},"7":{"loc":{"start":{"line":64,"column":13},"end":{"line":76,"column":null}},"type":"binary-expr","locations":[{"start":{"line":64,"column":13},"end":{"line":64,"column":21}},{"start":{"line":64,"column":21},"end":{"line":64,"column":null}},{"start":{"line":65,"column":16},"end":{"line":76,"column":null}}],"line":64}},"s":{"0":11,"1":11,"2":11,"3":11,"4":11,"5":6,"6":4,"7":0,"8":4,"9":4,"10":0,"11":4,"12":4,"13":4,"14":4,"15":1,"16":1,"17":11,"18":6,"19":5,"20":5,"21":4,"22":3,"23":3},"f":{"0":11,"1":6,"2":4,"3":4,"4":4,"5":1,"6":3,"7":3},"b":{"0":[0,4],"1":[0,4],"2":[6,5],"3":[1,4],"4":[2,2],"5":[3,0],"6":[3,3],"7":[11,4,2]},"meta":{"lastBranch":8,"lastFunction":8,"lastStatement":24,"seen":{"f:6:24:6:35":0,"s:7:26:7:Infinity":0,"s:8:24:8:Infinity":1,"s:9:30:9:Infinity":2,"s:10:26:10:Infinity":3,"s:12:4:32:Infinity":4,"f:12:14:12:20":1,"s:13:8:31:Infinity":5,"f:14:37:14:44":2,"b:15:16:15:Infinity:undefined:undefined:undefined:undefined":0,"s:15:16:15:Infinity":6,"s:15:29:15:Infinity":7,"s:16:16:16:Infinity":8,"f:18:35:18:42":3,"b:19:16:19:Infinity:undefined:undefined:undefined:undefined":1,"s:19:16:19:Infinity":9,"s:19:29:19:Infinity":10,"s:20:16:20:Infinity":11,"f:23:18:23:19":4,"s:24:16:24:Infinity":12,"s:25:16:25:Infinity":13,"s:26:16:26:Infinity":14,"f:28:19:28:20":5,"s:29:16:29:Infinity":15,"s:30:16:30:Infinity":16,"b:34:4:34:Infinity:undefined:undefined:undefined:undefined":2,"s:34:4:34:Infinity":17,"s:34:17:34:Infinity":18,"b:35:4:35:Infinity:undefined:undefined:undefined:undefined":3,"s:35:4:35:Infinity":19,"s:35:15:35:Infinity":20,"s:37:4:78:Infinity":21,"b:49:20:49:Infinity:51:20:60:Infinity":4,"f:52:35:52:36":6,"s:53:28:58:Infinity":22,"b:55:37:55:51:55:51:55:Infinity":5,"b:57:33:57:50:57:50:57:Infinity":6,"b:64:13:64:21:64:21:64:Infinity:65:16:76:Infinity":7,"f:68:34:68:35":7,"s:69:28:73:Infinity":23}}} -,"/Users/adam/workspace/vibecode/neko/frontend/src/components/Login.css": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/Login.css","statementMap":{},"fnMap":{},"branchMap":{},"s":{},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":0,"seen":{}}} -,"/Users/adam/workspace/vibecode/neko/frontend/src/components/Login.tsx": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/Login.tsx","statementMap":{"0":{"start":{"line":6,"column":32},"end":{"line":6,"column":null}},"1":{"start":{"line":7,"column":26},"end":{"line":7,"column":null}},"2":{"start":{"line":8,"column":10},"end":{"line":8,"column":null}},"3":{"start":{"line":10,"column":25},"end":{"line":33,"column":null}},"4":{"start":{"line":11,"column":8},"end":{"line":11,"column":null}},"5":{"start":{"line":12,"column":8},"end":{"line":12,"column":null}},"6":{"start":{"line":14,"column":8},"end":{"line":32,"column":null}},"7":{"start":{"line":16,"column":27},"end":{"line":16,"column":null}},"8":{"start":{"line":17,"column":12},"end":{"line":17,"column":null}},"9":{"start":{"line":19,"column":24},"end":{"line":22,"column":null}},"10":{"start":{"line":24,"column":12},"end":{"line":29,"column":null}},"11":{"start":{"line":25,"column":16},"end":{"line":25,"column":null}},"12":{"start":{"line":27,"column":29},"end":{"line":27,"column":null}},"13":{"start":{"line":28,"column":16},"end":{"line":28,"column":null}},"14":{"start":{"line":31,"column":12},"end":{"line":31,"column":null}},"15":{"start":{"line":35,"column":4},"end":{"line":52,"column":null}},"16":{"start":{"line":45,"column":41},"end":{"line":45,"column":null}}},"fnMap":{"0":{"name":"Login","decl":{"start":{"line":5,"column":24},"end":{"line":5,"column":32}},"loc":{"start":{"line":5,"column":32},"end":{"line":54,"column":null}},"line":5},"1":{"name":"(anonymous_1)","decl":{"start":{"line":10,"column":25},"end":{"line":10,"column":32}},"loc":{"start":{"line":10,"column":49},"end":{"line":33,"column":null}},"line":10},"2":{"name":"(anonymous_2)","decl":{"start":{"line":45,"column":34},"end":{"line":45,"column":35}},"loc":{"start":{"line":45,"column":41},"end":{"line":45,"column":null}},"line":45}},"branchMap":{"0":{"loc":{"start":{"line":24,"column":12},"end":{"line":29,"column":null}},"type":"if","locations":[{"start":{"line":24,"column":12},"end":{"line":29,"column":null}},{"start":{"line":26,"column":19},"end":{"line":29,"column":null}}],"line":24},"1":{"loc":{"start":{"line":28,"column":25},"end":{"line":28,"column":55}},"type":"binary-expr","locations":[{"start":{"line":28,"column":25},"end":{"line":28,"column":41}},{"start":{"line":28,"column":41},"end":{"line":28,"column":55}}],"line":28},"2":{"loc":{"start":{"line":49,"column":17},"end":{"line":49,"column":null}},"type":"binary-expr","locations":[{"start":{"line":49,"column":17},"end":{"line":49,"column":26}},{"start":{"line":49,"column":26},"end":{"line":49,"column":null}}],"line":49}},"s":{"0":14,"1":14,"2":14,"3":14,"4":3,"5":3,"6":3,"7":3,"8":3,"9":3,"10":2,"11":1,"12":1,"13":1,"14":1,"15":14,"16":3},"f":{"0":14,"1":3,"2":3},"b":{"0":[1,1],"1":[1,0],"2":[14,2]},"meta":{"lastBranch":3,"lastFunction":3,"lastStatement":17,"seen":{"f:5:24:5:32":0,"s:6:32:6:Infinity":0,"s:7:26:7:Infinity":1,"s:8:10:8:Infinity":2,"s:10:25:33:Infinity":3,"f:10:25:10:32":1,"s:11:8:11:Infinity":4,"s:12:8:12:Infinity":5,"s:14:8:32:Infinity":6,"s:16:27:16:Infinity":7,"s:17:12:17:Infinity":8,"s:19:24:22:Infinity":9,"b:24:12:29:Infinity:26:19:29:Infinity":0,"s:24:12:29:Infinity":10,"s:25:16:25:Infinity":11,"s:27:29:27:Infinity":12,"s:28:16:28:Infinity":13,"b:28:25:28:41:28:41:28:55":1,"s:31:12:31:Infinity":14,"s:35:4:52:Infinity":15,"f:45:34:45:35":2,"s:45:41:45:Infinity":16,"b:49:17:49:26:49:26:49:Infinity":2}}} -,"/Users/adam/workspace/vibecode/neko/frontend/src/components/Settings.css": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/Settings.css","statementMap":{},"fnMap":{},"branchMap":{},"s":{},"f":{},"b":{},"meta":{"lastBranch":0,"lastFunction":0,"lastStatement":0,"seen":{}}} -,"/Users/adam/workspace/vibecode/neko/frontend/src/components/Settings.tsx": {"path":"/Users/adam/workspace/vibecode/neko/frontend/src/components/Settings.tsx","statementMap":{"0":{"start":{"line":6,"column":26},"end":{"line":6,"column":null}},"1":{"start":{"line":7,"column":36},"end":{"line":7,"column":null}},"2":{"start":{"line":8,"column":30},"end":{"line":8,"column":null}},"3":{"start":{"line":9,"column":26},"end":{"line":9,"column":null}},"4":{"start":{"line":11,"column":4},"end":{"line":13,"column":null}},"5":{"start":{"line":12,"column":8},"end":{"line":12,"column":null}},"6":{"start":{"line":15,"column":23},"end":{"line":30,"column":null}},"7":{"start":{"line":16,"column":8},"end":{"line":16,"column":null}},"8":{"start":{"line":17,"column":8},"end":{"line":29,"column":null}},"9":{"start":{"line":19,"column":16},"end":{"line":19,"column":null}},"10":{"start":{"line":19,"column":29},"end":{"line":19,"column":null}},"11":{"start":{"line":20,"column":16},"end":{"line":20,"column":null}},"12":{"start":{"line":23,"column":16},"end":{"line":23,"column":null}},"13":{"start":{"line":24,"column":16},"end":{"line":24,"column":null}},"14":{"start":{"line":27,"column":16},"end":{"line":27,"column":null}},"15":{"start":{"line":28,"column":16},"end":{"line":28,"column":null}},"16":{"start":{"line":32,"column":26},"end":{"line":54,"column":null}},"17":{"start":{"line":33,"column":8},"end":{"line":33,"column":null}},"18":{"start":{"line":34,"column":8},"end":{"line":34,"column":null}},"19":{"start":{"line":34,"column":25},"end":{"line":34,"column":null}},"20":{"start":{"line":36,"column":8},"end":{"line":36,"column":null}},"21":{"start":{"line":37,"column":8},"end":{"line":53,"column":null}},"22":{"start":{"line":43,"column":16},"end":{"line":43,"column":null}},"23":{"start":{"line":43,"column":29},"end":{"line":43,"column":null}},"24":{"start":{"line":44,"column":16},"end":{"line":44,"column":null}},"25":{"start":{"line":47,"column":16},"end":{"line":47,"column":null}},"26":{"start":{"line":48,"column":16},"end":{"line":48,"column":null}},"27":{"start":{"line":51,"column":16},"end":{"line":51,"column":null}},"28":{"start":{"line":52,"column":16},"end":{"line":52,"column":null}},"29":{"start":{"line":56,"column":29},"end":{"line":72,"column":null}},"30":{"start":{"line":57,"column":8},"end":{"line":57,"column":null}},"31":{"start":{"line":57,"column":79},"end":{"line":57,"column":null}},"32":{"start":{"line":59,"column":8},"end":{"line":59,"column":null}},"33":{"start":{"line":60,"column":8},"end":{"line":71,"column":null}},"34":{"start":{"line":64,"column":16},"end":{"line":64,"column":null}},"35":{"start":{"line":64,"column":29},"end":{"line":64,"column":null}},"36":{"start":{"line":65,"column":16},"end":{"line":65,"column":null}},"37":{"start":{"line":65,"column":45},"end":{"line":65,"column":57}},"38":{"start":{"line":66,"column":16},"end":{"line":66,"column":null}},"39":{"start":{"line":69,"column":16},"end":{"line":69,"column":null}},"40":{"start":{"line":70,"column":16},"end":{"line":70,"column":null}},"41":{"start":{"line":74,"column":4},"end":{"line":119,"column":null}},"42":{"start":{"line":84,"column":41},"end":{"line":84,"column":null}},"43":{"start":{"line":102,"column":24},"end":{"line":115,"column":null}},"44":{"start":{"line":108,"column":47},"end":{"line":108,"column":null}}},"fnMap":{"0":{"name":"Settings","decl":{"start":{"line":5,"column":24},"end":{"line":5,"column":35}},"loc":{"start":{"line":5,"column":35},"end":{"line":121,"column":null}},"line":5},"1":{"name":"(anonymous_1)","decl":{"start":{"line":11,"column":14},"end":{"line":11,"column":20}},"loc":{"start":{"line":11,"column":20},"end":{"line":13,"column":7}},"line":11},"2":{"name":"(anonymous_2)","decl":{"start":{"line":15,"column":23},"end":{"line":15,"column":29}},"loc":{"start":{"line":15,"column":29},"end":{"line":30,"column":null}},"line":15},"3":{"name":"(anonymous_3)","decl":{"start":{"line":18,"column":18},"end":{"line":18,"column":19}},"loc":{"start":{"line":18,"column":27},"end":{"line":21,"column":13}},"line":18},"4":{"name":"(anonymous_4)","decl":{"start":{"line":22,"column":18},"end":{"line":22,"column":19}},"loc":{"start":{"line":22,"column":28},"end":{"line":25,"column":13}},"line":22},"5":{"name":"(anonymous_5)","decl":{"start":{"line":26,"column":19},"end":{"line":26,"column":20}},"loc":{"start":{"line":26,"column":28},"end":{"line":29,"column":13}},"line":26},"6":{"name":"(anonymous_6)","decl":{"start":{"line":32,"column":26},"end":{"line":32,"column":27}},"loc":{"start":{"line":32,"column":50},"end":{"line":54,"column":null}},"line":32},"7":{"name":"(anonymous_7)","decl":{"start":{"line":42,"column":18},"end":{"line":42,"column":19}},"loc":{"start":{"line":42,"column":27},"end":{"line":45,"column":13}},"line":42},"8":{"name":"(anonymous_8)","decl":{"start":{"line":46,"column":18},"end":{"line":46,"column":24}},"loc":{"start":{"line":46,"column":24},"end":{"line":49,"column":13}},"line":46},"9":{"name":"(anonymous_9)","decl":{"start":{"line":50,"column":19},"end":{"line":50,"column":20}},"loc":{"start":{"line":50,"column":28},"end":{"line":53,"column":13}},"line":50},"10":{"name":"(anonymous_10)","decl":{"start":{"line":56,"column":29},"end":{"line":56,"column":30}},"loc":{"start":{"line":56,"column":45},"end":{"line":72,"column":null}},"line":56},"11":{"name":"(anonymous_11)","decl":{"start":{"line":63,"column":18},"end":{"line":63,"column":19}},"loc":{"start":{"line":63,"column":27},"end":{"line":67,"column":13}},"line":63},"12":{"name":"(anonymous_12)","decl":{"start":{"line":65,"column":38},"end":{"line":65,"column":39}},"loc":{"start":{"line":65,"column":45},"end":{"line":65,"column":57}},"line":65},"13":{"name":"(anonymous_13)","decl":{"start":{"line":68,"column":19},"end":{"line":68,"column":20}},"loc":{"start":{"line":68,"column":28},"end":{"line":71,"column":13}},"line":68},"14":{"name":"(anonymous_14)","decl":{"start":{"line":84,"column":34},"end":{"line":84,"column":35}},"loc":{"start":{"line":84,"column":41},"end":{"line":84,"column":null}},"line":84},"15":{"name":"(anonymous_15)","decl":{"start":{"line":101,"column":31},"end":{"line":101,"column":32}},"loc":{"start":{"line":102,"column":24},"end":{"line":115,"column":null}},"line":102},"16":{"name":"(anonymous_16)","decl":{"start":{"line":108,"column":41},"end":{"line":108,"column":47}},"loc":{"start":{"line":108,"column":47},"end":{"line":108,"column":null}},"line":108}},"branchMap":{"0":{"loc":{"start":{"line":19,"column":16},"end":{"line":19,"column":null}},"type":"if","locations":[{"start":{"line":19,"column":16},"end":{"line":19,"column":null}},{"start":{},"end":{}}],"line":19},"1":{"loc":{"start":{"line":34,"column":8},"end":{"line":34,"column":null}},"type":"if","locations":[{"start":{"line":34,"column":8},"end":{"line":34,"column":null}},{"start":{},"end":{}}],"line":34},"2":{"loc":{"start":{"line":43,"column":16},"end":{"line":43,"column":null}},"type":"if","locations":[{"start":{"line":43,"column":16},"end":{"line":43,"column":null}},{"start":{},"end":{}}],"line":43},"3":{"loc":{"start":{"line":57,"column":8},"end":{"line":57,"column":null}},"type":"if","locations":[{"start":{"line":57,"column":8},"end":{"line":57,"column":null}},{"start":{},"end":{}}],"line":57},"4":{"loc":{"start":{"line":64,"column":16},"end":{"line":64,"column":null}},"type":"if","locations":[{"start":{"line":64,"column":16},"end":{"line":64,"column":null}},{"start":{},"end":{}}],"line":64},"5":{"loc":{"start":{"line":94,"column":17},"end":{"line":94,"column":null}},"type":"binary-expr","locations":[{"start":{"line":94,"column":17},"end":{"line":94,"column":26}},{"start":{"line":94,"column":26},"end":{"line":94,"column":null}}],"line":94},"6":{"loc":{"start":{"line":99,"column":17},"end":{"line":99,"column":null}},"type":"binary-expr","locations":[{"start":{"line":99,"column":17},"end":{"line":99,"column":28}},{"start":{"line":99,"column":28},"end":{"line":99,"column":null}}],"line":99},"7":{"loc":{"start":{"line":104,"column":62},"end":{"line":104,"column":89}},"type":"binary-expr","locations":[{"start":{"line":104,"column":62},"end":{"line":104,"column":76}},{"start":{"line":104,"column":76},"end":{"line":104,"column":89}}],"line":104}},"s":{"0":14,"1":14,"2":14,"3":14,"4":14,"5":3,"6":14,"7":4,"8":4,"9":4,"10":0,"11":4,"12":4,"13":4,"14":0,"15":0,"16":14,"17":1,"18":1,"19":0,"20":1,"21":1,"22":1,"23":0,"24":1,"25":1,"26":1,"27":0,"28":0,"29":14,"30":1,"31":0,"32":1,"33":1,"34":1,"35":0,"36":1,"37":1,"38":1,"39":0,"40":0,"41":14,"42":1,"43":5,"44":1},"f":{"0":14,"1":3,"2":4,"3":4,"4":4,"5":0,"6":1,"7":1,"8":1,"9":0,"10":1,"11":1,"12":1,"13":0,"14":1,"15":5,"16":1},"b":{"0":[0,4],"1":[0,1],"2":[0,1],"3":[0,1],"4":[0,1],"5":[14,0],"6":[14,5],"7":[5,0]},"meta":{"lastBranch":8,"lastFunction":17,"lastStatement":45,"seen":{"f:5:24:5:35":0,"s:6:26:6:Infinity":0,"s:7:36:7:Infinity":1,"s:8:30:8:Infinity":2,"s:9:26:9:Infinity":3,"s:11:4:13:Infinity":4,"f:11:14:11:20":1,"s:12:8:12:Infinity":5,"s:15:23:30:Infinity":6,"f:15:23:15:29":2,"s:16:8:16:Infinity":7,"s:17:8:29:Infinity":8,"f:18:18:18:19":3,"b:19:16:19:Infinity:undefined:undefined:undefined:undefined":0,"s:19:16:19:Infinity":9,"s:19:29:19:Infinity":10,"s:20:16:20:Infinity":11,"f:22:18:22:19":4,"s:23:16:23:Infinity":12,"s:24:16:24:Infinity":13,"f:26:19:26:20":5,"s:27:16:27:Infinity":14,"s:28:16:28:Infinity":15,"s:32:26:54:Infinity":16,"f:32:26:32:27":6,"s:33:8:33:Infinity":17,"b:34:8:34:Infinity:undefined:undefined:undefined:undefined":1,"s:34:8:34:Infinity":18,"s:34:25:34:Infinity":19,"s:36:8:36:Infinity":20,"s:37:8:53:Infinity":21,"f:42:18:42:19":7,"b:43:16:43:Infinity:undefined:undefined:undefined:undefined":2,"s:43:16:43:Infinity":22,"s:43:29:43:Infinity":23,"s:44:16:44:Infinity":24,"f:46:18:46:24":8,"s:47:16:47:Infinity":25,"s:48:16:48:Infinity":26,"f:50:19:50:20":9,"s:51:16:51:Infinity":27,"s:52:16:52:Infinity":28,"s:56:29:72:Infinity":29,"f:56:29:56:30":10,"b:57:8:57:Infinity:undefined:undefined:undefined:undefined":3,"s:57:8:57:Infinity":30,"s:57:79:57:Infinity":31,"s:59:8:59:Infinity":32,"s:60:8:71:Infinity":33,"f:63:18:63:19":11,"b:64:16:64:Infinity:undefined:undefined:undefined:undefined":4,"s:64:16:64:Infinity":34,"s:64:29:64:Infinity":35,"s:65:16:65:Infinity":36,"f:65:38:65:39":12,"s:65:45:65:57":37,"s:66:16:66:Infinity":38,"f:68:19:68:20":13,"s:69:16:69:Infinity":39,"s:70:16:70:Infinity":40,"s:74:4:119:Infinity":41,"f:84:34:84:35":14,"s:84:41:84:Infinity":42,"b:94:17:94:26:94:26:94:Infinity":5,"b:99:17:99:28:99:28:99:Infinity":6,"f:101:31:101:32":15,"s:102:24:115:Infinity":43,"b:104:62:104:76:104:76:104:89":7,"f:108:41:108:47":16,"s:108:47:108:Infinity":44}}} +{ + "/Users/adam/workspace/vibecode/neko/frontend/src/App.css": { + "path": "/Users/adam/workspace/vibecode/neko/frontend/src/App.css", + "statementMap": {}, + "fnMap": {}, + "branchMap": {}, + "s": {}, + "f": {}, + "b": {}, + "meta": { "lastBranch": 0, "lastFunction": 0, "lastStatement": 0, "seen": {} } + }, + "/Users/adam/workspace/vibecode/neko/frontend/src/App.tsx": { + "path": "/Users/adam/workspace/vibecode/neko/frontend/src/App.tsx", + "statementMap": { + "0": { "start": { "line": 8, "column": 22 }, "end": { "line": 8, "column": null } }, + "1": { "start": { "line": 9, "column": 8 }, "end": { "line": 9, "column": null } }, + "2": { "start": { "line": 11, "column": 2 }, "end": { "line": 21, "column": null } }, + "3": { "start": { "line": 12, "column": 4 }, "end": { "line": 20, "column": null } }, + "4": { "start": { "line": 14, "column": 8 }, "end": { "line": 18, "column": null } }, + "5": { "start": { "line": 15, "column": 10 }, "end": { "line": 15, "column": null } }, + "6": { "start": { "line": 17, "column": 10 }, "end": { "line": 17, "column": null } }, + "7": { "start": { "line": 20, "column": 19 }, "end": { "line": 20, "column": 33 } }, + "8": { "start": { "line": 23, "column": 2 }, "end": { "line": 25, "column": null } }, + "9": { "start": { "line": 24, "column": 4 }, "end": { "line": 24, "column": null } }, + "10": { "start": { "line": 27, "column": 2 }, "end": { "line": 29, "column": null } }, + "11": { "start": { "line": 28, "column": 4 }, "end": { "line": 28, "column": null } }, + "12": { "start": { "line": 31, "column": 2 }, "end": { "line": 31, "column": null } }, + "13": { "start": { "line": 39, "column": 8 }, "end": { "line": 39, "column": null } }, + "14": { "start": { "line": 40, "column": 2 }, "end": { "line": 68, "column": null } }, + "15": { "start": { "line": 45, "column": 33 }, "end": { "line": 45, "column": 56 } }, + "16": { "start": { "line": 48, "column": 12 }, "end": { "line": 49, "column": null } }, + "17": { "start": { "line": 49, "column": 26 }, "end": { "line": 49, "column": 60 } }, + "18": { "start": { "line": 73, "column": 2 }, "end": { "line": 86, "column": null } } + }, + "fnMap": { + "0": { + "name": "RequireAuth", + "decl": { "start": { "line": 7, "column": 9 }, "end": { "line": 7, "column": 21 } }, + "loc": { "start": { "line": 7, "column": 69 }, "end": { "line": 32, "column": null } }, + "line": 7 + }, + "1": { + "name": "(anonymous_1)", + "decl": { "start": { "line": 11, "column": 12 }, "end": { "line": 11, "column": 18 } }, + "loc": { "start": { "line": 11, "column": 18 }, "end": { "line": 21, "column": 5 } }, + "line": 11 + }, + "2": { + "name": "(anonymous_2)", + "decl": { "start": { "line": 13, "column": 12 }, "end": { "line": 13, "column": 13 } }, + "loc": { "start": { "line": 13, "column": 21 }, "end": { "line": 19, "column": 7 } }, + "line": 13 + }, + "3": { + "name": "(anonymous_3)", + "decl": { "start": { "line": 20, "column": 13 }, "end": { "line": 20, "column": 19 } }, + "loc": { "start": { "line": 20, "column": 19 }, "end": { "line": 20, "column": 33 } }, + "line": 20 + }, + "4": { + "name": "Dashboard", + "decl": { "start": { "line": 38, "column": 9 }, "end": { "line": 38, "column": 21 } }, + "loc": { "start": { "line": 38, "column": 21 }, "end": { "line": 70, "column": null } }, + "line": 38 + }, + "5": { + "name": "(anonymous_5)", + "decl": { "start": { "line": 45, "column": 27 }, "end": { "line": 45, "column": 33 } }, + "loc": { "start": { "line": 45, "column": 33 }, "end": { "line": 45, "column": 56 } }, + "line": 45 + }, + "6": { + "name": "(anonymous_6)", + "decl": { "start": { "line": 47, "column": 27 }, "end": { "line": 47, "column": 33 } }, + "loc": { "start": { "line": 47, "column": 33 }, "end": { "line": 50, "column": 13 } }, + "line": 47 + }, + "7": { + "name": "(anonymous_7)", + "decl": { "start": { "line": 49, "column": 20 }, "end": { "line": 49, "column": 26 } }, + "loc": { "start": { "line": 49, "column": 26 }, "end": { "line": 49, "column": 60 } }, + "line": 49 + }, + "8": { + "name": "App", + "decl": { "start": { "line": 72, "column": 9 }, "end": { "line": 72, "column": 15 } }, + "loc": { "start": { "line": 72, "column": 15 }, "end": { "line": 88, "column": null } }, + "line": 72 + } + }, + "branchMap": { + "0": { + "loc": { "start": { "line": 14, "column": 8 }, "end": { "line": 18, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 14, "column": 8 }, "end": { "line": 18, "column": null } }, + { "start": { "line": 16, "column": 15 }, "end": { "line": 18, "column": null } } + ], + "line": 14 + }, + "1": { + "loc": { "start": { "line": 23, "column": 2 }, "end": { "line": 25, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 23, "column": 2 }, "end": { "line": 25, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 23 + }, + "2": { + "loc": { "start": { "line": 27, "column": 2 }, "end": { "line": 29, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 27, "column": 2 }, "end": { "line": 29, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 27 + } + }, + "s": { + "0": 2, + "1": 2, + "2": 2, + "3": 1, + "4": 1, + "5": 1, + "6": 0, + "7": 0, + "8": 2, + "9": 1, + "10": 1, + "11": 0, + "12": 1, + "13": 1, + "14": 1, + "15": 0, + "16": 1, + "17": 1, + "18": 2 + }, + "f": { "0": 2, "1": 1, "2": 1, "3": 0, "4": 1, "5": 0, "6": 1, "7": 1, "8": 2 }, + "b": { "0": [1, 0], "1": [1, 1], "2": [0, 1] }, + "meta": { + "lastBranch": 3, + "lastFunction": 9, + "lastStatement": 19, + "seen": { + "f:7:9:7:21": 0, + "s:8:22:8:Infinity": 0, + "s:9:8:9:Infinity": 1, + "s:11:2:21:Infinity": 2, + "f:11:12:11:18": 1, + "s:12:4:20:Infinity": 3, + "f:13:12:13:13": 2, + "b:14:8:18:Infinity:16:15:18:Infinity": 0, + "s:14:8:18:Infinity": 4, + "s:15:10:15:Infinity": 5, + "s:17:10:17:Infinity": 6, + "f:20:13:20:19": 3, + "s:20:19:20:33": 7, + "b:23:2:25:Infinity:undefined:undefined:undefined:undefined": 1, + "s:23:2:25:Infinity": 8, + "s:24:4:24:Infinity": 9, + "b:27:2:29:Infinity:undefined:undefined:undefined:undefined": 2, + "s:27:2:29:Infinity": 10, + "s:28:4:28:Infinity": 11, + "s:31:2:31:Infinity": 12, + "f:38:9:38:21": 4, + "s:39:8:39:Infinity": 13, + "s:40:2:68:Infinity": 14, + "f:45:27:45:33": 5, + "s:45:33:45:56": 15, + "f:47:27:47:33": 6, + "s:48:12:49:Infinity": 16, + "f:49:20:49:26": 7, + "s:49:26:49:60": 17, + "f:72:9:72:15": 8, + "s:73:2:86:Infinity": 18 + } + } + }, + "/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItem.css": { + "path": "/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItem.css", + "statementMap": {}, + "fnMap": {}, + "branchMap": {}, + "s": {}, + "f": {}, + "b": {}, + "meta": { "lastBranch": 0, "lastFunction": 0, "lastStatement": 0, "seen": {} } + }, + "/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItem.tsx": { + "path": "/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItem.tsx", + "statementMap": { + "0": { "start": { "line": 10, "column": 24 }, "end": { "line": 10, "column": null } }, + "1": { "start": { "line": 11, "column": 30 }, "end": { "line": 11, "column": null } }, + "2": { "start": { "line": 14, "column": 23 }, "end": { "line": 16, "column": null } }, + "3": { "start": { "line": 15, "column": 8 }, "end": { "line": 15, "column": null } }, + "4": { "start": { "line": 18, "column": 23 }, "end": { "line": 52, "column": null } }, + "5": { "start": { "line": 19, "column": 8 }, "end": { "line": 19, "column": null } }, + "6": { "start": { "line": 21, "column": 29 }, "end": { "line": 21, "column": null } }, + "7": { "start": { "line": 22, "column": 8 }, "end": { "line": 22, "column": null } }, + "8": { "start": { "line": 24, "column": 8 }, "end": { "line": 51, "column": null } }, + "9": { "start": { "line": 36, "column": 16 }, "end": { "line": 38, "column": null } }, + "10": { "start": { "line": 37, "column": 20 }, "end": { "line": 37, "column": null } }, + "11": { "start": { "line": 39, "column": 16 }, "end": { "line": 39, "column": null } }, + "12": { "start": { "line": 44, "column": 16 }, "end": { "line": 44, "column": null } }, + "13": { "start": { "line": 47, "column": 16 }, "end": { "line": 47, "column": null } }, + "14": { "start": { "line": 49, "column": 16 }, "end": { "line": 49, "column": null } }, + "15": { "start": { "line": 50, "column": 16 }, "end": { "line": 50, "column": null } }, + "16": { "start": { "line": 54, "column": 4 }, "end": { "line": 82, "column": null } }, + "17": { "start": { "line": 59, "column": 24 }, "end": { "line": 59, "column": null } }, + "18": { "start": { "line": 60, "column": 24 }, "end": { "line": 60, "column": null } } + }, + "fnMap": { + "0": { + "name": "FeedItem", + "decl": { "start": { "line": 9, "column": 24 }, "end": { "line": 9, "column": 33 } }, + "loc": { "start": { "line": 9, "column": 71 }, "end": { "line": 84, "column": null } }, + "line": 9 + }, + "1": { + "name": "(anonymous_1)", + "decl": { "start": { "line": 14, "column": 23 }, "end": { "line": 14, "column": 29 } }, + "loc": { "start": { "line": 14, "column": 29 }, "end": { "line": 16, "column": null } }, + "line": 14 + }, + "2": { + "name": "(anonymous_2)", + "decl": { "start": { "line": 18, "column": 23 }, "end": { "line": 18, "column": 24 } }, + "loc": { "start": { "line": 18, "column": 42 }, "end": { "line": 52, "column": null } }, + "line": 18 + }, + "3": { + "name": "(anonymous_3)", + "decl": { "start": { "line": 35, "column": 18 }, "end": { "line": 35, "column": 19 } }, + "loc": { "start": { "line": 35, "column": 27 }, "end": { "line": 40, "column": 13 } }, + "line": 35 + }, + "4": { + "name": "(anonymous_4)", + "decl": { "start": { "line": 41, "column": 18 }, "end": { "line": 41, "column": 24 } }, + "loc": { "start": { "line": 41, "column": 24 }, "end": { "line": 45, "column": 13 } }, + "line": 41 + }, + "5": { + "name": "(anonymous_5)", + "decl": { "start": { "line": 46, "column": 19 }, "end": { "line": 46, "column": 20 } }, + "loc": { "start": { "line": 46, "column": 28 }, "end": { "line": 51, "column": 13 } }, + "line": 46 + }, + "6": { + "name": "(anonymous_6)", + "decl": { "start": { "line": 58, "column": 29 }, "end": { "line": 58, "column": 30 } }, + "loc": { "start": { "line": 58, "column": 36 }, "end": { "line": 61, "column": null } }, + "line": 58 + } + }, + "branchMap": { + "0": { + "loc": { "start": { "line": 36, "column": 16 }, "end": { "line": 38, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 36, "column": 16 }, "end": { "line": 38, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 36 + }, + "1": { + "loc": { "start": { "line": 55, "column": 36 }, "end": { "line": 55, "column": 65 } }, + "type": "cond-expr", + "locations": [ + { "start": { "line": 55, "column": 48 }, "end": { "line": 55, "column": 57 } }, + { "start": { "line": 55, "column": 57 }, "end": { "line": 55, "column": 65 } } + ], + "line": 55 + }, + "2": { + "loc": { "start": { "line": 55, "column": 69 }, "end": { "line": 55, "column": 93 } }, + "type": "cond-expr", + "locations": [ + { "start": { "line": 55, "column": 79 }, "end": { "line": 55, "column": 91 } }, + { "start": { "line": 55, "column": 91 }, "end": { "line": 55, "column": 93 } } + ], + "line": 55 + }, + "3": { + "loc": { "start": { "line": 62, "column": 43 }, "end": { "line": 62, "column": 87 } }, + "type": "cond-expr", + "locations": [ + { "start": { "line": 62, "column": 58 }, "end": { "line": 62, "column": 73 } }, + { "start": { "line": 62, "column": 73 }, "end": { "line": 62, "column": 87 } } + ], + "line": 62 + }, + "4": { + "loc": { "start": { "line": 63, "column": 27 }, "end": { "line": 63, "column": null } }, + "type": "cond-expr", + "locations": [ + { "start": { "line": 63, "column": 42 }, "end": { "line": 63, "column": 53 } }, + { "start": { "line": 63, "column": 53 }, "end": { "line": 63, "column": null } } + ], + "line": 63 + }, + "5": { + "loc": { "start": { "line": 65, "column": 21 }, "end": { "line": 65, "column": null } }, + "type": "cond-expr", + "locations": [ + { "start": { "line": 65, "column": 36 }, "end": { "line": 65, "column": 42 } }, + { "start": { "line": 65, "column": 42 }, "end": { "line": 65, "column": null } } + ], + "line": 65 + }, + "6": { + "loc": { "start": { "line": 68, "column": 21 }, "end": { "line": 68, "column": null } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 68, "column": 21 }, "end": { "line": 68, "column": 35 } }, + { "start": { "line": 68, "column": 35 }, "end": { "line": 68, "column": null } } + ], + "line": 68 + }, + "7": { + "loc": { "start": { "line": 74, "column": 21 }, "end": { "line": 74, "column": null } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 74, "column": 21 }, "end": { "line": 74, "column": 40 } }, + { "start": { "line": 74, "column": 40 }, "end": { "line": 74, "column": null } } + ], + "line": 74 + }, + "8": { + "loc": { "start": { "line": 79, "column": 13 }, "end": { "line": 80, "column": null } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 79, "column": 13 }, "end": { "line": 79, "column": null } }, + { "start": { "line": 80, "column": 16 }, "end": { "line": 80, "column": null } } + ], + "line": 79 + } + }, + "s": { + "0": 21, + "1": 21, + "2": 21, + "3": 1, + "4": 21, + "5": 1, + "6": 1, + "7": 1, + "8": 1, + "9": 1, + "10": 0, + "11": 1, + "12": 1, + "13": 0, + "14": 0, + "15": 0, + "16": 21, + "17": 1, + "18": 1 + }, + "f": { "0": 21, "1": 1, "2": 1, "3": 1, "4": 1, "5": 0, "6": 1 }, + "b": { + "0": [0, 1], + "1": [9, 12], + "2": [1, 20], + "3": [2, 19], + "4": [2, 19], + "5": [2, 19], + "6": [21, 0], + "7": [21, 5], + "8": [21, 4] + }, + "meta": { + "lastBranch": 9, + "lastFunction": 7, + "lastStatement": 19, + "seen": { + "f:9:24:9:33": 0, + "s:10:24:10:Infinity": 0, + "s:11:30:11:Infinity": 1, + "s:14:23:16:Infinity": 2, + "f:14:23:14:29": 1, + "s:15:8:15:Infinity": 3, + "s:18:23:52:Infinity": 4, + "f:18:23:18:24": 2, + "s:19:8:19:Infinity": 5, + "s:21:29:21:Infinity": 6, + "s:22:8:22:Infinity": 7, + "s:24:8:51:Infinity": 8, + "f:35:18:35:19": 3, + "b:36:16:38:Infinity:undefined:undefined:undefined:undefined": 0, + "s:36:16:38:Infinity": 9, + "s:37:20:37:Infinity": 10, + "s:39:16:39:Infinity": 11, + "f:41:18:41:24": 4, + "s:44:16:44:Infinity": 12, + "f:46:19:46:20": 5, + "s:47:16:47:Infinity": 13, + "s:49:16:49:Infinity": 14, + "s:50:16:50:Infinity": 15, + "s:54:4:82:Infinity": 16, + "b:55:48:55:57:55:57:55:65": 1, + "b:55:79:55:91:55:91:55:93": 2, + "f:58:29:58:30": 6, + "s:59:24:59:Infinity": 17, + "s:60:24:60:Infinity": 18, + "b:62:58:62:73:62:73:62:87": 3, + "b:63:42:63:53:63:53:63:Infinity": 4, + "b:65:36:65:42:65:42:65:Infinity": 5, + "b:68:21:68:35:68:35:68:Infinity": 6, + "b:74:21:74:40:74:40:74:Infinity": 7, + "b:79:13:79:Infinity:80:16:80:Infinity": 8 + } + } + }, + "/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItems.css": { + "path": "/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItems.css", + "statementMap": {}, + "fnMap": {}, + "branchMap": {}, + "s": {}, + "f": {}, + "b": {}, + "meta": { "lastBranch": 0, "lastFunction": 0, "lastStatement": 0, "seen": {} } + }, + "/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItems.tsx": { + "path": "/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedItems.tsx", + "statementMap": { + "0": { "start": { "line": 8, "column": 28 }, "end": { "line": 8, "column": null } }, + "1": { "start": { "line": 9, "column": 23 }, "end": { "line": 9, "column": null } }, + "2": { "start": { "line": 10, "column": 21 }, "end": { "line": 10, "column": null } }, + "3": { "start": { "line": 12, "column": 26 }, "end": { "line": 12, "column": null } }, + "4": { "start": { "line": 13, "column": 30 }, "end": { "line": 13, "column": null } }, + "5": { "start": { "line": 14, "column": 38 }, "end": { "line": 14, "column": null } }, + "6": { "start": { "line": 15, "column": 30 }, "end": { "line": 15, "column": null } }, + "7": { "start": { "line": 16, "column": 26 }, "end": { "line": 16, "column": null } }, + "8": { "start": { "line": 18, "column": 23 }, "end": { "line": 78, "column": null } }, + "9": { "start": { "line": 19, "column": 8 }, "end": { "line": 24, "column": null } }, + "10": { "start": { "line": 20, "column": 12 }, "end": { "line": 20, "column": null } }, + "11": { "start": { "line": 22, "column": 12 }, "end": { "line": 22, "column": null } }, + "12": { "start": { "line": 23, "column": 12 }, "end": { "line": 23, "column": null } }, + "13": { "start": { "line": 25, "column": 8 }, "end": { "line": 25, "column": null } }, + "14": { "start": { "line": 27, "column": 18 }, "end": { "line": 27, "column": null } }, + "15": { "start": { "line": 28, "column": 23 }, "end": { "line": 28, "column": null } }, + "16": { "start": { "line": 30, "column": 8 }, "end": { "line": 34, "column": null } }, + "17": { "start": { "line": 31, "column": 12 }, "end": { "line": 31, "column": null } }, + "18": { "start": { "line": 32, "column": 8 }, "end": { "line": 34, "column": null } }, + "19": { "start": { "line": 33, "column": 12 }, "end": { "line": 33, "column": null } }, + "20": { "start": { "line": 36, "column": 8 }, "end": { "line": 38, "column": null } }, + "21": { "start": { "line": 37, "column": 12 }, "end": { "line": 37, "column": null } }, + "22": { "start": { "line": 41, "column": 8 }, "end": { "line": 49, "column": null } }, + "23": { "start": { "line": 42, "column": 12 }, "end": { "line": 42, "column": null } }, + "24": { "start": { "line": 43, "column": 8 }, "end": { "line": 49, "column": null } }, + "25": { "start": { "line": 44, "column": 12 }, "end": { "line": 44, "column": null } }, + "26": { "start": { "line": 45, "column": 12 }, "end": { "line": 45, "column": null } }, + "27": { "start": { "line": 48, "column": 12 }, "end": { "line": 48, "column": null } }, + "28": { "start": { "line": 51, "column": 28 }, "end": { "line": 51, "column": null } }, + "29": { "start": { "line": 52, "column": 8 }, "end": { "line": 54, "column": null } }, + "30": { "start": { "line": 53, "column": 12 }, "end": { "line": 53, "column": null } }, + "31": { "start": { "line": 56, "column": 8 }, "end": { "line": 77, "column": null } }, + "32": { "start": { "line": 58, "column": 16 }, "end": { "line": 60, "column": null } }, + "33": { "start": { "line": 59, "column": 20 }, "end": { "line": 59, "column": null } }, + "34": { "start": { "line": 61, "column": 16 }, "end": { "line": 61, "column": null } }, + "35": { "start": { "line": 64, "column": 16 }, "end": { "line": 68, "column": null } }, + "36": { "start": { "line": 65, "column": 20 }, "end": { "line": 65, "column": null } }, + "37": { "start": { "line": 65, "column": 39 }, "end": { "line": 65, "column": 57 } }, + "38": { "start": { "line": 67, "column": 20 }, "end": { "line": 67, "column": null } }, + "39": { "start": { "line": 69, "column": 16 }, "end": { "line": 69, "column": null } }, + "40": { "start": { "line": 70, "column": 16 }, "end": { "line": 70, "column": null } }, + "41": { "start": { "line": 71, "column": 16 }, "end": { "line": 71, "column": null } }, + "42": { "start": { "line": 74, "column": 16 }, "end": { "line": 74, "column": null } }, + "43": { "start": { "line": 75, "column": 16 }, "end": { "line": 75, "column": null } }, + "44": { "start": { "line": 76, "column": 16 }, "end": { "line": 76, "column": null } }, + "45": { "start": { "line": 80, "column": 4 }, "end": { "line": 82, "column": null } }, + "46": { "start": { "line": 81, "column": 8 }, "end": { "line": 81, "column": null } }, + "47": { "start": { "line": 84, "column": 42 }, "end": { "line": 84, "column": null } }, + "48": { "start": { "line": 86, "column": 4 }, "end": { "line": 122, "column": null } }, + "49": { "start": { "line": 87, "column": 30 }, "end": { "line": 118, "column": null } }, + "50": { "start": { "line": 88, "column": 12 }, "end": { "line": 88, "column": null } }, + "51": { "start": { "line": 88, "column": 36 }, "end": { "line": 88, "column": null } }, + "52": { "start": { "line": 90, "column": 12 }, "end": { "line": 117, "column": null } }, + "53": { "start": { "line": 91, "column": 16 }, "end": { "line": 101, "column": null } }, + "54": { "start": { "line": 92, "column": 38 }, "end": { "line": 92, "column": null } }, + "55": { "start": { "line": 93, "column": 20 }, "end": { "line": 99, "column": null } }, + "56": { "start": { "line": 94, "column": 37 }, "end": { "line": 94, "column": null } }, + "57": { "start": { "line": 95, "column": 24 }, "end": { "line": 97, "column": null } }, + "58": { "start": { "line": 96, "column": 28 }, "end": { "line": 96, "column": null } }, + "59": { "start": { "line": 98, "column": 24 }, "end": { "line": 98, "column": null } }, + "60": { "start": { "line": 100, "column": 20 }, "end": { "line": 100, "column": null } }, + "61": { "start": { "line": 102, "column": 12 }, "end": { "line": 117, "column": null } }, + "62": { "start": { "line": 103, "column": 16 }, "end": { "line": 109, "column": null } }, + "63": { "start": { "line": 104, "column": 38 }, "end": { "line": 104, "column": null } }, + "64": { "start": { "line": 105, "column": 20 }, "end": { "line": 107, "column": null } }, + "65": { "start": { "line": 106, "column": 24 }, "end": { "line": 106, "column": null } }, + "66": { "start": { "line": 108, "column": 20 }, "end": { "line": 108, "column": null } }, + "67": { "start": { "line": 110, "column": 12 }, "end": { "line": 117, "column": null } }, + "68": { "start": { "line": 111, "column": 16 }, "end": { "line": 116, "column": null } }, + "69": { "start": { "line": 112, "column": 20 }, "end": { "line": 114, "column": null } }, + "70": { "start": { "line": 113, "column": 24 }, "end": { "line": 113, "column": null } }, + "71": { "start": { "line": 115, "column": 20 }, "end": { "line": 115, "column": null } }, + "72": { "start": { "line": 120, "column": 8 }, "end": { "line": 120, "column": null } }, + "73": { "start": { "line": 121, "column": 8 }, "end": { "line": 121, "column": null } }, + "74": { "start": { "line": 121, "column": 21 }, "end": { "line": 121, "column": null } }, + "75": { "start": { "line": 124, "column": 25 }, "end": { "line": 129, "column": null } }, + "76": { "start": { "line": 125, "column": 24 }, "end": { "line": 125, "column": null } }, + "77": { "start": { "line": 126, "column": 8 }, "end": { "line": 128, "column": null } }, + "78": { "start": { "line": 127, "column": 12 }, "end": { "line": 127, "column": null } }, + "79": { "start": { "line": 131, "column": 23 }, "end": { "line": 141, "column": null } }, + "80": { "start": { "line": 132, "column": 28 }, "end": { "line": 132, "column": null } }, + "81": { "start": { "line": 134, "column": 8 }, "end": { "line": 134, "column": null } }, + "82": { "start": { "line": 134, "column": 32 }, "end": { "line": 134, "column": 92 } }, + "83": { "start": { "line": 134, "column": 54 }, "end": { "line": 134, "column": 91 } }, + "84": { "start": { "line": 136, "column": 8 }, "end": { "line": 140, "column": null } }, + "85": { "start": { "line": 140, "column": 26 }, "end": { "line": 140, "column": 67 } }, + "86": { "start": { "line": 143, "column": 23 }, "end": { "line": 153, "column": null } }, + "87": { "start": { "line": 144, "column": 28 }, "end": { "line": 144, "column": null } }, + "88": { "start": { "line": 146, "column": 8 }, "end": { "line": 146, "column": null } }, + "89": { "start": { "line": 146, "column": 32 }, "end": { "line": 146, "column": 92 } }, + "90": { "start": { "line": 146, "column": 54 }, "end": { "line": 146, "column": 91 } }, + "91": { "start": { "line": 148, "column": 8 }, "end": { "line": 152, "column": null } }, + "92": { "start": { "line": 152, "column": 26 }, "end": { "line": 152, "column": 69 } }, + "93": { "start": { "line": 155, "column": 4 }, "end": { "line": 191, "column": null } }, + "94": { "start": { "line": 156, "column": 25 }, "end": { "line": 180, "column": null } }, + "95": { "start": { "line": 158, "column": 16 }, "end": { "line": 177, "column": null } }, + "96": { "start": { "line": 160, "column": 20 }, "end": { "line": 165, "column": null } }, + "97": { "start": { "line": 161, "column": 24 }, "end": { "line": 163, "column": null } }, + "98": { "start": { "line": 162, "column": 28 }, "end": { "line": 162, "column": null } }, + "99": { "start": { "line": 164, "column": 24 }, "end": { "line": 164, "column": null } }, + "100": { "start": { "line": 168, "column": 20 }, "end": { "line": 176, "column": null } }, + "101": { "start": { "line": 169, "column": 38 }, "end": { "line": 169, "column": null } }, + "102": { "start": { "line": 170, "column": 24 }, "end": { "line": 175, "column": null } }, + "103": { "start": { "line": 171, "column": 41 }, "end": { "line": 171, "column": null } }, + "104": { "start": { "line": 172, "column": 28 }, "end": { "line": 174, "column": null } }, + "105": { "start": { "line": 173, "column": 32 }, "end": { "line": 173, "column": null } }, + "106": { "start": { "line": 182, "column": 8 }, "end": { "line": 185, "column": null } }, + "107": { "start": { "line": 183, "column": 23 }, "end": { "line": 183, "column": null } }, + "108": { "start": { "line": 184, "column": 12 }, "end": { "line": 184, "column": null } }, + "109": { "start": { "line": 184, "column": 20 }, "end": { "line": 184, "column": null } }, + "110": { "start": { "line": 187, "column": 25 }, "end": { "line": 187, "column": null } }, + "111": { "start": { "line": 188, "column": 8 }, "end": { "line": 188, "column": null } }, + "112": { "start": { "line": 188, "column": 22 }, "end": { "line": 188, "column": null } }, + "113": { "start": { "line": 190, "column": 8 }, "end": { "line": 190, "column": null } }, + "114": { "start": { "line": 190, "column": 21 }, "end": { "line": 190, "column": null } }, + "115": { "start": { "line": 193, "column": 4 }, "end": { "line": 193, "column": null } }, + "116": { "start": { "line": 193, "column": 17 }, "end": { "line": 193, "column": null } }, + "117": { "start": { "line": 194, "column": 4 }, "end": { "line": 194, "column": null } }, + "118": { "start": { "line": 194, "column": 15 }, "end": { "line": 194, "column": null } }, + "119": { "start": { "line": 197, "column": 4 }, "end": { "line": 221, "column": null } }, + "120": { "start": { "line": 204, "column": 24 }, "end": { "line": 212, "column": null } }, + "121": { "start": { "line": 209, "column": 43 }, "end": { "line": 209, "column": null } } + }, + "fnMap": { + "0": { + "name": "FeedItems", + "decl": { "start": { "line": 7, "column": 24 }, "end": { "line": 7, "column": 36 } }, + "loc": { "start": { "line": 7, "column": 36 }, "end": { "line": 223, "column": null } }, + "line": 7 + }, + "1": { + "name": "(anonymous_1)", + "decl": { "start": { "line": 18, "column": 23 }, "end": { "line": 18, "column": 24 } }, + "loc": { "start": { "line": 18, "column": 43 }, "end": { "line": 78, "column": null } }, + "line": 18 + }, + "2": { + "name": "(anonymous_2)", + "decl": { "start": { "line": 57, "column": 18 }, "end": { "line": 57, "column": 19 } }, + "loc": { "start": { "line": 57, "column": 27 }, "end": { "line": 62, "column": 13 } }, + "line": 57 + }, + "3": { + "name": "(anonymous_3)", + "decl": { "start": { "line": 63, "column": 18 }, "end": { "line": 63, "column": 19 } }, + "loc": { "start": { "line": 63, "column": 28 }, "end": { "line": 72, "column": 13 } }, + "line": 63 + }, + "4": { + "name": "(anonymous_4)", + "decl": { "start": { "line": 65, "column": 29 }, "end": { "line": 65, "column": 30 } }, + "loc": { "start": { "line": 65, "column": 39 }, "end": { "line": 65, "column": 57 } }, + "line": 65 + }, + "5": { + "name": "(anonymous_5)", + "decl": { "start": { "line": 73, "column": 19 }, "end": { "line": 73, "column": 20 } }, + "loc": { "start": { "line": 73, "column": 28 }, "end": { "line": 77, "column": 13 } }, + "line": 73 + }, + "6": { + "name": "(anonymous_6)", + "decl": { "start": { "line": 80, "column": 14 }, "end": { "line": 80, "column": 20 } }, + "loc": { "start": { "line": 80, "column": 20 }, "end": { "line": 82, "column": 7 } }, + "line": 80 + }, + "7": { + "name": "(anonymous_7)", + "decl": { "start": { "line": 86, "column": 14 }, "end": { "line": 86, "column": 20 } }, + "loc": { "start": { "line": 86, "column": 20 }, "end": { "line": 122, "column": 7 } }, + "line": 86 + }, + "8": { + "name": "(anonymous_8)", + "decl": { "start": { "line": 87, "column": 30 }, "end": { "line": 87, "column": 31 } }, + "loc": { "start": { "line": 87, "column": 52 }, "end": { "line": 118, "column": null } }, + "line": 87 + }, + "9": { + "name": "(anonymous_9)", + "decl": { "start": { "line": 91, "column": 33 }, "end": { "line": 91, "column": 34 } }, + "loc": { "start": { "line": 91, "column": 43 }, "end": { "line": 101, "column": 17 } }, + "line": 91 + }, + "10": { + "name": "(anonymous_10)", + "decl": { "start": { "line": 103, "column": 33 }, "end": { "line": 103, "column": 34 } }, + "loc": { "start": { "line": 103, "column": 43 }, "end": { "line": 109, "column": 17 } }, + "line": 103 + }, + "11": { + "name": "(anonymous_11)", + "decl": { "start": { "line": 111, "column": 33 }, "end": { "line": 111, "column": 34 } }, + "loc": { "start": { "line": 111, "column": 51 }, "end": { "line": 116, "column": 17 } }, + "line": 111 + }, + "12": { + "name": "(anonymous_12)", + "decl": { "start": { "line": 121, "column": 15 }, "end": { "line": 121, "column": 21 } }, + "loc": { "start": { "line": 121, "column": 21 }, "end": { "line": 121, "column": null } }, + "line": 121 + }, + "13": { + "name": "(anonymous_13)", + "decl": { "start": { "line": 124, "column": 25 }, "end": { "line": 124, "column": 26 } }, + "loc": { "start": { "line": 124, "column": 44 }, "end": { "line": 129, "column": null } }, + "line": 124 + }, + "14": { + "name": "(anonymous_14)", + "decl": { "start": { "line": 131, "column": 23 }, "end": { "line": 131, "column": 24 } }, + "loc": { "start": { "line": 131, "column": 39 }, "end": { "line": 141, "column": null } }, + "line": 131 + }, + "15": { + "name": "(anonymous_15)", + "decl": { "start": { "line": 134, "column": 17 }, "end": { "line": 134, "column": 18 } }, + "loc": { "start": { "line": 134, "column": 32 }, "end": { "line": 134, "column": 92 } }, + "line": 134 + }, + "16": { + "name": "(anonymous_16)", + "decl": { "start": { "line": 134, "column": 46 }, "end": { "line": 134, "column": 47 } }, + "loc": { "start": { "line": 134, "column": 54 }, "end": { "line": 134, "column": 91 } }, + "line": 134 + }, + "17": { + "name": "(anonymous_17)", + "decl": { "start": { "line": 140, "column": 17 }, "end": { "line": 140, "column": 18 } }, + "loc": { "start": { "line": 140, "column": 26 }, "end": { "line": 140, "column": 67 } }, + "line": 140 + }, + "18": { + "name": "(anonymous_18)", + "decl": { "start": { "line": 143, "column": 23 }, "end": { "line": 143, "column": 24 } }, + "loc": { "start": { "line": 143, "column": 39 }, "end": { "line": 153, "column": null } }, + "line": 143 + }, + "19": { + "name": "(anonymous_19)", + "decl": { "start": { "line": 146, "column": 17 }, "end": { "line": 146, "column": 18 } }, + "loc": { "start": { "line": 146, "column": 32 }, "end": { "line": 146, "column": 92 } }, + "line": 146 + }, + "20": { + "name": "(anonymous_20)", + "decl": { "start": { "line": 146, "column": 46 }, "end": { "line": 146, "column": 47 } }, + "loc": { "start": { "line": 146, "column": 54 }, "end": { "line": 146, "column": 91 } }, + "line": 146 + }, + "21": { + "name": "(anonymous_21)", + "decl": { "start": { "line": 152, "column": 17 }, "end": { "line": 152, "column": 18 } }, + "loc": { "start": { "line": 152, "column": 26 }, "end": { "line": 152, "column": 69 } }, + "line": 152 + }, + "22": { + "name": "(anonymous_22)", + "decl": { "start": { "line": 155, "column": 14 }, "end": { "line": 155, "column": 20 } }, + "loc": { "start": { "line": 155, "column": 20 }, "end": { "line": 191, "column": 7 } }, + "line": 155 + }, + "23": { + "name": "(anonymous_23)", + "decl": { "start": { "line": 157, "column": 12 }, "end": { "line": 157, "column": 13 } }, + "loc": { "start": { "line": 157, "column": 25 }, "end": { "line": 178, "column": null } }, + "line": 157 + }, + "24": { + "name": "(anonymous_24)", + "decl": { "start": { "line": 158, "column": 32 }, "end": { "line": 158, "column": 33 } }, + "loc": { "start": { "line": 158, "column": 43 }, "end": { "line": 177, "column": 17 } }, + "line": 158 + }, + "25": { + "name": "(anonymous_25)", + "decl": { "start": { "line": 182, "column": 22 }, "end": { "line": 182, "column": 23 } }, + "loc": { "start": { "line": 182, "column": 36 }, "end": { "line": 185, "column": 9 } }, + "line": 182 + }, + "26": { + "name": "(anonymous_26)", + "decl": { "start": { "line": 190, "column": 15 }, "end": { "line": 190, "column": 21 } }, + "loc": { "start": { "line": 190, "column": 21 }, "end": { "line": 190, "column": null } }, + "line": 190 + }, + "27": { + "name": "(anonymous_27)", + "decl": { "start": { "line": 203, "column": 31 }, "end": { "line": 203, "column": 32 } }, + "loc": { "start": { "line": 204, "column": 24 }, "end": { "line": 212, "column": null } }, + "line": 204 + }, + "28": { + "name": "(anonymous_28)", + "decl": { "start": { "line": 209, "column": 37 }, "end": { "line": 209, "column": 43 } }, + "loc": { "start": { "line": 209, "column": 43 }, "end": { "line": 209, "column": null } }, + "line": 209 + } + }, + "branchMap": { + "0": { + "loc": { "start": { "line": 10, "column": 21 }, "end": { "line": 10, "column": null } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 10, "column": 21 }, "end": { "line": 10, "column": 51 } }, + { "start": { "line": 10, "column": 51 }, "end": { "line": 10, "column": null } } + ], + "line": 10 + }, + "1": { + "loc": { "start": { "line": 19, "column": 8 }, "end": { "line": 24, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 19, "column": 8 }, "end": { "line": 24, "column": null } }, + { "start": { "line": 21, "column": 15 }, "end": { "line": 24, "column": null } } + ], + "line": 19 + }, + "2": { + "loc": { "start": { "line": 30, "column": 8 }, "end": { "line": 34, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 30, "column": 8 }, "end": { "line": 34, "column": null } }, + { "start": { "line": 32, "column": 8 }, "end": { "line": 34, "column": null } } + ], + "line": 30 + }, + "3": { + "loc": { "start": { "line": 32, "column": 8 }, "end": { "line": 34, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 32, "column": 8 }, "end": { "line": 34, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 32 + }, + "4": { + "loc": { "start": { "line": 36, "column": 8 }, "end": { "line": 38, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 36, "column": 8 }, "end": { "line": 38, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 36 + }, + "5": { + "loc": { "start": { "line": 41, "column": 8 }, "end": { "line": 49, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 41, "column": 8 }, "end": { "line": 49, "column": null } }, + { "start": { "line": 43, "column": 8 }, "end": { "line": 49, "column": null } } + ], + "line": 41 + }, + "6": { + "loc": { "start": { "line": 43, "column": 8 }, "end": { "line": 49, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 43, "column": 8 }, "end": { "line": 49, "column": null } }, + { "start": { "line": 46, "column": 15 }, "end": { "line": 49, "column": null } } + ], + "line": 43 + }, + "7": { + "loc": { "start": { "line": 52, "column": 8 }, "end": { "line": 54, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 52, "column": 8 }, "end": { "line": 54, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 52 + }, + "8": { + "loc": { "start": { "line": 58, "column": 16 }, "end": { "line": 60, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 58, "column": 16 }, "end": { "line": 60, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 58 + }, + "9": { + "loc": { "start": { "line": 64, "column": 16 }, "end": { "line": 68, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 64, "column": 16 }, "end": { "line": 68, "column": null } }, + { "start": { "line": 66, "column": 23 }, "end": { "line": 68, "column": null } } + ], + "line": 64 + }, + "10": { + "loc": { "start": { "line": 88, "column": 12 }, "end": { "line": 88, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 88, "column": 12 }, "end": { "line": 88, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 88 + }, + "11": { + "loc": { "start": { "line": 90, "column": 12 }, "end": { "line": 117, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 90, "column": 12 }, "end": { "line": 117, "column": null } }, + { "start": { "line": 102, "column": 12 }, "end": { "line": 117, "column": null } } + ], + "line": 90 + }, + "12": { + "loc": { "start": { "line": 93, "column": 20 }, "end": { "line": 99, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 93, "column": 20 }, "end": { "line": 99, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 93 + }, + "13": { + "loc": { "start": { "line": 95, "column": 24 }, "end": { "line": 97, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 95, "column": 24 }, "end": { "line": 97, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 95 + }, + "14": { + "loc": { "start": { "line": 102, "column": 12 }, "end": { "line": 117, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 102, "column": 12 }, "end": { "line": 117, "column": null } }, + { "start": { "line": 110, "column": 12 }, "end": { "line": 117, "column": null } } + ], + "line": 102 + }, + "15": { + "loc": { "start": { "line": 105, "column": 20 }, "end": { "line": 107, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 105, "column": 20 }, "end": { "line": 107, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 105 + }, + "16": { + "loc": { "start": { "line": 110, "column": 12 }, "end": { "line": 117, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 110, "column": 12 }, "end": { "line": 117, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 110 + }, + "17": { + "loc": { "start": { "line": 112, "column": 20 }, "end": { "line": 114, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 112, "column": 20 }, "end": { "line": 114, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 112 + }, + "18": { + "loc": { "start": { "line": 112, "column": 24 }, "end": { "line": 112, "column": 74 } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 112, "column": 24 }, "end": { "line": 112, "column": 45 } }, + { "start": { "line": 112, "column": 45 }, "end": { "line": 112, "column": 74 } } + ], + "line": 112 + }, + "19": { + "loc": { "start": { "line": 126, "column": 8 }, "end": { "line": 128, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 126, "column": 8 }, "end": { "line": 128, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 126 + }, + "20": { + "loc": { "start": { "line": 134, "column": 54 }, "end": { "line": 134, "column": 91 } }, + "type": "cond-expr", + "locations": [ + { "start": { "line": 134, "column": 75 }, "end": { "line": 134, "column": 89 } }, + { "start": { "line": 134, "column": 89 }, "end": { "line": 134, "column": 91 } } + ], + "line": 134 + }, + "21": { + "loc": { "start": { "line": 146, "column": 54 }, "end": { "line": 146, "column": 91 } }, + "type": "cond-expr", + "locations": [ + { "start": { "line": 146, "column": 75 }, "end": { "line": 146, "column": 89 } }, + { "start": { "line": 146, "column": 89 }, "end": { "line": 146, "column": 91 } } + ], + "line": 146 + }, + "22": { + "loc": { "start": { "line": 160, "column": 20 }, "end": { "line": 165, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 160, "column": 20 }, "end": { "line": 165, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 160 + }, + "23": { + "loc": { "start": { "line": 161, "column": 24 }, "end": { "line": 163, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 161, "column": 24 }, "end": { "line": 163, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 161 + }, + "24": { + "loc": { "start": { "line": 161, "column": 28 }, "end": { "line": 161, "column": 97 } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 161, "column": 28 }, "end": { "line": 161, "column": 52 } }, + { "start": { "line": 161, "column": 52 }, "end": { "line": 161, "column": 68 } }, + { "start": { "line": 161, "column": 68 }, "end": { "line": 161, "column": 79 } }, + { "start": { "line": 161, "column": 79 }, "end": { "line": 161, "column": 97 } } + ], + "line": 161 + }, + "25": { + "loc": { "start": { "line": 168, "column": 20 }, "end": { "line": 176, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 168, "column": 20 }, "end": { "line": 176, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 168 + }, + "26": { + "loc": { "start": { "line": 168, "column": 24 }, "end": { "line": 168, "column": 83 } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 168, "column": 24 }, "end": { "line": 168, "column": 49 } }, + { "start": { "line": 168, "column": 49 }, "end": { "line": 168, "column": 83 } } + ], + "line": 168 + }, + "27": { + "loc": { "start": { "line": 170, "column": 24 }, "end": { "line": 175, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 170, "column": 24 }, "end": { "line": 175, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 170 + }, + "28": { + "loc": { "start": { "line": 170, "column": 28 }, "end": { "line": 170, "column": 81 } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 170, "column": 28 }, "end": { "line": 170, "column": 45 } }, + { "start": { "line": 170, "column": 45 }, "end": { "line": 170, "column": 59 } }, + { "start": { "line": 170, "column": 59 }, "end": { "line": 170, "column": 81 } } + ], + "line": 170 + }, + "29": { + "loc": { "start": { "line": 172, "column": 28 }, "end": { "line": 174, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 172, "column": 28 }, "end": { "line": 174, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 172 + }, + "30": { + "loc": { "start": { "line": 184, "column": 12 }, "end": { "line": 184, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 184, "column": 12 }, "end": { "line": 184, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 184 + }, + "31": { + "loc": { "start": { "line": 188, "column": 8 }, "end": { "line": 188, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 188, "column": 8 }, "end": { "line": 188, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 188 + }, + "32": { + "loc": { "start": { "line": 193, "column": 4 }, "end": { "line": 193, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 193, "column": 4 }, "end": { "line": 193, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 193 + }, + "33": { + "loc": { "start": { "line": 194, "column": 4 }, "end": { "line": 194, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 194, "column": 4 }, "end": { "line": 194, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 194 + }, + "34": { + "loc": { "start": { "line": 199, "column": 13 }, "end": { "line": 219, "column": null } }, + "type": "cond-expr", + "locations": [ + { "start": { "line": 200, "column": 16 }, "end": { "line": 200, "column": null } }, + { "start": { "line": 202, "column": 16 }, "end": { "line": 219, "column": null } } + ], + "line": 199 + }, + "35": { + "loc": { "start": { "line": 208, "column": 39 }, "end": { "line": 208, "column": null } }, + "type": "cond-expr", + "locations": [ + { "start": { "line": 208, "column": 65 }, "end": { "line": 208, "column": 93 } }, + { "start": { "line": 208, "column": 93 }, "end": { "line": 208, "column": null } } + ], + "line": 208 + }, + "36": { + "loc": { "start": { "line": 214, "column": 21 }, "end": { "line": 217, "column": null } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 214, "column": 21 }, "end": { "line": 214, "column": null } }, + { "start": { "line": 215, "column": 24 }, "end": { "line": 217, "column": null } } + ], + "line": 214 + }, + "37": { + "loc": { "start": { "line": 216, "column": 29 }, "end": { "line": 216, "column": null } }, + "type": "cond-expr", + "locations": [ + { "start": { "line": 216, "column": 43 }, "end": { "line": 216, "column": 63 } }, + { "start": { "line": 216, "column": 63 }, "end": { "line": 216, "column": null } } + ], + "line": 216 + } + }, + "s": { + "0": 27, + "1": 27, + "2": 27, + "3": 27, + "4": 27, + "5": 27, + "6": 27, + "7": 27, + "8": 27, + "9": 8, + "10": 1, + "11": 7, + "12": 7, + "13": 8, + "14": 8, + "15": 8, + "16": 8, + "17": 2, + "18": 6, + "19": 1, + "20": 8, + "21": 1, + "22": 8, + "23": 0, + "24": 8, + "25": 0, + "26": 0, + "27": 8, + "28": 8, + "29": 8, + "30": 8, + "31": 8, + "32": 7, + "33": 0, + "34": 7, + "35": 6, + "36": 1, + "37": 1, + "38": 5, + "39": 6, + "40": 6, + "41": 6, + "42": 1, + "43": 1, + "44": 1, + "45": 27, + "46": 7, + "47": 27, + "48": 27, + "49": 23, + "50": 3, + "51": 0, + "52": 3, + "53": 2, + "54": 2, + "55": 2, + "56": 2, + "57": 2, + "58": 1, + "59": 2, + "60": 2, + "61": 1, + "62": 0, + "63": 0, + "64": 0, + "65": 0, + "66": 0, + "67": 1, + "68": 1, + "69": 1, + "70": 1, + "71": 1, + "72": 23, + "73": 23, + "74": 23, + "75": 27, + "76": 2, + "77": 2, + "78": 2, + "79": 27, + "80": 2, + "81": 2, + "82": 2, + "83": 3, + "84": 2, + "85": 0, + "86": 27, + "87": 1, + "88": 1, + "89": 1, + "90": 2, + "91": 1, + "92": 0, + "93": 27, + "94": 24, + "95": 2, + "96": 2, + "97": 1, + "98": 1, + "99": 1, + "100": 1, + "101": 1, + "102": 1, + "103": 1, + "104": 1, + "105": 1, + "106": 24, + "107": 15, + "108": 15, + "109": 15, + "110": 24, + "111": 24, + "112": 10, + "113": 24, + "114": 24, + "115": 27, + "116": 13, + "117": 14, + "118": 14, + "119": 13, + "120": 21, + "121": 0 + }, + "f": { + "0": 27, + "1": 8, + "2": 7, + "3": 6, + "4": 1, + "5": 1, + "6": 7, + "7": 23, + "8": 3, + "9": 2, + "10": 0, + "11": 1, + "12": 23, + "13": 2, + "14": 2, + "15": 2, + "16": 3, + "17": 0, + "18": 1, + "19": 1, + "20": 2, + "21": 0, + "22": 24, + "23": 2, + "24": 2, + "25": 15, + "26": 24, + "27": 21, + "28": 0 + }, + "b": { + "0": [27, 27], + "1": [1, 7], + "2": [2, 6], + "3": [1, 5], + "4": [1, 7], + "5": [0, 8], + "6": [0, 8], + "7": [8, 0], + "8": [0, 7], + "9": [1, 5], + "10": [0, 3], + "11": [2, 1], + "12": [2, 0], + "13": [1, 1], + "14": [0, 1], + "15": [0, 0], + "16": [1, 0], + "17": [1, 0], + "18": [1, 1], + "19": [2, 0], + "20": [2, 1], + "21": [1, 1], + "22": [1, 1], + "23": [1, 0], + "24": [1, 1, 1, 1], + "25": [1, 0], + "26": [1, 1], + "27": [1, 0], + "28": [1, 1, 1], + "29": [1, 0], + "30": [15, 0], + "31": [10, 14], + "32": [13, 14], + "33": [1, 13], + "34": [0, 13], + "35": [5, 16], + "36": [13, 13], + "37": [1, 12] + }, + "meta": { + "lastBranch": 38, + "lastFunction": 29, + "lastStatement": 122, + "seen": { + "f:7:24:7:36": 0, + "s:8:28:8:Infinity": 0, + "s:9:23:9:Infinity": 1, + "s:10:21:10:Infinity": 2, + "b:10:21:10:51:10:51:10:Infinity": 0, + "s:12:26:12:Infinity": 3, + "s:13:30:13:Infinity": 4, + "s:14:38:14:Infinity": 5, + "s:15:30:15:Infinity": 6, + "s:16:26:16:Infinity": 7, + "s:18:23:78:Infinity": 8, + "f:18:23:18:24": 1, + "b:19:8:24:Infinity:21:15:24:Infinity": 1, + "s:19:8:24:Infinity": 9, + "s:20:12:20:Infinity": 10, + "s:22:12:22:Infinity": 11, + "s:23:12:23:Infinity": 12, + "s:25:8:25:Infinity": 13, + "s:27:18:27:Infinity": 14, + "s:28:23:28:Infinity": 15, + "b:30:8:34:Infinity:32:8:34:Infinity": 2, + "s:30:8:34:Infinity": 16, + "s:31:12:31:Infinity": 17, + "b:32:8:34:Infinity:undefined:undefined:undefined:undefined": 3, + "s:32:8:34:Infinity": 18, + "s:33:12:33:Infinity": 19, + "b:36:8:38:Infinity:undefined:undefined:undefined:undefined": 4, + "s:36:8:38:Infinity": 20, + "s:37:12:37:Infinity": 21, + "b:41:8:49:Infinity:43:8:49:Infinity": 5, + "s:41:8:49:Infinity": 22, + "s:42:12:42:Infinity": 23, + "b:43:8:49:Infinity:46:15:49:Infinity": 6, + "s:43:8:49:Infinity": 24, + "s:44:12:44:Infinity": 25, + "s:45:12:45:Infinity": 26, + "s:48:12:48:Infinity": 27, + "s:51:28:51:Infinity": 28, + "b:52:8:54:Infinity:undefined:undefined:undefined:undefined": 7, + "s:52:8:54:Infinity": 29, + "s:53:12:53:Infinity": 30, + "s:56:8:77:Infinity": 31, + "f:57:18:57:19": 2, + "b:58:16:60:Infinity:undefined:undefined:undefined:undefined": 8, + "s:58:16:60:Infinity": 32, + "s:59:20:59:Infinity": 33, + "s:61:16:61:Infinity": 34, + "f:63:18:63:19": 3, + "b:64:16:68:Infinity:66:23:68:Infinity": 9, + "s:64:16:68:Infinity": 35, + "s:65:20:65:Infinity": 36, + "f:65:29:65:30": 4, + "s:65:39:65:57": 37, + "s:67:20:67:Infinity": 38, + "s:69:16:69:Infinity": 39, + "s:70:16:70:Infinity": 40, + "s:71:16:71:Infinity": 41, + "f:73:19:73:20": 5, + "s:74:16:74:Infinity": 42, + "s:75:16:75:Infinity": 43, + "s:76:16:76:Infinity": 44, + "s:80:4:82:Infinity": 45, + "f:80:14:80:20": 6, + "s:81:8:81:Infinity": 46, + "s:84:42:84:Infinity": 47, + "s:86:4:122:Infinity": 48, + "f:86:14:86:20": 7, + "s:87:30:118:Infinity": 49, + "f:87:30:87:31": 8, + "b:88:12:88:Infinity:undefined:undefined:undefined:undefined": 10, + "s:88:12:88:Infinity": 50, + "s:88:36:88:Infinity": 51, + "b:90:12:117:Infinity:102:12:117:Infinity": 11, + "s:90:12:117:Infinity": 52, + "s:91:16:101:Infinity": 53, + "f:91:33:91:34": 9, + "s:92:38:92:Infinity": 54, + "b:93:20:99:Infinity:undefined:undefined:undefined:undefined": 12, + "s:93:20:99:Infinity": 55, + "s:94:37:94:Infinity": 56, + "b:95:24:97:Infinity:undefined:undefined:undefined:undefined": 13, + "s:95:24:97:Infinity": 57, + "s:96:28:96:Infinity": 58, + "s:98:24:98:Infinity": 59, + "s:100:20:100:Infinity": 60, + "b:102:12:117:Infinity:110:12:117:Infinity": 14, + "s:102:12:117:Infinity": 61, + "s:103:16:109:Infinity": 62, + "f:103:33:103:34": 10, + "s:104:38:104:Infinity": 63, + "b:105:20:107:Infinity:undefined:undefined:undefined:undefined": 15, + "s:105:20:107:Infinity": 64, + "s:106:24:106:Infinity": 65, + "s:108:20:108:Infinity": 66, + "b:110:12:117:Infinity:undefined:undefined:undefined:undefined": 16, + "s:110:12:117:Infinity": 67, + "s:111:16:116:Infinity": 68, + "f:111:33:111:34": 11, + "b:112:20:114:Infinity:undefined:undefined:undefined:undefined": 17, + "s:112:20:114:Infinity": 69, + "b:112:24:112:45:112:45:112:74": 18, + "s:113:24:113:Infinity": 70, + "s:115:20:115:Infinity": 71, + "s:120:8:120:Infinity": 72, + "s:121:8:121:Infinity": 73, + "f:121:15:121:21": 12, + "s:121:21:121:Infinity": 74, + "s:124:25:129:Infinity": 75, + "f:124:25:124:26": 13, + "s:125:24:125:Infinity": 76, + "b:126:8:128:Infinity:undefined:undefined:undefined:undefined": 19, + "s:126:8:128:Infinity": 77, + "s:127:12:127:Infinity": 78, + "s:131:23:141:Infinity": 79, + "f:131:23:131:24": 14, + "s:132:28:132:Infinity": 80, + "s:134:8:134:Infinity": 81, + "f:134:17:134:18": 15, + "s:134:32:134:92": 82, + "f:134:46:134:47": 16, + "s:134:54:134:91": 83, + "b:134:75:134:89:134:89:134:91": 20, + "s:136:8:140:Infinity": 84, + "f:140:17:140:18": 17, + "s:140:26:140:67": 85, + "s:143:23:153:Infinity": 86, + "f:143:23:143:24": 18, + "s:144:28:144:Infinity": 87, + "s:146:8:146:Infinity": 88, + "f:146:17:146:18": 19, + "s:146:32:146:92": 89, + "f:146:46:146:47": 20, + "s:146:54:146:91": 90, + "b:146:75:146:89:146:89:146:91": 21, + "s:148:8:152:Infinity": 91, + "f:152:17:152:18": 21, + "s:152:26:152:69": 92, + "s:155:4:191:Infinity": 93, + "f:155:14:155:20": 22, + "s:156:25:180:Infinity": 94, + "f:157:12:157:13": 23, + "s:158:16:177:Infinity": 95, + "f:158:32:158:33": 24, + "b:160:20:165:Infinity:undefined:undefined:undefined:undefined": 22, + "s:160:20:165:Infinity": 96, + "b:161:24:163:Infinity:undefined:undefined:undefined:undefined": 23, + "s:161:24:163:Infinity": 97, + "b:161:28:161:52:161:52:161:68:161:68:161:79:161:79:161:97": 24, + "s:162:28:162:Infinity": 98, + "s:164:24:164:Infinity": 99, + "b:168:20:176:Infinity:undefined:undefined:undefined:undefined": 25, + "s:168:20:176:Infinity": 100, + "b:168:24:168:49:168:49:168:83": 26, + "s:169:38:169:Infinity": 101, + "b:170:24:175:Infinity:undefined:undefined:undefined:undefined": 27, + "s:170:24:175:Infinity": 102, + "b:170:28:170:45:170:45:170:59:170:59:170:81": 28, + "s:171:41:171:Infinity": 103, + "b:172:28:174:Infinity:undefined:undefined:undefined:undefined": 29, + "s:172:28:174:Infinity": 104, + "s:173:32:173:Infinity": 105, + "s:182:8:185:Infinity": 106, + "f:182:22:182:23": 25, + "s:183:23:183:Infinity": 107, + "b:184:12:184:Infinity:undefined:undefined:undefined:undefined": 30, + "s:184:12:184:Infinity": 108, + "s:184:20:184:Infinity": 109, + "s:187:25:187:Infinity": 110, + "b:188:8:188:Infinity:undefined:undefined:undefined:undefined": 31, + "s:188:8:188:Infinity": 111, + "s:188:22:188:Infinity": 112, + "s:190:8:190:Infinity": 113, + "f:190:15:190:21": 26, + "s:190:21:190:Infinity": 114, + "b:193:4:193:Infinity:undefined:undefined:undefined:undefined": 32, + "s:193:4:193:Infinity": 115, + "s:193:17:193:Infinity": 116, + "b:194:4:194:Infinity:undefined:undefined:undefined:undefined": 33, + "s:194:4:194:Infinity": 117, + "s:194:15:194:Infinity": 118, + "s:197:4:221:Infinity": 119, + "b:200:16:200:Infinity:202:16:219:Infinity": 34, + "f:203:31:203:32": 27, + "s:204:24:212:Infinity": 120, + "b:208:65:208:93:208:93:208:Infinity": 35, + "f:209:37:209:43": 28, + "s:209:43:209:Infinity": 121, + "b:214:21:214:Infinity:215:24:217:Infinity": 36, + "b:216:43:216:63:216:63:216:Infinity": 37 + } + } + }, + "/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedList.css": { + "path": "/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedList.css", + "statementMap": {}, + "fnMap": {}, + "branchMap": {}, + "s": {}, + "f": {}, + "b": {}, + "meta": { "lastBranch": 0, "lastFunction": 0, "lastStatement": 0, "seen": {} } + }, + "/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedList.tsx": { + "path": "/Users/adam/workspace/vibecode/neko/frontend/src/components/FeedList.tsx", + "statementMap": { + "0": { "start": { "line": 7, "column": 26 }, "end": { "line": 7, "column": null } }, + "1": { "start": { "line": 8, "column": 24 }, "end": { "line": 8, "column": null } }, + "2": { "start": { "line": 9, "column": 30 }, "end": { "line": 9, "column": null } }, + "3": { "start": { "line": 10, "column": 26 }, "end": { "line": 10, "column": null } }, + "4": { "start": { "line": 12, "column": 4 }, "end": { "line": 32, "column": null } }, + "5": { "start": { "line": 13, "column": 8 }, "end": { "line": 31, "column": null } }, + "6": { "start": { "line": 15, "column": 16 }, "end": { "line": 15, "column": null } }, + "7": { "start": { "line": 15, "column": 29 }, "end": { "line": 15, "column": null } }, + "8": { "start": { "line": 16, "column": 16 }, "end": { "line": 16, "column": null } }, + "9": { "start": { "line": 19, "column": 16 }, "end": { "line": 19, "column": null } }, + "10": { "start": { "line": 19, "column": 29 }, "end": { "line": 19, "column": null } }, + "11": { "start": { "line": 20, "column": 16 }, "end": { "line": 20, "column": null } }, + "12": { "start": { "line": 24, "column": 16 }, "end": { "line": 24, "column": null } }, + "13": { "start": { "line": 25, "column": 16 }, "end": { "line": 25, "column": null } }, + "14": { "start": { "line": 26, "column": 16 }, "end": { "line": 26, "column": null } }, + "15": { "start": { "line": 29, "column": 16 }, "end": { "line": 29, "column": null } }, + "16": { "start": { "line": 30, "column": 16 }, "end": { "line": 30, "column": null } }, + "17": { "start": { "line": 34, "column": 4 }, "end": { "line": 34, "column": null } }, + "18": { "start": { "line": 34, "column": 17 }, "end": { "line": 34, "column": null } }, + "19": { "start": { "line": 35, "column": 4 }, "end": { "line": 35, "column": null } }, + "20": { "start": { "line": 35, "column": 15 }, "end": { "line": 35, "column": null } }, + "21": { "start": { "line": 37, "column": 4 }, "end": { "line": 78, "column": null } }, + "22": { "start": { "line": 53, "column": 28 }, "end": { "line": 58, "column": null } }, + "23": { "start": { "line": 69, "column": 28 }, "end": { "line": 73, "column": null } } + }, + "fnMap": { + "0": { + "name": "FeedList", + "decl": { "start": { "line": 6, "column": 24 }, "end": { "line": 6, "column": 35 } }, + "loc": { "start": { "line": 6, "column": 35 }, "end": { "line": 80, "column": null } }, + "line": 6 + }, + "1": { + "name": "(anonymous_1)", + "decl": { "start": { "line": 12, "column": 14 }, "end": { "line": 12, "column": 20 } }, + "loc": { "start": { "line": 12, "column": 20 }, "end": { "line": 32, "column": 7 } }, + "line": 12 + }, + "2": { + "name": "(anonymous_2)", + "decl": { "start": { "line": 14, "column": 37 }, "end": { "line": 14, "column": 44 } }, + "loc": { "start": { "line": 14, "column": 44 }, "end": { "line": 17, "column": 13 } }, + "line": 14 + }, + "3": { + "name": "(anonymous_3)", + "decl": { "start": { "line": 18, "column": 35 }, "end": { "line": 18, "column": 42 } }, + "loc": { "start": { "line": 18, "column": 42 }, "end": { "line": 21, "column": 13 } }, + "line": 18 + }, + "4": { + "name": "(anonymous_4)", + "decl": { "start": { "line": 23, "column": 18 }, "end": { "line": 23, "column": 19 } }, + "loc": { "start": { "line": 23, "column": 45 }, "end": { "line": 27, "column": 13 } }, + "line": 23 + }, + "5": { + "name": "(anonymous_5)", + "decl": { "start": { "line": 28, "column": 19 }, "end": { "line": 28, "column": 20 } }, + "loc": { "start": { "line": 28, "column": 28 }, "end": { "line": 31, "column": 13 } }, + "line": 28 + }, + "6": { + "name": "(anonymous_6)", + "decl": { "start": { "line": 52, "column": 35 }, "end": { "line": 52, "column": 36 } }, + "loc": { "start": { "line": 53, "column": 28 }, "end": { "line": 58, "column": null } }, + "line": 53 + }, + "7": { + "name": "(anonymous_7)", + "decl": { "start": { "line": 68, "column": 34 }, "end": { "line": 68, "column": 35 } }, + "loc": { "start": { "line": 69, "column": 28 }, "end": { "line": 73, "column": null } }, + "line": 69 + } + }, + "branchMap": { + "0": { + "loc": { "start": { "line": 15, "column": 16 }, "end": { "line": 15, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 15, "column": 16 }, "end": { "line": 15, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 15 + }, + "1": { + "loc": { "start": { "line": 19, "column": 16 }, "end": { "line": 19, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 19, "column": 16 }, "end": { "line": 19, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 19 + }, + "2": { + "loc": { "start": { "line": 34, "column": 4 }, "end": { "line": 34, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 34, "column": 4 }, "end": { "line": 34, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 34 + }, + "3": { + "loc": { "start": { "line": 35, "column": 4 }, "end": { "line": 35, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 35, "column": 4 }, "end": { "line": 35, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 35 + }, + "4": { + "loc": { "start": { "line": 48, "column": 17 }, "end": { "line": 60, "column": null } }, + "type": "cond-expr", + "locations": [ + { "start": { "line": 49, "column": 20 }, "end": { "line": 49, "column": null } }, + { "start": { "line": 51, "column": 20 }, "end": { "line": 60, "column": null } } + ], + "line": 48 + }, + "5": { + "loc": { "start": { "line": 55, "column": 37 }, "end": { "line": 55, "column": null } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 55, "column": 37 }, "end": { "line": 55, "column": 51 } }, + { "start": { "line": 55, "column": 51 }, "end": { "line": 55, "column": null } } + ], + "line": 55 + }, + "6": { + "loc": { "start": { "line": 57, "column": 33 }, "end": { "line": 57, "column": null } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 57, "column": 33 }, "end": { "line": 57, "column": 50 } }, + { "start": { "line": 57, "column": 50 }, "end": { "line": 57, "column": null } } + ], + "line": 57 + }, + "7": { + "loc": { "start": { "line": 64, "column": 13 }, "end": { "line": 76, "column": null } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 64, "column": 13 }, "end": { "line": 64, "column": 21 } }, + { "start": { "line": 64, "column": 21 }, "end": { "line": 64, "column": null } }, + { "start": { "line": 65, "column": 16 }, "end": { "line": 76, "column": null } } + ], + "line": 64 + } + }, + "s": { + "0": 11, + "1": 11, + "2": 11, + "3": 11, + "4": 11, + "5": 6, + "6": 4, + "7": 0, + "8": 4, + "9": 4, + "10": 0, + "11": 4, + "12": 4, + "13": 4, + "14": 4, + "15": 1, + "16": 1, + "17": 11, + "18": 6, + "19": 5, + "20": 5, + "21": 4, + "22": 3, + "23": 3 + }, + "f": { "0": 11, "1": 6, "2": 4, "3": 4, "4": 4, "5": 1, "6": 3, "7": 3 }, + "b": { + "0": [0, 4], + "1": [0, 4], + "2": [6, 5], + "3": [1, 4], + "4": [2, 2], + "5": [3, 0], + "6": [3, 3], + "7": [11, 4, 2] + }, + "meta": { + "lastBranch": 8, + "lastFunction": 8, + "lastStatement": 24, + "seen": { + "f:6:24:6:35": 0, + "s:7:26:7:Infinity": 0, + "s:8:24:8:Infinity": 1, + "s:9:30:9:Infinity": 2, + "s:10:26:10:Infinity": 3, + "s:12:4:32:Infinity": 4, + "f:12:14:12:20": 1, + "s:13:8:31:Infinity": 5, + "f:14:37:14:44": 2, + "b:15:16:15:Infinity:undefined:undefined:undefined:undefined": 0, + "s:15:16:15:Infinity": 6, + "s:15:29:15:Infinity": 7, + "s:16:16:16:Infinity": 8, + "f:18:35:18:42": 3, + "b:19:16:19:Infinity:undefined:undefined:undefined:undefined": 1, + "s:19:16:19:Infinity": 9, + "s:19:29:19:Infinity": 10, + "s:20:16:20:Infinity": 11, + "f:23:18:23:19": 4, + "s:24:16:24:Infinity": 12, + "s:25:16:25:Infinity": 13, + "s:26:16:26:Infinity": 14, + "f:28:19:28:20": 5, + "s:29:16:29:Infinity": 15, + "s:30:16:30:Infinity": 16, + "b:34:4:34:Infinity:undefined:undefined:undefined:undefined": 2, + "s:34:4:34:Infinity": 17, + "s:34:17:34:Infinity": 18, + "b:35:4:35:Infinity:undefined:undefined:undefined:undefined": 3, + "s:35:4:35:Infinity": 19, + "s:35:15:35:Infinity": 20, + "s:37:4:78:Infinity": 21, + "b:49:20:49:Infinity:51:20:60:Infinity": 4, + "f:52:35:52:36": 6, + "s:53:28:58:Infinity": 22, + "b:55:37:55:51:55:51:55:Infinity": 5, + "b:57:33:57:50:57:50:57:Infinity": 6, + "b:64:13:64:21:64:21:64:Infinity:65:16:76:Infinity": 7, + "f:68:34:68:35": 7, + "s:69:28:73:Infinity": 23 + } + } + }, + "/Users/adam/workspace/vibecode/neko/frontend/src/components/Login.css": { + "path": "/Users/adam/workspace/vibecode/neko/frontend/src/components/Login.css", + "statementMap": {}, + "fnMap": {}, + "branchMap": {}, + "s": {}, + "f": {}, + "b": {}, + "meta": { "lastBranch": 0, "lastFunction": 0, "lastStatement": 0, "seen": {} } + }, + "/Users/adam/workspace/vibecode/neko/frontend/src/components/Login.tsx": { + "path": "/Users/adam/workspace/vibecode/neko/frontend/src/components/Login.tsx", + "statementMap": { + "0": { "start": { "line": 6, "column": 32 }, "end": { "line": 6, "column": null } }, + "1": { "start": { "line": 7, "column": 26 }, "end": { "line": 7, "column": null } }, + "2": { "start": { "line": 8, "column": 10 }, "end": { "line": 8, "column": null } }, + "3": { "start": { "line": 10, "column": 25 }, "end": { "line": 33, "column": null } }, + "4": { "start": { "line": 11, "column": 8 }, "end": { "line": 11, "column": null } }, + "5": { "start": { "line": 12, "column": 8 }, "end": { "line": 12, "column": null } }, + "6": { "start": { "line": 14, "column": 8 }, "end": { "line": 32, "column": null } }, + "7": { "start": { "line": 16, "column": 27 }, "end": { "line": 16, "column": null } }, + "8": { "start": { "line": 17, "column": 12 }, "end": { "line": 17, "column": null } }, + "9": { "start": { "line": 19, "column": 24 }, "end": { "line": 22, "column": null } }, + "10": { "start": { "line": 24, "column": 12 }, "end": { "line": 29, "column": null } }, + "11": { "start": { "line": 25, "column": 16 }, "end": { "line": 25, "column": null } }, + "12": { "start": { "line": 27, "column": 29 }, "end": { "line": 27, "column": null } }, + "13": { "start": { "line": 28, "column": 16 }, "end": { "line": 28, "column": null } }, + "14": { "start": { "line": 31, "column": 12 }, "end": { "line": 31, "column": null } }, + "15": { "start": { "line": 35, "column": 4 }, "end": { "line": 52, "column": null } }, + "16": { "start": { "line": 45, "column": 41 }, "end": { "line": 45, "column": null } } + }, + "fnMap": { + "0": { + "name": "Login", + "decl": { "start": { "line": 5, "column": 24 }, "end": { "line": 5, "column": 32 } }, + "loc": { "start": { "line": 5, "column": 32 }, "end": { "line": 54, "column": null } }, + "line": 5 + }, + "1": { + "name": "(anonymous_1)", + "decl": { "start": { "line": 10, "column": 25 }, "end": { "line": 10, "column": 32 } }, + "loc": { "start": { "line": 10, "column": 49 }, "end": { "line": 33, "column": null } }, + "line": 10 + }, + "2": { + "name": "(anonymous_2)", + "decl": { "start": { "line": 45, "column": 34 }, "end": { "line": 45, "column": 35 } }, + "loc": { "start": { "line": 45, "column": 41 }, "end": { "line": 45, "column": null } }, + "line": 45 + } + }, + "branchMap": { + "0": { + "loc": { "start": { "line": 24, "column": 12 }, "end": { "line": 29, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 24, "column": 12 }, "end": { "line": 29, "column": null } }, + { "start": { "line": 26, "column": 19 }, "end": { "line": 29, "column": null } } + ], + "line": 24 + }, + "1": { + "loc": { "start": { "line": 28, "column": 25 }, "end": { "line": 28, "column": 55 } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 28, "column": 25 }, "end": { "line": 28, "column": 41 } }, + { "start": { "line": 28, "column": 41 }, "end": { "line": 28, "column": 55 } } + ], + "line": 28 + }, + "2": { + "loc": { "start": { "line": 49, "column": 17 }, "end": { "line": 49, "column": null } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 49, "column": 17 }, "end": { "line": 49, "column": 26 } }, + { "start": { "line": 49, "column": 26 }, "end": { "line": 49, "column": null } } + ], + "line": 49 + } + }, + "s": { + "0": 14, + "1": 14, + "2": 14, + "3": 14, + "4": 3, + "5": 3, + "6": 3, + "7": 3, + "8": 3, + "9": 3, + "10": 2, + "11": 1, + "12": 1, + "13": 1, + "14": 1, + "15": 14, + "16": 3 + }, + "f": { "0": 14, "1": 3, "2": 3 }, + "b": { "0": [1, 1], "1": [1, 0], "2": [14, 2] }, + "meta": { + "lastBranch": 3, + "lastFunction": 3, + "lastStatement": 17, + "seen": { + "f:5:24:5:32": 0, + "s:6:32:6:Infinity": 0, + "s:7:26:7:Infinity": 1, + "s:8:10:8:Infinity": 2, + "s:10:25:33:Infinity": 3, + "f:10:25:10:32": 1, + "s:11:8:11:Infinity": 4, + "s:12:8:12:Infinity": 5, + "s:14:8:32:Infinity": 6, + "s:16:27:16:Infinity": 7, + "s:17:12:17:Infinity": 8, + "s:19:24:22:Infinity": 9, + "b:24:12:29:Infinity:26:19:29:Infinity": 0, + "s:24:12:29:Infinity": 10, + "s:25:16:25:Infinity": 11, + "s:27:29:27:Infinity": 12, + "s:28:16:28:Infinity": 13, + "b:28:25:28:41:28:41:28:55": 1, + "s:31:12:31:Infinity": 14, + "s:35:4:52:Infinity": 15, + "f:45:34:45:35": 2, + "s:45:41:45:Infinity": 16, + "b:49:17:49:26:49:26:49:Infinity": 2 + } + } + }, + "/Users/adam/workspace/vibecode/neko/frontend/src/components/Settings.css": { + "path": "/Users/adam/workspace/vibecode/neko/frontend/src/components/Settings.css", + "statementMap": {}, + "fnMap": {}, + "branchMap": {}, + "s": {}, + "f": {}, + "b": {}, + "meta": { "lastBranch": 0, "lastFunction": 0, "lastStatement": 0, "seen": {} } + }, + "/Users/adam/workspace/vibecode/neko/frontend/src/components/Settings.tsx": { + "path": "/Users/adam/workspace/vibecode/neko/frontend/src/components/Settings.tsx", + "statementMap": { + "0": { "start": { "line": 6, "column": 26 }, "end": { "line": 6, "column": null } }, + "1": { "start": { "line": 7, "column": 36 }, "end": { "line": 7, "column": null } }, + "2": { "start": { "line": 8, "column": 30 }, "end": { "line": 8, "column": null } }, + "3": { "start": { "line": 9, "column": 26 }, "end": { "line": 9, "column": null } }, + "4": { "start": { "line": 11, "column": 4 }, "end": { "line": 13, "column": null } }, + "5": { "start": { "line": 12, "column": 8 }, "end": { "line": 12, "column": null } }, + "6": { "start": { "line": 15, "column": 23 }, "end": { "line": 30, "column": null } }, + "7": { "start": { "line": 16, "column": 8 }, "end": { "line": 16, "column": null } }, + "8": { "start": { "line": 17, "column": 8 }, "end": { "line": 29, "column": null } }, + "9": { "start": { "line": 19, "column": 16 }, "end": { "line": 19, "column": null } }, + "10": { "start": { "line": 19, "column": 29 }, "end": { "line": 19, "column": null } }, + "11": { "start": { "line": 20, "column": 16 }, "end": { "line": 20, "column": null } }, + "12": { "start": { "line": 23, "column": 16 }, "end": { "line": 23, "column": null } }, + "13": { "start": { "line": 24, "column": 16 }, "end": { "line": 24, "column": null } }, + "14": { "start": { "line": 27, "column": 16 }, "end": { "line": 27, "column": null } }, + "15": { "start": { "line": 28, "column": 16 }, "end": { "line": 28, "column": null } }, + "16": { "start": { "line": 32, "column": 26 }, "end": { "line": 54, "column": null } }, + "17": { "start": { "line": 33, "column": 8 }, "end": { "line": 33, "column": null } }, + "18": { "start": { "line": 34, "column": 8 }, "end": { "line": 34, "column": null } }, + "19": { "start": { "line": 34, "column": 25 }, "end": { "line": 34, "column": null } }, + "20": { "start": { "line": 36, "column": 8 }, "end": { "line": 36, "column": null } }, + "21": { "start": { "line": 37, "column": 8 }, "end": { "line": 53, "column": null } }, + "22": { "start": { "line": 43, "column": 16 }, "end": { "line": 43, "column": null } }, + "23": { "start": { "line": 43, "column": 29 }, "end": { "line": 43, "column": null } }, + "24": { "start": { "line": 44, "column": 16 }, "end": { "line": 44, "column": null } }, + "25": { "start": { "line": 47, "column": 16 }, "end": { "line": 47, "column": null } }, + "26": { "start": { "line": 48, "column": 16 }, "end": { "line": 48, "column": null } }, + "27": { "start": { "line": 51, "column": 16 }, "end": { "line": 51, "column": null } }, + "28": { "start": { "line": 52, "column": 16 }, "end": { "line": 52, "column": null } }, + "29": { "start": { "line": 56, "column": 29 }, "end": { "line": 72, "column": null } }, + "30": { "start": { "line": 57, "column": 8 }, "end": { "line": 57, "column": null } }, + "31": { "start": { "line": 57, "column": 79 }, "end": { "line": 57, "column": null } }, + "32": { "start": { "line": 59, "column": 8 }, "end": { "line": 59, "column": null } }, + "33": { "start": { "line": 60, "column": 8 }, "end": { "line": 71, "column": null } }, + "34": { "start": { "line": 64, "column": 16 }, "end": { "line": 64, "column": null } }, + "35": { "start": { "line": 64, "column": 29 }, "end": { "line": 64, "column": null } }, + "36": { "start": { "line": 65, "column": 16 }, "end": { "line": 65, "column": null } }, + "37": { "start": { "line": 65, "column": 45 }, "end": { "line": 65, "column": 57 } }, + "38": { "start": { "line": 66, "column": 16 }, "end": { "line": 66, "column": null } }, + "39": { "start": { "line": 69, "column": 16 }, "end": { "line": 69, "column": null } }, + "40": { "start": { "line": 70, "column": 16 }, "end": { "line": 70, "column": null } }, + "41": { "start": { "line": 74, "column": 4 }, "end": { "line": 119, "column": null } }, + "42": { "start": { "line": 84, "column": 41 }, "end": { "line": 84, "column": null } }, + "43": { "start": { "line": 102, "column": 24 }, "end": { "line": 115, "column": null } }, + "44": { "start": { "line": 108, "column": 47 }, "end": { "line": 108, "column": null } } + }, + "fnMap": { + "0": { + "name": "Settings", + "decl": { "start": { "line": 5, "column": 24 }, "end": { "line": 5, "column": 35 } }, + "loc": { "start": { "line": 5, "column": 35 }, "end": { "line": 121, "column": null } }, + "line": 5 + }, + "1": { + "name": "(anonymous_1)", + "decl": { "start": { "line": 11, "column": 14 }, "end": { "line": 11, "column": 20 } }, + "loc": { "start": { "line": 11, "column": 20 }, "end": { "line": 13, "column": 7 } }, + "line": 11 + }, + "2": { + "name": "(anonymous_2)", + "decl": { "start": { "line": 15, "column": 23 }, "end": { "line": 15, "column": 29 } }, + "loc": { "start": { "line": 15, "column": 29 }, "end": { "line": 30, "column": null } }, + "line": 15 + }, + "3": { + "name": "(anonymous_3)", + "decl": { "start": { "line": 18, "column": 18 }, "end": { "line": 18, "column": 19 } }, + "loc": { "start": { "line": 18, "column": 27 }, "end": { "line": 21, "column": 13 } }, + "line": 18 + }, + "4": { + "name": "(anonymous_4)", + "decl": { "start": { "line": 22, "column": 18 }, "end": { "line": 22, "column": 19 } }, + "loc": { "start": { "line": 22, "column": 28 }, "end": { "line": 25, "column": 13 } }, + "line": 22 + }, + "5": { + "name": "(anonymous_5)", + "decl": { "start": { "line": 26, "column": 19 }, "end": { "line": 26, "column": 20 } }, + "loc": { "start": { "line": 26, "column": 28 }, "end": { "line": 29, "column": 13 } }, + "line": 26 + }, + "6": { + "name": "(anonymous_6)", + "decl": { "start": { "line": 32, "column": 26 }, "end": { "line": 32, "column": 27 } }, + "loc": { "start": { "line": 32, "column": 50 }, "end": { "line": 54, "column": null } }, + "line": 32 + }, + "7": { + "name": "(anonymous_7)", + "decl": { "start": { "line": 42, "column": 18 }, "end": { "line": 42, "column": 19 } }, + "loc": { "start": { "line": 42, "column": 27 }, "end": { "line": 45, "column": 13 } }, + "line": 42 + }, + "8": { + "name": "(anonymous_8)", + "decl": { "start": { "line": 46, "column": 18 }, "end": { "line": 46, "column": 24 } }, + "loc": { "start": { "line": 46, "column": 24 }, "end": { "line": 49, "column": 13 } }, + "line": 46 + }, + "9": { + "name": "(anonymous_9)", + "decl": { "start": { "line": 50, "column": 19 }, "end": { "line": 50, "column": 20 } }, + "loc": { "start": { "line": 50, "column": 28 }, "end": { "line": 53, "column": 13 } }, + "line": 50 + }, + "10": { + "name": "(anonymous_10)", + "decl": { "start": { "line": 56, "column": 29 }, "end": { "line": 56, "column": 30 } }, + "loc": { "start": { "line": 56, "column": 45 }, "end": { "line": 72, "column": null } }, + "line": 56 + }, + "11": { + "name": "(anonymous_11)", + "decl": { "start": { "line": 63, "column": 18 }, "end": { "line": 63, "column": 19 } }, + "loc": { "start": { "line": 63, "column": 27 }, "end": { "line": 67, "column": 13 } }, + "line": 63 + }, + "12": { + "name": "(anonymous_12)", + "decl": { "start": { "line": 65, "column": 38 }, "end": { "line": 65, "column": 39 } }, + "loc": { "start": { "line": 65, "column": 45 }, "end": { "line": 65, "column": 57 } }, + "line": 65 + }, + "13": { + "name": "(anonymous_13)", + "decl": { "start": { "line": 68, "column": 19 }, "end": { "line": 68, "column": 20 } }, + "loc": { "start": { "line": 68, "column": 28 }, "end": { "line": 71, "column": 13 } }, + "line": 68 + }, + "14": { + "name": "(anonymous_14)", + "decl": { "start": { "line": 84, "column": 34 }, "end": { "line": 84, "column": 35 } }, + "loc": { "start": { "line": 84, "column": 41 }, "end": { "line": 84, "column": null } }, + "line": 84 + }, + "15": { + "name": "(anonymous_15)", + "decl": { "start": { "line": 101, "column": 31 }, "end": { "line": 101, "column": 32 } }, + "loc": { "start": { "line": 102, "column": 24 }, "end": { "line": 115, "column": null } }, + "line": 102 + }, + "16": { + "name": "(anonymous_16)", + "decl": { "start": { "line": 108, "column": 41 }, "end": { "line": 108, "column": 47 } }, + "loc": { "start": { "line": 108, "column": 47 }, "end": { "line": 108, "column": null } }, + "line": 108 + } + }, + "branchMap": { + "0": { + "loc": { "start": { "line": 19, "column": 16 }, "end": { "line": 19, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 19, "column": 16 }, "end": { "line": 19, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 19 + }, + "1": { + "loc": { "start": { "line": 34, "column": 8 }, "end": { "line": 34, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 34, "column": 8 }, "end": { "line": 34, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 34 + }, + "2": { + "loc": { "start": { "line": 43, "column": 16 }, "end": { "line": 43, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 43, "column": 16 }, "end": { "line": 43, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 43 + }, + "3": { + "loc": { "start": { "line": 57, "column": 8 }, "end": { "line": 57, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 57, "column": 8 }, "end": { "line": 57, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 57 + }, + "4": { + "loc": { "start": { "line": 64, "column": 16 }, "end": { "line": 64, "column": null } }, + "type": "if", + "locations": [ + { "start": { "line": 64, "column": 16 }, "end": { "line": 64, "column": null } }, + { "start": {}, "end": {} } + ], + "line": 64 + }, + "5": { + "loc": { "start": { "line": 94, "column": 17 }, "end": { "line": 94, "column": null } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 94, "column": 17 }, "end": { "line": 94, "column": 26 } }, + { "start": { "line": 94, "column": 26 }, "end": { "line": 94, "column": null } } + ], + "line": 94 + }, + "6": { + "loc": { "start": { "line": 99, "column": 17 }, "end": { "line": 99, "column": null } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 99, "column": 17 }, "end": { "line": 99, "column": 28 } }, + { "start": { "line": 99, "column": 28 }, "end": { "line": 99, "column": null } } + ], + "line": 99 + }, + "7": { + "loc": { "start": { "line": 104, "column": 62 }, "end": { "line": 104, "column": 89 } }, + "type": "binary-expr", + "locations": [ + { "start": { "line": 104, "column": 62 }, "end": { "line": 104, "column": 76 } }, + { "start": { "line": 104, "column": 76 }, "end": { "line": 104, "column": 89 } } + ], + "line": 104 + } + }, + "s": { + "0": 14, + "1": 14, + "2": 14, + "3": 14, + "4": 14, + "5": 3, + "6": 14, + "7": 4, + "8": 4, + "9": 4, + "10": 0, + "11": 4, + "12": 4, + "13": 4, + "14": 0, + "15": 0, + "16": 14, + "17": 1, + "18": 1, + "19": 0, + "20": 1, + "21": 1, + "22": 1, + "23": 0, + "24": 1, + "25": 1, + "26": 1, + "27": 0, + "28": 0, + "29": 14, + "30": 1, + "31": 0, + "32": 1, + "33": 1, + "34": 1, + "35": 0, + "36": 1, + "37": 1, + "38": 1, + "39": 0, + "40": 0, + "41": 14, + "42": 1, + "43": 5, + "44": 1 + }, + "f": { + "0": 14, + "1": 3, + "2": 4, + "3": 4, + "4": 4, + "5": 0, + "6": 1, + "7": 1, + "8": 1, + "9": 0, + "10": 1, + "11": 1, + "12": 1, + "13": 0, + "14": 1, + "15": 5, + "16": 1 + }, + "b": { + "0": [0, 4], + "1": [0, 1], + "2": [0, 1], + "3": [0, 1], + "4": [0, 1], + "5": [14, 0], + "6": [14, 5], + "7": [5, 0] + }, + "meta": { + "lastBranch": 8, + "lastFunction": 17, + "lastStatement": 45, + "seen": { + "f:5:24:5:35": 0, + "s:6:26:6:Infinity": 0, + "s:7:36:7:Infinity": 1, + "s:8:30:8:Infinity": 2, + "s:9:26:9:Infinity": 3, + "s:11:4:13:Infinity": 4, + "f:11:14:11:20": 1, + "s:12:8:12:Infinity": 5, + "s:15:23:30:Infinity": 6, + "f:15:23:15:29": 2, + "s:16:8:16:Infinity": 7, + "s:17:8:29:Infinity": 8, + "f:18:18:18:19": 3, + "b:19:16:19:Infinity:undefined:undefined:undefined:undefined": 0, + "s:19:16:19:Infinity": 9, + "s:19:29:19:Infinity": 10, + "s:20:16:20:Infinity": 11, + "f:22:18:22:19": 4, + "s:23:16:23:Infinity": 12, + "s:24:16:24:Infinity": 13, + "f:26:19:26:20": 5, + "s:27:16:27:Infinity": 14, + "s:28:16:28:Infinity": 15, + "s:32:26:54:Infinity": 16, + "f:32:26:32:27": 6, + "s:33:8:33:Infinity": 17, + "b:34:8:34:Infinity:undefined:undefined:undefined:undefined": 1, + "s:34:8:34:Infinity": 18, + "s:34:25:34:Infinity": 19, + "s:36:8:36:Infinity": 20, + "s:37:8:53:Infinity": 21, + "f:42:18:42:19": 7, + "b:43:16:43:Infinity:undefined:undefined:undefined:undefined": 2, + "s:43:16:43:Infinity": 22, + "s:43:29:43:Infinity": 23, + "s:44:16:44:Infinity": 24, + "f:46:18:46:24": 8, + "s:47:16:47:Infinity": 25, + "s:48:16:48:Infinity": 26, + "f:50:19:50:20": 9, + "s:51:16:51:Infinity": 27, + "s:52:16:52:Infinity": 28, + "s:56:29:72:Infinity": 29, + "f:56:29:56:30": 10, + "b:57:8:57:Infinity:undefined:undefined:undefined:undefined": 3, + "s:57:8:57:Infinity": 30, + "s:57:79:57:Infinity": 31, + "s:59:8:59:Infinity": 32, + "s:60:8:71:Infinity": 33, + "f:63:18:63:19": 11, + "b:64:16:64:Infinity:undefined:undefined:undefined:undefined": 4, + "s:64:16:64:Infinity": 34, + "s:64:29:64:Infinity": 35, + "s:65:16:65:Infinity": 36, + "f:65:38:65:39": 12, + "s:65:45:65:57": 37, + "s:66:16:66:Infinity": 38, + "f:68:19:68:20": 13, + "s:69:16:69:Infinity": 39, + "s:70:16:70:Infinity": 40, + "s:74:4:119:Infinity": 41, + "f:84:34:84:35": 14, + "s:84:41:84:Infinity": 42, + "b:94:17:94:26:94:26:94:Infinity": 5, + "b:99:17:99:28:99:28:99:Infinity": 6, + "f:101:31:101:32": 15, + "s:102:24:115:Infinity": 43, + "b:104:62:104:76:104:76:104:89": 7, + "f:108:41:108:47": 16, + "s:108:47:108:Infinity": 44 + } + } + } } diff --git a/frontend/coverage/index.html b/frontend/coverage/index.html index 450975d..bd1150d 100644 --- a/frontend/coverage/index.html +++ b/frontend/coverage/index.html @@ -1,131 +1,141 @@ - - - + Code coverage report for All files - - - - -
-
+ + + +
+

All files

-
- -
- 86.17% - Statements - 212/246 -
- - -
- 76.76% - Branches - 109/142 -
- - -
- 86.3% - Functions - 63/73 -
- - -
- 88.39% - Lines - 198/224 -
- - +
+
+ 86.17% + Statements + 212/246 +
+ +
+ 76.76% + Branches + 109/142 +
+ +
+ 86.3% + Functions + 63/73 +
+ +
+ 88.39% + Lines + 198/224 +

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

-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
+
FileStatementsBranchesFunctionsLines
src -
-
78.94%15/1966.66%4/677.77%7/978.94%15/19
src/components -
-
86.78%197/22777.2%105/13687.5%56/6489.26%183/205
+ + + + + + + + + + + + + + + + + + + + + + + + + + + - -
File + Statements + BranchesFunctionsLines
src +
+
+
+
+
78.94%15/1966.66%4/677.77%7/978.94%15/19
-
-
-
- - - - - - + + + src/components + + +
+
+
+
+ + 86.78% + 197/227 + 77.2% + 105/136 + 87.5% + 56/64 + 89.26% + 183/205 + + + +
+
+ +
+ + + + + + + - \ No newline at end of file diff --git a/frontend/coverage/prettify.css b/frontend/coverage/prettify.css index b317a7c..006492c 100644 --- a/frontend/coverage/prettify.css +++ b/frontend/coverage/prettify.css @@ -1 +1,101 @@ -.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} +.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/frontend/coverage/prettify.js b/frontend/coverage/prettify.js index b322523..a318211 100644 --- a/frontend/coverage/prettify.js +++ b/frontend/coverage/prettify.js @@ -1,2 +1,937 @@ -/* 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"]); + +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; V < U; ++V) { + var ae = Z[V]; + if (ae.ignoreCase) { + ac = true; + } else { + if (/[a-z]/i.test(ae.source.replace(/\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) { + S = true; + ac = false; + break; + } + } + } + var Y = { b: 8, t: 9, n: 10, v: 11, f: 12, r: 13 }; + function ab(ah) { + var ag = ah.charCodeAt(0); + if (ag !== 92) { + return ag; + } + var af = ah.charAt(1); + ag = Y[af]; + if (ag) { + return ag; + } else { + if ('0' <= af && af <= '7') { + return parseInt(ah.substring(1), 8); + } else { + if (af === 'u' || af === 'x') { + return parseInt(ah.substring(2), 16); + } else { + return ah.charCodeAt(1); + } + } + } + } + function T(af) { + if (af < 32) { + return (af < 16 ? '\\x0' : '\\x') + af.toString(16); + } + var ag = String.fromCharCode(af); + if (ag === '\\' || ag === '-' || ag === '[' || ag === ']') { + ag = '\\' + ag; + } + return ag; + } + function X(am) { + var aq = am + .substring(1, am.length - 1) + .match( + new RegExp( + '\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]', + 'g' + ) + ); + var ak = []; + var af = []; + var ao = aq[0] === '^'; + for (var ar = ao ? 1 : 0, aj = aq.length; ar < aj; ++ar) { + var ah = aq[ar]; + if (/\\[bdsw]/i.test(ah)) { + ak.push(ah); + } else { + var ag = ab(ah); + var al; + if (ar + 2 < aj && '-' === aq[ar + 1]) { + al = ab(aq[ar + 2]); + ar += 2; + } else { + al = ag; + } + af.push([ag, al]); + if (!(al < 65 || ag > 122)) { + 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; ar < af.length; ++ar) { + var at = af[ar]; + if (at[0] <= ap[1] + 1) { + ap[1] = Math.max(ap[1], at[1]); + } else { + ai.push((ap = at)); + } + } + var an = ['[']; + if (ao) { + an.push('^'); + } + an.push.apply(an, ak); + for (var ar = 0; ar < ai.length; ++ar) { + var at = ai[ar]; + an.push(T(at[0])); + if (at[1] > at[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 < ah; ++ak) { + var ag = aj[ak]; + if (ag === '(') { + ++am; + } else { + if ('\\' === ag.charAt(0)) { + var af = +ag.substring(1); + if (af && af <= am) { + an[af] = -1; + } + } + } + } + for (var ak = 1; ak < an.length; ++ak) { + if (-1 === an[ak]) { + an[ak] = ++ad; + } + } + for (var ak = 0, am = 0; ak < ah; ++ak) { + var ag = aj[ak]; + if (ag === '(') { + ++am; + if (an[am] === undefined) { + aj[ak] = '(?:'; + } + } else { + if ('\\' === ag.charAt(0)) { + var af = +ag.substring(1); + if (af && af <= am) { + aj[ak] = '\\' + an[am]; + } + } + } + } + for (var ak = 0, am = 0; ak < ah; ++ak) { + if ('^' === aj[ak] && '^' !== aj[ak + 1]) { + aj[ak] = ''; + } + } + if (al.ignoreCase && S) { + for (var ak = 0; ak < ah; ++ak) { + var ag = aj[ak]; + var ai = ag.charAt(0); + if (ag.length >= 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 < U; ++V) { + var ae = Z[V]; + if (ae.global || ae.multiline) { + throw new Error('' + ae); + } + aa.push('(?:' + W(ae) + ')'); + } + return new RegExp(aa.join('|'), ac ? 'gi' : 'g'); + } + function a(V) { + var U = /(?:^|\s)nocode(?:\s|$)/; + var X = []; + var T = 0; + var Z = []; + var W = 0; + var S; + if (V.currentStyle) { + S = V.currentStyle.whiteSpace; + } else { + if (window.getComputedStyle) { + S = document.defaultView.getComputedStyle(V, null).getPropertyValue('white-space'); + } + } + var Y = S && 'pre' === S.substring(0, 3); + function aa(ab) { + switch (ab.nodeType) { + case 1: + if (U.test(ab.className)) { + return; + } + for (var ae = ab.firstChild; ae; ae = ae.nextSibling) { + aa(ae); + } + var ad = ab.nodeName; + if ('BR' === ad || 'LI' === ad) { + X[W] = '\n'; + Z[W << 1] = T++; + Z[(W++ << 1) | 1] = ab; + } + break; + case 3: + case 4: + var ac = ab.nodeValue; + if (ac.length) { + if (!Y) { + ac = ac.replace(/[ \t\r\n]+/g, ' '); + } else { + ac = ac.replace(/\r\n?/g, '\n'); + } + X[W] = ac; + Z[W << 1] = T; + T += ac.length; + Z[(W++ << 1) | 1] = ab; + } + break; + } + } + aa(V); + return { sourceCode: X.join('').replace(/\n$/, ''), spans: Z }; + } + function B(S, U, W, T) { + if (!U) { + return; + } + var V = { sourceCode: U, basePos: S }; + W(V); + T.push.apply(T, V.decorations); + } + var v = /\S/; + function o(S) { + var V = undefined; + for (var U = S.firstChild; U; U = U.nextSibling) { + var T = U.nodeType; + V = T === 1 ? (V ? S : U) : T === 3 ? (v.test(U.nodeValue) ? S : V) : V; + } + return V === S ? undefined : V; + } + function g(U, T) { + var S = {}; + var V; + (function () { + var ad = U.concat(T); + var ah = []; + var ag = {}; + for (var ab = 0, Z = ad.length; ab < Z; ++ab) { + var Y = ad[ab]; + var ac = Y[3]; + if (ac) { + for (var ae = ac.length; --ae >= 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 < aq; ++ae) { + var ag = an[ae]; + var ap = aj[ag]; + var ai = void 0; + var am; + if (typeof ap === 'string') { + am = false; + } else { + var aa = S[ag.charAt(0)]; + if (aa) { + ai = ag.match(aa[1]); + ap = aa[0]; + } else { + for (var ao = 0; ao < X; ++ao) { + aa = T[ao]; + ai = ag.match(aa[1]); + if (ai) { + ap = aa[0]; + break; + } + } + if (!ai) { + ap = F; + } + } + am = ap.length >= 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 < W.length; ++Y) { + ae(W[Y]); + } + if (ag === (ag | 0)) { + W[0].setAttribute('value', ag); + } + var aa = ac.createElement('OL'); + aa.className = 'linenums'; + var X = Math.max(0, (ag - 1) | 0) || 0; + for (var Y = 0, T = W.length; Y < T; ++Y) { + af = W[Y]; + af.className = 'L' + ((Y + X) % 10); + if (!af.firstChild) { + af.appendChild(ac.createTextNode('\xA0')); + } + aa.appendChild(af); + } + V.appendChild(aa); + } + function D(ac) { + var aj = /\bMSIE\b/.test(navigator.userAgent); + var am = /\n/g; + var al = ac.sourceCode; + var an = al.length; + var V = 0; + var aa = ac.spans; + var T = aa.length; + var ah = 0; + var X = ac.decorations; + var Y = X.length; + var Z = 0; + X[Y] = an; + var ar, aq; + for (aq = ar = 0; aq < Y; ) { + if (X[aq] !== X[aq + 2]) { + X[ar++] = X[aq++]; + X[ar++] = X[aq++]; + } else { + aq += 2; + } + } + Y = ar; + for (aq = ar = 0; aq < Y; ) { + var at = X[aq]; + var ab = X[aq + 1]; + var W = aq + 2; + while (W + 2 <= Y && X[W + 1] === ab) { + W += 2; + } + X[ar++] = at; + X[ar++] = ab; + aq = W; + } + Y = X.length = ar; + var ae = null; + while (ah < T) { + var af = aa[ah]; + var S = aa[ah + 2] || an; + var ag = X[Z]; + var ap = X[Z + 2] || an; + var W = Math.min(S, ap); + var ak = aa[ah + 1]; + var U; + if (ak.nodeType !== 1 && (U = al.substring(V, W))) { + if (aj) { + U = U.replace(am, '\r'); + } + ak.nodeValue = U; + var ai = ak.ownerDocument; + var ao = ai.createElement('SPAN'); + ao.className = X[Z + 1]; + var ad = ak.parentNode; + ad.replaceChild(ao, ak); + ao.appendChild(ak); + if (V < S) { + aa[ah + 1] = ak = ai.createTextNode(al.substring(W, S)); + ad.insertBefore(ak, ao.nextSibling); + } + } + V = W; + if (V >= 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 < ac.length; ++aa) { + for (var Z = 0, V = ac[aa].length; Z < V; ++Z) { + T.push(ac[aa][Z]); + } + } + ac = null; + var W = Date; + if (!W.now) { + W = { + now: function () { + return +new Date(); + }, + }; + } + var X = 0; + var S; + var ab = /\blang(?:uage)?-([\w.]+)(?!\S)/; + var ae = /\bprettyprint\b/; + function U() { + var ag = window.PR_SHOULD_USE_CONTINUATION ? W.now() + 250 : Infinity; + for (; X < T.length && W.now() < ag; X++) { + var aj = T[X]; + var ai = aj.className; + if (ai.indexOf('prettyprint') >= 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 < T.length) { + setTimeout(U, 250); + } else { + if (ad) { + ad(); + } + } + } + U(); + } + window.prettyPrintOne = y; + window.prettyPrint = b; + window.PR = { + createSimpleLexer: g, + registerLangHandler: c, + sourceDecorator: i, + PR_ATTRIB_NAME: P, + PR_ATTRIB_VALUE: n, + PR_COMMENT: j, + PR_DECLARATION: E, + PR_KEYWORD: z, + PR_LITERAL: G, + PR_NOCODE: N, + PR_PLAIN: F, + PR_PUNCTUATION: L, + PR_SOURCE: J, + PR_STRING: C, + PR_TAG: m, + PR_TYPE: O, + }; +})(); +PR.registerLangHandler( + PR.createSimpleLexer( + [], + [ + [PR.PR_DECLARATION, /^]*(?:>|$)/], + [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/frontend/coverage/sorter.js b/frontend/coverage/sorter.js index 4ed70ae..c3fbef4 100644 --- a/frontend/coverage/sorter.js +++ b/frontend/coverage/sorter.js @@ -1,210 +1,205 @@ -/* eslint-disable */ -var addSorting = (function() { - 'use strict'; - var cols, - currentSort = { - index: 0, - desc: false - }; + +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]; + // 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; } - 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; - 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'; - } - } + 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()); + } - // 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); + row.style.display = isMatch ? '' : 'none'; } - - // 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; + } + + // 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 + ''; + } } - // 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; + 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; } - // 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]); - } + 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]); - } + } + // 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); + }; } - // 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; + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); } - // adds sort indicators for current column being sorted - function addSortIndicators() { - getNthColumn(currentSort.index).className += currentSort.desc - ? ' sorted-desc' - : ' sorted'; + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); } - // 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)); - } - } + } + // 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(); - }; + } + // adds sorting functionality to the UI + return function () { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; })(); window.addEventListener('load', addSorting); diff --git a/frontend/coverage/src/App.css.html b/frontend/coverage/src/App.css.html index 213109e..a5aa753 100644 --- a/frontend/coverage/src/App.css.html +++ b/frontend/coverage/src/App.css.html @@ -1,68 +1,61 @@ - - - + Code coverage report for src/App.css - - - - -
-
+ + + +
+

All files / src App.css

-
- -
- 0% - Statements - 0/0 -
- - -
- 0% - Branches - 0/0 -
- - -
- 0% - Functions - 0/0 -
- - -
- 0% - Lines - 0/0 -
- - +
+
+ 0% + Statements + 0/0 +
+ +
+ 0% + Branches + 0/0 +
+ +
+ 0% + Functions + 0/0 +
+ +
+ 0% + Lines + 0/0 +

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

-
-
-

+      
+      
+
1 2 3 @@ -374,21 +367,22 @@ body { border-color: rgba(255, 255, 255, 0.5); }
-
-
- - - - - - +
+ +
+ + + + + + + - \ No newline at end of file diff --git a/frontend/coverage/src/App.tsx.html b/frontend/coverage/src/App.tsx.html index 0d5195d..ed02366 100644 --- a/frontend/coverage/src/App.tsx.html +++ b/frontend/coverage/src/App.tsx.html @@ -1,68 +1,61 @@ - - - + Code coverage report for src/App.tsx - - - - -
-
+ + + +
+

All files / src App.tsx

-
- -
- 78.94% - Statements - 15/19 -
- - -
- 66.66% - Branches - 4/6 -
- - -
- 77.77% - Functions - 7/9 -
- - -
- 78.94% - Lines - 15/19 -
- - +
+
+ 78.94% + Statements + 15/19 +
+ +
+ 66.66% + Branches + 4/6 +
+ +
+ 77.77% + Functions + 7/9 +
+ +
+ 78.94% + Lines + 15/19 +

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

-
-
-

+      
+      
+
1 2 3 @@ -335,21 +328,22 @@ function App() { export default App;  
-
-
- - - - - - +
+ +
+ + + + + + + - \ No newline at end of file diff --git a/frontend/coverage/src/components/FeedItem.css.html b/frontend/coverage/src/components/FeedItem.css.html index dd239a3..f6fe1a3 100644 --- a/frontend/coverage/src/components/FeedItem.css.html +++ b/frontend/coverage/src/components/FeedItem.css.html @@ -1,68 +1,64 @@ - - - + Code coverage report for src/components/FeedItem.css - - - - -
-
-

All files / src/components FeedItem.css

-
- -
- 0% - Statements - 0/0 -
- - -
- 0% - Branches - 0/0 -
- - -
- 0% - Functions - 0/0 -
- - -
- 0% - Lines - 0/0 -
- - + + + +
+
+

+ All files / + src/components FeedItem.css +

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

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

-
-
-

+      
+      
+
1 2 3 @@ -413,21 +409,22 @@ margin-left: 0; }
-
-
- - - - - - +
+ +
+ + + + + + + - \ No newline at end of file diff --git a/frontend/coverage/src/components/FeedItem.tsx.html b/frontend/coverage/src/components/FeedItem.tsx.html index f6b08e5..5512b78 100644 --- a/frontend/coverage/src/components/FeedItem.tsx.html +++ b/frontend/coverage/src/components/FeedItem.tsx.html @@ -1,68 +1,64 @@ - - - + Code coverage report for src/components/FeedItem.tsx - - - - -
-
-

All files / src/components FeedItem.tsx

-
- -
- 78.94% - Statements - 15/19 -
- - -
- 88.88% - Branches - 16/18 -
- - -
- 85.71% - Functions - 6/7 -
- - -
- 78.94% - Lines - 15/19 -
- - + + + +
+
+

+ All files / + src/components FeedItem.tsx +

+
+
+ 78.94% + Statements + 15/19 +
+ +
+ 88.88% + Branches + 16/18 +
+ +
+ 85.71% + Functions + 6/7 +
+ +
+ 78.94% + Lines + 15/19 +

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

-
-
-

+      
+      
+
1 2 3 @@ -317,21 +313,22 @@ export default function FeedItem({ item: initialItem }: FeedItemProps) { }  
-
-
- - - - - - +
+ +
+ + + + + + + - \ No newline at end of file diff --git a/frontend/coverage/src/components/FeedItems.css.html b/frontend/coverage/src/components/FeedItems.css.html index b5d2c41..0b1c77d 100644 --- a/frontend/coverage/src/components/FeedItems.css.html +++ b/frontend/coverage/src/components/FeedItems.css.html @@ -1,68 +1,64 @@ - - - + Code coverage report for src/components/FeedItems.css - - - - -
-
-

All files / src/components FeedItems.css

-
- -
- 0% - Statements - 0/0 -
- - -
- 0% - Branches - 0/0 -
- - -
- 0% - Functions - 0/0 -
- - -
- 0% - Lines - 0/0 -
- - + + + +
+
+

+ All files / + src/components FeedItems.css +

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

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

-
-
-

+      
+      
+
1 2 3 @@ -155,21 +151,22 @@ min-height: 50px; }
-
-
- - - - - - +
+ +
+ + + + + + + - \ No newline at end of file diff --git a/frontend/coverage/src/components/FeedItems.tsx.html b/frontend/coverage/src/components/FeedItems.tsx.html index e0f73cf..e57acf9 100644 --- a/frontend/coverage/src/components/FeedItems.tsx.html +++ b/frontend/coverage/src/components/FeedItems.tsx.html @@ -1,68 +1,64 @@ - - - + Code coverage report for src/components/FeedItems.tsx - - - - -
-
-

All files / src/components FeedItems.tsx

-
- -
- 89.34% - Statements - 109/122 -
- - -
- 77.21% - Branches - 61/79 -
- - -
- 86.2% - Functions - 25/29 -
- - -
- 89.09% - Lines - 98/110 -
- - + + + +
+
+

+ All files / + src/components FeedItems.tsx +

+
+
+ 89.34% + Statements + 109/122 +
+ +
+ 77.21% + Branches + 61/79 +
+ +
+ 86.2% + Functions + 25/29 +
+ +
+ 89.09% + Lines + 98/110 +

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

-
-
-

+      
+      
+
1 2 3 @@ -734,21 +730,22 @@ export default function FeedItems() { }  
-
-
- - - - - - +
+ +
+ + + + + + + - \ No newline at end of file diff --git a/frontend/coverage/src/components/FeedList.css.html b/frontend/coverage/src/components/FeedList.css.html index ffaa1a9..fe60b9d 100644 --- a/frontend/coverage/src/components/FeedList.css.html +++ b/frontend/coverage/src/components/FeedList.css.html @@ -1,68 +1,64 @@ - - - + Code coverage report for src/components/FeedList.css - - - - -
-
-

All files / src/components FeedList.css

-
- -
- 0% - Statements - 0/0 -
- - -
- 0% - Branches - 0/0 -
- - -
- 0% - Functions - 0/0 -
- - -
- 0% - Lines - 0/0 -
- - + + + +
+
+

+ All files / + src/components FeedList.css +

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

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

-
-
-

+      
+      
+
1 2 3 @@ -356,21 +352,22 @@ background-color: transparent; }
-
-
- - - - - - +
+ +
+ + + + + + + - \ No newline at end of file diff --git a/frontend/coverage/src/components/FeedList.tsx.html b/frontend/coverage/src/components/FeedList.tsx.html index b1b0a27..ba7d81f 100644 --- a/frontend/coverage/src/components/FeedList.tsx.html +++ b/frontend/coverage/src/components/FeedList.tsx.html @@ -1,68 +1,64 @@ - - - + Code coverage report for src/components/FeedList.tsx - - - - -
-
-

All files / src/components FeedList.tsx

-
- -
- 91.66% - Statements - 22/24 -
- - -
- 82.35% - Branches - 14/17 -
- - -
- 100% - Functions - 8/8 -
- - -
- 100% - Lines - 20/20 -
- - + + + +
+
+

+ All files / + src/components FeedList.tsx +

+
+
+ 91.66% + Statements + 22/24 +
+ +
+ 82.35% + Branches + 14/17 +
+ +
+ 100% + Functions + 8/8 +
+ +
+ 100% + Lines + 20/20 +

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

-
-
-

+      
+      
+
1 2 3 @@ -305,21 +301,22 @@ export default function FeedList() { }  
-
-
- - - - - - +
+ +
+ + + + + + + - \ No newline at end of file diff --git a/frontend/coverage/src/components/Login.css.html b/frontend/coverage/src/components/Login.css.html index 2b2fe0d..bb3654e 100644 --- a/frontend/coverage/src/components/Login.css.html +++ b/frontend/coverage/src/components/Login.css.html @@ -1,68 +1,64 @@ - - - + Code coverage report for src/components/Login.css - - - - -
-
-

All files / src/components Login.css

-
- -
- 0% - Statements - 0/0 -
- - -
- 0% - Branches - 0/0 -
- - -
- 0% - Functions - 0/0 -
- - -
- 0% - Lines - 0/0 -
- - + + + +
+
+

+ All files / + src/components Login.css +

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

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

-
-
-

+      
+      
+
1 2 3 @@ -254,21 +250,22 @@ button[type="submit"]:hover { }  
-
-
- - - - - - +
+ +
+ + + + + + + - \ No newline at end of file diff --git a/frontend/coverage/src/components/Login.tsx.html b/frontend/coverage/src/components/Login.tsx.html index 263fe57..f29e3cb 100644 --- a/frontend/coverage/src/components/Login.tsx.html +++ b/frontend/coverage/src/components/Login.tsx.html @@ -1,68 +1,64 @@ - - - + Code coverage report for src/components/Login.tsx - - - - -
-
-

All files / src/components Login.tsx

-
- -
- 100% - Statements - 17/17 -
- - -
- 83.33% - Branches - 5/6 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 17/17 -
- - + + + +
+
+

+ All files / + src/components Login.tsx +

+
+
+ 100% + Statements + 17/17 +
+ +
+ 83.33% + Branches + 5/6 +
+ +
+ 100% + Functions + 3/3 +
+ +
+ 100% + Lines + 17/17 +

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

-
-
-

+      
+      
+
1 2 3 @@ -227,21 +223,22 @@ export default function Login() { }  
-
-
- - - - - - +
+ +
+ + + + + + + - \ No newline at end of file diff --git a/frontend/coverage/src/components/Settings.css.html b/frontend/coverage/src/components/Settings.css.html index 428c9d2..6a1155e 100644 --- a/frontend/coverage/src/components/Settings.css.html +++ b/frontend/coverage/src/components/Settings.css.html @@ -1,68 +1,64 @@ - - - + Code coverage report for src/components/Settings.css - - - - -
-
-

All files / src/components Settings.css

-
- -
- 0% - Statements - 0/0 -
- - -
- 0% - Branches - 0/0 -
- - -
- 0% - Functions - 0/0 -
- - -
- 0% - Lines - 0/0 -
- - + + + +
+
+

+ All files / + src/components Settings.css +

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

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

-
-
-

+      
+      
+
1 2 3 @@ -311,21 +307,22 @@ cursor: not-allowed; }
-
-
- - - - - - +
+ +
+ + + + + + + - \ No newline at end of file diff --git a/frontend/coverage/src/components/Settings.tsx.html b/frontend/coverage/src/components/Settings.tsx.html index 62ca241..df6d027 100644 --- a/frontend/coverage/src/components/Settings.tsx.html +++ b/frontend/coverage/src/components/Settings.tsx.html @@ -1,68 +1,64 @@ - - - + Code coverage report for src/components/Settings.tsx - - - - -
-
-

All files / src/components Settings.tsx

-
- -
- 75.55% - Statements - 34/45 -
- - -
- 56.25% - Branches - 9/16 -
- - -
- 82.35% - Functions - 14/17 -
- - -
- 84.61% - Lines - 33/39 -
- - + + + +
+
+

+ All files / + src/components Settings.tsx +

+
+
+ 75.55% + Statements + 34/45 +
+ +
+ 56.25% + Branches + 9/16 +
+ +
+ 82.35% + Functions + 14/17 +
+ +
+ 84.61% + Lines + 33/39 +

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

-
-
-

+      
+      
+
1 2 3 @@ -428,21 +424,22 @@ export default function Settings() { }  
-
-
- - - - - - +
+ +
+ + + + + + + - \ No newline at end of file diff --git a/frontend/coverage/src/components/index.html b/frontend/coverage/src/components/index.html index 534e353..7e1a0b7 100644 --- a/frontend/coverage/src/components/index.html +++ b/frontend/coverage/src/components/index.html @@ -1,251 +1,303 @@ - - - + Code coverage report for src/components - - - - -
-
+ + + +
+

All files src/components

-
- -
- 86.78% - Statements - 197/227 -
- - -
- 77.2% - Branches - 105/136 -
- - -
- 87.5% - Functions - 56/64 -
- - -
- 89.26% - Lines - 183/205 -
- - +
+
+ 86.78% + Statements + 197/227 +
+ +
+ 77.2% + Branches + 105/136 +
+ +
+ 87.5% + Functions + 56/64 +
+ +
+ 89.26% + Lines + 183/205 +

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

-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
+
FileStatementsBranchesFunctionsLines
FeedItem.css -
-
0%0/00%0/00%0/00%0/0
FeedItem.tsx -
-
78.94%15/1988.88%16/1885.71%6/778.94%15/19
+ + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - -
File + Statements + BranchesFunctionsLines
+ FeedItem.css + +
+
+
+
+
0%0/00%0/00%0/00%0/0
FeedItems.css -
-
0%0/00%0/00%0/00%0/0
+ FeedItem.tsx + +
+
+
+
+
78.94%15/1988.88%16/1885.71%6/778.94%15/19
FeedItems.tsx -
-
89.34%109/12277.21%61/7986.2%25/2989.09%98/110
+ FeedItems.css + +
+
+
+
+
0%0/00%0/00%0/00%0/0
FeedList.css -
-
0%0/00%0/00%0/00%0/0
+ FeedItems.tsx + +
+
+
+
+
89.34%109/12277.21%61/7986.2%25/2989.09%98/110
FeedList.tsx -
-
91.66%22/2482.35%14/17100%8/8100%20/20
+ FeedList.css + +
+
+
+
+
0%0/00%0/00%0/00%0/0
Login.css -
-
0%0/00%0/00%0/00%0/0
+ FeedList.tsx + +
+
+
+
+
91.66%22/2482.35%14/17100%8/8100%20/20
Login.tsx -
-
100%17/1783.33%5/6100%3/3100%17/17
+ Login.css + +
+
+
+
+
0%0/00%0/00%0/00%0/0
Settings.css -
-
0%0/00%0/00%0/00%0/0
+ Login.tsx + +
+
+
+
+
100%17/1783.33%5/6100%3/3100%17/17
Settings.tsx -
-
75.55%34/4556.25%9/1682.35%14/1784.61%33/39
+ Settings.css + +
+
+
+
+
0%0/00%0/00%0/00%0/0
-
-
-
- - - - - - + + + Settings.tsx + + +
+
+
+
+ + 75.55% + 34/45 + 56.25% + 9/16 + 82.35% + 14/17 + 84.61% + 33/39 + + + +
+
+ +
+ + + + + + + - \ No newline at end of file diff --git a/frontend/coverage/src/index.html b/frontend/coverage/src/index.html index 023e9bb..14e9f51 100644 --- a/frontend/coverage/src/index.html +++ b/frontend/coverage/src/index.html @@ -1,131 +1,139 @@ - - - + Code coverage report for src - - - - -
-
+ + + +
+

All files src

-
- -
- 78.94% - Statements - 15/19 -
- - -
- 66.66% - Branches - 4/6 -
- - -
- 77.77% - Functions - 7/9 -
- - -
- 78.94% - Lines - 15/19 -
- - +
+
+ 78.94% + Statements + 15/19 +
+ +
+ 66.66% + Branches + 4/6 +
+ +
+ 77.77% + Functions + 7/9 +
+ +
+ 78.94% + Lines + 15/19 +

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

-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
+
+
FileStatementsBranchesFunctionsLines
App.css -
-
0%0/00%0/00%0/00%0/0
App.tsx -
-
78.94%15/1966.66%4/677.77%7/978.94%15/19
+ + + + + + + + + + + + + + + + + + + + + + + + + + + - -
File + Statements + BranchesFunctionsLines
App.css +
+
+
+
+
0%0/00%0/00%0/00%0/0
-
-
-
- - - - - - + + App.tsx + +
+
+
+
+ + 78.94% + 15/19 + 66.66% + 4/6 + 77.77% + 7/9 + 78.94% + 15/19 + + + +
+
+ +
+ + + + + + + - \ No newline at end of file diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 5e6b472..043ab7a 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,23 +1,27 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import tseslint from 'typescript-eslint' -import { defineConfig, globalIgnores } from 'eslint/config' +import js from '@eslint/js'; +import globals from 'globals'; +import reactHooks from 'eslint-plugin-react-hooks'; +import reactRefresh from 'eslint-plugin-react-refresh'; +import tseslint from 'typescript-eslint'; +import eslintConfigPrettier from 'eslint-config-prettier'; -export default defineConfig([ - globalIgnores(['dist']), +export default tseslint.config( + { ignores: ['dist'] }, { + extends: [js.configs.recommended, ...tseslint.configs.recommended], files: ['**/*.{ts,tsx}'], - extends: [ - js.configs.recommended, - tseslint.configs.recommended, - reactHooks.configs.flat.recommended, - reactRefresh.configs.vite, - ], languageOptions: { ecmaVersion: 2020, globals: globals.browser, }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], + }, }, -]) + eslintConfigPrettier +); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9414557..fda0116 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -23,10 +23,13 @@ "@vitejs/plugin-react": "^5.1.1", "@vitest/coverage-v8": "^4.0.18", "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", "jsdom": "^28.0.0", + "prettier": "^3.8.1", "typescript": "~5.9.3", "typescript-eslint": "^8.48.0", "vite": "^7.3.1", @@ -1174,6 +1177,18 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@playwright/test": { "version": "1.58.2", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", @@ -2651,6 +2666,51 @@ } } }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.5.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.5.tgz", + "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.1", + "synckit": "^0.11.12" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-plugin-react-hooks": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", @@ -2790,6 +2850,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -3568,6 +3634,33 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.1.tgz", + "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -3872,6 +3965,21 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/synckit": { + "version": "0.11.12", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.12.tgz", + "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index e2f7c1c..e5475dd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "vite", "build": "tsc -b && vite build", - "lint": "eslint .", + "lint": "eslint . --max-warnings 0", + "format": "prettier --write .", "preview": "vite preview", "test": "vitest", "test:e2e": "playwright test", @@ -28,13 +29,16 @@ "@vitejs/plugin-react": "^5.1.1", "@vitest/coverage-v8": "^4.0.18", "eslint": "^9.39.1", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-prettier": "^5.5.5", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", "jsdom": "^28.0.0", + "prettier": "^3.8.1", "typescript": "~5.9.3", "typescript-eslint": "^8.48.0", "vite": "^7.3.1", "vitest": "^4.0.18" } -} \ No newline at end of file +} diff --git a/frontend/playwright-report/index.html b/frontend/playwright-report/index.html index 4095719..1d408fd 100644 --- a/frontend/playwright-report/index.html +++ b/frontend/playwright-report/index.html @@ -1,85 +1,21807 @@ - - - - + + - - - + + + Playwright Test Report - - +`.trimStart(); + async function zv({ + testInfo: l, + metadata: u, + errorContext: c, + errors: f, + buildCodeFrame: r, + stdout: o, + stderr: h, + }) { + var S; + const v = new Set( + f + .filter( + (O) => + O.message && + !O.message.includes(` +`) + ) + .map((O) => O.message) + ); + for (const O of f) + for (const X of v.keys()) (S = O.message) != null && S.includes(X) && v.delete(X); + const y = f.filter( + (O) => + !( + !O.message || + (!O.message.includes(` +`) && + !v.has(O.message)) + ) + ); + if (!y.length) return; + const A = [Qv, '# Test info', '', l]; + (o && A.push('', '# Stdout', '', '```', Jf(o), '```'), + h && A.push('', '# Stderr', '', '```', Jf(h), '```'), + A.push('', '# Error details')); + for (const O of y) A.push('', '```', Jf(O.message || ''), '```'); + c && A.push(c); + const E = await r(y[y.length - 1]); + return ( + E && A.push('', '# Test source', '', '```ts', E, '```'), + u != null && u.gitDiff && A.push('', '# Local changes', '', '```diff', u.gitDiff, '```'), + A.join(` +`) + ); + } + const Yv = new RegExp( + '([\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~])))', + 'g' + ); + function Jf(l) { + return l.replace(Yv, ''); + } + function Lv(l, u) { + var f; + const c = new Map(); + for (const r of l) { + const o = r.name.match(/^(.*)-(expected|actual|diff|previous)(\.[^.]+)?$/); + if (!o) continue; + const [, h, v, y = ''] = o, + A = h + y; + let E = c.get(A); + (E || ((E = { name: A, anchors: [`attachment-${h}`] }), c.set(A, E)), + E.anchors.push(`attachment-${u.attachments.indexOf(r)}`), + v === 'actual' && (E.actual = { attachment: r }), + v === 'expected' && (E.expected = { attachment: r, title: 'Expected' }), + v === 'previous' && (E.expected = { attachment: r, title: 'Previous' }), + v === 'diff' && (E.diff = { attachment: r })); + } + for (const [r, o] of c) + !o.actual || !o.expected + ? c.delete(r) + : (l.delete(o.actual.attachment), + l.delete(o.expected.attachment), + l.delete((f = o.diff) == null ? void 0 : f.attachment)); + return [...c.values()]; + } + const Gv = ({ test: l, result: u, testRunMetadata: c, options: f }) => { + const { + screenshots: r, + videos: o, + traces: h, + otherAttachments: v, + diffs: y, + errors: A, + otherAttachmentAnchors: E, + screenshotAnchors: S, + errorContext: O, + } = ct.useMemo(() => { + const B = u.attachments.filter((N) => !N.name.startsWith('_')), + b = new Set(B.filter((N) => N.contentType.startsWith('image/'))), + p = [...b].map((N) => `attachment-${B.indexOf(N)}`), + x = B.filter((N) => N.contentType.startsWith('video/')), + R = B.filter((N) => N.name === 'trace'), + U = B.find((N) => N.name === 'error-context'), + Z = new Set(B); + [...b, ...x, ...R].forEach((N) => Z.delete(N)); + const F = [...Z].map((N) => `attachment-${B.indexOf(N)}`), + j = Lv(b, u), + D = u.errors.map((N) => N.message); + return { + screenshots: [...b], + videos: x, + traces: R, + otherAttachments: Z, + diffs: j, + errors: D, + otherAttachmentAnchors: F, + screenshotAnchors: p, + errorContext: U, + }; + }, [u]), + X = P5( + async () => { + if (f != null && f.noCopyPrompt) return; + const B = u.attachments.find((R) => R.name === 'stdout'), + b = u.attachments.find((R) => R.name === 'stderr'), + p = B != null && B.body && B.contentType === 'text/plain' ? B.body : void 0, + x = b != null && b.body && b.contentType === 'text/plain' ? b.body : void 0; + return await zv({ + testInfo: [ + `- Name: ${l.path.join(' >> ')} >> ${l.title}`, + `- Location: ${l.location.file}:${l.location.line}:${l.location.column}`, + ].join(` +`), + metadata: c, + errorContext: + O != null && O.path + ? await fetch(O.path).then((R) => R.text()) + : O == null + ? void 0 + : O.body, + errors: u.errors, + buildCodeFrame: async (R) => R.codeframe, + stdout: p, + stderr: x, + }); + }, + [l, O, c, u], + void 0 + ); + return m.jsxs('div', { + className: 'test-result', + children: [ + !!A.length && + m.jsxs(ke, { + header: 'Errors', + children: [ + X && + m.jsx('div', { + style: { position: 'absolute', right: '16px', padding: '10px', zIndex: 1 }, + children: m.jsx(Nv, { prompt: X }), + }), + A.map((B, b) => { + const p = Xv(B, y); + return m.jsxs(m.Fragment, { + children: [ + m.jsx(wr, { code: B }, 'test-result-error-message-' + b), + p && m.jsx(Bv, { diff: p }), + ], + }); + }), + ], + }), + !!u.steps.length && + m.jsx(ke, { + header: 'Test Steps', + children: u.steps.map((B, b) => + m.jsx(cm, { step: B, result: u, test: l, depth: 0 }, `step-${b}`) + ), + }), + y.map((B, b) => + m.jsx( + Si, + { + id: B.anchors, + children: m.jsx(ke, { + dataTestId: 'test-results-image-diff', + header: `Image mismatch: ${B.name}`, + revealOnAnchorId: B.anchors, + children: m.jsx(um, { diff: B }), + }), + }, + `diff-${b}` + ) + ), + !!r.length && + m.jsx(ke, { + header: 'Screenshots', + revealOnAnchorId: S, + children: r.map((B, b) => + m.jsxs( + Si, + { + id: `attachment-${u.attachments.indexOf(B)}`, + children: [ + m.jsx('a', { + href: Ve(B.path), + children: m.jsx('img', { className: 'screenshot', src: Ve(B.path) }), + }), + m.jsx(nc, { attachment: B, result: u }), + ], + }, + `screenshot-${b}` + ) + ), + }), + !!h.length && + m.jsx(Si, { + id: 'attachment-trace', + children: m.jsx(ke, { + header: 'Traces', + revealOnAnchorId: 'attachment-trace', + children: m.jsxs('div', { + children: [ + m.jsx('a', { + href: Ve(nm(h)), + children: m.jsx('img', { + className: 'screenshot', + src: Cv, + style: { width: 192, height: 117, marginLeft: 20 }, + }), + }), + h.map((B, b) => + m.jsx( + nc, + { + attachment: B, + result: u, + linkName: h.length === 1 ? 'trace' : `trace-${b + 1}`, + }, + `trace-${b}` + ) + ), + ], + }), + }), + }), + !!o.length && + m.jsx(Si, { + id: 'attachment-video', + children: m.jsx(ke, { + header: 'Videos', + revealOnAnchorId: 'attachment-video', + children: o.map((B) => + m.jsxs( + 'div', + { + children: [ + m.jsx('video', { + controls: !0, + children: m.jsx('source', { src: Ve(B.path), type: B.contentType }), + }), + m.jsx(nc, { attachment: B, result: u }), + ], + }, + B.path + ) + ), + }), + }), + !!v.size && + m.jsx(ke, { + header: 'Attachments', + revealOnAnchorId: E, + dataTestId: 'attachments', + children: [...v].map((B, b) => + m.jsx( + Si, + { + id: `attachment-${u.attachments.indexOf(B)}`, + children: m.jsx(nc, { + attachment: B, + result: u, + openInNewTab: B.contentType.startsWith('text/html'), + }), + }, + `attachment-link-${b}` + ) + ), + }), + ], + }); + }; + function Xv(l, u) { + const c = l.split(` +`)[0]; + if (!(!c.includes('toHaveScreenshot') && !c.includes('toMatchSnapshot'))) + return u.find((f) => l.includes(f.name)); + } + const cm = ({ test: l, step: u, result: c, depth: f }) => { + const r = se(); + return m.jsx(Tv, { + title: m.jsxs('div', { + 'aria-label': u.title, + className: 'step-title-container', + children: [ + hc(u.error || u.duration === -1 ? 'failed' : u.skipped ? 'skipped' : 'passed'), + m.jsxs('span', { + className: 'step-title-text', + children: [ + m.jsx('span', { children: u.title }), + u.count > 1 && + m.jsxs(m.Fragment, { + children: [ + ' ✕ ', + m.jsx('span', { className: 'test-result-counter', children: u.count }), + ], + }), + u.location && + m.jsxs('span', { + className: 'test-result-path', + children: ['— ', u.location.file, ':', u.location.line], + }), + ], + }), + m.jsx('span', { className: 'step-spacer' }), + u.attachments.length > 0 && + m.jsx('a', { + className: 'step-attachment-link', + title: 'reveal attachment', + href: Ve( + Cn({ test: l, result: c, anchor: `attachment-${u.attachments[0]}` }, r) + ), + onClick: (o) => { + o.stopPropagation(); + }, + children: Ih(), + }), + m.jsx('span', { className: 'step-duration', children: Ol(u.duration) }), + ], + }), + loadChildren: + u.steps.length || u.snippet + ? () => { + const o = u.snippet + ? [m.jsx(wr, { testId: 'test-snippet', code: u.snippet }, 'line')] + : [], + h = u.steps.map((v, y) => + m.jsx(cm, { step: v, depth: f + 1, result: c, test: l }, y) + ); + return o.concat(h); + } + : void 0, + depth: f, + }); + }, + Vv = ({ + projectNames: l, + test: u, + testRunMetadata: c, + run: f, + next: r, + prev: o, + options: h, + }) => { + const [v, y] = ct.useState(f), + A = se(), + E = u.annotations.filter((S) => !S.type.startsWith('_')) ?? []; + return m.jsxs(m.Fragment, { + children: [ + m.jsx(Or, { + title: u.title, + leftSuperHeader: m.jsx('div', { + className: 'test-case-path', + children: u.path.join(' › '), + }), + rightSuperHeader: m.jsxs(m.Fragment, { + children: [ + m.jsx('div', { + className: Ze(!o && 'hidden'), + children: m.jsx(Tn, { href: Cn({ test: o }, A), children: '« previous' }), + }), + m.jsx('div', { style: { width: 10 } }), + m.jsx('div', { + className: Ze(!r && 'hidden'), + children: m.jsx(Tn, { href: Cn({ test: r }, A), children: 'next »' }), + }), + ], + }), + }), + m.jsxs('div', { + className: 'hbox', + style: { lineHeight: '24px' }, + children: [ + m.jsx('div', { + className: 'test-case-location', + children: m.jsxs(Sr, { + value: `${u.location.file}:${u.location.line}`, + children: [u.location.file, ':', u.location.line], + }), + }), + m.jsx('div', { style: { flex: 'auto' } }), + m.jsx(tm, { test: u, trailingSeparator: !0 }), + m.jsx('div', { className: 'test-case-duration', children: Ol(u.duration) }), + ], + }), + m.jsx($h, { + style: { marginLeft: '6px' }, + projectNames: l, + activeProjectName: u.projectName, + otherLabels: u.tags, + }), + u.results.length === 0 && + E.length !== 0 && + m.jsx(ke, { + header: 'Annotations', + dataTestId: 'test-case-annotations', + children: E.map((S, O) => m.jsx(z2, { annotation: S }, O)), + }), + m.jsx(Sv, { + tabs: + u.results.map((S, O) => ({ + id: String(O), + title: m.jsxs('div', { + style: { display: 'flex', alignItems: 'center' }, + children: [ + hc(S.status), + ' ', + Zv(O), + u.results.length > 1 && + m.jsx('span', { + className: 'test-case-run-duration', + children: Ol(S.duration), + }), + ], + }), + render: () => { + const X = S.annotations.filter((B) => !B.type.startsWith('_')); + return m.jsxs(m.Fragment, { + children: [ + !!X.length && + m.jsx(ke, { + header: 'Annotations', + dataTestId: 'test-case-annotations', + children: X.map((B, b) => m.jsx(z2, { annotation: B }, b)), + }), + m.jsx(Gv, { test: u, result: S, testRunMetadata: c, options: h }), + ], + }); + }, + })) || [], + selectedTab: String(v), + setSelectedTab: (S) => y(+S), + }), + ], + }); + }; + function z2({ annotation: { type: l, description: u } }) { + return m.jsxs('div', { + className: 'test-case-annotation', + children: [ + m.jsx('span', { style: { fontWeight: 'bold' }, children: l }), + u && m.jsxs(Sr, { value: u, children: [': ', Di(u)] }), + ], + }); + } + function Zv(l) { + return l ? `Retry #${l}` : 'Run'; + } + const sm = ({ + file: l, + projectNames: u, + isFileExpanded: c, + setFileExpanded: f, + footer: r, + }) => { + const o = se(); + return m.jsx(im, { + expanded: c ? c(l.fileId) : void 0, + noInsets: !0, + setExpanded: f ? (h) => f(l.fileId, h) : void 0, + header: m.jsx('span', { className: 'chip-header-allow-selection', children: l.fileName }), + footer: r, + children: l.tests.map((h) => + m.jsxs( + 'div', + { + className: Ze('test-file-test', 'test-file-test-outcome-' + h.outcome), + children: [ + m.jsxs('div', { + className: 'hbox', + style: { alignItems: 'flex-start' }, + children: [ + m.jsxs('div', { + className: 'hbox', + children: [ + m.jsx('span', { + className: 'test-file-test-status-icon', + children: hc(h.outcome), + }), + m.jsxs('span', { + children: [ + m.jsx(Tn, { + href: Cn({ test: h }, o), + title: [...h.path, h.title].join(' › '), + children: m.jsx('span', { + className: 'test-file-title', + children: [...h.path, h.title].join(' › '), + }), + }), + m.jsx($h, { + style: { marginLeft: '6px' }, + projectNames: u, + activeProjectName: h.projectName, + otherLabels: h.tags, + }), + ], + }), + ], + }), + m.jsx('span', { + 'data-testid': 'test-duration', + style: { minWidth: '50px', textAlign: 'right' }, + children: Ol(h.duration), + }), + ], + }), + m.jsx('div', { + className: 'test-file-details-row', + children: m.jsxs('div', { + className: 'test-file-details-row-items', + children: [ + m.jsx(Tn, { + href: Cn({ test: h }, o), + title: [...h.path, h.title].join(' › '), + className: 'test-file-path-link', + children: m.jsxs('span', { + className: 'test-file-path', + children: [h.location.file, ':', h.location.line], + }), + }), + m.jsx(qv, { test: h }), + m.jsx(Iv, { test: h }), + m.jsx(tm, { test: h, dim: !0 }), + ], + }), + }), + ], + }, + `test-${h.testId}` + ) + ), + }); + }; + function qv({ test: l }) { + const u = se(); + for (const c of l.results) + for (const f of c.attachments) + if (f.contentType.startsWith('image/') && f.name.match(/-(expected|actual|diff)/)) + return m.jsx(Tr, { + href: Cn( + { test: l, result: c, anchor: `attachment-${c.attachments.indexOf(f)}` }, + u + ), + title: 'View images', + dim: !0, + children: k5(), + }); + } + function Iv({ test: l }) { + const u = se(), + c = l.results.find((f) => f.attachments.some((r) => r.name === 'video')); + return c + ? m.jsx(Tr, { + href: Cn({ test: l, result: c, anchor: 'attachment-video' }, u), + title: 'View video', + dim: !0, + children: J5(), + }) + : void 0; + } + class Kv extends ct.Component { + constructor() { + super(...arguments); + yn(this, 'state', { error: null, errorInfo: null }); + } + componentDidCatch(c, f) { + this.setState({ error: c, errorInfo: f }); + } + render() { + var c, f, r; + return this.state.error || this.state.errorInfo + ? m.jsxs('div', { + className: 'metadata-view p-3', + children: [ + m.jsx('p', { + children: 'An error was encountered when trying to render metadata.', + }), + m.jsx('p', { + children: m.jsxs('pre', { + style: { overflow: 'scroll' }, + children: [ + (c = this.state.error) == null ? void 0 : c.message, + m.jsx('br', {}), + (f = this.state.error) == null ? void 0 : f.stack, + m.jsx('br', {}), + (r = this.state.errorInfo) == null ? void 0 : r.componentStack, + ], + }), + }), + ], + }) + : this.props.children; + } + } + const kv = (l) => m.jsx(Kv, { children: m.jsx(Jv, { metadata: l.metadata }) }), + Jv = (l) => { + const u = l.metadata, + c = se().has('show-metadata-other') + ? Object.entries(l.metadata).filter(([r]) => !fm.has(r)) + : []; + if (u.ci || u.gitCommit || c.length > 0) + return m.jsxs('div', { + className: 'metadata-view', + children: [ + u.ci && !u.gitCommit && m.jsx(Fv, { info: u.ci }), + u.gitCommit && m.jsx(Wv, { ci: u.ci, commit: u.gitCommit }), + c.length > 0 && + m.jsxs(m.Fragment, { + children: [ + (u.gitCommit || u.ci) && m.jsx('div', { className: 'metadata-separator' }), + m.jsx('div', { + className: 'metadata-section metadata-properties', + role: 'list', + children: c.map(([r, o]) => { + const h = + typeof o != 'object' || o === null || o === void 0 + ? String(o) + : JSON.stringify(o), + v = h.length > 1e3 ? h.slice(0, 1e3) + '…' : h; + return m.jsx( + 'div', + { + className: 'copyable-property', + role: 'listitem', + children: m.jsxs(Sr, { + value: h, + children: [ + m.jsx('span', { + style: { fontWeight: 'bold' }, + title: r, + children: r, + }), + ': ', + m.jsx('span', { title: v, children: Di(v) }), + ], + }), + }, + r + ); + }), + }), + ], + }), + ], + }); + }, + Fv = ({ info: l }) => { + const u = l.prTitle || `Commit ${l.commitHash}`, + c = l.prHref || l.commitHref; + return m.jsx('div', { + className: 'metadata-section', + role: 'list', + children: m.jsx('div', { + role: 'listitem', + children: m.jsx('a', { + href: Ve(c), + target: '_blank', + rel: 'noopener noreferrer', + title: u, + children: u, + }), + }), + }); + }, + Wv = ({ ci: l, commit: u }) => { + const c = (l == null ? void 0 : l.prTitle) || u.subject, + f = (l == null ? void 0 : l.prHref) || (l == null ? void 0 : l.commitHref), + r = ` <${u.author.email}>`, + o = `${u.author.name}${r}`, + h = Intl.DateTimeFormat(void 0, { dateStyle: 'medium' }).format(u.committer.time), + v = Intl.DateTimeFormat(void 0, { dateStyle: 'full', timeStyle: 'long' }).format( + u.committer.time + ); + return m.jsxs('div', { + className: 'metadata-section', + role: 'list', + children: [ + m.jsxs('div', { + role: 'listitem', + children: [ + f && + m.jsx('a', { + href: Ve(f), + target: '_blank', + rel: 'noopener noreferrer', + title: c, + children: c, + }), + !f && m.jsx('span', { title: c, children: c }), + ], + }), + m.jsxs('div', { + role: 'listitem', + className: 'hbox', + children: [ + m.jsx('span', { className: 'mr-1', children: o }), + m.jsxs('span', { title: v, children: [' on ', h] }), + ], + }), + ], + }); + }, + fm = new Set(['ci', 'gitCommit', 'gitDiff', 'actualWorkers']), + _v = (l) => { + const u = Object.entries(l).filter(([c]) => !fm.has(c)); + return !l.ci && !l.gitCommit && !u.length; + }, + Pv = ({ files: l, expandedFiles: u, setExpandedFiles: c, projectNames: f }) => { + const r = ct.useMemo(() => { + const o = []; + let h = 0; + for (const v of l) + ((h += v.tests.length), o.push({ file: v, defaultExpanded: h < 200 })); + return o; + }, [l]); + return m.jsx(m.Fragment, { + children: + r.length > 0 + ? r.map(({ file: o, defaultExpanded: h }) => + m.jsx( + sm, + { + file: o, + projectNames: f, + isFileExpanded: (v) => { + const y = u.get(v); + return y === void 0 ? h : !!y; + }, + setFileExpanded: (v, y) => { + const A = new Map(u); + (A.set(v, y), c(A)); + }, + }, + `file-${o.fileId}` + ) + ) + : m.jsx('div', { + className: 'chip-header test-file-no-files', + children: 'No tests found', + }), + }); + }, + Y2 = ({ report: l, filteredStats: u, metadataVisible: c, toggleMetadataVisible: f }) => { + if (!l) return null; + const r = l.projectNames.length === 1 && !!l.projectNames[0], + o = !r && !u, + h = + !_v(l.metadata) && + m.jsxs('div', { + className: Ze('metadata-toggle', !o && 'metadata-toggle-second-line'), + role: 'button', + onClick: f, + title: c ? 'Hide metadata' : 'Show metadata', + children: [c ? Ni() : Cl(), 'Metadata'], + }), + v = m.jsxs('div', { + className: 'test-file-header-info', + children: [ + r && + m.jsxs('div', { + 'data-testid': 'project-name', + children: ['Project: ', l.projectNames[0]], + }), + u && + m.jsxs('div', { + 'data-testid': 'filtered-tests-count', + children: ['Filtered: ', u.total, ' ', !!u.total && '(' + Ol(u.duration) + ')'], + }), + o && h, + ], + }), + y = m.jsxs(m.Fragment, { + children: [ + m.jsx('div', { + 'data-testid': 'overall-time', + style: { marginRight: '10px' }, + children: l ? new Date(l.startTime).toLocaleString() : '', + }), + m.jsxs('div', { + 'data-testid': 'overall-duration', + children: ['Total time: ', Ol(l.duration ?? 0)], + }), + ], + }); + return m.jsxs(m.Fragment, { + children: [ + m.jsx(Or, { title: l.options.title, leftSuperHeader: v, rightSuperHeader: y }), + !o && h, + c && m.jsx(kv, { metadata: l.metadata }), + !!l.errors.length && + m.jsx(ke, { + header: 'Errors', + dataTestId: 'report-errors', + children: l.errors.map((A, E) => + m.jsx(wr, { code: A }, 'test-report-error-message-' + E) + ), + }), + ], + }); + }, + rm = (l) => { + const u = Math.round(l / 1e3), + c = Math.floor(u / 60), + f = u % 60; + return c === 0 ? `${f}s` : `${c}m ${f}s`; + }, + $v = ({ entries: l }) => { + const f = Math.max(...l.map((D) => D.label.length)) * 10, + o = { top: 20, right: 20, bottom: 40, left: Math.min(800 * 0.5, Math.max(50, f)) }, + h = 800 - o.left - o.right, + v = Math.min(...l.map((D) => D.startTime)), + y = Math.max(...l.map((D) => D.startTime + D.duration)); + let A, E; + const S = y - v; + S < 60 * 1e3 + ? ((A = 10 * 1e3), (E = !0)) + : S < 300 * 1e3 + ? ((A = 30 * 1e3), (E = !0)) + : S < 1800 * 1e3 + ? ((A = 300 * 1e3), (E = !1)) + : ((A = 600 * 1e3), (E = !1)); + const O = Math.ceil(v / A) * A, + X = (D, N) => { + const K = new Date(D).toLocaleTimeString(void 0, { + hour: '2-digit', + minute: '2-digit', + second: E ? '2-digit' : void 0, + }); + if (N) return K; + if (K.endsWith(' AM') || K.endsWith(' PM')) return K.slice(0, -3); + }, + b = (y - v) * 1.1, + p = Math.ceil(b / A) * A, + x = h / p, + R = 20, + U = 8, + Z = l.length * (R + U), + F = []; + for (let D = O; D <= v + p; D += A) { + const N = D - v; + F.push({ x: N * x, label: X(D, D === O) }); + } + const j = Z + o.top + o.bottom; + return m.jsx('svg', { + viewBox: `0 0 800 ${j}`, + preserveAspectRatio: 'xMidYMid meet', + style: { width: '100%', height: 'auto' }, + role: 'img', + children: m.jsxs('g', { + transform: `translate(${o.left}, ${o.top})`, + role: 'presentation', + children: [ + F.map(({ x: D, label: N }, K) => + m.jsxs( + 'g', + { + 'aria-hidden': 'true', + children: [ + m.jsx('line', { + x1: D, + y1: 0, + x2: D, + y2: Z, + stroke: 'var(--color-border-muted)', + strokeWidth: '1', + }), + m.jsx('text', { + x: D, + y: Z + 20, + textAnchor: 'middle', + dominantBaseline: 'middle', + fontSize: '12', + fill: 'var(--color-fg-muted)', + children: N, + }), + ], + }, + K + ) + ), + l.map((D, N) => { + const K = D.startTime - v, + J = D.duration * x, + k = K * x, + nt = N * (R + U), + P = [ + 'var(--color-scale-blue-2)', + 'var(--color-scale-blue-3)', + 'var(--color-scale-blue-4)', + ], + st = P[N % P.length]; + return m.jsxs( + 'g', + { + role: 'listitem', + 'aria-label': D.tooltip, + children: [ + m.jsx('rect', { + className: 'gantt-bar', + x: k, + y: nt, + width: J, + height: R, + fill: st, + rx: '2', + tabIndex: 0, + children: m.jsx('title', { children: D.tooltip }), + }), + m.jsx('text', { + x: k + J + 6, + y: nt + R / 2, + dominantBaseline: 'middle', + fontSize: '12', + fill: 'var(--color-fg-muted)', + 'aria-hidden': 'true', + children: rm(D.duration), + }), + m.jsx('text', { + x: -10, + y: nt + R / 2, + textAnchor: 'end', + dominantBaseline: 'middle', + fontSize: '12', + fill: 'var(--color-fg-muted)', + 'aria-hidden': 'true', + children: D.label, + }), + ], + }, + N + ); + }), + m.jsx('line', { + x1: 0, + y1: 0, + x2: 0, + y2: Z, + stroke: 'var(--color-fg-muted)', + strokeWidth: '1', + 'aria-hidden': 'true', + }), + m.jsx('line', { + x1: 0, + y1: Z, + x2: h, + y2: Z, + stroke: 'var(--color-fg-muted)', + strokeWidth: '1', + 'aria-hidden': 'true', + }), + ], + }), + }); + }; + function ty({ report: l, tests: u }) { + return m.jsxs(m.Fragment, { + children: [m.jsx(ny, { report: l }), m.jsx(ey, { report: l, tests: u })], + }); + } + function ey({ report: l, tests: u }) { + const [c, f] = ue.useState(50); + return m.jsx(sm, { + file: { fileId: 'slowest', fileName: 'Slowest Tests', tests: u.slice(0, c), stats: null }, + projectNames: l.json().projectNames, + footer: + c < u.length + ? m.jsxs('button', { + className: 'link-badge fullwidth-link', + style: { padding: '8px 5px' }, + onClick: () => f((r) => r + 50), + children: [Ni(), 'Show 50 more'], + }) + : void 0, + }); + } + function ny({ report: l }) { + const u = l.json().machines; + if (u.length === 0) return null; + const c = u + .map((f) => { + const r = f.tag.join(' '), + o = new Date(f.startTime).toLocaleTimeString([], { + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + timeZoneName: 'short', + }); + let h = `${r} started at ${o}, runs ${rm(f.duration)}`; + return ( + f.shardIndex && (h += ` (shard ${f.shardIndex})`), + { + label: r, + tooltip: h, + startTime: f.startTime, + duration: f.duration, + shardIndex: f.shardIndex ?? 1, + } + ); + }) + .sort((f, r) => f.label.localeCompare(r.label) || f.shardIndex - r.shardIndex); + return m.jsx(ke, { header: 'Timeline', children: m.jsx($v, { entries: c }) }); + } + const ay = (l) => !l.has('testId') && !l.has('speedboard'), + ly = (l) => l.has('testId'), + iy = (l) => l.has('speedboard') && !l.has('testId'), + uy = ({ report: l }) => { + var Z, F; + const u = se(), + [c, f] = ct.useState(new Map()), + [r, o] = ct.useState(u.get('q') || ''), + [h, v] = ct.useState(!1), + y = u.has('speedboard'), + [A] = _h('mergeFiles', !1), + E = u.get('testId'), + S = ((Z = u.get('q')) == null ? void 0 : Z.toString()) || '', + O = S ? '&q=' + S : '', + X = (F = l == null ? void 0 : l.json()) == null ? void 0 : F.options.title, + B = ct.useMemo(() => { + const j = new Map(); + for (const D of (l == null ? void 0 : l.json().files) || []) + for (const N of D.tests) j.set(N.testId, D.fileId); + return j; + }, [l]), + b = ct.useMemo(() => rc.parse(r), [r]), + p = ct.useMemo( + () => (b.empty() ? void 0 : sy((l == null ? void 0 : l.json().files) || [], b)), + [l, b] + ), + x = ct.useMemo(() => (y ? oy(l, b) : A ? ry(l, b) : fy(l, b)), [l, b, A, y]), + { prev: R, next: U } = ct.useMemo(() => { + const j = x.tests.findIndex((K) => K.testId === E), + D = j > 0 ? x.tests[j - 1] : void 0, + N = j < x.tests.length - 1 ? x.tests[j + 1] : void 0; + return { prev: D, next: N }; + }, [E, x]); + return ( + ct.useEffect(() => { + const j = (D) => { + if ( + D.target instanceof HTMLInputElement || + D.target instanceof HTMLTextAreaElement || + D.shiftKey || + D.ctrlKey || + D.metaKey || + D.altKey + ) + return; + const N = new URLSearchParams(u); + switch (D.key) { + case 'a': + (D.preventDefault(), ca('#?')); + break; + case 'p': + (D.preventDefault(), + N.delete('testId'), + N.delete('speedboard'), + ca(Na(N, 's:passed', !1))); + break; + case 'f': + (D.preventDefault(), + N.delete('testId'), + N.delete('speedboard'), + ca(Na(N, 's:failed', !1))); + break; + case 'ArrowLeft': + R && (D.preventDefault(), N.delete('testId'), ca(Cn({ test: R }, N) + O)); + break; + case 'ArrowRight': + U && (D.preventDefault(), N.delete('testId'), ca(Cn({ test: U }, N) + O)); + break; + } + }; + return ( + document.addEventListener('keydown', j), + () => document.removeEventListener('keydown', j) + ); + }, [R, U, O, S, u]), + ct.useEffect(() => { + X ? (document.title = X) : (document.title = 'Playwright Test Report'); + }, [X]), + m.jsx('div', { + className: 'htmlreport vbox px-4 pb-4', + children: m.jsxs('main', { + children: [ + l && m.jsx(Ev, { stats: l.json().stats, filterText: r, setFilterText: o }), + m.jsxs(Kf, { + predicate: ay, + children: [ + m.jsx(Y2, { + report: l == null ? void 0 : l.json(), + filteredStats: p, + metadataVisible: h, + toggleMetadataVisible: () => v((j) => !j), + }), + m.jsx(Pv, { + files: x.files, + expandedFiles: c, + setExpandedFiles: f, + projectNames: (l == null ? void 0 : l.json().projectNames) || [], + }), + ], + }), + m.jsxs(Kf, { + predicate: iy, + children: [ + m.jsx(Y2, { + report: l == null ? void 0 : l.json(), + filteredStats: p, + metadataVisible: h, + toggleMetadataVisible: () => v((j) => !j), + }), + l && m.jsx(ty, { report: l, tests: x.tests }), + ], + }), + m.jsx(Kf, { + predicate: ly, + children: + l && + m.jsx(cy, { report: l, next: U, prev: R, testId: E, testIdToFileIdMap: B }), + }), + ], + }), + }) + ); + }, + cy = ({ report: l, testIdToFileIdMap: u, next: c, prev: f, testId: r }) => { + const [o, h] = ct.useState('loading'), + v = +(se().get('run') || '0'); + if ( + (ct.useEffect(() => { + (async () => { + if (!r || (typeof o == 'object' && r === o.testId)) return; + const S = u.get(r); + if (!S) { + h('not-found'); + return; + } + const O = await l.entry(`${S}.json`); + h((O == null ? void 0 : O.tests.find((X) => X.testId === r)) || 'not-found'); + })(); + }, [o, l, r, u]), + o === 'loading') + ) + return m.jsx('div', { className: 'test-case-column' }); + if (o === 'not-found') + return m.jsxs('div', { + className: 'test-case-column', + children: [ + m.jsx(Or, { title: 'Test not found' }), + m.jsxs('div', { className: 'test-case-location', children: ['Test ID: ', r] }), + ], + }); + const { projectNames: y, metadata: A, options: E } = l.json(); + return m.jsx('div', { + className: 'test-case-column', + children: m.jsx(Vv, { + projectNames: y, + testRunMetadata: A, + options: E, + next: c, + prev: f, + test: o, + run: v, + }), + }); + }; + function sy(l, u) { + const c = { total: 0, duration: 0 }; + for (const f of l) { + const r = f.tests.filter((o) => u.matches(o)); + c.total += r.length; + for (const o of r) c.duration += o.duration; + } + return c; + } + function fy(l, u) { + const c = { files: [], tests: [] }; + for (const f of (l == null ? void 0 : l.json().files) || []) { + const r = f.tests.filter((o) => u.matches(o)); + (r.length && c.files.push({ ...f, tests: r }), c.tests.push(...r)); + } + return c; + } + function ry(l, u) { + const c = [], + f = new Map(); + for (const o of (l == null ? void 0 : l.json().files) || []) { + const h = o.tests.filter((v) => u.matches(v)); + for (const v of h) { + const y = v.path[0] ?? ''; + let A = f.get(y); + A || + ((A = { + fileId: y, + fileName: y, + tests: [], + stats: { total: 0, expected: 0, unexpected: 0, flaky: 0, skipped: 0, ok: !0 }, + }), + f.set(y, A), + c.push(A)); + const E = { ...v, path: v.path.slice(1) }; + A.tests.push(E); + } + } + c.sort((o, h) => o.fileName.localeCompare(h.fileName)); + const r = { files: c, tests: [] }; + for (const o of c) r.tests.push(...o.tests); + return r; + } + function oy(l, u) { + const f = ((l == null ? void 0 : l.json().files) || []) + .flatMap((r) => r.tests) + .filter((r) => u.matches(r)); + return (f.sort((r, o) => o.duration - r.duration), { files: [], tests: f }); + } + const dy = + "data:image/svg+xml,%3csvg%20width='400'%20height='400'%20viewBox='0%200%20400%20400'%20fill='none'%20xmlns='http://www.w3.org/2000/svg'%3e%3cpath%20d='M136.444%20221.556C123.558%20225.213%20115.104%20231.625%20109.535%20238.032C114.869%20233.364%20122.014%20229.08%20131.652%20226.348C141.51%20223.554%20149.92%20223.574%20156.869%20224.915V219.481C150.941%20218.939%20144.145%20219.371%20136.444%20221.556ZM108.946%20175.876L61.0895%20188.484C61.0895%20188.484%2061.9617%20189.716%2063.5767%20191.36L104.153%20180.668C104.153%20180.668%20103.578%20188.077%2098.5847%20194.705C108.03%20187.559%20108.946%20175.876%20108.946%20175.876ZM149.005%20288.347C81.6582%20306.486%2046.0272%20228.438%2035.2396%20187.928C30.2556%20169.229%2028.0799%20155.067%2027.5%20145.928C27.4377%20144.979%2027.4665%20144.179%2027.5336%20143.446C24.04%20143.657%2022.3674%20145.473%2022.7077%20150.721C23.2876%20159.855%2025.4633%20174.016%2030.4473%20192.721C41.2301%20233.225%2076.8659%20311.273%20144.213%20293.134C158.872%20289.185%20169.885%20281.992%20178.152%20272.81C170.532%20279.692%20160.995%20285.112%20149.005%20288.347ZM161.661%20128.11V132.903H188.077C187.535%20131.206%20186.989%20129.677%20186.447%20128.11H161.661Z'%20fill='%232D4552'/%3e%3cpath%20d='M193.981%20167.584C205.861%20170.958%20212.144%20179.287%20215.465%20186.658L228.711%20190.42C228.711%20190.42%20226.904%20164.623%20203.57%20157.995C181.741%20151.793%20168.308%20170.124%20166.674%20172.496C173.024%20167.972%20182.297%20164.268%20193.981%20167.584ZM299.422%20186.777C277.573%20180.547%20264.145%20198.916%20262.535%20201.255C268.89%20196.736%20278.158%20193.031%20289.837%20196.362C301.698%20199.741%20307.976%20208.06%20311.307%20215.436L324.572%20219.212C324.572%20219.212%20322.736%20193.41%20299.422%20186.777ZM286.262%20254.795L176.072%20223.99C176.072%20223.99%20177.265%20230.038%20181.842%20237.869L274.617%20263.805C282.255%20259.386%20286.262%20254.795%20286.262%20254.795ZM209.867%20321.102C122.618%20297.71%20133.166%20186.543%20147.284%20133.865C153.097%20112.156%20159.073%2096.0203%20164.029%2085.204C161.072%2084.5953%20158.623%2086.1529%20156.203%2091.0746C150.941%20101.747%20144.212%20119.124%20137.7%20143.45C123.586%20196.127%20113.038%20307.29%20200.283%20330.682C241.406%20341.699%20273.442%20324.955%20297.323%20298.659C274.655%20319.19%20245.714%20330.701%20209.867%20321.102Z'%20fill='%232D4552'/%3e%3cpath%20d='M161.661%20262.296V239.863L99.3324%20257.537C99.3324%20257.537%20103.938%20230.777%20136.444%20221.556C146.302%20218.762%20154.713%20218.781%20161.661%20220.123V128.11H192.869C189.471%20117.61%20186.184%20109.526%20183.423%20103.909C178.856%2094.612%20174.174%20100.775%20163.545%20109.665C156.059%20115.919%20137.139%20129.261%20108.668%20136.933C80.1966%20144.61%2057.179%20142.574%2047.5752%20140.911C33.9601%20138.562%2026.8387%20135.572%2027.5049%20145.928C28.0847%20155.062%2030.2605%20169.224%2035.2445%20187.928C46.0272%20228.433%2081.663%20306.481%20149.01%20288.342C166.602%20283.602%20179.019%20274.233%20187.626%20262.291H161.661V262.296ZM61.0848%20188.484L108.946%20175.876C108.946%20175.876%20107.551%20194.288%2089.6087%20199.018C71.6614%20203.743%2061.0848%20188.484%2061.0848%20188.484Z'%20fill='%23E2574C'/%3e%3cpath%20d='M341.786%20129.174C329.345%20131.355%20299.498%20134.072%20262.612%20124.185C225.716%20114.304%20201.236%2097.0224%20191.537%2088.8994C177.788%2077.3834%20171.74%2069.3802%20165.788%2081.4857C160.526%2092.163%20153.797%20109.54%20147.284%20133.866C133.171%20186.543%20122.623%20297.706%20209.867%20321.098C297.093%20344.47%20343.53%20242.92%20357.644%20190.238C364.157%20165.917%20367.013%20147.5%20367.799%20135.625C368.695%20122.173%20359.455%20126.078%20341.786%20129.174ZM166.497%20172.756C166.497%20172.756%20180.246%20151.372%20203.565%20158C226.899%20164.628%20228.706%20190.425%20228.706%20190.425L166.497%20172.756ZM223.42%20268.713C182.403%20256.698%20176.077%20223.99%20176.077%20223.99L286.262%20254.796C286.262%20254.791%20264.021%20280.578%20223.42%20268.713ZM262.377%20201.495C262.377%20201.495%20276.107%20180.126%20299.422%20186.773C322.736%20193.411%20324.572%20219.208%20324.572%20219.208L262.377%20201.495Z'%20fill='%232EAD33'/%3e%3cpath%20d='M139.88%20246.04L99.3324%20257.532C99.3324%20257.532%20103.737%20232.44%20133.607%20222.496L110.647%20136.33L108.663%20136.933C80.1918%20144.611%2057.1742%20142.574%2047.5704%20140.911C33.9554%20138.563%2026.834%20135.572%2027.5001%20145.929C28.08%20155.063%2030.2557%20169.224%2035.2397%20187.929C46.0225%20228.433%2081.6583%20306.481%20149.005%20288.342L150.989%20287.719L139.88%20246.04ZM61.0848%20188.485L108.946%20175.876C108.946%20175.876%20107.551%20194.288%2089.6087%20199.018C71.6615%20203.743%2061.0848%20188.485%2061.0848%20188.485Z'%20fill='%23D65348'/%3e%3cpath%20d='M225.27%20269.163L223.415%20268.712C182.398%20256.698%20176.072%20223.99%20176.072%20223.99L232.89%20239.872L262.971%20124.281L262.607%20124.185C225.711%20114.304%20201.232%2097.0224%20191.532%2088.8994C177.783%2077.3834%20171.735%2069.3802%20165.783%2081.4857C160.526%2092.163%20153.797%20109.54%20147.284%20133.866C133.171%20186.543%20122.623%20297.706%20209.867%20321.097L211.655%20321.5L225.27%20269.163ZM166.497%20172.756C166.497%20172.756%20180.246%20151.372%20203.565%20158C226.899%20164.628%20228.706%20190.425%20228.706%20190.425L166.497%20172.756Z'%20fill='%231D8D22'/%3e%3cpath%20d='M141.946%20245.451L131.072%20248.537C133.641%20263.019%20138.169%20276.917%20145.276%20289.195C146.513%20288.922%20147.74%20288.687%20149%20288.342C152.302%20287.451%20155.364%20286.348%20158.312%20285.145C150.371%20273.361%20145.118%20259.789%20141.946%20245.451ZM137.7%20143.451C132.112%20164.307%20127.113%20194.326%20128.489%20224.436C130.952%20223.367%20133.554%20222.371%20136.444%20221.551L138.457%20221.101C136.003%20188.939%20141.308%20156.165%20147.284%20133.866C148.799%20128.225%20150.318%20122.978%20151.832%20118.085C149.393%20119.637%20146.767%20121.228%20143.776%20122.867C141.759%20129.093%20139.722%20135.898%20137.7%20143.451Z'%20fill='%23C04B41'/%3e%3c/svg%3e", + Ff = N5, + Rr = document.createElement('link'); + Rr.rel = 'shortcut icon'; + Rr.href = dy; + document.head.appendChild(Rr); + const hy = () => { + const [l, u] = ct.useState(); + return ( + ct.useEffect(() => { + const c = new my(); + c.load().then(() => { + var f; + ((f = document.getElementById('playwrightReportBase64')) == null || f.remove(), u(c)); + }); + }, []), + m.jsx(cv, { children: m.jsx(uy, { report: l }) }) + ); + }; + window.onload = () => { + (gv(), X5.createRoot(document.querySelector('#root')).render(m.jsx(hy, {}))); + }; + class my { + constructor() { + yn(this, '_entries', new Map()); + yn(this, '_json'); + } + async load() { + const u = document.getElementById('playwrightReportBase64').textContent, + c = new Ff.ZipReader(new Ff.Data64URIReader(u), { useWebWorkers: !1 }); + for (const f of await c.getEntries()) this._entries.set(f.filename, f); + this._json = await this.entry('report.json'); + } + json() { + return this._json; + } + async entry(u) { + const c = this._entries.get(u), + f = new Ff.TextWriter(); + return (await c.getData(f), JSON.parse(await f.getData())); + } + } + + -
+
- \ No newline at end of file + diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts index a44a03e..26e0b59 100644 --- a/frontend/playwright.config.ts +++ b/frontend/playwright.config.ts @@ -1,25 +1,25 @@ import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ - testDir: './tests', - fullyParallel: true, - forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: 1, // Avoid VM crash - reporter: 'html', - use: { - baseURL: 'http://localhost:5173', // Vite dev server - trace: 'on-first-retry', - }, - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, - ], - webServer: { - command: 'npm run dev', - url: 'http://localhost:5173', - reuseExistingServer: !process.env.CI, + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: 1, // Avoid VM crash + reporter: 'html', + use: { + baseURL: 'http://localhost:5173', // Vite dev server + trace: 'on-first-retry', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, }, + ], + webServer: { + command: 'npm run dev', + url: 'http://localhost:5173', + reuseExistingServer: !process.env.CI, + }, }); diff --git a/frontend/src/App.css b/frontend/src/App.css index 3463f5d..09d1408 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -5,8 +5,6 @@ body { margin: 0; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; } /* Dashboard Layout */ @@ -89,7 +87,7 @@ body { } .dashboard-main>* { - max-width: 600px; + max-width: 35em; margin: 0; } diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 303ac7e..196f32a 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -5,52 +5,55 @@ import App from './App'; import { describe, it, expect, vi, beforeEach } from 'vitest'; describe('App', () => { - beforeEach(() => { - vi.resetAllMocks(); - global.fetch = vi.fn(); + beforeEach(() => { + vi.resetAllMocks(); + global.fetch = vi.fn(); + }); + + it('renders login on initial load (unauthenticated)', async () => { + (global.fetch as any).mockResolvedValueOnce({ + ok: false, }); - - it('renders login on initial load (unauthenticated)', async () => { - (global.fetch as any).mockResolvedValueOnce({ - ok: false, - }); - window.history.pushState({}, 'Test page', '/v2/login'); - render(); - expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument(); + window.history.pushState({}, 'Test page', '/v2/login'); + render(); + expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument(); + }); + + it('renders dashboard when authenticated', async () => { + (global.fetch as any).mockImplementation((url: string) => { + if (url.includes('/api/auth')) return Promise.resolve({ ok: true }); + if (url.includes('/api/feed/')) return Promise.resolve({ ok: true, json: async () => [] }); + if (url.includes('/api/tag')) return Promise.resolve({ ok: true, json: async () => [] }); + return Promise.resolve({ ok: true }); // Fallback }); - it('renders dashboard when authenticated', async () => { - (global.fetch as any).mockImplementation((url: string) => { - if (url.includes('/api/auth')) return Promise.resolve({ ok: true }); - if (url.includes('/api/feed/')) return Promise.resolve({ ok: true, json: async () => [] }); - if (url.includes('/api/tag')) return Promise.resolve({ ok: true, json: async () => [] }); - return Promise.resolve({ ok: true }); // Fallback - }); - - window.history.pushState({}, 'Test page', '/v2/'); - render(); + window.history.pushState({}, 'Test page', '/v2/'); + render(); - await waitFor(() => { - expect(screen.getByText('🐱')).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByText('🐱')).toBeInTheDocument(); + }); - // Test Logout - const logoutBtn = screen.getByText(/logout/i); - expect(logoutBtn).toBeInTheDocument(); + // Test Logout + const logoutBtn = screen.getByText(/logout/i); + expect(logoutBtn).toBeInTheDocument(); - // Mock window.location - Object.defineProperty(window, 'location', { - configurable: true, - value: { href: '' }, - }); + // Mock window.location + Object.defineProperty(window, 'location', { + configurable: true, + value: { href: '' }, + }); - (global.fetch as any).mockResolvedValueOnce({ ok: true }); + (global.fetch as any).mockResolvedValueOnce({ ok: true }); - fireEvent.click(logoutBtn); + fireEvent.click(logoutBtn); - await waitFor(() => { - expect(global.fetch).toHaveBeenCalledWith('/api/logout', expect.objectContaining({ method: 'POST' })); - expect(window.location.href).toBe('/v2/login'); - }); + await waitFor(() => { + expect(global.fetch).toHaveBeenCalledWith( + '/api/logout', + expect.objectContaining({ method: 'POST' }) + ); + expect(window.location.href).toBe('/v2/login'); }); + }); }); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9f53ace..4835cd3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -35,21 +35,47 @@ import FeedList from './components/FeedList'; import FeedItems from './components/FeedItems'; import Settings from './components/Settings'; -function Dashboard({ theme, setTheme }: { theme: string, setTheme: (t: string) => void }) { +function Dashboard({ theme, setTheme }: { theme: string; setTheme: (t: string) => void }) { const navigate = useNavigate(); const [sidebarVisible, setSidebarVisible] = useState(true); return ( -
+
-

setSidebarVisible(!sidebarVisible)} style={{ cursor: 'pointer' }}>🐱

+

setSidebarVisible(!sidebarVisible)} + style={{ cursor: 'pointer' }} + > + 🐱 +

diff --git a/frontend/src/components/FeedItem.css b/frontend/src/components/FeedItem.css index 1261737..1736032 100644 --- a/frontend/src/components/FeedItem.css +++ b/frontend/src/components/FeedItem.css @@ -1,114 +1,108 @@ .feed-item { - padding: 1rem; - margin-top: 5rem; - list-style: none; - border-bottom: none; + padding: 1rem; + margin-top: 5rem; + list-style: none; + border-bottom: none; } -.feed-item.read .item-title { - font-weight: normal; -} - -.feed-item.unread .item-title { - font-weight: bold; -} +/* removed read/unread specific font-weight to keep it always bold as requested */ .item-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - margin-bottom: 0.5rem; + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 0.5rem; } .item-title { - font-size: 1.35rem; - /* approx 24px */ - font-weight: bold; - text-decoration: none; - color: var(--link-color); - display: block; - flex: 1; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 1.8rem; + font-weight: bold; + text-decoration: none; + color: var(--link-color); + display: block; + flex: 1; } .item-title:hover { - text-decoration: none; - color: var(--link-color); + text-decoration: none; + color: var(--link-color); } .item-actions { - display: flex; - gap: 0.5rem; - margin-left: 1rem; + display: flex; + gap: 0.5rem; + margin-left: 1rem; } /* Legacy controls were simple text/links, but buttons are fine if minimal */ .star-btn { - background: none; - border: none; - cursor: pointer; - font-size: 1.25rem; - padding: 0 0 0 0.5rem; - vertical-align: middle; - transition: color 0.2s; - line-height: 1; + background: none; + border: none; + cursor: pointer; + font-size: 1.25rem; + padding: 0 0 0 0.5rem; + vertical-align: middle; + transition: color 0.2s; + line-height: 1; } .star-btn.is-starred { - color: blue; + color: blue; } .star-btn.is-unstarred { - color: black; + color: black; } .star-btn:hover { - color: blue; + color: blue; } .action-btn { - background: whitesmoke; - border: none; - cursor: pointer; - padding: 2px 6px; - font-size: 1rem; - color: blue; - font-weight: bold; + background: whitesmoke; + border: none; + cursor: pointer; + padding: 2px 6px; + font-size: 1rem; + color: blue; + font-weight: bold; } .action-btn:hover { - background-color: #eee; + background-color: #eee; } .dateline { - margin-top: 0; - font-weight: normal; - font-size: .75em; - color: #ccc; - margin-bottom: 1rem; + margin-top: 0; + font-weight: normal; + font-size: 0.75em; + color: #ccc; + margin-bottom: 1rem; } .dateline a { - color: #ccc; - text-decoration: none; + color: #ccc; + text-decoration: none; } .item-description { - color: #000; - line-height: 1.5; - font-size: 1rem; - margin-top: 1rem; + color: var(--text-color); + line-height: 1.5; + font-size: 1rem; + margin-top: 1rem; } .item-description img { - max-width: 100%; - height: auto; - display: block; - margin: 1rem 0; + max-width: 100%; + height: auto; + display: block; + margin: 1rem 0; } .item-description blockquote { - padding: 1rem 1rem 0 1rem; - border-left: 4px solid #ddd; - color: #666; - margin-left: 0; + padding: 1rem 1rem 0 1rem; + border-left: 4px solid #ddd; + color: #666; + margin-left: 0; } \ No newline at end of file diff --git a/frontend/src/components/FeedItem.test.tsx b/frontend/src/components/FeedItem.test.tsx index f0497c6..cb9aafa 100644 --- a/frontend/src/components/FeedItem.test.tsx +++ b/frontend/src/components/FeedItem.test.tsx @@ -6,66 +6,69 @@ import FeedItem from './FeedItem'; import type { Item } from '../types'; const mockItem: Item = { - _id: 1, - feed_id: 101, - title: 'Test Item', - url: 'http://example.com/item', - description: '

Description

', - publish_date: '2023-01-01', - read: false, - starred: false, - feed_title: 'Test Feed' + _id: 1, + feed_id: 101, + title: 'Test Item', + url: 'http://example.com/item', + description: '

Description

', + publish_date: '2023-01-01', + read: false, + starred: false, + feed_title: 'Test Feed', }; describe('FeedItem Component', () => { - beforeEach(() => { - vi.resetAllMocks(); - global.fetch = vi.fn(); - }); + beforeEach(() => { + vi.resetAllMocks(); + global.fetch = vi.fn(); + }); - it('renders item details', () => { - render(); - expect(screen.getByText('Test Item')).toBeInTheDocument(); - expect(screen.getByText(/Test Feed/)).toBeInTheDocument(); - // Check for relative time or date formatting? For now just check it renders - }); + it('renders item details', () => { + render(); + expect(screen.getByText('Test Item')).toBeInTheDocument(); + expect(screen.getByText(/Test Feed/)).toBeInTheDocument(); + // Check for relative time or date formatting? For now just check it renders + }); - it('toggles star status', async () => { - (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => ({}) }); + it('toggles star status', async () => { + (global.fetch as any).mockResolvedValueOnce({ ok: true, json: async () => ({}) }); - render(); + render(); - const starBtn = screen.getByTitle('Star'); - expect(starBtn).toHaveTextContent('★'); - fireEvent.click(starBtn); + const starBtn = screen.getByTitle('Star'); + expect(starBtn).toHaveTextContent('★'); + fireEvent.click(starBtn); - // Optimistic update - expect(await screen.findByTitle('Unstar')).toHaveTextContent('★'); + // Optimistic update + expect(await screen.findByTitle('Unstar')).toHaveTextContent('★'); - expect(global.fetch).toHaveBeenCalledWith('/api/item/1', expect.objectContaining({ - method: 'PUT', - body: JSON.stringify({ - _id: 1, - read: false, - starred: true - }) - })); - }); + expect(global.fetch).toHaveBeenCalledWith( + '/api/item/1', + expect.objectContaining({ + method: 'PUT', + body: JSON.stringify({ + _id: 1, + read: false, + starred: true, + }), + }) + ); + }); - it('updates styling when read state changes', () => { - const { rerender } = render(); - const link = screen.getByText('Test Item'); - // Initial state: unread (bold) - // Note: checking computed style might be flaky in jsdom, but we can check the class on the parent - const listItem = link.closest('li'); - expect(listItem).toHaveClass('unread'); - expect(listItem).not.toHaveClass('read'); + it('updates styling when read state changes', () => { + const { rerender } = render(); + const link = screen.getByText('Test Item'); + // Initial state: unread (bold) + // Note: checking computed style might be flaky in jsdom, but we can check the class on the parent + const listItem = link.closest('li'); + expect(listItem).toHaveClass('unread'); + expect(listItem).not.toHaveClass('read'); - // Update prop to read - rerender(); + // Update prop to read + rerender(); - // Should now be read - expect(listItem).toHaveClass('read'); - expect(listItem).not.toHaveClass('unread'); - }); + // Should now be read + expect(listItem).toHaveClass('read'); + expect(listItem).not.toHaveClass('unread'); + }); }); diff --git a/frontend/src/components/FeedItem.tsx b/frontend/src/components/FeedItem.tsx index b86e60c..9b40114 100644 --- a/frontend/src/components/FeedItem.tsx +++ b/frontend/src/components/FeedItem.tsx @@ -3,86 +3,84 @@ import type { Item } from '../types'; import './FeedItem.css'; interface FeedItemProps { - item: Item; + item: Item; } export default function FeedItem({ item: initialItem }: FeedItemProps) { - const [item, setItem] = useState(initialItem); - const [loading, setLoading] = useState(false); + const [item, setItem] = useState(initialItem); + const [loading, setLoading] = useState(false); - useEffect(() => { - setItem(initialItem); - }, [initialItem]); + useEffect(() => { + setItem(initialItem); + }, [initialItem]); + const toggleStar = () => { + updateItem({ ...item, starred: !item.starred }); + }; - const toggleStar = () => { - updateItem({ ...item, starred: !item.starred }); - }; + const updateItem = (newItem: Item) => { + setLoading(true); + // Optimistic update + const previousItem = item; + setItem(newItem); - const updateItem = (newItem: Item) => { - setLoading(true); - // Optimistic update - const previousItem = item; - setItem(newItem); + fetch(`/api/item/${newItem._id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + _id: newItem._id, + read: newItem.read, + starred: newItem.starred, + }), + }) + .then((res) => { + if (!res.ok) { + throw new Error('Failed to update item'); + } + return res.json(); + }) + .then(() => { + // Confirm with server response if needed, but for now we trust the optimistic update + // or we could setItem(updated) if the server returns the full object + setLoading(false); + }) + .catch((err) => { + console.error('Error updating item:', err); + // Revert on error + setItem(previousItem); + setLoading(false); + }); + }; - fetch(`/api/item/${newItem._id}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - _id: newItem._id, - read: newItem.read, - starred: newItem.starred, - }), - }) - .then((res) => { - if (!res.ok) { - throw new Error('Failed to update item'); - } - return res.json(); - }) - .then(() => { - // Confirm with server response if needed, but for now we trust the optimistic update - // or we could setItem(updated) if the server returns the full object - setLoading(false); - }) - .catch((err) => { - console.error('Error updating item:', err); - // Revert on error - setItem(previousItem); - setLoading(false); - }); - }; - - return ( -
  • -
    - - {item.title || '(No Title)'} - - -
    - - {item.description && ( -
    - )} -
  • - ); + return ( +
  • +
    + + {item.title || '(No Title)'} + + +
    + + {item.description && ( +
    + )} +
  • + ); } diff --git a/frontend/src/components/FeedItems.css b/frontend/src/components/FeedItems.css index 31394a4..02323a9 100644 --- a/frontend/src/components/FeedItems.css +++ b/frontend/src/components/FeedItems.css @@ -1,22 +1,23 @@ .feed-items { - padding: 1rem; + padding: 1rem 0; + /* Removing horizontal padding to avoid double-padding with FeedItem */ } .feed-items h2 { - margin-top: 0; - border-bottom: 2px solid #eee; - padding-bottom: 0.5rem; + margin-top: 0; + border-bottom: 2px solid #eee; + padding-bottom: 0.5rem; } .item-list { - list-style: none; - padding: 0; + list-style: none; + padding: 0; } .loading-more { - padding: 2rem; - text-align: center; - color: #888; - font-size: 0.9rem; - min-height: 50px; + padding: 2rem; + text-align: center; + color: #888; + font-size: 0.9rem; + min-height: 50px; } \ No newline at end of file diff --git a/frontend/src/components/FeedItems.test.tsx b/frontend/src/components/FeedItems.test.tsx index ea68a7c..4d96da9 100644 --- a/frontend/src/components/FeedItems.test.tsx +++ b/frontend/src/components/FeedItems.test.tsx @@ -6,220 +6,241 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import FeedItems from './FeedItems'; describe('FeedItems Component', () => { - beforeEach(() => { - vi.resetAllMocks(); - global.fetch = vi.fn(); - window.HTMLElement.prototype.scrollIntoView = vi.fn(); - - // Mock IntersectionObserver - class MockIntersectionObserver { - observe = vi.fn(); - unobserve = vi.fn(); - disconnect = vi.fn(); - } - window.IntersectionObserver = MockIntersectionObserver as any; + beforeEach(() => { + vi.resetAllMocks(); + global.fetch = vi.fn(); + window.HTMLElement.prototype.scrollIntoView = vi.fn(); + + // Mock IntersectionObserver + class MockIntersectionObserver { + observe = vi.fn(); + unobserve = vi.fn(); + disconnect = vi.fn(); + } + window.IntersectionObserver = MockIntersectionObserver as any; + }); + + it('renders loading state', () => { + (global.fetch as any).mockImplementation(() => new Promise(() => {})); + render( + + + } /> + + + ); + expect(screen.getByText(/loading items/i)).toBeInTheDocument(); + }); + + it('renders items for a feed', async () => { + const mockItems = [ + { + _id: 101, + title: 'Item One', + url: 'http://example.com/1', + publish_date: '2023-01-01', + read: false, + }, + { + _id: 102, + title: 'Item Two', + url: 'http://example.com/2', + publish_date: '2023-01-02', + read: true, + }, + ]; + + (global.fetch as any).mockResolvedValueOnce({ + ok: true, + json: async () => mockItems, }); - it('renders loading state', () => { - (global.fetch as any).mockImplementation(() => new Promise(() => { })); - render( - - - } /> - - - ); - expect(screen.getByText(/loading items/i)).toBeInTheDocument(); + render( + + + } /> + + + ); + + await waitFor(() => { + expect(screen.getByText('Item One')).toBeInTheDocument(); + }); + + const params = new URLSearchParams(); + params.append('feed_id', '1'); + params.append('read_filter', 'unread'); + expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`); + }); + + it('handles keyboard shortcuts', async () => { + const mockItems = [ + { _id: 101, title: 'Item 1', url: 'u1', read: false, starred: false }, + { _id: 102, title: 'Item 2', url: 'u2', read: true, starred: false }, + ]; + + (global.fetch as any).mockResolvedValue({ + ok: true, + json: async () => mockItems, + }); + + render( + + + + ); + + await waitFor(() => { + expect(screen.getByText('Item 1')).toBeVisible(); + }); + + // Press 'j' to select first item (index 0 -> 1 because it starts at -1... wait logic says min(prev+1)) + // init -1. j -> 0. + fireEvent.keyDown(window, { key: 'j' }); + + // Item 1 (index 0) should be selected. + // It's unread, so it should be marked read. + await waitFor(() => { + expect(global.fetch).toHaveBeenCalledWith( + '/api/item/101', + expect.objectContaining({ + method: 'PUT', + body: JSON.stringify({ read: true, starred: false }), + }) + ); + }); + + // Press 'j' again -> index 1 (Item 2) + fireEvent.keyDown(window, { key: 'j' }); + + // Item 2 is already read, so no markRead call expected for it (mocks clear? no). + // let's check selection class if possible, but testing library doesn't easily check class on div wrapper unless we query it. + + // Press 's' to star Item 2 + fireEvent.keyDown(window, { key: 's' }); + + await waitFor(() => { + expect(global.fetch).toHaveBeenCalledWith( + '/api/item/102', + expect.objectContaining({ + method: 'PUT', + body: JSON.stringify({ read: true, starred: true }), // toggled to true + }) + ); }); + }); - it('renders items for a feed', async () => { - const mockItems = [ - { _id: 101, title: 'Item One', url: 'http://example.com/1', publish_date: '2023-01-01', read: false }, - { _id: 102, title: 'Item Two', url: 'http://example.com/2', publish_date: '2023-01-02', read: true }, - ]; - - (global.fetch as any).mockResolvedValueOnce({ - ok: true, - json: async () => mockItems, - }); - - render( - - - } /> - - - ); - - await waitFor(() => { - expect(screen.getByText('Item One')).toBeInTheDocument(); - }); - - const params = new URLSearchParams(); - params.append('feed_id', '1'); - params.append('read_filter', 'unread'); - expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`); + it('marks items as read when scrolled past', async () => { + const mockItems = [{ _id: 101, title: 'Item 1', url: 'u1', read: false, starred: false }]; + (global.fetch as any).mockResolvedValue({ + ok: true, + json: async () => mockItems, }); - it('handles keyboard shortcuts', async () => { - const mockItems = [ - { _id: 101, title: 'Item 1', url: 'u1', read: false, starred: false }, - { _id: 102, title: 'Item 2', url: 'u2', read: true, starred: false }, - ]; - - (global.fetch as any).mockResolvedValue({ - ok: true, - json: async () => mockItems, - }); - - render( - - - - ); - - await waitFor(() => { - expect(screen.getByText('Item 1')).toBeVisible(); - }); - - // Press 'j' to select first item (index 0 -> 1 because it starts at -1... wait logic says min(prev+1)) - // init -1. j -> 0. - fireEvent.keyDown(window, { key: 'j' }); - - // Item 1 (index 0) should be selected. - // It's unread, so it should be marked read. - await waitFor(() => { - expect(global.fetch).toHaveBeenCalledWith('/api/item/101', expect.objectContaining({ - method: 'PUT', - body: JSON.stringify({ read: true, starred: false }), - })); - }); - - // Press 'j' again -> index 1 (Item 2) - fireEvent.keyDown(window, { key: 'j' }); - - // Item 2 is already read, so no markRead call expected for it (mocks clear? no). - // let's check selection class if possible, but testing library doesn't easily check class on div wrapper unless we query it. - - // Press 's' to star Item 2 - fireEvent.keyDown(window, { key: 's' }); - - await waitFor(() => { - expect(global.fetch).toHaveBeenCalledWith('/api/item/102', expect.objectContaining({ - method: 'PUT', - body: JSON.stringify({ read: true, starred: true }), // toggled to true - })); - }); + // Capture the callback + let observerCallback: IntersectionObserverCallback = () => {}; + + // Override the mock to capture callback + class MockIntersectionObserver { + constructor(callback: IntersectionObserverCallback) { + observerCallback = callback; + } + observe = vi.fn(); + unobserve = vi.fn(); + disconnect = vi.fn(); + } + window.IntersectionObserver = MockIntersectionObserver as any; + + render( + + + + ); + + await waitFor(() => { + expect(screen.getByText('Item 1')).toBeVisible(); + }); + + // Simulate item leaving viewport at the top + // Element index is 0 + const entry = { + isIntersecting: false, + boundingClientRect: { top: -50 } as DOMRectReadOnly, + target: { getAttribute: () => '0' } as unknown as Element, + intersectionRatio: 0, + time: 0, + rootBounds: null, + intersectionRect: {} as DOMRectReadOnly, + } as IntersectionObserverEntry; + + // Use vi.waitUntil to wait for callback to be assigned if needed, + // though strictly synchronous render + effect should do it. + // Direct call: + act(() => { + observerCallback([entry], {} as IntersectionObserver); + }); + + await waitFor(() => { + expect(global.fetch).toHaveBeenCalledWith( + '/api/item/101', + expect.objectContaining({ + method: 'PUT', + body: JSON.stringify({ read: true, starred: false }), + }) + ); + }); + }); + + it('loads more items when sentinel becomes visible', async () => { + const initialItems = [{ _id: 101, title: 'Item 1', url: 'u1', read: true, starred: false }]; + const moreItems = [{ _id: 100, title: 'Item 0', url: 'u0', read: true, starred: false }]; + + (global.fetch as any) + .mockResolvedValueOnce({ ok: true, json: async () => initialItems }) + .mockResolvedValueOnce({ ok: true, json: async () => moreItems }); + + let observerCallback: IntersectionObserverCallback = () => {}; + class MockIntersectionObserver { + constructor(callback: IntersectionObserverCallback) { + observerCallback = callback; + } + observe = vi.fn(); + unobserve = vi.fn(); + disconnect = vi.fn(); + } + window.IntersectionObserver = MockIntersectionObserver as any; + + render( + + + + ); + + await waitFor(() => { + expect(screen.getByText('Item 1')).toBeInTheDocument(); }); - it('marks items as read when scrolled past', async () => { - const mockItems = [{ _id: 101, title: 'Item 1', url: 'u1', read: false, starred: false }]; - (global.fetch as any).mockResolvedValue({ - ok: true, - json: async () => mockItems, - }); - - // Capture the callback - let observerCallback: IntersectionObserverCallback = () => { }; - - // Override the mock to capture callback - class MockIntersectionObserver { - constructor(callback: IntersectionObserverCallback) { - observerCallback = callback; - } - observe = vi.fn(); - unobserve = vi.fn(); - disconnect = vi.fn(); - } - window.IntersectionObserver = MockIntersectionObserver as any; - - render( - - - - ); - - await waitFor(() => { - expect(screen.getByText('Item 1')).toBeVisible(); - }); - - // Simulate item leaving viewport at the top - // Element index is 0 - const entry = { - isIntersecting: false, - boundingClientRect: { top: -50 } as DOMRectReadOnly, - target: { getAttribute: () => '0' } as unknown as Element, - intersectionRatio: 0, - time: 0, - rootBounds: null, - intersectionRect: {} as DOMRectReadOnly, - } as IntersectionObserverEntry; - - // Use vi.waitUntil to wait for callback to be assigned if needed, - // though strictly synchronous render + effect should do it. - // Direct call: - act(() => { - observerCallback([entry], {} as IntersectionObserver); - }); - - await waitFor(() => { - expect(global.fetch).toHaveBeenCalledWith('/api/item/101', expect.objectContaining({ - method: 'PUT', - body: JSON.stringify({ read: true, starred: false }), - })); - }); + // Simulate sentinel becoming visible + const entry = { + isIntersecting: true, + target: { id: 'load-more-sentinel' } as unknown as Element, + boundingClientRect: {} as DOMRectReadOnly, + intersectionRatio: 1, + time: 0, + rootBounds: null, + intersectionRect: {} as DOMRectReadOnly, + } as IntersectionObserverEntry; + + act(() => { + observerCallback([entry], {} as IntersectionObserver); }); - it('loads more items when sentinel becomes visible', async () => { - const initialItems = [{ _id: 101, title: 'Item 1', url: 'u1', read: true, starred: false }]; - const moreItems = [{ _id: 100, title: 'Item 0', url: 'u0', read: true, starred: false }]; - - (global.fetch as any) - .mockResolvedValueOnce({ ok: true, json: async () => initialItems }) - .mockResolvedValueOnce({ ok: true, json: async () => moreItems }); - - let observerCallback: IntersectionObserverCallback = () => { }; - class MockIntersectionObserver { - constructor(callback: IntersectionObserverCallback) { - observerCallback = callback; - } - observe = vi.fn(); - unobserve = vi.fn(); - disconnect = vi.fn(); - } - window.IntersectionObserver = MockIntersectionObserver as any; - - render( - - - - ); - - await waitFor(() => { - expect(screen.getByText('Item 1')).toBeInTheDocument(); - }); - - // Simulate sentinel becoming visible - const entry = { - isIntersecting: true, - target: { id: 'load-more-sentinel' } as unknown as Element, - boundingClientRect: {} as DOMRectReadOnly, - intersectionRatio: 1, - time: 0, - rootBounds: null, - intersectionRect: {} as DOMRectReadOnly, - } as IntersectionObserverEntry; - - act(() => { - observerCallback([entry], {} as IntersectionObserver); - }); - - await waitFor(() => { - expect(screen.getByText('Item 0')).toBeInTheDocument(); - const params = new URLSearchParams(); - params.append('max_id', '101'); - params.append('read_filter', 'unread'); - expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`); - }); + await waitFor(() => { + expect(screen.getByText('Item 0')).toBeInTheDocument(); + const params = new URLSearchParams(); + params.append('max_id', '101'); + params.append('read_filter', 'unread'); + expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`); }); + }); }); diff --git a/frontend/src/components/FeedItems.tsx b/frontend/src/components/FeedItems.tsx index bcee3b0..81c9139 100644 --- a/frontend/src/components/FeedItems.tsx +++ b/frontend/src/components/FeedItems.tsx @@ -5,227 +5,228 @@ import FeedItem from './FeedItem'; import './FeedItems.css'; export default function FeedItems() { - const { feedId, tagName } = useParams<{ feedId: string; tagName: string }>(); - const [searchParams] = useSearchParams(); - const filterFn = searchParams.get('filter') || 'unread'; - - const [items, setItems] = useState([]); - const [loading, setLoading] = useState(true); - const [loadingMore, setLoadingMore] = useState(false); - const [hasMore, setHasMore] = useState(true); - const [error, setError] = useState(''); - - const fetchItems = (maxId?: string) => { - if (maxId) { - setLoadingMore(true); - } else { - setLoading(true); - setItems([]); + const { feedId, tagName } = useParams<{ feedId: string; tagName: string }>(); + const [searchParams] = useSearchParams(); + const filterFn = searchParams.get('filter') || 'unread'; + + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + const [loadingMore, setLoadingMore] = useState(false); + const [hasMore, setHasMore] = useState(true); + const [error, setError] = useState(''); + const [selectedIndex, setSelectedIndex] = useState(-1); + + const fetchItems = (maxId?: string) => { + if (maxId) { + setLoadingMore(true); + } else { + setLoading(true); + setItems([]); + } + setError(''); + + let url = '/api/stream'; + const params = new URLSearchParams(); + + if (feedId) { + params.append('feed_id', feedId); + } else if (tagName) { + params.append('tag', tagName); + } + + if (maxId) { + params.append('max_id', maxId); + } + + // Apply filters + const searchQuery = searchParams.get('q'); + if (searchQuery) { + params.append('q', searchQuery); + } + + if (filterFn === 'all') { + params.append('read_filter', 'all'); + } else if (filterFn === 'starred') { + params.append('starred', 'true'); + params.append('read_filter', 'all'); + } else { + // default to unread + if (!searchQuery) { + params.append('read_filter', 'unread'); + } + } + + const queryString = params.toString(); + if (queryString) { + url += `?${queryString}`; + } + + fetch(url) + .then((res) => { + if (!res.ok) { + throw new Error('Failed to fetch items'); } - setError(''); - - let url = '/api/stream'; - const params = new URLSearchParams(); - - if (feedId) { - params.append('feed_id', feedId); - } else if (tagName) { - params.append('tag', tagName); - } - + return res.json(); + }) + .then((data) => { if (maxId) { - params.append('max_id', maxId); - } - - // Apply filters - const searchQuery = searchParams.get('q'); - if (searchQuery) { - params.append('q', searchQuery); - } - - if (filterFn === 'all') { - params.append('read_filter', 'all'); - } else if (filterFn === 'starred') { - params.append('starred', 'true'); - params.append('read_filter', 'all'); + setItems((prev) => [...prev, ...data]); } else { - // default to unread - if (!searchQuery) { - params.append('read_filter', 'unread'); - } - } - - const queryString = params.toString(); - if (queryString) { - url += `?${queryString}`; + setItems(data); } - - fetch(url) - .then((res) => { - if (!res.ok) { - throw new Error('Failed to fetch items'); - } - return res.json(); - }) - .then((data) => { - if (maxId) { - setItems((prev) => [...prev, ...data]); - } else { - setItems(data); - } - setHasMore(data.length > 0); - setLoading(false); - setLoadingMore(false); - }) - .catch((err) => { - setError(err.message); - setLoading(false); - setLoadingMore(false); - }); - }; - - useEffect(() => { - fetchItems(); - setSelectedIndex(-1); - }, [feedId, tagName, filterFn, searchParams]); - - const [selectedIndex, setSelectedIndex] = useState(-1); - - useEffect(() => { - const handleKeyDown = (e: KeyboardEvent) => { - if (items.length === 0) return; - - if (e.key === 'j') { - setSelectedIndex((prev) => { - const nextIndex = Math.min(prev + 1, items.length - 1); - if (nextIndex !== prev) { - const item = items[nextIndex]; - if (!item.read) { - markAsRead(item); - } - scrollToItem(nextIndex); - } - return nextIndex; - }); - } else if (e.key === 'k') { - setSelectedIndex((prev) => { - const nextIndex = Math.max(prev - 1, 0); - if (nextIndex !== prev) { - scrollToItem(nextIndex); - } - return nextIndex; - }); - } else if (e.key === 's') { - setSelectedIndex((currentIndex) => { - if (currentIndex >= 0 && currentIndex < items.length) { - toggleStar(items[currentIndex]); - } - return currentIndex; - }); + setHasMore(data.length > 0); + setLoading(false); + setLoadingMore(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + setLoadingMore(false); + }); + }; + + useEffect(() => { + fetchItems(); + setSelectedIndex(-1); + }, [feedId, tagName, filterFn, searchParams]); + + + const scrollToItem = (index: number) => { + const element = document.getElementById(`item-${index}`); + if (element) { + element.scrollIntoView({ behavior: 'auto', block: 'start' }); + } + }; + + const markAsRead = (item: Item) => { + const updatedItem = { ...item, read: true }; + // Optimistic update + setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i))); + + fetch(`/api/item/${item._id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ read: true, starred: item.starred }), + }).catch((err) => console.error('Failed to mark read', err)); + }; + + const toggleStar = (item: Item) => { + const updatedItem = { ...item, starred: !item.starred }; + // Optimistic update + setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i))); + + fetch(`/api/item/${item._id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ read: item.read, starred: !item.starred }), + }).catch((err) => console.error('Failed to toggle star', err)); + }; + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (items.length === 0) return; + + if (e.key === 'j') { + setSelectedIndex((prev) => { + const nextIndex = Math.min(prev + 1, items.length - 1); + if (nextIndex !== prev) { + const item = items[nextIndex]; + if (!item.read) { + markAsRead(item); } - }; - - window.addEventListener('keydown', handleKeyDown); - return () => window.removeEventListener('keydown', handleKeyDown); - }, [items]); - - const scrollToItem = (index: number) => { - const element = document.getElementById(`item-${index}`); - if (element) { - element.scrollIntoView({ behavior: 'auto', block: 'start' }); - } + scrollToItem(nextIndex); + } + return nextIndex; + }); + } else if (e.key === 'k') { + setSelectedIndex((prev) => { + const nextIndex = Math.max(prev - 1, 0); + if (nextIndex !== prev) { + scrollToItem(nextIndex); + } + return nextIndex; + }); + } else if (e.key === 's') { + setSelectedIndex((currentIndex) => { + if (currentIndex >= 0 && currentIndex < items.length) { + toggleStar(items[currentIndex]); + } + return currentIndex; + }); + } }; - const markAsRead = (item: Item) => { - const updatedItem = { ...item, read: true }; - // Optimistic update - setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i))); + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [items]); - fetch(`/api/item/${item._id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ read: true, starred: item.starred }), - }).catch((err) => console.error('Failed to mark read', err)); - }; - - const toggleStar = (item: Item) => { - const updatedItem = { ...item, starred: !item.starred }; - // Optimistic update - setItems((prevItems) => prevItems.map((i) => (i._id === item._id ? updatedItem : i))); - fetch(`/api/item/${item._id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ read: item.read, starred: !item.starred }), - }).catch((err) => console.error('Failed to toggle star', err)); - }; - useEffect(() => { - const observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - // Infinity scroll sentinel - if (entry.target.id === 'load-more-sentinel') { - if (entry.isIntersecting && !loadingMore && hasMore && items.length > 0) { - fetchItems(String(items[items.length - 1]._id)); - } - return; - } - - // If item is not intersecting and is above the viewport, it's been scrolled past - if (!entry.isIntersecting && entry.boundingClientRect.top < 0) { - const index = Number(entry.target.getAttribute('data-index')); - if (!isNaN(index) && index >= 0 && index < items.length) { - const item = items[index]; - if (!item.read) { - markAsRead(item); - } - } - } - }); - }, - { root: null, threshold: 0 } - ); - - items.forEach((_, index) => { - const el = document.getElementById(`item-${index}`); - if (el) observer.observe(el); + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + // Infinity scroll sentinel + if (entry.target.id === 'load-more-sentinel') { + if (entry.isIntersecting && !loadingMore && hasMore && items.length > 0) { + fetchItems(String(items[items.length - 1]._id)); + } + return; + } + + // If item is not intersecting and is above the viewport, it's been scrolled past + if (!entry.isIntersecting && entry.boundingClientRect.top < 0) { + const index = Number(entry.target.getAttribute('data-index')); + if (!isNaN(index) && index >= 0 && index < items.length) { + const item = items[index]; + if (!item.read) { + markAsRead(item); + } + } + } }); - - const sentinel = document.getElementById('load-more-sentinel'); - if (sentinel) observer.observe(sentinel); - - return () => observer.disconnect(); - }, [items, loadingMore, hasMore]); - - if (loading) return
    Loading items...
    ; - if (error) return
    Error: {error}
    ; - - - return ( -
    - {items.length === 0 ? ( -

    No items found.

    - ) : ( -
      - {items.map((item, index) => ( -
      setSelectedIndex(index)} - > - -
      - ))} - {hasMore && ( -
      - {loadingMore ? 'Loading more...' : ''} -
      - )} -
    - )} -
    + }, + { root: null, threshold: 0 } ); + + items.forEach((_, index) => { + const el = document.getElementById(`item-${index}`); + if (el) observer.observe(el); + }); + + const sentinel = document.getElementById('load-more-sentinel'); + if (sentinel) observer.observe(sentinel); + + return () => observer.disconnect(); + }, [items, loadingMore, hasMore]); + + if (loading) return
    Loading items...
    ; + if (error) return
    Error: {error}
    ; + + return ( +
    + {items.length === 0 ? ( +

    No items found.

    + ) : ( +
      + {items.map((item, index) => ( +
      setSelectedIndex(index)} + > + +
      + ))} + {hasMore && ( +
      + {loadingMore ? 'Loading more...' : ''} +
      + )} +
    + )} +
    + ); } diff --git a/frontend/src/components/FeedList.css b/frontend/src/components/FeedList.css index 0d6d26d..ff0f41b 100644 --- a/frontend/src/components/FeedList.css +++ b/frontend/src/components/FeedList.css @@ -1,170 +1,170 @@ .feed-list { - /* Removed card styling */ - padding: 0; - background: transparent; + /* Removed card styling */ + padding: 0; + background: transparent; } .search-section { - margin-bottom: 1.5rem; + margin-bottom: 1.5rem; } .search-form { - display: flex; + display: flex; } .search-input { - width: 100%; - padding: 0.5rem; - border: 1px solid #999; - background: #eee; - font-size: 1rem; - font-family: inherit; + width: 100%; + padding: 0.5rem; + border: 1px solid #999; + background: #eee; + font-size: 1rem; + font-family: inherit; } .search-input:focus { - outline: none; - background: white; - border-color: #000; + outline: none; + background: white; + border-color: #000; } .feed-list h2, .feed-section-header { - font-size: 1.2rem; - margin-bottom: 0.5rem; - border-bottom: 1px solid #999; - padding-bottom: 0.25rem; - text-transform: uppercase; - letter-spacing: 1px; - cursor: pointer; - user-select: none; - display: flex; - align-items: center; + font-size: 1.2rem; + margin-bottom: 0.5rem; + border-bottom: 1px solid #999; + padding-bottom: 0.25rem; + text-transform: uppercase; + letter-spacing: 1px; + cursor: pointer; + user-select: none; + display: flex; + align-items: center; } .toggle-indicator { - font-size: 0.8rem; - margin-right: 0.5rem; - display: inline-block; - width: 1rem; - text-align: center; + font-size: 0.8rem; + margin-right: 0.5rem; + display: inline-block; + width: 1rem; + text-align: center; } .feed-list-items, .tag-list-items, .filter-list { - list-style: none; - padding: 0; - margin: 0; + list-style: none; + padding: 0; + margin: 0; } .sidebar-feed-item { - padding: 0.25rem 0; - border-bottom: none; - /* Clean look */ - display: flex; - justify-content: space-between; - align-items: center; + padding: 0.25rem 0; + border-bottom: none; + /* Clean look */ + display: flex; + justify-content: space-between; + align-items: center; } .feed-title { - color: var(--link-color); - text-decoration: none; - font-size: 0.9rem; - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + color: var(--link-color); + text-decoration: none; + font-size: 0.9rem; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } .feed-title:hover { - text-decoration: underline; - color: var(--link-color); + text-decoration: underline; + color: var(--link-color); } .feed-category { - display: none; - /* Hide category in sidebar list to save space */ + display: none; + /* Hide category in sidebar list to save space */ } .tag-section { - margin-top: 2rem; + margin-top: 2rem; } .tag-link { - color: var(--link-color); - text-decoration: none; - font-size: 0.9rem; - display: block; - padding: 0.1rem 0; - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + color: var(--link-color); + text-decoration: none; + font-size: 0.9rem; + display: block; + padding: 0.1rem 0; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } .tag-link:hover { - text-decoration: underline; - background: transparent; - color: var(--link-color); + text-decoration: underline; + background: transparent; + color: var(--link-color); } .filter-section { - margin-bottom: 2rem; + margin-bottom: 2rem; } .filter-list { - display: block; - list-style: none; - padding: 0; - margin: 0; + display: block; + list-style: none; + padding: 0; + margin: 0; } .filter-list li a { - text-decoration: none; - color: #333; - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-variant: small-caps; - text-transform: lowercase; - font-size: 1.1rem; - display: block; - margin-bottom: 0.5rem; + text-decoration: none; + color: var(--text-color); + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-variant: small-caps; + text-transform: lowercase; + font-size: 1.1rem; + display: block; + margin-bottom: 0.5rem; } .filter-list li a:hover { - color: blue; - background-color: transparent; - text-decoration: underline; + color: blue; + background-color: transparent; + text-decoration: underline; } .feed-title.active, .tag-link.active, .filter-list li a.active, .theme-selector button.active { - font-weight: bold !important; + font-weight: bold !important; } .theme-section { - margin-top: 2rem; - padding-bottom: 2rem; + margin-top: 2rem; + padding-bottom: 2rem; } .theme-selector { - display: flex; - justify-content: space-between; - gap: 5px; + display: flex; + justify-content: space-between; + gap: 5px; } .theme-selector button { - font-size: 0.8rem; - padding: 0.2rem 0.5rem; - width: 30%; - background: whitesmoke; - color: blue; - border: 1px solid #ccc; - border-radius: 4px; - font-variant: small-caps; - text-transform: lowercase; + font-size: 0.8rem; + padding: 0.2rem 0.5rem; + width: 30%; + background: whitesmoke; + color: blue; + border: 1px solid #ccc; + border-radius: 4px; + font-variant: small-caps; + text-transform: lowercase; } .theme-selector button:hover { - background: #eee; + background: #eee; } .theme-selector button.active { - color: black; - border-color: #000; -} \ No newline at end of file + color: black; + border-color: #000; +} diff --git a/frontend/src/components/FeedList.test.tsx b/frontend/src/components/FeedList.test.tsx index d5f49b7..daa4d69 100644 --- a/frontend/src/components/FeedList.test.tsx +++ b/frontend/src/components/FeedList.test.tsx @@ -7,114 +7,126 @@ import FeedList from './FeedList'; import { BrowserRouter } from 'react-router-dom'; describe('FeedList Component', () => { - beforeEach(() => { - vi.resetAllMocks(); - global.fetch = vi.fn(); - }); - - it('renders loading state initially', () => { - (global.fetch as any).mockImplementation(() => new Promise(() => { })); - render( - - {/* @ts-ignore */} - { }} /> - - ); - expect(screen.getByText(/loading feeds/i)).toBeInTheDocument(); - }); - - it('renders list of feeds', async () => { - const mockFeeds = [ - { _id: 1, title: 'Feed One', url: 'http://example.com/rss', web_url: 'http://example.com', category: 'Tech' }, - { _id: 2, title: 'Feed Two', url: 'http://test.com/rss', web_url: 'http://test.com', category: 'News' }, - ]; - - (global.fetch as any).mockImplementation((url: string) => { - if (url.includes('/api/feed/')) { - return Promise.resolve({ - ok: true, - json: async () => mockFeeds, - }); - } - if (url.includes('/api/tag')) { - return Promise.resolve({ - ok: true, - json: async () => [{ title: 'Tech' }], - }); - } - return Promise.reject(new Error(`Unknown URL: ${url}`)); + beforeEach(() => { + vi.resetAllMocks(); + global.fetch = vi.fn(); + }); + + it('renders loading state initially', () => { + (global.fetch as any).mockImplementation(() => new Promise(() => {})); + render( + + {/* @ts-ignore */} + {}} /> + + ); + expect(screen.getByText(/loading feeds/i)).toBeInTheDocument(); + }); + + it('renders list of feeds', async () => { + const mockFeeds = [ + { + _id: 1, + title: 'Feed One', + url: 'http://example.com/rss', + web_url: 'http://example.com', + category: 'Tech', + }, + { + _id: 2, + title: 'Feed Two', + url: 'http://test.com/rss', + web_url: 'http://test.com', + category: 'News', + }, + ]; + + (global.fetch as any).mockImplementation((url: string) => { + if (url.includes('/api/feed/')) { + return Promise.resolve({ + ok: true, + json: async () => mockFeeds, }); + } + if (url.includes('/api/tag')) { + return Promise.resolve({ + ok: true, + json: async () => [{ title: 'Tech' }], + }); + } + return Promise.reject(new Error(`Unknown URL: ${url}`)); + }); - render( - - {/* @ts-ignore */} - { }} /> - - ); + render( + + {/* @ts-ignore */} + {}} /> + + ); - await waitFor(() => { - expect(screen.queryByText(/loading feeds/i)).not.toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.queryByText(/loading feeds/i)).not.toBeInTheDocument(); + }); - // Expand feeds - fireEvent.click(screen.getByText(/feeds/i, { selector: 'h2' })); + // Expand feeds + fireEvent.click(screen.getByText(/feeds/i, { selector: 'h2' })); - await waitFor(() => { - expect(screen.getByText('Feed One')).toBeInTheDocument(); - expect(screen.getByText('Feed Two')).toBeInTheDocument(); - const techElements = screen.getAllByText('Tech'); - expect(techElements.length).toBeGreaterThan(0); - }); + await waitFor(() => { + expect(screen.getByText('Feed One')).toBeInTheDocument(); + expect(screen.getByText('Feed Two')).toBeInTheDocument(); + const techElements = screen.getAllByText('Tech'); + expect(techElements.length).toBeGreaterThan(0); }); + }); - it('handles fetch error', async () => { - (global.fetch as any).mockImplementation(() => Promise.reject(new Error('API Error'))); + it('handles fetch error', async () => { + (global.fetch as any).mockImplementation(() => Promise.reject(new Error('API Error'))); - render( - - {/* @ts-ignore */} - { }} /> - - ); + render( + + {/* @ts-ignore */} + {}} /> + + ); - await waitFor(() => { - expect(screen.getByText(/error: api error/i)).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByText(/error: api error/i)).toBeInTheDocument(); }); - - it('handles empty feed list', async () => { - (global.fetch as any).mockImplementation((url: string) => { - if (url.includes('/api/feed/')) { - return Promise.resolve({ - ok: true, - json: async () => [], - }); - } - if (url.includes('/api/tag')) { - return Promise.resolve({ - ok: true, - json: async () => [], - }); - } - return Promise.reject(new Error(`Unknown URL: ${url}`)); + }); + + it('handles empty feed list', async () => { + (global.fetch as any).mockImplementation((url: string) => { + if (url.includes('/api/feed/')) { + return Promise.resolve({ + ok: true, + json: async () => [], + }); + } + if (url.includes('/api/tag')) { + return Promise.resolve({ + ok: true, + json: async () => [], }); + } + return Promise.reject(new Error(`Unknown URL: ${url}`)); + }); - render( - - {/* @ts-ignore */} - { }} /> - - ); + render( + + {/* @ts-ignore */} + {}} /> + + ); - await waitFor(() => { - expect(screen.queryByText(/loading feeds/i)).not.toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.queryByText(/loading feeds/i)).not.toBeInTheDocument(); + }); - // Expand feeds - fireEvent.click(screen.getByText(/feeds/i, { selector: 'h2' })); + // Expand feeds + fireEvent.click(screen.getByText(/feeds/i, { selector: 'h2' })); - await waitFor(() => { - expect(screen.getByText(/no feeds found/i)).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByText(/no feeds found/i)).toBeInTheDocument(); }); + }); }); diff --git a/frontend/src/components/FeedList.tsx b/frontend/src/components/FeedList.tsx index 56c96cd..497baf8 100644 --- a/frontend/src/components/FeedList.tsx +++ b/frontend/src/components/FeedList.tsx @@ -3,121 +3,151 @@ import { Link, useNavigate, useSearchParams, useLocation, useParams } from 'reac import type { Feed, Category } from '../types'; import './FeedList.css'; -export default function FeedList({ theme, setTheme }: { theme: string, setTheme: (t: string) => void }) { - const [feeds, setFeeds] = useState([]); - const [tags, setTags] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(''); - const [feedsExpanded, setFeedsExpanded] = useState(false); - const [searchQuery, setSearchQuery] = useState(''); - const navigate = useNavigate(); - const [searchParams] = useSearchParams(); - const location = useLocation(); - const { feedId, tagName } = useParams(); +export default function FeedList({ + theme, + setTheme, +}: { + theme: string; + setTheme: (t: string) => void; +}) { + const [feeds, setFeeds] = useState([]); + const [tags, setTags] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [feedsExpanded, setFeedsExpanded] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const location = useLocation(); + const { feedId, tagName } = useParams(); - const currentFilter = searchParams.get('filter') || (location.pathname === '/' && !feedId && !tagName ? 'unread' : ''); + const currentFilter = + searchParams.get('filter') || + (location.pathname === '/' && !feedId && !tagName ? 'unread' : ''); - const handleSearch = (e: React.FormEvent) => { - e.preventDefault(); - if (searchQuery.trim()) { - navigate(`/?q=${encodeURIComponent(searchQuery.trim())}`); - } - }; + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + if (searchQuery.trim()) { + navigate(`/?q=${encodeURIComponent(searchQuery.trim())}`); + } + }; - const toggleFeeds = () => { - setFeedsExpanded(!feedsExpanded); - }; + const toggleFeeds = () => { + setFeedsExpanded(!feedsExpanded); + }; - useEffect(() => { - Promise.all([ - fetch('/api/feed/').then(res => { - if (!res.ok) throw new Error('Failed to fetch feeds'); - return res.json(); - }), - fetch('/api/tag').then(res => { - if (!res.ok) throw new Error('Failed to fetch tags'); - return res.json(); - }) - ]) - .then(([feedsData, tagsData]) => { - setFeeds(feedsData); - setTags(tagsData); - setLoading(false); - }) - .catch((err) => { - setError(err.message); - setLoading(false); - }); - }, []); + useEffect(() => { + Promise.all([ + fetch('/api/feed/').then((res) => { + if (!res.ok) throw new Error('Failed to fetch feeds'); + return res.json(); + }), + fetch('/api/tag').then((res) => { + if (!res.ok) throw new Error('Failed to fetch tags'); + return res.json(); + }), + ]) + .then(([feedsData, tagsData]) => { + setFeeds(feedsData); + setTags(tagsData); + setLoading(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }, []); - if (loading) return
    Loading feeds...
    ; - if (error) return
    Error: {error}
    ; + if (loading) return
    Loading feeds...
    ; + if (error) return
    Error: {error}
    ; - return ( -
    -
    -
    - setSearchQuery(e.target.value)} - className="search-input" - /> -
    -
    -
    -
      -
    • Unread
    • -
    • All
    • -
    • Starred
    • -
    -
    -
    -

    - {feedsExpanded ? '▼' : '▶'} Feeds -

    - {feedsExpanded && ( - feeds.length === 0 ? ( -

    No feeds found.

    - ) : ( -
      - {feeds.map((feed) => ( -
    • - - {feed.title || feed.url} - - {feed.category && {feed.category}} -
    • - ))} -
    - ) - )} -
    + return ( +
    +
    +
    + setSearchQuery(e.target.value)} + className="search-input" + /> +
    +
    +
    +
      +
    • + + Unread + +
    • +
    • + + All + +
    • +
    • + + Starred + +
    • +
    +
    +
    +

    + {feedsExpanded ? '▼' : '▶'} Feeds +

    + {feedsExpanded && + (feeds.length === 0 ? ( +

    No feeds found.

    + ) : ( +
      + {feeds.map((feed) => ( +
    • + + {feed.title || feed.url} + + {feed.category && {feed.category}} +
    • + ))} +
    + ))} +
    - {tags && tags.length > 0 && ( -
    -

    Tags

    -
      - {tags.map((tag) => ( -
    • - - {tag.title} - -
    • - ))} -
    -
    - )} + {tags && tags.length > 0 && ( +
    +

    Tags

    +
      + {tags.map((tag) => ( +
    • + + {tag.title} + +
    • + ))} +
    +
    + )} -
    -

    Themes

    -
    - - - -
    -
    +
    +
    + + +
    - ); +
    +
    + ); } diff --git a/frontend/src/components/Login.css b/frontend/src/components/Login.css index f1ca976..6f40731 100644 --- a/frontend/src/components/Login.css +++ b/frontend/src/components/Login.css @@ -46,7 +46,7 @@ text-align: center; } -button[type="submit"] { +button[type='submit'] { width: 100%; padding: 0.75rem; background-color: #007bff; @@ -58,6 +58,6 @@ button[type="submit"] { transition: background-color 0.2s; } -button[type="submit"]:hover { +button[type='submit']:hover { background-color: #0056b3; } diff --git a/frontend/src/components/Login.test.tsx b/frontend/src/components/Login.test.tsx index ef946e2..aea7042 100644 --- a/frontend/src/components/Login.test.tsx +++ b/frontend/src/components/Login.test.tsx @@ -9,70 +9,73 @@ import Login from './Login'; global.fetch = vi.fn(); const renderLogin = () => { - render( - - - - ); + render( + + + + ); }; describe('Login Component', () => { - beforeEach(() => { - vi.resetAllMocks(); + beforeEach(() => { + vi.resetAllMocks(); + }); + + it('renders login form', () => { + renderLogin(); + expect(screen.getByLabelText(/password/i)).toBeInTheDocument(); + expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument(); + }); + + it('handles successful login', async () => { + (global.fetch as any).mockResolvedValueOnce({ + ok: true, }); - it('renders login form', () => { - renderLogin(); - expect(screen.getByLabelText(/password/i)).toBeInTheDocument(); - expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument(); - }); - - it('handles successful login', async () => { - (global.fetch as any).mockResolvedValueOnce({ - ok: true, - }); - - renderLogin(); + renderLogin(); - fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'secret' } }); - fireEvent.click(screen.getByRole('button', { name: /login/i })); + fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'secret' } }); + fireEvent.click(screen.getByRole('button', { name: /login/i })); - await waitFor(() => { - expect(global.fetch).toHaveBeenCalledWith('/api/login', expect.objectContaining({ - method: 'POST', - })); - }); - // Navigation assertion is tricky without mocking useNavigate, - // but if no error is shown, we assume success path was taken - expect(screen.queryByText(/login failed/i)).not.toBeInTheDocument(); + await waitFor(() => { + expect(global.fetch).toHaveBeenCalledWith( + '/api/login', + expect.objectContaining({ + method: 'POST', + }) + ); + }); + // Navigation assertion is tricky without mocking useNavigate, + // but if no error is shown, we assume success path was taken + expect(screen.queryByText(/login failed/i)).not.toBeInTheDocument(); + }); + + it('handles failed login', async () => { + (global.fetch as any).mockResolvedValueOnce({ + ok: false, + json: async () => ({ message: 'Bad credentials' }), }); - it('handles failed login', async () => { - (global.fetch as any).mockResolvedValueOnce({ - ok: false, - json: async () => ({ message: 'Bad credentials' }), - }); - - renderLogin(); + renderLogin(); - fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'wrong' } }); - fireEvent.click(screen.getByRole('button', { name: /login/i })); + fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'wrong' } }); + fireEvent.click(screen.getByRole('button', { name: /login/i })); - await waitFor(() => { - expect(screen.getByText(/bad credentials/i)).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByText(/bad credentials/i)).toBeInTheDocument(); }); + }); - it('handles network error', async () => { - (global.fetch as any).mockRejectedValueOnce(new Error('Network error')); + it('handles network error', async () => { + (global.fetch as any).mockRejectedValueOnce(new Error('Network error')); - renderLogin(); + renderLogin(); - fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'secret' } }); - fireEvent.click(screen.getByRole('button', { name: /login/i })); + fireEvent.change(screen.getByLabelText(/password/i), { target: { value: 'secret' } }); + fireEvent.click(screen.getByRole('button', { name: /login/i })); - await waitFor(() => { - expect(screen.getByText(/network error/i)).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByText(/network error/i)).toBeInTheDocument(); }); + }); }); diff --git a/frontend/src/components/Login.tsx b/frontend/src/components/Login.tsx index 2e8bbf7..5f63248 100644 --- a/frontend/src/components/Login.tsx +++ b/frontend/src/components/Login.tsx @@ -3,52 +3,52 @@ import { useNavigate } from 'react-router-dom'; import './Login.css'; export default function Login() { - const [password, setPassword] = useState(''); - const [error, setError] = useState(''); - const navigate = useNavigate(); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const navigate = useNavigate(); - const handleSubmit = async (e: FormEvent) => { - e.preventDefault(); - setError(''); + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + setError(''); - try { - // Use URLSearchParams to send as form-urlencoded, matching backend expectation - const params = new URLSearchParams(); - params.append('password', password); + try { + // Use URLSearchParams to send as form-urlencoded, matching backend expectation + const params = new URLSearchParams(); + params.append('password', password); - const res = await fetch('/api/login', { - method: 'POST', - body: params, - }); + const res = await fetch('/api/login', { + method: 'POST', + body: params, + }); - if (res.ok) { - navigate('/'); - } else { - const data = await res.json(); - setError(data.message || 'Login failed'); - } - } catch (err) { - setError('Network error'); - } - }; + if (res.ok) { + navigate('/'); + } else { + const data = await res.json(); + setError(data.message || 'Login failed'); + } + } catch (err) { + setError('Network error'); + } + }; - return ( -
    -
    -

    neko rss mode

    -
    - - setPassword(e.target.value)} - autoFocus - /> -
    - {error &&
    {error}
    } - -
    + return ( +
    +
    +

    neko rss mode

    +
    + + setPassword(e.target.value)} + autoFocus + />
    - ); + {error &&
    {error}
    } + +
    +
    + ); } diff --git a/frontend/src/components/Settings.css b/frontend/src/components/Settings.css index 4065e88..6e74475 100644 --- a/frontend/src/components/Settings.css +++ b/frontend/src/components/Settings.css @@ -1,83 +1,84 @@ .settings-page { - padding: 2rem; - max-width: 800px; - margin: 0 auto; + padding: 2rem; + max-width: 800px; + margin: 0 auto; } .add-feed-section { - background: #f9f9f9; - padding: 1.5rem; - border-radius: 8px; - margin-bottom: 2rem; - border: 1px solid #eee; + background: #f9f9f9; + padding: 1.5rem; + border-radius: 8px; + margin-bottom: 2rem; + border: 1px solid #eee; } .add-feed-form { - display: flex; - gap: 1rem; + display: flex; + gap: 1rem; } .feed-input { - flex: 1; - padding: 0.5rem; - border: 1px solid #ccc; - border-radius: 4px; - font-size: 1rem; + flex: 1; + padding: 0.5rem; + border: 1px solid #ccc; + border-radius: 4px; + font-size: 1rem; } .error-message { - color: #d32f2f; - margin-top: 1rem; + color: #d32f2f; + margin-top: 1rem; } .settings-feed-list { - list-style: none; - padding: 0; - border: 1px solid #eee; - border-radius: 8px; + list-style: none; + padding: 0; + border: 1px solid #eee; + border-radius: 8px; } .settings-feed-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 1rem; - border-bottom: 1px solid #eee; + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + border-bottom: 1px solid #eee; } .settings-feed-item:last-child { - border-bottom: none; + border-bottom: none; } .feed-info { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .feed-title { - font-weight: bold; - font-size: 1.1rem; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-weight: bold; + font-size: 1.1rem; } .feed-url { - color: #666; - font-size: 0.9rem; + color: #666; + font-size: 0.9rem; } .delete-btn { - background: #ff5252; - color: white; - border: none; - padding: 0.5rem 1rem; - border-radius: 4px; - cursor: pointer; + background: #ff5252; + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 4px; + cursor: pointer; } .delete-btn:hover { - background: #ff1744; + background: #ff1744; } .delete-btn:disabled { - background: #ffcdd2; - cursor: not-allowed; -} \ No newline at end of file + background: #ffcdd2; + cursor: not-allowed; +} diff --git a/frontend/src/components/Settings.test.tsx b/frontend/src/components/Settings.test.tsx index a15192d..f46ce6f 100644 --- a/frontend/src/components/Settings.test.tsx +++ b/frontend/src/components/Settings.test.tsx @@ -5,88 +5,97 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import Settings from './Settings'; describe('Settings Component', () => { - beforeEach(() => { - vi.resetAllMocks(); - global.fetch = vi.fn(); - // Mock confirm - global.confirm = vi.fn(() => true); + beforeEach(() => { + vi.resetAllMocks(); + global.fetch = vi.fn(); + // Mock confirm + global.confirm = vi.fn(() => true); + }); + + it('renders feed list', async () => { + const mockFeeds = [ + { _id: 1, title: 'Tech News', url: 'http://tech.com/rss', category: 'tech' }, + { _id: 2, title: 'Gaming', url: 'http://gaming.com/rss', category: 'gaming' }, + ]; + + (global.fetch as any).mockResolvedValueOnce({ + ok: true, + json: async () => mockFeeds, }); - it('renders feed list', async () => { - const mockFeeds = [ - { _id: 1, title: 'Tech News', url: 'http://tech.com/rss', category: 'tech' }, - { _id: 2, title: 'Gaming', url: 'http://gaming.com/rss', category: 'gaming' }, - ]; + render(); - (global.fetch as any).mockResolvedValueOnce({ - ok: true, - json: async () => mockFeeds, - }); - - render(); - - await waitFor(() => { - expect(screen.getByText('Tech News')).toBeInTheDocument(); - expect(screen.getByText('http://tech.com/rss')).toBeInTheDocument(); - expect(screen.getByText('Gaming')).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.getByText('Tech News')).toBeInTheDocument(); + expect(screen.getByText('http://tech.com/rss')).toBeInTheDocument(); + expect(screen.getByText('Gaming')).toBeInTheDocument(); + }); + }); + + it('adds a new feed', async () => { + (global.fetch as any) + .mockResolvedValueOnce({ ok: true, json: async () => [] }) // Initial load + .mockResolvedValueOnce({ ok: true, json: async () => ({}) }) // Add feed + .mockResolvedValueOnce({ + ok: true, + json: async () => [{ _id: 3, title: 'New Feed', url: 'http://new.com/rss' }], + }); // Refresh load + + render(); + + // Wait for initial load to finish + await waitFor(() => { + expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); }); - it('adds a new feed', async () => { - (global.fetch as any) - .mockResolvedValueOnce({ ok: true, json: async () => [] }) // Initial load - .mockResolvedValueOnce({ ok: true, json: async () => ({}) }) // Add feed - .mockResolvedValueOnce({ ok: true, json: async () => [{ _id: 3, title: 'New Feed', url: 'http://new.com/rss' }] }); // Refresh load - - render(); - - // Wait for initial load to finish - await waitFor(() => { - expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); - }); - - const input = screen.getByPlaceholderText('https://example.com/feed.xml'); - const button = screen.getByText('Add Feed'); + const input = screen.getByPlaceholderText('https://example.com/feed.xml'); + const button = screen.getByText('Add Feed'); - fireEvent.change(input, { target: { value: 'http://new.com/rss' } }); - fireEvent.click(button); + fireEvent.change(input, { target: { value: 'http://new.com/rss' } }); + fireEvent.click(button); - await waitFor(() => { - expect(global.fetch).toHaveBeenCalledWith('/api/feed/', expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ url: 'http://new.com/rss' }), - })); - }); + await waitFor(() => { + expect(global.fetch).toHaveBeenCalledWith( + '/api/feed/', + expect.objectContaining({ + method: 'POST', + body: JSON.stringify({ url: 'http://new.com/rss' }), + }) + ); + }); - // Wait for refresh - await waitFor(() => { - expect(screen.getByText('New Feed')).toBeInTheDocument(); - }); + // Wait for refresh + await waitFor(() => { + expect(screen.getByText('New Feed')).toBeInTheDocument(); }); + }); - it('deletes a feed', async () => { - const mockFeeds = [ - { _id: 1, title: 'Tech News', url: 'http://tech.com/rss', category: 'tech' }, - ]; + it('deletes a feed', async () => { + const mockFeeds = [ + { _id: 1, title: 'Tech News', url: 'http://tech.com/rss', category: 'tech' }, + ]; - (global.fetch as any) - .mockResolvedValueOnce({ ok: true, json: async () => mockFeeds }) // Initial load - .mockResolvedValueOnce({ ok: true }); // Delete + (global.fetch as any) + .mockResolvedValueOnce({ ok: true, json: async () => mockFeeds }) // Initial load + .mockResolvedValueOnce({ ok: true }); // Delete - render(); + render(); - await waitFor(() => { - expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); - expect(screen.getByText('Tech News')).toBeInTheDocument(); - }); + await waitFor(() => { + expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); + expect(screen.getByText('Tech News')).toBeInTheDocument(); + }); - const deleteBtn = screen.getByTitle('Delete Feed'); - fireEvent.click(deleteBtn); + const deleteBtn = screen.getByTitle('Delete Feed'); + fireEvent.click(deleteBtn); - await waitFor(() => { - expect(global.confirm).toHaveBeenCalled(); - expect(global.fetch).toHaveBeenCalledWith('/api/feed/1', expect.objectContaining({ method: 'DELETE' })); - expect(screen.queryByText('Tech News')).not.toBeInTheDocument(); - }); + await waitFor(() => { + expect(global.confirm).toHaveBeenCalled(); + expect(global.fetch).toHaveBeenCalledWith( + '/api/feed/1', + expect.objectContaining({ method: 'DELETE' }) + ); + expect(screen.queryByText('Tech News')).not.toBeInTheDocument(); }); + }); }); diff --git a/frontend/src/components/Settings.tsx b/frontend/src/components/Settings.tsx index def8ffe..b4f6a3b 100644 --- a/frontend/src/components/Settings.tsx +++ b/frontend/src/components/Settings.tsx @@ -3,119 +3,121 @@ import type { Feed } from '../types'; import './Settings.css'; export default function Settings() { - const [feeds, setFeeds] = useState([]); - const [newFeedUrl, setNewFeedUrl] = useState(''); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); + const [feeds, setFeeds] = useState([]); + const [newFeedUrl, setNewFeedUrl] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); - useEffect(() => { - fetchFeeds(); - }, []); + const fetchFeeds = () => { + setLoading(true); + fetch('/api/feed/') + .then((res) => { + if (!res.ok) throw new Error('Failed to fetch feeds'); + return res.json(); + }) + .then((data) => { + setFeeds(data); + setLoading(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }; - const fetchFeeds = () => { - setLoading(true); - fetch('/api/feed/') - .then((res) => { - if (!res.ok) throw new Error('Failed to fetch feeds'); - return res.json(); - }) - .then((data) => { - setFeeds(data); - setLoading(false); - }) - .catch((err) => { - setError(err.message); - setLoading(false); - }); - }; + useEffect(() => { + fetchFeeds(); + }, []); - const handleAddFeed = (e: React.FormEvent) => { - e.preventDefault(); - if (!newFeedUrl) return; - setLoading(true); - fetch('/api/feed/', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ url: newFeedUrl }), - }) - .then((res) => { - if (!res.ok) throw new Error('Failed to add feed'); - return res.json(); - }) - .then(() => { - setNewFeedUrl(''); - fetchFeeds(); // Refresh list (or we could append if server returns full feed object) - }) - .catch((err) => { - setError(err.message); - setLoading(false); - }); - }; - const handleDeleteFeed = (id: number) => { - if (!globalThis.confirm('Are you sure you want to delete this feed?')) return; + const handleAddFeed = (e: React.FormEvent) => { + e.preventDefault(); + if (!newFeedUrl) return; - setLoading(true); - fetch(`/api/feed/${id}`, { - method: 'DELETE', - }) - .then((res) => { - if (!res.ok) throw new Error('Failed to delete feed'); - setFeeds(feeds.filter((f) => f._id !== id)); - setLoading(false); - }) - .catch((err) => { - setError(err.message); - setLoading(false); - }); - }; + setLoading(true); + fetch('/api/feed/', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ url: newFeedUrl }), + }) + .then((res) => { + if (!res.ok) throw new Error('Failed to add feed'); + return res.json(); + }) + .then(() => { + setNewFeedUrl(''); + fetchFeeds(); // Refresh list (or we could append if server returns full feed object) + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }; - return ( -
    -

    Settings

    + const handleDeleteFeed = (id: number) => { + if (!globalThis.confirm('Are you sure you want to delete this feed?')) return; -
    -

    Add New Feed

    -
    - setNewFeedUrl(e.target.value)} - placeholder="https://example.com/feed.xml" - required - className="feed-input" - disabled={loading} - /> - -
    - {error &&

    {error}

    } -
    + setLoading(true); + fetch(`/api/feed/${id}`, { + method: 'DELETE', + }) + .then((res) => { + if (!res.ok) throw new Error('Failed to delete feed'); + setFeeds(feeds.filter((f) => f._id !== id)); + setLoading(false); + }) + .catch((err) => { + setError(err.message); + setLoading(false); + }); + }; -
    -

    Manage Feeds

    - {loading &&

    Loading...

    } -
      - {feeds.map((feed) => ( -
    • -
      - {feed.title || '(No Title)'} - {feed.url} -
      - -
    • - ))} -
    -
    -
    - ); + return ( +
    +

    Settings

    + +
    +

    Add New Feed

    +
    + setNewFeedUrl(e.target.value)} + placeholder="https://example.com/feed.xml" + required + className="feed-input" + disabled={loading} + /> + +
    + {error &&

    {error}

    } +
    + +
    +

    Manage Feeds

    + {loading &&

    Loading...

    } +
      + {feeds.map((feed) => ( +
    • +
      + {feed.title || '(No Title)'} + {feed.url} +
      + +
    • + ))} +
    +
    +
    + ); } diff --git a/frontend/src/components/TagView.test.tsx b/frontend/src/components/TagView.test.tsx index d19d4bb..10872bc 100644 --- a/frontend/src/components/TagView.test.tsx +++ b/frontend/src/components/TagView.test.tsx @@ -6,79 +6,81 @@ import FeedList from './FeedList'; import FeedItems from './FeedItems'; describe('Tag View Integration', () => { - beforeEach(() => { - vi.resetAllMocks(); - global.fetch = vi.fn(); - }); + beforeEach(() => { + vi.resetAllMocks(); + global.fetch = vi.fn(); + }); - it('renders tags in FeedList and navigates to tag view', async () => { - const mockFeeds = [{ _id: 1, title: 'Feed 1', url: 'http://example.com/rss', category: 'Tech' }]; - const mockTags = [{ title: 'Tech' }, { title: 'News' }]; + it('renders tags in FeedList and navigates to tag view', async () => { + const mockFeeds = [ + { _id: 1, title: 'Feed 1', url: 'http://example.com/rss', category: 'Tech' }, + ]; + const mockTags = [{ title: 'Tech' }, { title: 'News' }]; - (global.fetch as any).mockImplementation((url: string) => { - if (url.includes('/api/feed/')) { - return Promise.resolve({ - ok: true, - json: async () => mockFeeds, - }); - } - if (url.includes('/api/tag')) { - return Promise.resolve({ - ok: true, - json: async () => mockTags, - }); - } - return Promise.reject(new Error(`Unknown URL: ${url}`)); + (global.fetch as any).mockImplementation((url: string) => { + if (url.includes('/api/feed/')) { + return Promise.resolve({ + ok: true, + json: async () => mockFeeds, }); - - render( - - - - ); - - await waitFor(() => { - const techTags = screen.getAllByText('Tech'); - expect(techTags.length).toBeGreaterThan(0); - expect(screen.getByText('News')).toBeInTheDocument(); + } + if (url.includes('/api/tag')) { + return Promise.resolve({ + ok: true, + json: async () => mockTags, }); - - // Verify structure - const techTag = screen.getByText('News').closest('a'); - expect(techTag).toHaveAttribute('href', '/tag/News'); + } + return Promise.reject(new Error(`Unknown URL: ${url}`)); }); - it('fetches items by tag in FeedItems', async () => { - const mockItems = [ - { _id: 101, title: 'Tag Item 1', url: 'http://example.com/1', feed_title: 'Feed 1' } - ]; + render( + + + + ); - (global.fetch as any).mockImplementation((url: string) => { - if (url.includes('/api/stream')) { - return Promise.resolve({ - ok: true, - json: async () => mockItems, - }); - } - return Promise.reject(new Error(`Unknown URL: ${url}`)); - }); + await waitFor(() => { + const techTags = screen.getAllByText('Tech'); + expect(techTags.length).toBeGreaterThan(0); + expect(screen.getByText('News')).toBeInTheDocument(); + }); + + // Verify structure + const techTag = screen.getByText('News').closest('a'); + expect(techTag).toHaveAttribute('href', '/tag/News'); + }); - render( - - - } /> - - - ); + it('fetches items by tag in FeedItems', async () => { + const mockItems = [ + { _id: 101, title: 'Tag Item 1', url: 'http://example.com/1', feed_title: 'Feed 1' }, + ]; - await waitFor(() => { - // expect(screen.getByText('Tag: Tech')).toBeInTheDocument(); - expect(screen.getByText('Tag Item 1')).toBeInTheDocument(); + (global.fetch as any).mockImplementation((url: string) => { + if (url.includes('/api/stream')) { + return Promise.resolve({ + ok: true, + json: async () => mockItems, }); + } + return Promise.reject(new Error(`Unknown URL: ${url}`)); + }); - const params = new URLSearchParams(); - params.append('tag', 'Tech'); - params.append('read_filter', 'unread'); - expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`); + render( + + + } /> + + + ); + + await waitFor(() => { + // expect(screen.getByText('Tag: Tech')).toBeInTheDocument(); + expect(screen.getByText('Tag Item 1')).toBeInTheDocument(); }); + + const params = new URLSearchParams(); + params.append('tag', 'Tech'); + params.append('read_filter', 'unread'); + expect(global.fetch).toHaveBeenCalledWith(`/api/stream?${params.toString()}`); + }); }); diff --git a/frontend/src/index.css b/frontend/src/index.css index aca76c6..209a30a 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -16,24 +16,18 @@ h5, :root { line-height: 1.5; - font-weight: 400; font-size: 18px; /* Light Mode Defaults */ --bg-color: #ffffff; --text-color: rgba(0, 0, 0, 0.87); --sidebar-bg: #ccc; - --link-color: #0000EE; + --link-color: #0000ee; /* Standard blue link */ color-scheme: light dark; color: var(--text-color); background-color: var(--bg-color); - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; } @media (prefers-color-scheme: dark) { @@ -88,7 +82,7 @@ button { border: 1px solid transparent; padding: 0.6em 1.2em; font-size: 1em; - font-weight: 500; + font-weight: bold; font-family: inherit; background-color: #1a1a1a; cursor: pointer; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index bef5202..df655ea 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,10 +1,10 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './index.css'; +import App from './App.tsx'; createRoot(document.getElementById('root')!).render( - , -) + +); diff --git a/frontend/src/setupTests.ts b/frontend/src/setupTests.ts index 052d18e..5781184 100644 --- a/frontend/src/setupTests.ts +++ b/frontend/src/setupTests.ts @@ -2,39 +2,39 @@ import '@testing-library/jest-dom'; // Mock IntersectionObserver class IntersectionObserver { - readonly root: Element | null = null; - readonly rootMargin: string = ''; - readonly thresholds: ReadonlyArray = []; + readonly root: Element | null = null; + readonly rootMargin: string = ''; + readonly thresholds: ReadonlyArray = []; - constructor(_callback: IntersectionObserverCallback, _options?: IntersectionObserverInit) { - // nothing - } + constructor(_callback: IntersectionObserverCallback, _options?: IntersectionObserverInit) { + // nothing + } - observe(_target: Element): void { - // nothing - } + observe(_target: Element): void { + // nothing + } - unobserve(_target: Element): void { - // nothing - } + unobserve(_target: Element): void { + // nothing + } - disconnect(): void { - // nothing - } + disconnect(): void { + // nothing + } - takeRecords(): IntersectionObserverEntry[] { - return []; - } + takeRecords(): IntersectionObserverEntry[] { + return []; + } } Object.defineProperty(window, 'IntersectionObserver', { - writable: true, - configurable: true, - value: IntersectionObserver, + writable: true, + configurable: true, + value: IntersectionObserver, }); Object.defineProperty(globalThis, 'IntersectionObserver', { - writable: true, - configurable: true, - value: IntersectionObserver, + writable: true, + configurable: true, + value: IntersectionObserver, }); diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 4c1110f..1feea1f 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -1,24 +1,24 @@ export interface Feed { - _id: number; - url: string; - web_url: string; - title: string; - category: string; + _id: number; + url: string; + web_url: string; + title: string; + category: string; } export interface Item { - _id: number; - feed_id: number; - title: string; - url: string; - description: string; - publish_date: string; - read: boolean; - starred: boolean; - full_content?: string; - header_image?: string; - feed_title?: string; + _id: number; + feed_id: number; + title: string; + url: string; + description: string; + publish_date: string; + read: boolean; + starred: boolean; + full_content?: string; + header_image?: string; + feed_title?: string; } export interface Category { - title: string; + title: string; } diff --git a/frontend/test-results/.last-run.json b/frontend/test-results/.last-run.json index cbcc1fb..f740f7c 100644 --- a/frontend/test-results/.last-run.json +++ b/frontend/test-results/.last-run.json @@ -1,4 +1,4 @@ { "status": "passed", "failedTests": [] -} \ No newline at end of file +} diff --git a/frontend/tests/e2e.spec.ts b/frontend/tests/e2e.spec.ts index ca4b4ad..3f4898a 100644 --- a/frontend/tests/e2e.spec.ts +++ b/frontend/tests/e2e.spec.ts @@ -1,56 +1,66 @@ import { test, expect } from '@playwright/test'; test.describe('Neko Reader E2E', () => { - test('should allow login, viewing feeds, and logout', async ({ page }) => { - // 1. Go to Login - await page.goto('/v2/login'); - await expect(page).toHaveTitle(/Neko/); - - // 2. Login - // 2. Login - // Password is empty by default in test env - await page.click('button[type="submit"]'); - - // Check for error message if login failed (optional, for debugging) - // await expect(page.locator('.error-message')).toBeVisible({ timeout: 2000 }).catch(() => {}); - - // 3. Verify Dashboard - // Keep checking for /v2/ or /v2 - await expect(page).toHaveURL(/.*\/v2\/?$/); - await expect(page.locator('h1.logo')).toContainText('🐱'); - await expect(page.getByText('Logout')).toBeVisible(); - - // 4. Verify Feed List - await page.click('text=Settings'); - await expect(page).toHaveURL(/.*\/v2\/settings/); - - // Add a feed - const feedUrl = 'http://rss.cnn.com/rss/cnn_topstories.rss'; - await page.fill('input[type="url"]', feedUrl); - await page.click('text=Add Feed'); - - // Wait for it to appear - await expect(page.getByText(feedUrl)).toBeVisible(); - - // 5. Navigate to Feed - await page.goto('/v2/'); - // Default view is now the stream. - // It should NOT show "Select a feed" anymore. - // Wait for items or "No items found" or loading state - await expect(page.locator('.feed-items').or(page.locator('.feed-items-loading')).or(page.getByText('No items found'))).toBeVisible({ timeout: 10000 }); - - // 6. Verify Tag View - // Go to a tag URL (simulated, since we can't easily add tags via UI in this test yet without setup) - // But we can check if the route loads without crashing - await page.goto('/v2/tag/Tech'); - // The TagView component might show "Category: Tech" or "Tag: Tech" or just items. - // In the current FeedItems.tsx it doesn't show a header, but it should load. - // The TagView component might show "Category: Tech" or "Tag: Tech" or just items. - // In the current FeedItems.tsx it doesn't show a header, but it should load. - await expect(page.locator('.feed-items').or(page.locator('.feed-items-loading')).or(page.getByText('No items found'))).toBeVisible({ timeout: 10000 }); - - // 7. Logout - await page.click('text=Logout'); - await expect(page).toHaveURL(/.*\/v2\/login/); - }); + test('should allow login, viewing feeds, and logout', async ({ page }) => { + // 1. Go to Login + await page.goto('/v2/login'); + await expect(page).toHaveTitle(/Neko/); + + // 2. Login + // 2. Login + // Password is empty by default in test env + await page.click('button[type="submit"]'); + + // Check for error message if login failed (optional, for debugging) + // await expect(page.locator('.error-message')).toBeVisible({ timeout: 2000 }).catch(() => {}); + + // 3. Verify Dashboard + // Keep checking for /v2/ or /v2 + await expect(page).toHaveURL(/.*\/v2\/?$/); + await expect(page.locator('h1.logo')).toContainText('🐱'); + await expect(page.getByText('Logout')).toBeVisible(); + + // 4. Verify Feed List + await page.click('text=Settings'); + await expect(page).toHaveURL(/.*\/v2\/settings/); + + // Add a feed + const feedUrl = 'http://rss.cnn.com/rss/cnn_topstories.rss'; + await page.fill('input[type="url"]', feedUrl); + await page.click('text=Add Feed'); + + // Wait for it to appear + await expect(page.getByText(feedUrl)).toBeVisible(); + + // 5. Navigate to Feed + await page.goto('/v2/'); + // Default view is now the stream. + // It should NOT show "Select a feed" anymore. + // Wait for items or "No items found" or loading state + await expect( + page + .locator('.feed-items') + .or(page.locator('.feed-items-loading')) + .or(page.getByText('No items found')) + ).toBeVisible({ timeout: 10000 }); + + // 6. Verify Tag View + // Go to a tag URL (simulated, since we can't easily add tags via UI in this test yet without setup) + // But we can check if the route loads without crashing + await page.goto('/v2/tag/Tech'); + // The TagView component might show "Category: Tech" or "Tag: Tech" or just items. + // In the current FeedItems.tsx it doesn't show a header, but it should load. + // The TagView component might show "Category: Tech" or "Tag: Tech" or just items. + // In the current FeedItems.tsx it doesn't show a header, but it should load. + await expect( + page + .locator('.feed-items') + .or(page.locator('.feed-items-loading')) + .or(page.getByText('No items found')) + ).toBeVisible({ timeout: 10000 }); + + // 7. Logout + await page.click('text=Logout'); + await expect(page).toHaveURL(/.*\/v2\/login/); + }); }); diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json index f03834c..3fcc60b 100644 --- a/frontend/tsconfig.app.json +++ b/frontend/tsconfig.app.json @@ -3,15 +3,9 @@ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "ES2022", "useDefineForClassFields": true, - "lib": [ - "ES2022", - "DOM", - "DOM.Iterable" - ], + "lib": ["ES2022", "DOM", "DOM.Iterable"], "module": "ESNext", - "types": [ - "vite/client" - ], + "types": ["vite/client"], "skipLibCheck": true, /* Bundler mode */ "moduleResolution": "bundler", @@ -28,11 +22,6 @@ "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, - "include": [ - "src" - ], - "exclude": [ - "**/*.test.tsx", - "**/*.test.ts" - ] -} \ No newline at end of file + "include": ["src"], + "exclude": ["**/*.test.tsx", "**/*.test.ts"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 1ffef60..d32ff68 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -1,7 +1,4 @@ { "files": [], - "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } - ] + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }] } diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json index 61d6465..50145d1 100644 --- a/frontend/tsconfig.node.json +++ b/frontend/tsconfig.node.json @@ -2,14 +2,9 @@ "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", "target": "ES2023", - "lib": [ - "ES2023" - ], + "lib": ["ES2023"], "module": "ESNext", - "types": [ - "node", - "vitest" - ], + "types": ["node", "vitest"], "skipLibCheck": true, /* Bundler mode */ "moduleResolution": "bundler", @@ -25,7 +20,5 @@ "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, - "include": [ - "vite.config.ts" - ] -} \ No newline at end of file + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index e04aec1..025cbb3 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,6 +1,6 @@ /// -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; // https://vite.dev/config/ export default defineConfig({ @@ -10,6 +10,6 @@ export default defineConfig({ proxy: { '/api': 'http://127.0.0.1:4994', '/image': 'http://127.0.0.1:4994', - } + }, }, -}) +}); diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index eee2cfa..9cb79ae 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -1,11 +1,11 @@ /// -import { defineConfig } from 'vite' +import { defineConfig } from 'vite'; export default defineConfig({ - test: { - globals: true, - environment: 'jsdom', - setupFiles: './src/setupTests.ts', - exclude: ['**/node_modules/**', '**/dist/**', '**/tests/**'], - }, -}) + test: { + globals: true, + environment: 'jsdom', + setupFiles: './src/setupTests.ts', + exclude: ['**/node_modules/**', '**/dist/**', '**/tests/**'], + }, +}); -- cgit v1.2.3