From 7fc7a3e05970e4c4ccc0cc2e4d4a75dc8f7d10f0 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Wed, 9 Sep 2026 10:39:42 -0400 Subject: [PATCH] test(napplet): read the shell resource where a resource can be read `shellTemplateKeepsTheOpaqueIframeAndSourceChecks` sat in `commonTest`, so it ran on both targets: green under `:commons:jvmTest`, and red under `:commons:testAndroidHostTest` with MissingResourceException: ... files/napplet/shell.html. Android context is not initialized. `NappletWebContract.shellHtml()` goes through Compose Resources, whose Android reader needs an initialised `Context`. A host unit test has none and commons does not use Robolectric, so the same assertion was red or green depending only on which target happened to run it. That is the worst shape for a test: it fails on a developer's machine having passed in whatever ran last, and it kept the whole module red regardless of the change under review. Moved to `jvmTest` as `NappletShellResourceTest`. Nothing about it is platform-specific -- `shell.html` is one shared file, so reading it once on the JVM checks its contents everywhere. What is genuinely not covered is the Android resource plumbing, which needs a `Context`; that wants an instrumented test, and the KDoc says so rather than leaving the gap silent. The other two contract tests never touch a resource and stay in `commonTest`, still running on both targets. `:commons:testAndroidHostTest` now passes 1530 tests with no failures, and `:commons:jvmTest` 1913 -- the shell assertion among them, so it is still enforced. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01Wwk8tDEaEvsNoGbtrjZavz --- .../commons/napplet/NappletWebContractTest.kt | 13 ----- .../napplet/NappletShellResourceTest.kt | 57 +++++++++++++++++++ 2 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/napplet/NappletShellResourceTest.kt diff --git a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/napplet/NappletWebContractTest.kt b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/napplet/NappletWebContractTest.kt index 656ebcc1a6..61ce841117 100644 --- a/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/napplet/NappletWebContractTest.kt +++ b/commons/src/commonTest/kotlin/com/vitorpamplona/amethyst/commons/napplet/NappletWebContractTest.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.commons.napplet -import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertFalse @@ -64,16 +63,4 @@ class NappletWebContractTest { assertContains(csp, "form-action 'none'") assertFalse(csp.contains("'self'")) } - - @Test - fun shellTemplateKeepsTheOpaqueIframeAndSourceChecks() = - runTest { - val shell = NappletWebContract.shellHtml().decodeToString() - - assertContains(shell, "sandbox=\"${NappletWebContract.APP_SANDBOX_PLACEHOLDER}\"") - assertContains(shell, NappletWebContract.APP_BOOTSTRAP_PLACEHOLDER) - assertContains(shell, "e.source !== iframe.contentWindow") - assertContains(shell, "iframe.contentWindow.postMessage(msg, '*')") - assertFalse(shell.contains("allow-same-origin")) - } } diff --git a/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/napplet/NappletShellResourceTest.kt b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/napplet/NappletShellResourceTest.kt new file mode 100644 index 0000000000..43bd4b9159 --- /dev/null +++ b/commons/src/jvmTest/kotlin/com/vitorpamplona/amethyst/commons/napplet/NappletShellResourceTest.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.commons.napplet + +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertContains +import kotlin.test.assertFalse + +/** + * The shell page's own contents, asserted where the resource can actually be read. + * + * This lives in `jvmTest` rather than `commonTest` because [NappletWebContract.shellHtml] goes + * through Compose Resources, and that reader needs an initialised Android `Context` on the + * android target. A host unit test has none, so running this in `commonTest` failed + * `:commons:testAndroidHostTest` with `MissingResourceException` while passing `:commons:jvmTest` + * — the same assertion, red or green depending only on which target ran it. + * + * Nothing about the assertions is platform-specific: `shell.html` is one shared file, so reading + * it once on the JVM checks its contents everywhere. What is *not* covered here is the Android + * resource plumbing itself, which needs a `Context` — an instrumented test, or Robolectric, which + * commons does not use. + * + * The rest of the contract's tests stay in `commonTest` and still run on both targets; they never + * touch a resource. + */ +class NappletShellResourceTest { + @Test + fun shellTemplateKeepsTheOpaqueIframeAndSourceChecks() = + runTest { + val shell = NappletWebContract.shellHtml().decodeToString() + + assertContains(shell, "sandbox=\"${NappletWebContract.APP_SANDBOX_PLACEHOLDER}\"") + assertContains(shell, NappletWebContract.APP_BOOTSTRAP_PLACEHOLDER) + assertContains(shell, "e.source !== iframe.contentWindow") + assertContains(shell, "iframe.contentWindow.postMessage(msg, '*')") + assertFalse(shell.contains("allow-same-origin")) + } +}