Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

JavaScript execution in .Net. Includes: Jish the JavaScript Interactive SHell, Bindings to lots of Unit Testing frameworks, and much more!!!!

branch: master
README.md

jish - JavaScript the .Net way

Overview

jish / js.net provides several tools for working with JavaScript in a '.Net' kind of way:

  • A wrapper around V8: Include js.net.dll in your project and you can run JavaScript straight from your .Net programs
  • A command line interface (REPL) and JavaScript file interpreter for running JavaScript straight from your console (Jish).
  • A set of unit testing bindings for different JavaScript frameoworks that allows you tu run your JavaScript tests straight from Visual Studio or your favourite CI tools.

Getting Started

The best way to get started is to download the source:

git clone git://github.com/gatapia/jish.git

That way you can send me fixes ;)

Otherwise just download one of the following:

Note: The installer just 'unzips' the 'Both' package into a specified folder.

Using js.net in your code

To use js.net simply add a reference to the js.net.dll in your project and initialise the Engine.

Example:

using (IEngine engine = new new JSNetEngine()) {
  Console.WriteLine("Answer Is: " + engine.Run("1 + 1"));
}

Jish

Jish.exe is a command line shell interpreter to run JavaScript scripts that allows .Net framework integration. Jish can also be used to run JavaScript scripts files.

Running Jish

Running 'jish.exe' will yield the interactive shell.

jish.exe
> 1 + 1
2
> .exit

Built-in Jish commands

There are two type of built in commands available to Jish Inline Commands and Console Commands. The main difference is that Console command are there to help you using the shell console, commands like .help, .exit, etc.

Inline commands can be run inboth console and interpreted mode (running script files). And are your window into the .Net framework.

All Console Jish commands start with a '.' character and are only available if running in console mode. Jish commands cannot be mixed on the same line with other JavaScript commands. Commands included in Jish are:

Jish Help
=========

Console Commands

.break:
    Cancels the execution of a multi-line command.
    Arguments: ()

.clear:
    Break, and also clear the local context.
    Arguments: ()

.exit:
    Exit Jish.
    Arguments: ()


Inline Commands

jish.assembly:
    Loads an assembly into the Jish 'context'. You can now
        jish.create(<typeNames>) types from this assembly.
    Arguments: (assemblyFileNameOrAssemblyName)

jish.closure:
    Loads google closure library environment.
    Arguments: (baseJsPath)

jish.create:
    Creates and instance of any type (including static classes).  If the
        type's assembly is not loaded you must precede this call with a
        call to jish.assembly('assemblyFileName').
    Arguments: (typeName, param object[] args)

jish.load:
    Load and executes another Jish or plain JavaScript file.
    Arguments: (file)

jish.process:
    Executes the command in a separate Process.
    Arguments: (command, arguments?)

Extending Jish

There are 3 main extension points to Jish.

JavaScript Modules

The first way you can extend Jish is by creating a JavaScript extension files that will be available to all your Jish scripts.

Simply create a 'modules' directory next to your 'jish.exe' file. This directory will parse all .js files and they will be loaded into your Jish environment. This directory is also where you can drop any additional dll's that you want loaded into the context. They will be parsed for implementations if IInlineCommands and ICommands also.

js.net.jish.InlineCommand.IInlineCommand

The IInlineCommand(s) extend the JavsScript environment by adding a type to the global namespace. Inline commands are the main way of providing .Net framwork capabilities to your JavaScript scripts.

  • IInlineCommand(s) must have a non-embedded namespace (cannot contain '.'s)
  • IInlineCommand(s) cannot execute other scripts, or set/get globals
  • IInlineCommand(s) can return any type to the JavaScript environment.
  • IInlineCommand(s) intgrate into the built in .help command.

An example IInlineCommand follows, this is the jish.process command and is used to spawn a process from your JavaScript environment. Note: this is the actual implementation code and is available every time you use jish by jish.process('commandName', 'arguments_string'):

using System;
using System.Collections.Generic;
using System.Diagnostics;
using js.net.Util;

namespace js.net.jish.Command.InlineCommand
{
  public class ProcessCommand : IInlineCommand
  {
    private readonly JSConsole console;

    public ProcessCommand(JSConsole console)
    {
      Trace.Assert(console != null);

      this.console = console;
    }

    public string GetName()
    {
      return "process";
    }

    public string GetHelpDescription()
    {
      return "Executes the command in a separate Process.";
    }

    public IEnumerable<CommandParam> GetParameters()
    {
      CommandParam a1 = new CommandParam { Name = "command" };
      CommandParam a2 = new CommandParam { Name = "arguments", Null = true};
      return new[] { a1, a2 };
    }

    public string GetNameSpace()
    {
      return "jish";
    }

    public int process(string command, string arguments = null) 
    {
      Trace.Assert(!String.IsNullOrWhiteSpace(command));

      using (var process = new Process
                      {
                        StartInfo =
                          {
                            FileName = command,
                            Arguments = arguments,
                            UseShellExecute = false,
                            RedirectStandardOutput = true,
                            RedirectStandardError = true
                          }
                      })
      {
        process.Start();
        string err = process.StandardError.ReadToEnd();
        string output = process.StandardOutput.ReadToEnd();

        if (!String.IsNullOrWhiteSpace(err)) console.error(err);
        if (!String.IsNullOrWhiteSpace(output)) console.log(output);

        process.WaitForExit();

        return process.ExitCode;
      }
    }
  }
}

js.net.jish.Command.IConsoleCommand (Console Commands)

The console commands implement the ICommand interface. ICommand(s) have certain charasteristics which may not be immediately obvious.

  • ICommand(s) have access to IJishInterpreter which allows all ICommand(s) to run additional JavaScript files, load and set globals and integrate into the built in .help command.
  • ICommand(s) get run before any other JavaScript command, regardless where they appear on the JavaScript file.
  • ICommand(s) only accept simple primitive inputs.
  • ICommand(s) cannot return any values into the JavaScript environment
  • ICommand(s) integrate into Jish's .help system
  • ICommand(s) are invoked by calling the command prefixed by a .. I.e. .commandname

Running multi-file scripts / Google Closure Support

jish comes with out of the box support for Google Closure Library. This means that if you want to levarage the huge utility library in closure or use multi file projects then this is easy.

Download the [closure library] http://code.google.com/closure/library/docs/gettingstarted.html) if you have not already done so and then Jish away:

jish.closure('path/to/your/closure/base.js');      
jish.addClosureDeps('path/to/any/additional/deps.js');    
// Require your files or any closure library file
goog.require('jish.test.main');    
// Go!!
var main = new jish.test.main();
console.log(main.callUtilMethod());

Unit Testing

One of js.net's primary and most stable feature is JavaScript unit testing support (including tests that rely on the DOM). Current supported frameworks are:

To unit test your JavaScript simply follow these steps:

  • Change the target framework of your unit test project to x86 (Project Properties -> Build -> Playform Target -> x86)
  • Add a reference to the js.net.dll
  • Add your unit test like this (This is using NUnit style code but you can use anything you want):

Example:

[TestFixture] public class JavaScriptTests {
  [Test] public void TestSingleFile()
  { 
    // Initialise your adapter using the helpers in JSNet utility class
    using (ITestAdapter adapter = JSNet.QUnit(pathToTheQunitJsFile)) 
    {
      // Run your test file
      ITestResults results = 
        adapter.RunTest(pathToTestJsOrHtmlFile); 

      // Assert no failures
      Assert.AreEqual(0, results.Failed.Count(), results.ToString());
      Assert.AreEqual(22, results.Passed.Count(), results.ToString());
    }            
  }
}

Unit Testing Multiple Files

Unit testing multiple files is just as simple as testing a single file. So once you have added the js.net.dll reference to your project simply write some code like this:

Example:

[TestFixture] public class JavaScriptTests {
  [Test] public void TestAllFiles()
  { 
    // Run all tests
    string[] files = GetTestSuiteFiles();
    TestSuiteRunner runner = 
      JSNet.ClosureLibraryTestSuiteRunner(baseJsFile);
    runner.AddGlobalSourceFile(depsJsFile);
    TestSuiteResults results = runner.TestFiles(files);

    // Assert no failures
    Assert.AreEqual(0, results.Failed.Count(), results.ToString());
    Assert.Greater(results.Passed.Count(), 0, results.ToString());
    }            
  }
}

Integration with NUnit test runners

Modern NUnit test runners that support the ValueSource attribute can very nicely display test results of JavaScript tests if you do the following:

// Must inherit: 'JSNetNUnitFixture' 
[TestFixture] public class JavaScriptTests : JSNetNUnitFixture {  

  // If running tests in the constructor is not an option, you can also 
  // call base.SetResults at a later stage.
  public JSNetNunitFixtureTests() : 
    base(JSNet.QUnitTestSuiteRunner(@"..\lib\qunit.js").
      TestFiles(new[] { @"..\src\tests.js" })) {}    

  // Implement a test that has the ValueSource("GetTestNames") attribute
  [Test] public void JavaScriptTest(
      [ValueSource("GetTestNames")] string testName) {  
    Assert.IsTrue(Passed(testName));
  }

Coverage

Running coverage on your tests is just as simple as running the tests themselves.

Example:

[TestFixture] public class JavaScriptTests {
  [Test] public void TestRunCoverageWithProperAdapter()
  {
    // Code must be instrumented first (use the native instrumenter).
    Process p = 
      Process.Start("jscoverage.exe", "src\ instrumented\").WaitForExit();

    using (ICoverageAdapter adapter = 
      JSNet.JSCoverage(JSNet.ClosureLibrary(basejsfile)))
    {        
      adapter.LoadSourceFile(@"instrumented\instrumentedSourceFile.js"); 
      ICoverageResults results = 
        adapter.RunCoverage(@"src\tests\sourceFileTests.js");

      // Assert tests passes as per normal
      Assert.AreEqual(0, results.Failed.Count(), results.ToString());
      Assert.AreEqual(4, results.Passed.Count(), results.ToString());

      // Assert coverage is as expected
      Assert.AreEqual(1, results.FilesCount);
      Assert.AreEqual(5, results.Statements);
      Assert.AreEqual(5, results.Executed);
      // Assert we have 100% coverage
      Assert.AreEqual(100.0m, results.CoveragePercentage);

      // Assert coverage for individual files within the test
      IFileCoverageResults sourceCoverage = results.FileResults.First();
      Assert.AreEqual("jscoverage_source.js", sourceCoverage.FileName);
      Assert.AreEqual(5, sourceCoverage.Statements);
      Assert.AreEqual(5, sourceCoverage.Executed);
      Assert.AreEqual(100.0m, sourceCoverage.CoveragePercentage);
    } 
  }
}

Shout Outs

This project would not be possible without JavaScript.Net: http://javascriptdotnet.codeplex.com/.

John Resig's awesome Env.js provides the DOM support that all test adapters levarage.

About PicNet

PicNet does software development for large companies in Australia. We specialise in .Net development and serious JavaScript development.

About Guido Tapia

Guido Tapia is the software development manager for PicNet and he is a really awesome guy.

Feel free to email: guido@tapia.com.au

License

BSD, see license.txt for full license

An example jish JavaScript file (1) - Windows Forms

This example is a very simple winforms app.

jish.assembly('js.net.jish/bin/System.Drawing.dll')
jish.assembly('js.net.jish/bin/System.Windows.Forms.dll')

var app = jish.create('System.Windows.Forms.Application');
var form = jish.create('System.Windows.Forms.Form');
var lbl = jish.create('System.Windows.Forms.Label');
form.Text = lbl.Text = 'Hello Jish!!!';
lbl.Location = jish.create('System.Drawing.Point', 50, 50);
form.Controls.Add(lbl);

app.Run(form);

An example jish JavaScript file (2) - Build Script

This is Jish's very own build file. You can find the latest version of this file in github.

// Use jish.exe to execute this file. Takes two optional command line 
// instructions: 
//    updatever:  Increments the build numbers on the NuGet files
//    push:       Publishes NuGet packages

// Load additional assemblies into the context.  This dll includes the 
// build.zip command used in createZipBundles() below;
jish.assembly('js.net.test.module/bin/js.net.test.module.dll');

// Create a handle on the File static class.  Yes, jish.create even creates
// static handles.
var file = jish.create('System.IO.File');

run(); // Go!!!!

function run() {
  createZipBundles();

  if (args.indexOf('updatever') >= 0) {
    updateVersionNumberInNuGetConfigs();
  } else {
    console.log('Not updating version numbers. To update versions please ' +
      'execute with "updatever" argument');
  }
  packNuGetPacakges();
  if (args.indexOf('push') >= 0) {
    pushNuGetPackages();
  } else {
    console.log('Not "pushing". To push please execute with "push" argmuent');
  }  
};

function createZipBundles() {
  build.zip('build\\jish.exe.zip', ['build\\jish\\tools\\jish.exe']);
  build.zip('build\\js.net.dll.zip', ['build\\js.net\\lib\\js.net.dll']);  
  build.zip('build\\both.zip', ['build\\js.net.dll.zip', 'build\\jish.exe.zip']);  
  console.log('Successfully created the zip bundles');
};

function copyFile(from, to) {  
  file.Copy(from, to, true);
};

function updateVersionNumberInNuGetConfigs() {
  updateVersionOnConfig('build\\js.net\\js.net.nuspec');
  updateVersionOnConfig('build\\jish\\jish.nuspec');
};

function packNuGetPacakges() {  
  jish.process('build\\NuGet.exe', 
    'Pack -OutputDirectory build\\js.net build\\js.net\\js.net.nuspec');
  jish.process('build\\NuGet.exe', 
    'Pack -OutputDirectory build\\jish build\\jish\\jish.nuspec');
};

function pushNuGetPackages() {  
  var name = 'build\\js.net\\js.net.' + 
    getVersionNumberFromConfig('build\\js.net\\js.net.nuspec') + '.nupkg';

  console.log('Publishing ' + name);
  jish.process('build\\NuGet.exe', 'Push ' + name);

  name = 'build\\jish\\jish.' + 
    getVersionNumberFromConfig('build\\jish\\jish.nuspec') + '.nupkg';

  console.log('Publishing ' + name);
  jish.process('build\\NuGet.exe', 'Push ' + name);
};

function getVersionNumberFromConfig(configFile) {
  var contents = file.ReadAllText(configFile);
  var version = contents.substring(contents.indexOf('<version>') + 9);
  version = version.substring(0, version.indexOf('<'));
  return version;
};

function updateVersionOnConfig(configFile) {
  var version = getVersionNumberFromConfig(configFile);
  version = updateVersionNumber(version);
  setVersionNumberOnConfig(configFile, version);
};

function updateVersionNumber(oldVersion) {
  var pre = oldVersion.substring(0, oldVersion.lastIndexOf('.') + 1);
  var buildNum = 
    parseInt(oldVersion.substring(oldVersion.lastIndexOf('.') + 1), 10);
  buildNum++;
  return pre + buildNum.toString();
};

function setVersionNumberOnConfig(file, newv) {
  var contents = file.ReadAllText(file);
  var newContents = contents.substring(0, contents.indexOf('<version>') + 9);
  newContents += newv;
  newContents += contents.substring(contents.indexOf('</version>'));
  file.WriteAllText(file, newContents);

  console.log('Updated the version on [' + file + '] to [' + newv + ']');
};
Something went wrong with that request. Please try again.