also looking at this
fix(mobile): preserve native editing, navigation, and durable media requests
#369
Merged
chris
wants to merge
codex/native-vertical-scroll-tab-swipes
into
main
36 files
+864
-185
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.
| @@ -217,11 +217,15 @@ class NativeFlatListTest { | ||
| 217 | 217 | list.apply(nodes, true, 1, true, 0.1, renderer(context, renders), { _ -> }, { _ -> }, null) |
| 218 | 218 | } |
| 219 | 219 | awaitCount(list, 9) |
| 220 | - val horizontal = list.layoutManager as LinearLayoutManager | |
| 221 | - assertEquals(LinearLayoutManager.HORIZONTAL, horizontal.orientation) | |
| 222 | - val invertedIdentities = list.visibleItemIdentities | |
| 223 | - assertEquals("row-7", invertedIdentities.first()) | |
| 224 | - assertEquals("mounted identities must stay in adapter order", invertedIdentities, invertedIdentities.sortedBy { it.substringAfter('-').toInt() }) | |
| 220 | + instrumentation.runOnMainSync { | |
| 221 | + attach(list) | |
| 222 | + val horizontal = list.layoutManager as LinearLayoutManager | |
| 223 | + assertEquals(LinearLayoutManager.HORIZONTAL, horizontal.orientation) | |
| 224 | + val invertedIdentities = list.visibleItemIdentities | |
| 225 | + assertEquals("row-7", invertedIdentities.first()) | |
| 226 | + val adapterOrder = nodes.reversed().map { it.getString("id") } | |
| 227 | + assertEquals("mounted identities must stay in adapter order", adapterOrder.filter { it in invertedIdentities }, invertedIdentities) | |
| 228 | + } | |
| 225 | 229 | |
| 226 | 230 | instrumentation.runOnMainSync { |
| 227 | 231 | list.apply(nodes, false, 3, false, 0.1, renderer(context, renders), { _ -> }, { _ -> }, null) |
| @@ -284,29 +288,33 @@ class NativeFlatListTest { | ||
| 284 | 288 | |
| 285 | 289 | @Test |
| 286 | 290 | fun nonScrollingListMeasuresItsFullContentHeight() { |
| 287 | - val context = instrumentation.targetContext | |
| 288 | - val list = CraftNativeFlatList(context) | |
| 291 | + val (scenario, list) = launchHost() | |
| 292 | + val context = list.context | |
| 289 | 293 | val rows = (0 until 3).map(::row) |
| 290 | 294 | val contentSizeChanges = AtomicInteger() |
| 291 | - instrumentation.runOnMainSync { | |
| 292 | - list.setScrollEnabled(false) | |
| 293 | - list.onContentSizeChanged = { contentSizeChanges.incrementAndGet() } | |
| 294 | - list.apply(rows, false, 1, false, 0.1, renderer(context, AtomicInteger()), { _ -> }, { _ -> }, null) | |
| 295 | - } | |
| 296 | - awaitCount(list, rows.size) | |
| 297 | - instrumentation.runOnMainSync { | |
| 298 | - list.measure( | |
| 299 | - View.MeasureSpec.makeMeasureSpec(320, View.MeasureSpec.EXACTLY), | |
| 300 | - View.MeasureSpec.makeMeasureSpec(1_000, View.MeasureSpec.AT_MOST), | |
| 301 | - ) | |
| 302 | - list.layout(0, 0, list.measuredWidth, list.measuredHeight) | |
| 303 | - } | |
| 304 | - assertTrue("non-scrolling FlatList should include every row", list.measuredHeight >= rows.size * 44) | |
| 305 | - repeat(100) { | |
| 306 | - if (contentSizeChanges.get() > 0) return@repeat | |
| 307 | - SystemClock.sleep(10) | |
| 295 | + try { | |
| 296 | + instrumentation.runOnMainSync { | |
| 297 | + list.setScrollEnabled(false) | |
| 298 | + list.onContentSizeChanged = { contentSizeChanges.incrementAndGet() } | |
| 299 | + list.apply(rows, false, 1, false, 0.1, renderer(context, AtomicInteger()), { _ -> }, { _ -> }, null) | |
| 300 | + } | |
| 301 | + awaitCount(list, rows.size) | |
| 302 | + instrumentation.runOnMainSync { | |
| 303 | + list.measure( | |
| 304 | + View.MeasureSpec.makeMeasureSpec(320, View.MeasureSpec.EXACTLY), | |
| 305 | + View.MeasureSpec.makeMeasureSpec(1_000, View.MeasureSpec.AT_MOST), | |
| 306 | + ) | |
| 307 | + list.layout(0, 0, list.measuredWidth, list.measuredHeight) | |
| 308 | + assertTrue("non-scrolling FlatList should include every row", list.measuredHeight >= rows.size * 44) | |
| 309 | + } | |
| 310 | + repeat(100) { | |
| 311 | + if (contentSizeChanges.get() > 0) return@repeat | |
| 312 | + SystemClock.sleep(10) | |
| 313 | + } | |
| 314 | + assertTrue("non-scrolling FlatList should report content-size changes", contentSizeChanges.get() > 0) | |
| 315 | + } finally { | |
| 316 | + scenario.close() | |
| 308 | 317 | } |
| 309 | - assertTrue("non-scrolling FlatList should report content-size changes", contentSizeChanges.get() > 0) | |
| 310 | 318 | } |
| 311 | 319 | |
| 312 | 320 | @Test |
| @@ -176,8 +176,10 @@ class NativeMapTest { | ||
| 176 | 176 | ?.getNumberProperty("width") != null |
| 177 | 177 | } |
| 178 | 178 | val density = main { host.resources.displayMetrics.density.toDouble() } |
| 179 | - val width = features(map).first { it.getStringProperty("craftOverlayId") == "route" } | |
| 180 | - .getNumberProperty("width").toDouble() | |
| 179 | + val width = main { | |
| 180 | + features(map).first { it.getStringProperty("craftOverlayId") == "route" } | |
| 181 | + .getNumberProperty("width").toDouble() | |
| 182 | + } | |
| 181 | 183 | assertEquals("MapLibre must scale logical stroke points by display density", 5.0 * density, width, 0.001) |
| 182 | 184 | } |
| 183 | 185 | |
| @@ -1858,10 +1858,16 @@ class NativeNavigationTest { | ||
| 1858 | 1858 | people.scrollTo(0, (peopleContent?.height ?: 0) - people.height) |
| 1859 | 1859 | } |
| 1860 | 1860 | SystemClock.sleep(150) |
| 1861 | - assertTrue( | |
| 1862 | - "ScrollView sticky header did not remain pinned", | |
| 1863 | - screenTop(peopleHeader) >= screenTop(people) + (8 * density).toInt() - 1, | |
| 1864 | - ) | |
| 1861 | + instrumentation.runOnMainSync { | |
| 1862 | + // Content padding scrolls away; a sticky header pins to the | |
| 1863 | + // viewport edge, matching the iOS native scroll host. | |
| 1864 | + val headerTop = screenTop(peopleHeader) | |
| 1865 | + val viewportTop = screenTop(people) | |
| 1866 | + assertTrue( | |
| 1867 | + "ScrollView sticky header did not remain pinned: header=$headerTop viewport=$viewportTop", | |
| 1868 | + kotlin.math.abs(headerTop - viewportTop) <= 1, | |
| 1869 | + ) | |
| 1870 | + } | |
| 1865 | 1871 | val peopleLayouts = awaitView(activity, "people-layout-status") as TextView |
| 1866 | 1872 | val initialRowLayouts = peopleLayouts.text.toString().substringAfterLast(": ").toInt() |
| 1867 | 1873 | assertTrue("ScrollView rows did not report layout", initialRowLayouts > 0) |