Gracefully handle lack of HLJS

Throw an exception when gr-app.html cannot be used to locate the
JS path root. Handle the case where HLJS is independently loaded.

Change-Id: Idd669907abff0bfc213ed8531a75380b1ad1d34a
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.js b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.js
index 75b00e8..bfd8e90 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.js
@@ -26,7 +26,6 @@
 
         // NOTE: intended singleton.
         value: {
-          loaded: false,
           loading: false,
           callbacks: [],
         },
@@ -34,9 +33,9 @@
     },
 
     get() {
-      return new Promise(resolve => {
+      return new Promise((resolve, reject) => {
         // If the lib is totally loaded, resolve immediately.
-        if (this._state.loaded) {
+        if (this._getHighlightLib()) {
           resolve(this._getHighlightLib());
           return;
         }
@@ -44,7 +43,7 @@
         // If the library is not currently being loaded, then start loading it.
         if (!this._state.loading) {
           this._state.loading = true;
-          this._loadHLJS().then(this._onLibLoaded.bind(this));
+          this._loadHLJS().then(this._onLibLoaded.bind(this)).catch(reject);
         }
 
         this._state.callbacks.push(resolve);
@@ -53,7 +52,6 @@
 
     _onLibLoaded() {
       const lib = this._getHighlightLib();
-      this._state.loaded = true;
       this._state.loading = false;
       for (const cb of this._state.callbacks) {
         cb(lib);
@@ -73,17 +71,28 @@
     _getLibRoot() {
       if (this._cachedLibRoot) { return this._cachedLibRoot; }
 
-      return this._cachedLibRoot = document.head
-          .querySelector('link[rel=import][href$="gr-app.html"]')
+      const appLink = document.head
+        .querySelector('link[rel=import][href$="gr-app.html"]');
+
+      if (!appLink) { return null; }
+
+      return this._cachedLibRoot = appLink
           .href
           .match(LIB_ROOT_PATTERN)[1];
     },
     _cachedLibRoot: null,
 
     _loadHLJS() {
-      return new Promise(resolve => {
+      return new Promise((resolve, reject) => {
         const script = document.createElement('script');
-        script.src = this._getLibRoot() + HLJS_PATH;
+        const src = this._getHLJSUrl();
+
+        if (!src) {
+          reject(new Error('Unable to load blank HLJS url.'));
+          return;
+        }
+
+        script.src = src;
         script.onload = function() {
           this._configureHighlightLib();
           resolve();
@@ -91,5 +100,11 @@
         Polymer.dom(document.head).appendChild(script);
       });
     },
+
+    _getHLJSUrl() {
+      const root = this._getLibRoot();
+      if (!root) { return null; }
+      return root + HLJS_PATH;
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html
index b46910a..6ddde46 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html
@@ -45,7 +45,6 @@
       );
 
       // Assert preconditions:
-      assert.isFalse(element._state.loaded);
       assert.isFalse(element._state.loading);
     });
 
@@ -57,7 +56,6 @@
 
       // Because the element state is a singleton, clean it up.
       element._state.loading = false;
-      element._state.loaded = false;
       element._state.callbacks = [];
     });
 
@@ -68,7 +66,6 @@
       // It should now be in the loading state.
       assert.isTrue(loadStub.called);
       assert.isTrue(element._state.loading);
-      assert.isFalse(element._state.loaded);
       assert.isFalse(firstCallHandler.called);
 
       const secondCallHandler = sinon.stub();
@@ -76,7 +73,6 @@
 
       // No change in state.
       assert.isTrue(element._state.loading);
-      assert.isFalse(element._state.loaded);
       assert.isFalse(firstCallHandler.called);
       assert.isFalse(secondCallHandler.called);
 
@@ -85,11 +81,55 @@
       flush(() => {
         // The state should be loaded and both handlers called.
         assert.isFalse(element._state.loading);
-        assert.isTrue(element._state.loaded);
         assert.isTrue(firstCallHandler.called);
         assert.isTrue(secondCallHandler.called);
         done();
       });
     });
+
+    suite('preloaded', () => {
+      setup(() => {
+        window.hljs = 'test-object';
+      });
+
+      teardown(() => {
+        delete window.hljs;
+      });
+
+      test('returns hljs', done => {
+        const firstCallHandler = sinon.stub();
+        element.get().then(firstCallHandler);
+        flush(() => {
+          assert.isTrue(firstCallHandler.called);
+          assert.isTrue(firstCallHandler.calledWith('test-object'));
+          done();
+        });
+      });
+    });
+
+    suite('_getHLJSUrl', () => {
+      suite('checking _getLibRoot', () => {
+        let libRootStub;
+        let root;
+
+        setup(() => {
+          libRootStub = sinon.stub(element, '_getLibRoot', () => root);
+        });
+
+        teardown(() => {
+          libRootStub.restore();
+        });
+
+        test('with no root', () => {
+          assert.isNull(element._getHLJSUrl());
+        });
+
+        test('with root', () => {
+          root = 'test-root.com/';
+          assert.equal(element._getHLJSUrl(),
+              'test-root.com/bower_components/highlightjs/highlight.min.js');
+        });
+      });
+    });
   });
 </script>