Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
63 changes: 63 additions & 0 deletions src/brsTypes/components/RoSGNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ export class RoSGNode extends BrsComponent implements BrsValue, BrsIterable {
this.callfunc,
this.issubtype,
this.parentsubtype,
this.clone,
],
ifSGNodeBoundingRect: [this.boundingRect],
});
Expand Down Expand Up @@ -1695,6 +1696,16 @@ export class RoSGNode extends BrsComponent implements BrsValue, BrsIterable {
},
});

private clone = new Callable("clone", {
signature: {
args: [new StdlibArgument("isDeepCopy", ValueKind.Boolean)],
returns: ValueKind.Object,
},
impl: (interpreter: Interpreter, isDeepCopy: BrsBoolean) => {
return cloneNode(interpreter, this, isDeepCopy.toBoolean());
},
});

/* Returns the subtype of this node as specified when it was created */
private subtype = new Callable("subtype", {
signature: {
Expand Down Expand Up @@ -1914,3 +1925,55 @@ function addChildren(
}
});
}

function cloneNode(
interpreter: Interpreter,
toBeCloned: RoSGNode,
isDeepCopy: Boolean
): RoSGNode | BrsInvalid {
let copy = createNodeByType(interpreter, new BrsString(toBeCloned.nodeSubtype));

let originalFields = toBeCloned.getFields();
let copiedFields = new RoAssociativeArray([]);
let addReplace = copiedFields.getMethod("addreplace");

// Copy the fields.
for (let [key, value] of originalFields) {
if (addReplace) {
addReplace.call(
interpreter,
new BrsString(key),
getBrsValueFromFieldType(value.getType(), value.toString())
);
}
}

if (!(copy instanceof BrsInvalid)) {
let update = copy.getMethod("update");

if (update) {
update.call(interpreter, copiedFields, BrsBoolean.True);
}

// A deep clone also copies children.
if (isDeepCopy) {
let appendChild = copy.getMethod("appendchild");
let getChildren = toBeCloned.getMethod("getchildren");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code would be a little cleaner if we exposed getChildren in typescript. I wonder if that's worth it. Then the callable would just wrap that response in an RoArray.


if (getChildren && appendChild) {
let children = getChildren.call(interpreter, new Int32(-1), new Int32(0));

if (children instanceof RoArray) {
for (let child of children.getElements()) {
if (child instanceof RoSGNode) {
let childCopy = cloneNode(interpreter, child, isDeepCopy);
appendChild.call(interpreter, childCopy);
}
}
}
}
}
}

return copy;
}
103 changes: 103 additions & 0 deletions test/brsTypes/components/RoSGNode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2514,6 +2514,109 @@ describe("RoSGNode", () => {
expect(result).toBe(BrsInvalid.Instance);
});
});

describe("clone", () => {
let interpreter, originalNode;

beforeEach(() => {
interpreter = new Interpreter();
originalNode = new MarkupGrid([
{ name: new BrsString("field1"), value: new BrsString("a string") },
{ name: new BrsString("field2"), value: new Int32(-19999) },
{ name: new BrsString("field3"), value: BrsBoolean.False },
{ name: new BrsString("<33"), value: new BrsString("") },
]);

child1 = new RoSGNode([
{ name: new BrsString("child1name"), value: new BrsString("1") },
]);
child2 = new RoSGNode([
{ name: new BrsString("child2name"), value: new Int32(2) },
]);
child3 = new RoSGNode([
{ name: new BrsString("child3name"), value: BrsBoolean.False },
]);
grandchild1 = new RoSGNode([
{ name: new BrsString("grandchild1name"), value: new Int32(1.2) },
]);
grandchild2 = new RoSGNode([
{
name: new BrsString("grandchild2name"),
value: new BrsString("second grandchild"),
},
]);

let appendGrandchildren = child1.getMethod("appendchildren");
appendGrandchildren.call(interpreter, new RoArray([grandchild1, grandchild2]));

let appendChildren = originalNode.getMethod("appendchildren");
appendChildren.call(interpreter, new RoArray([child1, child2, child3]));
});

it("return type is same as of subject", () => {
let cloneMethod = originalNode.getMethod("clone");
let copyNode = cloneMethod.call(interpreter, new BrsBoolean(true));

expect(copyNode).toBeInstanceOf(RoSGNode);
expect(copyNode).toBeInstanceOf(MarkupGrid);
});

it("copies field values", () => {
let cloneMethod = originalNode.getMethod("clone");
let copyNode = cloneMethod.call(interpreter, new BrsBoolean(true));

expect(copyNode.get(new BrsString("field1"))).toEqual(
new BrsString("a string")
);
expect(copyNode.get(new BrsString("field2"))).toEqual(new Int32(-19999));
expect(copyNode.get(new BrsString("field3"))).toEqual(BrsBoolean.False);
expect(copyNode.get(new BrsString("<33"))).toEqual(new BrsString(""));

let isSameNode = originalNode.getMethod("isSameNode");
expect(isSameNode.call(interpreter, copyNode)).toEqual(BrsBoolean.False);
});

it("shallow copy doesn't copy children", () => {
let cloneMethod = originalNode.getMethod("clone");
let copyNode = cloneMethod.call(interpreter, new BrsBoolean(false));

let getChildCount = copyNode.getMethod("getchildcount");
let childCount = getChildCount.call(interpreter);

expect(childCount).toEqual(new Int32(0));
});

it("deep clone copies children and grandchildren", () => {
let cloneMethod = originalNode.getMethod("clone");
let copyNode = cloneMethod.call(interpreter, new BrsBoolean(true));

let getChildCount = copyNode.getMethod("getchildcount");
let childCount = getChildCount.call(interpreter);

expect(childCount).toEqual(new Int32(3));

getChild = copyNode.getMethod("getchild");
let child1copy = getChild.call(interpreter, new Int32(0));

expect(child1copy.get(new BrsString("child1name"))).toEqual(new BrsString("1"));

let getGrandchildCount = child1copy.getMethod("getchildcount");
let grandchildCount = getGrandchildCount.call(interpreter);

expect(grandchildCount).toEqual(new Int32(2));

getChild = child1copy.getMethod("getchild");
let grandchild1copy = getChild.call(interpreter, new Int32(0));
let grandchild2copy = getChild.call(interpreter, new Int32(1));

expect(grandchild1copy.get(new BrsString("grandchild1name"))).toEqual(
new Int32(1.2)
);
expect(grandchild2copy.get(new BrsString("grandchild2name"))).toEqual(
new BrsString("second grandchild")
);
});
});
});
});

Expand Down
2 changes: 2 additions & 0 deletions test/e2e/RoSGNode.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ describe("components/roSGNode", () => {
"invalid",
"33",
"37",
"updatedId",
"newValue",
"0",
"0",
"0",
Expand Down
10 changes: 10 additions & 0 deletions test/e2e/resources/components/roSGNode/roSGNode.brs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,16 @@ sub init()
node.ClipStart = 37
?node.ClipStart

' it clones nodes
node = createObject("roSGNode", "ContentNode")
node.update({
id: "updatedId",
newField: "newValue"
}, true)
clonedNode = node.clone(true)
print clonedNode.id
print clonedNode.newField

' ifSGNodeBoundingRect
rect = node.boundingRect()
print rect.x ' => 0
Expand Down