q-component q-test-accumulator { q-property manifest: q-map { } q-property results: q-map { } q-property manualStatus: q-map { } q-signal refreshRequested() q-signal resultReported(testNo, value) q-signal expectedReported(testNo, expectedList) q-signal passingReported(testNo, isPassing) q-signal outcomeReported(testNo, isPassing, value) function normalize(v) { return String(v == null ? "" : v).replace(/\s+/g, " ").trim(); } function mapKeys(store) { if (store && typeof store.keys === "function") { return store.keys(); } return []; } function mapGet(store, key) { if (store && typeof store.value === "function") { return store.value(key); } return undefined; } function ensureModelStore(targetProp) { var store = this.component[targetProp]; if (store && typeof store.set === "function" && typeof store.value === "function" && typeof store.keys === "function") { return store; } var seed = {}; if (store && typeof store === "object") { var keys = Object.keys(store); for (var i = 0; i < keys.length; i += 1) { seed[keys[i]] = store[keys[i]]; } } this.component[targetProp] = QModel(seed); return this.component[targetProp]; } function mapSet(targetProp, key, value) { var store = this.component.ensureModelStore(targetProp); store.set(key, value); } function doneMarkerState(testNo) { var key = String(testNo || ""); var host = document.querySelector("#host-t" + key); if (!host) { return "none"; } var doneNode = host.querySelector("#t" + key + "-done"); if (!doneNode) { return "none"; } var token = String(doneNode.textContent == null ? "" : doneNode.textContent).trim().toLowerCase(); if (token === "1" || token === "true" || token === "done" || token === "ok" || token === "pass") { return "done"; } return "pending"; } function bootstrapManifest() { var cards = document.querySelectorAll(".test-card"); var data = {}; for (var i = 0; i < cards.length; i += 1) { var card = cards[i]; var cardId = String(card.getAttribute("id") || ""); if (cardId.indexOf("card-") !== 0) { continue; } var key = cardId.slice(5); if (!key) { continue; } var titleNode = card.querySelector(".test-head h2"); var rawTitle = titleNode ? String(titleNode.textContent || "") : ("Test " + key); var title = rawTitle.replace(/^\s*\d+\.\s*/, ""); var expectedNode = card.querySelector(".test-expected"); var expectedText = expectedNode ? String(expectedNode.textContent || "") : ""; expectedText = expectedText.replace(/^Expected:\s*/i, "").trim(); var expected = []; if (expectedText) { expected = expectedText.split("|").map(function(part) { return String(part || "").trim(); }).filter(function(part) { return part.length > 0; }); } var deprecated = expected.length === 1 && expected[0].toLowerCase() === "test has been deprecated"; data[key] = { title: title, expected: expected, deprecated: deprecated }; } this.component.manifest = QModel(data); } function ensureManifest() { var manifest = this.component.ensureModelStore("manifest"); var currentKeys = this.component.mapKeys(manifest); var cardCount = document.querySelectorAll(".test-card[id^='card-']").length; if (currentKeys.length === 0 || cardCount > currentKeys.length) { this.component.bootstrapManifest(); } } function setExpected(testNo, expectedList) { var key = String(testNo); var entry = this.component.mapGet(this.component.manifest, key) || {}; if (!entry || typeof entry !== "object") { entry = {}; } entry.expected = Array.isArray(expectedList) ? expectedList.slice() : [String(expectedList || "")]; this.component.mapSet("manifest", key, entry); } function setResult(testNo, value) { this.component.mapSet("results", String(testNo), String(value == null ? "" : value)); } function setPassing(testNo, isPassing) { var key = String(testNo); var value = isPassing ? "pass" : "fail"; this.component.mapSet("manualStatus", key, value); } function report(testNo, isPassing, value) { this.component.setPassing(testNo, isPassing); if (value !== undefined) { this.component.setResult(testNo, value); } this.component.showResults(); } function evaluateOne(testNo) { var key = String(testNo); var entry = this.component.mapGet(this.component.manifest, key) || {}; var expected = Array.isArray(entry.expected) ? entry.expected : []; var deprecated = entry.deprecated === true; var title = String(entry.title || ""); var actual = String(this.component.mapGet(this.component.results, key) || ""); var manual = this.component.ensureModelStore("manualStatus"); var status = ""; var hasResult = false; hasResult = this.component.mapKeys(this.component.ensureModelStore("results")).indexOf(key) >= 0; status = this.component.mapGet(manual, key); if (status !== "pass" && status !== "fail" && status !== "deprecated" && deprecated) { status = "deprecated"; } if (status !== "pass" && status !== "fail" && status !== "deprecated") { status = hasResult ? "pass" : "pending"; } var doneState = this.component.doneMarkerState(key); if (status !== "pass" && status !== "fail" && status !== "deprecated" && doneState === "pending") { status = "pending"; } if (status === "pass" && doneState === "pending") { status = "pending"; } if (status === "pass" && !deprecated) { var normalizedActual = this.component.normalize(actual); for (var i = 0; i < expected.length; i += 1) { var token = this.component.normalize(expected[i]); if (token && normalizedActual.indexOf(token) < 0) { status = "fail"; break; } } } var badge = document.querySelector("#badge-" + key); if (badge) { badge.className = "badge " + status; badge.textContent = status; } return { key: key, title: title, status: status, actual: actual, expected: expected }; } function evaluateAll() { var hosts = document.querySelectorAll("q-html[data-test-no]"); for (var i = 0; i < hosts.length; i += 1) { var host = hosts[i]; var no = String(host.getAttribute("data-test-no") || ""); if (!no) { continue; } this.component.setResult(no, host.innerHTML); } this.component.showResults(); } function refresh() { this.component.evaluateAll(); } function showResults() { this.component.ensureManifest(); var keys = this.component.mapKeys(this.component.manifest); var passed = 0; var failed = 0; var pending = 0; var lines = []; for (var i = 0; i < keys.length; i += 1) { var rec = this.component.evaluateOne(keys[i]); if (rec.status === "pass") { passed += 1; } if (rec.status === "fail") { failed += 1; lines.push(rec.key + ". " + rec.title + "\n expected: " + rec.expected.join(" | ") + "\n actual: " + this.component.normalize(rec.actual)); } if (rec.status !== "pass" && rec.status !== "fail") { pending += 1; } } document.querySelector("#sum-pass").textContent = "Passes: " + String(passed); document.querySelector("#sum-fail").textContent = "Failures: " + String(failed); document.querySelector("#sum-pending").textContent = "Pending: " + String(pending); document.querySelector("#failed-list").textContent = lines.length > 0 ? lines.join("\n\n") : "none"; } onrefreshRequested { this.component.refresh(); } onresultReported { var p = event && event.detail && event.detail.params ? event.detail.params : {}; this.component.setResult(p.testNo, p.value); this.component.showResults(); } onexpectedReported { var p = event && event.detail && event.detail.params ? event.detail.params : {}; this.component.setExpected(p.testNo, p.expectedList); this.component.showResults(); } onpassingReported { var p = event && event.detail && event.detail.params ? event.detail.params : {}; this.component.setPassing(p.testNo, p.isPassing); this.component.showResults(); } onoutcomeReported { var p = event && event.detail && event.detail.params ? event.detail.params : {}; this.component.report(p.testNo, p.isPassing, p.value); } onReady { try { if (!this.component) { return; } if (typeof this.component.bootstrapManifest === "function") { this.component.bootstrapManifest(); } if (typeof this.refreshRequested === "function") { this.refreshRequested(); } else if (typeof this.component.refresh === "function") { this.component.refresh(); } setTimeout(function() { try { if (this.component && typeof this.component.refresh === "function") { this.component.refresh(); } } catch (err1) { console.warn("accumulator delayed refresh(250) failed", err1); } }.bind(this), 250); setTimeout(function() { try { if (this.component && typeof this.component.refresh === "function") { this.component.refresh(); } } catch (err2) { console.warn("accumulator delayed refresh(1000) failed", err2); } }.bind(this), 1000); } catch (err) { console.warn("accumulator onReady failed", err); } } div.panel { div.summary { span#sum-fail.fail { text { Failures: 0 } } span#sum-pending.pending { text { Pending: 0 } } span#sum-pass.pass { text { Passes: 0 } } } div.actions { button#btn-refresh { type: "button" onclick { var acc = document.querySelector("#acc-main"); if (!acc) { return; } if (typeof acc.refreshRequested === "function") { acc.refreshRequested(); } } text { Refresh } } button#btn-copy { type: "button" onclick { var txt = document.querySelector("#failed-list").textContent; if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") { navigator.clipboard.writeText(txt); } } text { Copy Failures } } button#btn-loop { type: "button" onclick { QHtml.printEventLoopSnapshot({ limit: 80, includePayload: false }); } text { Print Event Loop } } } pre#failed-list { text { none } } } } q-test-accumulator#acc-main { } q-timer accRefreshTimer { interval: 5000 repeat: true running: false ontimeout { var acc = document.querySelector("#acc-main"); if (!acc) { return; } if (typeof acc.refreshRequested === "function") { acc.refreshRequested(); } } }

Particle System Tests

section
Includes: tests 120, 121, 122
Native particle-emitter visual, start/stop behavior, q-particle-emitter component wrapper, SVG star mask tests, and mixed-container emitter tests. Back to test index.

120. particle-emitter custom element start/stop

pending
Expected: Start particles | Stop particles | particle-emitter canvas layer
div#t120-system { style { display: block; position: relative; width: 420px; height: 220px; margin: 8px 0; border: 1px solid #334155; background: #07111f; overflow: hidden; } particle-emitter#t120-emitter { emitRate: "260" lifetime: "1600" lifetimeVariation: "90" xVelocity: "0" yVelocity: "-1.75" xAcceleration: "0" yAcceleration: "0.0028" startSize: "30" endSize: "10" startOpacity: "0.9" endOpacity: "0.02" startSizeVariation: "7" endSizeVariation: "5" startOpacityVariation: "0.16" endOpacityVariation: "0.02" x: "210" y: "214" xVariation: "245" yVariation: "20" xVelocityVariation: "0" yVelocityVariation: "0.04" xAccelerationVariation: "0" yAccelerationVariation: "0.005" maxActiveParticles: "720" maxActiveParticlesVariation: "120" running: "false" interval: "10" src: "tools/assets/particle.png" color: "#08c473" } } div#t120-controls { button#t120-start { type: "button" onclick { this.closest("q-html").querySelector("#t120-emitter").running = true; $("#t120-status").textContent = "running"; document.querySelector("#acc-main").resultReported(120, "Start particles | Stop particles | particle-emitter canvas layer"); } text { Start particles } } button#t120-stop { type: "button" onclick { this.closest("q-html").querySelector("#t120-emitter").running = false; $("#t120-status").textContent = "stopped"; document.querySelector("#acc-main").resultReported(120, "Start particles | Stop particles | particle-emitter canvas layer"); } text { Stop particles } } div#t120-status { text { stopped } } } onReady { document.querySelector("#acc-main").resultReported(120, "Start particles | Stop particles | particle-emitter canvas layer"); }

121. q-particle-emitter SVG star mask

pending
Expected: q-particle-emitter | star mask | SVG mask | attr passthrough
q-import { dist/q-components/q-particle-emitter.qhtml } div#t121-system { style { display: block; position: relative; width: 420px; height: 220px; margin: 8px 0; border: 1px solid #334155; background: #07111f; overflow: hidden; } q-particle-emitter#t121-emitter { emitRate: "240" lifetime: "1900" lifetimeVariation: "220" xVelocity: "0" yVelocity: "-1.35" xAcceleration: "0" yAcceleration: "0.0018" startSize: "34" endSize: "12" startOpacity: "0.88" endOpacity: "0.03" startSizeVariation: "8" endSizeVariation: "5" startOpacityVariation: "0.08" endOpacityVariation: "0.02" x: "210" y: "214" xVariation: "190" yVariation: "18" xVelocityVariation: "0.08" yVelocityVariation: "0.08" xAccelerationVariation: "0.01" yAccelerationVariation: "0.003" maxActiveParticles: "640" maxActiveParticlesVariation: "96" running: "false" interval: "10" src: "tools/assets/particle.png" emitterMask: "tools/assets/particle-mask-star.svg" color: "#67e8f9" } } div#t121-controls { button#t121-start { type: "button" onclick { this.closest("q-html").querySelector("#t121-emitter").running = true; $("#t121-status").textContent = "running"; document.querySelector("#acc-main").resultReported(121, "q-particle-emitter | star mask | SVG mask | attr passthrough"); } text { Start masked particles } } button#t121-stop { type: "button" onclick { this.closest("q-html").querySelector("#t121-emitter").running = false; $("#t121-status").textContent = "stopped"; document.querySelector("#acc-main").resultReported(121, "q-particle-emitter | star mask | SVG mask | attr passthrough"); } text { Stop masked particles } } span#t121-status { text { stopped } } span#t121-mask-status { text { star mask } } span#t121-attr-status { text { attr pending } } span#t121-expected { text { q-particle-emitter star mask SVG mask } } } onReady { var emitter = document.querySelector("#t121-emitter"); emitter.setAttribute("emitRate", "88"); $("#t121-attr-status").textContent = "attr " + emitter.getAttribute("emitRate"); document.querySelector("#acc-main").resultReported(121, "q-particle-emitter | star mask | SVG mask | attr passthrough"); }

122. particle emitters on mixed container elements

pending
Expected: div emitter | h3 emitter | button emitter | span emitter | q-modal emitter
q-import { dist/q-components/q-modal.qhtml } q-import { dist/q-components/q-particle-emitter.qhtml } div#t122-board { style { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; padding: 10px; background: #07111f; border: 1px solid #334155; border-radius: 10px; } div#t122-div-host { style { display: block; position: relative; min-height: 130px; overflow: hidden; border-radius: 12px; border: 1px solid #155e75; background: #062336; color: #dffcff; padding: 12px; } strong { text { div host } } particle-emitter#t122-div-emitter { emitRate: "220" lifetime: "2100" lifetimeVariation: "180" x: "110" y: "128" xVariation: "116" yVariation: "10" xVelocity: "0.12" yVelocity: "-1.45" xVelocityVariation: "0.12" yVelocityVariation: "0.08" xAcceleration: "0.006" yAcceleration: "-0.001" xAccelerationVariation: "0.006" yAccelerationVariation: "0.002" startSize: "22" endSize: "9" startSizeVariation: "5" endSizeVariation: "3" startOpacity: "0.82" endOpacity: "0.03" startOpacityVariation: "0.06" endOpacityVariation: "0.02" maxActiveParticles: "520" maxActiveParticlesVariation: "80" interval: "10" running: "false" src: "tools/assets/particle.png" color: "#67e8f9" } } h3#t122-h3-host { style { display: block; position: relative; min-height: 130px; margin: 0; overflow: hidden; border-radius: 12px; border: 1px solid #7c3aed; background: #1e1238; color: #f0e8ff; padding: 12px; font-size: 17px; font-weight: 700; } text { h3 host } q-particle-emitter#t122-h3-emitter { emitRate: "210" lifetime: "2300" lifetimeVariation: "220" x: "108" y: "126" xVariation: "130" yVariation: "12" xVelocity: "-0.08" yVelocity: "-1.32" xVelocityVariation: "0.18" yVelocityVariation: "0.1" xAcceleration: "-0.003" yAcceleration: "-0.001" xAccelerationVariation: "0.008" yAccelerationVariation: "0.003" startSize: "24" endSize: "10" startSizeVariation: "4" endSizeVariation: "4" startOpacity: "0.78" endOpacity: "0.025" startOpacityVariation: "0.07" endOpacityVariation: "0.02" maxActiveParticles: "480" maxActiveParticlesVariation: "72" interval: "10" running: "false" src: "tools/assets/particle.png" color: "#c084fc" } } button#t122-button-host { type: "button" style { display: block; position: relative; min-height: 130px; width: 100%; overflow: hidden; border-radius: 12px; border: 1px solid #f59e0b; background: #33220a; color: #fff7d6; padding: 12px; text-align: left; font: inherit; } text { button host } particle-emitter#t122-button-emitter { emitRate: "240" lifetime: "1950" lifetimeVariation: "160" x: "116" y: "128" xVariation: "122" yVariation: "9" xVelocity: "0.02" yVelocity: "-1.58" xVelocityVariation: "0.2" yVelocityVariation: "0.11" xAcceleration: "0.002" yAcceleration: "-0.002" xAccelerationVariation: "0.006" yAccelerationVariation: "0.003" startSize: "20" endSize: "8" startSizeVariation: "6" endSizeVariation: "2" startOpacity: "0.86" endOpacity: "0.035" startOpacityVariation: "0.05" endOpacityVariation: "0.018" maxActiveParticles: "560" maxActiveParticlesVariation: "84" interval: "10" running: "false" src: "tools/assets/particle.png" color: "#facc15" } } span#t122-span-host { style { display: block; position: relative; min-height: 130px; overflow: hidden; border-radius: 12px; border: 1px solid #16a34a; background: #082915; color: #dcfce7; padding: 12px; } text { span host } q-particle-emitter#t122-span-emitter { emitRate: "220" lifetime: "2200" lifetimeVariation: "200" x: "112" y: "126" xVariation: "138" yVariation: "14" xVelocity: "0.1" yVelocity: "-1.38" xVelocityVariation: "0.22" yVelocityVariation: "0.12" xAcceleration: "0.004" yAcceleration: "-0.001" xAccelerationVariation: "0.007" yAccelerationVariation: "0.003" startSize: "23" endSize: "9" startSizeVariation: "5" endSizeVariation: "3" startOpacity: "0.8" endOpacity: "0.03" startOpacityVariation: "0.06" endOpacityVariation: "0.02" maxActiveParticles: "500" maxActiveParticlesVariation: "76" interval: "10" running: "false" src: "tools/assets/particle.png" color: "#86efac" } } } q-modal#t122-modal-host { title { text { q-modal host } } body { div#t122-modal-particle-box { style { display: block; position: relative; min-height: 150px; overflow: hidden; border-radius: 12px; border: 1px solid #ef4444; background: #330d12; color: #ffe4e6; padding: 12px; } text { q-modal body emitter } particle-emitter#t122-modal-emitter { emitRate: "260" lifetime: "2400" lifetimeVariation: "240" x: "180" y: "146" xVariation: "180" yVariation: "12" xVelocity: "-0.02" yVelocity: "-1.5" xVelocityVariation: "0.26" yVelocityVariation: "0.1" xAcceleration: "0.001" yAcceleration: "-0.002" xAccelerationVariation: "0.008" yAccelerationVariation: "0.003" startSize: "25" endSize: "10" startSizeVariation: "5" endSizeVariation: "4" startOpacity: "0.84" endOpacity: "0.025" startOpacityVariation: "0.06" endOpacityVariation: "0.02" maxActiveParticles: "620" maxActiveParticlesVariation: "96" interval: "10" running: "false" src: "tools/assets/particle.png" mask: "tools/assets/particle-mask-star.svg" color: "#fb7185" } } } } div#t122-controls { style { margin-top: 10px; display: flex; gap: 8px; flex-wrap: wrap; } button#t122-start { type: "button" onclick { var host = this.closest("q-html"); var emitters = host.querySelectorAll("particle-emitter,q-particle-emitter"); for (var i = 0; i < emitters.length; i += 1) { emitters[i].running = true; } $("#t122-status").textContent = "running"; document.querySelector("#acc-main").resultReported(122, "div emitter | h3 emitter | button emitter | span emitter | q-modal emitter"); } text { Start all mixed emitters } } button#t122-stop { type: "button" onclick { var host = this.closest("q-html"); var emitters = host.querySelectorAll("particle-emitter,q-particle-emitter"); for (var i = 0; i < emitters.length; i += 1) { emitters[i].running = false; } $("#t122-status").textContent = "stopped"; document.querySelector("#acc-main").resultReported(122, "div emitter | h3 emitter | button emitter | span emitter | q-modal emitter"); } text { Stop all mixed emitters } } span#t122-status { text { stopped } } span#t122-expected { text { div emitter h3 emitter button emitter span emitter q-modal emitter } } } onReady { this.querySelector("#t122-modal-host").show(); document.querySelector("#acc-main").resultReported(122, "div emitter | h3 emitter | button emitter | span emitter | q-modal emitter"); }