Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@fiscozen/link

Package Overview
Dependencies
Maintainers
5
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fiscozen/link - npm Package Compare versions

Comparing version
0.1.4
to
1.0.0-next.0
+224
coverage/base.css
body, html {
margin:0; padding: 0;
height: 100%;
}
body {
font-family: Helvetica Neue, Helvetica, Arial;
font-size: 14px;
color:#333;
}
.small { font-size: 12px; }
*, *:after, *:before {
-webkit-box-sizing:border-box;
-moz-box-sizing:border-box;
box-sizing:border-box;
}
h1 { font-size: 20px; margin: 0;}
h2 { font-size: 14px; }
pre {
font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
margin: 0;
padding: 0;
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
}
a { color:#0074D9; text-decoration:none; }
a:hover { text-decoration:underline; }
.strong { font-weight: bold; }
.space-top1 { padding: 10px 0 0 0; }
.pad2y { padding: 20px 0; }
.pad1y { padding: 10px 0; }
.pad2x { padding: 0 20px; }
.pad2 { padding: 20px; }
.pad1 { padding: 10px; }
.space-left2 { padding-left:55px; }
.space-right2 { padding-right:20px; }
.center { text-align:center; }
.clearfix { display:block; }
.clearfix:after {
content:'';
display:block;
height:0;
clear:both;
visibility:hidden;
}
.fl { float: left; }
@media only screen and (max-width:640px) {
.col3 { width:100%; max-width:100%; }
.hide-mobile { display:none!important; }
}
.quiet {
color: #7f7f7f;
color: rgba(0,0,0,0.5);
}
.quiet a { opacity: 0.7; }
.fraction {
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 10px;
color: #555;
background: #E8E8E8;
padding: 4px 5px;
border-radius: 3px;
vertical-align: middle;
}
div.path a:link, div.path a:visited { color: #333; }
table.coverage {
border-collapse: collapse;
margin: 10px 0 0 0;
padding: 0;
}
table.coverage td {
margin: 0;
padding: 0;
vertical-align: top;
}
table.coverage td.line-count {
text-align: right;
padding: 0 5px 0 20px;
}
table.coverage td.line-coverage {
text-align: right;
padding-right: 10px;
min-width:20px;
}
table.coverage td span.cline-any {
display: inline-block;
padding: 0 5px;
width: 100%;
}
.missing-if-branch {
display: inline-block;
margin-right: 5px;
border-radius: 3px;
position: relative;
padding: 0 4px;
background: #333;
color: yellow;
}
.skip-if-branch {
display: none;
margin-right: 10px;
position: relative;
padding: 0 4px;
background: #ccc;
color: white;
}
.missing-if-branch .typ, .skip-if-branch .typ {
color: inherit !important;
}
.coverage-summary {
border-collapse: collapse;
width: 100%;
}
.coverage-summary tr { border-bottom: 1px solid #bbb; }
.keyline-all { border: 1px solid #ddd; }
.coverage-summary td, .coverage-summary th { padding: 10px; }
.coverage-summary tbody { border: 1px solid #bbb; }
.coverage-summary td { border-right: 1px solid #bbb; }
.coverage-summary td:last-child { border-right: none; }
.coverage-summary th {
text-align: left;
font-weight: normal;
white-space: nowrap;
}
.coverage-summary th.file { border-right: none !important; }
.coverage-summary th.pct { }
.coverage-summary th.pic,
.coverage-summary th.abs,
.coverage-summary td.pct,
.coverage-summary td.abs { text-align: right; }
.coverage-summary td.file { white-space: nowrap; }
.coverage-summary td.pic { min-width: 120px !important; }
.coverage-summary tfoot td { }
.coverage-summary .sorter {
height: 10px;
width: 7px;
display: inline-block;
margin-left: 0.5em;
background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
}
.coverage-summary .sorted .sorter {
background-position: 0 -20px;
}
.coverage-summary .sorted-desc .sorter {
background-position: 0 -10px;
}
.status-line { height: 10px; }
/* yellow */
.cbranch-no { background: yellow !important; color: #111; }
/* dark red */
.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
.low .chart { border:1px solid #C21F39 }
.highlighted,
.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
background: #C21F39 !important;
}
/* medium red */
.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
/* light red */
.low, .cline-no { background:#FCE1E5 }
/* light green */
.high, .cline-yes { background:rgb(230,245,208) }
/* medium green */
.cstat-yes { background:rgb(161,215,106) }
/* dark green */
.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
.high .chart { border:1px solid rgb(77,146,33) }
/* dark yellow (gold) */
.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
.medium .chart { border:1px solid #f9cd0b; }
/* light yellow */
.medium { background: #fff4c2; }
.cstat-skip { background: #ddd; color: #111; }
.fstat-skip { background: #ddd; color: #111 !important; }
.cbranch-skip { background: #ddd !important; color: #111; }
span.cline-neutral { background: #eaeaea; }
.coverage-summary td.empty {
opacity: .5;
padding-top: 4px;
padding-bottom: 4px;
line-height: 1;
color: #888;
}
.cover-fill, .cover-empty {
display:inline-block;
height: 12px;
}
.chart {
line-height: 0;
}
.cover-empty {
background: white;
}
.cover-full {
border-right: none !important;
}
pre.prettyprint {
border: none !important;
padding: 0 !important;
margin: 0 !important;
}
.com { color: #999 !important; }
.ignore-none { color: #999; font-weight: normal; }
.wrapper {
min-height: 100%;
height: auto !important;
height: 100%;
margin: 0 auto -48px;
}
.footer, .push {
height: 48px;
}
/* eslint-disable */
var jumpToCode = (function init() {
// Classes of code we would like to highlight in the file view
var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
// Elements to highlight in the file listing view
var fileListingElements = ['td.pct.low'];
// We don't want to select elements that are direct descendants of another match
var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
// Selecter that finds elements on the page to which we can jump
var selector =
fileListingElements.join(', ') +
', ' +
notSelector +
missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
// The NodeList of matching elements
var missingCoverageElements = document.querySelectorAll(selector);
var currentIndex;
function toggleClass(index) {
missingCoverageElements
.item(currentIndex)
.classList.remove('highlighted');
missingCoverageElements.item(index).classList.add('highlighted');
}
function makeCurrent(index) {
toggleClass(index);
currentIndex = index;
missingCoverageElements.item(index).scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center'
});
}
function goToPrevious() {
var nextIndex = 0;
if (typeof currentIndex !== 'number' || currentIndex === 0) {
nextIndex = missingCoverageElements.length - 1;
} else if (missingCoverageElements.length > 1) {
nextIndex = currentIndex - 1;
}
makeCurrent(nextIndex);
}
function goToNext() {
var nextIndex = 0;
if (
typeof currentIndex === 'number' &&
currentIndex < missingCoverageElements.length - 1
) {
nextIndex = currentIndex + 1;
}
makeCurrent(nextIndex);
}
return function jump(event) {
if (
document.getElementById('fileSearch') === document.activeElement &&
document.activeElement != null
) {
// if we're currently focused on the search input, we don't want to navigate
return;
}
switch (event.which) {
case 78: // n
case 74: // j
goToNext();
break;
case 66: // b
case 75: // k
case 80: // p
goToPrevious();
break;
}
};
})();
window.addEventListener('keydown', jumpToCode);
<?xml version="1.0" encoding="UTF-8"?>
<coverage generated="1762523195109" clover="3.2.0">
<project timestamp="1762523195109" name="All files">
<metrics statements="199" coveredstatements="198" conditionals="32" coveredconditionals="30" methods="6" coveredmethods="6" elements="237" coveredelements="234" complexity="0" loc="199" ncloc="199" packages="1" files="1" classes="1"/>
<file name="FzLink.vue" path="/Users/fiscozen/Workspace/design_system/packages/link/src/FzLink.vue">
<metrics statements="199" coveredstatements="198" conditionals="32" coveredconditionals="30" methods="6" coveredmethods="6"/>
<line num="1" count="1" type="stmt"/>
<line num="2" count="1" type="stmt"/>
<line num="3" count="1" type="stmt"/>
<line num="4" count="1" type="stmt"/>
<line num="5" count="1" type="stmt"/>
<line num="6" count="1" type="stmt"/>
<line num="7" count="1" type="stmt"/>
<line num="8" count="1" type="stmt"/>
<line num="9" count="1" type="stmt"/>
<line num="10" count="1" type="stmt"/>
<line num="11" count="1" type="stmt"/>
<line num="12" count="1" type="stmt"/>
<line num="13" count="1" type="stmt"/>
<line num="14" count="1" type="stmt"/>
<line num="15" count="1" type="stmt"/>
<line num="16" count="1" type="stmt"/>
<line num="17" count="1" type="stmt"/>
<line num="18" count="1" type="stmt"/>
<line num="19" count="1" type="stmt"/>
<line num="20" count="1" type="stmt"/>
<line num="21" count="1" type="stmt"/>
<line num="22" count="1" type="stmt"/>
<line num="23" count="1" type="stmt"/>
<line num="24" count="1" type="stmt"/>
<line num="25" count="1" type="stmt"/>
<line num="26" count="1" type="stmt"/>
<line num="27" count="1" type="stmt"/>
<line num="28" count="1" type="stmt"/>
<line num="29" count="1" type="stmt"/>
<line num="30" count="1" type="stmt"/>
<line num="31" count="1" type="stmt"/>
<line num="32" count="1" type="stmt"/>
<line num="33" count="1" type="stmt"/>
<line num="34" count="1" type="stmt"/>
<line num="35" count="1" type="stmt"/>
<line num="36" count="1" type="stmt"/>
<line num="37" count="1" type="stmt"/>
<line num="38" count="1" type="stmt"/>
<line num="39" count="1" type="stmt"/>
<line num="40" count="1" type="stmt"/>
<line num="41" count="1" type="stmt"/>
<line num="42" count="1" type="stmt"/>
<line num="43" count="1" type="stmt"/>
<line num="44" count="1" type="cond" truecount="1" falsecount="0"/>
<line num="45" count="37" type="cond" truecount="1" falsecount="0"/>
<line num="46" count="1" type="stmt"/>
<line num="47" count="1" type="stmt"/>
<line num="48" count="1" type="stmt"/>
<line num="49" count="1" type="stmt"/>
<line num="50" count="1" type="stmt"/>
<line num="51" count="1" type="cond" truecount="1" falsecount="0"/>
<line num="52" count="36" type="stmt"/>
<line num="53" count="37" type="cond" truecount="1" falsecount="0"/>
<line num="54" count="1" type="stmt"/>
<line num="55" count="1" type="stmt"/>
<line num="56" count="1" type="stmt"/>
<line num="57" count="1" type="stmt"/>
<line num="58" count="1" type="stmt"/>
<line num="59" count="1" type="cond" truecount="1" falsecount="0"/>
<line num="60" count="35" type="stmt"/>
<line num="61" count="35" type="stmt"/>
<line num="62" count="1" type="stmt"/>
<line num="63" count="1" type="stmt"/>
<line num="64" count="1" type="stmt"/>
<line num="65" count="1" type="stmt"/>
<line num="66" count="1" type="stmt"/>
<line num="67" count="1" type="stmt"/>
<line num="68" count="1" type="stmt"/>
<line num="69" count="1" type="stmt"/>
<line num="70" count="1" type="cond" truecount="1" falsecount="0"/>
<line num="71" count="37" type="stmt"/>
<line num="72" count="37" type="stmt"/>
<line num="73" count="37" type="stmt"/>
<line num="74" count="37" type="stmt"/>
<line num="75" count="37" type="stmt"/>
<line num="76" count="37" type="stmt"/>
<line num="77" count="1" type="stmt"/>
<line num="78" count="1" type="stmt"/>
<line num="79" count="1" type="stmt"/>
<line num="80" count="1" type="stmt"/>
<line num="81" count="1" type="stmt"/>
<line num="82" count="1" type="stmt"/>
<line num="83" count="1" type="stmt"/>
<line num="84" count="1" type="stmt"/>
<line num="85" count="1" type="cond" truecount="2" falsecount="0"/>
<line num="86" count="1" type="cond" truecount="2" falsecount="0"/>
<line num="87" count="1" type="cond" truecount="1" falsecount="0"/>
<line num="88" count="1" type="cond" truecount="1" falsecount="0"/>
<line num="89" count="1" type="cond" truecount="2" falsecount="0"/>
<line num="90" count="1" type="cond" truecount="1" falsecount="0"/>
<line num="91" count="1" type="stmt"/>
<line num="92" count="1" type="stmt"/>
<line num="93" count="1" type="stmt"/>
<line num="94" count="1" type="stmt"/>
<line num="95" count="1" type="cond" truecount="1" falsecount="0"/>
<line num="96" count="29" type="stmt"/>
<line num="97" count="29" type="stmt"/>
<line num="98" count="29" type="stmt"/>
<line num="99" count="29" type="cond" truecount="1" falsecount="0"/>
<line num="100" count="2" type="stmt"/>
<line num="101" count="2" type="stmt"/>
<line num="102" count="29" type="stmt"/>
<line num="103" count="29" type="cond" truecount="1" falsecount="0"/>
<line num="104" count="24" type="stmt"/>
<line num="105" count="24" type="stmt"/>
<line num="106" count="29" type="stmt"/>
<line num="107" count="29" type="cond" truecount="1" falsecount="0"/>
<line num="108" count="2" type="stmt"/>
<line num="109" count="2" type="stmt"/>
<line num="110" count="29" type="stmt"/>
<line num="111" count="29" type="cond" truecount="1" falsecount="0"/>
<line num="112" count="1" type="stmt"/>
<line num="113" count="1" type="stmt"/>
<line num="114" count="29" type="stmt"/>
<line num="115" count="29" type="stmt"/>
<line num="116" count="29" type="stmt"/>
<line num="117" count="1" type="stmt"/>
<line num="118" count="1" type="stmt"/>
<line num="119" count="1" type="stmt"/>
<line num="120" count="1" type="stmt"/>
<line num="121" count="1" type="stmt"/>
<line num="122" count="1" type="stmt"/>
<line num="123" count="1" type="stmt"/>
<line num="124" count="1" type="stmt"/>
<line num="125" count="1" type="cond" truecount="1" falsecount="0"/>
<line num="126" count="8" type="stmt"/>
<line num="127" count="8" type="stmt"/>
<line num="128" count="8" type="stmt"/>
<line num="129" count="8" type="cond" truecount="1" falsecount="0"/>
<line num="130" count="6" type="stmt"/>
<line num="131" count="6" type="stmt"/>
<line num="132" count="6" type="stmt"/>
<line num="133" count="8" type="stmt"/>
<line num="134" count="8" type="cond" truecount="1" falsecount="0"/>
<line num="135" count="2" type="stmt"/>
<line num="136" count="2" type="stmt"/>
<line num="137" count="2" type="stmt"/>
<line num="138" count="8" type="stmt"/>
<line num="139" count="8" type="stmt"/>
<line num="140" count="8" type="stmt"/>
<line num="141" count="1" type="stmt"/>
<line num="142" count="1" type="stmt"/>
<line num="143" count="1" type="stmt"/>
<line num="144" count="1" type="stmt"/>
<line num="145" count="1" type="stmt"/>
<line num="146" count="1" type="stmt"/>
<line num="147" count="1" type="stmt"/>
<line num="148" count="1" type="stmt"/>
<line num="149" count="1" type="cond" truecount="1" falsecount="0"/>
<line num="150" count="9" type="stmt"/>
<line num="151" count="9" type="stmt"/>
<line num="152" count="9" type="stmt"/>
<line num="153" count="9" type="cond" truecount="0" falsecount="1"/>
<line num="154" count="0" type="stmt"/>
<line num="155" count="1" type="stmt"/>
<line num="156" count="1" type="stmt"/>
<line num="157" count="1" type="stmt"/>
<line num="158" count="1" type="stmt"/>
<line num="159" count="1" type="stmt"/>
<line num="160" count="1" type="stmt"/>
<line num="161" count="1" type="stmt"/>
<line num="162" count="1" type="stmt"/>
<line num="163" count="1" type="cond" truecount="1" falsecount="0"/>
<line num="164" count="9" type="cond" truecount="1" falsecount="0"/>
<line num="165" count="2" type="stmt"/>
<line num="166" count="2" type="cond" truecount="1" falsecount="0"/>
<line num="167" count="7" type="stmt"/>
<line num="168" count="1" type="stmt"/>
<line num="169" count="1" type="stmt"/>
<line num="170" count="1" type="stmt"/>
<line num="171" count="1" type="stmt"/>
<line num="172" count="1" type="stmt"/>
<line num="173" count="1" type="cond" truecount="1" falsecount="0"/>
<line num="174" count="8" type="stmt"/>
<line num="175" count="8" type="cond" truecount="0" falsecount="1"/>
<line num="176" count="8" type="stmt"/>
<line num="177" count="8" type="stmt"/>
<line num="178" count="8" type="stmt"/>
<line num="179" count="8" type="stmt"/>
<line num="180" count="1" type="stmt"/>
<line num="181" count="1" type="stmt"/>
<line num="182" count="1" type="cond" truecount="1" falsecount="0"/>
<line num="183" count="9" type="stmt"/>
<line num="184" count="9" type="stmt"/>
<line num="185" count="9" type="stmt"/>
<line num="186" count="9" type="stmt"/>
<line num="187" count="9" type="stmt"/>
<line num="188" count="9" type="stmt"/>
<line num="189" count="1" type="stmt"/>
<line num="190" count="1" type="stmt"/>
<line num="191" count="1" type="stmt"/>
<line num="192" count="1" type="stmt"/>
<line num="193" count="1" type="stmt"/>
<line num="194" count="1" type="stmt"/>
<line num="195" count="1" type="stmt"/>
<line num="196" count="1" type="stmt"/>
<line num="197" count="1" type="cond" truecount="1" falsecount="0"/>
<line num="198" count="1" type="stmt"/>
<line num="199" count="1" type="stmt"/>
</file>
</project>
</coverage>
{"/Users/fiscozen/Workspace/design_system/packages/link/src/FzLink.vue": {"path":"/Users/fiscozen/Workspace/design_system/packages/link/src/FzLink.vue","all":false,"statementMap":{"0":{"start":{"line":1,"column":0},"end":{"line":1,"column":24}},"1":{"start":{"line":2,"column":0},"end":{"line":2,"column":3}},"2":{"start":{"line":3,"column":0},"end":{"line":3,"column":19}},"3":{"start":{"line":4,"column":0},"end":{"line":4,"column":2}},"4":{"start":{"line":5,"column":0},"end":{"line":5,"column":92}},"5":{"start":{"line":6,"column":0},"end":{"line":6,"column":90}},"6":{"start":{"line":7,"column":0},"end":{"line":7,"column":88}},"7":{"start":{"line":8,"column":0},"end":{"line":8,"column":2}},"8":{"start":{"line":9,"column":0},"end":{"line":9,"column":42}},"9":{"start":{"line":10,"column":0},"end":{"line":10,"column":97}},"10":{"start":{"line":11,"column":0},"end":{"line":11,"column":60}},"11":{"start":{"line":12,"column":0},"end":{"line":12,"column":2}},"12":{"start":{"line":13,"column":0},"end":{"line":13,"column":13}},"13":{"start":{"line":14,"column":0},"end":{"line":14,"column":11}},"14":{"start":{"line":15,"column":0},"end":{"line":15,"column":61}},"15":{"start":{"line":16,"column":0},"end":{"line":16,"column":3}},"16":{"start":{"line":17,"column":0},"end":{"line":17,"column":11}},"17":{"start":{"line":18,"column":0},"end":{"line":18,"column":75}},"18":{"start":{"line":19,"column":0},"end":{"line":19,"column":3}},"19":{"start":{"line":20,"column":0},"end":{"line":20,"column":11}},"20":{"start":{"line":21,"column":0},"end":{"line":21,"column":83}},"21":{"start":{"line":22,"column":0},"end":{"line":22,"column":3}},"22":{"start":{"line":23,"column":0},"end":{"line":23,"column":30}},"23":{"start":{"line":24,"column":0},"end":{"line":24,"column":37}},"24":{"start":{"line":25,"column":0},"end":{"line":25,"column":0}},"25":{"start":{"line":26,"column":0},"end":{"line":26,"column":56}},"26":{"start":{"line":27,"column":0},"end":{"line":27,"column":18}},"27":{"start":{"line":28,"column":0},"end":{"line":28,"column":23}},"28":{"start":{"line":29,"column":0},"end":{"line":29,"column":13}},"29":{"start":{"line":30,"column":0},"end":{"line":30,"column":18}},"30":{"start":{"line":31,"column":0},"end":{"line":31,"column":17}},"31":{"start":{"line":32,"column":0},"end":{"line":32,"column":17}},"32":{"start":{"line":33,"column":0},"end":{"line":33,"column":2}},"33":{"start":{"line":34,"column":0},"end":{"line":34,"column":0}},"34":{"start":{"line":35,"column":0},"end":{"line":35,"column":3}},"35":{"start":{"line":36,"column":0},"end":{"line":36,"column":68}},"36":{"start":{"line":37,"column":0},"end":{"line":37,"column":3}},"37":{"start":{"line":38,"column":0},"end":{"line":38,"column":47}},"38":{"start":{"line":39,"column":0},"end":{"line":39,"column":31}},"39":{"start":{"line":40,"column":0},"end":{"line":40,"column":31}},"40":{"start":{"line":41,"column":0},"end":{"line":41,"column":3}},"41":{"start":{"line":42,"column":0},"end":{"line":42,"column":81}},"42":{"start":{"line":43,"column":0},"end":{"line":43,"column":3}},"43":{"start":{"line":44,"column":0},"end":{"line":44,"column":39}},"44":{"start":{"line":45,"column":0},"end":{"line":45,"column":28}},"45":{"start":{"line":46,"column":0},"end":{"line":46,"column":17}},"46":{"start":{"line":47,"column":0},"end":{"line":47,"column":99}},"47":{"start":{"line":48,"column":0},"end":{"line":48,"column":91}},"48":{"start":{"line":49,"column":0},"end":{"line":49,"column":5}},"49":{"start":{"line":50,"column":0},"end":{"line":50,"column":15}},"50":{"start":{"line":51,"column":0},"end":{"line":51,"column":3}},"51":{"start":{"line":52,"column":0},"end":{"line":52,"column":2}},"52":{"start":{"line":53,"column":0},"end":{"line":53,"column":28}},"53":{"start":{"line":54,"column":0},"end":{"line":54,"column":17}},"54":{"start":{"line":55,"column":0},"end":{"line":55,"column":99}},"55":{"start":{"line":56,"column":0},"end":{"line":56,"column":91}},"56":{"start":{"line":57,"column":0},"end":{"line":57,"column":5}},"57":{"start":{"line":58,"column":0},"end":{"line":58,"column":15}},"58":{"start":{"line":59,"column":0},"end":{"line":59,"column":3}},"59":{"start":{"line":60,"column":0},"end":{"line":60,"column":2}},"60":{"start":{"line":61,"column":0},"end":{"line":61,"column":34}},"61":{"start":{"line":62,"column":0},"end":{"line":62,"column":2}},"62":{"start":{"line":63,"column":0},"end":{"line":63,"column":0}},"63":{"start":{"line":64,"column":0},"end":{"line":64,"column":3}},"64":{"start":{"line":65,"column":0},"end":{"line":65,"column":61}},"65":{"start":{"line":66,"column":0},"end":{"line":66,"column":3}},"66":{"start":{"line":67,"column":0},"end":{"line":67,"column":70}},"67":{"start":{"line":68,"column":0},"end":{"line":68,"column":66}},"68":{"start":{"line":69,"column":0},"end":{"line":69,"column":3}},"69":{"start":{"line":70,"column":0},"end":{"line":70,"column":36}},"70":{"start":{"line":71,"column":0},"end":{"line":71,"column":45}},"71":{"start":{"line":72,"column":0},"end":{"line":72,"column":3}},"72":{"start":{"line":73,"column":0},"end":{"line":73,"column":56}},"73":{"start":{"line":74,"column":0},"end":{"line":74,"column":60}},"74":{"start":{"line":75,"column":0},"end":{"line":75,"column":46}},"75":{"start":{"line":76,"column":0},"end":{"line":76,"column":3}},"76":{"start":{"line":77,"column":0},"end":{"line":77,"column":2}},"77":{"start":{"line":78,"column":0},"end":{"line":78,"column":0}},"78":{"start":{"line":79,"column":0},"end":{"line":79,"column":3}},"79":{"start":{"line":80,"column":0},"end":{"line":80,"column":42}},"80":{"start":{"line":81,"column":0},"end":{"line":81,"column":3}},"81":{"start":{"line":82,"column":0},"end":{"line":82,"column":85}},"82":{"start":{"line":83,"column":0},"end":{"line":83,"column":64}},"83":{"start":{"line":84,"column":0},"end":{"line":84,"column":3}},"84":{"start":{"line":85,"column":0},"end":{"line":85,"column":99}},"85":{"start":{"line":86,"column":0},"end":{"line":86,"column":101}},"86":{"start":{"line":87,"column":0},"end":{"line":87,"column":97}},"87":{"start":{"line":88,"column":0},"end":{"line":88,"column":99}},"88":{"start":{"line":89,"column":0},"end":{"line":89,"column":81}},"89":{"start":{"line":90,"column":0},"end":{"line":90,"column":79}},"90":{"start":{"line":91,"column":0},"end":{"line":91,"column":0}},"91":{"start":{"line":92,"column":0},"end":{"line":92,"column":3}},"92":{"start":{"line":93,"column":0},"end":{"line":93,"column":68}},"93":{"start":{"line":94,"column":0},"end":{"line":94,"column":3}},"94":{"start":{"line":95,"column":0},"end":{"line":95,"column":34}},"95":{"start":{"line":96,"column":0},"end":{"line":96,"column":44}},"96":{"start":{"line":97,"column":0},"end":{"line":97,"column":2}},"97":{"start":{"line":98,"column":0},"end":{"line":98,"column":17}},"98":{"start":{"line":99,"column":0},"end":{"line":99,"column":35}},"99":{"start":{"line":100,"column":0},"end":{"line":100,"column":85}},"100":{"start":{"line":101,"column":0},"end":{"line":101,"column":11}},"101":{"start":{"line":102,"column":0},"end":{"line":102,"column":6}},"102":{"start":{"line":103,"column":0},"end":{"line":103,"column":37}},"103":{"start":{"line":104,"column":0},"end":{"line":104,"column":104}},"104":{"start":{"line":105,"column":0},"end":{"line":105,"column":11}},"105":{"start":{"line":106,"column":0},"end":{"line":106,"column":6}},"106":{"start":{"line":107,"column":0},"end":{"line":107,"column":34}},"107":{"start":{"line":108,"column":0},"end":{"line":108,"column":115}},"108":{"start":{"line":109,"column":0},"end":{"line":109,"column":11}},"109":{"start":{"line":110,"column":0},"end":{"line":110,"column":6}},"110":{"start":{"line":111,"column":0},"end":{"line":111,"column":36}},"111":{"start":{"line":112,"column":0},"end":{"line":112,"column":134}},"112":{"start":{"line":113,"column":0},"end":{"line":113,"column":11}},"113":{"start":{"line":114,"column":0},"end":{"line":114,"column":3}},"114":{"start":{"line":115,"column":0},"end":{"line":115,"column":2}},"115":{"start":{"line":116,"column":0},"end":{"line":116,"column":20}},"116":{"start":{"line":117,"column":0},"end":{"line":117,"column":2}},"117":{"start":{"line":118,"column":0},"end":{"line":118,"column":0}},"118":{"start":{"line":119,"column":0},"end":{"line":119,"column":3}},"119":{"start":{"line":120,"column":0},"end":{"line":120,"column":58}},"120":{"start":{"line":121,"column":0},"end":{"line":121,"column":3}},"121":{"start":{"line":122,"column":0},"end":{"line":122,"column":83}},"122":{"start":{"line":123,"column":0},"end":{"line":123,"column":78}},"123":{"start":{"line":124,"column":0},"end":{"line":124,"column":3}},"124":{"start":{"line":125,"column":0},"end":{"line":125,"column":34}},"125":{"start":{"line":126,"column":0},"end":{"line":126,"column":66}},"126":{"start":{"line":127,"column":0},"end":{"line":127,"column":2}},"127":{"start":{"line":128,"column":0},"end":{"line":128,"column":17}},"128":{"start":{"line":129,"column":0},"end":{"line":129,"column":34}},"129":{"start":{"line":130,"column":0},"end":{"line":130,"column":87}},"130":{"start":{"line":131,"column":0},"end":{"line":131,"column":39}},"131":{"start":{"line":132,"column":0},"end":{"line":132,"column":11}},"132":{"start":{"line":133,"column":0},"end":{"line":133,"column":6}},"133":{"start":{"line":134,"column":0},"end":{"line":134,"column":33}},"134":{"start":{"line":135,"column":0},"end":{"line":135,"column":96}},"135":{"start":{"line":136,"column":0},"end":{"line":136,"column":49}},"136":{"start":{"line":137,"column":0},"end":{"line":137,"column":11}},"137":{"start":{"line":138,"column":0},"end":{"line":138,"column":3}},"138":{"start":{"line":139,"column":0},"end":{"line":139,"column":2}},"139":{"start":{"line":140,"column":0},"end":{"line":140,"column":20}},"140":{"start":{"line":141,"column":0},"end":{"line":141,"column":2}},"141":{"start":{"line":142,"column":0},"end":{"line":142,"column":0}},"142":{"start":{"line":143,"column":0},"end":{"line":143,"column":3}},"143":{"start":{"line":144,"column":0},"end":{"line":144,"column":33}},"144":{"start":{"line":145,"column":0},"end":{"line":145,"column":3}},"145":{"start":{"line":146,"column":0},"end":{"line":146,"column":63}},"146":{"start":{"line":147,"column":0},"end":{"line":147,"column":53}},"147":{"start":{"line":148,"column":0},"end":{"line":148,"column":3}},"148":{"start":{"line":149,"column":0},"end":{"line":149,"column":37}},"149":{"start":{"line":150,"column":0},"end":{"line":150,"column":23}},"150":{"start":{"line":151,"column":0},"end":{"line":151,"column":60}},"151":{"start":{"line":152,"column":0},"end":{"line":152,"column":29}},"152":{"start":{"line":153,"column":0},"end":{"line":153,"column":3}},"153":{"start":{"line":154,"column":0},"end":{"line":154,"column":11}},"154":{"start":{"line":155,"column":0},"end":{"line":155,"column":2}},"155":{"start":{"line":156,"column":0},"end":{"line":156,"column":0}},"156":{"start":{"line":157,"column":0},"end":{"line":157,"column":3}},"157":{"start":{"line":158,"column":0},"end":{"line":158,"column":57}},"158":{"start":{"line":159,"column":0},"end":{"line":159,"column":3}},"159":{"start":{"line":160,"column":0},"end":{"line":160,"column":79}},"160":{"start":{"line":161,"column":0},"end":{"line":161,"column":59}},"161":{"start":{"line":162,"column":0},"end":{"line":162,"column":3}},"162":{"start":{"line":163,"column":0},"end":{"line":163,"column":36}},"163":{"start":{"line":164,"column":0},"end":{"line":164,"column":52}},"164":{"start":{"line":165,"column":0},"end":{"line":165,"column":32}},"165":{"start":{"line":166,"column":0},"end":{"line":166,"column":3}},"166":{"start":{"line":167,"column":0},"end":{"line":167,"column":18}},"167":{"start":{"line":168,"column":0},"end":{"line":168,"column":2}},"168":{"start":{"line":169,"column":0},"end":{"line":169,"column":9}},"169":{"start":{"line":170,"column":0},"end":{"line":170,"column":0}},"170":{"start":{"line":171,"column":0},"end":{"line":171,"column":10}},"171":{"start":{"line":172,"column":0},"end":{"line":172,"column":7}},"172":{"start":{"line":173,"column":0},"end":{"line":173,"column":19}},"173":{"start":{"line":174,"column":0},"end":{"line":174,"column":22}},"174":{"start":{"line":175,"column":0},"end":{"line":175,"column":48}},"175":{"start":{"line":176,"column":0},"end":{"line":176,"column":15}},"176":{"start":{"line":177,"column":0},"end":{"line":177,"column":30}},"177":{"start":{"line":178,"column":0},"end":{"line":178,"column":3}},"178":{"start":{"line":179,"column":0},"end":{"line":179,"column":17}},"179":{"start":{"line":180,"column":0},"end":{"line":180,"column":9}},"180":{"start":{"line":181,"column":0},"end":{"line":181,"column":4}},"181":{"start":{"line":182,"column":0},"end":{"line":182,"column":24}},"182":{"start":{"line":183,"column":0},"end":{"line":183,"column":24}},"183":{"start":{"line":184,"column":0},"end":{"line":184,"column":22}},"184":{"start":{"line":185,"column":0},"end":{"line":185,"column":20}},"185":{"start":{"line":186,"column":0},"end":{"line":186,"column":22}},"186":{"start":{"line":187,"column":0},"end":{"line":187,"column":3}},"187":{"start":{"line":188,"column":0},"end":{"line":188,"column":17}},"188":{"start":{"line":189,"column":0},"end":{"line":189,"column":6}},"189":{"start":{"line":190,"column":0},"end":{"line":190,"column":14}},"190":{"start":{"line":191,"column":0},"end":{"line":191,"column":10}},"191":{"start":{"line":192,"column":0},"end":{"line":192,"column":12}},"192":{"start":{"line":193,"column":0},"end":{"line":193,"column":22}},"193":{"start":{"line":194,"column":0},"end":{"line":194,"column":22}},"194":{"start":{"line":195,"column":0},"end":{"line":195,"column":20}},"195":{"start":{"line":196,"column":0},"end":{"line":196,"column":3}},"196":{"start":{"line":197,"column":0},"end":{"line":197,"column":17}},"197":{"start":{"line":198,"column":0},"end":{"line":198,"column":16}},"198":{"start":{"line":199,"column":0},"end":{"line":199,"column":11}}},"s":{"0":1,"1":1,"2":1,"3":1,"4":1,"5":1,"6":1,"7":1,"8":1,"9":1,"10":1,"11":1,"12":1,"13":1,"14":1,"15":1,"16":1,"17":1,"18":1,"19":1,"20":1,"21":1,"22":1,"23":1,"24":1,"25":1,"26":1,"27":1,"28":1,"29":1,"30":1,"31":1,"32":1,"33":1,"34":1,"35":1,"36":1,"37":1,"38":1,"39":1,"40":1,"41":1,"42":1,"43":1,"44":37,"45":1,"46":1,"47":1,"48":1,"49":1,"50":1,"51":36,"52":37,"53":1,"54":1,"55":1,"56":1,"57":1,"58":1,"59":35,"60":35,"61":1,"62":1,"63":1,"64":1,"65":1,"66":1,"67":1,"68":1,"69":1,"70":37,"71":37,"72":37,"73":37,"74":37,"75":37,"76":1,"77":1,"78":1,"79":1,"80":1,"81":1,"82":1,"83":1,"84":1,"85":1,"86":1,"87":1,"88":1,"89":1,"90":1,"91":1,"92":1,"93":1,"94":1,"95":29,"96":29,"97":29,"98":29,"99":2,"100":2,"101":29,"102":29,"103":24,"104":24,"105":29,"106":29,"107":2,"108":2,"109":29,"110":29,"111":1,"112":1,"113":29,"114":29,"115":29,"116":1,"117":1,"118":1,"119":1,"120":1,"121":1,"122":1,"123":1,"124":1,"125":8,"126":8,"127":8,"128":8,"129":6,"130":6,"131":6,"132":8,"133":8,"134":2,"135":2,"136":2,"137":8,"138":8,"139":8,"140":1,"141":1,"142":1,"143":1,"144":1,"145":1,"146":1,"147":1,"148":1,"149":9,"150":9,"151":9,"152":9,"153":0,"154":1,"155":1,"156":1,"157":1,"158":1,"159":1,"160":1,"161":1,"162":1,"163":9,"164":2,"165":2,"166":7,"167":1,"168":1,"169":1,"170":1,"171":1,"172":1,"173":8,"174":8,"175":8,"176":8,"177":8,"178":8,"179":1,"180":1,"181":1,"182":9,"183":9,"184":9,"185":9,"186":9,"187":9,"188":1,"189":1,"190":1,"191":1,"192":1,"193":1,"194":1,"195":1,"196":1,"197":1,"198":1},"branchMap":{"0":{"type":"branch","line":44,"loc":{"start":{"line":44,"column":32},"end":{"line":62,"column":1}},"locations":[{"start":{"line":44,"column":32},"end":{"line":62,"column":1}}]},"1":{"type":"branch","line":45,"loc":{"start":{"line":45,"column":27},"end":{"line":51,"column":3}},"locations":[{"start":{"line":45,"column":27},"end":{"line":51,"column":3}}]},"2":{"type":"branch","line":51,"loc":{"start":{"line":51,"column":2},"end":{"line":53,"column":27}},"locations":[{"start":{"line":51,"column":2},"end":{"line":53,"column":27}}]},"3":{"type":"branch","line":53,"loc":{"start":{"line":53,"column":27},"end":{"line":59,"column":3}},"locations":[{"start":{"line":53,"column":27},"end":{"line":59,"column":3}}]},"4":{"type":"branch","line":59,"loc":{"start":{"line":59,"column":2},"end":{"line":61,"column":34}},"locations":[{"start":{"line":59,"column":2},"end":{"line":61,"column":34}}]},"5":{"type":"branch","line":70,"loc":{"start":{"line":70,"column":29},"end":{"line":77,"column":1}},"locations":[{"start":{"line":70,"column":29},"end":{"line":77,"column":1}}]},"6":{"type":"branch","line":85,"loc":{"start":{"line":85,"column":27},"end":{"line":85,"column":99}},"locations":[{"start":{"line":85,"column":27},"end":{"line":85,"column":99}}]},"7":{"type":"branch","line":85,"loc":{"start":{"line":85,"column":59},"end":{"line":85,"column":99}},"locations":[{"start":{"line":85,"column":59},"end":{"line":85,"column":99}}]},"8":{"type":"branch","line":86,"loc":{"start":{"line":86,"column":29},"end":{"line":86,"column":101}},"locations":[{"start":{"line":86,"column":29},"end":{"line":86,"column":101}}]},"9":{"type":"branch","line":86,"loc":{"start":{"line":86,"column":61},"end":{"line":86,"column":101}},"locations":[{"start":{"line":86,"column":61},"end":{"line":86,"column":101}}]},"10":{"type":"branch","line":87,"loc":{"start":{"line":87,"column":26},"end":{"line":87,"column":97}},"locations":[{"start":{"line":87,"column":26},"end":{"line":87,"column":97}}]},"11":{"type":"branch","line":88,"loc":{"start":{"line":88,"column":28},"end":{"line":88,"column":99}},"locations":[{"start":{"line":88,"column":28},"end":{"line":88,"column":99}}]},"12":{"type":"branch","line":89,"loc":{"start":{"line":89,"column":26},"end":{"line":89,"column":81}},"locations":[{"start":{"line":89,"column":26},"end":{"line":89,"column":81}}]},"13":{"type":"branch","line":89,"loc":{"start":{"line":89,"column":58},"end":{"line":89,"column":81}},"locations":[{"start":{"line":89,"column":58},"end":{"line":89,"column":81}}]},"14":{"type":"branch","line":90,"loc":{"start":{"line":90,"column":25},"end":{"line":90,"column":79}},"locations":[{"start":{"line":90,"column":25},"end":{"line":90,"column":79}}]},"15":{"type":"branch","line":95,"loc":{"start":{"line":95,"column":27},"end":{"line":117,"column":1}},"locations":[{"start":{"line":95,"column":27},"end":{"line":117,"column":1}}]},"16":{"type":"branch","line":99,"loc":{"start":{"line":99,"column":4},"end":{"line":101,"column":11}},"locations":[{"start":{"line":99,"column":4},"end":{"line":101,"column":11}}]},"17":{"type":"branch","line":103,"loc":{"start":{"line":103,"column":4},"end":{"line":105,"column":11}},"locations":[{"start":{"line":103,"column":4},"end":{"line":105,"column":11}}]},"18":{"type":"branch","line":107,"loc":{"start":{"line":107,"column":4},"end":{"line":109,"column":11}},"locations":[{"start":{"line":107,"column":4},"end":{"line":109,"column":11}}]},"19":{"type":"branch","line":111,"loc":{"start":{"line":111,"column":4},"end":{"line":113,"column":11}},"locations":[{"start":{"line":111,"column":4},"end":{"line":113,"column":11}}]},"20":{"type":"branch","line":125,"loc":{"start":{"line":125,"column":27},"end":{"line":141,"column":1}},"locations":[{"start":{"line":125,"column":27},"end":{"line":141,"column":1}}]},"21":{"type":"branch","line":129,"loc":{"start":{"line":129,"column":4},"end":{"line":132,"column":11}},"locations":[{"start":{"line":129,"column":4},"end":{"line":132,"column":11}}]},"22":{"type":"branch","line":134,"loc":{"start":{"line":134,"column":4},"end":{"line":137,"column":11}},"locations":[{"start":{"line":134,"column":4},"end":{"line":137,"column":11}}]},"23":{"type":"branch","line":149,"loc":{"start":{"line":149,"column":30},"end":{"line":155,"column":1}},"locations":[{"start":{"line":149,"column":30},"end":{"line":155,"column":1}}]},"24":{"type":"branch","line":153,"loc":{"start":{"line":153,"column":2},"end":{"line":154,"column":11}},"locations":[{"start":{"line":153,"column":2},"end":{"line":154,"column":11}}]},"25":{"type":"branch","line":163,"loc":{"start":{"line":163,"column":29},"end":{"line":168,"column":1}},"locations":[{"start":{"line":163,"column":29},"end":{"line":168,"column":1}}]},"26":{"type":"branch","line":164,"loc":{"start":{"line":164,"column":51},"end":{"line":166,"column":3}},"locations":[{"start":{"line":164,"column":51},"end":{"line":166,"column":3}}]},"27":{"type":"branch","line":166,"loc":{"start":{"line":166,"column":2},"end":{"line":167,"column":18}},"locations":[{"start":{"line":166,"column":2},"end":{"line":167,"column":18}}]},"28":{"type":"branch","line":173,"loc":{"start":{"line":173,"column":10},"end":{"line":179,"column":17}},"locations":[{"start":{"line":173,"column":10},"end":{"line":179,"column":17}}]},"29":{"type":"branch","line":175,"loc":{"start":{"line":175,"column":28},"end":{"line":175,"column":48}},"locations":[{"start":{"line":175,"column":28},"end":{"line":175,"column":48}}]},"30":{"type":"branch","line":182,"loc":{"start":{"line":182,"column":15},"end":{"line":188,"column":17}},"locations":[{"start":{"line":182,"column":15},"end":{"line":188,"column":17}}]},"31":{"type":"branch","line":197,"loc":{"start":{"line":197,"column":4},"end":{"line":197,"column":17}},"locations":[{"start":{"line":197,"column":4},"end":{"line":197,"column":17}}]}},"b":{"0":[37],"1":[1],"2":[36],"3":[1],"4":[35],"5":[37],"6":[29],"7":[26],"8":[27],"9":[24],"10":[3],"11":[1],"12":[8],"13":[6],"14":[2],"15":[29],"16":[2],"17":[24],"18":[2],"19":[1],"20":[8],"21":[6],"22":[2],"23":[9],"24":[0],"25":[9],"26":[2],"27":[7],"28":[8],"29":[0],"30":[9],"31":[20]},"fnMap":{"0":{"name":"isDefaultUnderline","decl":{"start":{"line":85,"column":27},"end":{"line":85,"column":99}},"loc":{"start":{"line":85,"column":27},"end":{"line":85,"column":99}},"line":85},"1":{"name":"isDefaultNoUnderline","decl":{"start":{"line":86,"column":29},"end":{"line":86,"column":101}},"loc":{"start":{"line":86,"column":29},"end":{"line":86,"column":101}},"line":86},"2":{"name":"isDangerUnderline","decl":{"start":{"line":87,"column":26},"end":{"line":87,"column":97}},"loc":{"start":{"line":87,"column":26},"end":{"line":87,"column":97}},"line":87},"3":{"name":"isDangerNoUnderline","decl":{"start":{"line":88,"column":28},"end":{"line":88,"column":99}},"loc":{"start":{"line":88,"column":28},"end":{"line":88,"column":99}},"line":88},"4":{"name":"isDefaultDisabled","decl":{"start":{"line":89,"column":26},"end":{"line":89,"column":81}},"loc":{"start":{"line":89,"column":26},"end":{"line":89,"column":81}},"line":89},"5":{"name":"isDangerDisabled","decl":{"start":{"line":90,"column":25},"end":{"line":90,"column":79}},"loc":{"start":{"line":90,"column":25},"end":{"line":90,"column":79}},"line":90}},"f":{"0":29,"1":27,"2":3,"3":1,"4":8,"5":2}}
}

Sorry, the diff of this file is not supported yet

<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for FzLink.vue</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1><a href="index.html">All files</a> FzLink.vue</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">99.49% </span>
<span class="quiet">Statements</span>
<span class='fraction'>198/199</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">93.75% </span>
<span class="quiet">Branches</span>
<span class='fraction'>30/32</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>6/6</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">99.49% </span>
<span class="quiet">Lines</span>
<span class='fraction'>198/199</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<pre><table class="coverage">
<tr><td class="line-count quiet"><a name='L1'></a><a href='#L1'>1</a>
<a name='L2'></a><a href='#L2'>2</a>
<a name='L3'></a><a href='#L3'>3</a>
<a name='L4'></a><a href='#L4'>4</a>
<a name='L5'></a><a href='#L5'>5</a>
<a name='L6'></a><a href='#L6'>6</a>
<a name='L7'></a><a href='#L7'>7</a>
<a name='L8'></a><a href='#L8'>8</a>
<a name='L9'></a><a href='#L9'>9</a>
<a name='L10'></a><a href='#L10'>10</a>
<a name='L11'></a><a href='#L11'>11</a>
<a name='L12'></a><a href='#L12'>12</a>
<a name='L13'></a><a href='#L13'>13</a>
<a name='L14'></a><a href='#L14'>14</a>
<a name='L15'></a><a href='#L15'>15</a>
<a name='L16'></a><a href='#L16'>16</a>
<a name='L17'></a><a href='#L17'>17</a>
<a name='L18'></a><a href='#L18'>18</a>
<a name='L19'></a><a href='#L19'>19</a>
<a name='L20'></a><a href='#L20'>20</a>
<a name='L21'></a><a href='#L21'>21</a>
<a name='L22'></a><a href='#L22'>22</a>
<a name='L23'></a><a href='#L23'>23</a>
<a name='L24'></a><a href='#L24'>24</a>
<a name='L25'></a><a href='#L25'>25</a>
<a name='L26'></a><a href='#L26'>26</a>
<a name='L27'></a><a href='#L27'>27</a>
<a name='L28'></a><a href='#L28'>28</a>
<a name='L29'></a><a href='#L29'>29</a>
<a name='L30'></a><a href='#L30'>30</a>
<a name='L31'></a><a href='#L31'>31</a>
<a name='L32'></a><a href='#L32'>32</a>
<a name='L33'></a><a href='#L33'>33</a>
<a name='L34'></a><a href='#L34'>34</a>
<a name='L35'></a><a href='#L35'>35</a>
<a name='L36'></a><a href='#L36'>36</a>
<a name='L37'></a><a href='#L37'>37</a>
<a name='L38'></a><a href='#L38'>38</a>
<a name='L39'></a><a href='#L39'>39</a>
<a name='L40'></a><a href='#L40'>40</a>
<a name='L41'></a><a href='#L41'>41</a>
<a name='L42'></a><a href='#L42'>42</a>
<a name='L43'></a><a href='#L43'>43</a>
<a name='L44'></a><a href='#L44'>44</a>
<a name='L45'></a><a href='#L45'>45</a>
<a name='L46'></a><a href='#L46'>46</a>
<a name='L47'></a><a href='#L47'>47</a>
<a name='L48'></a><a href='#L48'>48</a>
<a name='L49'></a><a href='#L49'>49</a>
<a name='L50'></a><a href='#L50'>50</a>
<a name='L51'></a><a href='#L51'>51</a>
<a name='L52'></a><a href='#L52'>52</a>
<a name='L53'></a><a href='#L53'>53</a>
<a name='L54'></a><a href='#L54'>54</a>
<a name='L55'></a><a href='#L55'>55</a>
<a name='L56'></a><a href='#L56'>56</a>
<a name='L57'></a><a href='#L57'>57</a>
<a name='L58'></a><a href='#L58'>58</a>
<a name='L59'></a><a href='#L59'>59</a>
<a name='L60'></a><a href='#L60'>60</a>
<a name='L61'></a><a href='#L61'>61</a>
<a name='L62'></a><a href='#L62'>62</a>
<a name='L63'></a><a href='#L63'>63</a>
<a name='L64'></a><a href='#L64'>64</a>
<a name='L65'></a><a href='#L65'>65</a>
<a name='L66'></a><a href='#L66'>66</a>
<a name='L67'></a><a href='#L67'>67</a>
<a name='L68'></a><a href='#L68'>68</a>
<a name='L69'></a><a href='#L69'>69</a>
<a name='L70'></a><a href='#L70'>70</a>
<a name='L71'></a><a href='#L71'>71</a>
<a name='L72'></a><a href='#L72'>72</a>
<a name='L73'></a><a href='#L73'>73</a>
<a name='L74'></a><a href='#L74'>74</a>
<a name='L75'></a><a href='#L75'>75</a>
<a name='L76'></a><a href='#L76'>76</a>
<a name='L77'></a><a href='#L77'>77</a>
<a name='L78'></a><a href='#L78'>78</a>
<a name='L79'></a><a href='#L79'>79</a>
<a name='L80'></a><a href='#L80'>80</a>
<a name='L81'></a><a href='#L81'>81</a>
<a name='L82'></a><a href='#L82'>82</a>
<a name='L83'></a><a href='#L83'>83</a>
<a name='L84'></a><a href='#L84'>84</a>
<a name='L85'></a><a href='#L85'>85</a>
<a name='L86'></a><a href='#L86'>86</a>
<a name='L87'></a><a href='#L87'>87</a>
<a name='L88'></a><a href='#L88'>88</a>
<a name='L89'></a><a href='#L89'>89</a>
<a name='L90'></a><a href='#L90'>90</a>
<a name='L91'></a><a href='#L91'>91</a>
<a name='L92'></a><a href='#L92'>92</a>
<a name='L93'></a><a href='#L93'>93</a>
<a name='L94'></a><a href='#L94'>94</a>
<a name='L95'></a><a href='#L95'>95</a>
<a name='L96'></a><a href='#L96'>96</a>
<a name='L97'></a><a href='#L97'>97</a>
<a name='L98'></a><a href='#L98'>98</a>
<a name='L99'></a><a href='#L99'>99</a>
<a name='L100'></a><a href='#L100'>100</a>
<a name='L101'></a><a href='#L101'>101</a>
<a name='L102'></a><a href='#L102'>102</a>
<a name='L103'></a><a href='#L103'>103</a>
<a name='L104'></a><a href='#L104'>104</a>
<a name='L105'></a><a href='#L105'>105</a>
<a name='L106'></a><a href='#L106'>106</a>
<a name='L107'></a><a href='#L107'>107</a>
<a name='L108'></a><a href='#L108'>108</a>
<a name='L109'></a><a href='#L109'>109</a>
<a name='L110'></a><a href='#L110'>110</a>
<a name='L111'></a><a href='#L111'>111</a>
<a name='L112'></a><a href='#L112'>112</a>
<a name='L113'></a><a href='#L113'>113</a>
<a name='L114'></a><a href='#L114'>114</a>
<a name='L115'></a><a href='#L115'>115</a>
<a name='L116'></a><a href='#L116'>116</a>
<a name='L117'></a><a href='#L117'>117</a>
<a name='L118'></a><a href='#L118'>118</a>
<a name='L119'></a><a href='#L119'>119</a>
<a name='L120'></a><a href='#L120'>120</a>
<a name='L121'></a><a href='#L121'>121</a>
<a name='L122'></a><a href='#L122'>122</a>
<a name='L123'></a><a href='#L123'>123</a>
<a name='L124'></a><a href='#L124'>124</a>
<a name='L125'></a><a href='#L125'>125</a>
<a name='L126'></a><a href='#L126'>126</a>
<a name='L127'></a><a href='#L127'>127</a>
<a name='L128'></a><a href='#L128'>128</a>
<a name='L129'></a><a href='#L129'>129</a>
<a name='L130'></a><a href='#L130'>130</a>
<a name='L131'></a><a href='#L131'>131</a>
<a name='L132'></a><a href='#L132'>132</a>
<a name='L133'></a><a href='#L133'>133</a>
<a name='L134'></a><a href='#L134'>134</a>
<a name='L135'></a><a href='#L135'>135</a>
<a name='L136'></a><a href='#L136'>136</a>
<a name='L137'></a><a href='#L137'>137</a>
<a name='L138'></a><a href='#L138'>138</a>
<a name='L139'></a><a href='#L139'>139</a>
<a name='L140'></a><a href='#L140'>140</a>
<a name='L141'></a><a href='#L141'>141</a>
<a name='L142'></a><a href='#L142'>142</a>
<a name='L143'></a><a href='#L143'>143</a>
<a name='L144'></a><a href='#L144'>144</a>
<a name='L145'></a><a href='#L145'>145</a>
<a name='L146'></a><a href='#L146'>146</a>
<a name='L147'></a><a href='#L147'>147</a>
<a name='L148'></a><a href='#L148'>148</a>
<a name='L149'></a><a href='#L149'>149</a>
<a name='L150'></a><a href='#L150'>150</a>
<a name='L151'></a><a href='#L151'>151</a>
<a name='L152'></a><a href='#L152'>152</a>
<a name='L153'></a><a href='#L153'>153</a>
<a name='L154'></a><a href='#L154'>154</a>
<a name='L155'></a><a href='#L155'>155</a>
<a name='L156'></a><a href='#L156'>156</a>
<a name='L157'></a><a href='#L157'>157</a>
<a name='L158'></a><a href='#L158'>158</a>
<a name='L159'></a><a href='#L159'>159</a>
<a name='L160'></a><a href='#L160'>160</a>
<a name='L161'></a><a href='#L161'>161</a>
<a name='L162'></a><a href='#L162'>162</a>
<a name='L163'></a><a href='#L163'>163</a>
<a name='L164'></a><a href='#L164'>164</a>
<a name='L165'></a><a href='#L165'>165</a>
<a name='L166'></a><a href='#L166'>166</a>
<a name='L167'></a><a href='#L167'>167</a>
<a name='L168'></a><a href='#L168'>168</a>
<a name='L169'></a><a href='#L169'>169</a>
<a name='L170'></a><a href='#L170'>170</a>
<a name='L171'></a><a href='#L171'>171</a>
<a name='L172'></a><a href='#L172'>172</a>
<a name='L173'></a><a href='#L173'>173</a>
<a name='L174'></a><a href='#L174'>174</a>
<a name='L175'></a><a href='#L175'>175</a>
<a name='L176'></a><a href='#L176'>176</a>
<a name='L177'></a><a href='#L177'>177</a>
<a name='L178'></a><a href='#L178'>178</a>
<a name='L179'></a><a href='#L179'>179</a>
<a name='L180'></a><a href='#L180'>180</a>
<a name='L181'></a><a href='#L181'>181</a>
<a name='L182'></a><a href='#L182'>182</a>
<a name='L183'></a><a href='#L183'>183</a>
<a name='L184'></a><a href='#L184'>184</a>
<a name='L185'></a><a href='#L185'>185</a>
<a name='L186'></a><a href='#L186'>186</a>
<a name='L187'></a><a href='#L187'>187</a>
<a name='L188'></a><a href='#L188'>188</a>
<a name='L189'></a><a href='#L189'>189</a>
<a name='L190'></a><a href='#L190'>190</a>
<a name='L191'></a><a href='#L191'>191</a>
<a name='L192'></a><a href='#L192'>192</a>
<a name='L193'></a><a href='#L193'>193</a>
<a name='L194'></a><a href='#L194'>194</a>
<a name='L195'></a><a href='#L195'>195</a>
<a name='L196'></a><a href='#L196'>196</a>
<a name='L197'></a><a href='#L197'>197</a>
<a name='L198'></a><a href='#L198'>198</a>
<a name='L199'></a><a href='#L199'>199</a></td><td class="line-coverage quiet"><span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">37x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">36x</span>
<span class="cline-any cline-yes">37x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">35x</span>
<span class="cline-any cline-yes">35x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">37x</span>
<span class="cline-any cline-yes">37x</span>
<span class="cline-any cline-yes">37x</span>
<span class="cline-any cline-yes">37x</span>
<span class="cline-any cline-yes">37x</span>
<span class="cline-any cline-yes">37x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">29x</span>
<span class="cline-any cline-yes">29x</span>
<span class="cline-any cline-yes">29x</span>
<span class="cline-any cline-yes">29x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">29x</span>
<span class="cline-any cline-yes">29x</span>
<span class="cline-any cline-yes">24x</span>
<span class="cline-any cline-yes">24x</span>
<span class="cline-any cline-yes">29x</span>
<span class="cline-any cline-yes">29x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">29x</span>
<span class="cline-any cline-yes">29x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">29x</span>
<span class="cline-any cline-yes">29x</span>
<span class="cline-any cline-yes">29x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">6x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-no">&nbsp;</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">2x</span>
<span class="cline-any cline-yes">7x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">8x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">9x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span>
<span class="cline-any cline-yes">1x</span></td><td class="text"><pre class="prettyprint lang-js">&lt;script setup lang="ts"&gt;
/**
* FzLink Component
*
* Flexible link component supporting internal routing (vue-router) and external navigation.
* Automatically renders as router-link for internal routes, anchor tag for external URLs,
* or non-interactive span when disabled. Provides consistent styling and accessibility.
*
* TypeScript enforces correct prop types:
* - Internal links (external=false or undefined): to accepts RouteLocationRaw (string or object)
* - External links (external=true): to must be a string URL
*
* @component
* @example
* &lt;FzLink to="/dashboard" size="md"&gt;Go to Dashboard&lt;/FzLink&gt;
*
* @example
* &lt;FzLink :to="{ name: 'user', params: { id: 123 }}"&gt;User Profile&lt;/FzLink&gt;
*
* @example
* &lt;FzLink to="https://example.com" external target="_blank"&gt;External Site&lt;/FzLink&gt;
*/
import { computed } from 'vue'
import { FzLinkProps } from './types'
&nbsp;
const props = withDefaults(defineProps&lt;FzLinkProps&gt;(), {
type: 'default',
linkStyle: 'default',
size: 'md',
disabled: false,
replace: false,
external: false
})
&nbsp;
/**
* Normalizes deprecated size values and shows deprecation warnings.
*
* Maps deprecated sizes to their replacements:
* - 'xs' → 'sm' (with warning)
* - 'lg' → 'md' (with warning)
*
* This ensures backward compatibility while encouraging migration to new values.
*/
const normalizedSize = computed(() =&gt; {
if (props.size === 'xs') {
console.warn(
'[FzLink] The size prop value "xs" is deprecated and will be removed in a future version. ' +
'Please use "sm" instead. The component will automatically map "xs" to "sm" for now.'
)
return 'sm'
}
if (props.size === 'lg') {
console.warn(
'[FzLink] The size prop value "lg" is deprecated and will be removed in a future version. ' +
'Please use "md" instead. The component will automatically map "lg" to "md" for now.'
)
return 'md'
}
return props.size as 'sm' | 'md'
})
&nbsp;
/**
* Base classes shared between link and disabled span states.
*
* Includes size-based text classes and conditional underline styling.
* Border classes provide consistent spacing for focus indicators.
*/
const commonClass = computed(() =&gt; [
'border-1 border-transparent inline-block',
{
'text-sm leading-xs': normalizedSize.value === 'sm',
'text-base leading-base': normalizedSize.value === 'md',
underline: props.linkStyle === 'underline'
}
])
&nbsp;
/**
* Helper functions to identify UI states.
*
* These functions explicitly describe when each UI representation should be applied,
* making the component logic more declarative and maintainable.
*/
const isDefaultUnderline = (p: typeof props) =&gt; p.type === 'default' &amp;&amp; p.linkStyle === 'underline'
const isDefaultNoUnderline = (p: typeof props) =&gt; p.type === 'default' &amp;&amp; p.linkStyle !== 'underline'
const isDangerUnderline = (p: typeof props) =&gt; p.type === 'danger' &amp;&amp; p.linkStyle === 'underline'
const isDangerNoUnderline = (p: typeof props) =&gt; p.type === 'danger' &amp;&amp; p.linkStyle !== 'underline'
const isDefaultDisabled = (p: typeof props) =&gt; p.type === 'default' &amp;&amp; p.disabled
const isDangerDisabled = (p: typeof props) =&gt; p.type === 'danger' &amp;&amp; p.disabled
&nbsp;
/**
* CSS classes for interactive link states (router-link and anchor).
*/
const linkClass = computed(() =&gt; {
const baseClasses = [...commonClass.value]
switch (true) {
case isDefaultUnderline(props):
baseClasses.push('text-blue-500', 'hover:text-blue-600', 'focus:text-blue-600')
break
case isDefaultNoUnderline(props):
baseClasses.push('text-blue-500', 'hover:text-blue-600', 'hover:underline', 'focus:text-blue-600')
break
case isDangerUnderline(props):
baseClasses.push('text-semantic-error-200', 'hover:text-semantic-error-300', 'focus:text-semantic-error-300')
break
case isDangerNoUnderline(props):
baseClasses.push('text-semantic-error-200', 'hover:text-semantic-error-300', 'hover:underline', 'focus:text-semantic-error-300')
break
}
return baseClasses
})
&nbsp;
/**
* CSS classes for disabled link state (rendered as span).
*
* Uses switch(true) pattern to explicitly map disabled UI states to their styling.
* Each case represents a distinct visual representation of the disabled link.
*/
const spanClass = computed(() =&gt; {
const baseClasses = [...commonClass.value, 'cursor-not-allowed']
switch (true) {
case isDefaultDisabled(props):
// Default type disabled: blue-200, underline preserved if linkStyle is underline
baseClasses.push('text-blue-200')
break
case isDangerDisabled(props):
// Danger type disabled: semantic-error-100, underline preserved if linkStyle is underline
baseClasses.push('text-semantic-error-100')
break
}
return baseClasses
})
&nbsp;
/**
* Href value for external links.
*
* When external is true, TypeScript guarantees to is a string.
* This computed ensures type safety in the template.
*/
const externalHref = computed(() =&gt; {
if (props.external) {
// TypeScript narrows to to string when external is true
return props.to as string
<span class="branch-0 cbranch-no" title="branch not covered" > }</span>
<span class="cstat-no" title="statement not covered" > return ''</span>
})
&nbsp;
/**
* Rel attribute for external links with target="_blank".
*
* Adds security attributes (noopener noreferrer) when opening links in new tab
* to prevent security vulnerabilities and improve privacy.
*/
const externalRel = computed(() =&gt; {
if (props.external &amp;&amp; props.target === '_blank') {
return 'noopener noreferrer'
}
return undefined
})
&lt;/script&gt;
&nbsp;
&lt;template&gt;
&lt;span
v-if="disabled"
:class="spanClass"
:aria-disabled="disabled<span class="branch-0 cbranch-no" title="branch not covered" > ? 'true' : 'false'"</span>
role="link"
aria-label="Link disabled"
&gt;
&lt;slot&gt;&lt;/slot&gt;
&lt;/span&gt;
&lt;a
v-else-if="external"
:href="externalHref"
:class="linkClass"
:target="target"
:rel="externalRel"
&gt;
&lt;slot&gt;&lt;/slot&gt;
&lt;/a&gt;
&lt;router-link
v-else
:to="to"
:replace="replace"
:class="linkClass"
:target="target"
&gt;
&lt;slot&gt;&lt;/slot&gt;
&lt;/router-link&gt;
&lt;/template&gt;</pre></td></tr></table></pre>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2025-11-07T13:46:35.098Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<title>Code coverage report for All files</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="prettify.css" />
<link rel="stylesheet" href="base.css" />
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type='text/css'>
.coverage-summary .sorter {
background-image: url(sort-arrow-sprite.png);
}
</style>
</head>
<body>
<div class='wrapper'>
<div class='pad1'>
<h1>All files</h1>
<div class='clearfix'>
<div class='fl pad1y space-right2'>
<span class="strong">99.49% </span>
<span class="quiet">Statements</span>
<span class='fraction'>198/199</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">93.75% </span>
<span class="quiet">Branches</span>
<span class='fraction'>30/32</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">100% </span>
<span class="quiet">Functions</span>
<span class='fraction'>6/6</span>
</div>
<div class='fl pad1y space-right2'>
<span class="strong">99.49% </span>
<span class="quiet">Lines</span>
<span class='fraction'>198/199</span>
</div>
</div>
<p class="quiet">
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
</p>
<template id="filterTemplate">
<div class="quiet">
Filter:
<input type="search" id="fileSearch">
</div>
</template>
</div>
<div class='status-line high'></div>
<div class="pad1">
<table class="coverage-summary">
<thead>
<tr>
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
</tr>
</thead>
<tbody><tr>
<td class="file high" data-value="FzLink.vue"><a href="FzLink.vue.html">FzLink.vue</a></td>
<td data-value="99.49" class="pic high">
<div class="chart"><div class="cover-fill" style="width: 99%"></div><div class="cover-empty" style="width: 1%"></div></div>
</td>
<td data-value="99.49" class="pct high">99.49%</td>
<td data-value="199" class="abs high">198/199</td>
<td data-value="93.75" class="pct high">93.75%</td>
<td data-value="32" class="abs high">30/32</td>
<td data-value="100" class="pct high">100%</td>
<td data-value="6" class="abs high">6/6</td>
<td data-value="99.49" class="pct high">99.49%</td>
<td data-value="199" class="abs high">198/199</td>
</tr>
</tbody>
</table>
</div>
<div class='push'></div><!-- for sticky footer -->
</div><!-- /wrapper -->
<div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2025-11-07T13:46:35.098Z
</div>
<script src="prettify.js"></script>
<script>
window.onload = function () {
prettyPrint();
};
</script>
<script src="sorter.js"></script>
<script src="block-navigation.js"></script>
</body>
</html>
.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}
/* 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;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*</.test(S)?"default-markup":"default-code"}return t[T]}c(K,["default-code"]);c(g([],[[F,/^[^<?]+/],[E,/^<!\w[^>]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\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,/^<!\w[^>]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^<script\b[^>]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\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"]);

Sorry, the diff of this file is not supported yet

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

Sorry, the diff of this file is not supported yet

import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import FzLink from '../FzLink.vue'
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{ name: '', path: '/example', component: () => {} },
{ name: 'route-name', path: '/route/:id', component: () => {} }
]
})
describe('FzLink', () => {
describe('Rendering', () => {
it('matches snapshot', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example'
},
slots: {
default: 'This is a link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
expect(wrapper.html()).toMatchSnapshot()
})
it('renders router-link when not disabled and not external', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example'
},
slots: {
default: 'This is a link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
// router-link renders as <a> tag, so we check for the anchor
expect(wrapper.find('a').exists()).toBe(true)
// Verify it's not an external link (no href attribute, uses router-link)
const link = wrapper.find('a')
// router-link doesn't have href in test environment, it uses to prop
expect(link.exists()).toBe(true)
})
it('renders anchor tag when external', async () => {
const wrapper = mount(FzLink, {
props: {
to: 'https://example.com',
external: true
},
slots: {
default: 'External link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
expect(wrapper.find('a').exists()).toBe(true)
expect(wrapper.find('router-link-stub').exists()).toBe(false)
expect(wrapper.find('a').attributes('href')).toBe('https://example.com')
})
it('renders span when disabled', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
disabled: true
},
slots: {
default: 'Disabled link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
expect(wrapper.find('span').exists()).toBe(true)
expect(wrapper.find('a').exists()).toBe(false)
expect(wrapper.find('router-link-stub').exists()).toBe(false)
})
it('renders slot content', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example'
},
slots: {
default: 'Link text content'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
expect(wrapper.html()).toContain('Link text content')
})
})
describe('Props: type', () => {
it('applies default type classes with default linkStyle', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
type: 'default',
linkStyle: 'default'
},
slots: {
default: 'Default link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.classes()).toContain('text-blue-500')
expect(link.classes()).toContain('hover:text-blue-600')
expect(link.classes()).toContain('hover:underline')
expect(link.classes()).toContain('focus:text-blue-600')
expect(link.classes()).toContain('focus:outline-none')
expect(link.classes()).toContain('focus:border-solid')
expect(link.classes()).toContain('focus:no-underline')
expect(link.classes()).toContain('focus:border-blue-600')
expect(link.classes()).not.toContain('underline')
})
it('applies default type classes with underline linkStyle', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
type: 'default',
linkStyle: 'underline'
},
slots: {
default: 'Default underline link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.classes()).toContain('text-blue-500')
expect(link.classes()).toContain('underline')
expect(link.classes()).toContain('hover:text-blue-600')
expect(link.classes()).toContain('focus:text-blue-600')
expect(link.classes()).toContain('focus:outline-none')
expect(link.classes()).toContain('focus:border-solid')
expect(link.classes()).toContain('focus:no-underline')
expect(link.classes()).toContain('focus:border-blue-600')
})
it('applies danger type classes with default linkStyle', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
type: 'danger',
linkStyle: 'default'
},
slots: {
default: 'Danger link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.classes()).toContain('text-semantic-error-200')
expect(link.classes()).toContain('hover:text-semantic-error-300')
expect(link.classes()).toContain('hover:underline')
expect(link.classes()).toContain('focus:text-semantic-error-300')
expect(link.classes()).toContain('focus:outline-none')
expect(link.classes()).toContain('focus:border-solid')
expect(link.classes()).toContain('focus:no-underline')
expect(link.classes()).toContain('focus:border-semantic-error-300')
expect(link.classes()).not.toContain('underline')
})
it('applies danger type classes with underline linkStyle', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
type: 'danger',
linkStyle: 'underline'
},
slots: {
default: 'Danger underline link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.classes()).toContain('text-semantic-error-200')
expect(link.classes()).toContain('underline')
expect(link.classes()).toContain('hover:text-semantic-error-300')
expect(link.classes()).toContain('focus:text-semantic-error-300')
expect(link.classes()).toContain('focus:outline-none')
expect(link.classes()).toContain('focus:border-solid')
expect(link.classes()).toContain('focus:no-underline')
expect(link.classes()).toContain('focus:border-semantic-error-300')
})
it('applies default type disabled classes', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
type: 'default',
disabled: true
},
slots: {
default: 'Disabled default link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const span = wrapper.find('span')
expect(span.classes()).toContain('text-blue-200')
})
it('applies default type disabled classes with underline', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
type: 'default',
linkStyle: 'underline',
disabled: true
},
slots: {
default: 'Disabled default underline link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const span = wrapper.find('span')
expect(span.classes()).toContain('text-blue-200')
expect(span.classes()).toContain('underline')
})
it('applies danger type disabled classes', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
type: 'danger',
disabled: true
},
slots: {
default: 'Disabled danger link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const span = wrapper.find('span')
expect(span.classes()).toContain('text-semantic-error-100')
})
it('applies danger type disabled classes with underline', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
type: 'danger',
linkStyle: 'underline',
disabled: true
},
slots: {
default: 'Disabled danger underline link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const span = wrapper.find('span')
expect(span.classes()).toContain('text-semantic-error-100')
expect(span.classes()).toContain('underline')
})
})
describe('Props: linkStyle', () => {
it('applies default linkStyle (no underline by default)', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
linkStyle: 'default'
},
slots: {
default: 'Default style'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.classes()).not.toContain('underline')
expect(link.classes()).toContain('hover:underline')
})
it('applies underline linkStyle', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
linkStyle: 'underline'
},
slots: {
default: 'Underline style'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.classes()).toContain('underline')
})
})
describe('Props: size', () => {
it('applies sm size classes (text-sm + leading-xs)', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
size: 'sm'
},
slots: {
default: 'Link text'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.classes()).toContain('text-sm')
expect(link.classes()).toContain('leading-xs')
})
it('applies md size classes (text-base + leading-base)', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
size: 'md'
},
slots: {
default: 'Link text'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.classes()).toContain('text-base')
expect(link.classes()).toContain('leading-base')
})
it('maps deprecated xs size to sm and shows warning', async () => {
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
const wrapper = mount(FzLink, {
props: {
to: '/example',
size: 'xs'
},
slots: {
default: 'Link text'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
// Verify xs is mapped to sm classes
expect(link.classes()).toContain('text-sm')
expect(link.classes()).toContain('leading-xs')
expect(link.classes()).not.toContain('text-xs')
// Verify warning was shown
expect(consoleSpy).toHaveBeenCalledTimes(1)
const warningMessage = consoleSpy.mock.calls[0][0] as string
expect(warningMessage).toContain('[FzLink] The size prop value "xs" is deprecated')
expect(warningMessage).toContain('Please use "sm" instead')
consoleSpy.mockRestore()
})
it('maps deprecated lg size to md and shows warning', async () => {
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
const wrapper = mount(FzLink, {
props: {
to: '/example',
size: 'lg'
},
slots: {
default: 'Link text'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
// Verify lg is mapped to md classes
expect(link.classes()).toContain('text-base')
expect(link.classes()).toContain('leading-base')
expect(link.classes()).not.toContain('text-lg')
// Verify warning was shown
expect(consoleSpy).toHaveBeenCalledTimes(1)
const warningMessage = consoleSpy.mock.calls[0][0] as string
expect(warningMessage).toContain('[FzLink] The size prop value "lg" is deprecated')
expect(warningMessage).toContain('Please use "md" instead')
consoleSpy.mockRestore()
})
})
describe('Props: disabled', () => {
it('renders span with disabled classes', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
disabled: true
},
slots: {
default: 'Disabled link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const span = wrapper.find('span')
expect(span.classes()).toContain('cursor-not-allowed')
expect(span.classes()).toContain('text-blue-200')
})
it('applies default type disabled classes', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
disabled: true,
type: 'default'
},
slots: {
default: 'Disabled default'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const span = wrapper.find('span')
expect(span.classes()).toContain('text-blue-200')
})
})
describe('Props: external', () => {
it('renders anchor tag with href when external', async () => {
const wrapper = mount(FzLink, {
props: {
to: 'https://example.com',
external: true
},
slots: {
default: 'External link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.attributes('href')).toBe('https://example.com')
})
it('does not render router-link when external', async () => {
const wrapper = mount(FzLink, {
props: {
to: 'https://example.com',
external: true
},
slots: {
default: 'External link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
// External links use <a> with href, not router-link
const link = wrapper.find('a')
expect(link.exists()).toBe(true)
expect(link.attributes('href')).toBe('https://example.com')
})
it('uses correct href for external links', async () => {
const wrapper = mount(FzLink, {
props: {
to: 'https://example.com',
external: true
},
slots: {
default: 'External link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
// Verify href attribute is set correctly
const link = wrapper.find('a')
expect(link.attributes('href')).toBe('https://example.com')
})
})
describe('Props: target', () => {
it('applies target attribute to anchor tag', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
target: '_blank'
},
slots: {
default: 'Link with target'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.attributes('target')).toBe('_blank')
})
it('applies target attribute to external link', async () => {
const wrapper = mount(FzLink, {
props: {
to: 'https://example.com',
external: true,
target: '_blank'
},
slots: {
default: 'External link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.attributes('target')).toBe('_blank')
})
})
describe('Props: replace', () => {
it('passes replace prop to router-link', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
replace: true
},
slots: {
default: 'Replace link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
// router-link renders as <a>, verify component accepts replace prop
const link = wrapper.find('a')
expect(link.exists()).toBe(true)
// The replace prop is handled by router-link internally
expect(wrapper.props('replace')).toBe(true)
})
})
describe('Accessibility', () => {
it('should have proper ARIA attributes when disabled', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
disabled: true
},
slots: {
default: 'Disabled link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const span = wrapper.find('span')
expect(span.exists()).toBe(true)
expect(span.attributes('aria-disabled')).toBe('true')
expect(span.attributes('role')).toBe('link')
expect(span.attributes('aria-label')).toBe('Link disabled')
})
it('external link should have href attribute', async () => {
const wrapper = mount(FzLink, {
props: {
to: 'https://example.com',
external: true
},
slots: {
default: 'External link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.attributes('href')).toBeDefined()
expect(link.attributes('href')).toBe('https://example.com')
})
it('external link with target="_blank" should have rel="noopener noreferrer"', async () => {
const wrapper = mount(FzLink, {
props: {
to: 'https://example.com',
external: true,
target: '_blank'
},
slots: {
default: 'External link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.attributes('rel')).toBe('noopener noreferrer')
expect(link.attributes('target')).toBe('_blank')
})
it('external link without target="_blank" should not have rel attribute', async () => {
const wrapper = mount(FzLink, {
props: {
to: 'https://example.com',
external: true,
target: '_self'
},
slots: {
default: 'External link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.attributes('rel')).toBeUndefined()
expect(link.attributes('target')).toBe('_self')
})
})
describe('RouteLocationRaw support', () => {
it('accepts string path for internal links', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example'
},
slots: {
default: 'String path link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.exists()).toBe(true)
})
it('accepts object with path for internal links', async () => {
const wrapper = mount(FzLink, {
props: {
to: { path: '/example', query: { id: '123' } }
},
slots: {
default: 'Object path link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.exists()).toBe(true)
// router-link handles the object internally
})
it('accepts object with name for internal links', async () => {
const wrapper = mount(FzLink, {
props: {
to: { name: 'route-name', params: { id: 123 } }
},
slots: {
default: 'Named route link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.exists()).toBe(true)
// router-link handles the named route internally
})
it('requires string URL for external links', async () => {
const wrapper = mount(FzLink, {
props: {
to: 'https://example.com',
external: true
},
slots: {
default: 'External string link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.attributes('href')).toBe('https://example.com')
// TypeScript enforces: external=true requires to to be string
// This test verifies runtime behavior with correct types
})
})
describe('Edge cases', () => {
it('handles default props correctly', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example'
},
slots: {
default: 'Link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.classes()).toContain('text-base') // default size
expect(link.classes()).toContain('leading-base') // default size line-height
expect(link.classes()).toContain('text-blue-500') // default type
expect(link.classes()).toContain('hover:text-blue-600') // default hover
expect(link.classes()).toContain('hover:underline') // default hover underline
expect(link.classes()).not.toContain('underline') // default linkStyle (no underline by default)
})
it('renders correctly with all props combined', async () => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
type: 'danger',
linkStyle: 'underline',
size: 'sm',
target: '_self'
},
slots: {
default: 'Complete link'
},
global: {
plugins: [router]
}
})
await wrapper.vm.$nextTick()
const link = wrapper.find('a')
expect(link.classes()).toContain('text-sm')
expect(link.classes()).toContain('leading-xs')
expect(link.classes()).toContain('underline')
expect(link.classes()).toContain('text-semantic-error-200')
expect(link.classes()).toContain('hover:text-semantic-error-300')
expect(link.classes()).toContain('focus:text-semantic-error-300')
expect(link.classes()).toContain('focus:outline-none')
expect(link.classes()).toContain('focus:border-solid')
expect(link.classes()).toContain('focus:no-underline')
expect(link.classes()).toContain('focus:border-semantic-error-300')
expect(link.attributes('target')).toBe('_self')
})
})
})
+65
-42

@@ -1,62 +0,85 @@

import { defineComponent as i, computed as l, resolveComponent as c, openBlock as a, createElementBlock as d, normalizeClass as r, renderSlot as o, createBlock as m, withCtx as g } from "vue";
const k = ["href", "target"], v = /* @__PURE__ */ i({
import { defineComponent as x, computed as a, resolveComponent as z, createElementBlock as u, createBlock as w, openBlock as r, normalizeClass as l, renderSlot as n, withCtx as C } from "vue";
const D = ["aria-disabled"], S = ["href", "target", "rel"], _ = /* @__PURE__ */ x({
__name: "FzLink",
props: {
to: {},
replace: { type: Boolean, default: !1 },
type: { default: "default" },
linkStyle: { default: "default" },
size: { default: "lg" },
size: { default: "md" },
disabled: { type: Boolean, default: !1 },
target: {},
external: { type: Boolean, default: !1 }
to: {},
external: { type: Boolean, default: !1 },
replace: { type: Boolean, default: !1 },
target: {}
},
setup(u) {
const t = u, s = l(() => [
"border-1 border-transparent",
setup(d) {
const t = d, s = a(() => t.size === "xs" ? (console.warn(
'[FzLink] The size prop value "xs" is deprecated and will be removed in a future version. Please use "sm" instead. The component will automatically map "xs" to "sm" for now.'
), "sm") : t.size === "lg" ? (console.warn(
'[FzLink] The size prop value "lg" is deprecated and will be removed in a future version. Please use "md" instead. The component will automatically map "lg" to "md" for now.'
), "md") : t.size), o = a(() => [
"border-1 border-transparent inline-block",
{
"text-xs": t.size === "xs",
"text-sm": t.size === "sm",
"text-md": t.size === "md",
"text-lg": t.size === "lg",
"text-sm leading-xs": s.value === "sm",
"text-base leading-base": s.value === "md",
underline: t.linkStyle === "underline"
}
]), n = l(() => [
...s.value,
"hover:underline",
{
"text-blue-500 hover:text-blue-600 focus:text-blue-600 focus:border-blue-600": t.type === "default",
"text-semantic-error hover:text-red-600 focus:text-red-600 focus:border-red-600": t.type === "danger"
]), c = (e) => e.type === "default" && e.linkStyle === "underline", f = (e) => e.type === "default" && e.linkStyle !== "underline", p = (e) => e.type === "danger" && e.linkStyle === "underline", m = (e) => e.type === "danger" && e.linkStyle !== "underline", b = (e) => e.type === "default" && e.disabled, k = (e) => e.type === "danger" && e.disabled, i = a(() => {
const e = [...o.value];
switch (!0) {
case c(t):
e.push("text-blue-500", "hover:text-blue-600", "focus:text-blue-600");
break;
case f(t):
e.push("text-blue-500", "hover:text-blue-600", "hover:underline", "focus:text-blue-600");
break;
case p(t):
e.push("text-semantic-error-200", "hover:text-semantic-error-300", "focus:text-semantic-error-300");
break;
case m(t):
e.push("text-semantic-error-200", "hover:text-semantic-error-300", "hover:underline", "focus:text-semantic-error-300");
break;
}
]), p = l(() => [
...s.value,
"cursor-not-allowed",
{
"text-red-200": t.type === "danger",
"text-blue-200": t.type === "default"
return e;
}), h = a(() => {
const e = [...o.value, "cursor-not-allowed"];
switch (!0) {
case b(t):
e.push("text-blue-200");
break;
case k(t):
e.push("text-semantic-error-100");
break;
}
]);
return (e, y) => {
const f = c("router-link");
return e.disabled ? (a(), d("span", {
return e;
}), v = a(() => t.external ? t.to : ""), y = a(() => {
if (t.external && t.target === "_blank")
return "noopener noreferrer";
});
return (e, B) => {
const g = z("router-link");
return e.disabled ? (r(), u("span", {
key: 0,
class: r(p.value)
class: l(h.value),
"aria-disabled": e.disabled ? "true" : "false",
role: "link",
"aria-label": "Link disabled"
}, [
o(e.$slots, "default")
], 2)) : e.external ? (a(), d("a", {
n(e.$slots, "default")
], 10, D)) : e.external ? (r(), u("a", {
key: 1,
href: e.to.toString(),
class: r(n.value),
target: e.target
href: v.value,
class: l(i.value),
target: e.target,
rel: y.value
}, [
o(e.$slots, "default")
], 10, k)) : (a(), m(f, {
n(e.$slots, "default")
], 10, S)) : (r(), w(g, {
key: 2,
to: e.to,
replace: e.replace,
class: r(n.value),
class: l(i.value),
target: e.target
}, {
default: g(() => [
o(e.$slots, "default")
default: C(() => [
n(e.$slots, "default")
]),

@@ -69,3 +92,3 @@ _: 3

export {
v as FzLink
_ as FzLink
};

@@ -1,1 +0,1 @@

(function(o,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(o=typeof globalThis<"u"?globalThis:o||self,e(o.FzLink={},o.Vue))})(this,function(o,e){"use strict";const s=["href","target"],a=e.defineComponent({__name:"FzLink",props:{to:{},replace:{type:Boolean,default:!1},type:{default:"default"},linkStyle:{default:"default"},size:{default:"lg"},disabled:{type:Boolean,default:!1},target:{},external:{type:Boolean,default:!1}},setup(d){const l=d,n=e.computed(()=>["border-1 border-transparent",{"text-xs":l.size==="xs","text-sm":l.size==="sm","text-md":l.size==="md","text-lg":l.size==="lg",underline:l.linkStyle==="underline"}]),r=e.computed(()=>[...n.value,"hover:underline",{"text-blue-500 hover:text-blue-600 focus:text-blue-600 focus:border-blue-600":l.type==="default","text-semantic-error hover:text-red-600 focus:text-red-600 focus:border-red-600":l.type==="danger"}]),i=e.computed(()=>[...n.value,"cursor-not-allowed",{"text-red-200":l.type==="danger","text-blue-200":l.type==="default"}]);return(t,p)=>{const f=e.resolveComponent("router-link");return t.disabled?(e.openBlock(),e.createElementBlock("span",{key:0,class:e.normalizeClass(i.value)},[e.renderSlot(t.$slots,"default")],2)):t.external?(e.openBlock(),e.createElementBlock("a",{key:1,href:t.to.toString(),class:e.normalizeClass(r.value),target:t.target},[e.renderSlot(t.$slots,"default")],10,s)):(e.openBlock(),e.createBlock(f,{key:2,to:t.to,replace:t.replace,class:e.normalizeClass(r.value),target:t.target},{default:e.withCtx(()=>[e.renderSlot(t.$slots,"default")]),_:3},8,["to","replace","class","target"]))}}});o.FzLink=a,Object.defineProperty(o,Symbol.toStringTag,{value:"Module"})});
(function(l,t){typeof exports=="object"&&typeof module<"u"?t(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],t):(l=typeof globalThis<"u"?globalThis:l||self,t(l.FzLink={},l.Vue))})(this,function(l,t){"use strict";const o=["aria-disabled"],i=["href","target","rel"],d=t.defineComponent({__name:"FzLink",props:{type:{default:"default"},linkStyle:{default:"default"},size:{default:"md"},disabled:{type:Boolean,default:!1},to:{},external:{type:Boolean,default:!1},replace:{type:Boolean,default:!1},target:{}},setup(u){const n=u,r=t.computed(()=>n.size==="xs"?(console.warn('[FzLink] The size prop value "xs" is deprecated and will be removed in a future version. Please use "sm" instead. The component will automatically map "xs" to "sm" for now.'),"sm"):n.size==="lg"?(console.warn('[FzLink] The size prop value "lg" is deprecated and will be removed in a future version. Please use "md" instead. The component will automatically map "lg" to "md" for now.'),"md"):n.size),a=t.computed(()=>["border-1 border-transparent inline-block",{"text-sm leading-xs":r.value==="sm","text-base leading-base":r.value==="md",underline:n.linkStyle==="underline"}]),c=e=>e.type==="default"&&e.linkStyle==="underline",f=e=>e.type==="default"&&e.linkStyle!=="underline",p=e=>e.type==="danger"&&e.linkStyle==="underline",m=e=>e.type==="danger"&&e.linkStyle!=="underline",b=e=>e.type==="default"&&e.disabled,k=e=>e.type==="danger"&&e.disabled,s=t.computed(()=>{const e=[...a.value];switch(!0){case c(n):e.push("text-blue-500","hover:text-blue-600","focus:text-blue-600");break;case f(n):e.push("text-blue-500","hover:text-blue-600","hover:underline","focus:text-blue-600");break;case p(n):e.push("text-semantic-error-200","hover:text-semantic-error-300","focus:text-semantic-error-300");break;case m(n):e.push("text-semantic-error-200","hover:text-semantic-error-300","hover:underline","focus:text-semantic-error-300");break}return e}),h=t.computed(()=>{const e=[...a.value,"cursor-not-allowed"];switch(!0){case b(n):e.push("text-blue-200");break;case k(n):e.push("text-semantic-error-100");break}return e}),y=t.computed(()=>n.external?n.to:""),g=t.computed(()=>{if(n.external&&n.target==="_blank")return"noopener noreferrer"});return(e,z)=>{const x=t.resolveComponent("router-link");return e.disabled?(t.openBlock(),t.createElementBlock("span",{key:0,class:t.normalizeClass(h.value),"aria-disabled":e.disabled?"true":"false",role:"link","aria-label":"Link disabled"},[t.renderSlot(e.$slots,"default")],10,o)):e.external?(t.openBlock(),t.createElementBlock("a",{key:1,href:y.value,class:t.normalizeClass(s.value),target:e.target,rel:g.value},[t.renderSlot(e.$slots,"default")],10,i)):(t.openBlock(),t.createBlock(x,{key:2,to:e.to,replace:e.replace,class:t.normalizeClass(s.value),target:e.target},{default:t.withCtx(()=>[t.renderSlot(e.$slots,"default")]),_:3},8,["to","replace","class","target"]))}}});l.FzLink=d,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})});

@@ -1,45 +0,5 @@

import { FzLinkProps } from './types';
declare const _default: __VLS_WithTemplateSlots<import('vue').DefineComponent<__VLS_WithDefaults<__VLS_TypePropsToRuntimeProps<FzLinkProps>, {
type: string;
linkStyle: string;
size: string;
disabled: boolean;
replace: boolean;
external: boolean;
}>, {}, unknown, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<__VLS_WithDefaults<__VLS_TypePropsToRuntimeProps<FzLinkProps>, {
type: string;
linkStyle: string;
size: string;
disabled: boolean;
replace: boolean;
external: boolean;
}>>>, {
replace: boolean;
type: "default" | "danger";
linkStyle: "default" | "underline";
size: "xs" | "sm" | "md" | "lg";
disabled: boolean;
external: boolean;
}, {}>, {
declare const _default: __VLS_WithTemplateSlots<import('vue').DefineSetupFnComponent<Record<string, any>, {}, {}, Record<string, any> & {}, import('vue').PublicProps>, {
default?(_: {}): any;
}>;
export default _default;
type __VLS_NonUndefinedable<T> = T extends undefined ? never : T;
type __VLS_TypePropsToRuntimeProps<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? {
type: import('vue').PropType<__VLS_NonUndefinedable<T[K]>>;
} : {
type: import('vue').PropType<T[K]>;
required: true;
};
};
type __VLS_WithDefaults<P, D> = {
[K in keyof Pick<P, keyof P>]: K extends keyof D ? __VLS_Prettify<P[K] & {
default: D[K];
}> : P[K];
};
type __VLS_Prettify<T> = {
[K in keyof T]: T[K];
} & {};
type __VLS_WithTemplateSlots<T, S> = T & {

@@ -46,0 +6,0 @@ new (): {

import { RouteLocationRaw } from 'vue-router';
type FzLinkProps = {
/**
* Base props shared between internal and external link variants.
*/
type FzLinkBaseProps = {
/**
* Route Location the link should navigate to when clicked on.
* Visual variant indicating link purpose. Affects color scheme.
* @default 'default'
*/
type?: 'default' | 'danger';
/**
* Text decoration style. 'default' shows underline on hover, 'underline' always shows underline.
* @default 'default'
*/
linkStyle?: 'default' | 'underline';
/**
* Text size affecting font size and line height.
* @default 'md'
* @deprecated 'xs' is deprecated and will be mapped to 'sm'. Use 'sm' instead.
* @deprecated 'lg' is deprecated and will be mapped to 'md'. Use 'md' instead.
*/
size?: 'xs' | 'sm' | 'md' | 'lg';
/**
* Disables the link, rendering as non-interactive span with disabled styling.
* @default false
*/
disabled?: boolean;
};
/**
* Props for internal links (using vue-router).
*
* When external is false or undefined, to accepts RouteLocationRaw
* (string path or object with name, params, query, etc.).
*
* @example
* <FzLink to="/dashboard">Dashboard</FzLink>
*
* @example
* <FzLink :to="{ name: 'user', params: { id: 123 }}">User</FzLink>
*/
export type FzLinkInternalProps = FzLinkBaseProps & {
/**
* Destination route. Accepts vue-router RouteLocationRaw:
* - String path: "/dashboard"
* - Object with name: { name: 'user', params: { id: 123 } }
* - Object with path: { path: '/search', query: { q: 'vue' } }
*/
to: RouteLocationRaw;
/**
* Calls `router.replace` instead of `router.push`.
* When false or undefined, link uses vue-router for internal navigation.
* @default false
*/
replace?: boolean;
external?: false;
/**
* The purpose of the link
* Uses router.replace instead of router.push for navigation.
* @default false
*/
type?: 'default' | 'danger';
replace?: boolean;
/**
* The appearance of the link
* Target attribute for anchor tag (e.g., '_blank', '_self').
* Can be used with router-link to open internal routes in new tab/window.
*/
linkStyle?: 'default' | 'underline';
target?: string;
};
/**
* Props for external links (using anchor tag).
*
* When external is true, to must be a string URL.
*
* @example
* <FzLink to="https://example.com" external>External Site</FzLink>
*
* @example
* <FzLink to="https://example.com" external target="_blank">Open in New Tab</FzLink>
*/
export type FzLinkExternalProps = FzLinkBaseProps & {
/**
* Size of the link
* External URL as string. Must be a full URL (e.g., "https://example.com").
*/
size?: 'xs' | 'sm' | 'md' | 'lg';
to: string;
/**
* Whether the link is disabled
* When true, link renders as anchor tag with href for external navigation.
*/
disabled?: boolean;
external: true;
/**
* Target of the link
* Target attribute for anchor tag (e.g., '_blank', '_self').
*/
target?: string;
/**
* Whether the link is for an external page or not
* Replace is not applicable for external links.
*/
external?: boolean;
replace?: never;
};
export { FzLinkProps };
/**
* Props for the FzLink component.
*
* A flexible link component that supports both internal routing (via vue-router)
* and external navigation. Automatically renders as router-link for internal routes,
* anchor tag for external URLs, or span when disabled.
*
* TypeScript will enforce:
* - Internal links (external=false or undefined): to accepts RouteLocationRaw (string or object)
* - External links (external=true): to must be a string URL
*
* @example
* <FzLink to="/dashboard" size="md">Go to Dashboard</FzLink>
*
* @example
* <FzLink :to="{ name: 'user', params: { id: 123 }}">User Profile</FzLink>
*
* @example
* <FzLink to="https://example.com" external target="_blank">External Site</FzLink>
*/
export type FzLinkProps = FzLinkInternalProps | FzLinkExternalProps;
export {};
{
"name": "@fiscozen/link",
"version": "0.1.4",
"version": "1.0.0-next.0",
"description": "Design System Link component",

@@ -5,0 +5,0 @@ "main": "src/index.ts",

+179
-1
# @fiscozen/link
Links are used as navigational elements. They navigate users to another location, such as a different site, resource or section within the same page.
Flexible link component supporting internal routing (vue-router) and external navigation. Automatically renders as router-link for internal routes, anchor tag for external URLs, or non-interactive span when disabled.
## Features
- **Internal Routing**: Uses vue-router for SPA navigation with RouteLocationRaw support (string or object)
- **External Navigation**: Supports external URLs with anchor tags
- **Disabled State**: Renders as non-interactive span with disabled styling
- **Multiple Variants**: Default and danger types
- **Text Styles**: Default (underline on hover) or always underlined
- **Size Options**: sm, md
- **TypeScript Type Safety**: Discriminated union types enforce correct prop combinations
- **Full Accessibility**: ARIA support and keyboard navigation
- **Security**: Automatic rel="noopener noreferrer" for external links with target="_blank"
## Installation
```bash
npm install @fiscozen/link
```
## Basic Usage
### Internal Route (String Path)
```vue
<script setup lang="ts">
import { FzLink } from '@fiscozen/link'
</script>
<template>
<FzLink to="/dashboard" size="md">Go to Dashboard</FzLink>
</template>
```
### Internal Route (Object with Name)
```vue
<template>
<FzLink :to="{ name: 'user', params: { id: 123 }}">
User Profile
</FzLink>
</template>
```
### Internal Route (Object with Path and Query)
```vue
<template>
<FzLink :to="{ path: '/search', query: { q: 'vue' }}">
Search Results
</FzLink>
</template>
```
### External Link
```vue
<template>
<FzLink to="https://example.com" external target="_blank">
External Site
</FzLink>
</template>
```
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `to` | `RouteLocationRaw \| string` | - | Destination route or URL. Type depends on `external` prop: **Internal** (external=false or undefined): Accepts vue-router RouteLocationRaw (string path or object with name/path, params, query, etc.). **External** (external=true): Must be a string URL. |
| `type` | `'default' \| 'danger'` | `'default'` | Visual variant indicating link purpose. Affects color scheme. |
| `linkStyle` | `'default' \| 'underline'` | `'default'` | Text decoration style. 'default' shows underline on hover, 'underline' always shows underline. |
| `size` | `'sm' \| 'md'` | `'md'` | Text size affecting font size and line height. |
| `disabled` | `boolean` | `false` | Disables the link, rendering as non-interactive span with disabled styling. |
| `target` | `string` | - | Target attribute for anchor tag (e.g., '_blank', '_self'). Can be used with both internal (router-link) and external links. |
| `external` | `boolean` | `false` | When true, renders as anchor tag with href instead of router-link. Use for external URLs or when router navigation is not desired. |
| `replace` | `boolean` | `false` | Uses router.replace instead of router.push for navigation. Only applies to internal routes (when external is false). |
## TypeScript Type Safety
The component uses discriminated union types to enforce correct prop combinations:
- **Internal links** (`external=false` or `undefined`):
- `to` accepts `RouteLocationRaw` (string path or object with name/path, params, query, etc.)
- `replace` can be used
- `target` can be used (opens internal route in new tab/window)
- **External links** (`external=true`):
- `to` must be a string URL
- `target` can be used
- `replace` cannot be used (not applicable for external links)
TypeScript will show errors if you try to use incompatible prop combinations.
## Examples
### Navigation Menu
```vue
<template>
<nav>
<FzLink to="/dashboard" size="md">Dashboard</FzLink>
<FzLink to="/settings" size="md">Settings</FzLink>
<FzLink :to="{ name: 'profile', params: { id: userId }}" size="md">
Profile
</FzLink>
</nav>
</template>
```
### External Links with Security
```vue
<template>
<FzLink to="https://example.com" external target="_blank">
Visit Example Site
</FzLink>
</template>
```
The component automatically adds `rel="noopener noreferrer"` for security when `target="_blank"` is used.
### Disabled State
```vue
<template>
<FzLink to="/premium" disabled>
Premium Feature (Requires Upgrade)
</FzLink>
</template>
```
### Danger Link
```vue
<template>
<FzLink to="/delete-account" type="danger" size="md">
Delete Account
</FzLink>
</template>
```
### Replace Navigation
```vue
<template>
<FzLink to="/redirect" replace>
Redirect Without History
</FzLink>
</template>
```
## Accessibility
FzLink is fully accessible and meets WCAG 2.1 AA standards:
- **Keyboard Navigation**: All links are keyboard accessible (Tab, Enter)
- **Screen Readers**: Proper ARIA attributes for disabled state
- **Focus Indicators**: Visible focus outline with proper contrast
- **Semantic HTML**: Uses router-link for internal routes, anchor for external
- **Disabled State**: Renders as span with aria-disabled, role="link", and aria-label
### ARIA Attributes
- `aria-disabled`: Set to "true" when link is disabled (string value for Vue 3 compatibility)
- `role`: Set to "link" for disabled span to maintain semantic meaning
- `aria-label`: Provides accessible label for disabled links
- `rel`: Automatically adds "noopener noreferrer" for external links with target="_blank"
## Security
External links with `target="_blank"` automatically include `rel="noopener noreferrer"` to:
- Prevent security vulnerabilities (window.opener access)
- Improve privacy (no referrer information sent)
## Related Components
- **FzNavlink**: Navigation link with active state support
- **FzButton**: Button component for actions
- **FzRouterNavlink**: Router-aware navigation link

@@ -1,15 +0,24 @@

<template>
<span v-if="disabled" :class="spanClass">
<slot></slot>
</span>
<a v-else-if="external" :href="to.toString()" :class="linkClass" :target>
<slot></slot>
</a>
<router-link v-else :to :replace :class="linkClass" :target>
<slot></slot>
</router-link>
</template>
<script setup lang="ts">
import { computed } from 'vue'
/**
* FzLink Component
*
* Flexible link component supporting internal routing (vue-router) and external navigation.
* Automatically renders as router-link for internal routes, anchor tag for external URLs,
* or non-interactive span when disabled. Provides consistent styling and accessibility.
*
* TypeScript enforces correct prop types:
* - Internal links (external=false or undefined): to accepts RouteLocationRaw (string or object)
* - External links (external=true): to must be a string URL
*
* @component
* @example
* <FzLink to="/dashboard" size="md">Go to Dashboard</FzLink>
*
* @example
* <FzLink :to="{ name: 'user', params: { id: 123 }}">User Profile</FzLink>
*
* @example
* <FzLink to="https://example.com" external target="_blank">External Site</FzLink>
*/
import { computed, watch } from 'vue'
import { FzLinkProps } from './types'

@@ -20,3 +29,3 @@

linkStyle: 'default',
size: 'lg',
size: 'md',
disabled: false,

@@ -27,9 +36,73 @@ replace: false,

/**
* Deprecation warning for 'xs' size prop value.
*
* Watches for 'xs' size usage and logs warning once on mount or when size changes.
* Using watch with immediate:true ensures the warning only fires once per component instance.
*/
watch(
() => props.size === 'xs',
(isXs) => {
if (isXs) {
console.warn(
'[FzLink] The size prop value "xs" is deprecated and will be removed in a future version. ' +
'Please use "sm" instead. The component will automatically map "xs" to "sm" for now.'
)
}
},
{ immediate: true }
)
/**
* Deprecation warning for 'lg' size prop value.
*
* Watches for 'lg' size usage and logs warning once on mount or when size changes.
* Using watch with immediate:true ensures the warning only fires once per component instance.
*/
watch(
() => props.size === 'lg',
(isLg) => {
if (isLg) {
console.warn(
'[FzLink] The size prop value "lg" is deprecated and will be removed in a future version. ' +
'Please use "md" instead. The component will automatically map "lg" to "md" for now.'
)
}
},
{ immediate: true }
)
/**
* Normalizes deprecated size values to their replacements.
*
* Maps deprecated sizes to their replacements:
* - 'xs' → 'sm'
* - 'lg' → 'md'
*
* This ensures backward compatibility while encouraging migration to new values.
* Deprecation warnings are handled separately via watch hooks.
*/
const normalizedSize = computed(() => {
if (props.size === 'xs') {
return 'sm'
}
if (props.size === 'lg') {
return 'md'
}
return props.size as 'sm' | 'md'
})
/**
* Base classes shared between link and disabled span states.
*
* Includes size-based text classes and conditional underline styling.
* Border classes provide consistent spacing for focus indicators.
*/
const commonClass = computed(() => [
'border-1 border-transparent inline-block',
{
'text-xs': props.size === 'xs',
'text-sm': props.size === 'sm',
'text-md': props.size === 'md',
'text-lg': props.size === 'lg',
'text-sm leading-xs': normalizedSize.value === 'sm',
'text-base leading-base': normalizedSize.value === 'md',
underline: props.linkStyle === 'underline'

@@ -39,21 +112,122 @@ }

const linkClass = computed(() => [
...commonClass.value,
'hover:underline',
{
'text-blue-500 hover:text-blue-600 focus:text-blue-600 focus:border-blue-600':
props.type === 'default',
'text-semantic-error hover:text-red-600 focus:text-red-600 focus:border-red-600':
props.type === 'danger'
/**
* Helper functions to identify UI states.
*
* These functions explicitly describe when each UI representation should be applied,
* making the component logic more declarative and maintainable.
*/
const isDefaultUnderline = (p: typeof props) => p.type === 'default' && p.linkStyle === 'underline'
const isDefaultNoUnderline = (p: typeof props) => p.type === 'default' && p.linkStyle !== 'underline'
const isDangerUnderline = (p: typeof props) => p.type === 'danger' && p.linkStyle === 'underline'
const isDangerNoUnderline = (p: typeof props) => p.type === 'danger' && p.linkStyle !== 'underline'
const isDefaultDisabled = (p: typeof props) => p.type === 'default' && p.disabled
const isDangerDisabled = (p: typeof props) => p.type === 'danger' && p.disabled
/**
* CSS classes for interactive link states (router-link and anchor).
*/
const linkClass = computed(() => {
const baseClasses = [...commonClass.value, 'focus:outline-none', 'focus:border-solid', 'focus:no-underline']
switch (true) {
case isDefaultUnderline(props):
baseClasses.push('text-blue-500', 'hover:text-blue-600', 'focus:text-blue-600', 'focus:border-blue-600')
break
case isDefaultNoUnderline(props):
baseClasses.push('text-blue-500', 'hover:text-blue-600', 'hover:underline', 'focus:text-blue-600', 'focus:border-blue-600')
break
case isDangerUnderline(props):
baseClasses.push('text-semantic-error-200', 'hover:text-semantic-error-300', 'focus:text-semantic-error-300', 'focus:border-semantic-error-300')
break
case isDangerNoUnderline(props):
baseClasses.push('text-semantic-error-200', 'hover:text-semantic-error-300', 'hover:underline', 'focus:text-semantic-error-300', 'focus:border-semantic-error-300')
break
}
])
return baseClasses
})
const spanClass = computed(() => [
...commonClass.value,
'cursor-not-allowed',
{
'text-red-200': props.type === 'danger',
'text-blue-200': props.type === 'default'
/**
* CSS classes for disabled link state (rendered as span).
*
* Uses switch(true) pattern to explicitly map disabled UI states to their styling.
* Each case represents a distinct visual representation of the disabled link.
*/
const spanClass = computed(() => {
const baseClasses = [...commonClass.value, 'cursor-not-allowed']
switch (true) {
case isDefaultDisabled(props):
// Default type disabled: blue-200, underline preserved if linkStyle is underline
baseClasses.push('text-blue-200')
break
case isDangerDisabled(props):
// Danger type disabled: semantic-error-100, underline preserved if linkStyle is underline
baseClasses.push('text-semantic-error-100')
break
}
])
return baseClasses
})
/**
* Href value for external links.
*
* When external is true, TypeScript guarantees to is a string.
* This computed ensures type safety in the template.
*/
const externalHref = computed(() => {
if (props.external) {
// TypeScript narrows to to string when external is true
return props.to as string
}
return ''
})
/**
* Rel attribute for external links with target="_blank".
*
* Adds security attributes (noopener noreferrer) when opening links in new tab
* to prevent security vulnerabilities and improve privacy.
*/
const externalRel = computed(() => {
if (props.external && props.target === '_blank') {
return 'noopener noreferrer'
}
return undefined
})
</script>
<template>
<span
v-if="disabled"
:class="spanClass"
:aria-disabled="disabled ? 'true' : 'false'"
role="link"
aria-label="Link disabled"
>
<slot></slot>
</span>
<a
v-else-if="external"
:href="externalHref"
:class="linkClass"
:target="target"
:rel="externalRel"
>
<slot></slot>
</a>
<router-link
v-else
:to="to"
:replace="replace"
:class="linkClass"
:target="target"
>
<slot></slot>
</router-link>
</template>

@@ -0,38 +1,124 @@

/**
* Type definitions for the Fiscozen Link component library.
*
* @module @fiscozen/link/types
*/
import { RouteLocationRaw } from 'vue-router'
type FzLinkProps = {
/**
* Base props shared between internal and external link variants.
*/
type FzLinkBaseProps = {
/**
* Route Location the link should navigate to when clicked on.
* Visual variant indicating link purpose. Affects color scheme.
* @default 'default'
*/
type?: 'default' | 'danger'
/**
* Text decoration style. 'default' shows underline on hover, 'underline' always shows underline.
* @default 'default'
*/
linkStyle?: 'default' | 'underline'
/**
* Text size affecting font size and line height.
* @default 'md'
* @deprecated 'xs' is deprecated and will be mapped to 'sm'. Use 'sm' instead.
* @deprecated 'lg' is deprecated and will be mapped to 'md'. Use 'md' instead.
*/
size?: 'xs' | 'sm' | 'md' | 'lg'
/**
* Disables the link, rendering as non-interactive span with disabled styling.
* @default false
*/
disabled?: boolean
}
/**
* Props for internal links (using vue-router).
*
* When external is false or undefined, to accepts RouteLocationRaw
* (string path or object with name, params, query, etc.).
*
* @example
* <FzLink to="/dashboard">Dashboard</FzLink>
*
* @example
* <FzLink :to="{ name: 'user', params: { id: 123 }}">User</FzLink>
*/
export type FzLinkInternalProps = FzLinkBaseProps & {
/**
* Destination route. Accepts vue-router RouteLocationRaw:
* - String path: "/dashboard"
* - Object with name: { name: 'user', params: { id: 123 } }
* - Object with path: { path: '/search', query: { q: 'vue' } }
*/
to: RouteLocationRaw
/**
* Calls `router.replace` instead of `router.push`.
* When false or undefined, link uses vue-router for internal navigation.
* @default false
*/
replace?: boolean
external?: false
/**
* The purpose of the link
* Uses router.replace instead of router.push for navigation.
* @default false
*/
type?: 'default' | 'danger'
replace?: boolean
/**
* The appearance of the link
* Target attribute for anchor tag (e.g., '_blank', '_self').
* Can be used with router-link to open internal routes in new tab/window.
*/
linkStyle?: 'default' | 'underline'
target?: string
}
/**
* Props for external links (using anchor tag).
*
* When external is true, to must be a string URL.
*
* @example
* <FzLink to="https://example.com" external>External Site</FzLink>
*
* @example
* <FzLink to="https://example.com" external target="_blank">Open in New Tab</FzLink>
*/
export type FzLinkExternalProps = FzLinkBaseProps & {
/**
* Size of the link
* External URL as string. Must be a full URL (e.g., "https://example.com").
*/
size?: 'xs' | 'sm' | 'md' | 'lg'
to: string
/**
* Whether the link is disabled
* When true, link renders as anchor tag with href for external navigation.
*/
disabled?: boolean
external: true
/**
* Target of the link
* Target attribute for anchor tag (e.g., '_blank', '_self').
*/
target?: string
/**
* Whether the link is for an external page or not
* Replace is not applicable for external links.
*/
external?: boolean
replace?: never
}
export { FzLinkProps }
/**
* Props for the FzLink component.
*
* A flexible link component that supports both internal routing (via vue-router)
* and external navigation. Automatically renders as router-link for internal routes,
* anchor tag for external URLs, or span when disabled.
*
* TypeScript will enforce:
* - Internal links (external=false or undefined): to accepts RouteLocationRaw (string or object)
* - External links (external=true): to must be a string URL
*
* @example
* <FzLink to="/dashboard" size="md">Go to Dashboard</FzLink>
*
* @example
* <FzLink :to="{ name: 'user', params: { id: 123 }}">User Profile</FzLink>
*
* @example
* <FzLink to="https://example.com" external target="_blank">External Site</FzLink>
*/
export type FzLinkProps = FzLinkInternalProps | FzLinkExternalProps

Sorry, the diff of this file is not supported yet

import { describe, it } from 'vitest'
import { mount } from '@vue/test-utils'
import FzLink from '../FzLink.vue'
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [{ name: '', path: '/example', component: () => {} }]
})
describe.concurrent('FzLink', () => {
it('image matches snapshot', async ({ expect }) => {
const wrapper = mount(FzLink, {
props: {
to: '/example'
},
slots: {
default: 'This is a link'
},
global: {
plugins: [router]
}
})
expect(wrapper.html()).toMatchSnapshot()
})
it('should render link when not disabled', async ({ expect }) => {
const wrapper = mount(FzLink, {
props: {
to: '/example'
},
slots: {
default: 'This is a link'
},
global: {
plugins: [router]
}
})
expect(wrapper.find('a').exists()).toBe(true)
})
it('should render span when disabled', async ({ expect }) => {
const wrapper = mount(FzLink, {
props: {
to: '/example',
disabled: true
},
slots: {
default: 'This is a link'
},
global: {
plugins: [router]
}
})
expect(wrapper.find('span').exists()).toBe(true)
})
it('should render link href', async ({ expect }) => {
const wrapper = mount(FzLink, {
props: {
to: '/example'
},
slots: {
default: 'This is a link'
},
global: {
plugins: [router]
}
})
expect(wrapper.find('a').attributes('href')).toBe('/example')
})
it('should render text content', async ({ expect }) => {
const wrapper = mount(FzLink, {
props: {
to: '/example'
},
slots: {
default: 'This is a link'
},
global: {
plugins: [router]
}
})
expect(wrapper.html()).toContain('This is a link')
})
it('should render size', async ({ expect }) => {
const wrapper = mount(FzLink, {
props: {
size: 'md',
to: '/example'
},
slots: {
default: 'This is a link'
},
global: {
plugins: [router]
}
})
expect(wrapper.find('a').classes()).toContain('text-md')
})
it('should render target', async ({ expect }) => {
const wrapper = mount(FzLink, {
props: {
size: 'md',
to: '/example',
target: '_blank'
},
slots: {
default: 'This is a link'
},
global: {
plugins: [router]
}
})
expect(wrapper.find('a').attributes().target).toBe('_blank')
})
})

Sorry, the diff of this file is not supported yet