feat(state): 合并组件的 class 和 样式,修复双重指令等 Bug(by AI) By: AICoder
This commit is contained in:
parent
b4f7ca6468
commit
1e46c8a76e
16
CHANGELOG.md
16
CHANGELOG.md
@ -1,5 +1,21 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## v1.0.22 (2026-07-04)
|
||||||
|
|
||||||
|
### 核心增强与样式合并优化
|
||||||
|
- **双向样式智能合并与排序**:
|
||||||
|
- 重构了组件合并时的类名/样式排列机制。现在应用层(外部宿主)写的 `class/style` 将始终按高优先级排在组件内部样式的后侧(右侧),结构严格符合 `[内部静态, 内部动态, 外部动态, 外部静态]`。
|
||||||
|
- 引入了**动态模板字符串合并(Template String Merging)**算法。当组件内外同时存在 `$class`/`$style` 或 `st-class`/`st-style` 绑定时,不采用覆写替换,而是将其物理合并为单一大模板字符串,确保各动态表达式能在正确的上下文(通过 parent 链)中独立安全求值。
|
||||||
|
- 在 `_updateBinding` 渲染分支中加入了对 class 和 style 的**惰性捕获备份(Lazy Cache)**机制,只在首次更新时从 DOM 读取备份静态基准值,大幅减少不必要的 DOM 属性重置。
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
- **双重解析指令修复**:
|
||||||
|
- 修复了自 v1.0.19 大重构后,由于硬编码属性匹配列表导致双重解析指令(`$$if` / `$$each` 等)全系失效的 Bug。已恢复对非 `TEMPLATE` 节点的自动包裹提升以及对 `TEMPLATE` 节点上双重指令的正常提取。
|
||||||
|
- **自动化测试缺陷修正**:
|
||||||
|
- 修正了在测试中因重写 `document.body.innerHTML` 导致 `body` 内已被挂载的 `template` 元素被误抹去的严重缺陷。
|
||||||
|
- 修正了 `testPriority` 全局 `window.state` 缺失导致的空值求值缺陷,补齐了由于 `MutationObserver` 异步微任务渲染时序所需的 `await` 等待。
|
||||||
|
- 修正了 `testMerging` 里非法 CSS 属性赋予 JS 动态绑定 `$style` 导致的语法解析错误,并补全了对同名动态绑定合并与优先级的断言。
|
||||||
|
|
||||||
## v1.0.17 (2026-06-05)
|
## v1.0.17 (2026-06-05)
|
||||||
|
|
||||||
### 重大变更 (Philosophical Restoration)
|
### 重大变更 (Philosophical Restoration)
|
||||||
|
|||||||
51
dist/state.js
vendored
51
dist/state.js
vendored
@ -222,12 +222,26 @@
|
|||||||
if (attr.name === "style") {
|
if (attr.name === "style") {
|
||||||
if (to.hasAttribute("style")) to.setAttribute("style", `${attr.value}; ${to.getAttribute("style")}`);
|
if (to.hasAttribute("style")) to.setAttribute("style", `${attr.value}; ${to.getAttribute("style")}`);
|
||||||
else to.setAttribute("style", attr.value);
|
else to.setAttribute("style", attr.value);
|
||||||
} else if (!to.hasAttribute(attr.name)) {
|
} else if (to.hasAttribute(attr.name)) {
|
||||||
|
const isClass = ["$class", "st-class"].includes(attr.name);
|
||||||
|
const isStyle = ["$style", "st-style"].includes(attr.name);
|
||||||
|
if (isClass || isStyle) {
|
||||||
|
const oldVal = to.getAttribute(attr.name);
|
||||||
|
const newVal = attr.value;
|
||||||
|
const delimiter = isClass ? " " : "; ";
|
||||||
|
const oldExpr = oldVal.includes("${") ? oldVal : `\${${oldVal}}`;
|
||||||
|
const newExpr = newVal.includes("${") ? newVal : `\${${newVal}}`;
|
||||||
|
to.setAttribute(attr.name, `${newExpr}${delimiter}${oldExpr}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
to.setAttribute(attr.name, attr.value);
|
to.setAttribute(attr.name, attr.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const toClassList = [...to.classList];
|
||||||
|
to.className = "";
|
||||||
to.classList.add(...from.classList);
|
to.classList.add(...from.classList);
|
||||||
|
to.classList.add(...toClassList);
|
||||||
const target = to.tagName === "TEMPLATE" ? to.content : to;
|
const target = to.tagName === "TEMPLATE" ? to.content : to;
|
||||||
const sourceNodes = from.tagName === "TEMPLATE" ? from.content.childNodes : from.childNodes;
|
const sourceNodes = from.tagName === "TEMPLATE" ? from.content.childNodes : from.childNodes;
|
||||||
Array.from(sourceNodes).forEach((child) => target.appendChild(child));
|
Array.from(sourceNodes).forEach((child) => target.appendChild(child));
|
||||||
@ -318,6 +332,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
_setActiveBinding(null);
|
_setActiveBinding(null);
|
||||||
|
binding.lastResult = result;
|
||||||
if (binding.prop) {
|
if (binding.prop) {
|
||||||
const prop = binding.prop;
|
const prop = binding.prop;
|
||||||
let o = node;
|
let o = node;
|
||||||
@ -441,7 +456,21 @@
|
|||||||
if (attr === "text") node.textContent = result ?? "";
|
if (attr === "text") node.textContent = result ?? "";
|
||||||
else if (attr === "html") node.innerHTML = result ?? "";
|
else if (attr === "html") node.innerHTML = result ?? "";
|
||||||
else if (node.tagName === "IMG" && attr === "src" && result.includes(".svg")) node.setAttribute("_src", result ?? "");
|
else if (node.tagName === "IMG" && attr === "src" && result.includes(".svg")) node.setAttribute("_src", result ?? "");
|
||||||
else node.setAttribute(attr, result ?? "");
|
else if (attr === "class") {
|
||||||
|
if (node._staticClasses === void 0) {
|
||||||
|
node._staticClasses = node.getAttribute("class") || "";
|
||||||
|
}
|
||||||
|
const staticClasses = node._staticClasses;
|
||||||
|
const finalClass = result ? staticClasses ? `${result} ${staticClasses}` : result : staticClasses;
|
||||||
|
node.setAttribute("class", finalClass.trim().replace(/\s+/g, " "));
|
||||||
|
} else if (attr === "style") {
|
||||||
|
if (node._staticStyles === void 0) {
|
||||||
|
node._staticStyles = node.getAttribute("style") || "";
|
||||||
|
}
|
||||||
|
const staticStyles = node._staticStyles;
|
||||||
|
const finalStyle = result ? staticStyles ? `${result}; ${staticStyles}` : result : staticStyles;
|
||||||
|
node.setAttribute("style", finalStyle.trim().replace(/;;+/g, ";").replace(/^;+\s*|;\s*$/g, ""));
|
||||||
|
} else node.setAttribute(attr, result ?? "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -486,9 +515,9 @@
|
|||||||
}
|
}
|
||||||
let attrs = [];
|
let attrs = [];
|
||||||
if (node.tagName === "TEMPLATE") {
|
if (node.tagName === "TEMPLATE") {
|
||||||
["$if", "$each", "st-if", "st-each"].forEach((n) => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n)));
|
["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"].forEach((n) => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n)));
|
||||||
} else {
|
} 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-")) && !["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"].includes(a.name) || a.name.includes("."));
|
||||||
}
|
}
|
||||||
if (node._thisObj && scanObj.thisObj && node._thisObj !== scanObj.thisObj) node._thisObj.parent = scanObj.thisObj;
|
if (node._thisObj && scanObj.thisObj && node._thisObj !== scanObj.thisObj) node._thisObj.parent = scanObj.thisObj;
|
||||||
if (!node._thisObj) node._thisObj = scanObj.thisObj || null;
|
if (!node._thisObj) node._thisObj = scanObj.thisObj || null;
|
||||||
@ -552,9 +581,9 @@
|
|||||||
});
|
});
|
||||||
node._stTranslated = true;
|
node._stTranslated = true;
|
||||||
}
|
}
|
||||||
if (node.tagName !== "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("$each") || node.hasAttribute("st-if") || node.hasAttribute("st-each"))) {
|
if (node.tagName !== "TEMPLATE" && (node.hasAttribute("$if") || node.hasAttribute("$each") || node.hasAttribute("st-if") || node.hasAttribute("st-each") || node.hasAttribute("$$if") || node.hasAttribute("$$each") || node.hasAttribute("st-st-if") || node.hasAttribute("st-st-each"))) {
|
||||||
const template = document.createElement("TEMPLATE");
|
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) => ["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"].includes(attr.name) || (node.hasAttribute("$each") || node.hasAttribute("st-each") || node.hasAttribute("$$each") || node.hasAttribute("st-st-each")) && ["as", "index"].includes(attr.name));
|
||||||
attrs.forEach((attr) => {
|
attrs.forEach((attr) => {
|
||||||
template.setAttribute(attr.name, attr.value);
|
template.setAttribute(attr.name, attr.value);
|
||||||
node.removeAttribute(attr.name);
|
node.removeAttribute(attr.name);
|
||||||
@ -565,13 +594,13 @@
|
|||||||
_scanTree(template, scanObj);
|
_scanTree(template, scanObj);
|
||||||
return;
|
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 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) => ["$if", "$each", "st-if", "st-each", "$$if", "$$each", "st-st-if", "st-st-each"].includes(attr2.name));
|
||||||
const attr = attrs[attrs.length - 1];
|
const attr = attrs[attrs.length - 1];
|
||||||
template.setAttribute(attr.name, attr.value);
|
template.setAttribute(attr.name, attr.value);
|
||||||
node.removeAttribute(attr.name);
|
node.removeAttribute(attr.name);
|
||||||
if (attr.name === "$each" || attr.name === "st-each") {
|
if (["$each", "st-each", "$$each", "st-st-each"].includes(attr.name)) {
|
||||||
Array.from(node.attributes).filter((attr2) => ["as", "index"].includes(attr2.name)).forEach((attr2) => {
|
Array.from(node.attributes).filter((attr2) => ["as", "index"].includes(attr2.name)).forEach((attr2) => {
|
||||||
template.setAttribute(attr2.name, attr2.value);
|
template.setAttribute(attr2.name, attr2.value);
|
||||||
node.removeAttribute(attr2.name);
|
node.removeAttribute(attr2.name);
|
||||||
@ -658,5 +687,9 @@
|
|||||||
exports2.State = State;
|
exports2.State = State;
|
||||||
exports2.Util = Util;
|
exports2.Util = Util;
|
||||||
exports2.__unsafeRefreshState = __unsafeRefreshState;
|
exports2.__unsafeRefreshState = __unsafeRefreshState;
|
||||||
|
exports2._returnCode = _returnCode;
|
||||||
|
exports2._runCode = _runCode;
|
||||||
|
exports2.onNotifyUpdate = _onNotifyUpdate;
|
||||||
|
exports2.setActiveBinding = _setActiveBinding;
|
||||||
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
|
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
|
||||||
});
|
});
|
||||||
|
|||||||
2
dist/state.min.js
vendored
2
dist/state.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@apigo.cc/state",
|
"name": "@apigo.cc/state",
|
||||||
"version": "1.0.21",
|
"version": "1.0.22",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/state.js",
|
"main": "dist/state.js",
|
||||||
"module": "dist/state.js",
|
"module": "dist/state.js",
|
||||||
|
|||||||
@ -42,12 +42,26 @@ export function _mergeNode(from, to, scanObj, exists = {}) {
|
|||||||
if (attr.name === 'style') {
|
if (attr.name === 'style') {
|
||||||
if (to.hasAttribute('style')) to.setAttribute('style', `${attr.value}; ${to.getAttribute('style')}`);
|
if (to.hasAttribute('style')) to.setAttribute('style', `${attr.value}; ${to.getAttribute('style')}`);
|
||||||
else to.setAttribute('style', attr.value);
|
else to.setAttribute('style', attr.value);
|
||||||
} else if (!to.hasAttribute(attr.name)) {
|
} else if (to.hasAttribute(attr.name)) {
|
||||||
|
const isClass = ['$class', 'st-class'].includes(attr.name);
|
||||||
|
const isStyle = ['$style', 'st-style'].includes(attr.name);
|
||||||
|
if (isClass || isStyle) {
|
||||||
|
const oldVal = to.getAttribute(attr.name);
|
||||||
|
const newVal = attr.value;
|
||||||
|
const delimiter = isClass ? " " : "; ";
|
||||||
|
const oldExpr = oldVal.includes('${') ? oldVal : `\${${oldVal}}`;
|
||||||
|
const newExpr = newVal.includes('${') ? newVal : `\${${newVal}}`;
|
||||||
|
to.setAttribute(attr.name, `${newExpr}${delimiter}${oldExpr}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
to.setAttribute(attr.name, attr.value);
|
to.setAttribute(attr.name, attr.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
const toClassList = [...to.classList];
|
||||||
|
to.className = '';
|
||||||
to.classList.add(...from.classList);
|
to.classList.add(...from.classList);
|
||||||
|
to.classList.add(...toClassList);
|
||||||
|
|
||||||
const target = to.tagName === 'TEMPLATE' ? to.content : to;
|
const target = to.tagName === 'TEMPLATE' ? to.content : to;
|
||||||
const sourceNodes = from.tagName === 'TEMPLATE' ? from.content.childNodes : from.childNodes;
|
const sourceNodes = from.tagName === 'TEMPLATE' ? from.content.childNodes : from.childNodes;
|
||||||
@ -145,6 +159,7 @@ export function _updateBinding(binding) {
|
|||||||
try { result = _returnCode(result, { thisNode: node }, node._thisObj || node, node._ref || null); } catch (e) { }
|
try { result = _returnCode(result, { thisNode: node }, node._thisObj || node, node._ref || null); } catch (e) { }
|
||||||
}
|
}
|
||||||
_setActiveBinding(null);
|
_setActiveBinding(null);
|
||||||
|
binding.lastResult = result;
|
||||||
|
|
||||||
if (binding.prop) {
|
if (binding.prop) {
|
||||||
const prop = binding.prop;
|
const prop = binding.prop;
|
||||||
@ -262,7 +277,21 @@ export function _updateBinding(binding) {
|
|||||||
if (attr === 'text') node.textContent = result ?? '';
|
if (attr === 'text') node.textContent = result ?? '';
|
||||||
else if (attr === 'html') node.innerHTML = result ?? '';
|
else if (attr === 'html') node.innerHTML = result ?? '';
|
||||||
else if (node.tagName === 'IMG' && attr === 'src' && result.includes('.svg')) node.setAttribute('_src', result ?? '');
|
else if (node.tagName === 'IMG' && attr === 'src' && result.includes('.svg')) node.setAttribute('_src', result ?? '');
|
||||||
else node.setAttribute(attr, result ?? '');
|
else if (attr === 'class') {
|
||||||
|
if (node._staticClasses === undefined) {
|
||||||
|
node._staticClasses = node.getAttribute('class') || '';
|
||||||
|
}
|
||||||
|
const staticClasses = node._staticClasses;
|
||||||
|
const finalClass = result ? (staticClasses ? `${result} ${staticClasses}` : result) : staticClasses;
|
||||||
|
node.setAttribute('class', finalClass.trim().replace(/\s+/g, ' '));
|
||||||
|
} else if (attr === 'style') {
|
||||||
|
if (node._staticStyles === undefined) {
|
||||||
|
node._staticStyles = node.getAttribute('style') || '';
|
||||||
|
}
|
||||||
|
const staticStyles = node._staticStyles;
|
||||||
|
const finalStyle = result ? (staticStyles ? `${result}; ${staticStyles}` : result) : staticStyles;
|
||||||
|
node.setAttribute('style', finalStyle.trim().replace(/;;+/g, ';').replace(/^;+\s*|;\s*$/g, ''));
|
||||||
|
} else node.setAttribute(attr, result ?? '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -309,11 +338,12 @@ export function _parseNode(node, scanObj) {
|
|||||||
|
|
||||||
let attrs = [];
|
let attrs = [];
|
||||||
if (node.tagName === 'TEMPLATE') {
|
if (node.tagName === 'TEMPLATE') {
|
||||||
['$if', '$each', 'st-if', 'st-each'].forEach(n => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n)));
|
['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each'].forEach(n => node.hasAttribute(n) && attrs.push(node.getAttributeNode(n)));
|
||||||
} else {
|
} 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-')) && !['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each'].includes(a.name) || a.name.includes('.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (node._thisObj && scanObj.thisObj && node._thisObj !== scanObj.thisObj) node._thisObj.parent = scanObj.thisObj;
|
if (node._thisObj && scanObj.thisObj && node._thisObj !== scanObj.thisObj) node._thisObj.parent = scanObj.thisObj;
|
||||||
if (!node._thisObj) node._thisObj = scanObj.thisObj || null;
|
if (!node._thisObj) node._thisObj = scanObj.thisObj || null;
|
||||||
if (!node._ref) node._ref = scanObj.extendVars || {};
|
if (!node._ref) node._ref = scanObj.extendVars || {};
|
||||||
@ -374,9 +404,9 @@ export const _scanTree = (node, scanObj = {}) => {
|
|||||||
node._stTranslated = true;
|
node._stTranslated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.tagName !== 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('$each') || node.hasAttribute('st-if') || node.hasAttribute('st-each'))) {
|
if (node.tagName !== 'TEMPLATE' && (node.hasAttribute('$if') || node.hasAttribute('$each') || node.hasAttribute('st-if') || node.hasAttribute('st-each') || node.hasAttribute('$$if') || node.hasAttribute('$$each') || node.hasAttribute('st-st-if') || node.hasAttribute('st-st-each'))) {
|
||||||
const template = document.createElement('TEMPLATE');
|
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 => ['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each'].includes(attr.name) || ((node.hasAttribute('$each') || node.hasAttribute('st-each') || node.hasAttribute('$$each') || node.hasAttribute('st-st-each')) && ['as', 'index'].includes(attr.name)));
|
||||||
attrs.forEach(attr => {
|
attrs.forEach(attr => {
|
||||||
template.setAttribute(attr.name, attr.value);
|
template.setAttribute(attr.name, attr.value);
|
||||||
node.removeAttribute(attr.name);
|
node.removeAttribute(attr.name);
|
||||||
@ -388,13 +418,13 @@ export const _scanTree = (node, scanObj = {}) => {
|
|||||||
return;
|
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 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 => ['$if', '$each', 'st-if', 'st-each', '$$if', '$$each', 'st-st-if', 'st-st-each'].includes(attr.name));
|
||||||
const attr = attrs[attrs.length - 1];
|
const attr = attrs[attrs.length - 1];
|
||||||
template.setAttribute(attr.name, attr.value);
|
template.setAttribute(attr.name, attr.value);
|
||||||
node.removeAttribute(attr.name);
|
node.removeAttribute(attr.name);
|
||||||
if (attr.name === '$each' || attr.name === 'st-each') {
|
if (['$each', 'st-each', '$$each', 'st-st-each'].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.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));
|
Array.from(node.content.childNodes).forEach(child => template.content.appendChild(child));
|
||||||
|
|||||||
@ -35,8 +35,8 @@ if (typeof document !== 'undefined') {
|
|||||||
else document.addEventListener('DOMContentLoaded', init, true);
|
else document.addEventListener('DOMContentLoaded', init, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { NewState } from './observer.js';
|
export { NewState, _setActiveBinding as setActiveBinding, _onNotifyUpdate as onNotifyUpdate } from './observer.js';
|
||||||
export { Component, SetTranslator } from './engine.js';
|
export { Component, SetTranslator } from './engine.js';
|
||||||
export { $, $$, Util } from './utils.js';
|
export { $, $$, Util } from './utils.js';
|
||||||
export { Hash, LocalStorage, State } from './core.js';
|
export { Hash, LocalStorage, State, _runCode, _returnCode } from './core.js';
|
||||||
export const __unsafeRefreshState = _scanTree;
|
export const __unsafeRefreshState = _scanTree;
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
{
|
{
|
||||||
"status": "failed",
|
"status": "passed",
|
||||||
"failedTests": [
|
"failedTests": []
|
||||||
"8a84b43f13b676ea22b7-5fcda25ae3a58304c071"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
@ -1,89 +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).toBe(expected) // Object.is equality
|
|
||||||
|
|
||||||
Expected: "passed"
|
|
||||||
Received: "failed"
|
|
||||||
```
|
|
||||||
|
|
||||||
# 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');
|
|
||||||
| ^ Error: expect(received).toBe(expected) // Object.is equality
|
|
||||||
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 | <ul id="bench-list">
|
|
||||||
27 | <template $each="state.benchItems" as="item">
|
|
||||||
28 | <li $text="item.val"></li>
|
|
||||||
29 | </template>
|
|
||||||
30 | </ul>
|
|
||||||
31 | `;
|
|
||||||
32 | const items = [];
|
|
||||||
33 | for(let i=0; i<1000; i++) items.push({val: 'item ' + i});
|
|
||||||
34 | window.state.benchItems = items;
|
|
||||||
35 | const { _unsafeRefreshState } = await import('@apigo.cc/state');
|
|
||||||
36 | _unsafeRefreshState(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);
|
|
||||||
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 { _unsafeRefreshState } = await import('@apigo.cc/state');
|
|
||||||
55 | document.body.innerHTML = '<div id="extreme" $each="state.extreme"></div>';
|
|
||||||
56 | window.state.extreme = null;
|
|
||||||
57 | _unsafeRefreshState(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 |
|
|
||||||
```
|
|
||||||
@ -32,8 +32,8 @@ test('modular unit tests and benchmark', async ({ page }) => {
|
|||||||
const items = [];
|
const items = [];
|
||||||
for(let i=0; i<1000; i++) items.push({val: 'item ' + i});
|
for(let i=0; i<1000; i++) items.push({val: 'item ' + i});
|
||||||
window.state.benchItems = items;
|
window.state.benchItems = items;
|
||||||
const { ___unsafeRefreshState } = await import('@apigo.cc/state');
|
const { __unsafeRefreshState } = window.ApigoState;
|
||||||
___unsafeRefreshState(document.documentElement);
|
__unsafeRefreshState(document.documentElement);
|
||||||
return performance.now() - start;
|
return performance.now() - start;
|
||||||
});
|
});
|
||||||
console.log(`BENCHMARK: 1000 items initial render: ${renderTime.toFixed(2)}ms`);
|
console.log(`BENCHMARK: 1000 items initial render: ${renderTime.toFixed(2)}ms`);
|
||||||
@ -51,10 +51,10 @@ test('modular unit tests and benchmark', async ({ page }) => {
|
|||||||
|
|
||||||
// Extreme Data Test
|
// Extreme Data Test
|
||||||
await page.evaluate(async () => {
|
await page.evaluate(async () => {
|
||||||
const { ___unsafeRefreshState } = await import('@apigo.cc/state');
|
const { __unsafeRefreshState } = window.ApigoState;
|
||||||
document.body.innerHTML = '<div id="extreme" $each="state.extreme"></div>';
|
document.body.innerHTML = '<div id="extreme" $each="state.extreme"></div>';
|
||||||
window.state.extreme = null;
|
window.state.extreme = null;
|
||||||
___unsafeRefreshState(document.getElementById('extreme'));
|
__unsafeRefreshState(document.getElementById('extreme'));
|
||||||
window.state.extreme = undefined;
|
window.state.extreme = undefined;
|
||||||
window.state.extreme = { a: 1 };
|
window.state.extreme = { a: 1 };
|
||||||
window.state.extreme = [1, 2];
|
window.state.extreme = [1, 2];
|
||||||
|
|||||||
@ -1,16 +1,18 @@
|
|||||||
// test/component.test.js
|
// test/component.test.js
|
||||||
window.testComponent = async function() {
|
window.testComponent = async function() {
|
||||||
const { Component, ___unsafeRefreshState, $ } = ApigoState;
|
const { Component, __unsafeRefreshState, $ } = ApigoState;
|
||||||
console.log('Testing component.js...');
|
console.log('Testing component.js...');
|
||||||
|
|
||||||
// 1. Register component
|
// 1. Register component
|
||||||
Component.register('TestComp', container => {
|
Component.register('TestComp', container => {
|
||||||
container.state.msg = 'Component Content';
|
container.state.msg = 'Component Content';
|
||||||
}, document.createRange().createContextualFragment('<div id="comp-inner" $text="this.state.msg"></div>').firstChild);
|
}, document.createRange().createContextualFragment('<div><div id="comp-inner" $text="this.state.msg"></div></div>').firstChild);
|
||||||
|
|
||||||
// 2. Render component
|
// 2. Render component
|
||||||
document.body.innerHTML = '<TestComp id="comp-inst"></TestComp>';
|
const comp = document.createElement('TestComp');
|
||||||
___unsafeRefreshState(document.documentElement);
|
comp.id = 'comp-inst';
|
||||||
|
document.body.appendChild(comp);
|
||||||
|
__unsafeRefreshState(document.documentElement);
|
||||||
|
|
||||||
const instance = $('#comp-inst');
|
const instance = $('#comp-inst');
|
||||||
if (!instance) throw new Error('Component instance not found');
|
if (!instance) throw new Error('Component instance not found');
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// test/dom.test.js
|
// test/dom.test.js
|
||||||
window.testDom = async function() {
|
window.testDom = async function() {
|
||||||
const { ___unsafeRefreshState, $, $$, NewState } = ApigoState;
|
const { __unsafeRefreshState, $, $$, NewState } = ApigoState;
|
||||||
console.log('Testing dom.js...');
|
console.log('Testing dom.js...');
|
||||||
|
|
||||||
const wait = () => new Promise(r => setTimeout(r, 10));
|
const wait = () => new Promise(r => setTimeout(r, 10));
|
||||||
@ -10,7 +10,7 @@ window.testDom = async function() {
|
|||||||
const state = NewState({ msg: 'hello' });
|
const state = NewState({ msg: 'hello' });
|
||||||
window.state = state; // TRY: 确保在非 ESM 环境下 state 全局可见
|
window.state = state; // TRY: 确保在非 ESM 环境下 state 全局可见
|
||||||
document.documentElement._thisObj = { state };
|
document.documentElement._thisObj = { state };
|
||||||
___unsafeRefreshState(document.documentElement);
|
__unsafeRefreshState(document.documentElement);
|
||||||
if ($('#test-text').textContent !== 'hello') throw new Error('$text binding failed');
|
if ($('#test-text').textContent !== 'hello') throw new Error('$text binding failed');
|
||||||
|
|
||||||
state.msg = 'world';
|
state.msg = 'world';
|
||||||
@ -20,7 +20,7 @@ window.testDom = async function() {
|
|||||||
// 2. $if directive
|
// 2. $if directive
|
||||||
document.body.innerHTML = '<template $if="state.show"><div id="test-if">visible</div></template>';
|
document.body.innerHTML = '<template $if="state.show"><div id="test-if">visible</div></template>';
|
||||||
state.show = false;
|
state.show = false;
|
||||||
___unsafeRefreshState(document.documentElement);
|
__unsafeRefreshState(document.documentElement);
|
||||||
if ($('#test-if')) throw new Error('$if fail: should be hidden');
|
if ($('#test-if')) throw new Error('$if fail: should be hidden');
|
||||||
|
|
||||||
state.show = true;
|
state.show = true;
|
||||||
@ -30,7 +30,8 @@ window.testDom = async function() {
|
|||||||
// 3. $each directive
|
// 3. $each directive
|
||||||
document.body.innerHTML = '<template $each="state.items"><div class="test-item" $text="item"></div></template>';
|
document.body.innerHTML = '<template $each="state.items"><div class="test-item" $text="item"></div></template>';
|
||||||
state.items = ['A', 'B'];
|
state.items = ['A', 'B'];
|
||||||
___unsafeRefreshState(document.documentElement);
|
__unsafeRefreshState(document.documentElement);
|
||||||
|
await wait();
|
||||||
if ($$('.test-item').length !== 2) throw new Error('$each fail: count mismatch');
|
if ($$('.test-item').length !== 2) throw new Error('$each fail: count mismatch');
|
||||||
if ($$('.test-item')[0].textContent !== 'A') throw new Error('$each fail: content mismatch');
|
if ($$('.test-item')[0].textContent !== 'A') throw new Error('$each fail: content mismatch');
|
||||||
|
|
||||||
@ -41,14 +42,14 @@ window.testDom = async function() {
|
|||||||
// 4. Event binding $onclick
|
// 4. Event binding $onclick
|
||||||
document.body.innerHTML = '<button id="test-click" $onclick="state.count++"></button>';
|
document.body.innerHTML = '<button id="test-click" $onclick="state.count++"></button>';
|
||||||
state.count = 0;
|
state.count = 0;
|
||||||
___unsafeRefreshState(document.documentElement);
|
__unsafeRefreshState(document.documentElement);
|
||||||
$('#test-click').click();
|
$('#test-click').click();
|
||||||
if (state.count !== 1) throw new Error('$onclick failed');
|
if (state.count !== 1) throw new Error('$onclick failed');
|
||||||
|
|
||||||
// 5. $bind (input)
|
// 5. $bind (input)
|
||||||
document.body.innerHTML = '<input id="test-bind" $bind="state.val">';
|
document.body.innerHTML = '<input id="test-bind" $bind="state.val">';
|
||||||
state.val = 'init';
|
state.val = 'init';
|
||||||
___unsafeRefreshState(document.documentElement);
|
__unsafeRefreshState(document.documentElement);
|
||||||
await wait(); // TRY: 等待 $bind 的 setTimeout 完成
|
await wait(); // TRY: 等待 $bind 的 setTimeout 完成
|
||||||
const input = $('#test-bind');
|
const input = $('#test-bind');
|
||||||
if (input.value !== 'init') throw new Error('$bind initial failed');
|
if (input.value !== 'init') throw new Error('$bind initial failed');
|
||||||
@ -74,13 +75,13 @@ window.testDom = async function() {
|
|||||||
const root = $('#double-eval-root');
|
const root = $('#double-eval-root');
|
||||||
root._thisObj = { state: doubleState };
|
root._thisObj = { state: doubleState };
|
||||||
|
|
||||||
___unsafeRefreshState(root);
|
__unsafeRefreshState(root);
|
||||||
await wait();
|
await wait();
|
||||||
if ($('#inner-node')) throw new Error('$$if failed: should be hidden initially');
|
if ($('#inner-node')) throw new Error('$$if failed: should be hidden initially');
|
||||||
|
|
||||||
console.log('Enabling inner node...');
|
console.log('Enabling inner node...');
|
||||||
doubleState.innerShow = true;
|
doubleState.innerShow = true;
|
||||||
___unsafeRefreshState(root);
|
__unsafeRefreshState(root);
|
||||||
await wait();
|
await wait();
|
||||||
const inner = $('#inner-node');
|
const inner = $('#inner-node');
|
||||||
if (!inner) throw new Error('$$if failed: should be visible after innerShow=true');
|
if (!inner) throw new Error('$$if failed: should be visible after innerShow=true');
|
||||||
@ -100,12 +101,12 @@ window.testDom = async function() {
|
|||||||
nestedRoot._thisObj = { state: doubleState };
|
nestedRoot._thisObj = { state: doubleState };
|
||||||
doubleState.outer = true;
|
doubleState.outer = true;
|
||||||
doubleState.innerShow = false;
|
doubleState.innerShow = false;
|
||||||
___unsafeRefreshState(nestedRoot);
|
__unsafeRefreshState(nestedRoot);
|
||||||
await wait();
|
await wait();
|
||||||
if ($('#nested-inner')) throw new Error('nested $$if failed: should be hidden initially');
|
if ($('#nested-inner')) throw new Error('nested $$if failed: should be hidden initially');
|
||||||
|
|
||||||
doubleState.innerShow = true;
|
doubleState.innerShow = true;
|
||||||
___unsafeRefreshState(nestedRoot);
|
__unsafeRefreshState(nestedRoot);
|
||||||
await wait();
|
await wait();
|
||||||
if (!$('#nested-inner')) throw new Error('nested $$if failed: should be visible after update');
|
if (!$('#nested-inner')) throw new Error('nested $$if failed: should be visible after update');
|
||||||
|
|
||||||
|
|||||||
4
test/foundation.test.js
vendored
4
test/foundation.test.js
vendored
@ -1,6 +1,6 @@
|
|||||||
// test/foundation.test.js
|
// test/foundation.test.js
|
||||||
window.testFoundation = async function() {
|
window.testFoundation = async function() {
|
||||||
const { ___unsafeRefreshState, Component, NewState, $, Util } = ApigoState;
|
const { __unsafeRefreshState, Component, NewState, $, Util } = ApigoState;
|
||||||
console.log('Testing framework foundation...');
|
console.log('Testing framework foundation...');
|
||||||
|
|
||||||
Component.register('NavTest', container => {
|
Component.register('NavTest', container => {
|
||||||
@ -26,7 +26,7 @@ window.testFoundation = async function() {
|
|||||||
const inst = document.createElement('NAVTEST');
|
const inst = document.createElement('NAVTEST');
|
||||||
inst.id = 'nav-inst';
|
inst.id = 'nav-inst';
|
||||||
document.body.appendChild(inst);
|
document.body.appendChild(inst);
|
||||||
___unsafeRefreshState(document.documentElement);
|
__unsafeRefreshState(document.documentElement);
|
||||||
await new Promise(r => setTimeout(r, 100));
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
|
||||||
const nav = $('#nav-inst');
|
const nav = $('#nav-inst');
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// test/inheritance.test.js
|
// test/inheritance.test.js
|
||||||
window.testInheritance = async function() {
|
window.testInheritance = async function() {
|
||||||
const { ___unsafeRefreshState, Component, NewState, $, Util } = ApigoState;
|
const { __unsafeRefreshState, Component, NewState, $, Util } = ApigoState;
|
||||||
console.log('Testing inheritance...');
|
console.log('Testing inheritance...');
|
||||||
|
|
||||||
Component.register('MyComp', container => {
|
Component.register('MyComp', container => {
|
||||||
@ -26,7 +26,7 @@ window.testInheritance = async function() {
|
|||||||
inst.id = 'comp-inst';
|
inst.id = 'comp-inst';
|
||||||
document.body.appendChild(inst);
|
document.body.appendChild(inst);
|
||||||
|
|
||||||
___unsafeRefreshState(document.documentElement);
|
__unsafeRefreshState(document.documentElement);
|
||||||
await new Promise(r => setTimeout(r, 100));
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
|
||||||
const comp = $('#comp-inst');
|
const comp = $('#comp-inst');
|
||||||
|
|||||||
@ -1,14 +1,19 @@
|
|||||||
// test/merging.test.js
|
// test/merging.test.js
|
||||||
window.testMerging = async function() {
|
window.testMerging = async function() {
|
||||||
const { Component, ___unsafeRefreshState, $ } = ApigoState;
|
const { Component, __unsafeRefreshState, $ } = ApigoState;
|
||||||
console.log('Testing complex class/style merging (DataTable Resizer scenario)...');
|
console.log('Testing complex class/style merging (DataTable Resizer scenario)...');
|
||||||
|
|
||||||
Component.register('Resizer-Real', container => {
|
Component.register('Resizer-Real', container => {
|
||||||
container.isVertical = true;
|
container.isVertical = true;
|
||||||
}, document.createRange().createContextualFragment('<div $class="tpl-static ${this.isVertical?\'tpl-v\':\'tpl-h\'}" $style="color:blue"></div>').firstChild);
|
}, document.createRange().createContextualFragment('<div $class="tpl-static ${this.isVertical?\'tpl-v\':\'tpl-h\'}" $style="\'color:blue\'"></div>').firstChild);
|
||||||
|
|
||||||
document.body.innerHTML = '<Resizer-Real id="res-inst" class="ins-static" style="opacity:0.5"></Resizer-Real>';
|
const res = document.createElement('Resizer-Real');
|
||||||
___unsafeRefreshState(document.documentElement);
|
res.id = 'res-inst';
|
||||||
|
res.className = 'ins-static';
|
||||||
|
res.setAttribute('style', 'opacity:0.5');
|
||||||
|
res.setAttribute('$class', "'ins-dynamic'");
|
||||||
|
document.body.appendChild(res);
|
||||||
|
__unsafeRefreshState(document.documentElement);
|
||||||
await new Promise(r => setTimeout(r, 50));
|
await new Promise(r => setTimeout(r, 50));
|
||||||
|
|
||||||
const inst = $('#res-inst');
|
const inst = $('#res-inst');
|
||||||
@ -17,9 +22,14 @@ window.testMerging = async function() {
|
|||||||
|
|
||||||
const classList = Array.from(inst.classList);
|
const classList = Array.from(inst.classList);
|
||||||
console.log('Final ClassList:', classList);
|
console.log('Final ClassList:', classList);
|
||||||
if (!classList.includes('ins-static') || !classList.includes('tpl-static')) {
|
if (!classList.includes('ins-static') || !classList.includes('tpl-static') || !classList.includes('ins-dynamic')) {
|
||||||
throw new Error('Class merging failed');
|
throw new Error('Class merging failed');
|
||||||
}
|
}
|
||||||
|
const tplIndex = classList.indexOf('tpl-static');
|
||||||
|
const insIndex = classList.indexOf('ins-static');
|
||||||
|
if (tplIndex > insIndex) {
|
||||||
|
throw new Error('Class merging order incorrect: tpl-static should be before ins-static');
|
||||||
|
}
|
||||||
|
|
||||||
const style = inst.getAttribute('style');
|
const style = inst.getAttribute('style');
|
||||||
console.log('Final Style:', style);
|
console.log('Final Style:', style);
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
// test/priority.test.js
|
// test/priority.test.js
|
||||||
window.testPriority = async function() {
|
window.testPriority = async function() {
|
||||||
const { ___unsafeRefreshState, $, NewState } = ApigoState;
|
const { __unsafeRefreshState, $, NewState } = ApigoState;
|
||||||
console.log('Testing directive priorities...');
|
console.log('Testing directive priorities...');
|
||||||
|
|
||||||
// Test $if vs $text (if should hide text)
|
// Test $if vs $text (if should hide text)
|
||||||
document.body.innerHTML = '<div id="prio-test" $if="state.hide" $text="state.msg"></div>';
|
document.body.innerHTML = '<div id="prio-test" $if="!state.hide" $text="state.msg"></div>';
|
||||||
const state = NewState({ hide: false, msg: 'visible' });
|
const state = NewState({ hide: false, msg: 'visible' });
|
||||||
|
window.state = state;
|
||||||
document.documentElement._thisObj = { state };
|
document.documentElement._thisObj = { state };
|
||||||
___unsafeRefreshState(document.documentElement);
|
__unsafeRefreshState(document.documentElement);
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r, 20));
|
||||||
if ($('#prio-test').textContent !== 'visible') throw new Error('Basic visibility failed');
|
if ($('#prio-test').textContent !== 'visible') throw new Error('Basic visibility failed');
|
||||||
|
|
||||||
state.hide = true;
|
state.hide = true;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// test/timing.test.js
|
// test/timing.test.js
|
||||||
window.testTiming = async function() {
|
window.testTiming = async function() {
|
||||||
const { ___unsafeRefreshState, Component, NewState, $, Util } = ApigoState;
|
const { __unsafeRefreshState, Component, NewState, $, Util } = ApigoState;
|
||||||
console.log('Testing initialization timing...');
|
console.log('Testing initialization timing...');
|
||||||
|
|
||||||
Component.register('TimingComp', container => {
|
Component.register('TimingComp', container => {
|
||||||
@ -14,7 +14,7 @@ window.testTiming = async function() {
|
|||||||
`));
|
`));
|
||||||
|
|
||||||
document.body.innerHTML += `<TimingComp id="timing-inst"></TimingComp>`;
|
document.body.innerHTML += `<TimingComp id="timing-inst"></TimingComp>`;
|
||||||
___unsafeRefreshState(document.documentElement);
|
__unsafeRefreshState(document.documentElement);
|
||||||
|
|
||||||
await new Promise(r => setTimeout(r, 100));
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user