Developing on Staxmanade

TVJS TVHelpers DirectionalNavigation and Adapting/Hacking some WinJS Focus Management

(Comments)

So, Microsoft created what really turned out to be an amazing set of HTML/JS/CSS controls when they released the WinJS library. Not to go too much into the history, but honestly I hated it when I first had to use it. But, let me clarify. It wasn't until this last year when I learned that I didn't hate the WinJS controls by themselves, but I despised the way you declared their usage using the specialized win-* html attributes. It felt like a total hack to get an app up and running by littering semantic html with these attributes.

Then along comes a little toy project they created called react-winjs and all of a sudden the WinJS "Components" made total sense. When looking at them through the lense of WinJS through ReactJS components was the first time that I not only clicked with WinJS, but I actually fell in lov... (well I won't go that far), but was excited enough about them to pick them as the primary U.I. control suite while building out a little side-project.

Fast forward a year of development, and Microsoft essentially bailed on WinJS but at least they left it out in the open so I could hack on it and continue to depend on my own fork for the time being.

Then, they announce a NEW & SHINY library that can be used to help develop UWP and TV/Xbox One apps which is great. Except, WinJS doesn't work with this new library out-of-the-box, and since Microsoft isn't adding new features to WinJS, they likely never will build-in compatibility with the new & shiny library.

Guess that means we (I) have to figure it out on my own. And although I write this knowing that I'm probably the ONLY developer on the planet using this combination of libraries, I wanted to put out some of the hacks/code I've thrown together to get some WinJS controls to play nice with TVJS with regards to focus management.

What is focus management you say?

In the context of an Xbox app, the idea is to take your'e web-page-app and get rid of the ugly mouse-like-cursor you'd see if you didn't do this and replace it with a controller navigable approach - so up/down/left/right on the controller moves the visible "focus" around the application and the A button "presses enter" (or invokes) the control/button/etc.

What IS provided by TVJS

The TVJS library has a helper within it called DirectionalNavigation and is great in that it provides a focused and specific API to enable focus management while developing a Xbox App UWP Javascript (& C#) apps.

Just dropping the library in is enough to get much of the basics to work with most web apps.

However, the conflict between this and WinJS comes into play because WinJS also tried to implement some of their own focus management and the mix of these two just doesn't quite cut it.

Get rid of mouse cursor

Well, this isn't really a hack:

If you're looking at building a UWP JavaScript app for the Xbox, and tried to run your app on the Xbox (in dev mode), you may have noticed that your app behaves almost like it was just another web-page and doesn't default the cursor focus the way other xbox apps work. You're app just has a mouse-like cursor.

The way to deal with this is just by accessing the browser's gamepad api. Now, the Microsoft TVJS TVHelpers DirectionalNavigation library automatically does this for you, but for a better experience if you don't want to wait for the browser to download this library, you can manually access the api to hide the mouse cursor by throwing this at the top of your start page EX: index.html

    <script>
        // Hide the Xbox/Edge mouse cursor during load.
        try {
            navigator.getGamepads();
        } catch(err) {
            console && console.error('Error with navigator.getGamepads()', err);
        }
    </script>

Just by calling navigator.getGamepads(), this tells the browser/hosted web app that you are going to take control of the app's focus management and to hide the mouse cursor.

Once you've done this and your app loads up with the TVJS DirectionalNavigation library and in my case some WinJS controls, focus management mostly works (sort-of).

Completely Remove XYFocus built-in to WinJS:

This is about as ugly as they get...

The below code is bascially looking for the XYFocus handlers that WinJS is trying add to the document and we wan to not allow it to get added.

This XYFocus handler really creates havoc when we add the XYFocus handler from TVSJ DirectionalNavigation.

// HacktyHackHack
// The goal of this is to remove XYFocus management from WinJS
(function() {
  var totalRemovedHandlers = 0;
  var checkRemovedHandler = function() {
    totalRemovedHandlers++;
    if (totalRemovedHandlers > 2) {
      console.error("EEEK, removing more than 2 handlers... be sure to validate that we're removing the right ones...");
    }
  };
  var realAddEventListener = document.addEventListener;
  document.addEventListener = function(eventName, handler, c){
    if (handler.toString().indexOf('function _handleKeyEvent(e)') >= 0) {
      console.warn("Ignoring _handleKeyEvent...", eventName, handler, c);
      checkRemovedHandler();
      return;
    }
    if (handler.toString().indexOf('function _handleCaptureKeyEvent(e)') >= 0) {
      console.warn("Ignoring _handleCaptureKeyEvent...", eventName, handler, c);
      checkRemovedHandler();
      return;
    }
    return realAddEventListener(eventName, handler, c);
  };
}());

By not allowing WinJS to add it's XYFocus handlers, we can avoid many of the issues that I worked through below...

Dealing with a WinJS Pivot control

For my app, the first control I ran into trouble with was the WinJS Pivot control. This control already does some focus management all by itself, and it's own management style contradicts the way the DirectionalNavigation helper works. So we basically have to detect focus on it, turn of TVJS focus management and handle it internally (until we leave focus of the Pivot).

To work through that, I created the following helper function:


WinJS.UI.Pivot.prototype._headersKeyDown = function (e) {
    if (this.locked) {
        return;
    }
    if (e.keyCode === Keys.leftArrow ||
        e.keyCode === Keys.pageUp ||
        e.keyCode === Keys.GamepadDPadLeft ||
        e.keyCode === Keys.GamepadLeftThumbstickLeft) {
        this._rtl ? this._goNext() : this._goPrevious();
        e.preventDefault();
    } else if (e.keyCode === Keys.rightArrow ||
               e.keyCode === Keys.pageDown ||
               e.keyCode === Keys.GamepadDPadRight ||
               e.keyCode === Keys.GamepadLeftThumbstickRight) {
        this._rtl ? this._goPrevious() : this._goNext();
        e.preventDefault();
    }
};

function handlePivotNavigation(pivotElement) {
  console.log("handlePivotNavigation", pivotElement);
  if (!pivotElement) {
    throw new Error("handlePivotNavigation cannot use pivotElement as it wasn't passed in");
  }

  var pivotHeader = pivotElement.querySelector('.win-pivot-headers')

  if (!pivotHeader) {
    let msg = "handlePivotNavigation cannot find .win-pivot-headers in";
    console.error(msg, pivotElement);
    throw new Error(msg);
  }


  pivotHeader.addEventListener('focus', function() {
    console.log("pivotHeader focus");
    DirectionalNavigation.enabled = false;
  });
  pivotHeader.addEventListener('keyup', function(eventInfo) {
    console.log('pivot keyup ', eventInfo.keyCode, eventInfo.key);

    switch(eventInfo.keyCode) {
      case 204: // gampead down
      case 40: // keyboard down
        DirectionalNavigation.enabled = true;
        var target = DirectionalNavigation.findNextFocusElement('down');
        if (target) {
          target.focus();
          eventInfo.preventDefault();
        }
        break;
      case 203: // gamepad up
        // since the Pivot is at the top of the page - we won't release
        // control, or try to navigate up??? (maybe consider flowing up from the bottom of the page?)
        break;
      // case 205: // gamepad left arrow
      // case 211: // gamepad 211 GamepadLeftThumbstickUp
      // case 200: // gamepad left bumper
      //   pivotElement.winControl._goPrevious();
      //   eventInfo.preventDefault();
      //   break;
      // case 206: // gamepad right arrow
      // case 213: // gamepad 213 GamepadLeftThumbstickRight
      // case 199: // gamepad 199 GamepadRightShoulder
      //   pivotElement.winControl._goNext();
      //   eventInfo.preventDefault();
      //   break;
    }
  });
}

And use it by doing the following in my React page:

    componentDidMount() {
        var pivot = ReactDOM.findDOMNode(this.refs.pivot);
        handlePivotNavigation(pivot);
    }

Or if you're not using React you can likely just go:

    var pivot = document.getElementById('my-pivot-id');
    handlePivotNavigation(pivot);

It's not pretty, but has been working for me so far.

Now when I navigate around using an Xbox controller I can properly navigate around the WinJS Pivot.

Next up are ItemContainers.

UPDATE:

With the added (remove XYFocus above - I removed the below hack)

This one is a total hack, and I look forward to a better solution, but for now it's been working.

The issue I was seeing was with WinJS ItemContainers and the TVJS library applying a separate forced "click" on the element when the control itself has already "clicked/invoked" the element.

The real fix would likely to figure out how to get the ItemContainer to event.preventDefault() and/or event.stopPropagation() and avoid the bubbling up to the document keyup event handler that DirectionalNavigation has under it's control, but WinJS ItemControl management is just so complicated that this hack was easier to figure out at the time I threw it together.

So what does this do?

It's basically hijacking the DirectionalNavigation._handleKeyUpEvent function, and re-writing it with one that ignores the keyup event if the currently focused element is an ItemContainer.

// Hack to avoid Item containers getting double click
var originalDNKeyUp = TVJS.DirectionalNavigation._handleKeyUpEvent
TVJS.DirectionalNavigation._handleKeyUpEvent = function (e) {
console.log("Check for itemContaner", event.target.className)
  if (e.target.className.split(" ").indexOf("win-itemcontainer") >= 0) {
    console.log("MonkeyHack on DirectionalNavigation - SKIPPING CLICK");
    return;
  }
  return originalDNKeyUp.apply(null, arguments);
}
document.removeEventListener("keyup", originalDNKeyUp);
document.addEventListener("keyup", TVJS.DirectionalNavigation._handleKeyUpEvent);

It's not pretty, but meh, is working so far.

ItemContainers within a ContentDialog

UPDATE

I gave up on ContentDialog, and just started using react-modal

That's just a big mess from what I could figure out. I was able to get it working by using the ContentDialog but manually creat my own buttons as the ItemContainer in combination with the dialog kept swallowing events that didn't allow focus navigation to be sucessful. The internals of what was holding me back didn't appear to be monkey-patch-able from what I could tell... ugh...

Next up is a ListView hack,

This one is a hack proposed by Todd over on the GitHub issues.

I've essentially taken the original implementation of WinJS.UI.ListView.prototype._onFocusIn, and if you look for the line starting with /* JJ */ below you can see the change there.

Don't know what this actually could mean from other scenarios, but for now it's allowing the ListView to focus properly on my initial xbox testing.

var _Constants = WinJS.UI;
var _UI = WinJS.UI;

WinJS.UI.ListView.prototype._onFocusIn = function ListView_onFocusIn(event) {
                    this._hasKeyboardFocus = true;
                    var that = this;
                    function moveFocusToItem(keyboardFocused) {
                        that._changeFocus(that._selection._getFocused(), true, false, false, keyboardFocused);
                    }
                    // The keyboardEventsHelper object can get focus through three ways: We give it focus explicitly, in which case _shouldHaveFocus will be true,
                    // or the item that should be focused isn't in the viewport, so keyboard focus could only go to our helper. The third way happens when
                    // focus was already on the keyboard helper and someone alt tabbed away from and eventually back to the app. In the second case, we want to navigate
                    // back to the focused item via changeFocus(). In the third case, we don't want to move focus to a real item. We differentiate between cases two and three
                    // by checking if the flag _keyboardFocusInbound is true. It'll be set to true when the tab manager notifies us about the user pressing tab
                    // to move focus into the listview.
                    if (event.target === this._keyboardEventsHelper) {
                        if (!this._keyboardEventsHelper._shouldHaveFocus && this._keyboardFocusInbound) {
                            moveFocusToItem(true);
                        } else {
                            this._keyboardEventsHelper._shouldHaveFocus = false;
                        }
                    } else if (event.target === this._element) {
                        // If someone explicitly calls .focus() on the listview element, we need to route focus to the item that should be focused
                        moveFocusToItem();
                    } else {
                        if (this._mode.inboundFocusHandled) {
                            this._mode.inboundFocusHandled = false;
                            return;
                        }

                        // In the event that .focus() is explicitly called on an element, we need to figure out what item got focus and set our state appropriately.
                        var items = this._view.items,
                            entity = {},
                            element = this._getHeaderOrFooterFromElement(event.target),
                            winItem = null;
                        if (element) {
                            entity.index = 0;
                            entity.type = (element === this._header ? _UI.ObjectType.header : _UI.ObjectType.footer);
                            this._lastFocusedElementInGroupTrack = entity;
                        } else {
                            element = this._groups.headerFrom(event.target);
                            if (element) {
                                entity.type = _UI.ObjectType.groupHeader;
                                entity.index = this._groups.index(element);
                                this._lastFocusedElementInGroupTrack = entity;
                            } else {
                                entity.index = items.index(event.target);
                                entity.type = _UI.ObjectType.item;
                                element = items.itemBoxAt(entity.index);
                                winItem = items.itemAt(entity.index);
                            }
                        }

                        // In the old layouts, index will be -1 if a group header got focus
                        if (entity.index !== _Constants._INVALID_INDEX) {
/* JJ */                            /*if (this._keyboardFocusInbound || this._selection._keyboardFocused())*/ {
                                if ((entity.type === _UI.ObjectType.groupHeader && event.target === element) ||
                                        (entity.type === _UI.ObjectType.item && event.target.parentNode === element)) {
                                    // For items we check the parentNode because the srcElement is win-item and element is win-itembox,
                                    // for header, they should both be the win-groupheader
                                    this._drawFocusRectangle(element);
                                }
                            }
                            if (this._tabManager.childFocus !== element && this._tabManager.childFocus !== winItem) {
                                this._selection._setFocused(entity, this._keyboardFocusInbound || this._selection._keyboardFocused());
                                this._keyboardFocusInbound = false;
                                if (entity.type === _UI.ObjectType.item) {
                                    element = items.itemAt(entity.index);
                                }
                                this._tabManager.childFocus = element;

                                if (that._updater) {
                                    var elementInfo = that._updater.elements[uniqueID(element)],
                                        focusIndex = entity.index;
                                    if (elementInfo && elementInfo.newIndex) {
                                        focusIndex = elementInfo.newIndex;
                                    }

                                    // Note to not set old and new focus to the same object
                                    that._updater.oldFocus = { type: entity.type, index: focusIndex };
                                    that._updater.newFocus = { type: entity.type, index: focusIndex };
                                }
                            }
                        }
                    }
                }

One big improvement could be to consider setting up a unit-test that could take the original "string" value of the entire function code, and comparing it to the current version of WinJS library you're using and fail if they're even one character different. This would allow you to detect if say a fix were applied, or you need to update our local hacked version with some remote changes... It's not pretty, but one way to avoid over-writing possible working WinJS code with our potentially not-so-future-proof hacked version.

Next one is the WinJS [ToggleSwitch].

This control just seemed to have all behavior wrong for me. So I hacked the keyDownHandler and simplified it's implementation which seems to have really made it more usable (for me).

var _ElementUtilities = WinJS.Utilities

WinJS.UI.ToggleSwitch.prototype._keyDownHandler =  function ToggleSwitch_keyDown(e) {
    if (this.disabled) {
        return;
    }

    // Toggle checked on spacebar
    if (e.keyCode === _ElementUtilities.Key.space ||
        e.keyCode === _ElementUtilities.Key.GamepadA ||
        e.keyCode === _ElementUtilities.Key.enter) {
        e.preventDefault();
        this.checked = !this.checked;
    }

}

The original had up/down/left/right configured to toggle the switch on/off which meant focus in/out was nearly impossible, it also only listened to space as a toggle option. So by removing the up/down/left/right we can navigate in/around the control and we wanted to listen to space, GamepadA, and enter to toggle the control on/off.

What else?

The WinJS control set is quite large, and I certainly haven't worked with each control in this manor, however, it's a step forward eh and if you managed to come across this random post on the interweb I hope it was useful?

Integrate WinJS.Navigation with the Browser's History

(Comments)

I've been playing with WinJS a bit lately, specifically the React-WinJS and wanted the native WinJS Navigation to play a little nicer with a web browser. The original/default environment for WinJS app is within a WinRT/Metro application where there is no "url/address" bar to be seen.

My uneducated guess is that the WinJS team decided not to worry about how WinJS.Navigation would integrate with a normal browser's history as there doesn't appear to be native integration or documentation about how to do it so far.

I asked the team if they had plans to work on any integration options, but only asked that last night so don't expect to hear back from over the weekend.

UPDATE: I got tired of updating this blog post with my bug fixes/iterations of the idea - so I've moved it over to GitHub: github.com/staxmanade/WinJSBrowserHistory.

So I spent a moment and prototyped one possible solution which works for this simple test using the browser's history api since I'm not looking to support browsers older than IE 10.

Ideally we could leverage WinJS controls without worrying about how to "integrate" the WinJS.Navigation with anything, but sadly some of the WinJS controls take a dependency on WinJS.Navigation (like the BackButton) so finding a way to play nice with this can be challenging.

If you want to get this prototype running yourself, you can:

  1. save both files below to a folder
  2. start up a simple web server. (I like to use nws)

This prototype is 2 files:

  • index.html <-- basic JSPM bootstrapping and configuration
  • app.jsx <-- my whole navigation app in here...

index.html

Couple mentions on this bootstrapping code:

  1. I set the background style to black (since in app.jsx I'm using the WinJS dark css) - this avoids a flash from white to black when the page loads
  2. This is using SystemJS which makes it really easy to prototype and bootstrap dependencies like WinJS and React. Please don't deploy something like this to production - follow proper JSPM production workflow procedures...
  3. The map section in the System.config defines a pointer to a fork of react-winjs I have that supports React 0.14 (so if you find this in the future and need it, try to use the native react-winjs if they've merged in my pull request instead.)
<!DOCTYPE html>
<html>
  <head>
    <style media="screen"> body{ background-color: black; } </style>
  	<script src="https://jspm.io/[email protected]"></script>
    <script type="text/javascript">
      System.config({
        transpiler: 'babel',
        packages: {
          './': {
            defaultExtension: false
          }
        },
        map: {
          'react': 'npm:[email protected]',
          'react-winjs': 'github:staxmanade/[email protected]',
        }
      });

      System.import('./app.jsx');

    </script>
  </head>
  <body>
    <div id="main"></div>
  </body>
</html>

app.jsx

import React from 'react';
import ReactDOM from 'react-dom';
import WinJS from 'npm:winjs';
import 'npm:winjs/css/ui-dark.css!';
import { BackButton } from 'react-winjs';


class WinJSBrowserHistory {
    isNavigationBeingHandled;
    isWinJSNavigationBackBeingHandled;
    isNavigationTriggeredByPopStateEvent;

    constructor(onApplyNavigaitonChange) {

        if(typeof onApplyNavigaitonChange !== "function") {
          throw new Error("Expecting first argumet to be a function that can take 2 parametes (location, state) => {}");
        }

        this.onApplyNavigaitonChange = onApplyNavigaitonChange;

        WinJS.Navigation.addEventListener("beforenavigate", this.handleBeforeNavigate.bind(this));
        WinJS.Navigation.addEventListener("navigating", this.handleNavigating.bind(this));
        WinJS.Navigation.addEventListener("navigated", this.handleNavigated.bind(this));

        window.addEventListener('popstate', (eventObject) => {
            console.log('popstate', this.isNavigationBeingHandled, eventObject);

            if(!this.isNavigationBeingHandled && !this.isWinJSNavigationBackBeingHandled) {
              this.handlePopState(eventObject);
            }
            this.isWinJSNavigationBackBeingHandled = false;
        })
    }

    cleanup() {
      WinJS.Navigation.removeEventListener("navigated", this.handleNavigated);
    }


    handlePopState(eventObject) {
      console.log("handlePopState", eventObject, location.hash);

      this.isNavigationTriggeredByPopStateEvent = true;

      WinJS.Navigation.navigate(location.hash, location.state);
    }

    handleBeforeNavigate(eventObject) {
        this.isNavigationBeingHandled = true;
        console.log("handleBeforeNavigate:", eventObject);
    }

    handleNavigating(eventObject) {
        console.log("handleNavigating:", eventObject);
        console.log("handleNavigating delta:", eventObject.detail.delta);

        var location = eventObject.detail.location;
        var state = eventObject.detail.state;
        var delta = eventObject.detail.delta;

        this.onApplyNavigaitonChange(location, state);

        if(delta < 0) {
            this.isWinJSNavigationBackBeingHandled = true;
            window.history.go(delta);
        } else {
            //router.setRoute(location);
            window.history.pushState(state, "", "#" + location);
        }
    }

    handleNavigated(eventObject) {
        this.isNavigationBeingHandled = false;
        this.isNavigationTriggeredByPopStateEvent = false;

        console.log("handleNavigated", eventObject);
    }

}


export default class App extends React.Component {

    constructor(props) {
        super(props);

        this.winJSBrowserHistory = new WinJSBrowserHistory(this.onApplyNavigaitonChange.bind(this));

        this.state = {
            nav: {
                state: WinJS.Navigation.state,
                location: WinJS.Navigation.location
            }
        }
    }

    componentWillMount () {
        console.log("componentWillMount");
        WinJS.Navigation.navigate(this.state.nav.location, this.state.nav.state);
    }

    componentWillUnmount () {
        console.log("componentWillUnmount");
    }

    onApplyNavigaitonChange(location, state) {
        this.setState({
            nav: {
                location: location,
                state: state
            }
        });
    }

    gotoPage1Nested() {
        WinJS.Navigation.navigate("/page1/nested");
    }

    gotoPage1() {
        WinJS.Navigation.navigate("/page1");
    }

    render() {

        console.log("render() location:", this.state.nav.location);

        var componentWithBackButton = component => {
            return (
                <div>
                    <BackButton />
                    <div>
                        {component}
                    </div>
                </div>
            );
        };

        var page;

        switch(this.state.nav.location) {
            case "/page1":
                page = componentWithBackButton(<div>page 1<br/> <button type="button" onClick={this.gotoPage1Nested.bind(this)}>Goto Page 1 Nested</button></div>);
            break;

            case "/page1/nested":
                page = componentWithBackButton(<div>page 1 nested</div>);
            break;

            case "/page2":
                page = componentWithBackButton(<div>page 1</div>);
            break;

            default:
                page = (
                    <div>
                      <button type="button" onClick={this.gotoPage1.bind(this)}>Goto Page 1</button>
                    </div>
                );
        }

        return page;
    }
}

ReactDOM.render(<App />, document.getElementById('main'));

Next I'd like to see if I could leverage something like flatiron/director for routing and get it to play nice with WinJS.Navigation and if I do, I'll post it as well...

Hope this helps.

In App Unit test example with JSPM, React, WinJS and Mocha

(Comments)

A while back I wrote about In App Unit Tests and have been having too much fun creating little starter plunks on Plunker. So...

As a simple demonstration on in-app unit tests I've thrown together a little plunk that shows in-app tests within the a WinJS Pivot. And add to the layers of abstraction I'm using react-winjs which I'm LOVING over normal WinJS development.

Link to: JSPM/React/WinJS/Mocha Plunk

If you like this starter, I have it and a few more linked here: for re-use

I'd like to highlight this particular starter at bit more in this post, not only because there are a few more concepts to this basic plunk, but also because I'm fairly happy with the MochaJS React component that I've now copied around and re-used a few times in some small projects where use In App Unit Tests.

Plunker file overview

  • index.html - The index file is a very basic JSPM bootstrap page that loads the app.jsx react component.
  • app.jsx - Defines a WinJS Pivot control where we render the in-app MochaTests.jsx React component. This also defines the test file(s) and using MochaJS's global detection we can tell the react MochaTests component what test files to load and run as well as what globals are allowed to exist.
  • config.js - This is JSPM's config that defines what version of React we're using and to use babel for transpilation.
  • tests.js - is our Mocha set of unit tests. We can have multiple test files if we want, just have to define what files to load in app.jsx.

Lastly the MochaTests.jsx which I'll include the full component below:

easier to copy-paste inherit for myself in the future

import mocha from 'mocha';
import React from 'react';

export default class MochaTests extends React.Component {

    static get propTypes() {
        return {
            testScripts: React.PropTypes.array.isRequired,
            allowedGlobals: React.PropTypes.array
        };
    }

    constructor(props) {
        super(props);
    }

    componentDidMount() {

        var testScripts = this.props.testScripts;
        var runTests = this.runTests.bind(this);

        // for some reason importing mocha with JSPM and ES6 doesn't
        // place the mocha globals on the window object. The below
        // handles that for us - as well as setting up the rest of the
        // test scripts for the first run
        mocha.suite.on('pre-require', context => {
            var exports = window;

            exports.afterEach = context.afterEach || context.teardown;
            exports.after = context.after || context.suiteTeardown;
            exports.beforeEach = context.beforeEach || context.setup;
            exports.before = context.before || context.suiteSetup;
            exports.describe = context.describe || context.suite;
            exports.it = context.it || context.test;
            exports.setup = context.setup || context.beforeEach;
            exports.suiteSetup = context.suiteSetup || context.before;
            exports.suiteTeardown = context.suiteTeardown || context.after;
            exports.suite = context.suite || context.describe;
            exports.teardown = context.teardown || context.afterEach;
            exports.test = context.test || context.it;
            exports.run = context.run;

            // now use SystemJS to load all test files
            Promise
            .all(testScripts.map(function(testScript) {
                console.log("Adding Mocha Test File: ", testScript);
                return System.import(testScript);
            })).then(runTests, function(err) {
                console.error("Error loading test modules");
                console.error(err);
            });

        });
        mocha.setup('bdd');
    }

    runTests() {
        var allowedGlobals = this.props.allowedGlobals || [];

        this.refs.mocha.getDOMNode().innerHTML = "";

        mocha.checkLeaks();
        mocha.globals(allowedGlobals);
        mocha.run();

    }

    render() {
        return (
            <div>
                <style>{"\
                  #mocha-stats em { \
                      color: inherit; \
                  } \
                  #mocha-stats { \
                    position: inherit; \
                  }\
                  #mocha .test.fail pre { \
                      color: red; \
                  } \
                "}</style>

                <button onClick={this.runTests.bind(this)}>Rerun Tests</button>

                <div id="mocha" ref="mocha"></div>
            </div>
        );
    }
}

Usage example of this React MochaTests component.

// Define what test files get loaded by the MochaTests component
var testScripts = [
  './tests.js'
];


var allowedTestGlobals = [
  // Declare what globals are allowed to be created during any test runs.
];


// Usage of MochaTests in a react render() method.
<MochaTests testScripts={testScripts} allowedGlobals={allowedTestGlobals} />

I'm not expecting to see a large up-tick in WinJS apps out there with in-app unit tests that run in the browser, however hopefully the MochaTests.jsx React Component is of value to you and can be utilized outside of WinJS within almost any React app.

Please drop a line if you end up using it or if it can be adapted. If there's value in the component, maybe

Known Issue

If the number of tests starts to go beyond the height of the pivot in this sample, it has an issue where the WinJS Pivot cuts off at the bottom not allowing you to scroll and see the rest of the test output. I haven't dug into it yet because I've been clicking the failures: X link and it filters the U.I. to just the erroring tests.

If you happen to come up with a good solution, drop me a note - I'd love it. Thanks in advance!

Happy Testing!

Running in-app mocha tests within WinJS

(Comments)

I described a while back a scenario about running in-app unit tests. So if you'd like some background on the subject have a look there before reading here.

This post is going to give some specifics about how to run MochaJS tests within a Microsoft Windows JavaScript application (WinJS).

Prerequisites

These steps assume you already have a WinJS application, possibly using the universal template or other. In the end it doesn't matter. As long as you have a project that probably has a default.html file and the ability to add js & css files to.

Get Mocha

You can acquire the mocha library however you want, Bower, Npm, or download it manually from the site (mochajs.org).

Reference Mocha Within Project & App

However you get the mocha source, you need to both add references to the mocha js and css files into your project file either in a *.jsproj file or if using a universal shared app, in the shared project.

Then you need to include a reference to the code in your default.html file.

In my example below you can see I used bower to download the MochaJS library.

The mocha.setup('bdd') tells mocha to use the BDD style of tests which defines the describe(...), it(...), etc functions.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>MyApp.Windows</title>

    <!-- WinJS references -->
    <link href="/js/bower_components/winjs/css/ui-light.css" rel="stylesheet" />
    <script src="/js/bower_components/winjs/js/base.js"></script>
    <script src="/js/bower_components/winjs/js/ui.js"></script>

    <!-- TESTS-->
    <link href="/js/bower_components/mocha/mocha.css" rel="stylesheet" />
    <script src="/js/bower_components/mocha/mocha.js"></script>
    <script>
        mocha.setup('bdd');
    </script>

    <!-- My Project js/css files below... -->

...Rest of default.html file excluded

Create a WinJS Control for hosting Mocha reporting.

Below is a sample WinJS control that can be used to host the mocha html report.

<!DOCTYPE html>
<html>
<head>
    <style>
        #mocha {
            height: 800px;
            width: 600px;
            overflow: scroll;
        }
    </style>
</head>
<body>
    <div class="fragment section1page">
        <section aria-label="Main content" role="main">

            <!-- define a button that we can use to manually run or re-run tests -->
            <button id="mochaTestsRun">Run Tests</button>

            <!-- define a checkbox that allow us to toggle auto-run
                 of the tests when we start up the app -->
            Auto start <input type="checkbox" id="mochaTestsRunOnStart" />


            <!-- this is a blank div we use to inject U.I. related tests -->
            <div id="testElementContainer"></div>

            <!-- mocha throws the html output in the below div -->
            <div id="mocha"></div>
        </section>
    </div>
</body>
</html>
(function () {
    "use strict";

    var runMochaTests = function() {
        // clear any current test results since mocha just appends.
        element.querySelector('#mocha').innerHTML = "";

        // If you want your tests to verify nothing is leaked globally...
        mocha.checkLeaks();

        // specify any globals that you are OK with your tests leaking
        mocha.globals([
            'someGlobalIAmExpecting'
        ]);

        // start the test run
        mocha.run();
    }

    var ControlConstructor = WinJS.UI.Pages.define("/js/tests/tests.html", {
        // This function is called after the page control contents
        // have been loaded, controls have been activated, and
        // the resulting elements have been parented to the DOM.
        ready: function (element, options) {
            options = options || {};

            // get our possibly already cached value of whether to run the tests on startup.
            var runOnStart = (JSON.parse(localStorage.getItem("mochaTestsRunOnStart") || "true"));

            // checkbox to manage auto-run state
            var runOnStartCheckbox = element.querySelector('#mochaTestsRunOnStart');
            runOnStartCheckbox.addEventListener('change', function () {
                localStorage.setItem("mochaTestsRunOnStart", runOnStartCheckbox.checked);
            });

            // button to manually trigger a test run
            var mochaTestsRunButton = element.querySelector('#mochaTestsRun');
            mochaTestsRunButton.addEventListener('click', function(){
                runMochaTests();
            })
            runOnStartCheckbox.checked = runOnStart;

            if (runOnStart && !window.hasRunMochaTestsAtLeastOnce) {
                // this value is used to avoid extra test runs as we navigation around the app.
                // EX: if the test control is on a home pivot - we nav away and come back home
                // we probably don't want to auto-run the tests again. (use the menu button
                // instead if you want another test run)
                window.hasRunMochaTestsAtLeastOnce = true;
                runMochaTests();
            }

        },
    });

    // The following lines expose this control constructor as a global.
    // This lets you use the control as a declarative control inside the
    // data-win-control attribute.

    WinJS.Namespace.define("MyApp.Testing.Controls", {
        TestsControl: ControlConstructor
    });
})();

Handle global exceptions

If you write any async code (difficult not to these days) an exception or assertion failure will not be trapped by the internal try/catch mechanism's of Mocha in a Windows WinJS environment.

We have to give Mocha a hint on how to hook into global exceptions.

Mocha tries to attach to the browser's global window.onerror method, and since a WinJS app doesn't use this same handler, we have to forward the exceptions and to mocha's attached window.onerror handler.

In your default.js or wherever you configure the app you can attach to the WinJS.Application.onerror and after some exception massaging we can hand the exceptions to mocha so when a test fails it can be reported correctly.

    WinJS.Application.onerror = function (ex) {

        var errorMessage, errorLine, errorUrl;
        if (ex.detail.errorMessage) {
            errorMessage = ex.detail.errorMessage;
            errorLine = ex.detail.errorLine;
            errorUrl = ex.detail.errorUrl;
        } else if (ex &&
            ex.detail &&
            ex.detail.exception &&
            ex.detail.exception.stack) {
            errorMessage = ex.detail.exception.stack;
            errorLine = null;
            errorUrl = null;
        }

        // if window.onerror exists, assume mochajs is here and call it's error handler
        // This may be a poor assumption because 3rd party libraries could also attach
        // their handlers, but it's working for me so far...
        if (window.onerror && errorMessage) {
            window.onerror(errorMessage, errorLine, errorUrl);

            // return true signalling that the error's been
            // handled (keeping the whole app from crashing)
            return true;
        }

        // if we get here, assuming mochajs isn't running
        // let's log out the errors...
        try {
            var exMessage = JSON.stringify(ex, null, '  ');
            console.error(exMessage);
        } catch (e) {
            // can't JSON serialize exception object here (probably circular reference)
            // log what we can...
            console.error(ex);
            console.error(e);
        }

        // I like to be stopped while debugging to possibly
        // poke around and do further inspection
        debugger;
    }

Reference the test control.

While developing out the application, I like to throw it front and center in the first hub of my apps home page hub control.

Here's an example of how to reference the above control:

<head>
    <meta charset="utf-8" />
    <title>hubPage</title>

    <link href="/pages/hub/hub.css" rel="stylesheet" />
    <script src="/js/tests/tests.js"></script>
</head>

...page details trimmed for brevity...

            <div class="hub" data-win-control="WinJS.UI.Hub">

                <div class="sectionTests"
                     data-win-control="WinJS.UI.HubSection"
                     data-win-options="{ isHeaderStatic: true }"
                     data-win-res="{ winControl: {'header': 'TestSection'} }">
                    <div id="sectionTestscontenthost" data-win-control="MyApp.Testing.TestsControl"></div>
                </div>
            </div>

...rest of page details trimmed for brevity...

If you manage to get everything wired up correctly above, when you run the app (F5) your mocha tests should be all set and run automatically. Oh wait, let's not forget to add a mocha test :)...

describe('Mocha test spike', function(){

    it("should report a passing test", function(){
      // doing nothing should be a passing test
    });

    it("should fail on a synchronous exception", function(){
        throw new Error("Some test error to make sure mocha reports a test failure")
    });

    it("should fail on an asynchronous exception", function(done){
        setTimeout(function(){
            throw new Error("Some test error to make sure mocha reports an async test failure")
            done();
        }, 100);
        throw new Error("Some test error to make sure mocha reports a test failure")
    });

});

Save the above as your first test file, include it so it runs on startup and verify your tests run and report correctly within the test control.

Happy testing!

Introducing ToTypeScriptD - Automatic TypeScript Definition files for C++/CX or .Net assemblies

If you’re unfamiliar with TypeScript, I’d highly recommend checking it out. It’s my new preferred way to write JavaScript based applications.

If you write an application in TypeScript and need to interact with an external set of either JavaScript or C++/CX dependencies then you will quickly find that you are going to be mucking with some TypeScript Definition files and the more you take a dependency on the WinRT runtime the more type definitions you probably need to manually create.

It’s true that the TypeScript team has created a set of TypeScript Definition files for WinRT and WinJS, but I couldn’t get any comment as to how they were generated. And the more I looked into what was provided, the more I realized that there were inconsistencies and inaccuracies that were not good. Which led me to believe that they weren’t working on a super-secret tool to automatically generate these for us.

What is ToTypeScriptD ?

ToTypeScriptD is a quick little attempt at automatically taking the type system provided to us by a C++/CX winmd or .Net assembly file and projecting that into a set of TypeScript Definition files. This tool can basically take anything that is Ecma 355 Common Language Infrastructure (CLI) and spit out a .d.ts file for you automatically.

Why would I need this?

In the project readme I describe two use-cases that I know of (so far).

  1. If you build a 'Modern' (come on, we still call it Metro) Windows 8 app with WinJS and want to leverage TypeScript, wouldn't it be nice to get a set of TypeScript Definition files that reflect the native API's you're calling in the platform without manually creating the definition files?
  2. Say your building an MVC/WebAPI server application. It would be useful if your C.I./Build system could generate a set of TypeScript interfaces that were based on the server objects used to render your JSON API. This can be useful for client side JavaScript/TypeScript libraries that need to consume these objects and also provide a simple way to document the format of the input and output API response objects.

How do I get the project and start using it?

Before you install the tool and dive in head first, I would like warn you that I have only been working on this for about 2 weeks (in my off-hours) and there is going to be lots of room for improvement. But it’s currently generating a solid set of .d.ts files for .winmd files.

Head over to the project home page and check out the Readme for some quick-start information.

I’d would love to hear feedback. If anyone is interested in helping out, you can submit new issues or review the open issues list and start submitting pull requests.

Happy TypeScripting!