/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Verbosio Test Engine Service.
 *
 * The Initial Developer of the Original Code is
 * Alexander J. Vincent <ajvincent@gmail.com>.
 * Portions created by the Initial Developer are Copyright (C) 2007
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// Get JS modules.
Components.utils.import("resource://app/modules/XPCOMUtils.jsm");
Components.utils.import("resource://app/modules/ArrayConverter.jsm");
/* We want assert() only, and we want it to work every time.  So we're not
   going to import the ecma-debug.jsm module.
*/
//Components.utils.import("resource://app/modules/ecma-debug.jsm");

/*****************************************************************************
 * Common module code.                                                       *
 *****************************************************************************/

// Utility function.
function ERROR_FAILURE() {
  throw Components.results.NS_ERROR_FAILURE;
}

// Utility function.
function NOT_IMPLEMENTED() {
  throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}

// Utility function.
function ERROR_ABORT() {
  throw Components.results.NS_ERROR_ABORT;
}

// Utility function
function NOT_INITIALIZED() {
  throw Components.results.NS_ERROR_NOT_INITIALIZED;
}

// Utility function
function ALREADY_INITIALIZED() {
  throw Components.results.NS_ERROR_ALREADY_INITIALIZED;
}

const C_i = Components.interfaces;

const xeITestEngine               = C_i.xeITestEngine;
const xeIVerbosioUtilsService     = C_i.xeIVerbosioUtilsService;
const xeIDocumentPack             = C_i.xeIDocumentPack;
const xeIVirtualFileService       = C_i.xeIVirtualFileService;
const xeIEntity                   = C_i.xeIEntity;
const xeIDTDWrapper               = C_i.xeIDTDWrapper;
const xeITransactionControl       = C_i.xeITransactionControl;
const xeITransaction              = C_i.xeITransaction;
const nsITransaction              = C_i.nsITransaction;
const nsIDOMAttr                  = C_i.nsIDOMAttr;
const nsIDOMDocumentFragment      = C_i.nsIDOMDocumentFragment;
const nsIDOM3Node                 = C_i.nsIDOM3Node;
const nsIClipboard                = C_i.nsIClipboard;
const nsITransferable             = C_i.nsITransferable;
const nsIArray                    = C_i.nsIArray;
const nsISupportsString           = C_i.nsISupportsString;
const nsIStringBundleService      = C_i.nsIStringBundleService;
const nsIStringBundle             = C_i.nsIStringBundle;
const nsIIOService                = C_i.nsIIOService;
const nsIScriptableInputStream    = C_i.nsIScriptableInputStream;
const mozIJSSubScriptLoader       = C_i.mozIJSSubScriptLoader;
const nsIException                = C_i.nsIException;
const nsIScriptError              = C_i.nsIScriptError;
const nsIProgrammingLanguage      = C_i.nsIProgrammingLanguage;
const nsISecurityCheckedComponent = C_i.nsISecurityCheckedComponent;
const nsIClassInfo                = C_i.nsIClassInfo;
const nsISupports                 = C_i.nsISupports;

Error.prototype.shiftStack = function(aShiftLines) {
  var stackArray  = this.stack.split("\n").splice(aShiftLines);
  var stackTop    = stackArray[0];
  var lastColon   = stackTop.lastIndexOf(":");
  this.fileName   = stackTop.substring(stackTop.indexOf("@") + 1, lastColon);
  this.lineNumber = stackTop.substr(lastColon + 1);
  this.stack      = stackArray.join("\n");
}

// Turn on assertions.
function assert(mustBeTrue, errMsg) {
  if (mustBeTrue) {
    return mustBeTrue;
  }

  var e = new Error(errMsg);
  // This takes us to the actual stack error.
  e.shiftStack(2);

  e.name = "AssertionError";
  // Reset the error message.
  e.message = errMsg;
  dump(e.stack + "\n\n");
  throw e;
}

var subscriptsLoaded = false;
var vbUtils = null;

function loadSubscripts() {
  if (subscriptsLoaded) {
    return;
  }

  var jsLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
                         .getService(mozIJSSubScriptLoader);

  jsLoader.loadSubScript("chrome://jslib/content/jslib.js");
  jsLoader.loadSubScript("chrome://jslib/content/io/io.js");

  vbUtils = Components.classes["@verbosio.mozdev.org/verbosio-utils;1"]
                      .getService(xeIVerbosioUtilsService);

  assert(vbUtils, "No Verbosio utilities service?");
  subscriptsLoaded = true;
}

const packClass = "@verbosio.mozdev.org/document-pack;1?type=xul-application";
const stringBundleClass = "@mozilla.org/intl/stringbundle;1"
const bundleSvc = Components.classes[stringBundleClass]
                            .getService(nsIStringBundleService);
var virtualFileSvc = null;

var gTestAppDir = null;
function getTestAppDir() {
  if (!gTestAppDir) {
    var appURI = "resource://app/";
    var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                              .getService(nsIIOService);
    var uri = ioService.newURI(appURI, null, null)
                       .QueryInterface(C_i.nsIFileURL);
    var localFile = uri.file.QueryInterface(C_i.nsILocalFile);
    assert(localFile.leafName == "default", "Not the default Verbosio app?");
    assert(localFile.isDirectory(), "Not a directory?");
    localFile = localFile.parent;
    localFile.append("testing");
    assert(localFile.exists(), "testing directory doesn't exist?");
    assert(localFile.isDirectory(), "Not a directory?");
    gTestAppDir = localFile.path;
  }
  return gTestAppDir;
}

/*****************************************************************************
 * Tests to run.                                                             *
 *****************************************************************************/
function nullTest() {
}

function virtualProtocolTest_01() {
  dump("Starting virtualProtocolTest_01\n");
  if (!virtualFileSvc) {
    virtualFileSvc = Components.classes["@verbosio.mozdev.org/virtual-file-service;1"]
                               .getService(xeIVirtualFileService);
    assert(virtualFileSvc, "No virtual file service?");
  }
  const docPack = {
    _wrappers: {},
    _addWrapperByPath: function addWrapperByPath(aWrapper, aPath) {
      this._wrappers[aPath] = aWrapper;
    },

    getWrapper: function getWrapper(aPath) {
      if (aPath in this._wrappers) {
        return this._wrappers[aPath];
      }
      return null;
    }
  }

  function DocumentWrapper(aSource) {
    this._Source = aSource;
    this.contentType = "text/plain";
  }
  DocumentWrapper.prototype = {
    get wrappedSource() {
      return this._Source;
    }
  }


  var testURI = "test:/01";
  var source = "foo:test_01";
  docPack._addWrapperByPath(new DocumentWrapper(source), testURI);
  virtualFileSvc.setDocumentPack(docPack);

  var ioService = Components.classes["@mozilla.org/network/io-service;1"]
                            .getService(nsIIOService);
  var chan = ioService.newChannel(virtualFileSvc.convertURIToVirtual(testURI),
                                  "",
                                  null);

  const scriptInputContract = "@mozilla.org/scriptableinputstream;1"
  var scriptableStream = Components.classes[scriptInputContract]
                                   .createInstance(nsIScriptableInputStream);

  var listener = {
    receivedString: "",

    // nsIStreamListener
    onDataAvailable:
    function onDataAvailable(aRequest,
                             aContext,
                             aInputStream,
                             aOffset,
                             aCount) {
      scriptableStream.init(aInputStream);
      this.receivedString += scriptableStream.read(aCount);
    },

    onStartRequest: function onStartRequest(aRequest, aContext) {
    },
    onStopRequest: function onStopRequest(aRequest, aContext, aStatusCode) {
      assert(aStatusCode == Components.results.NS_OK, "Broken request!");
      assert(this.receivedString == source, "Source discrepancy!");
      virtualFileSvc.setDocumentPack(null);
      dump("Completing virtualProtocolTest_01\n");
    }
  }
  chan.asyncOpen(listener, null);
}

function baseURIMapTest_01() {
  dump("Starting baseURIMapTest_01\n");
  const testPath = getTestAppDir()
  const slash = (testPath.indexOf("/") == -1) ? "\\" : "/";

  const pack = Components.classes[packClass]
                         .createInstance(xeIDocumentPack);
  pack.initializeByFile(testPath + slash + "application.ini");

  const baseChromePath = "chrome://verbosio/content/";

  var base_1 = pack.getBaseURI("chrome://verbosio/content/verbosio.xul");
  var chromePath = base_1.getURI("chrome");
  var localPath = base_1.getURI("local");
  assert(chromePath, "No chrome path?");
  assert(localPath, "No local path?");
  assert(chromePath == baseChromePath,
         "Path failure:\n" + chromePath + "\n" + baseChromePath + "***\n\n");

  assert(base_1 == pack.getBaseURI(localPath), "Base URI maps didn't match!");
  assert(base_1 == pack.getBaseURI(chromePath), "Base URI maps didn't match!");
  dump("Completing baseURIMapTest_01\n");
}

function baseURIMapTest_02() {
  dump("Starting baseURIMapTest_02\n");
  var testPath = getTestAppDir();
  const slash = (testPath.indexOf("/") == -1) ? "\\" : "/";

  var pack = Components.classes[packClass]
                       .createInstance(xeIDocumentPack);
  pack.initializeByFile(testPath + slash + "application.ini");
  testPath = pack.initialAppPath;
  testPath = testPath.substr(0, testPath.lastIndexOf(slash));
  dump(testPath + "\n");
  assert(testPath.indexOf("..") == -1, "Extraneous .. characters!");

  var topDir = new Dir(testPath + slash + "extensions");
  assert(topDir.isDir(), topDir.path + " is not a directory!");
  const testedPaths = {};
  var extDirs = topDir.readDir();
  for (var i = 0; i < extDirs.length; i++) {
    var dir = extDirs[i];
    assert(dir.isDir(), dir.path + " is not a directory!");
    var installPath = dir.path + slash + "install.rdf";
    var base_1 = pack.getBaseURI(installPath);
    assert(base_1, "No base URI map?");
    var localPath = base_1.getURI("local");

    // We want to be at the directory containing install.rdf.
    assert((dir.path + slash) == localPath,
           "Path failure:\n" + localPath + "\n" + (dir.path + slash) + "\n***\n");

    // chrome.manifest checks
    var manifestFile = new File(dir.path + slash + "chrome.manifest");
    if (manifestFile.isFile()) {
      manifestFile.open("r");
      var contents = manifestFile.read();
      manifestFile.close();

      // Parse the manifest; split it into its various components.
      var packageName;
      var packageType;
      var subType;
      var packagePath;
      var extension;

      for (var i = 0; i < contents.length; i++) {
        if (!(/\S/.test(contents[i]))) {
          continue;
        }
        var storage = null;
        var lineValues = contents[i].match(/\S+/g);
        packageType = lineValues[0];
        packageName = lineValues[1];
        packagePath = null;
        extension = "";

        // Determine which subpackage of chrome the new map goes into.
        switch (packageType) {
          case "content":
            packagePath = lineValues[2];
            extension = ".xul";
            break;

          case "locale":
            packagePath = lineValues[3];
            extension = ".dtd";
            break;

          case "skin":
            packagePath = lineValues[3];
            extension = ".css";
            break;

          default:
            // ignore it for now.
        } // end switch (packageType);

        if (!packagePath) {
          continue;
        }

        assert(packageName, "What chrome package does this belong to?");
        var chromePath = "chrome://" + packageName + "/" + packageType + "/";
        if (chromePath in testedPaths) {
          continue;
        }

        var chromeBase = pack.getBaseURI(chromePath + packageName + extension);
        assert(chromeBase.getURI("chrome") == chromePath,
               "chrome base didn't match!");
        var chromeLocal = chromeBase.getURI("local");
        if (chromeLocal.indexOf("jar:") == 0) {
          assert(chromeLocal.indexOf(localPath) == 4, "Local path not found!");
          var pathToJar = chromeLocal.substring(4, chromeLocal.indexOf("!"));
          var jarFile = new File(pathToJar);
          assert(jarFile.isFile(), "Not a jar file!");
          var jarZip = new Zip(pathToJar);
          var pathWithinJar = chromeLocal.substr(chromeLocal.indexOf("!") + 1);
          var fileContents = jarZip.readEntry(pathWithinJar);
          assert(fileContents, "No file found?");
        } else {
          assert(chromeLocal.indexOf(localPath) == 0, "Local path not found!");
          assert(chromeLocal != localPath, "Local path not distinct enough!");
        }
        testedPaths[chromePath] = true;
      }
    } // end if (manifestFile.isFile())
  }
  dump("Completing baseURIMapTest_02\n");
}

function baseURIMapTest_03() {
  dump("Starting baseURIMapTest_03\n");
  /* This test differs from baseURIMapTest_02 in that the local path for
     install.rdf is established later.  This means we check to make sure the
     ordering of base URI retrievals doesn't mess us up.
   */
  const testPath = getTestAppDir();
  const slash = (testPath.indexOf("/") == -1) ? "\\" : "/";

  var pack = Components.classes[packClass]
                       .createInstance(xeIDocumentPack);
  pack.initializeByFile(testPath + slash + "application.ini");

  var topDir = new Dir(testPath + slash + "extensions");
  assert(topDir.isDir(), topDir.path + " is not a directory!");
  const testedPaths = {};
  var extDirs = topDir.readDir();
  for (var i = 0; i < extDirs.length; i++) {
    var dir = extDirs[i];
    assert(dir.isDir(), dir.path + " is not a directory!");
    var installPath = dir.path + slash + "install.rdf";

    // chrome.manifest checks
    var manifestFile = new File(dir.path + slash + "chrome.manifest");
    if (manifestFile.isFile()) {
      manifestFile.open("r");
      var contents = manifestFile.read();
      manifestFile.close();

      // Parse the manifest; split it into its various components.
      var packageName;
      var packageType;
      var subType;
      var packagePath;
      var extension;

      for (var i = 0; i < contents.length; i++) {
        if (!(/\S/.test(contents[i]))) {
          continue;
        }
        var storage = null;
        var lineValues = contents[i].match(/\S+/g);
        packageType = lineValues[0];
        packageName = lineValues[1];
        packagePath = null;
        extension = "";

        // Determine which subpackage of chrome the new map goes into.
        switch (packageType) {
          case "content":
            packagePath = lineValues[2];
            extension = ".xul";
            break;

          case "locale":
            packagePath = lineValues[3];
            extension = ".dtd";
            break;

          case "skin":
            packagePath = lineValues[3];
            extension = ".css";
            break;

          default:
            // ignore it for now.
        } // end switch (packageType);

        if (!packagePath) {
          continue;
        }

        assert(packageName, "What chrome package does this belong to?");
        var chromePath = "chrome://" + packageName + "/" + packageType + "/";
        if (chromePath in testedPaths) {
          continue;
        }

        var chromeBase = pack.getBaseURI(chromePath + packageName + extension);
        assert(chromeBase.getURI("chrome") == chromePath,
               "chrome base didn't match!");
        var chromeLocal = chromeBase.getURI("local");

        var base_1 = pack.getBaseURI(installPath);
        assert(base_1, "No base URI map?");
        var localPath = base_1.getURI("local");

        // We want to be at the directory containing install.rdf.
        assert((dir.path + slash) == localPath,
               "Path failure:\n" + localPath + "\n" + (dir.path + slash) + "\n***\n");
        if (chromeLocal.indexOf("jar:") == 0) {
          assert(chromeLocal.indexOf(localPath) == 4, "Local path not found!");
        } else {
          assert(chromeLocal.indexOf(localPath) == 0, "Local path not found!");
          assert(chromeLocal != localPath, "Local path not distinct enough!");
        }
        testedPaths[chromePath] = true;
      }
    } // end if (manifestFile.isFile())
  }
  dump("Completing baseURIMapTest_03\n");
}

function EntityTest_01() {
  dump("Starting EntityTest_01\n");
  const testPath = getTestAppDir();
  const slash = (testPath.indexOf("/") == -1) ? "\\" : "/";

  var pack = Components.classes[packClass]
                       .createInstance(xeIDocumentPack);
  pack.initializeByFile(testPath + slash + "application.ini");

  var wrapper = pack.getWrapper("chrome://verbosio/content/entityTest.xul");
  dump("Completing EntityTest_01\n");
}

function XULDocTest_01() {
  dump("Starting XULDocTest_01\n");
  const testPath = getTestAppDir();
  const slash = (testPath.indexOf("/") == -1) ? "\\" : "/";

  var pack = Components.classes[packClass]
                       .createInstance(xeIDocumentPack);
  pack.initializeByFile(testPath + slash + "application.ini");

  var wrapper = pack.getWrapper("chrome://verbosio/content/verbosio.xul");

  var rootElement = wrapper.wrappedDocument.documentElement;
  const XMLNS_NS = "http://www.w3.org/2000/xmlns/";
  var xulNSAttr = rootElement.getAttributeNodeNS(XMLNS_NS, "xul");
  assert(xulNSAttr instanceof nsIDOM3Node, "Not a DOM node?");
  var entityData = xulNSAttr.getUserData("entityMatches");
  assert(entityData instanceof nsIArray, "No user data?");

  var foundXUL = null;
  var len = entityData.length;
  assert(len > 0, "No entities in the stored nsIArray?");
  for (var i = 0; i < len; i++) {
    try {
      if (entityData.queryElementAt(i, xeIEntity).name == "XUL_NS") {
        foundXUL = entityData.queryElementAt(i, xeIEntity);
        dump("Entity found.\n");
        break;
      }
    } catch (e) {
      dump("Entity mismatch: " + e + "\n");
      // do nothing, keep going.
    }
  }
  assert(foundXUL, "No &XUL_NS; entity recorded?");
  const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  assert(foundXUL.source == XUL_NS, "Not the XUL namespace URI?");
  assert(foundXUL.safeForAttributes, "Should be safe for attributes.");

  var dtdWrapper = foundXUL.ownerDTD;
  assert(dtdWrapper instanceof xeIDTDWrapper, "Not a DTD wrapper?");
  assert(dtdWrapper.getEntityValue("XUL_NS") == XUL_NS,
         "DTD wrapper has a different value?");

  var origXUL = foundXUL.source;

  var pass = false;
  try {
    foundXUL.acceptNewValue("<foo");
  } catch (e if ((e instanceof nsIException)) &&
          (e.result == Components.results.NS_ERROR_ILLEGAL_VALUE)) {
    pass = true;
  }
  assert(pass, "Shouldn't have accepted not-well-formed XML!");
  assert(origXUL == foundXUL.source, "Should not have changed entity value!");

  pass = false;
  try {
    foundXUL.acceptNewValue("<foo/>");
  } catch (e if ((e instanceof nsIException)) &&
          (e.result == Components.results.NS_ERROR_ILLEGAL_VALUE)) {
    pass = true;
  }
  assert(pass, "Shouldn't have accepted well-formed XML!");
  assert(origXUL == foundXUL.source, "Should not have changed entity value!");

  // Change the entity value for real.
  foundXUL.acceptNewValue("foo");
  assert(foundXUL.source == "foo", "Should have updated source!");
  assert(dtdWrapper.getEntityValue("XUL_NS") == XUL_NS,
         "DTD wrapper has a different value?");

  // Transaction test.
  var txn = {
    // xeITransaction
    description: "Test transaction for DTD setting.",

    // nsITransaction
    doTransaction: function doTransaction() {
      foundXUL.persistValue();
    },

    // nsITransaction
    undoTransaction: function undoTransaction() {
      foundXUL.acceptNewValue(XUL_NS);
      foundXUL.persistValue();
    },

    // nsITransaction
    redoTransaction: function redoTransaction() {
      foundXUL.acceptNewValue("foo");
      foundXUL.persistValue();
    },

    // nsITransaction
    isTransient: false,

    // nsITransaction
    merge: function merge(aTxn) {
      return false;
    },

    QueryInterface: function QueryInterface(aIID) {
      if (aIID.equals(xeITransaction) ||
          aIID.equals(nsITransaction) ||
          aIID.equals(nsISupports)) {
        return this;
      }
      throw Components.results.NS_ERROR_NO_INTERFACE;
    }
  };

  // Do the transaction, make sure the attribute did change.
  pack.txmgr.doTransaction(txn);
  assert(xulNSAttr.nodeValue == "foo", "XUL_NS attribute didn't update?");

  /* Ask a second XUL document, which the pack hasn't loaded yet:  Are you using
     the new and improved entity value?
  */
  const overlayURI = "chrome://verbosio/content/verbosioOverlay.xul";
  var wrapper2 = pack.getWrapper(overlayURI);
  var root2 = wrapper2.wrappedDocument.documentElement;
  var attr2 = root2.getAttributeNodeNS(XMLNS_NS, "xmlns");
  assert(attr2 instanceof nsIDOMAttr, "Failed to get an attribute?");
  assert(attr2.nodeValue == "foo", "Default namespace attribute didn't update?");

  // Undo the transaction, make sure it affects both documents.
  pack.txmgr.undoTransaction();
  assert(xulNSAttr.nodeValue == XUL_NS, "XUL_NS attribute didn't update?");
  assert(attr2.nodeValue == XUL_NS, "XUL_NS attribute didn't update?");

  // Check the xeIEntity associated with xmlns:xul.
  assert(attr2 instanceof nsIDOM3Node, "Not a DOM 3 node?");
  entityData = attr2.getUserData("entityMatches");
  assert(entityData instanceof nsIArray, "No user data?");
  foundXUL = null;
  len = entityData.length;
  assert(len > 0, "No entities in the stored nsIArray?");
  for (var i = 0; i < len; i++) {
    try {
      if (entityData.queryElementAt(i, xeIEntity).name == "XUL_NS") {
        foundXUL = entityData.queryElementAt(i, xeIEntity);
        dump("Entity found.\n");
        break;
      }
    } catch (e) {
      dump("Entity mismatch: " + e + "\n");
      // do nothing, keep going.
    }
  }
  assert(foundXUL, "No &XUL_NS; entity recorded?");
  assert(foundXUL.source == XUL_NS, "XUL_NS entity wasn't updated?");

  // Redo the transaction.  Make sure the attributes change for both documents,
  // and for the entity.
  pack.txmgr.redoTransaction();
  assert(xulNSAttr.nodeValue == "foo", "XUL_NS attribute didn't update?");
  assert(attr2.nodeValue == "foo", "XUL_NS attribute didn't update?");
  assert(foundXUL.source == "foo", "xeIEntity value wasn't updated?");

  // Set up for clipboard tests.
  var commandset = wrapper.wrappedDocument.getElementsByTagName("commandset")[0];
  var docViewer = wrapper.wrappedDocument.getElementById("documentViewer");
  wrapper.selectionRange.setStartBefore(commandset);
  wrapper.selectionRange.setEndAfter(docViewer);

  // Base line clipboard test.
  vbUtils.copySelection(wrapper);
  const CLIP_NODE = "application/x-vnd.verbosio.dom-node";
  assert(vbUtils.clipboardType == CLIP_NODE, "Not a DOM node?");
  var fragmentNode = {};
  var contents = vbUtils.getClipboardContents(CLIP_NODE, fragmentNode);
  assert(contents, "No contents?");
  fragmentNode = fragmentNode.value;
  assert(fragmentNode instanceof nsIDOMDocumentFragment, "No doc fragment?");
  assert(fragmentNode.isEqualNode(wrapper.selectionRange.cloneContents()),
         "How do these nodes differ?");

  // Replace it with a string.
  var trans = Components.classes["@mozilla.org/widget/transferable;1"]
                        .createInstance(nsITransferable);
  var str = Components.classes["@mozilla.org/supports-string;1"]
                           .createInstance(nsISupportsString);
  str.data = "Foo fighters fight on for foo!";
  trans.addDataFlavor("text/unicode");
  trans.setTransferData("text/unicode", str, str.data.length * 2);
  const clipboard = Components.classes["@mozilla.org/widget/clipboard;1"]
                              .getService(nsIClipboard);
  clipboard.setData(trans, null, nsIClipboard.kGlobalClipboard);

  assert(vbUtils.clipboardType === null, "Wasn't the clipboard cleared?");
  fragmentNode = {};
  contents = vbUtils.getClipboardContents(CLIP_NODE, fragmentNode);
  assert(contents === null, "contents: " + contents + "\n");
  assert(fragmentNode.value === null, "fragmentNode: " + fragmentNode.value + '\n');

  contents = vbUtils.getClipboardContents("text/unicode", fragmentNode);
  assert(contents === str.data, "contents: " + contents + "\n");
  assert(fragmentNode.value === null, "fragmentNode: " + fragmentNode.value + '\n');

  dump("Completing XULDocTest_01\n");
}

function CSSLoadTest_01() {
  dump("Starting CSSLoadTest_01\n");
  const testPath = getTestAppDir();
  const slash = (testPath.indexOf("/") == -1) ? "\\" : "/";

  var pack = Components.classes[packClass]
                       .createInstance(xeIDocumentPack);
  pack.initializeByFile(testPath + slash + "application.ini");

  var wrapper = pack.getWrapper("chrome://verbosio/skin/verbosio.css");
  dump("Completing CSSLoadTest_01\n");
}

function InstallRDFAppPackTest_01() {
  dump("Starting InstallRDFAppPackTest_01\n");
  var testPath = getTestAppDir();
  const slash = (testPath.indexOf("/") == -1) ? "\\" : "/";

  var appIniPack = Components.classes[packClass]
                       .createInstance(xeIDocumentPack);
  appIniPack.initializeByFile(testPath + slash + "application.ini");

  // Get the install.rdf application pack.
  testPath = appIniPack.initialAppPath;
  testPath = testPath.substr(0, testPath.lastIndexOf(slash));
  dump(testPath + "\n");
  assert(testPath.indexOf("..") == -1, "Extraneous .. characters!");

  var installRDFPath = testPath + slash + "extensions" + slash
                     + "source-edit@verbosio.mozdev.org" + slash
                     + "install.rdf";
  const rdfPackClass = "@verbosio.mozdev.org/document-pack;1?"
                     + "type=xul-install-rdf";
  var installRDFPack = Components.classes[rdfPackClass]
                                 .createInstance(xeIDocumentPack);
  installRDFPack.initializeByFile(installRDFPath);

  const sourceEditJS = "chrome://source-edit/content/source-edit.js";

  var jsWrapper1 = appIniPack.getWrapper(sourceEditJS);
  var jsWrapper2 = installRDFPack.getWrapper(sourceEditJS);
  assert(jsWrapper1.wrappedSource, "Failed to get the source code?");
  assert(jsWrapper1.wrappedSource == jsWrapper2.wrappedSource,
         "Source doesn't match for the same file under two different packs!");

  dump("Completing InstallRDFAppPackTest_01\n");
}

const testArray = [
  virtualProtocolTest_01,
  baseURIMapTest_01,
  baseURIMapTest_02,
  baseURIMapTest_03,
  EntityTest_01,
  XULDocTest_01,
  CSSLoadTest_01,
  InstallRDFAppPackTest_01,
  nullTest
];

/*****************************************************************************
 * Test engine service.                                                      *
 *****************************************************************************/

const TestEngine =
{
  // xeITestEngine
  runActiveTest: function runActiveTest() {
    loadSubscripts();
    XULDocTest_01();
  },

  // xeITestEngine
  runAllTests: function runAllTests() {
    loadSubscripts();
    for (var i = 0; i < testArray.length; i++) {
      try {
        testArray[i]();
      } catch (e) {
        dump("Test " + i + " failed:\n");
        throw e;
      }
    }
    dump("All tests passed!!!\n");
  },

  _interfaces: [xeITestEngine,
                nsIClassInfo],

  // nsIClassInfo
  getInterfaces: ArrayConverter.xpcomArrayMethod("_interfaces"),

  // nsIClassInfo
  getHelperForLanguage: function getHelperForLanguage(aLanguage) {
    return null;
  },

  // nsIClassInfo
  classDescription: "Verbosio Test Engine Service",

  // nsIClassInfo
  classID: Components.ID("46350bb6-d1db-4f12-9845-7abf64d46279"),

  // nsIClassInfo
  contractID: "@verbosio.mozdev.org/test-engine;1",

  // nsIClassInfo
  implementationLanguage: nsIProgrammingLanguage.JAVASCRIPT,

  // nsIClassInfo
  flags: nsIClassInfo.SINGLETON, /* service */

  // nsISupports
  QueryInterface: function QueryInterface(aIID) {
    for (var j = 0; j < this._interfaces.length; j++) {
      if (aIID.equals(this._interfaces[j])) {
        return this;
      }
    }

    if (aIID.equals(nsISupports))
      return this;

    // Known interfaces we don't support.
    if (aIID.equals(nsISecurityCheckedComponent)) {
      throw Components.results.NS_ERROR_NO_INTERFACE;
      return null;
    }

    // Discover what interfaces we need
    var i = Components.interfaces;
    var found = false;
    for (var prop in i) {
      if (aIID.equals(i[prop])) {
        found = true;
        dump("QUERYINTERFACE: TestEngine " + prop + "\n");
        break;
      }
    }
    if (!found) {
      dump("QUERYINTERFACE: TestEngine Unknown\n");
    }

    throw Components.results.NS_ERROR_NO_INTERFACE;
    return null;
  }
};

var NSGetModule = XPCOMUtils.generateNSGetModule([
  wrapServiceComponent(TestEngine)
]);
