diff --git a/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt b/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt index 21464bc..27594e3 100644 --- a/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt +++ b/app/src/main/kotlin/io/flamenet/messenger/app/ChatScreens.kt @@ -2565,15 +2565,7 @@ fun RegisteredDevicesSection( } } Text( - device.createdAt?.let { raw -> - runCatching { - val instant = java.time.Instant.parse(raw) - val fmt = java.time.format.DateTimeFormatter - .ofPattern("MMM d, yyyy HH:mm") - .withZone(java.time.ZoneId.systemDefault()) - "Registered ${'$'}{fmt.format(instant)}" - }.getOrDefault("Registered $raw") - } ?: "Registered date unknown", + registeredLine(device.createdAt), fontSize = 12.sp, color = Palette.SecondaryText, ) @@ -2671,3 +2663,26 @@ private val DISAPPEAR_LABELS = mapOf( 86_400 to "a day", 604_800 to "a week", ) + +/** + * "Registered " for one device row. + * + * 🔴 **This shipped rendering its own source code.** The string was written + * `"Registered ${'$'}{fmt.format(instant)}"`, and `${'$'}` emits a LITERAL + * dollar sign — so the template was never evaluated and the screen showed + * `Registered ${fmt.format(instant)}` to the user. Kotlin cannot warn about + * it: escaping a dollar is a legitimate thing to want, just never here. + * + * Lifted out of the Composable so it can be asserted. A string built inline in + * a `Text(...)` is a string nothing can test, which is why this survived. + */ +internal fun registeredLine(createdAt: String?): String { + val raw = createdAt ?: return "Registered date unknown" + return runCatching { + val instant = java.time.Instant.parse(raw) + val fmt = java.time.format.DateTimeFormatter + .ofPattern("MMM d, yyyy HH:mm") + .withZone(java.time.ZoneId.systemDefault()) + "Registered ${fmt.format(instant)}" + }.getOrDefault("Registered $raw") +} diff --git a/app/src/test/kotlin/io/flamenet/messenger/app/RegisteredLineTest.kt b/app/src/test/kotlin/io/flamenet/messenger/app/RegisteredLineTest.kt new file mode 100644 index 0000000..4c77387 --- /dev/null +++ b/app/src/test/kotlin/io/flamenet/messenger/app/RegisteredLineTest.kt @@ -0,0 +1,39 @@ +package io.flamenet.messenger.app + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +/** + * The device row's "Registered " line. + * + * 🔴 **This shipped rendering its own source code.** The string was written + * with an escaped dollar, so the template was never evaluated and the screen + * showed `Registered ${fmt.format(instant)}` to the user. Nothing could warn + * about it — escaping a dollar is legitimate, just never there — and nothing + * could test it either, because it was built inline inside a `Text(...)`. + */ +class RegisteredLineTest { + + @Test + fun `a real timestamp becomes a date, not template source`() { + val line = registeredLine("2026-08-29T14:35:00Z") + assertFalse("\${" in line, "the template was not evaluated: $line") + assertFalse("fmt.format" in line, "source text reached the screen: $line") + assertTrue(line.startsWith("Registered "), line) + assertTrue("2026" in line, "the year should be in there somewhere: $line") + } + + /** An unparseable value is shown as-is rather than swallowed. */ + @Test + fun `an unparseable timestamp falls back to the raw value`() { + assertEquals("Registered whenever", registeredLine("whenever")) + } + + /** ⚠️ Absent is not the same as unparseable, and must not read as a date. */ + @Test + fun `a missing timestamp says so`() { + assertEquals("Registered date unknown", registeredLine(null)) + } +}