Repository: sjbarag/brs
Pull request #462 (2020-07-16 16:33:30)

fix(rsg,mock): Allow mocked components as children of created components
We previously didn't create a mocked component when building out a node's child tree, e.g.:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- components/HasALabel.xml -->

<component name="HasALabel">
    <children>
        <Label id="foo"/>
    </children>
</component>
```

```brightscript
' example.brs

_brs_.mockComponent("Label", {
    id: "foo",
    extra_field: "some extra field"
})
hal = createObject("HasALabel")
print hal.findNode("foo").hasField("extra_field")
'                         ~~~~~~~~~~~~~~~~~~~~~~~
'                         used to be false, is now true!
```

Now, any child nodes of a created node will create a mock component if one is found!  This should enable more robust testing scenarios, as well as allowing consumers to provide their own mocks for components we haven't implemented yet.


Patch:
diff --git a/src/brsTypes/components/RoSGNode.ts b/src/brsTypes/components/RoSGNode.ts
index bc9bdf1..4ed766a 100644
--- a/src/brsTypes/components/RoSGNode.ts
+++ b/src/brsTypes/components/RoSGNode.ts
@@ -21,6 +21,7 @@ import { ComponentDefinition, ComponentNode } from "../../componentprocessor";
 import { ComponentFactory, BrsComponentName } from "./ComponentFactory";
 import { Environment } from "../../interpreter/Environment";
 import { roInvalid } from "./RoInvalid";
+import type * as MockNodeModule from "../../extensions/MockNode";
 
 interface BrsCallback {
     interpreter: Interpreter;
@@ -1320,6 +1321,13 @@ export class RoSGNode extends BrsComponent implements BrsValue, BrsIterable {
 export const mGlobal = new RoSGNode([]);
 
 export function createNodeByType(interpreter: Interpreter, type: BrsString): RoSGNode | BrsInvalid {
+    // If the requested component has been mocked, construct and return the mock instead
+    let maybeMock = interpreter.environment.getMockObject(type.value.toLowerCase());
+    if (maybeMock instanceof RoAssociativeArray) {
+        let mock: typeof MockNodeModule = require("../../extensions/MockNode");
+        return new mock.MockNode(maybeMock);
+    }
+
     // If this is a built-in component, then return it.
     let component = ComponentFactory.createComponent(type.value as BrsComponentName);
     if (component) {


Test patch:
diff --git a/test/e2e/BrsMock.test.js b/test/e2e/BrsMock.test.js
index fac7bc4..b811fe1 100644
--- a/test/e2e/BrsMock.test.js
+++ b/test/e2e/BrsMock.test.js
@@ -9,6 +9,7 @@ describe("end to end brightscript functions", () => {
     beforeAll(() => {
         clock = lolex.install({ now: 1547072370937 });
         outputStreams = createMockStreams();
+        outputStreams.root = __dirname + "/resources";
     });
 
     afterEach(() => {
@@ -44,6 +45,8 @@ describe("end to end brightscript functions", () => {
             "id",
             "second mock node name is not mutated by first mock:",
             "name",
+            "created mock for child node:",
+            "true",
         ]);
     });
 
diff --git a/test/e2e/resources/mockComponents.brs b/test/e2e/resources/mockComponents.brs
index 7890997..adc738e 100644
--- a/test/e2e/resources/mockComponents.brs
+++ b/test/e2e/resources/mockComponents.brs
@@ -53,6 +53,15 @@ sub Main()
 
     print "second mock node id is not mutated by first mock:" secondMockNode.id     ' => id
     print "second mock node name is not mutated by first mock:" secondMockNode.name ' => name
+
+    ' mock a child of NormalWidget (Label) and ensure the mock is created when the parent is
+    _brs_.mockComponent("Label", {
+        id: "normalLabel",
+        extra_field: true
+    })
+    widget = createObject("roSGNode", "NormalWidget")
+    label = widget.findNode("normalLabel")
+    print "created mock for child node:" label.hasField("extra_field") ' => true
 end sub
 
 sub onNameChanged()