Repository: sjbarag/brs
Pull request #450 (2020-06-23 20:24:47)

feat(stdlib): Implement Timer Component
This change implements the SceneGraph [`Timer`](https://developer.roku.com/docs/references/scenegraph/control-nodes/timer.md) component.

kirkyoder: > On second thought... This will allow you to create a `Timer`, but the `Timer` won't actually work the way that it does in RBI, right? As in, this won't allow you to trigger the timer to call your `fire` function.

That's right! I was mostly interested in just unblocking unit testing of components that rely on `Timer`.

I'm open to opinions, here, but I figured we'd just circle back once we actually needed the ability to test observer callback functions. 💭 

Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 6c491be..cf04aa9 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -42,6 +42,22 @@ jobs:
     docker:
       - image: circleci/node:14
 
+  "Publish release":
+    working_directory: ~/circleci
+    docker:
+      - image: circleci/node:10
+    steps:
+      - checkout
+      - run:
+          name: Authenticate with registry
+          command: echo "//registry.npmjs.com/:_authToken=$NPM_TOKEN" > ~/.npmrc
+      - run:
+          name: Install dependencies
+          command: yarn
+      - run:
+          name: Publish
+          command: yarn publish
+
   "Publish nightly build":
     working_directory: ~/circleci
     docker:
@@ -66,6 +82,23 @@ workflows:
       - "Node 12"
       - "Node 14"
 
+  "Publish tagged build":
+    jobs:
+      - "Node 10":
+          filters:
+            tags:
+              only: /v[0-9]+(\.[0-9]+)*(-.*)?/
+            branches:
+              ignore: /.*/
+      - "Publish release":
+          requires:
+            - "Node 10"
+          filters:
+            tags:
+              only: /v[0-9]+(\.[0-9]+)*(-.*)?/
+            branches:
+              ignore: /.*/
+
   "Nightly build":
     triggers:
       - schedule:
diff --git a/src/brsTypes/components/ComponentFactory.ts b/src/brsTypes/components/ComponentFactory.ts
index 61dd181..6ad0cca 100644
--- a/src/brsTypes/components/ComponentFactory.ts
+++ b/src/brsTypes/components/ComponentFactory.ts
@@ -9,6 +9,7 @@ import {
     ArrayGrid,
     MarkupGrid,
     ContentNode,
+    Timer,
 } from "..";
 
 export enum BrsComponentName {
@@ -22,6 +23,7 @@ export enum BrsComponentName {
     ArrayGrid = "ArrayGrid",
     MarkupGrid = "MarkupGrid",
     ContentNode = "ContentNode",
+    Timer = "Timer",
 }
 
 // TODO: update with more components as they're implemented.
@@ -52,6 +54,8 @@ export class ComponentFactory {
                 return new MarkupGrid([], name);
             case BrsComponentName.ContentNode:
                 return new ContentNode(name);
+            case BrsComponentName.Timer:
+                return new Timer([], name);
             default:
                 return;
         }
diff --git a/src/brsTypes/components/Timer.ts b/src/brsTypes/components/Timer.ts
new file mode 100644
index 0000000..25b9886
--- /dev/null
+++ b/src/brsTypes/components/Timer.ts
@@ -0,0 +1,18 @@
+import { RoSGNode, FieldModel } from "./RoSGNode";
+import { AAMember } from "./RoAssociativeArray";
+
+export class Timer extends RoSGNode {
+    readonly defaultFields: FieldModel[] = [
+        { name: "control", type: "string" },
+        { name: "repeat", type: "boolean" },
+        { name: "duration", type: "float" },
+        { name: "fire", type: "function" },
+    ];
+
+    constructor(members: AAMember[] = [], readonly name: string = "Timer") {
+        super([], name);
+
+        this.registerDefaultFields(this.defaultFields);
+        this.registerInitializedFields(members);
+    }
+}
diff --git a/src/brsTypes/index.ts b/src/brsTypes/index.ts
index e490a4d..301f239 100644
--- a/src/brsTypes/index.ts
+++ b/src/brsTypes/index.ts
@@ -52,6 +52,7 @@ export * from "./components/Poster";
 export * from "./components/ArrayGrid";
 export * from "./components/MarkupGrid";
 export * from "./components/ContentNode";
+export * from "./components/Timer";
 
 /**
  * Determines whether or not the given value is a number.


Test patch:
diff --git a/test/brsTypes/components/Timer.test.js b/test/brsTypes/components/Timer.test.js
new file mode 100644
index 0000000..245a0da
--- /dev/null
+++ b/test/brsTypes/components/Timer.test.js
@@ -0,0 +1,24 @@
+const brs = require("brs");
+const { Timer } = brs.types;
+
+describe("Timer", () => {
+    describe("stringification", () => {
+        it("initializes a new Timer component", () => {
+            let timer = new Timer();
+
+            expect(timer.toString()).toEqual(
+                `<Component: roSGNode:Timer> =
+{
+    change: <UNINITIALIZED>
+    focusable: false
+    focusedchild: invalid
+    id: 
+    control: 
+    repeat: false
+    duration: 0
+    fire: <UNINITIALIZED>
+}`
+            );
+        });
+    });
+});
diff --git a/test/e2e/BrsComponents.test.js b/test/e2e/BrsComponents.test.js
index 21584f5..6ce52a7 100644
--- a/test/e2e/BrsComponents.test.js
+++ b/test/e2e/BrsComponents.test.js
@@ -458,6 +458,25 @@ describe("end to end brightscript functions", () => {
         ]);
     });
 
+    test("components/Timer.brs", async () => {
+        await execute([resourceFile("components", "Timer.brs")], outputStreams);
+
+        expect(allArgs(outputStreams.stdout.write).filter(arg => arg !== "\n")).toEqual([
+            "timer node type:",
+            "Node",
+            "timer node subtype:",
+            "Timer",
+            "timer node control:",
+            "",
+            "timer node repeat:",
+            "false",
+            "timer node duration:",
+            "0",
+            "timer node fire:",
+            "<UNINITIALIZED>",
+        ]);
+    });
+
     test("components/Font.brs", async () => {
         await execute([resourceFile("components", "Font.brs")], outputStreams);
 
diff --git a/test/e2e/resources/components/Timer.brs b/test/e2e/resources/components/Timer.brs
new file mode 100644
index 0000000..ecb5f47
--- /dev/null
+++ b/test/e2e/resources/components/Timer.brs
@@ -0,0 +1,9 @@
+sub Main()
+    timer = createObject("roSGNode", "Timer")
+    print "timer node type:" type(timer)
+    print "timer node subtype:" timer.subtype()
+    print "timer node control:" timer.control
+    print "timer node repeat:" timer.repeat
+    print "timer node duration:" timer.duration
+    print "timer node fire:" timer.fire
+end sub