SidecarPathParseSmokeTest.kt
src/jvmMain/kotlin/afield/examples/SidecarPathParseSmokeTest.kt · 106 lines
package afield.examples
import afield.document.graph.Box
import afield.document.persistence.FileWatchManager
import afield.document.persistence.SavingContext
import afield.app.server.SessionInformation
import afield.document.graph.box
import afield.app.server.thisSessionInfo
import java.io.File
import java.nio.file.Files
import java.util.concurrent.ConcurrentHashMap
FIELD_TEST id: examples.file-watch.sidecar-path-parse runner: jvm-main entrypoint: afield.examples.SidecarPathParseSmokeTest tags: [smoke, file-watch, sidecar] timeoutSec: 20 expect: exitCode: 0 stdoutContains: - "SidecarPathParseSmokeTest ok"
object SidecarPathParseSmokeTest
Regression guard for the "dragging a macro spawns a spurious phantom box" bug.
A box whose id contains underscores (e.g. fn_fit_demo, created by the
fn_differentiable_fit_demo transcript) saves its code property to the sidecar file
fn_fit_demo_code_.kt. FileWatchManager.parseSidecarPath used to split on the FIRST
underscore, mis-parsing that into box id "fn" / property "fit_demo_code". Because box "fn"
did not exist, the orphan scanner auto-created a phantom box named "Fit demo code".
The parse must instead recover box id "fn_fit_demo" / property "code" so the existing box owns the file and no phantom is created.
object SidecarPathParseSmokeTest {
fun main(args: Array<String>)
body — 66 lines, from line 40
@JvmStatic
fun main(args: Array<String>) {
val workspace = Files.createTempDirectory("afield-sidecar-parse").toFile()
val document = File(workspace, "docA").also { it.mkdirs() }
File(document, SavingContext.METADATA_EDN).writeText("{:version 2 :boxes []}\n")
val root = Box("root")
val session = SessionInformation(
filename = document.absolutePath,
documentName = document.name,
boxes = ConcurrentHashMap(),
root = root,
ephemeral = true
)
root.inside = session
session.boxes[root.id] = root
thisSessionInfo.set(session)
try {
// The box the transcript actually defines.
box("fn_fit_demo", createIfMissing = true, initIfCreated = false, root = session)
val watcher = FileWatchManager(session)
fun parse(name: String) = watcher.parseSidecarPath(File(document, name))
// 1. The exact regression: underscore-bearing box id resolves to the real box, not "fn".
val fitDemo = parse("fn_fit_demo_code_.kt")
require(fitDemo != null) { "expected fn_fit_demo_code_.kt to parse as a sidecar" }
require(fitDemo.boxId == "fn_fit_demo") {
"expected box id 'fn_fit_demo', got '${fitDemo.boxId}' (property '${fitDemo.propertyName}')"
}
require(fitDemo.propertyName == "code") { "expected property 'code', got '${fitDemo.propertyName}'" }
// 2. Underscore-bearing property name resolves correctly for a not-yet-created box.
// This used to be spelled with `remote_js`, which was never a real property — see the
// comment beside `customization` in Box.ioSeparateProperties. `fragment_s` is a live one.
val remote = parse("some_box_fragment_s_.slang")
require(remote != null && remote.boxId == "some_box" && remote.propertyName == "fragment_s") {
"expected some_box/fragment_s, got $remote"
}
// 3. Underscores on BOTH sides at once — the hardest case for the old first-underscore split.
val both = parse("my_cool_box_fragment_s_.slang")
require(both != null && both.boxId == "my_cool_box" && both.propertyName == "fragment_s") {
"expected my_cool_box/fragment_s, got $both"
}
// 4. Longest known property suffix wins (vertex_fragment over fragment) when no box matches.
val longest = parse("mybox_vertex_fragment_.slang")
require(longest != null && longest.boxId == "mybox" && longest.propertyName == "vertex_fragment") {
"expected mybox/vertex_fragment, got $longest"
}
// 5. Fallback: a name matching no known property keeps the legacy first-underscore split.
val loose = parse("foo_stuff_.md")
require(loose != null && loose.boxId == "foo" && loose.propertyName == "stuff") {
"expected foo/stuff fallback, got $loose"
}
} finally {
thisSessionInfo.remove()
}
println("SidecarPathParseSmokeTest ok")
}
}