Compare commits

..
Author SHA1 Message Date
renovate[bot] b3cf3501b0 Update ktor monorepo to v3.6.1 2026-10-29 18:44:41 +00:00
8 changed files with 7 additions and 311 deletions

No files matched your search

-12
View File
@@ -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`:
-16
View File
@@ -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
View File
@@ -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
+4 -4
View File
@@ -1,20 +1,20 @@
[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"