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:
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
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user