'use strict'; const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const test = require('node:test'); const vm = require('node:vm'); function runtime({ autoopen = false, empty = false } = {}) { const ids = new Map(); const doc = { activeElement: null }; class Element { constructor(attrs = {}) { this.attrs = attrs; this.listeners = {}; this.queries = new Map(); this.disabled = false; this.hidden = false; this.value = attrs.value || ''; this.name = attrs.name || ''; this.checked = false; this.required = false; this.validity = ''; this.textContent = ''; const classes = new Set(); this.classList = { toggle(key, on) { if (on) classes.add(key); else classes.delete(key); }, contains(key) { return classes.has(key); }, }; } getAttribute(name) { return this.attrs[name] ?? null; } hasAttribute(name) { return Object.hasOwn(this.attrs, name); } addEventListener(type, fn) { (this.listeners[type] ||= []).push(fn); } fire(type, values = {}) { const event = { target: this, defaultPrevented: false, preventDefault() { this.defaultPrevented = true; }, ...values }; (this.listeners[type] || []).forEach((fn) => fn(event)); return event; } querySelector(selector) { const result = this.queries.get(selector); return Array.isArray(result) ? result[0] || null : result || null; } querySelectorAll(selector) { const result = this.queries.get(selector); return Array.isArray(result) ? result : result ? [result] : []; } map(selector, elements) { this.queries.set(selector, elements); return this; } focus() { doc.activeElement = this; } setCustomValidity(value) { this.validity = value; } closest(selector) { return this.row; } replaceChildren(child) { this.content = child; } getBoundingClientRect() { return { left: 100, top: 100, right: 500, bottom: 500 }; } } const root = new Element(); const window = new Element(); doc.documentElement = new Element(); doc.getElementById = (id) => ids.get(id) || null; doc.querySelector = (selector) => selector === '.jio-messages-admin' ? root : null; const dialogs = []; function dialog(id) { const node = new Element(); node.open = false; node.showModal = () => { node.previous = doc.activeElement; node.open = true; }; node.close = () => { node.open = false; node.previous?.focus(); node.fire('close'); }; node.cancel = () => { node.fire('cancel'); node.close(); }; node.closeButton = new Element(); node.closeButton.addEventListener('click', () => {}); node.map('[data-jio-dialog-close]', [node.closeButton]); ids.set(id, node); dialogs.push(node); return node; } const compose = dialog('jio-msg-compose'); const detail = dialog('jio-msg-detail'); const confirm = dialog('jio-msg-confirm'); confirm.attrs = { 'data-i18n-delete-title': '删除消息', 'data-i18n-delete-text': '删除 {count} 条消息', 'data-i18n-delete-send': '确认删除' }; const accept = new Element(); const cancel = confirm.closeButton; const confirmation = new Element(); confirm.map('[data-jio-msg-confirm]', accept).map('[data-jio-dialog-title]', new Element()).map('#jio-msg-confirm-text', confirmation).map('[data-jio-dialog-close][autofocus]', cancel); const detailBody = new Element(); detail.map('[data-jio-msg-detail-body]', detailBody); ids.set('jio-msg-detail-8', { content: { cloneNode: () => ({ text: 'Full