Current section
Files
Jump to
Current section
Files
src/testing_ffi.mjs
import jsdom from "jsdom"
import fs from "fs"
import { unique_id } from "./utils_ffi.mjs"
import { List } from "../prelude.mjs"
import { init as init_document } from "./document_ffi.mjs"
// ------------------------------- INIT --------------------------------
// This function is needed to initialize the global window and document objects.
// Also to initialize the document with all its maps. (with novdom_testing.render these are not overwritten)
export function init() {
const html = fs.readFileSync("build/packages/novdom_dev_tools/priv/template/index.html", "utf8")
const dom = new jsdom.JSDOM(html)
global.window = dom.window
global.document = dom.window.document
global.callees = new Map() // id: String --> count: Int
globalThis.currentStep = null
globalThis.EventTarget = window.EventTarget
globalThis.Element = window.Element
// Adding requestAnimationFrame() and setTimeout() to global scope (making it synchronous to be able to test it)
globalThis.requestAnimationFrame = (callback) => {
callback()
}
globalThis.setTimeout = (callback, ms) => {
if (ms) {
// sycronous wait (never use this in production, only for testing)
const start = new Date().getTime()
let end = start
while (end < start + ms) {
end = new Date().getTime()
}
}
callback()
}
init_document()
}
// ------------------------------- UTILS --------------------------------
export function current_step(step) {
if (step) {
// Write
globalThis.currentStep = step
return
}
if (globalThis.currentStep) {
return globalThis.currentStep
}
return "Unit Test"
}
// ------------------------------- ELEMENT --------------------------------
export function component_visible(elem) {
return !!elem && !!document.getElementById("_app_").querySelector("#" + elem.id) && !elem.hidden // && elem.style.display !== "none" && elem.style.visibility !== "hidden"
}
export function has_child(elem, child) {
if (!component_visible(child)) {
return false
}
return elem.contains(child)
}
export function shows_text(elem, text) {
function has_text(elem, text) {
if (elem.textContent.replace("\u00A0", " ").includes(text)) {
return true
}
for (const child of elem.children) {
if (has_text(child, text)) {
return true
}
}
return false
}
return has_text(elem, text)
}
export function exclusively_shows_text(elem, text) {
return elem.textContent.replace("\u00A0", " ") === text
}
export function has_tag(elem, tag) {
return elem.tagName === tag.toUpperCase()
}
export function set_description(elem, description) {
elem.description = description
return elem
}
export function get_description(elem, def) {
return elem.description || def || "Component"
}
// ------------------------------- PARAMENTER --------------------------------
export function attribute(elem, name) {
switch (name) {
case "tailwind":
case "class":
return elem.className
case "style":
return elem.style.cssText
default:
return elem.getAttribute(name)
}
}
// Never use this in production code, only for testing (no event is actually triggered)
// Not using `elem.dispatchEvent(new window.Event(event_name))` because it does not work for ontransitionend / -start & onrender / onunrender
export function trigger_event(elem, event_name) {
const event = {
target: elem,
type: event_name,
preventDefault: () => {},
stopPropagation: () => {},
}
if (elem[event_name + "Listeners"]) {
elem[event_name + "Listeners"].forEach((l) => l(event))
}
if (elem["on" + event_name]) {
elem["on" + event_name](event)
}
}
export function trigger_unrender(elem) {
const event_name = "replacement"
const args = { oldElem: elem }
if (elem[event_name + "Listeners"]) {
elem[event_name + "Listeners"].forEach((l) => l({ oldElem: elem }))
}
if (elem["on" + event_name]) {
elem["on" + event_name](args)
}
return elem
}
export function has_attribute(elem, name, value) {
if (value === "") {
return elem.hasAttribute(name)
}
switch (name) {
case "tailwind":
case "class":
for (const v of value.smartSplit(" ")) {
if (!elem.classList.contains(v)) {
return false
}
}
return true
case "style":
for (const style of value.smartSplit(";")) {
if (!style.includes(":")) {
if (elem.style[style] === "") {
continue
}
return false
}
const [k, v] = style.smartSplit(":")
if (elem.style[k] !== v) {
return false
}
}
return true
default:
return elem.getAttribute(name) === value
}
}
export function has_listener(elem, listener_fn) {
const { name } = listener_fn(() => {})
return [!!elem[name + "Listeners"] && elem[name + "Listeners"].length > 0, name]
}
export function has_listener_callback(elem, name, callback) {
return !!elem[name + "Listeners"] && elem[name + "Listeners"].includes(callback)
}
// ------------------------------- CALLEE --------------------------------
export function create_callee() {
const id = unique_id()
callees.set(id, 0)
return id
}
export function call_callee(id) {
const count = callees.get(id)
callees.set(id, count + 1)
}
export function callee_count(id) {
return callees.get(id)
}