also looking at this
fix(native): preserve workspace sources and native overlay lifecycle
#384
20 files
+500
-227
Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.
| @@ -77,10 +77,12 @@ fun main() { | ||
| 77 | 77 | |
| 78 | 78 | test('preserves modal viewport promotion and ordering across targeted reconciliation', async () => { |
| 79 | 79 | const template = await Bun.file(new URL('../templates/MainActivityNative.kt.template', import.meta.url)).text() |
| 80 | const promote = template.match(/ private fun promoteModalOverlays\(screen: Screen, controls: Collection<View>\) \{[\s\S]*?\n \}/)?.[0] | |
| 80 | const promote = template.match(/ private fun promoteModalOverlays\(screen: Screen\) \{[\s\S]*?\n \}/)?.[0] | |
| 81 | 81 | const targeted = template.match(/ private fun applyTargetedUpdates\(screen: Screen, ids: List<String>\): Boolean \{[\s\S]*?\n \}/)?.[0] |
| 82 | 82 | if (!promote || !targeted) throw new Error('Native modal reconciliation implementation is missing') |
| 83 | 83 | await runKotlinFixture(` |
| 84 | import kotlin.math.min | |
| 85 | ||
| 84 | 86 | open class View { |
| 85 | 87 | var parent: ViewGroup? = null |
| 86 | 88 | fun bringToFront() { val group = parent ?: return; group.children.remove(this); group.children.add(this) } |
| @@ -88,6 +90,8 @@ open class View { | ||
| 88 | 90 | open class ViewGroup : View() { |
| 89 | 91 | val children = mutableListOf<View>() |
| 90 | 92 | val childStyles = mutableMapOf<View, JSONObject>() |
| 93 | val childCount: Int get() = children.size | |
| 94 | fun getChildAt(index: Int): View = children[index] | |
| 91 | 95 | fun removeView(view: View) { children.remove(view); childStyles.remove(view); view.parent = null } |
| 92 | 96 | fun setChildStyle(view: View, style: JSONObject, density: Float) { childStyles[view] = style } |
| 93 | 97 | fun addView(view: View) { view.parent?.removeView(view); children.add(view); view.parent = this } |
| @@ -95,6 +99,7 @@ open class ViewGroup : View() { | ||
| 95 | 99 | class CraftNativeModalView : ViewGroup() { |
| 96 | 100 | val content = ViewGroup().also(::addView) |
| 97 | 101 | var viewportStyle = JSONObject("popup-frame") |
| 102 | var declarationPath = "" | |
| 98 | 103 | } |
| 99 | 104 | class DisplayMetrics { val density = 1f } |
| 100 | 105 | class Resources { val displayMetrics = DisplayMetrics() } |
| @@ -111,6 +116,7 @@ class Screen { | ||
| 111 | 116 | val nodePaths = mutableMapOf<String, String>() |
| 112 | 117 | val protocolIds = mutableMapOf<View, String>() |
| 113 | 118 | val lastLayoutBounds = mutableMapOf<View, String>() |
| 119 | val modalVisibility = mutableMapOf<View, Boolean>() | |
| 114 | 120 | val mutations = MutationDocument(mapOf("parent" to "root", "nested" to "parent", "standalone" to "root")) |
| 115 | 121 | var layoutEvents = 0 |
| 116 | 122 | } |
| @@ -134,12 +140,16 @@ fun main() { | ||
| 134 | 140 | parent.content.addView(nested) |
| 135 | 141 | root.addView(standalone) |
| 136 | 142 | for (modal in listOf(parent, nested, standalone)) screen.view.setChildStyle(modal, modal.viewportStyle, 1f) |
| 143 | parent.declarationPath = "0" | |
| 144 | nested.declarationPath = "0.0" | |
| 145 | standalone.declarationPath = "1" | |
| 137 | 146 | for ((id, view) in listOf("root" to root, "nested" to nested, "parent" to parent, "standalone" to standalone)) { |
| 138 | 147 | screen.nodeViews[id] = view |
| 139 | 148 | screen.nodePaths[id] = id |
| 140 | 149 | screen.protocolIds[view] = id |
| 141 | 150 | screen.controls[id] = view |
| 142 | 151 | screen.controls["id:" + id] = view |
| 152 | if (view is CraftNativeModalView) screen.modalVisibility[view] = true | |
| 143 | 153 | } |
| 144 | 154 | fun verify() { |
| 145 | 155 | check(parent.parent === screen.view && nested.parent === screen.view && standalone.parent === screen.view) { "An overlay escaped its viewport" } |
| @@ -148,7 +158,7 @@ fun main() { | ||
| 148 | 158 | check(children.indexOf(parent) < children.indexOf(nested)) { "The parent scrim covered its nested popup" } |
| 149 | 159 | check(children.indexOf(nested) < children.indexOf(standalone)) { "Sibling overlay order changed" } |
| 150 | 160 | } |
| 151 | promoteModalOverlays(screen, screen.controls.values) | |
| 161 | promoteModalOverlays(screen) | |
| 152 | 162 | verify() |
| 153 | 163 | check(applyTargetedUpdates(screen, listOf("parent"))) |
| 154 | 164 | verify() |
| @@ -159,7 +169,7 @@ fun main() { | ||
| 159 | 169 | parent.content.addView(nested) |
| 160 | 170 | root.addView(standalone) |
| 161 | 171 | screen.protocolIds.clear() |
| 162 | promoteModalOverlays(screen, screen.controls.values) | |
| 172 | promoteModalOverlays(screen) | |
| 163 | 173 | verify() |
| 164 | 174 | println("PASS modal viewport, nested scrim ordering, sibling ordering, targeted updates and legacy renders") |
| 165 | 175 | } |