test(artifacts): write the storyboard fixture's action in Cocoa syntax

MacFixture.storyboard declared its button action with the iOS attribute,
<action selector="buildTapped:" destination="vc1">. Cocoa documents
name an action's receiver with target=. ibtool compiled the iOS spelling
into an NSNibControlConnector whose NSSource is the view controller
placeholder and whose NSDestination is the button. That is the reverse
of what Xcode-authored AppKit NIBs contain (Music, Chess,
KeepingYouAwake, and Transmission all archive NSSource as the control),
and it is what the old inversion had matched. With target= the compiled
fixture archives the button as NSSource. The temporary connector dump is
removed; the observed routes remain in the assertion message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 committed 2026-10-07 09:06:36 +00:00
1 parent d421d3f286
commit 53bfa7e3f7
2 files changed
+2 -50

No files matched your search

@@ -245,55 +245,7 @@ describe("bounded Interface Builder archive decoding", () => {
(from === action?.id || to === action?.id),
)
.map(({ from, to }) => `${describeNode(from)} -> ${describeNode(to)}`);
// Temporary diagnostic: the compiled connector records.
const { decodeNibArchive } =
await import("../../../src/artifacts/NibArchive.js");
const { readdir, readFile } = await import("node:fs/promises");
const compiled = join(resources, "MacFixture.storyboardc");
const records: string[] = [];
for (const entry of await readdir(compiled, { recursive: true })) {
if (!String(entry).endsWith(".nib")) continue;
let bytes: Buffer;
try {
bytes = await readFile(join(compiled, String(entry)));
} catch {
continue;
}
if (bytes.subarray(0, 10).toString("ascii") !== "NIBArchive") {
records.push(
`${String(entry)}: ${bytes.subarray(0, 8).toString("ascii")}`,
);
continue;
}
const archive = decodeNibArchive(bytes);
const byId = new Map(archive.objects.map((item) => [item.id, item]));
const named = (value: unknown) => {
const ref =
typeof value === "object" &&
value !== null &&
"$nib_object_ref" in value
? Number(value.$nib_object_ref)
: undefined;
return ref === undefined
? JSON.stringify(value)
: `${byId.get(ref)?.class_name.replace(/\0+$/u, "") ?? "?"}#${String(ref)}`;
};
for (const item of archive.objects) {
const name = item.class_name.replace(/\0+$/u, "");
if (!/Connector|Placeholder/u.test(name)) continue;
records.push(
`${String(entry)}#${String(item.id)} ${name} {${Object.entries(
item.values,
)
.map(([key, value]) => `${key}=${named(value)}`)
.join(", ")}}`,
);
}
}
expect(
actionSource,
`${actionRoutes.join("; ")} | ${records.join(" | ")}`,
).toBeDefined();
expect(actionSource, actionRoutes.join("; ")).toBeDefined();
expect(
analysis.graph.edges.some(
({ from, relation, to }) =>
+1 -1
View File
@@ -11,7 +11,7 @@
<rect key="frame" x="120" y="400" width="150" height="44"/>
<title key="title" string="Build"/>
<connections>
<action selector="buildTapped:" destination="vc1" id="action1"/>
<action selector="buildTapped:" target="vc1" id="action1"/>
</connections>
</button>
</subviews>