Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions packages/sdk/src/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,92 @@ describe("override-set orphan cleanup on removeElement", () => {
expect(overrides["hf-sub.style.opacity"]).toBe("1");
});
});

// ─── setSelection / getSelection / selectionchange ───────────────────────────

describe("setSelection", () => {
it("getSelection returns empty array before any setSelection call", async () => {
const comp = await openComposition(BASE_HTML);
expect(comp.getSelection()).toEqual([]);
});

it("setSelection updates getSelection", async () => {
const comp = await openComposition(BASE_HTML);
comp.setSelection(["hf-title"]);
expect(comp.getSelection()).toEqual(["hf-title"]);
});

it("setSelection with multiple ids", async () => {
const comp = await openComposition(BASE_HTML);
comp.setSelection(["hf-title", "hf-sub"]);
expect(comp.getSelection()).toEqual(["hf-title", "hf-sub"]);
});

it("setSelection([]) clears selection", async () => {
const comp = await openComposition(BASE_HTML);
comp.setSelection(["hf-title"]);
comp.setSelection([]);
expect(comp.getSelection()).toEqual([]);
});

it("setSelection fires selectionchange with new ids", async () => {
const comp = await openComposition(BASE_HTML);
const calls: string[][] = [];
comp.on("selectionchange", (ids) => calls.push(ids));
comp.setSelection(["hf-title"]);
expect(calls).toEqual([["hf-title"]]);
});

it("setSelection fires selectionchange with empty array when clearing", async () => {
const comp = await openComposition(BASE_HTML);
comp.setSelection(["hf-title"]);
const calls: string[][] = [];
comp.on("selectionchange", (ids) => calls.push(ids));
comp.setSelection([]);
expect(calls).toEqual([[]]);
});

it("selectionchange listener receives a fresh copy each call", async () => {
const comp = await openComposition(BASE_HTML);
const snapshots: string[][] = [];
comp.on("selectionchange", (ids) => snapshots.push(ids));
comp.setSelection(["hf-title"]);
comp.setSelection(["hf-sub"]);
expect(snapshots[0]).toEqual(["hf-title"]);
expect(snapshots[1]).toEqual(["hf-sub"]);
});

it("unsubscribed listener does not fire", async () => {
const comp = await openComposition(BASE_HTML);
const calls: string[][] = [];
const off = comp.on("selectionchange", (ids) => calls.push(ids));
off();
comp.setSelection(["hf-title"]);
expect(calls).toHaveLength(0);
});

it("selection() proxy operates on ids at call time", async () => {
const comp = await openComposition(BASE_HTML);
comp.setSelection(["hf-title"]);
const proxy = comp.selection();
expect(proxy.ids).toEqual(["hf-title"]);
});

it("setSelection does not affect undo stack", async () => {
const comp = await openComposition(BASE_HTML);
comp.setStyle("hf-title", { color: "#ff0000" });
comp.setSelection(["hf-sub"]);
expect(comp.canUndo()).toBe(true);
comp.undo();
// selection must not have been pushed to history
expect(comp.canUndo()).toBe(false);
});

it("setSelection does not emit a patch event", async () => {
const comp = await openComposition(BASE_HTML);
const patches: unknown[] = [];
comp.on("patch", (e) => patches.push(e));
comp.setSelection(["hf-title"]);
expect(patches).toHaveLength(0);
});
});
5 changes: 5 additions & 0 deletions packages/sdk/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,11 @@ class CompositionImpl implements Composition {
return [...this.currentSelection];
}

setSelection(ids: string[]): void {
this.currentSelection = [...ids];
this.selectionHandlers.forEach((h) => h([...this.currentSelection]));
}

// ── Dispatch / batch ─────────────────────────────────────────────────────────

// fallow-ignore-next-line complexity
Expand Down
2 changes: 2 additions & 0 deletions packages/sdk/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,8 @@ export interface Composition {
/** Curried handle — holds only the id, no stale-ref hazard */
element(id: HfId): ElementHandle;
getSelection(): string[];
/** Replace the current selection; fires selectionchange. Pass [] to clear. */
setSelection(ids: string[]): void;

// ── Advanced / agent layer (F10 layer 2) ──────────────────────────────────
dispatch(op: EditOp, opts?: { origin?: unknown }): void;
Expand Down
Loading