diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fc53e3..84672a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # CHANGELOG +## v1.0.13 (2026-05-26) + +### 新特性 +- **双重计算指令 ($$)**: 引入了 `$$` 前缀指令支持(如 `$$if`, `$$each`, `$$text` 等)。该指令会先评估表达式获取字符串结果,随后将该字符串作为代码进行二次评估。这为高度动态的界面逻辑(如由后端下发的动态指令字符串)提供了原生支持。 + +### 优化 +- **指令扫描系统**: + - **严谨性增强**: 在 `_scanTree` 中引入了显式的 `triggerAttrs` 与 `eachAttrs` 匹配机制,彻底杜绝了以往基于字符串模糊匹配可能导致的误判风险。 + - **上下文传播**: 修复并增强了自动创建的 `TEMPLATE` 包装节点的上下文(`_thisObj`, `_ref`)传播逻辑,确保动态生成的 DOM 片段能完美继承父级作用域。 + - **端口修复**: 修复了自动化测试脚本中的端口配置冲突,确保 CI/CD 流程的稳定性。 + ## v1.0.12 (2026-05-25) ### 修复 diff --git a/README.md b/README.md index b51cba2..f81fb00 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ State.js 是基于 Vanilla JS 的无编译数据驱动 UI 框架。依赖原生 | 语法 | 类型 | 解析与执行规则 | | :--- | :--- | :--- | | `$attr="exp"` | 动态 JS 表达式 | 映射为 HTML 属性。若包含 `\${}`,作为模板字符串计算;若不包含,作为纯 JS 表达式执行并保留返回值类型。 | +| `$$attr="exp"` | 双重计算表达式 | **二次评估机制**:首先评估 `exp` 获取字符串结果,若结果为字符串,则将其作为代码再次执行。适用于动态指令场景(如后端下发指令)。 | | `.prop="val"` | 静态 DOM 属性 | 将字符串字面量赋值给底层 DOM 节点的 JS 属性。 | | `$.prop="exp"` | 动态 DOM 属性 | 将 JS 执行结果赋值给底层 DOM 节点的 JS 属性。
**对象自动初始化**:对深层路径(如 `$.state.schema`)赋值时,若路径上的中间节点不存在,框架会自动生成空对象(`{}`)以防止报错。 | | `$bind="exp"` | 双向数据绑定 | 适用于原生表单元素及遵循 `$bind` 契约的组件(如 `` 的显示状态绑定、`` 的数据绑定)。 | diff --git a/TEST.md b/TEST.md index 5e78db9..d6af325 100644 --- a/TEST.md +++ b/TEST.md @@ -9,8 +9,8 @@ | 指标 | 耗时 (ms) | 备注 | | :--- | :--- | :--- | -| **首次渲染 (1000 items)** | 59.10 | 包含模板克隆、数据绑定及 DOM 插入 | -| **浅更新 (Shallow Update)** | 7.90 | 触发 Array 重新扫描,基于索引复用 DOM 节点 | +| **首次渲染 (1000 items)** | 160.00 | 包含模板克隆、数据绑定及 DOM 插入 | +| **浅更新 (Shallow Update)** | 40.00 | 触发 Array 重新扫描,基于索引复用 DOM 节点 | ## 核心架构设计决策 (Design Decisions) diff --git a/dist/state.js b/dist/state.js index 8a5763d..830a229 100644 --- a/dist/state.js +++ b/dist/state.js @@ -198,7 +198,8 @@ if (typeof document !== "undefined") { const originalSetAttribute = Element.prototype.setAttribute; Element.prototype.setAttribute = function(name, value) { if (!name.startsWith("$")) return originalSetAttribute.call(this, name, value); - return originalSetAttribute.call(this, "st-" + name.substring(1), value); + const prefix = name.startsWith("$$") ? "st-st-" : "st-"; + return originalSetAttribute.call(this, prefix + name.substring(name.startsWith("$$") ? 2 : 1), value); }; } } @@ -216,6 +217,12 @@ function _updateBinding(binding) { if (window.__perfTrace) window.__perfTrace.evalCount++; const evalStart = window.__perfTrace ? performance.now() : 0; let result = binding.exp ? binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null : binding.tpl; + if (binding.exp === 2 && typeof result === "string") { + try { + result = _returnCode(result, { thisNode: node }, node._thisObj || node, node._ref || null); + } catch (e) { + } + } if (window.__perfTrace) window.__perfTrace.evalTotal += performance.now() - evalStart; setActiveBinding(null); if (binding.prop) { @@ -406,18 +413,21 @@ const _parseNode = (node, scanObj) => { } if (hasBindings) return; let attrs = []; + const triggerAttrs = ["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"]; if (node.tagName === "TEMPLATE") { - ["$if", "$each", "st-if", "st-each"].forEach((n) => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n))); + triggerAttrs.forEach((n) => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n))); } else { - attrs = Array.from(node.attributes).filter((a) => (a.name.startsWith("$") || a.name.startsWith("st-")) && !["$if", "$each", "st-if", "st-each"].includes(a.name) || a.name.includes(".")); + attrs = Array.from(node.attributes).filter((a) => (a.name.startsWith("$") || a.name.startsWith("st-")) && !triggerAttrs.includes(a.name) || a.name.includes(".")); } if (node._thisObj && scanObj.thisObj) node._thisObj.parent = scanObj.thisObj; if (!node._thisObj) node._thisObj = scanObj.thisObj || null; if (!node._ref) node._ref = scanObj.extendVars || {}; node._states = /* @__PURE__ */ new Set(); attrs.forEach((attr) => { - const exp = attr.name.startsWith("$") || attr.name.startsWith("st-"); - const realAttrName = exp ? attr.name.slice(attr.name.startsWith("$") ? 1 : 3) : attr.name; + let exp = 0; + if (attr.name.startsWith("$$") || attr.name.startsWith("st-st-")) exp = 2; + else if (attr.name.startsWith("$") || attr.name.startsWith("st-")) exp = 1; + const realAttrName = exp === 2 ? attr.name.startsWith("$$") ? attr.name.slice(2) : attr.name.slice(6) : exp === 1 ? attr.name.startsWith("$") ? attr.name.slice(1) : attr.name.slice(3) : attr.name; let tpl = attr.value; node.removeAttribute(attr.name); if (realAttrName.startsWith(".")) _initBinding({ node, prop: realAttrName.split("."), tpl, exp }); @@ -482,9 +492,11 @@ const _scanTree = (node, scanObj = {}) => { } if (resolvedThisObj === void 0) resolvedThisObj = scanObj.thisObj; if (resolvedRef === void 0) resolvedRef = scanObj.extendVars; - if (node.tagName !== "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("$each") || node.hasAttribute("st-if") || node.hasAttribute("st-each"))) { + const triggerAttrs = ["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"]; + const eachAttrs = ["$each", "st-each", "$$each", "st-st-each"]; + if (node.tagName !== "TEMPLATE" && triggerAttrs.some((t) => node.hasAttribute(t))) { const template = document.createElement("TEMPLATE"); - const attrs = Array.from(node.attributes).filter((attr) => ["$if", "$each", "st-if", "st-each"].includes(attr.name) || (node.hasAttribute("$each") || node.hasAttribute("st-each")) && ["as", "index"].includes(attr.name)); + const attrs = Array.from(node.attributes).filter((attr) => triggerAttrs.includes(attr.name) || eachAttrs.some((t) => node.hasAttribute(t)) && ["as", "index"].includes(attr.name)); attrs.forEach((attr) => { template.setAttribute(attr.name, attr.value); node.removeAttribute(attr.name); @@ -496,13 +508,13 @@ const _scanTree = (node, scanObj = {}) => { _scanTree(template, scanObj); return; } - if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each"))) { + if (node.tagName === "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("st-if") || node.hasAttribute("$$if") || node.hasAttribute("st-st-if")) && (node.hasAttribute("$each") || node.hasAttribute("st-each") || node.hasAttribute("$$each") || node.hasAttribute("st-st-each"))) { const template = document.createElement("TEMPLATE"); - const attrs = Array.from(node.attributes).filter((attr2) => ["$if", "$each", "st-if", "st-each"].includes(attr2.name)); + const attrs = Array.from(node.attributes).filter((attr2) => triggerAttrs.includes(attr2.name)); const attr = attrs[attrs.length - 1]; template.setAttribute(attr.name, attr.value); node.removeAttribute(attr.name); - if (attr.name === "$each" || attr.name === "st-each") { + if (eachAttrs.includes(attr.name)) { Array.from(node.attributes).filter((attr2) => ["as", "index"].includes(attr2.name)).forEach((attr2) => { template.setAttribute(attr2.name, attr2.value); node.removeAttribute(attr2.name); diff --git a/dist/state.min.js b/dist/state.min.js index 163b849..a13ddc9 100644 --- a/dist/state.min.js +++ b/dist/state.min.js @@ -1 +1 @@ -var e;let t=null,n=null;const r=e=>t=e,a=e=>n=e,s=new Set;function o(e={},r=null,a=null){const o={},i=new Map,d=new Map,l=(e,t)=>(d.has(e)||d.set(e,new Set),t?d.get(e).add(t):d.get(e).clear(),()=>d.get(e).delete(t)),c=(e,t)=>{d.has(e)&&d.get(e).delete(t)},h=r||(e=>o[e]),u=a||((e,t)=>o[e]=t);return Object.assign(o,e),new Proxy(o,{get:(e,n)=>"__watch"===n?l:"__unwatch"===n?c:"__isProxy"===n||(t&&(i.has(n)||i.set(n,new Set),i.get(n).add(t),t.node._states||(t.node._states=new Set),t.node._states.add(i)),h(n)),set(e,t,r){if(h(t)!==r&&u(t,r),d.has(t)&&d.get(t).forEach(n=>{const a=n(r);void 0!==a&&(r=a,e[t]=r)}),d.has(null)&&d.get(null).forEach(e=>e(r)),i.has(t)){const e=i.get(t);for(const t of e)t.node.isConnected?n!==t.node&&s.forEach(e=>e(t)):e.delete(t)}return!0}})}const i=(e,t)=>t?e.querySelector(t):document.querySelector(e),d=(e,t)=>t?e.querySelectorAll(t):document.querySelectorAll(e),l=new Map,c=[],h={getTemplate:e=>document.querySelector(`template[component="${e.toUpperCase()}"]`),register:(e,t,n=null,...r)=>{l.set(e.toUpperCase(),t),"loading"!==document.readyState?h._addTemplate(e,n,r):c.push([e,n,r])},exists:e=>l.has(e.toUpperCase()),getSetupFunction:e=>l.get(e.toUpperCase()),_addTemplate:(e,t,n)=>{if(t){const n=document.createElement("TEMPLATE");n.setAttribute("component",e.toUpperCase()),n.content.appendChild(t),document.body.appendChild(n)}n&&n.forEach(e=>document.body.appendChild(e))},_initPending:()=>{c.forEach(([e,t,n])=>h._addTemplate(e,t,n)),c.length=0}};function u(e,t,n,r={}){e.attributes&&Array.from(e.attributes).forEach(e=>{"class"!==e.name&&("style"===e.name?t.hasAttribute("style")?t.setAttribute("style",`${e.value}; ${t.getAttribute("style")}`):t.setAttribute("style",e.value):t.hasAttribute(e.name)||t.setAttribute(e.name,e.value))}),t.classList.add(...e.classList);const a="TEMPLATE"===e.tagName?e.content:e,s="TEMPLATE"===t.tagName?t.content:t;Array.from(a.childNodes).forEach(e=>s.appendChild(e)),e.tagName&&h.exists(e.tagName)&&f(e.tagName,t,n,r)}function f(e,t,n,r={}){if(r[e])return;r[e]=!0,n.thisObj&&Array.from(t.attributes).forEach(e=>{(e.name.startsWith("$")||e.name.startsWith("st-"))&&e.value.includes("this.")&&(e.value=e.value.replace(/\bthis\./g,"this.parent."))});const a=h.getSetupFunction(e),s={};Array.from(t.childNodes).forEach(e=>{e.nodeType===Node.ELEMENT_NODE&&e.hasAttribute("slot")&&(s[e.getAttribute("slot")]=e,e.removeAttribute("slot"))}),t.innerHTML="",t.state=o(t.state||{});const i=h.getTemplate(e);if(i){const e=i.content.cloneNode(!0);if(e.childNodes.length){const a=Array.from(e.childNodes).find(e=>e.nodeType===Node.ELEMENT_NODE);a&&u(a,t,n,r),d(t,"[slot-id]").forEach(e=>{const t=e.getAttribute("slot-id");s[t]&&(e.removeAttribute("slot-id"),e.innerHTML="",u(s[t],e,n,r))})}}a&&a(t)}let b=!1;function m(e){b=e}const p=new Map;function _(e,t,n,r){const a={...r||{},...t||{}},s=Object.keys(a),o=Object.values(a),i=e+s.join(",");try{let t=p.get(i);return t||(t=new Function("Hash","LocalStorage","State",...s,e),p.set(i,t)),t.apply(n,[globalThis.Hash,globalThis.LocalStorage,globalThis.State,...o])}catch(a){return b||console.error(a,r,[e,r,t,n]),null}}function g(e,t,n,r){return e.includes("${")?_("return `"+e+"`",t,n,r):_("return "+e,t,n,r)}let v=(e,t)=>e&&"string"==typeof e?e.replace(/\{(.+?)\}/g,(e,n)=>t.hasOwnProperty(n)?t[n]:e):e;const E=e=>v=e,y=e=>e&&"string"==typeof e&&e.includes("{#")?e.replace(/\{#(.+?)#\}/g,(e,t)=>{const n=t.split("||").map(e=>e.trim()),r={};if(n.length>1){const e=n[0].match(/\{(.+?)\}/g);e&&e.forEach((e,t)=>r[e.substring(1,e.length-1)]=n[t+1]||"")}return v(n[0],r)}):e;if("undefined"!=typeof document)try{document.createElement("div").setAttribute("$t","1")}catch(e){const t=Element.prototype.setAttribute;Element.prototype.setAttribute=function(e,n){return e.startsWith("$")?t.call(this,"st-"+e.substring(1),n):t.call(this,e,n)}}var A;function N(e){e._renderedNodes&&e._renderedNodes.forEach(e=>e.forEach(e=>{e.remove(),e._renderedNodes&&N(e)}))}function T(e){const t=e.node;if(!t.isConnected&&"TEMPLATE"!==t.tagName)return;r(e),window.__perfTrace&&window.__perfTrace.evalCount++;const n=window.__perfTrace?performance.now():0;let a=e.exp?e.tpl?g(e.tpl,{thisNode:t},t._thisObj||t,t._ref||null):null:e.tpl;if(window.__perfTrace&&(window.__perfTrace.evalTotal+=performance.now()-n),r(null),e.prop){const n=e.prop;let r=t;for(let e=0;eO(e,{thisObj:t._thisObj,extendVars:e._ref})):(t._children.forEach(e=>{e._stManaged=!0,t.parentNode.insertBefore(e,t),e._ref={...t._ref}}),t._renderedNodes=[t._children]):(N(t),t._renderedNodes=[]);else if("each"===n)if(a&&"object"==typeof a){const e=t.getAttribute("as")||"item",n=t.getAttribute("index")||"index",r=t.getAttribute("key");let s,o;if(a instanceof Map)s=Array.from(a.keys()),o=e=>a.get(e);else if("function"==typeof a[Symbol.iterator]){const e=Array.isArray(a)?a:Array.from(a);s=new Array(e.length);for(let t=0;te[t]}else s=Object.keys(a),o=e=>a[e];t._keyedNodes||(t._keyedNodes=new Map);const i=new Map,d=[];s.forEach((a,s)=>{const l=o(a),c=r?l&&"object"==typeof l?l[r]:l:a,h=null==c||i.has(c)?`st_key_fallback_${s}_${Math.random()}`:c;let u=t._keyedNodes.get(h);u?(t._keyedNodes.delete(h),u.forEach(r=>{window.__statePerformanceTelemetry&&window.__statePerformanceTelemetry.reuseCount++;let s=!1;for(let a in t._ref)a!==e&&a!==n&&r._ref[a]!==t._ref[a]&&(r._ref[a]=t._ref[a],s=!0);r._ref[n]!==a&&(r._ref[n]=a),r._ref[e]!==l||s?(r._ref[e]=l,window.__statePerformanceTelemetry&&window.__statePerformanceTelemetry.scanCount++,O(r,{thisObj:t._thisObj,extendVars:r._ref})):t.parentNode.lastChild!==r&&(window.__statePerformanceTelemetry&&window.__statePerformanceTelemetry.moveCount++,t.parentNode.insertBefore(r,t))})):(u=[],t._children.forEach(r=>{const s=r.cloneNode(!0);s._stManaged=!0,s._ref={...t._ref,[n]:a,[e]:l},s._thisObj=t._thisObj,t.parentNode.insertBefore(s,t),u.push(s)})),i.set(h,u),d.push(u),u.forEach(e=>t.parentNode.insertBefore(e,t))}),t._keyedNodes.forEach(e=>e.forEach(e=>{N(e),e.remove()})),t._keyedNodes=i,t._renderedNodes=d}else N(t),t._keyedNodes&&t._keyedNodes.forEach(e=>e.forEach(e=>e.remove())),t._keyedNodes=new Map,t._renderedNodes=[];else if("bind"===n){if(["INPUT","SELECT","TEXTAREA"].includes(t.tagName)&&!t.hasAttribute("autocomplete")&&t.setAttribute("autocomplete","off"),"checkbox"===t.type){"on"===t.value||a||(_(`${e.tpl} = []`,{thisNode:t},t._thisObj||t,t._ref||{}),a=[]),t._checkboxMultiMode=a instanceof Array;const n=a instanceof Array?a.includes(t.value):!!a;t.checked!==n&&(t.checked=n)}else"radio"===t.type?t.checked!==(t.value===String(a??""))&&(t.checked=t.value===String(a??"")):"value"in t&&"file"!==t.type?setTimeout(()=>{t.value!==String(a??"")&&(t.value=a)}):t.isContentEditable&&t.innerHTML!==String(a??"")&&(t.innerHTML=a);t.dispatchEvent(new CustomEvent("bind",{bubbles:!1,detail:a}))}else["checked","disabled","readonly"].includes(n)&&(a=!!a),"boolean"==typeof a?a?t.setAttribute(n,""):t.removeAttribute(n):void 0!==a&&("string"!=typeof a&&(a=JSON.stringify(a)),"text"===n?t.textContent=a??"":"html"===n?t.innerHTML=a??"":"IMG"===t.tagName&&"src"===n&&a.includes(".svg")?t.setAttribute("_src",a??""):t.setAttribute(n,a??""))}}A=e=>T(e),s.add(A);const w=e=>{e.node._bindings||(e.node._bindings=[]),e.node._bindings.push({attr:e.attr,prop:e.prop,tpl:e.tpl,exp:e.exp}),T(e)},O=(e,t={})=>{if(3===e.nodeType){if(e._stTranslated)return;const t=y(e.textContent);return t!==e.textContent&&(e.textContent=t),void(e._stTranslated=!0)}if(1!==e.nodeType)return;e._stTranslated||(Array.from(e.attributes).forEach(e=>{if(!e.name.startsWith("$")&&!e.name.startsWith("st-")&&!e.name.startsWith(".")){const t=y(e.value);t!==e.value&&(e.value=t)}}),e._stTranslated=!0);let n=e._thisObj,r=e._ref;if(void 0===n||void 0===r){let t=e;for(;t&&(void 0===n||void 0===r);)void 0===n&&void 0!==t._thisObj&&(n=t._thisObj),void 0===r&&void 0!==t._ref&&(r={...t._ref}),t=t.parentNode}if(void 0===n&&(n=t.thisObj),void 0===r&&(r=t.extendVars),"TEMPLATE"!==e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("$each")||e.hasAttribute("st-if")||e.hasAttribute("st-each"))){const a=document.createElement("TEMPLATE");return Array.from(e.attributes).filter(t=>["$if","$each","st-if","st-each"].includes(t.name)||(e.hasAttribute("$each")||e.hasAttribute("st-each"))&&["as","index"].includes(t.name)).forEach(t=>{a.setAttribute(t.name,t.value),e.removeAttribute(t.name)}),e.parentNode.insertBefore(a,e),a.content.appendChild(e),a._ref=r,a._thisObj=n,void O(a,t)}if("TEMPLATE"===e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("st-if"))&&(e.hasAttribute("$each")||e.hasAttribute("st-each"))){const t=document.createElement("TEMPLATE"),a=Array.from(e.attributes).filter(e=>["$if","$each","st-if","st-each"].includes(e.name)),s=a[a.length-1];t.setAttribute(s.name,s.value),e.removeAttribute(s.name),"$each"!==s.name&&"st-each"!==s.name||Array.from(e.attributes).filter(e=>["as","index"].includes(e.name)).forEach(n=>{t.setAttribute(n.name,n.value),e.removeAttribute(n.name)}),Array.from(e.content.childNodes).forEach(e=>t.content.appendChild(e)),e.content.appendChild(t),t._ref=r,t._thisObj=n}if("IMG"===e.tagName&&(e.hasAttribute("src")||e.hasAttribute("_src")||e.hasAttribute("$src"))){const t=e;Promise.resolve().then(()=>{const e=t.getAttribute("_src")||t.getAttribute("src");e&&fetch(e,{cache:"force-cache"}).then(e=>e.text()).then(e=>{const n=(new DOMParser).parseFromString(e,"image/svg+xml").querySelector("svg");n&&(Array.from(t.attributes).forEach(e=>n.setAttribute(e.name,e.value)),t.replaceWith(n))})})}if(void 0!==e._thisObj)t.thisObj=e._thisObj||null;else{let n=e;for(;n&&void 0===n._thisObj;)n=n.parentNode;t.thisObj=n?n._thisObj:null}if(void 0===e._ref){let t=e;for(;t&&void 0===t._ref;)t=t.parentNode;e._ref=t?{...t._ref}:{}}void 0!==e._refExt&&Object.assign(e._ref,e._refExt),t.extendVars&&Object.assign(e._ref,t.extendVars),((e,t)=>{let n=!1;if(e._bindings&&(e._states=new Set,e._bindings.forEach(t=>T({node:e,...t})),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),n=!0),h.exists(e.tagName)&&!e._componentInitialized&&(Array.from(e.attributes).forEach(n=>{var r;if(n.name.startsWith("$.")){const a=n.name.slice(2);let s=y(n.value);s.includes("this.")&&(s=s.replace(/\bthis\./g,"this.parent."));const o=g(s,{thisNode:e},{parent:t.thisObj||e},e._ref||{});let i=e;const d=a.split(".");for(let e=0;ee.removeAttribute("slot-id")),e._componentInitialized=!0,e._thisObj||(e._thisObj=e)),"TEMPLATE"===e.tagName&&(e._children=[...e.content.childNodes],e._renderedNodes||(e._renderedNodes=[])),n)return;let r=[];"TEMPLATE"===e.tagName?["$if","$each","st-if","st-each"].forEach(t=>e.hasAttribute(t)&&r.push(e.getAttributeNode(t))):r=Array.from(e.attributes).filter(e=>(e.name.startsWith("$")||e.name.startsWith("st-"))&&!["$if","$each","st-if","st-each"].includes(e.name)||e.name.includes(".")),e._thisObj&&t.thisObj&&(e._thisObj.parent=t.thisObj),e._thisObj||(e._thisObj=t.thisObj||null),e._ref||(e._ref=t.extendVars||{}),e._states=new Set,r.forEach(n=>{const r=n.name.startsWith("$")||n.name.startsWith("st-"),s=r?n.name.slice(n.name.startsWith("$")?1:3):n.name;let o=n.value;if(e.removeAttribute(n.name),s.startsWith("."))w({node:e,prop:s.split("."),tpl:o,exp:r});else if(s.startsWith("on")){const n=s.slice(2);"update"===n&&(e._hasOnUpdate=!0),"load"!==n||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnLoad=!0),"unload"!==n||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnUnload=!0),e.addEventListener(n,n=>_(o,{event:n,thisNode:e,...n.detail||{}},t.thisObj||e,e._ref||{}))}else"bind"===s?e.addEventListener("TEXTAREA"===e.tagName||e.isContentEditable||"text"===e.type||"password"===e.type?"input":"change",n=>{let r=e.isContentEditable?n.target.innerHTML:"checkbox"===e.type?n.target.checked:n.target.files||n.target.value||n.detail;a(e),m(!0),"checkbox"===e.type&&e._checkboxMultiMode?_(`!!checked ? (!${o}.includes(val) && ${o}.push(val)) : (index = ${o}.indexOf(val), index > -1 && ${o}.splice(index, 1))`,{val:e.value,checked:r,thisNode:e},t.thisObj||e,e._ref||{}):_(`${o} = val`,{val:r,thisNode:e},t.thisObj||e,e._ref||{}),m(!1),a(null)}):"text"!==s||o||(o=e.textContent,e.textContent=""),o&&(o=y(o),w({node:e,attr:s,tpl:o,exp:r}))}),(e._hasOnLoad||e._componentInitialized)&&Promise.resolve().then(()=>e.dispatchEvent(new Event("load",{bubbles:!1}))),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),e._thisObj&&(t.thisObj=e._thisObj)})(e,{...t});const s=[...e.childNodes||[]],o={thisObj:t.thisObj,extendVars:{...e._ref}};s.forEach(e=>{e._stManaged||O(e,o)})},j=e=>{1===e.nodeType&&(e._hasOnUnload&&e.dispatchEvent(new Event("unload",{bubbles:!1})),e._states&&e._states.forEach(t=>{for(const[n,r]of t)for(const t of r)t.node===e&&r.delete(t)}),e.childNodes&&e.childNodes.forEach(e=>j(e)))},x=O,M={clone:window.structuredClone||(e=>JSON.parse(JSON.stringify(e))),base64:e=>btoa(String.fromCharCode(...(new TextEncoder).encode(e))),unbase64:e=>(new TextDecoder).decode(Uint8Array.from(atob(e),e=>e.charCodeAt(0))),urlbase64:e=>M.base64(e).replace(/[+/=]/g,e=>({"+":"-","/":"","=":""}[e])),unurlbase64:e=>M.unbase64(e.replace(/[-_.]/g,e=>({"-":"+",_:"/",".":"="}[e])).padEnd(4*Math.ceil(e.length/4),"=")),safeJson:e=>{try{return JSON.parse(e)}catch{return null}},updateDefaults:(e,t)=>{for(const n in t)void 0===e[n]&&(e[n]=t[n])},copyFunction:(e,t,...n)=>{n.forEach(n=>e[n]=t[n].bind(t))},getFunctionBody:e=>{const t=e.toString();return t.slice(t.indexOf("{")+1,t.lastIndexOf("}")).trim()},makeDom:e=>{e.includes(">\n")&&(e=e.replace(/>\s+<").trim());const t=document.createElement("div");return t.innerHTML=e,t.children[0]},newAvg:()=>{let e=0,t=0,n=0;return{add:r=>(e+=r,t++,n=e/t),get:()=>n,clear:()=>{e=0,t=0,n=0}}},newTimeCount:()=>{let e=0,t=0,n=0;return{start:()=>e=(new Date).getTime(),end:()=>{const r=(new Date).getTime(),a=r-e;return e=r,t+=a,n++,a},avg:()=>t/n}}};globalThis.Util=M;let S=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||"");const C=o({},e=>M.safeJson(S.get(e)),(e,t)=>{const n=S.get(e),r=void 0===t?void 0:JSON.stringify(t);n===r||null===n&&void 0===r||(void 0===t?S.delete(e):S.set(e,r),window.location.hash="#"+S.toString())});"undefined"!=typeof window&&window.addEventListener("hashchange",()=>{var e;const t=S;S=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||""),S.forEach((e,n)=>{t.get(n)!==e&&(C[n]=M.safeJson(e))}),t.forEach((e,t)=>{void 0===S.get(t)&&(C[t]=void 0)})});const L=o({},e=>M.safeJson(localStorage.getItem(e)),(e,t)=>{const n=localStorage.getItem(e),r=void 0===t?void 0:JSON.stringify(t);n===r||null===n&&void 0===r||(void 0===t?localStorage.removeItem(e):localStorage.setItem(e,r))});if(globalThis.Hash=C,globalThis.LocalStorage=L,"undefined"!=typeof document){const e=()=>{h._initPending(),new MutationObserver(e=>{e.forEach(e=>{e.addedNodes.forEach(e=>{e.isConnected&&O(e)}),e.removedNodes.forEach(e=>j(e))})}).observe(document.documentElement,{childList:!0,subtree:!0}),O(document.documentElement)};"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,!0)}export{i as $,d as $$,h as Component,C as Hash,L as LocalStorage,o as NewState,x as RefreshState,E as SetTranslator,M as Util,O as _scanTree,j as _unbindTree}; +var e;let t=null,n=null;const r=e=>t=e,s=e=>n=e,a=new Set;function o(e={},r=null,s=null){const o={},i=new Map,d=new Map,c=(e,t)=>(d.has(e)||d.set(e,new Set),t?d.get(e).add(t):d.get(e).clear(),()=>d.get(e).delete(t)),l=(e,t)=>{d.has(e)&&d.get(e).delete(t)},h=r||(e=>o[e]),u=s||((e,t)=>o[e]=t);return Object.assign(o,e),new Proxy(o,{get:(e,n)=>"__watch"===n?c:"__unwatch"===n?l:"__isProxy"===n||(t&&(i.has(n)||i.set(n,new Set),i.get(n).add(t),t.node._states||(t.node._states=new Set),t.node._states.add(i)),h(n)),set(e,t,r){if(h(t)!==r&&u(t,r),d.has(t)&&d.get(t).forEach(n=>{const s=n(r);void 0!==s&&(r=s,e[t]=r)}),d.has(null)&&d.get(null).forEach(e=>e(r)),i.has(t)){const e=i.get(t);for(const t of e)t.node.isConnected?n!==t.node&&a.forEach(e=>e(t)):e.delete(t)}return!0}})}const i=(e,t)=>t?e.querySelector(t):document.querySelector(e),d=(e,t)=>t?e.querySelectorAll(t):document.querySelectorAll(e),c=new Map,l=[],h={getTemplate:e=>document.querySelector(`template[component="${e.toUpperCase()}"]`),register:(e,t,n=null,...r)=>{c.set(e.toUpperCase(),t),"loading"!==document.readyState?h._addTemplate(e,n,r):l.push([e,n,r])},exists:e=>c.has(e.toUpperCase()),getSetupFunction:e=>c.get(e.toUpperCase()),_addTemplate:(e,t,n)=>{if(t){const n=document.createElement("TEMPLATE");n.setAttribute("component",e.toUpperCase()),n.content.appendChild(t),document.body.appendChild(n)}n&&n.forEach(e=>document.body.appendChild(e))},_initPending:()=>{l.forEach(([e,t,n])=>h._addTemplate(e,t,n)),l.length=0}};function u(e,t,n,r={}){e.attributes&&Array.from(e.attributes).forEach(e=>{"class"!==e.name&&("style"===e.name?t.hasAttribute("style")?t.setAttribute("style",`${e.value}; ${t.getAttribute("style")}`):t.setAttribute("style",e.value):t.hasAttribute(e.name)||t.setAttribute(e.name,e.value))}),t.classList.add(...e.classList);const s="TEMPLATE"===e.tagName?e.content:e,a="TEMPLATE"===t.tagName?t.content:t;Array.from(s.childNodes).forEach(e=>a.appendChild(e)),e.tagName&&h.exists(e.tagName)&&f(e.tagName,t,n,r)}function f(e,t,n,r={}){if(r[e])return;r[e]=!0,n.thisObj&&Array.from(t.attributes).forEach(e=>{(e.name.startsWith("$")||e.name.startsWith("st-"))&&e.value.includes("this.")&&(e.value=e.value.replace(/\bthis\./g,"this.parent."))});const s=h.getSetupFunction(e),a={};Array.from(t.childNodes).forEach(e=>{e.nodeType===Node.ELEMENT_NODE&&e.hasAttribute("slot")&&(a[e.getAttribute("slot")]=e,e.removeAttribute("slot"))}),t.innerHTML="",t.state=o(t.state||{});const i=h.getTemplate(e);if(i){const e=i.content.cloneNode(!0);if(e.childNodes.length){const s=Array.from(e.childNodes).find(e=>e.nodeType===Node.ELEMENT_NODE);s&&u(s,t,n,r),d(t,"[slot-id]").forEach(e=>{const t=e.getAttribute("slot-id");a[t]&&(e.removeAttribute("slot-id"),e.innerHTML="",u(a[t],e,n,r))})}}s&&s(t)}let b=!1;function m(e){b=e}const p=new Map;function _(e,t,n,r){const s={...r||{},...t||{}},a=Object.keys(s),o=Object.values(s),i=e+a.join(",");try{let t=p.get(i);return t||(t=new Function("Hash","LocalStorage","State",...a,e),p.set(i,t)),t.apply(n,[globalThis.Hash,globalThis.LocalStorage,globalThis.State,...o])}catch(s){return b||console.error(s,r,[e,r,t,n]),null}}function g(e,t,n,r){return e.includes("${")?_("return `"+e+"`",t,n,r):_("return "+e,t,n,r)}let v=(e,t)=>e&&"string"==typeof e?e.replace(/\{(.+?)\}/g,(e,n)=>t.hasOwnProperty(n)?t[n]:e):e;const E=e=>v=e,y=e=>e&&"string"==typeof e&&e.includes("{#")?e.replace(/\{#(.+?)#\}/g,(e,t)=>{const n=t.split("||").map(e=>e.trim()),r={};if(n.length>1){const e=n[0].match(/\{(.+?)\}/g);e&&e.forEach((e,t)=>r[e.substring(1,e.length-1)]=n[t+1]||"")}return v(n[0],r)}):e;if("undefined"!=typeof document)try{document.createElement("div").setAttribute("$t","1")}catch(e){const t=Element.prototype.setAttribute;Element.prototype.setAttribute=function(e,n){if(!e.startsWith("$"))return t.call(this,e,n);const r=e.startsWith("$$")?"st-st-":"st-";return t.call(this,r+e.substring(e.startsWith("$$")?2:1),n)}}var A;function N(e){e._renderedNodes&&e._renderedNodes.forEach(e=>e.forEach(e=>{e.remove(),e._renderedNodes&&N(e)}))}function T(e){const t=e.node;if(!t.isConnected&&"TEMPLATE"!==t.tagName)return;r(e),window.__perfTrace&&window.__perfTrace.evalCount++;const n=window.__perfTrace?performance.now():0;let s=e.exp?e.tpl?g(e.tpl,{thisNode:t},t._thisObj||t,t._ref||null):null:e.tpl;if(2===e.exp&&"string"==typeof s)try{s=g(s,{thisNode:t},t._thisObj||t,t._ref||null)}catch(e){}if(window.__perfTrace&&(window.__perfTrace.evalTotal+=performance.now()-n),r(null),e.prop){const n=e.prop;let r=t;for(let e=0;eO(e,{thisObj:t._thisObj,extendVars:e._ref})):(t._children.forEach(e=>{e._stManaged=!0,t.parentNode.insertBefore(e,t),e._ref={...t._ref}}),t._renderedNodes=[t._children]):(N(t),t._renderedNodes=[]);else if("each"===n)if(s&&"object"==typeof s){const e=t.getAttribute("as")||"item",n=t.getAttribute("index")||"index",r=t.getAttribute("key");let a,o;if(s instanceof Map)a=Array.from(s.keys()),o=e=>s.get(e);else if("function"==typeof s[Symbol.iterator]){const e=Array.isArray(s)?s:Array.from(s);a=new Array(e.length);for(let t=0;te[t]}else a=Object.keys(s),o=e=>s[e];t._keyedNodes||(t._keyedNodes=new Map);const i=new Map,d=[];a.forEach((s,a)=>{const c=o(s),l=r?c&&"object"==typeof c?c[r]:c:s,h=null==l||i.has(l)?`st_key_fallback_${a}_${Math.random()}`:l;let u=t._keyedNodes.get(h);u?(t._keyedNodes.delete(h),u.forEach(r=>{window.__statePerformanceTelemetry&&window.__statePerformanceTelemetry.reuseCount++;let a=!1;for(let s in t._ref)s!==e&&s!==n&&r._ref[s]!==t._ref[s]&&(r._ref[s]=t._ref[s],a=!0);r._ref[n]!==s&&(r._ref[n]=s),r._ref[e]!==c||a?(r._ref[e]=c,window.__statePerformanceTelemetry&&window.__statePerformanceTelemetry.scanCount++,O(r,{thisObj:t._thisObj,extendVars:r._ref})):t.parentNode.lastChild!==r&&(window.__statePerformanceTelemetry&&window.__statePerformanceTelemetry.moveCount++,t.parentNode.insertBefore(r,t))})):(u=[],t._children.forEach(r=>{const a=r.cloneNode(!0);a._stManaged=!0,a._ref={...t._ref,[n]:s,[e]:c},a._thisObj=t._thisObj,t.parentNode.insertBefore(a,t),u.push(a)})),i.set(h,u),d.push(u),u.forEach(e=>t.parentNode.insertBefore(e,t))}),t._keyedNodes.forEach(e=>e.forEach(e=>{N(e),e.remove()})),t._keyedNodes=i,t._renderedNodes=d}else N(t),t._keyedNodes&&t._keyedNodes.forEach(e=>e.forEach(e=>e.remove())),t._keyedNodes=new Map,t._renderedNodes=[];else if("bind"===n){if(["INPUT","SELECT","TEXTAREA"].includes(t.tagName)&&!t.hasAttribute("autocomplete")&&t.setAttribute("autocomplete","off"),"checkbox"===t.type){"on"===t.value||s||(_(`${e.tpl} = []`,{thisNode:t},t._thisObj||t,t._ref||{}),s=[]),t._checkboxMultiMode=s instanceof Array;const n=s instanceof Array?s.includes(t.value):!!s;t.checked!==n&&(t.checked=n)}else"radio"===t.type?t.checked!==(t.value===String(s??""))&&(t.checked=t.value===String(s??"")):"value"in t&&"file"!==t.type?setTimeout(()=>{t.value!==String(s??"")&&(t.value=s)}):t.isContentEditable&&t.innerHTML!==String(s??"")&&(t.innerHTML=s);t.dispatchEvent(new CustomEvent("bind",{bubbles:!1,detail:s}))}else["checked","disabled","readonly"].includes(n)&&(s=!!s),"boolean"==typeof s?s?t.setAttribute(n,""):t.removeAttribute(n):void 0!==s&&("string"!=typeof s&&(s=JSON.stringify(s)),"text"===n?t.textContent=s??"":"html"===n?t.innerHTML=s??"":"IMG"===t.tagName&&"src"===n&&s.includes(".svg")?t.setAttribute("_src",s??""):t.setAttribute(n,s??""))}}A=e=>T(e),a.add(A);const w=e=>{e.node._bindings||(e.node._bindings=[]),e.node._bindings.push({attr:e.attr,prop:e.prop,tpl:e.tpl,exp:e.exp}),T(e)},O=(e,t={})=>{if(3===e.nodeType){if(e._stTranslated)return;const t=y(e.textContent);return t!==e.textContent&&(e.textContent=t),void(e._stTranslated=!0)}if(1!==e.nodeType)return;e._stTranslated||(Array.from(e.attributes).forEach(e=>{if(!e.name.startsWith("$")&&!e.name.startsWith("st-")&&!e.name.startsWith(".")){const t=y(e.value);t!==e.value&&(e.value=t)}}),e._stTranslated=!0);let n=e._thisObj,r=e._ref;if(void 0===n||void 0===r){let t=e;for(;t&&(void 0===n||void 0===r);)void 0===n&&void 0!==t._thisObj&&(n=t._thisObj),void 0===r&&void 0!==t._ref&&(r={...t._ref}),t=t.parentNode}void 0===n&&(n=t.thisObj),void 0===r&&(r=t.extendVars);const a=["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"],o=["$each","st-each","$$each","st-st-each"];if("TEMPLATE"!==e.tagName&&a.some(t=>e.hasAttribute(t))){const s=document.createElement("TEMPLATE");return Array.from(e.attributes).filter(t=>a.includes(t.name)||o.some(t=>e.hasAttribute(t))&&["as","index"].includes(t.name)).forEach(t=>{s.setAttribute(t.name,t.value),e.removeAttribute(t.name)}),e.parentNode.insertBefore(s,e),s.content.appendChild(e),s._ref=r,s._thisObj=n,void O(s,t)}if("TEMPLATE"===e.tagName&&(e.hasAttribute("$if")||e.hasAttribute("st-if")||e.hasAttribute("$$if")||e.hasAttribute("st-st-if"))&&(e.hasAttribute("$each")||e.hasAttribute("st-each")||e.hasAttribute("$$each")||e.hasAttribute("st-st-each"))){const t=document.createElement("TEMPLATE"),s=Array.from(e.attributes).filter(e=>a.includes(e.name)),i=s[s.length-1];t.setAttribute(i.name,i.value),e.removeAttribute(i.name),o.includes(i.name)&&Array.from(e.attributes).filter(e=>["as","index"].includes(e.name)).forEach(n=>{t.setAttribute(n.name,n.value),e.removeAttribute(n.name)}),Array.from(e.content.childNodes).forEach(e=>t.content.appendChild(e)),e.content.appendChild(t),t._ref=r,t._thisObj=n}if("IMG"===e.tagName&&(e.hasAttribute("src")||e.hasAttribute("_src")||e.hasAttribute("$src"))){const t=e;Promise.resolve().then(()=>{const e=t.getAttribute("_src")||t.getAttribute("src");e&&fetch(e,{cache:"force-cache"}).then(e=>e.text()).then(e=>{const n=(new DOMParser).parseFromString(e,"image/svg+xml").querySelector("svg");n&&(Array.from(t.attributes).forEach(e=>n.setAttribute(e.name,e.value)),t.replaceWith(n))})})}if(void 0!==e._thisObj)t.thisObj=e._thisObj||null;else{let n=e;for(;n&&void 0===n._thisObj;)n=n.parentNode;t.thisObj=n?n._thisObj:null}if(void 0===e._ref){let t=e;for(;t&&void 0===t._ref;)t=t.parentNode;e._ref=t?{...t._ref}:{}}void 0!==e._refExt&&Object.assign(e._ref,e._refExt),t.extendVars&&Object.assign(e._ref,t.extendVars),((e,t)=>{let n=!1;if(e._bindings&&(e._states=new Set,e._bindings.forEach(t=>T({node:e,...t})),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),n=!0),h.exists(e.tagName)&&!e._componentInitialized&&(Array.from(e.attributes).forEach(n=>{var r;if(n.name.startsWith("$.")){const s=n.name.slice(2);let a=y(n.value);a.includes("this.")&&(a=a.replace(/\bthis\./g,"this.parent."));const o=g(a,{thisNode:e},{parent:t.thisObj||e},e._ref||{});let i=e;const d=s.split(".");for(let e=0;ee.removeAttribute("slot-id")),e._componentInitialized=!0,e._thisObj||(e._thisObj=e)),"TEMPLATE"===e.tagName&&(e._children=[...e.content.childNodes],e._renderedNodes||(e._renderedNodes=[])),n)return;let r=[];const a=["$if","$each","st-if","st-each","$$if","$$each","st-st-if","st-st-each"];"TEMPLATE"===e.tagName?a.forEach(t=>e.hasAttribute(t)&&r.push(e.getAttributeNode(t))):r=Array.from(e.attributes).filter(e=>(e.name.startsWith("$")||e.name.startsWith("st-"))&&!a.includes(e.name)||e.name.includes(".")),e._thisObj&&t.thisObj&&(e._thisObj.parent=t.thisObj),e._thisObj||(e._thisObj=t.thisObj||null),e._ref||(e._ref=t.extendVars||{}),e._states=new Set,r.forEach(n=>{let r=0;n.name.startsWith("$$")||n.name.startsWith("st-st-")?r=2:(n.name.startsWith("$")||n.name.startsWith("st-"))&&(r=1);const a=2===r?n.name.startsWith("$$")?n.name.slice(2):n.name.slice(6):1===r?n.name.startsWith("$")?n.name.slice(1):n.name.slice(3):n.name;let o=n.value;if(e.removeAttribute(n.name),a.startsWith("."))w({node:e,prop:a.split("."),tpl:o,exp:r});else if(a.startsWith("on")){const n=a.slice(2);"update"===n&&(e._hasOnUpdate=!0),"load"!==n||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnLoad=!0),"unload"!==n||["BODY","IMG","IFRAME"].includes(e.tagName)||(e._hasOnUnload=!0),e.addEventListener(n,n=>_(o,{event:n,thisNode:e,...n.detail||{}},t.thisObj||e,e._ref||{}))}else"bind"===a?e.addEventListener("TEXTAREA"===e.tagName||e.isContentEditable||"text"===e.type||"password"===e.type?"input":"change",n=>{let r=e.isContentEditable?n.target.innerHTML:"checkbox"===e.type?n.target.checked:n.target.files||n.target.value||n.detail;s(e),m(!0),"checkbox"===e.type&&e._checkboxMultiMode?_(`!!checked ? (!${o}.includes(val) && ${o}.push(val)) : (index = ${o}.indexOf(val), index > -1 && ${o}.splice(index, 1))`,{val:e.value,checked:r,thisNode:e},t.thisObj||e,e._ref||{}):_(`${o} = val`,{val:r,thisNode:e},t.thisObj||e,e._ref||{}),m(!1),s(null)}):"text"!==a||o||(o=e.textContent,e.textContent=""),o&&(o=y(o),w({node:e,attr:a,tpl:o,exp:r}))}),(e._hasOnLoad||e._componentInitialized)&&Promise.resolve().then(()=>e.dispatchEvent(new Event("load",{bubbles:!1}))),e._hasOnUpdate&&e.dispatchEvent(new Event("update",{bubbles:!1})),e._thisObj&&(t.thisObj=e._thisObj)})(e,{...t});const i=[...e.childNodes||[]],c={thisObj:t.thisObj,extendVars:{...e._ref}};i.forEach(e=>{e._stManaged||O(e,c)})},j=e=>{1===e.nodeType&&(e._hasOnUnload&&e.dispatchEvent(new Event("unload",{bubbles:!1})),e._states&&e._states.forEach(t=>{for(const[n,r]of t)for(const t of r)t.node===e&&r.delete(t)}),e.childNodes&&e.childNodes.forEach(e=>j(e)))},$=O,x={clone:window.structuredClone||(e=>JSON.parse(JSON.stringify(e))),base64:e=>btoa(String.fromCharCode(...(new TextEncoder).encode(e))),unbase64:e=>(new TextDecoder).decode(Uint8Array.from(atob(e),e=>e.charCodeAt(0))),urlbase64:e=>x.base64(e).replace(/[+/=]/g,e=>({"+":"-","/":"","=":""}[e])),unurlbase64:e=>x.unbase64(e.replace(/[-_.]/g,e=>({"-":"+",_:"/",".":"="}[e])).padEnd(4*Math.ceil(e.length/4),"=")),safeJson:e=>{try{return JSON.parse(e)}catch{return null}},updateDefaults:(e,t)=>{for(const n in t)void 0===e[n]&&(e[n]=t[n])},copyFunction:(e,t,...n)=>{n.forEach(n=>e[n]=t[n].bind(t))},getFunctionBody:e=>{const t=e.toString();return t.slice(t.indexOf("{")+1,t.lastIndexOf("}")).trim()},makeDom:e=>{e.includes(">\n")&&(e=e.replace(/>\s+<").trim());const t=document.createElement("div");return t.innerHTML=e,t.children[0]},newAvg:()=>{let e=0,t=0,n=0;return{add:r=>(e+=r,t++,n=e/t),get:()=>n,clear:()=>{e=0,t=0,n=0}}},newTimeCount:()=>{let e=0,t=0,n=0;return{start:()=>e=(new Date).getTime(),end:()=>{const r=(new Date).getTime(),s=r-e;return e=r,t+=s,n++,s},avg:()=>t/n}}};globalThis.Util=x;let M=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||"");const S=o({},e=>x.safeJson(M.get(e)),(e,t)=>{const n=M.get(e),r=void 0===t?void 0:JSON.stringify(t);n===r||null===n&&void 0===r||(void 0===t?M.delete(e):M.set(e,r),window.location.hash="#"+M.toString())});"undefined"!=typeof window&&window.addEventListener("hashchange",()=>{var e;const t=M;M=new URLSearchParams((null==(e=window.location.hash)?void 0:e.substring(1))||""),M.forEach((e,n)=>{t.get(n)!==e&&(S[n]=x.safeJson(e))}),t.forEach((e,t)=>{void 0===M.get(t)&&(S[t]=void 0)})});const C=o({},e=>x.safeJson(localStorage.getItem(e)),(e,t)=>{const n=localStorage.getItem(e),r=void 0===t?void 0:JSON.stringify(t);n===r||null===n&&void 0===r||(void 0===t?localStorage.removeItem(e):localStorage.setItem(e,r))});if(globalThis.Hash=S,globalThis.LocalStorage=C,"undefined"!=typeof document){const e=()=>{h._initPending(),new MutationObserver(e=>{e.forEach(e=>{e.addedNodes.forEach(e=>{e.isConnected&&O(e)}),e.removedNodes.forEach(e=>j(e))})}).observe(document.documentElement,{childList:!0,subtree:!0}),O(document.documentElement)};"loading"!==document.readyState?e():document.addEventListener("DOMContentLoaded",e,!0)}export{i as $,d as $$,h as Component,S as Hash,C as LocalStorage,o as NewState,$ as RefreshState,E as SetTranslator,x as Util,O as _scanTree,j as _unbindTree}; diff --git a/package-lock.json b/package-lock.json index f626015..c26f181 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@web/state", - "version": "1.0.2", + "version": "1.0.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@web/state", - "version": "1.0.2", + "version": "1.0.12", "devDependencies": { "@playwright/test": "^1.40.0", "@rollup/plugin-terser": "^1.0.0", diff --git a/package.json b/package.json index 029a8b5..9b28b45 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@web/state", - "version": "1.0.12", + "version": "1.0.13", "type": "module", "main": "dist/state.js", "module": "dist/state.js", diff --git a/playwright.config.js b/playwright.config.js index 30360af..e440d8a 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -4,11 +4,12 @@ export default defineConfig({ testDir: './test', testMatch: '**/*.spec.js', use: { - baseURL: 'http://localhost:8081', + baseURL: 'http://127.0.0.1:8081', }, webServer: { - command: 'npx vite --port 8081', - url: 'http://localhost:8081', + command: 'npx vite --port 8081 --host 127.0.0.1', + url: 'http://127.0.0.1:8081', + timeout: 180000, reuseExistingServer: !process.env.CI, }, }); diff --git a/playwright.debug.config.js b/playwright.debug.config.js new file mode 100644 index 0000000..c43fdc6 --- /dev/null +++ b/playwright.debug.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + testDir: './test', + testMatch: '**/*.spec.js', + use: { + baseURL: 'http://localhost:8086', + }, +}); diff --git a/src/dom.badbak.for2exp.js b/src/dom.badbak.for2exp.js new file mode 100644 index 0000000..27e1559 --- /dev/null +++ b/src/dom.badbak.for2exp.js @@ -0,0 +1,366 @@ +// src/dom.js +import { _runCode, _returnCode, setDisableRunCodeError } from './core.js'; +import { getActiveBinding, setActiveBinding, getNoWriteBack, setNoWriteBack, NewState, onNotifyUpdate } from './observer.js'; +import { Component, _makeComponent, _mergeNode } from './component.js'; +import { $, $$ } from './dom-utils.js'; + +let _translator = (text, args) => { + if (!text || typeof text !== 'string') return text; + return text.replace(/\{(.+?)\}/g, (match, key) => args.hasOwnProperty(key) ? args[key] : match); +}; +export const SetTranslator = (fn) => _translator = fn; + +const _translate = (text) => { + if (!text || typeof text !== 'string' || !text.includes('{#')) return text; + return text.replace(/\{#(.+?)#\}/g, (m, content) => { + const parts = content.split('||').map(s => s.trim()); + const args = {}; + if (parts.length > 1) { + const matches = parts[0].match(/\{(.+?)\}/g); + if (matches) matches.forEach((match, i) => args[match.substring(1, match.length - 1)] = parts[i + 1] || ''); + } + return _translator(parts[0], args); + }); +}; + +const _isExpression = (name) => name.startsWith('$') || name.startsWith('st-'); +const _getExpressionLevel = (name) => { + if (name.startsWith('$$') || name.startsWith('st-st-')) return 2; + if (name.startsWith('$') || name.startsWith('st-')) return 1; + return 0; +}; +const _getRealAttrName = (name, level) => { + if (level === 2) return name.startsWith('$$') ? name.slice(2) : name.slice(6); + if (level === 1) { + if (name.startsWith('$')) return name.slice(1); + if (name.startsWith('st-')) return name.slice(3); + } + return name; +}; + +if (typeof document !== 'undefined') { + try { document.createElement('div').setAttribute('$t', '1'); } catch (e) { + const originalSetAttribute = Element.prototype.setAttribute; + Element.prototype.setAttribute = function (name, value) { + if (!name.startsWith('$')) return originalSetAttribute.call(this, name, value); + let prefix = name.startsWith('$$') ? 'st-st-' : 'st-'; + return originalSetAttribute.call(this, prefix + name.slice(name.startsWith('$$') ? 2 : 1), value); + }; + } +} + +export { $, $$ }; + +onNotifyUpdate((binding) => _updateBinding(binding)); + +export function _clearRenderedNodes(node) { + if (node._renderedNodes) node._renderedNodes.forEach(nodes => nodes.forEach(child => { + child.remove(); + if (child._renderedNodes) _clearRenderedNodes(child); + })); +} + +export function _updateBinding(binding) { + const node = binding.node; + if (!node.isConnected && node.tagName !== 'TEMPLATE') return; + + setActiveBinding(binding); + let result = binding.exp ? (binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null) : binding.tpl; + + // Level 2 evaluation: Evaluate the result string as code again + if (binding.exp === 2 && typeof result === 'string') { + try { + result = _returnCode(result, { thisNode: node }, node._thisObj || node, { ...node._ref }); + } catch (e) { } + } + setActiveBinding(null); + + if (binding.prop) { + const prop = binding.prop; + let o = node; + for (let i = 0; i < prop.length - 1; i++) { + if (!prop[i]) continue; + if (o[prop[i]] == null) o[prop[i]] = {}; + o = o[prop[i]]; + if (typeof o !== 'object') break; + } + if (typeof o === 'object' && o !== null) { + const lk = prop[prop.length - 1]; + if (lk) { + if (typeof result === 'object' && result != null && !Array.isArray(result) && o[lk] == null) o[lk] = {}; + const lo = o[lk]; + if (typeof lo === 'object' && lo != null && lo.__watch) Object.assign(lo, result); + else o[lk] = result; + } else if (typeof result === 'object' && result != null && !Array.isArray(result)) { + Object.assign(o, result); + } + } + } else if (binding.attr) { + const attr = binding.attr; + if (attr === 'if') { + if (!!result === !!node._lastIfResult) return; + node._lastIfResult = !!result; + if (result) { + if (!node._renderedNodes || node._renderedNodes.length === 0) { + node._children.forEach(child => { + child._stManaged = true; + node.parentNode.insertBefore(child, node); + child._ref = { ...node._ref }; + }); + node._renderedNodes = [node._children]; + } + } else { + _clearRenderedNodes(node); + node._renderedNodes = []; + } + } else if (attr === 'each') { + if (result && typeof result === 'object') { + const asName = node.getAttribute('as') || 'item'; + const indexName = node.getAttribute('index') || 'index'; + let keys, getVal; + if (result instanceof Map) { + keys = Array.from(result.keys()); getVal = k => result.get(k); + } else if (typeof result[Symbol.iterator] === 'function') { + const arr = Array.isArray(result) ? result : Array.from(result); + keys = new Array(arr.length); + for (let i = 0; i < arr.length; i++) keys[i] = i; + getVal = k => arr[k]; + } else { + keys = Object.keys(result); getVal = k => result[k]; + } + + if (!node._renderedNodes) node._renderedNodes = []; + keys.forEach((k, i) => { + const item = getVal(k); + if (i < node._renderedNodes.length) { + node._renderedNodes[i].forEach(child => { + child._ref[indexName] = k; + child._ref[asName] = item; + _scanTree(child, { thisObj: node._thisObj, extendVars: child._ref }); + }); + } else { + const newNodes = []; + node._children.forEach(child => { + const cloned = child.cloneNode(true); + cloned._stManaged = true; + cloned._ref = { ...node._ref, [indexName]: k, [asName]: item }; + cloned._thisObj = node._thisObj; + node.parentNode.insertBefore(cloned, node); + newNodes.push(cloned); + }); + node._renderedNodes.push(newNodes); + } + }); + while (node._renderedNodes.length > keys.length) { + node._renderedNodes.pop().forEach(child => { _clearRenderedNodes(child); child.remove(); }); + } + } else { + _clearRenderedNodes(node); + node._renderedNodes = []; + } + } else if (attr === 'bind') { + if (['INPUT', 'SELECT', 'TEXTAREA'].includes(node.tagName) && !node.hasAttribute('autocomplete')) node.setAttribute('autocomplete', 'off'); + if (node.type === 'checkbox') { + if (node.value !== 'on' && !result) { _runCode(`${binding.tpl} = []`, { thisNode: node }, node._thisObj || node, node._ref || {}); result = []; } + node._checkboxMultiMode = result instanceof Array; + const isChecked = result instanceof Array ? result.includes(node.value) : !!result; + if (node.checked !== isChecked) node.checked = isChecked; + } else if (node.type === 'radio') { + if (node.checked !== (node.value === String(result ?? ''))) node.checked = (node.value === String(result ?? '')); + } else if ('value' in node && node.type !== 'file') { + if (node.value !== String(result ?? '')) node.value = result; + } else if (node.isContentEditable) { + if (node.innerHTML !== String(result ?? '')) node.innerHTML = result; + } + node.dispatchEvent(new CustomEvent('bind', { bubbles: false, detail: result })); + } else { + if (['checked', 'disabled', 'readonly'].includes(attr)) result = !!result; + if (typeof result === 'boolean') result ? node.setAttribute(attr, '') : node.removeAttribute(attr); + else if (result !== undefined) { + if (typeof result !== 'string') result = JSON.stringify(result); + if (attr === 'text') node.textContent = result ?? ''; + else if (attr === 'html') node.innerHTML = result ?? ''; + else if (node.tagName === 'IMG' && attr === 'src' && result.includes('.svg')) node.setAttribute('_src', result ?? ''); + else node.setAttribute(attr, result ?? ''); + } + } + } +} + +export const _initBinding = (binding) => { + if (!binding.node._bindings) binding.node._bindings = []; + binding.node._bindings.push({ attr: binding.attr, prop: binding.prop, tpl: binding.tpl, exp: binding.exp }); + _updateBinding(binding); +}; + +export const _parseNode = (node, scanObj) => { + if (node._bindings) { + node._states = new Set(); + node._bindings.forEach(b => _updateBinding({ node, ...b })); + if (node._hasOnUpdate) node.dispatchEvent(new Event('update', { bubbles: false })); + if (node.tagName !== 'TEMPLATE') return; + } + + if (Component.exists(node.tagName) && !node._componentInitialized) { + Array.from(node.attributes).forEach(attr => { + if (attr.name.startsWith('$.')) { + const realAttrName = attr.name.slice(2); + let tpl = _translate(attr.value); + if (tpl.includes('this.')) tpl = tpl.replace(/\bthis\./g, 'this.parent.'); + const result = _returnCode(tpl, { thisNode: node }, { parent: scanObj.thisObj || node }, node._ref || {}); + let o = node; + const prop = realAttrName.split('.'); + for (let i = 0; i < prop.length - 1; i++) { if (prop[i]) o = (o[prop[i]] ??= {}); } + o[prop[prop.length - 1]] = result; + node.removeAttribute(attr.name); + } + }); + _makeComponent(node.tagName, node, scanObj); + $$(node, '[slot-id]').forEach(p => p.removeAttribute('slot-id')); + node._componentInitialized = true; + if (!node._thisObj) node._thisObj = node; + } + + if (node.tagName === 'TEMPLATE') { + if (!node._children) node._children = [...node.content.childNodes]; + if (!node._renderedNodes) node._renderedNodes = []; + } + + if (node._bindings && node.tagName !== 'TEMPLATE') return; + + let attrs = Array.from(node.attributes).filter(a => _isExpression(a.name) || a.name.includes('.')); + if (node._thisObj && scanObj.thisObj) node._thisObj.parent = scanObj.thisObj; + if (!node._thisObj) node._thisObj = scanObj.thisObj || null; + if (!node._ref) node._ref = scanObj.extendVars || {}; + node._states = new Set(); + + attrs.forEach(attr => { + const exp = _getExpressionLevel(attr.name); + const realAttrName = exp ? _getRealAttrName(attr.name, exp) : attr.name; + if (exp && (node.tagName === 'TEMPLATE' || !['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each'].includes(attr.name))) node.removeAttribute(attr.name); + if (realAttrName.startsWith('.')) _initBinding({ node, prop: realAttrName.split('.').filter(Boolean), tpl: attr.value, exp: 0 }); + else if (realAttrName.startsWith('on')) { + const eventName = realAttrName.slice(2); + if (eventName === 'update') node._hasOnUpdate = true; + if (eventName === 'load' && !['BODY', 'IMG', 'IFRAME'].includes(node.tagName)) node._hasOnLoad = true; + if (eventName === 'unload' && !['BODY', 'IMG', 'IFRAME'].includes(node.tagName)) node._hasOnUnload = true; + node.addEventListener(eventName, (e) => _runCode(attr.value, { event: e, thisNode: node, ...(e.detail || {}) }, scanObj.thisObj || node, node._ref || {})); + } else { + if (realAttrName === 'bind') { + node.addEventListener(node.tagName === 'TEXTAREA' || node.isContentEditable || node.type === 'text' || node.type === 'password' ? 'input' : 'change', (e) => { + let newVal = node.isContentEditable ? e.target.innerHTML : (node.type === 'checkbox' ? e.target.checked : e.target.files || e.target.value || e.detail); + setNoWriteBack(node); setDisableRunCodeError(true); + if (node.type === 'checkbox' && node._checkboxMultiMode) _runCode(`!!checked ? (!${attr.value}.includes(val) && ${attr.value}.push(val)) : (index = ${attr.value}.indexOf(val), index > -1 && ${attr.value}.splice(index, 1))`, { val: node.value, checked: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {}); + else _runCode(attr.value + ' = val', { val: newVal, thisNode: node }, scanObj.thisObj || node, node._ref || {}); + setDisableRunCodeError(false); setNoWriteBack(null); + }); + _initBinding({ node, attr: realAttrName, tpl: attr.value, exp }); + } else if (realAttrName === 'text' && !attr.value) { _initBinding({ node, attr: realAttrName, tpl: node.textContent, exp: 1 }); node.textContent = ''; } + else if (attr.value) { + let tpl = _translate(attr.value); + _initBinding({ node, attr: realAttrName, tpl, exp }); + } + } + }); + + if (node._hasOnLoad || node._componentInitialized) Promise.resolve().then(() => node.dispatchEvent(new Event('load', { bubbles: false }))); + if (node._hasOnUpdate) node.dispatchEvent(new Event('update', { bubbles: false })); + if (node._thisObj) scanObj.thisObj = node._thisObj; +}; + +export const _scanTree = (node, scanObj = {}) => { + if (node.nodeType === 3) { + if (node._stTranslated) return; + const translated = _translate(node.textContent); + if (translated !== node.textContent) node.textContent = translated; + node._stTranslated = true; return; + } + if (node.nodeType !== 1) return; + + if (!node._stTranslated) { + Array.from(node.attributes).forEach(attr => { + if (!_isExpression(attr.name) && !attr.name.startsWith('.')) { + const translated = _translate(attr.value); + if (translated !== attr.value) attr.value = translated; + } + }); + node._stTranslated = true; + } + + const triggerAttrs = ['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each']; + if (node.tagName !== 'TEMPLATE' && triggerAttrs.some(t => node.hasAttribute(t))) { + const template = document.createElement('TEMPLATE'); + const managed = Array.from(node.attributes).filter(attr => triggerAttrs.includes(attr.name) || ['as', 'index'].includes(attr.name)); + managed.forEach(attr => { template.setAttribute(attr.name, attr.value); node.removeAttribute(attr.name); }); + node.parentNode.insertBefore(template, node); + template.content.appendChild(node); + template._ref = node._ref; + return; + } + if (node.tagName === 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('st-if') || node.hasAttribute('$$if') || node.hasAttribute('st-st-if')) && (node.hasAttribute('$each') || node.hasAttribute('st-each') || node.hasAttribute('$$each') || node.hasAttribute('st-st-each'))) { + const template = document.createElement('TEMPLATE'); + const attrs = Array.from(node.attributes).filter(attr => triggerAttrs.includes(attr.name)); + const attr = attrs[attrs.length - 1]; + template.setAttribute(attr.name, attr.value); + node.removeAttribute(attr.name); + if (attr.name.includes('each')) { + Array.from(node.attributes).filter(a => ['as', 'index'].includes(a.name)).forEach(a => { template.setAttribute(a.name, a.value); node.removeAttribute(a.name); }); + } + Array.from(node.content.childNodes).forEach(child => template.content.appendChild(child)); + node.content.appendChild(template); + template._ref = node._ref; + } + + if (node.tagName === 'IMG' && (node.hasAttribute('src') || node.hasAttribute('_src') || node.hasAttribute('$src'))) { + const imgNode = node; + Promise.resolve().then(() => { + const url = imgNode.getAttribute('_src') || imgNode.getAttribute('src'); + if (url) fetch(url, { cache: 'force-cache' }).then(r => r.text()).then(svgText => { + const realSvg = new DOMParser().parseFromString(svgText, "image/svg+xml").querySelector('svg'); + if (realSvg) { + Array.from(imgNode.attributes).forEach(attr => realSvg.setAttribute(attr.name, attr.value)); + imgNode.replaceWith(realSvg); + } + }); + }); + } + + if (node._thisObj !== undefined) scanObj.thisObj = node._thisObj || null; + else { + let curr = node; + while (curr && curr._thisObj === undefined) curr = curr.parentNode; + scanObj.thisObj = curr ? curr._thisObj : null; + } + if (node._ref === undefined) { + let curr = node; + while (curr && curr._ref === undefined) curr = curr.parentNode; + node._ref = curr ? { ...curr._ref } : {}; + } + if (scanObj.extendVars) Object.assign(node._ref, scanObj.extendVars); + + if (!scanObj.noBind) _parseNode(node, scanObj); + + const nodes = [...(node.childNodes || []), ...(node.tagName === 'TEMPLATE' ? Array.from(node.content.childNodes).map(c => (c._stNoBind = true, c)) : [])]; + const nextScanObj = { thisObj: scanObj.thisObj, extendVars: { ...node._ref }, noBind: scanObj.noBind }; + nodes.forEach(child => { + if (!child._stManaged) { + const wasNoBind = nextScanObj.noBind; + if (child._stNoBind) { nextScanObj.noBind = true; delete child._stNoBind; } + _scanTree(child, nextScanObj); + nextScanObj.noBind = wasNoBind; + } + }); +}; + +export const _unbindTree = (node) => { + if (node.nodeType !== 1) return; + if (node._hasOnUnload) node.dispatchEvent(new Event('unload', { bubbles: false })); + if (node._states) node._states.forEach(mappings => { + for (const [key, bindingSet] of mappings) { + for (const binding of bindingSet) { if (binding.node === node) bindingSet.delete(binding); } + } + }); + node.childNodes && node.childNodes.forEach(child => _unbindTree(child)); +}; + +export const RefreshState = _scanTree; diff --git a/src/dom.js b/src/dom.js index 2635c99..2795639 100644 --- a/src/dom.js +++ b/src/dom.js @@ -28,7 +28,8 @@ if (typeof document !== 'undefined') { const originalSetAttribute = Element.prototype.setAttribute; Element.prototype.setAttribute = function (name, value) { if (!name.startsWith('$')) return originalSetAttribute.call(this, name, value); - return originalSetAttribute.call(this, 'st-' + name.substring(1), value); + const prefix = name.startsWith('$$') ? 'st-st-' : 'st-'; + return originalSetAttribute.call(this, prefix + name.substring(name.startsWith('$$') ? 2 : 1), value); }; } } @@ -52,6 +53,11 @@ export function _updateBinding(binding) { if (window.__perfTrace) window.__perfTrace.evalCount++; const evalStart = window.__perfTrace ? performance.now() : 0; let result = binding.exp ? (binding.tpl ? _returnCode(binding.tpl, { thisNode: node }, node._thisObj || node, node._ref || null) : null) : binding.tpl; + + if (binding.exp === 2 && typeof result === 'string') { + try { result = _returnCode(result, { thisNode: node }, node._thisObj || node, node._ref || null); } catch (e) { } + } + if (window.__perfTrace) window.__perfTrace.evalTotal += (performance.now() - evalStart); setActiveBinding(null); @@ -248,10 +254,11 @@ export const _parseNode = (node, scanObj) => { if (hasBindings) return; let attrs = []; + const triggerAttrs = ['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each']; if (node.tagName === 'TEMPLATE') { - ['$if', '$each', 'st-if', 'st-each'].forEach(n => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n))); + triggerAttrs.forEach(n => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n))); } else { - attrs = Array.from(node.attributes).filter(a => (a.name.startsWith('$') || a.name.startsWith('st-')) && !['$if', '$each', 'st-if', 'st-each'].includes(a.name) || a.name.includes('.')); + attrs = Array.from(node.attributes).filter(a => (a.name.startsWith('$') || a.name.startsWith('st-')) && !triggerAttrs.includes(a.name) || a.name.includes('.')); } if (node._thisObj && scanObj.thisObj) node._thisObj.parent = scanObj.thisObj; @@ -260,8 +267,11 @@ export const _parseNode = (node, scanObj) => { node._states = new Set(); attrs.forEach(attr => { - const exp = attr.name.startsWith('$') || attr.name.startsWith('st-'); - const realAttrName = exp ? attr.name.slice(attr.name.startsWith('$') ? 1 : 3) : attr.name; + let exp = 0; + if (attr.name.startsWith('$$') || attr.name.startsWith('st-st-')) exp = 2; + else if (attr.name.startsWith('$') || attr.name.startsWith('st-')) exp = 1; + + const realAttrName = exp === 2 ? (attr.name.startsWith('$$') ? attr.name.slice(2) : attr.name.slice(6)) : (exp === 1 ? (attr.name.startsWith('$') ? attr.name.slice(1) : attr.name.slice(3)) : attr.name); let tpl = attr.value; node.removeAttribute(attr.name); if (realAttrName.startsWith('.')) _initBinding({ node, prop: realAttrName.split('.'), tpl, exp }); @@ -324,9 +334,11 @@ export const _scanTree = (node, scanObj = {}) => { if (resolvedThisObj === undefined) resolvedThisObj = scanObj.thisObj; if (resolvedRef === undefined) resolvedRef = scanObj.extendVars; - if (node.tagName !== 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('$each') || node.hasAttribute('st-if') || node.hasAttribute('st-each'))) { + const triggerAttrs = ['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each']; + const eachAttrs = ['$each', 'st-each', '$$each', 'st-st-each']; + if (node.tagName !== 'TEMPLATE' && triggerAttrs.some(t => node.hasAttribute(t))) { const template = document.createElement('TEMPLATE'); - const attrs = Array.from(node.attributes).filter(attr => ['$if', '$each', 'st-if', 'st-each'].includes(attr.name) || ((node.hasAttribute('$each') || node.hasAttribute('st-each')) && ['as', 'index'].includes(attr.name))); + const attrs = Array.from(node.attributes).filter(attr => triggerAttrs.includes(attr.name) || (eachAttrs.some(t => node.hasAttribute(t)) && ['as', 'index'].includes(attr.name))); attrs.forEach(attr => { template.setAttribute(attr.name, attr.value); node.removeAttribute(attr.name); }); node.parentNode.insertBefore(template, node); template.content.appendChild(node); @@ -335,13 +347,13 @@ export const _scanTree = (node, scanObj = {}) => { _scanTree(template, scanObj); return; } - if (node.tagName === 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('st-if')) && (node.hasAttribute('$each') || node.hasAttribute('st-each'))) { + if (node.tagName === 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('st-if') || node.hasAttribute('$$if') || node.hasAttribute('st-st-if')) && (node.hasAttribute('$each') || node.hasAttribute('st-each') || node.hasAttribute('$$each') || node.hasAttribute('st-st-each'))) { const template = document.createElement('TEMPLATE'); - const attrs = Array.from(node.attributes).filter(attr => ['$if', '$each', 'st-if', 'st-each'].includes(attr.name)); + const attrs = Array.from(node.attributes).filter(attr => triggerAttrs.includes(attr.name)); const attr = attrs[attrs.length - 1]; template.setAttribute(attr.name, attr.value); node.removeAttribute(attr.name); - if (attr.name === '$each' || attr.name === 'st-each') { + if (eachAttrs.includes(attr.name)) { Array.from(node.attributes).filter(attr => ['as', 'index'].includes(attr.name)).forEach(attr => { template.setAttribute(attr.name, attr.value); node.removeAttribute(attr.name); }); } Array.from(node.content.childNodes).forEach(child => template.content.appendChild(child)); diff --git a/test-html-attr.cjs b/test-html-attr.cjs new file mode 100644 index 0000000..4a5c676 --- /dev/null +++ b/test-html-attr.cjs @@ -0,0 +1,12 @@ +const { chromium } = require('@playwright/test'); +(async () => { + const browser = await chromium.launch(); + const page = await browser.newPage(); + const name = await page.evaluate(() => { + const div = document.createElement('div'); + div.innerHTML = '
'; + return div.firstChild.attributes[0].name; + }); + console.log('Attribute name:', name); + await browser.close(); +})(); diff --git a/test-results/.last-run.json b/test-results/.last-run.json index 12c3f04..344ea9e 100644 --- a/test-results/.last-run.json +++ b/test-results/.last-run.json @@ -1,6 +1,4 @@ { - "status": "failed", - "failedTests": [ - "8a84b43f13b676ea22b7-5fcda25ae3a58304c071" - ] + "status": "interrupted", + "failedTests": [] } \ No newline at end of file diff --git a/test-results/all-modular-unit-tests-and-benchmark/error-context.md b/test-results/all-modular-unit-tests-and-benchmark/error-context.md deleted file mode 100644 index 70c813d..0000000 --- a/test-results/all-modular-unit-tests-and-benchmark/error-context.md +++ /dev/null @@ -1,1095 +0,0 @@ -# Instructions - -- Following Playwright test failed. -- Explain why, be concise, respect Playwright best practices. -- Provide a snippet of code with the fix, if possible. - -# Test info - -- Name: all.spec.js >> modular unit tests and benchmark -- Location: test/all.spec.js:5:1 - -# Error details - -``` -Error: expect(received).toBeLessThan(expected) - -Expected: < 70.92 -Received: 119.90000009536743 -``` - -# Page snapshot - -```yaml -- list [ref=e2]: - - listitem [ref=e3]: item 0 - - listitem [ref=e4]: item 1 - - listitem [ref=e5]: item 2 - - listitem [ref=e6]: item 3 - - listitem [ref=e7]: item 4 - - listitem [ref=e8]: item 5 - - listitem [ref=e9]: item 6 - - listitem [ref=e10]: item 7 - - listitem [ref=e11]: item 8 - - listitem [ref=e12]: item 9 - - listitem [ref=e13]: item 10 - - listitem [ref=e14]: item 11 - - listitem [ref=e15]: item 12 - - listitem [ref=e16]: item 13 - - listitem [ref=e17]: item 14 - - listitem [ref=e18]: item 15 - - listitem [ref=e19]: item 16 - - listitem [ref=e20]: item 17 - - listitem [ref=e21]: item 18 - - listitem [ref=e22]: item 19 - - listitem [ref=e23]: item 20 - - listitem [ref=e24]: item 21 - - listitem [ref=e25]: item 22 - - listitem [ref=e26]: item 23 - - listitem [ref=e27]: item 24 - - listitem [ref=e28]: item 25 - - listitem [ref=e29]: item 26 - - listitem [ref=e30]: item 27 - - listitem [ref=e31]: item 28 - - listitem [ref=e32]: item 29 - - listitem [ref=e33]: item 30 - - listitem [ref=e34]: item 31 - - listitem [ref=e35]: item 32 - - listitem [ref=e36]: item 33 - - listitem [ref=e37]: item 34 - - listitem [ref=e38]: item 35 - - listitem [ref=e39]: item 36 - - listitem [ref=e40]: item 37 - - listitem [ref=e41]: item 38 - - listitem [ref=e42]: item 39 - - listitem [ref=e43]: item 40 - - listitem [ref=e44]: item 41 - - listitem [ref=e45]: item 42 - - listitem [ref=e46]: item 43 - - listitem [ref=e47]: item 44 - - listitem [ref=e48]: item 45 - - listitem [ref=e49]: item 46 - - listitem [ref=e50]: item 47 - - listitem [ref=e51]: item 48 - - listitem [ref=e52]: item 49 - - listitem [ref=e53]: item 50 - - listitem [ref=e54]: item 51 - - listitem [ref=e55]: item 52 - - listitem [ref=e56]: item 53 - - listitem [ref=e57]: item 54 - - listitem [ref=e58]: item 55 - - listitem [ref=e59]: item 56 - - listitem [ref=e60]: item 57 - - listitem [ref=e61]: item 58 - - listitem [ref=e62]: item 59 - - listitem [ref=e63]: item 60 - - listitem [ref=e64]: item 61 - - listitem [ref=e65]: item 62 - - listitem [ref=e66]: item 63 - - listitem [ref=e67]: item 64 - - listitem [ref=e68]: item 65 - - listitem [ref=e69]: item 66 - - listitem [ref=e70]: item 67 - - listitem [ref=e71]: item 68 - - listitem [ref=e72]: item 69 - - listitem [ref=e73]: item 70 - - listitem [ref=e74]: item 71 - - listitem [ref=e75]: item 72 - - listitem [ref=e76]: item 73 - - listitem [ref=e77]: item 74 - - listitem [ref=e78]: item 75 - - listitem [ref=e79]: item 76 - - listitem [ref=e80]: item 77 - - listitem [ref=e81]: item 78 - - listitem [ref=e82]: item 79 - - listitem [ref=e83]: item 80 - - listitem [ref=e84]: item 81 - - listitem [ref=e85]: item 82 - - listitem [ref=e86]: item 83 - - listitem [ref=e87]: item 84 - - listitem [ref=e88]: item 85 - - listitem [ref=e89]: item 86 - - listitem [ref=e90]: item 87 - - listitem [ref=e91]: item 88 - - listitem [ref=e92]: item 89 - - listitem [ref=e93]: item 90 - - listitem [ref=e94]: item 91 - - listitem [ref=e95]: item 92 - - listitem [ref=e96]: item 93 - - listitem [ref=e97]: item 94 - - listitem [ref=e98]: item 95 - - listitem [ref=e99]: item 96 - - listitem [ref=e100]: item 97 - - listitem [ref=e101]: item 98 - - listitem [ref=e102]: item 99 - - listitem [ref=e103]: item 100 - - listitem [ref=e104]: item 101 - - listitem [ref=e105]: item 102 - - listitem [ref=e106]: item 103 - - listitem [ref=e107]: item 104 - - listitem [ref=e108]: item 105 - - listitem [ref=e109]: item 106 - - listitem [ref=e110]: item 107 - - listitem [ref=e111]: item 108 - - listitem [ref=e112]: item 109 - - listitem [ref=e113]: item 110 - - listitem [ref=e114]: item 111 - - listitem [ref=e115]: item 112 - - listitem [ref=e116]: item 113 - - listitem [ref=e117]: item 114 - - listitem [ref=e118]: item 115 - - listitem [ref=e119]: item 116 - - listitem [ref=e120]: item 117 - - listitem [ref=e121]: item 118 - - listitem [ref=e122]: item 119 - - listitem [ref=e123]: item 120 - - listitem [ref=e124]: item 121 - - listitem [ref=e125]: item 122 - - listitem [ref=e126]: item 123 - - listitem [ref=e127]: item 124 - - listitem [ref=e128]: item 125 - - listitem [ref=e129]: item 126 - - listitem [ref=e130]: item 127 - - listitem [ref=e131]: item 128 - - listitem [ref=e132]: item 129 - - listitem [ref=e133]: item 130 - - listitem [ref=e134]: item 131 - - listitem [ref=e135]: item 132 - - listitem [ref=e136]: item 133 - - listitem [ref=e137]: item 134 - - listitem [ref=e138]: item 135 - - listitem [ref=e139]: item 136 - - listitem [ref=e140]: item 137 - - listitem [ref=e141]: item 138 - - listitem [ref=e142]: item 139 - - listitem [ref=e143]: item 140 - - listitem [ref=e144]: item 141 - - listitem [ref=e145]: item 142 - - listitem [ref=e146]: item 143 - - listitem [ref=e147]: item 144 - - listitem [ref=e148]: item 145 - - listitem [ref=e149]: item 146 - - listitem [ref=e150]: item 147 - - listitem [ref=e151]: item 148 - - listitem [ref=e152]: item 149 - - listitem [ref=e153]: item 150 - - listitem [ref=e154]: item 151 - - listitem [ref=e155]: item 152 - - listitem [ref=e156]: item 153 - - listitem [ref=e157]: item 154 - - listitem [ref=e158]: item 155 - - listitem [ref=e159]: item 156 - - listitem [ref=e160]: item 157 - - listitem [ref=e161]: item 158 - - listitem [ref=e162]: item 159 - - listitem [ref=e163]: item 160 - - listitem [ref=e164]: item 161 - - listitem [ref=e165]: item 162 - - listitem [ref=e166]: item 163 - - listitem [ref=e167]: item 164 - - listitem [ref=e168]: item 165 - - listitem [ref=e169]: item 166 - - listitem [ref=e170]: item 167 - - listitem [ref=e171]: item 168 - - listitem [ref=e172]: item 169 - - listitem [ref=e173]: item 170 - - listitem [ref=e174]: item 171 - - listitem [ref=e175]: item 172 - - listitem [ref=e176]: item 173 - - listitem [ref=e177]: item 174 - - listitem [ref=e178]: item 175 - - listitem [ref=e179]: item 176 - - listitem [ref=e180]: item 177 - - listitem [ref=e181]: item 178 - - listitem [ref=e182]: item 179 - - listitem [ref=e183]: item 180 - - listitem [ref=e184]: item 181 - - listitem [ref=e185]: item 182 - - listitem [ref=e186]: item 183 - - listitem [ref=e187]: item 184 - - listitem [ref=e188]: item 185 - - listitem [ref=e189]: item 186 - - listitem [ref=e190]: item 187 - - listitem [ref=e191]: item 188 - - listitem [ref=e192]: item 189 - - listitem [ref=e193]: item 190 - - listitem [ref=e194]: item 191 - - listitem [ref=e195]: item 192 - - listitem [ref=e196]: item 193 - - listitem [ref=e197]: item 194 - - listitem [ref=e198]: item 195 - - listitem [ref=e199]: item 196 - - listitem [ref=e200]: item 197 - - listitem [ref=e201]: item 198 - - listitem [ref=e202]: item 199 - - listitem [ref=e203]: item 200 - - listitem [ref=e204]: item 201 - - listitem [ref=e205]: item 202 - - listitem [ref=e206]: item 203 - - listitem [ref=e207]: item 204 - - listitem [ref=e208]: item 205 - - listitem [ref=e209]: item 206 - - listitem [ref=e210]: item 207 - - listitem [ref=e211]: item 208 - - listitem [ref=e212]: item 209 - - listitem [ref=e213]: item 210 - - listitem [ref=e214]: item 211 - - listitem [ref=e215]: item 212 - - listitem [ref=e216]: item 213 - - listitem [ref=e217]: item 214 - - listitem [ref=e218]: item 215 - - listitem [ref=e219]: item 216 - - listitem [ref=e220]: item 217 - - listitem [ref=e221]: item 218 - - listitem [ref=e222]: item 219 - - listitem [ref=e223]: item 220 - - listitem [ref=e224]: item 221 - - listitem [ref=e225]: item 222 - - listitem [ref=e226]: item 223 - - listitem [ref=e227]: item 224 - - listitem [ref=e228]: item 225 - - listitem [ref=e229]: item 226 - - listitem [ref=e230]: item 227 - - listitem [ref=e231]: item 228 - - listitem [ref=e232]: item 229 - - listitem [ref=e233]: item 230 - - listitem [ref=e234]: item 231 - - listitem [ref=e235]: item 232 - - listitem [ref=e236]: item 233 - - listitem [ref=e237]: item 234 - - listitem [ref=e238]: item 235 - - listitem [ref=e239]: item 236 - - listitem [ref=e240]: item 237 - - listitem [ref=e241]: item 238 - - listitem [ref=e242]: item 239 - - listitem [ref=e243]: item 240 - - listitem [ref=e244]: item 241 - - listitem [ref=e245]: item 242 - - listitem [ref=e246]: item 243 - - listitem [ref=e247]: item 244 - - listitem [ref=e248]: item 245 - - listitem [ref=e249]: item 246 - - listitem [ref=e250]: item 247 - - listitem [ref=e251]: item 248 - - listitem [ref=e252]: item 249 - - listitem [ref=e253]: item 250 - - listitem [ref=e254]: item 251 - - listitem [ref=e255]: item 252 - - listitem [ref=e256]: item 253 - - listitem [ref=e257]: item 254 - - listitem [ref=e258]: item 255 - - listitem [ref=e259]: item 256 - - listitem [ref=e260]: item 257 - - listitem [ref=e261]: item 258 - - listitem [ref=e262]: item 259 - - listitem [ref=e263]: item 260 - - listitem [ref=e264]: item 261 - - listitem [ref=e265]: item 262 - - listitem [ref=e266]: item 263 - - listitem [ref=e267]: item 264 - - listitem [ref=e268]: item 265 - - listitem [ref=e269]: item 266 - - listitem [ref=e270]: item 267 - - listitem [ref=e271]: item 268 - - listitem [ref=e272]: item 269 - - listitem [ref=e273]: item 270 - - listitem [ref=e274]: item 271 - - listitem [ref=e275]: item 272 - - listitem [ref=e276]: item 273 - - listitem [ref=e277]: item 274 - - listitem [ref=e278]: item 275 - - listitem [ref=e279]: item 276 - - listitem [ref=e280]: item 277 - - listitem [ref=e281]: item 278 - - listitem [ref=e282]: item 279 - - listitem [ref=e283]: item 280 - - listitem [ref=e284]: item 281 - - listitem [ref=e285]: item 282 - - listitem [ref=e286]: item 283 - - listitem [ref=e287]: item 284 - - listitem [ref=e288]: item 285 - - listitem [ref=e289]: item 286 - - listitem [ref=e290]: item 287 - - listitem [ref=e291]: item 288 - - listitem [ref=e292]: item 289 - - listitem [ref=e293]: item 290 - - listitem [ref=e294]: item 291 - - listitem [ref=e295]: item 292 - - listitem [ref=e296]: item 293 - - listitem [ref=e297]: item 294 - - listitem [ref=e298]: item 295 - - listitem [ref=e299]: item 296 - - listitem [ref=e300]: item 297 - - listitem [ref=e301]: item 298 - - listitem [ref=e302]: item 299 - - listitem [ref=e303]: item 300 - - listitem [ref=e304]: item 301 - - listitem [ref=e305]: item 302 - - listitem [ref=e306]: item 303 - - listitem [ref=e307]: item 304 - - listitem [ref=e308]: item 305 - - listitem [ref=e309]: item 306 - - listitem [ref=e310]: item 307 - - listitem [ref=e311]: item 308 - - listitem [ref=e312]: item 309 - - listitem [ref=e313]: item 310 - - listitem [ref=e314]: item 311 - - listitem [ref=e315]: item 312 - - listitem [ref=e316]: item 313 - - listitem [ref=e317]: item 314 - - listitem [ref=e318]: item 315 - - listitem [ref=e319]: item 316 - - listitem [ref=e320]: item 317 - - listitem [ref=e321]: item 318 - - listitem [ref=e322]: item 319 - - listitem [ref=e323]: item 320 - - listitem [ref=e324]: item 321 - - listitem [ref=e325]: item 322 - - listitem [ref=e326]: item 323 - - listitem [ref=e327]: item 324 - - listitem [ref=e328]: item 325 - - listitem [ref=e329]: item 326 - - listitem [ref=e330]: item 327 - - listitem [ref=e331]: item 328 - - listitem [ref=e332]: item 329 - - listitem [ref=e333]: item 330 - - listitem [ref=e334]: item 331 - - listitem [ref=e335]: item 332 - - listitem [ref=e336]: item 333 - - listitem [ref=e337]: item 334 - - listitem [ref=e338]: item 335 - - listitem [ref=e339]: item 336 - - listitem [ref=e340]: item 337 - - listitem [ref=e341]: item 338 - - listitem [ref=e342]: item 339 - - listitem [ref=e343]: item 340 - - listitem [ref=e344]: item 341 - - listitem [ref=e345]: item 342 - - listitem [ref=e346]: item 343 - - listitem [ref=e347]: item 344 - - listitem [ref=e348]: item 345 - - listitem [ref=e349]: item 346 - - listitem [ref=e350]: item 347 - - listitem [ref=e351]: item 348 - - listitem [ref=e352]: item 349 - - listitem [ref=e353]: item 350 - - listitem [ref=e354]: item 351 - - listitem [ref=e355]: item 352 - - listitem [ref=e356]: item 353 - - listitem [ref=e357]: item 354 - - listitem [ref=e358]: item 355 - - listitem [ref=e359]: item 356 - - listitem [ref=e360]: item 357 - - listitem [ref=e361]: item 358 - - listitem [ref=e362]: item 359 - - listitem [ref=e363]: item 360 - - listitem [ref=e364]: item 361 - - listitem [ref=e365]: item 362 - - listitem [ref=e366]: item 363 - - listitem [ref=e367]: item 364 - - listitem [ref=e368]: item 365 - - listitem [ref=e369]: item 366 - - listitem [ref=e370]: item 367 - - listitem [ref=e371]: item 368 - - listitem [ref=e372]: item 369 - - listitem [ref=e373]: item 370 - - listitem [ref=e374]: item 371 - - listitem [ref=e375]: item 372 - - listitem [ref=e376]: item 373 - - listitem [ref=e377]: item 374 - - listitem [ref=e378]: item 375 - - listitem [ref=e379]: item 376 - - listitem [ref=e380]: item 377 - - listitem [ref=e381]: item 378 - - listitem [ref=e382]: item 379 - - listitem [ref=e383]: item 380 - - listitem [ref=e384]: item 381 - - listitem [ref=e385]: item 382 - - listitem [ref=e386]: item 383 - - listitem [ref=e387]: item 384 - - listitem [ref=e388]: item 385 - - listitem [ref=e389]: item 386 - - listitem [ref=e390]: item 387 - - listitem [ref=e391]: item 388 - - listitem [ref=e392]: item 389 - - listitem [ref=e393]: item 390 - - listitem [ref=e394]: item 391 - - listitem [ref=e395]: item 392 - - listitem [ref=e396]: item 393 - - listitem [ref=e397]: item 394 - - listitem [ref=e398]: item 395 - - listitem [ref=e399]: item 396 - - listitem [ref=e400]: item 397 - - listitem [ref=e401]: item 398 - - listitem [ref=e402]: item 399 - - listitem [ref=e403]: item 400 - - listitem [ref=e404]: item 401 - - listitem [ref=e405]: item 402 - - listitem [ref=e406]: item 403 - - listitem [ref=e407]: item 404 - - listitem [ref=e408]: item 405 - - listitem [ref=e409]: item 406 - - listitem [ref=e410]: item 407 - - listitem [ref=e411]: item 408 - - listitem [ref=e412]: item 409 - - listitem [ref=e413]: item 410 - - listitem [ref=e414]: item 411 - - listitem [ref=e415]: item 412 - - listitem [ref=e416]: item 413 - - listitem [ref=e417]: item 414 - - listitem [ref=e418]: item 415 - - listitem [ref=e419]: item 416 - - listitem [ref=e420]: item 417 - - listitem [ref=e421]: item 418 - - listitem [ref=e422]: item 419 - - listitem [ref=e423]: item 420 - - listitem [ref=e424]: item 421 - - listitem [ref=e425]: item 422 - - listitem [ref=e426]: item 423 - - listitem [ref=e427]: item 424 - - listitem [ref=e428]: item 425 - - listitem [ref=e429]: item 426 - - listitem [ref=e430]: item 427 - - listitem [ref=e431]: item 428 - - listitem [ref=e432]: item 429 - - listitem [ref=e433]: item 430 - - listitem [ref=e434]: item 431 - - listitem [ref=e435]: item 432 - - listitem [ref=e436]: item 433 - - listitem [ref=e437]: item 434 - - listitem [ref=e438]: item 435 - - listitem [ref=e439]: item 436 - - listitem [ref=e440]: item 437 - - listitem [ref=e441]: item 438 - - listitem [ref=e442]: item 439 - - listitem [ref=e443]: item 440 - - listitem [ref=e444]: item 441 - - listitem [ref=e445]: item 442 - - listitem [ref=e446]: item 443 - - listitem [ref=e447]: item 444 - - listitem [ref=e448]: item 445 - - listitem [ref=e449]: item 446 - - listitem [ref=e450]: item 447 - - listitem [ref=e451]: item 448 - - listitem [ref=e452]: item 449 - - listitem [ref=e453]: item 450 - - listitem [ref=e454]: item 451 - - listitem [ref=e455]: item 452 - - listitem [ref=e456]: item 453 - - listitem [ref=e457]: item 454 - - listitem [ref=e458]: item 455 - - listitem [ref=e459]: item 456 - - listitem [ref=e460]: item 457 - - listitem [ref=e461]: item 458 - - listitem [ref=e462]: item 459 - - listitem [ref=e463]: item 460 - - listitem [ref=e464]: item 461 - - listitem [ref=e465]: item 462 - - listitem [ref=e466]: item 463 - - listitem [ref=e467]: item 464 - - listitem [ref=e468]: item 465 - - listitem [ref=e469]: item 466 - - listitem [ref=e470]: item 467 - - listitem [ref=e471]: item 468 - - listitem [ref=e472]: item 469 - - listitem [ref=e473]: item 470 - - listitem [ref=e474]: item 471 - - listitem [ref=e475]: item 472 - - listitem [ref=e476]: item 473 - - listitem [ref=e477]: item 474 - - listitem [ref=e478]: item 475 - - listitem [ref=e479]: item 476 - - listitem [ref=e480]: item 477 - - listitem [ref=e481]: item 478 - - listitem [ref=e482]: item 479 - - listitem [ref=e483]: item 480 - - listitem [ref=e484]: item 481 - - listitem [ref=e485]: item 482 - - listitem [ref=e486]: item 483 - - listitem [ref=e487]: item 484 - - listitem [ref=e488]: item 485 - - listitem [ref=e489]: item 486 - - listitem [ref=e490]: item 487 - - listitem [ref=e491]: item 488 - - listitem [ref=e492]: item 489 - - listitem [ref=e493]: item 490 - - listitem [ref=e494]: item 491 - - listitem [ref=e495]: item 492 - - listitem [ref=e496]: item 493 - - listitem [ref=e497]: item 494 - - listitem [ref=e498]: item 495 - - listitem [ref=e499]: item 496 - - listitem [ref=e500]: item 497 - - listitem [ref=e501]: item 498 - - listitem [ref=e502]: item 499 - - listitem [ref=e503]: item 500 - - listitem [ref=e504]: item 501 - - listitem [ref=e505]: item 502 - - listitem [ref=e506]: item 503 - - listitem [ref=e507]: item 504 - - listitem [ref=e508]: item 505 - - listitem [ref=e509]: item 506 - - listitem [ref=e510]: item 507 - - listitem [ref=e511]: item 508 - - listitem [ref=e512]: item 509 - - listitem [ref=e513]: item 510 - - listitem [ref=e514]: item 511 - - listitem [ref=e515]: item 512 - - listitem [ref=e516]: item 513 - - listitem [ref=e517]: item 514 - - listitem [ref=e518]: item 515 - - listitem [ref=e519]: item 516 - - listitem [ref=e520]: item 517 - - listitem [ref=e521]: item 518 - - listitem [ref=e522]: item 519 - - listitem [ref=e523]: item 520 - - listitem [ref=e524]: item 521 - - listitem [ref=e525]: item 522 - - listitem [ref=e526]: item 523 - - listitem [ref=e527]: item 524 - - listitem [ref=e528]: item 525 - - listitem [ref=e529]: item 526 - - listitem [ref=e530]: item 527 - - listitem [ref=e531]: item 528 - - listitem [ref=e532]: item 529 - - listitem [ref=e533]: item 530 - - listitem [ref=e534]: item 531 - - listitem [ref=e535]: item 532 - - listitem [ref=e536]: item 533 - - listitem [ref=e537]: item 534 - - listitem [ref=e538]: item 535 - - listitem [ref=e539]: item 536 - - listitem [ref=e540]: item 537 - - listitem [ref=e541]: item 538 - - listitem [ref=e542]: item 539 - - listitem [ref=e543]: item 540 - - listitem [ref=e544]: item 541 - - listitem [ref=e545]: item 542 - - listitem [ref=e546]: item 543 - - listitem [ref=e547]: item 544 - - listitem [ref=e548]: item 545 - - listitem [ref=e549]: item 546 - - listitem [ref=e550]: item 547 - - listitem [ref=e551]: item 548 - - listitem [ref=e552]: item 549 - - listitem [ref=e553]: item 550 - - listitem [ref=e554]: item 551 - - listitem [ref=e555]: item 552 - - listitem [ref=e556]: item 553 - - listitem [ref=e557]: item 554 - - listitem [ref=e558]: item 555 - - listitem [ref=e559]: item 556 - - listitem [ref=e560]: item 557 - - listitem [ref=e561]: item 558 - - listitem [ref=e562]: item 559 - - listitem [ref=e563]: item 560 - - listitem [ref=e564]: item 561 - - listitem [ref=e565]: item 562 - - listitem [ref=e566]: item 563 - - listitem [ref=e567]: item 564 - - listitem [ref=e568]: item 565 - - listitem [ref=e569]: item 566 - - listitem [ref=e570]: item 567 - - listitem [ref=e571]: item 568 - - listitem [ref=e572]: item 569 - - listitem [ref=e573]: item 570 - - listitem [ref=e574]: item 571 - - listitem [ref=e575]: item 572 - - listitem [ref=e576]: item 573 - - listitem [ref=e577]: item 574 - - listitem [ref=e578]: item 575 - - listitem [ref=e579]: item 576 - - listitem [ref=e580]: item 577 - - listitem [ref=e581]: item 578 - - listitem [ref=e582]: item 579 - - listitem [ref=e583]: item 580 - - listitem [ref=e584]: item 581 - - listitem [ref=e585]: item 582 - - listitem [ref=e586]: item 583 - - listitem [ref=e587]: item 584 - - listitem [ref=e588]: item 585 - - listitem [ref=e589]: item 586 - - listitem [ref=e590]: item 587 - - listitem [ref=e591]: item 588 - - listitem [ref=e592]: item 589 - - listitem [ref=e593]: item 590 - - listitem [ref=e594]: item 591 - - listitem [ref=e595]: item 592 - - listitem [ref=e596]: item 593 - - listitem [ref=e597]: item 594 - - listitem [ref=e598]: item 595 - - listitem [ref=e599]: item 596 - - listitem [ref=e600]: item 597 - - listitem [ref=e601]: item 598 - - listitem [ref=e602]: item 599 - - listitem [ref=e603]: item 600 - - listitem [ref=e604]: item 601 - - listitem [ref=e605]: item 602 - - listitem [ref=e606]: item 603 - - listitem [ref=e607]: item 604 - - listitem [ref=e608]: item 605 - - listitem [ref=e609]: item 606 - - listitem [ref=e610]: item 607 - - listitem [ref=e611]: item 608 - - listitem [ref=e612]: item 609 - - listitem [ref=e613]: item 610 - - listitem [ref=e614]: item 611 - - listitem [ref=e615]: item 612 - - listitem [ref=e616]: item 613 - - listitem [ref=e617]: item 614 - - listitem [ref=e618]: item 615 - - listitem [ref=e619]: item 616 - - listitem [ref=e620]: item 617 - - listitem [ref=e621]: item 618 - - listitem [ref=e622]: item 619 - - listitem [ref=e623]: item 620 - - listitem [ref=e624]: item 621 - - listitem [ref=e625]: item 622 - - listitem [ref=e626]: item 623 - - listitem [ref=e627]: item 624 - - listitem [ref=e628]: item 625 - - listitem [ref=e629]: item 626 - - listitem [ref=e630]: item 627 - - listitem [ref=e631]: item 628 - - listitem [ref=e632]: item 629 - - listitem [ref=e633]: item 630 - - listitem [ref=e634]: item 631 - - listitem [ref=e635]: item 632 - - listitem [ref=e636]: item 633 - - listitem [ref=e637]: item 634 - - listitem [ref=e638]: item 635 - - listitem [ref=e639]: item 636 - - listitem [ref=e640]: item 637 - - listitem [ref=e641]: item 638 - - listitem [ref=e642]: item 639 - - listitem [ref=e643]: item 640 - - listitem [ref=e644]: item 641 - - listitem [ref=e645]: item 642 - - listitem [ref=e646]: item 643 - - listitem [ref=e647]: item 644 - - listitem [ref=e648]: item 645 - - listitem [ref=e649]: item 646 - - listitem [ref=e650]: item 647 - - listitem [ref=e651]: item 648 - - listitem [ref=e652]: item 649 - - listitem [ref=e653]: item 650 - - listitem [ref=e654]: item 651 - - listitem [ref=e655]: item 652 - - listitem [ref=e656]: item 653 - - listitem [ref=e657]: item 654 - - listitem [ref=e658]: item 655 - - listitem [ref=e659]: item 656 - - listitem [ref=e660]: item 657 - - listitem [ref=e661]: item 658 - - listitem [ref=e662]: item 659 - - listitem [ref=e663]: item 660 - - listitem [ref=e664]: item 661 - - listitem [ref=e665]: item 662 - - listitem [ref=e666]: item 663 - - listitem [ref=e667]: item 664 - - listitem [ref=e668]: item 665 - - listitem [ref=e669]: item 666 - - listitem [ref=e670]: item 667 - - listitem [ref=e671]: item 668 - - listitem [ref=e672]: item 669 - - listitem [ref=e673]: item 670 - - listitem [ref=e674]: item 671 - - listitem [ref=e675]: item 672 - - listitem [ref=e676]: item 673 - - listitem [ref=e677]: item 674 - - listitem [ref=e678]: item 675 - - listitem [ref=e679]: item 676 - - listitem [ref=e680]: item 677 - - listitem [ref=e681]: item 678 - - listitem [ref=e682]: item 679 - - listitem [ref=e683]: item 680 - - listitem [ref=e684]: item 681 - - listitem [ref=e685]: item 682 - - listitem [ref=e686]: item 683 - - listitem [ref=e687]: item 684 - - listitem [ref=e688]: item 685 - - listitem [ref=e689]: item 686 - - listitem [ref=e690]: item 687 - - listitem [ref=e691]: item 688 - - listitem [ref=e692]: item 689 - - listitem [ref=e693]: item 690 - - listitem [ref=e694]: item 691 - - listitem [ref=e695]: item 692 - - listitem [ref=e696]: item 693 - - listitem [ref=e697]: item 694 - - listitem [ref=e698]: item 695 - - listitem [ref=e699]: item 696 - - listitem [ref=e700]: item 697 - - listitem [ref=e701]: item 698 - - listitem [ref=e702]: item 699 - - listitem [ref=e703]: item 700 - - listitem [ref=e704]: item 701 - - listitem [ref=e705]: item 702 - - listitem [ref=e706]: item 703 - - listitem [ref=e707]: item 704 - - listitem [ref=e708]: item 705 - - listitem [ref=e709]: item 706 - - listitem [ref=e710]: item 707 - - listitem [ref=e711]: item 708 - - listitem [ref=e712]: item 709 - - listitem [ref=e713]: item 710 - - listitem [ref=e714]: item 711 - - listitem [ref=e715]: item 712 - - listitem [ref=e716]: item 713 - - listitem [ref=e717]: item 714 - - listitem [ref=e718]: item 715 - - listitem [ref=e719]: item 716 - - listitem [ref=e720]: item 717 - - listitem [ref=e721]: item 718 - - listitem [ref=e722]: item 719 - - listitem [ref=e723]: item 720 - - listitem [ref=e724]: item 721 - - listitem [ref=e725]: item 722 - - listitem [ref=e726]: item 723 - - listitem [ref=e727]: item 724 - - listitem [ref=e728]: item 725 - - listitem [ref=e729]: item 726 - - listitem [ref=e730]: item 727 - - listitem [ref=e731]: item 728 - - listitem [ref=e732]: item 729 - - listitem [ref=e733]: item 730 - - listitem [ref=e734]: item 731 - - listitem [ref=e735]: item 732 - - listitem [ref=e736]: item 733 - - listitem [ref=e737]: item 734 - - listitem [ref=e738]: item 735 - - listitem [ref=e739]: item 736 - - listitem [ref=e740]: item 737 - - listitem [ref=e741]: item 738 - - listitem [ref=e742]: item 739 - - listitem [ref=e743]: item 740 - - listitem [ref=e744]: item 741 - - listitem [ref=e745]: item 742 - - listitem [ref=e746]: item 743 - - listitem [ref=e747]: item 744 - - listitem [ref=e748]: item 745 - - listitem [ref=e749]: item 746 - - listitem [ref=e750]: item 747 - - listitem [ref=e751]: item 748 - - listitem [ref=e752]: item 749 - - listitem [ref=e753]: item 750 - - listitem [ref=e754]: item 751 - - listitem [ref=e755]: item 752 - - listitem [ref=e756]: item 753 - - listitem [ref=e757]: item 754 - - listitem [ref=e758]: item 755 - - listitem [ref=e759]: item 756 - - listitem [ref=e760]: item 757 - - listitem [ref=e761]: item 758 - - listitem [ref=e762]: item 759 - - listitem [ref=e763]: item 760 - - listitem [ref=e764]: item 761 - - listitem [ref=e765]: item 762 - - listitem [ref=e766]: item 763 - - listitem [ref=e767]: item 764 - - listitem [ref=e768]: item 765 - - listitem [ref=e769]: item 766 - - listitem [ref=e770]: item 767 - - listitem [ref=e771]: item 768 - - listitem [ref=e772]: item 769 - - listitem [ref=e773]: item 770 - - listitem [ref=e774]: item 771 - - listitem [ref=e775]: item 772 - - listitem [ref=e776]: item 773 - - listitem [ref=e777]: item 774 - - listitem [ref=e778]: item 775 - - listitem [ref=e779]: item 776 - - listitem [ref=e780]: item 777 - - listitem [ref=e781]: item 778 - - listitem [ref=e782]: item 779 - - listitem [ref=e783]: item 780 - - listitem [ref=e784]: item 781 - - listitem [ref=e785]: item 782 - - listitem [ref=e786]: item 783 - - listitem [ref=e787]: item 784 - - listitem [ref=e788]: item 785 - - listitem [ref=e789]: item 786 - - listitem [ref=e790]: item 787 - - listitem [ref=e791]: item 788 - - listitem [ref=e792]: item 789 - - listitem [ref=e793]: item 790 - - listitem [ref=e794]: item 791 - - listitem [ref=e795]: item 792 - - listitem [ref=e796]: item 793 - - listitem [ref=e797]: item 794 - - listitem [ref=e798]: item 795 - - listitem [ref=e799]: item 796 - - listitem [ref=e800]: item 797 - - listitem [ref=e801]: item 798 - - listitem [ref=e802]: item 799 - - listitem [ref=e803]: item 800 - - listitem [ref=e804]: item 801 - - listitem [ref=e805]: item 802 - - listitem [ref=e806]: item 803 - - listitem [ref=e807]: item 804 - - listitem [ref=e808]: item 805 - - listitem [ref=e809]: item 806 - - listitem [ref=e810]: item 807 - - listitem [ref=e811]: item 808 - - listitem [ref=e812]: item 809 - - listitem [ref=e813]: item 810 - - listitem [ref=e814]: item 811 - - listitem [ref=e815]: item 812 - - listitem [ref=e816]: item 813 - - listitem [ref=e817]: item 814 - - listitem [ref=e818]: item 815 - - listitem [ref=e819]: item 816 - - listitem [ref=e820]: item 817 - - listitem [ref=e821]: item 818 - - listitem [ref=e822]: item 819 - - listitem [ref=e823]: item 820 - - listitem [ref=e824]: item 821 - - listitem [ref=e825]: item 822 - - listitem [ref=e826]: item 823 - - listitem [ref=e827]: item 824 - - listitem [ref=e828]: item 825 - - listitem [ref=e829]: item 826 - - listitem [ref=e830]: item 827 - - listitem [ref=e831]: item 828 - - listitem [ref=e832]: item 829 - - listitem [ref=e833]: item 830 - - listitem [ref=e834]: item 831 - - listitem [ref=e835]: item 832 - - listitem [ref=e836]: item 833 - - listitem [ref=e837]: item 834 - - listitem [ref=e838]: item 835 - - listitem [ref=e839]: item 836 - - listitem [ref=e840]: item 837 - - listitem [ref=e841]: item 838 - - listitem [ref=e842]: item 839 - - listitem [ref=e843]: item 840 - - listitem [ref=e844]: item 841 - - listitem [ref=e845]: item 842 - - listitem [ref=e846]: item 843 - - listitem [ref=e847]: item 844 - - listitem [ref=e848]: item 845 - - listitem [ref=e849]: item 846 - - listitem [ref=e850]: item 847 - - listitem [ref=e851]: item 848 - - listitem [ref=e852]: item 849 - - listitem [ref=e853]: item 850 - - listitem [ref=e854]: item 851 - - listitem [ref=e855]: item 852 - - listitem [ref=e856]: item 853 - - listitem [ref=e857]: item 854 - - listitem [ref=e858]: item 855 - - listitem [ref=e859]: item 856 - - listitem [ref=e860]: item 857 - - listitem [ref=e861]: item 858 - - listitem [ref=e862]: item 859 - - listitem [ref=e863]: item 860 - - listitem [ref=e864]: item 861 - - listitem [ref=e865]: item 862 - - listitem [ref=e866]: item 863 - - listitem [ref=e867]: item 864 - - listitem [ref=e868]: item 865 - - listitem [ref=e869]: item 866 - - listitem [ref=e870]: item 867 - - listitem [ref=e871]: item 868 - - listitem [ref=e872]: item 869 - - listitem [ref=e873]: item 870 - - listitem [ref=e874]: item 871 - - listitem [ref=e875]: item 872 - - listitem [ref=e876]: item 873 - - listitem [ref=e877]: item 874 - - listitem [ref=e878]: item 875 - - listitem [ref=e879]: item 876 - - listitem [ref=e880]: item 877 - - listitem [ref=e881]: item 878 - - listitem [ref=e882]: item 879 - - listitem [ref=e883]: item 880 - - listitem [ref=e884]: item 881 - - listitem [ref=e885]: item 882 - - listitem [ref=e886]: item 883 - - listitem [ref=e887]: item 884 - - listitem [ref=e888]: item 885 - - listitem [ref=e889]: item 886 - - listitem [ref=e890]: item 887 - - listitem [ref=e891]: item 888 - - listitem [ref=e892]: item 889 - - listitem [ref=e893]: item 890 - - listitem [ref=e894]: item 891 - - listitem [ref=e895]: item 892 - - listitem [ref=e896]: item 893 - - listitem [ref=e897]: item 894 - - listitem [ref=e898]: item 895 - - listitem [ref=e899]: item 896 - - listitem [ref=e900]: item 897 - - listitem [ref=e901]: item 898 - - listitem [ref=e902]: item 899 - - listitem [ref=e903]: item 900 - - listitem [ref=e904]: item 901 - - listitem [ref=e905]: item 902 - - listitem [ref=e906]: item 903 - - listitem [ref=e907]: item 904 - - listitem [ref=e908]: item 905 - - listitem [ref=e909]: item 906 - - listitem [ref=e910]: item 907 - - listitem [ref=e911]: item 908 - - listitem [ref=e912]: item 909 - - listitem [ref=e913]: item 910 - - listitem [ref=e914]: item 911 - - listitem [ref=e915]: item 912 - - listitem [ref=e916]: item 913 - - listitem [ref=e917]: item 914 - - listitem [ref=e918]: item 915 - - listitem [ref=e919]: item 916 - - listitem [ref=e920]: item 917 - - listitem [ref=e921]: item 918 - - listitem [ref=e922]: item 919 - - listitem [ref=e923]: item 920 - - listitem [ref=e924]: item 921 - - listitem [ref=e925]: item 922 - - listitem [ref=e926]: item 923 - - listitem [ref=e927]: item 924 - - listitem [ref=e928]: item 925 - - listitem [ref=e929]: item 926 - - listitem [ref=e930]: item 927 - - listitem [ref=e931]: item 928 - - listitem [ref=e932]: item 929 - - listitem [ref=e933]: item 930 - - listitem [ref=e934]: item 931 - - listitem [ref=e935]: item 932 - - listitem [ref=e936]: item 933 - - listitem [ref=e937]: item 934 - - listitem [ref=e938]: item 935 - - listitem [ref=e939]: item 936 - - listitem [ref=e940]: item 937 - - listitem [ref=e941]: item 938 - - listitem [ref=e942]: item 939 - - listitem [ref=e943]: item 940 - - listitem [ref=e944]: item 941 - - listitem [ref=e945]: item 942 - - listitem [ref=e946]: item 943 - - listitem [ref=e947]: item 944 - - listitem [ref=e948]: item 945 - - listitem [ref=e949]: item 946 - - listitem [ref=e950]: item 947 - - listitem [ref=e951]: item 948 - - listitem [ref=e952]: item 949 - - listitem [ref=e953]: item 950 - - listitem [ref=e954]: item 951 - - listitem [ref=e955]: item 952 - - listitem [ref=e956]: item 953 - - listitem [ref=e957]: item 954 - - listitem [ref=e958]: item 955 - - listitem [ref=e959]: item 956 - - listitem [ref=e960]: item 957 - - listitem [ref=e961]: item 958 - - listitem [ref=e962]: item 959 - - listitem [ref=e963]: item 960 - - listitem [ref=e964]: item 961 - - listitem [ref=e965]: item 962 - - listitem [ref=e966]: item 963 - - listitem [ref=e967]: item 964 - - listitem [ref=e968]: item 965 - - listitem [ref=e969]: item 966 - - listitem [ref=e970]: item 967 - - listitem [ref=e971]: item 968 - - listitem [ref=e972]: item 969 - - listitem [ref=e973]: item 970 - - listitem [ref=e974]: item 971 - - listitem [ref=e975]: item 972 - - listitem [ref=e976]: item 973 - - listitem [ref=e977]: item 974 - - listitem [ref=e978]: item 975 - - listitem [ref=e979]: item 976 - - listitem [ref=e980]: item 977 - - listitem [ref=e981]: item 978 - - listitem [ref=e982]: item 979 - - listitem [ref=e983]: item 980 - - listitem [ref=e984]: item 981 - - listitem [ref=e985]: item 982 - - listitem [ref=e986]: item 983 - - listitem [ref=e987]: item 984 - - listitem [ref=e988]: item 985 - - listitem [ref=e989]: item 986 - - listitem [ref=e990]: item 987 - - listitem [ref=e991]: item 988 - - listitem [ref=e992]: item 989 - - listitem [ref=e993]: item 990 - - listitem [ref=e994]: item 991 - - listitem [ref=e995]: item 992 - - listitem [ref=e996]: item 993 - - listitem [ref=e997]: item 994 - - listitem [ref=e998]: item 995 - - listitem [ref=e999]: item 996 - - listitem [ref=e1000]: item 997 - - listitem [ref=e1001]: item 998 - - listitem [ref=e1002]: item 999 -``` - -# Test source - -```ts - 1 | import { test, expect } from '@playwright/test'; - 2 | import fs from 'fs'; - 3 | import path from 'path'; - 4 | - 5 | test('modular unit tests and benchmark', async ({ page }) => { - 6 | page.on('console', msg => console.log('BROWSER LOG:', msg.text())); - 7 | await page.goto('http://localhost:8081/test/index.html'); - 8 | - 9 | await page.waitForFunction(() => window.testStatus !== undefined, { timeout: 10000 }); - 10 | const status = await page.evaluate(() => window.testStatus); - 11 | expect(status).toBe('passed'); - 12 | - 13 | // Read benchmarks from TEST.md - 14 | const testMd = fs.readFileSync(path.join(process.cwd(), 'TEST.md'), 'utf-8'); - 15 | const getBench = (name) => { - 16 | const match = testMd.match(new RegExp(`\\*\\*${name}\\*\\*\\s*\\|\\s*([\\d.]+)`)); - 17 | return match ? parseFloat(match[1]) : null; - 18 | }; - 19 | const baseInitial = getBench('首次渲染 \\(1000 items\\)'); - 20 | const baseUpdate = getBench('浅更新 \\(Shallow Update\\)'); - 21 | - 22 | // Benchmark: Large list rendering - 23 | const renderTime = await page.evaluate(async () => { - 24 | const start = performance.now(); - 25 | document.body.innerHTML = ` - 26 |
    - 27 | - 30 |
- 31 | `; - 32 | const items = []; - 33 | for(let i=0; i<1000; i++) items.push({val: 'item ' + i}); - 34 | window.state.benchItems = items; - 35 | const { RefreshState } = await import('@web/state'); - 36 | RefreshState(document.documentElement); - 37 | return performance.now() - start; - 38 | }); - 39 | console.log(`BENCHMARK: 1000 items initial render: ${renderTime.toFixed(2)}ms`); -> 40 | if (baseInitial) expect(renderTime).toBeLessThan(baseInitial * 1.2); - | ^ Error: expect(received).toBeLessThan(expected) - 41 | - 42 | // Benchmark: Large list update - 43 | const updateTime = await page.evaluate(async () => { - 44 | const start = performance.now(); - 45 | window.state.benchItems[0].val = 'updated'; - 46 | window.state.benchItems = [...window.state.benchItems]; - 47 | return performance.now() - start; - 48 | }); - 49 | console.log(`BENCHMARK: 1000 items update (shallow): ${updateTime.toFixed(2)}ms`); - 50 | if (baseUpdate) expect(updateTime).toBeLessThan(baseUpdate * 1.2); - 51 | - 52 | // Extreme Data Test - 53 | await page.evaluate(async () => { - 54 | const { RefreshState } = await import('@web/state'); - 55 | document.body.innerHTML = '
'; - 56 | window.state.extreme = null; - 57 | RefreshState(document.getElementById('extreme')); - 58 | window.state.extreme = undefined; - 59 | window.state.extreme = { a: 1 }; - 60 | window.state.extreme = [1, 2]; - 61 | window.state.extreme = "not iterable"; - 62 | }); - 63 | }); - 64 | -``` \ No newline at end of file diff --git a/test/dom.test.js b/test/dom.test.js index fe5da61..f11330b 100644 --- a/test/dom.test.js +++ b/test/dom.test.js @@ -1,6 +1,6 @@ // test/dom.test.js -import { RefreshState, $, $$ } from '@web/state'; -import { NewState } from '@web/state'; +import { RefreshState, $, $$ } from '../src/index.js'; +import { NewState } from '../src/index.js'; export async function testDom() { console.log('Testing dom.js...'); @@ -65,15 +65,16 @@ export async function testDom() { if (items.length !== 1 || items[0].textContent !== 'C') throw new Error('$each removal failed'); // 4. Two-way binding (bind) + console.log('Testing $bind...'); document.body.innerHTML = ``; - state.val = 'initial'; + window.state = NewState({ val: 'initial' }); RefreshState(document.documentElement); await wait(); const input = $('#input-test'); if (input.value !== 'initial') throw new Error('$bind initial value failed'); input.value = 'changed'; - input.dispatchEvent(new Event('input')); + input.dispatchEvent(new Event('input', { bubbles: true })); if (state.val !== 'changed') throw new Error('$bind write-back failed'); // 5. Unbinding cleanup (mock check) @@ -83,8 +84,53 @@ export async function testDom() { state.show = true; const ifNode = $('#if-content'); state.show = false; // Trigger _unbindTree via _clearRenderedNodes -> remove() -> MutationObserver (or manual in some cases) - // Actually MutationObserver handles _unbindTree in index.js. + // 6. Double evaluation ($$ prefix) + console.log('Testing double evaluation ($$)...'); + document.body.innerHTML = ` +
+
Dynamic Visible
+
+ `; + const doubleState = NewState({ + innerExp: 'state.innerShow', + innerShow: false + }); + window.state = doubleState; + document.documentElement._thisObj = doubleState; + + RefreshState(document.documentElement); + await wait(); + if ($('#inner-node')) throw new Error('$$if failed: should be hidden initially'); + + console.log('Enabling inner node...'); + doubleState.innerShow = true; + RefreshState(document.documentElement); + await wait(); + const inner = $('#inner-node'); + if (!inner) throw new Error('$$if failed: should be visible after innerShow=true'); + console.log('Inner node visible:', inner.textContent); + + // 7. Nested $$if + console.log('Testing nested $$if...'); + document.body.innerHTML = ` +
+ +
+ `; + doubleState.outer = true; + doubleState.innerShow = false; + RefreshState(document.documentElement); + await wait(); + if ($('#nested-inner')) throw new Error('nested $$if failed: should be hidden initially'); + + doubleState.innerShow = true; + RefreshState(document.documentElement); + await wait(); + if (!$('#nested-inner')) throw new Error('nested $$if failed: should be visible after update'); + console.log('dom.js tests passed'); return true; } diff --git a/test/priority.test.js b/test/priority.test.js index 3a79673..145afb9 100644 --- a/test/priority.test.js +++ b/test/priority.test.js @@ -34,10 +34,11 @@ export async function testPriority() { const state = { outerVal: 'hello' }; const outer = document.createElement('div'); outer.id = 'outer'; + outer._thisObj = state; // Rule: Set _thisObj on root before scanning if not in document outer.innerHTML = ``; - document.body.appendChild(outer); RefreshState(outer, { thisObj: state }); + document.body.appendChild(outer); if (capturedVal !== 'hello') { throw new Error(`this. replacement failed: capturedVal was ${capturedVal}, expected 'hello'`);