# 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: * [Jish - JavaScript Interactive SHell](https://github.com/gatapia/jish/raw/master/build/jish.exe.zip) * [js.net - Embed JavaScript in Your .Net Projects](https://github.com/gatapia/jish/raw/master/build/js.net.dll.zip) * [Both](https://github.com/gatapia/jish/raw/master/build/both.zip) * [Installer](https://github.com/gatapia/jish/raw/master/build/jish.msi) 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() 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 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: - [Closure](http://closure-library.googlecode.com/svn/docs/closure_goog_testing_jsunit.js.html) - [qUnit](http://docs.jquery.com/Qunit) - [jsUnit](http://www.jsunit.net/) - [Jasmine](http://pivotal.github.com/jasmine/) - [JSCoverage](http://siliconforks.com/jscoverage/) 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/](http://javascriptdotnet.codeplex.com/). John Resig's awesome [Env.js](http://jqueryjs.googlecode.com/svn/trunk/jquery/build/runtest/env.js) provides the DOM support that all test adapters levarage. ## About [PicNet](http://www.picnet.com.au) PicNet does software development for large companies in Australia. We specialise in .Net development and serious JavaScript development. ## About Guido Tapia ## [Guido Tapia](guido@tapia.com.au) is the software development manager for [PicNet](http://www.picnet.com.au) and he is a really awesome guy. Feel free to email: [guido@tapia.com.au](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](https://github.com/gatapia/jish/blob/master/build.js). // 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('') + 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('') + 9); newContents += newv; newContents += contents.substring(contents.indexOf('')); file.WriteAllText(file, newContents); console.log('Updated the version on [' + file + '] to [' + newv + ']'); };