mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2026-11-01 17:43:03 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ef192871b |
No files matched your search
@@ -1,17 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.33.0
|
||||
|
||||
* `Versions`:
|
||||
* `Exposed`: `1.5.1` -> `1.6.0`
|
||||
* `Serialization`: `1.11.0` -> `1.12.0`
|
||||
* `Compose`: `1.12.1` -> `1.12.2`
|
||||
* `SQLite`: `3.53.4.0` -> `3.54.1.0`
|
||||
* `Ktor`: `3.6.0` -> `3.6.1`
|
||||
* `Common`:
|
||||
* Add JS `MutationObserverInit` factory that omits unspecified options and preserves browser defaults.
|
||||
* Use safe `MutationObserver` options in `onRemoved` and cover DOM observation and outside-click cleanup with browser tests.
|
||||
|
||||
## 0.32.1
|
||||
|
||||
* `Coroutines`:
|
||||
|
||||
@@ -7,22 +7,6 @@ plugins {
|
||||
apply from: "$mppJvmJsWasmJsAndroidLinuxMingwLinuxArm64Project"
|
||||
|
||||
kotlin {
|
||||
js {
|
||||
browser {
|
||||
testTask {
|
||||
useKarma {
|
||||
useChromeHeadlessNoSandbox()
|
||||
useConfigDirectory(rootProject.relativeProjectPath("gradle/karma.config.d"))
|
||||
}
|
||||
}
|
||||
}
|
||||
nodejs {
|
||||
testTask {
|
||||
filter.excludeTestsMatching("dev.inmo.micro_utils.common.MutationObserverBrowserTests*")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlinx.browser.document
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.MutationObserver
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.*
|
||||
|
||||
fun Node.onRemoved(block: () -> Unit): MutationObserver {
|
||||
lateinit var observer: MutationObserver
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import org.w3c.dom.MutationObserverInit as NativeMutationObserverInit
|
||||
|
||||
/**
|
||||
* Creates options for [org.w3c.dom.MutationObserver] without adding unspecified properties.
|
||||
*
|
||||
* Unlike the Kotlin DOM factory, this factory leaves absent options out of the JavaScript object. In particular,
|
||||
* an absent [attributeFilter] must not become a null sequence, and absent [attributes] and [characterData] must not
|
||||
* override the browser defaults implied by their filters or old-value options.
|
||||
*
|
||||
* Null arguments are omitted. Explicit false values and empty arrays are preserved. The browser remains responsible
|
||||
* for validating the options passed to [org.w3c.dom.MutationObserver.observe].
|
||||
*/
|
||||
fun MutationObserverInit(
|
||||
childList: Boolean? = null,
|
||||
attributes: Boolean? = null,
|
||||
characterData: Boolean? = null,
|
||||
subtree: Boolean? = null,
|
||||
attributeOldValue: Boolean? = null,
|
||||
characterDataOldValue: Boolean? = null,
|
||||
attributeFilter: Array<String>? = null
|
||||
): NativeMutationObserverInit = js("{}").unsafeCast<NativeMutationObserverInit>().apply {
|
||||
if (childList != null) this.childList = childList
|
||||
if (attributes != null) this.attributes = attributes
|
||||
if (characterData != null) this.characterData = characterData
|
||||
if (subtree != null) this.subtree = subtree
|
||||
if (attributeOldValue != null) this.attributeOldValue = attributeOldValue
|
||||
if (characterDataOldValue != null) this.characterDataOldValue = characterDataOldValue
|
||||
if (attributeFilter != null) this.attributeFilter = attributeFilter
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlinx.browser.document
|
||||
import org.w3c.dom.MutationObserver
|
||||
import org.w3c.dom.events.Event
|
||||
import org.w3c.dom.events.EventListener
|
||||
import kotlin.js.Promise
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFails
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertSame
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/** Runs only in the browser: jsNodeTest excludes this class rather than reporting DOM checks as passing. */
|
||||
class MutationObserverBrowserTests {
|
||||
@Test
|
||||
fun childListSubtreeRecordsCanBeDrainedAndDisconnected() {
|
||||
val root = document.createElement("div")
|
||||
val nested = document.createElement("div")
|
||||
val child = document.createElement("span")
|
||||
root.appendChild(nested)
|
||||
val observer = MutationObserver { _, _ -> }
|
||||
try {
|
||||
observer.observe(root, MutationObserverInit(childList = true, subtree = true))
|
||||
nested.appendChild(child)
|
||||
val record = observer.takeRecords().single()
|
||||
assertEquals("childList", record.type)
|
||||
assertSame(nested, record.target)
|
||||
assertSame(child, record.addedNodes.item(0))
|
||||
assertEquals(0, record.removedNodes.length)
|
||||
assertNull(record.attributeName)
|
||||
assertTrue(observer.takeRecords().isEmpty())
|
||||
observer.disconnect()
|
||||
nested.removeChild(child)
|
||||
assertTrue(observer.takeRecords().isEmpty())
|
||||
} finally {
|
||||
observer.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun attributeFilterEnablesObservationAndPreservesOldValues() {
|
||||
val target = document.createElement("div")
|
||||
target.setAttribute("data-state", "old")
|
||||
val observer = MutationObserver { _, _ -> }
|
||||
try {
|
||||
observer.observe(target, MutationObserverInit(attributeFilter = arrayOf("data-state"), attributeOldValue = true))
|
||||
target.setAttribute("ignored", "value")
|
||||
target.setAttribute("data-state", "new")
|
||||
val record = observer.takeRecords().single()
|
||||
assertEquals("attributes", record.type)
|
||||
assertSame(target, record.target)
|
||||
assertEquals("data-state", record.attributeName)
|
||||
assertEquals("old", record.oldValue)
|
||||
assertNull(record.attributeNamespace)
|
||||
} finally {
|
||||
observer.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun characterDataOldValueEnablesObservation() {
|
||||
val target = document.createTextNode("old")
|
||||
val observer = MutationObserver { _, _ -> }
|
||||
try {
|
||||
observer.observe(target, MutationObserverInit(characterDataOldValue = true))
|
||||
target.data = "new"
|
||||
val record = observer.takeRecords().single()
|
||||
assertEquals("characterData", record.type)
|
||||
assertSame(target, record.target)
|
||||
assertEquals("old", record.oldValue)
|
||||
} finally {
|
||||
observer.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nullAttributeFilterIsOmittedInsteadOfConvertedToASequence() {
|
||||
val target = document.createElement("div")
|
||||
val observer = MutationObserver { _, _ -> }
|
||||
try {
|
||||
observer.observe(target, MutationObserverInit(childList = true, attributeFilter = null))
|
||||
target.appendChild(document.createElement("span"))
|
||||
assertEquals("childList", observer.takeRecords().single().type)
|
||||
} finally {
|
||||
observer.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun emptyAttributeFilterDoesNotObserveAllAttributes() {
|
||||
val target = document.createElement("div")
|
||||
val observer = MutationObserver { _, _ -> }
|
||||
try {
|
||||
observer.observe(target, MutationObserverInit(attributeFilter = emptyArray()))
|
||||
target.setAttribute("data-state", "new")
|
||||
assertTrue(observer.takeRecords().isEmpty())
|
||||
} finally {
|
||||
observer.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nativeValidationRejectsContradictoryOptions() {
|
||||
val target = document.createElement("div")
|
||||
val observer = MutationObserver { _, _ -> }
|
||||
try {
|
||||
assertFails { observer.observe(target, MutationObserverInit()) }
|
||||
assertFails {
|
||||
observer.observe(target, MutationObserverInit(attributes = false, attributeFilter = arrayOf("class")))
|
||||
}
|
||||
assertFails {
|
||||
observer.observe(target, MutationObserverInit(characterData = false, characterDataOldValue = true))
|
||||
}
|
||||
} finally {
|
||||
observer.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onRemovedCallsBackOnceAndDisconnects(): Promise<Unit> {
|
||||
val parent = document.createElement("div")
|
||||
val child = document.createElement("span")
|
||||
var calls = 0
|
||||
var observer: MutationObserver? = null
|
||||
return Promise.resolve(Unit).then {
|
||||
parent.appendChild(child)
|
||||
document.body!!.appendChild(parent)
|
||||
observer = child.onRemoved { calls++ }
|
||||
parent.removeChild(child)
|
||||
}.then {
|
||||
assertEquals(1, calls)
|
||||
parent.appendChild(child)
|
||||
parent.removeChild(child)
|
||||
}.then {
|
||||
assertEquals(1, calls)
|
||||
}.finally {
|
||||
observer?.disconnect()
|
||||
parent.parentNode?.removeChild(parent)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun onClickOutsideStopsListeningAfterRemoval(): Promise<Unit> {
|
||||
val target = document.createElement("div")
|
||||
var calls = 0
|
||||
var listener: EventListener? = null
|
||||
return Promise.resolve(Unit).then {
|
||||
document.body!!.appendChild(target)
|
||||
listener = target.onClickOutside { calls++ }
|
||||
document.dispatchEvent(Event("click"))
|
||||
assertEquals(1, calls)
|
||||
target.parentNode!!.removeChild(target)
|
||||
}.then {
|
||||
document.dispatchEvent(Event("click"))
|
||||
assertEquals(1, calls)
|
||||
}.finally {
|
||||
listener?.let { document.removeEventListener("click", it) }
|
||||
target.parentNode?.removeChild(target)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import org.w3c.dom.MutationObserverInit as NativeMutationObserverInit
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertContentEquals
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertSame
|
||||
|
||||
class MutationObserverInitTests {
|
||||
private fun keys(options: NativeMutationObserverInit): List<String> =
|
||||
js("Object.keys(options)").unsafeCast<Array<String>>().sorted()
|
||||
|
||||
@Test
|
||||
fun emptyOptionsHaveNoProperties() {
|
||||
assertEquals(emptyList(), keys(MutationObserverInit()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun childListOptionsOmitUnspecifiedProperties() {
|
||||
val options = MutationObserverInit(childList = true, subtree = true)
|
||||
assertEquals(listOf("childList", "subtree"), keys(options))
|
||||
assertEquals(true, options.childList)
|
||||
assertEquals(true, options.subtree)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun explicitNullOptionsAreOmitted() {
|
||||
val options = MutationObserverInit(
|
||||
childList = null,
|
||||
attributes = null,
|
||||
characterData = null,
|
||||
subtree = null,
|
||||
attributeOldValue = null,
|
||||
characterDataOldValue = null,
|
||||
attributeFilter = null
|
||||
)
|
||||
assertEquals(emptyList(), keys(options))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun explicitFalseAndEmptyFilterArePreserved() {
|
||||
val filter = emptyArray<String>()
|
||||
val options = MutationObserverInit(false, false, false, false, false, false, filter)
|
||||
assertEquals(
|
||||
listOf("attributeFilter", "attributeOldValue", "attributes", "characterData", "characterDataOldValue", "childList", "subtree"),
|
||||
keys(options)
|
||||
)
|
||||
assertEquals(false, options.childList)
|
||||
assertEquals(false, options.attributes)
|
||||
assertEquals(false, options.characterData)
|
||||
assertEquals(false, options.subtree)
|
||||
assertEquals(false, options.attributeOldValue)
|
||||
assertEquals(false, options.characterDataOldValue)
|
||||
assertSame(filter, options.attributeFilter)
|
||||
assertContentEquals(emptyArray(), options.attributeFilter)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filtersAndOldValueOptionsDoNotForceObservationFlags() {
|
||||
val filter = arrayOf("class", "data-state")
|
||||
val options = MutationObserverInit(
|
||||
attributeFilter = filter,
|
||||
attributeOldValue = true,
|
||||
characterDataOldValue = true
|
||||
)
|
||||
assertEquals(listOf("attributeFilter", "attributeOldValue", "characterDataOldValue"), keys(options))
|
||||
assertSame(filter, options.attributeFilter)
|
||||
assertEquals(true, options.attributeOldValue)
|
||||
assertEquals(true, options.characterDataOldValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun eachCallCreatesIndependentOptions() {
|
||||
val first = MutationObserverInit(childList = true)
|
||||
val second = MutationObserverInit(attributes = true)
|
||||
first.subtree = true
|
||||
assertEquals(listOf("childList", "subtree"), keys(first))
|
||||
assertEquals(listOf("attributes"), keys(second))
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -16,5 +16,5 @@ crypto_js_version=4.2.0
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.33.0
|
||||
android_code_version=321
|
||||
version=0.32.1
|
||||
android_code_version=320
|
||||
@@ -1,25 +1,25 @@
|
||||
[versions]
|
||||
|
||||
kt = "2.4.21"
|
||||
kt-serialization = "1.12.0"
|
||||
kt-serialization = "1.11.0"
|
||||
kt-coroutines = "1.11.0"
|
||||
|
||||
kotlinx-browser = "0.5.0"
|
||||
|
||||
kslog = "2.1.0"
|
||||
|
||||
jb-compose = "1.12.2"
|
||||
jb-compose = "1.12.1"
|
||||
jb-compose-material3 = "1.9.0"
|
||||
jb-compose-icons = "1.7.8"
|
||||
jb-exposed = "1.6.0"
|
||||
jb-exposed = "1.5.1"
|
||||
jb-dokka = "2.2.0"
|
||||
|
||||
sqlite = "3.54.1.0"
|
||||
sqlite = "3.53.4.0"
|
||||
|
||||
korlibs = "5.4.0"
|
||||
uuid = "0.8.4"
|
||||
|
||||
ktor = "3.6.1"
|
||||
ktor = "3.6.0"
|
||||
|
||||
gh-release = "2.5.2"
|
||||
|
||||
@@ -27,7 +27,7 @@ koin = "4.2.2"
|
||||
|
||||
okio = "3.18.2"
|
||||
|
||||
ksp = "2.3.11"
|
||||
ksp = "2.3.13"
|
||||
kotlin-poet = "2.4.0"
|
||||
|
||||
versions = "0.65.0"
|
||||
|
||||
Reference in new issue
Block a user