Files
rhinelab-ui/reference/ui-transition-check.html
T

252 lines
8.3 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8" />
<title>详情与弹窗过渡检查</title>
<style>
body {
font: 14px system-ui;
background: #e8e5e1;
color: #252820;
padding: 20px;
}
iframe {
width: 1100px;
height: 620px;
border: 0;
}
pre {
white-space: pre-wrap;
}
</style>
<h1>详情与弹窗过渡检查</h1>
<pre id="result">正在检查…</pre>
<iframe src="/?scene=archive" title="真实应用"></iframe>
<script type="module">
const frame = document.querySelector("iframe"),
result = document.querySelector("#result"),
checks = [];
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const check = (condition, name) => {
if (!condition) throw Error(name);
checks.push(name);
};
async function until(fn) {
const start = performance.now();
while (!fn()) {
if (performance.now() - start > 30000) throw Error("等待超时");
await pause(25);
}
}
let previousPrefs, previousSaved;
try {
await until(
() =>
frame.contentWindow.rhine?.stats().loaded &&
!frame.contentDocument.querySelector("#loading"),
);
const win = frame.contentWindow,
doc = frame.contentDocument,
app = win.rhine;
const q = (selector) => doc.querySelector(selector);
previousPrefs = win.localStorage.getItem("rhine-settings");
previousSaved = win.localStorage.getItem("rhine-saved");
const key = (element, value) =>
element.dispatchEvent(
new win.KeyboardEvent("keydown", { key: value, bubbles: true }),
);
const finish = (element) =>
element
.getAnimations({ subtree: true })
.forEach((animation) => animation.finish());
const openSettings = async () => {
q('[data-action="settings"]').click();
await until(() => q('[data-pref="reduced"]'));
};
await openSettings();
let reduced = q('[data-pref="reduced"]');
if (reduced.checked) reduced.click();
q('[data-action="close-modal"]').click();
await until(() => !q(".terminal-modal"));
const opener = q('[data-action="search"]');
opener.focus();
opener.click();
let backdrop = q(".modal-backdrop");
check(
backdrop.dataset.transition === "opening",
"检索弹窗以入场状态打开",
);
finish(backdrop);
await pause(0);
q('[data-action="close-modal"]').click();
check(
backdrop.isConnected && backdrop.dataset.transition === "closing",
"关闭时保留弹窗节点进行淡出",
);
check(
q("#archive-ui").inert && q(".system-nav").inert,
"退出完成前保持背景输入隔离",
);
key(q("#archive-search"), "Escape");
key(q("#archive-search"), "Escape");
finish(backdrop);
await until(() => !q(".terminal-modal"));
check(
doc.activeElement === opener && !q("#archive-ui").inert,
"重复 Esc 后正确释放弹窗并恢复原按钮焦点",
);
opener.click();
backdrop = q(".modal-backdrop");
const intro = backdrop.getAnimations()[0];
intro.pause();
intro.currentTime = 120;
const before = Number(win.getComputedStyle(backdrop).opacity);
q('[data-action="close-modal"]').click();
check(
Math.abs(Number(win.getComputedStyle(backdrop).opacity) - before) <
0.02,
"打开途中关闭从当前透明度接续",
);
finish(backdrop);
await until(() => !q(".terminal-modal"));
opener.click();
finish(q(".modal-backdrop"));
await pause(0);
const wanted = q('[data-result="5"]');
wanted.click();
check(
q(".modal-backdrop").dataset.transition === "closing" &&
app.stats().mode === "archive",
"检索选档先完成弹窗退出",
);
finish(q(".modal-backdrop"));
await until(
() => app.stats().mode === "detail" && app.stats().canInspect,
);
check(
app.stats().selected === "X-006" && !q("#detail-ui").inert,
"弹窗退出后进入所选档案",
);
await until(() => doc.activeElement === q("#detail-content"));
check(true, "详情可读后焦点进入正文");
q('[data-tab="notes"]').click();
const panel = q("#tab-panel"),
content = panel.firstChild;
panel.scrollTop = 80;
const scroll = panel.scrollTop,
bookmark = q('[data-action="bookmark"]');
bookmark.focus();
bookmark.click();
check(
q("#tab-panel") === panel &&
panel.firstChild === content &&
panel.scrollTop === scroll,
"收藏保留正文节点和滚动位置",
);
check(
doc.activeElement === bookmark &&
bookmark.getAttribute("aria-pressed") ===
String(app.stats().saved.includes("X-006")),
"收藏保留按钮焦点并同步按下状态",
);
bookmark.click();
q('[data-tab="history"]').click();
q('[data-tab="overview"]').click();
q('[data-tab="notes"]').click();
check(
panel.getAttribute("aria-labelledby") === "tab-notes" &&
q('[data-tab="notes"]').getAttribute("aria-selected") === "true",
"页签快速反向切换显示最后选择",
);
check(
panel.getAnimations().length <= 1,
"连续页签切换只保留当前内容动画",
);
const detail = q("#detail-ui");
finish(detail);
await pause(0);
q('[data-action="back"]').click();
check(
!detail.hidden &&
detail.inert &&
detail.dataset.transition === "closing",
"详情返回时保留可见退出并禁用旧内容",
);
const shade = Number(
q("#stage").style.getPropertyValue("--detail-shade"),
);
check(shade > 0.9, "返回首帧保留镜头对应的背景遮罩");
const exit = detail.getAnimations()[0];
exit.pause();
exit.currentTime = 90;
const detailBefore = Number(win.getComputedStyle(detail).opacity);
app.detail();
check(
!detail.hidden &&
detail.dataset.transition === "opening" &&
Math.abs(
Number(win.getComputedStyle(detail).opacity) - detailBefore,
) < 0.02,
"详情返回中重新进入从当前透明度衔接",
);
await until(() => app.stats().canInspect);
app.archive();
finish(detail);
await until(() => detail.hidden);
check(
app.stats().mode === "archive" && doc.activeElement === q(".read-file"),
"完整返回后隐藏详情并恢复读取按钮焦点",
);
await until(
() =>
Number(q("#stage").style.getPropertyValue("--detail-shade")) < 0.01,
);
check(true, "背景遮罩随镜头归位连续退回阵列");
await openSettings();
reduced = q('[data-pref="reduced"]');
reduced.click();
q('[data-action="close-modal"]').click();
check(!q(".terminal-modal"), "减少动态效果时弹窗立即退出");
app.detail();
await until(() => app.stats().canInspect);
q('[data-tab="history"]').click();
check(
q("#tab-panel").getAnimations().length === 0,
"减少动态效果时页签不播放动画",
);
app.archive();
check(detail.hidden, "减少动态效果时详情立即隐藏");
await openSettings();
q('[data-pref="reduced"]').click();
q('[data-action="close-modal"]').click();
await until(() => !q(".terminal-modal"));
result.textContent = JSON.stringify(
{ passed: true, count: checks.length, checks },
null,
2,
);
} catch (error) {
result.textContent = JSON.stringify(
{ passed: false, checks, error: error.message },
null,
2,
);
console.error(error);
} finally {
const storage = frame.contentWindow.localStorage;
if (previousPrefs !== undefined)
previousPrefs === null
? storage.removeItem("rhine-settings")
: storage.setItem("rhine-settings", previousPrefs);
if (previousSaved !== undefined)
previousSaved === null
? storage.removeItem("rhine-saved")
: storage.setItem("rhine-saved", previousSaved);
}
</script>
</html>