Merge branch 'master' of github.com:mathematicalcoffee/jsdoc into tests-util

This commit is contained in:
mathematicalcoffee 2013-02-14 15:29:33 +10:00
commit c6314a107a
17 changed files with 547 additions and 38 deletions

View File

@ -60,7 +60,8 @@ JSDoc.
In rare cases, users may have their Java CLASSPATH configured to override the
included Rhino and point to an older version of Rhino instead. If this is the
case, simply correct the CLASSPATH to remove the older Rhino.
case, simply correct the CLASSPATH to remove the older Rhino. (On OS X, you may
need to remove the file `~/Library/Java/Extensions/js.jar`.)
The version of Rhino distributed with JSDoc 3 can be found here:
https://github.com/hegemonic/rhino

View File

@ -6,6 +6,27 @@
var markdown = require('jsdoc/util/markdown');
/** Removes child tutorial from the parent. Does *not* unset child.parent though.
@param {Tutorial} parent - parent tutorial.
@param {Tutorial} child - Old child.
@private
*/
function removeChild(parent, child) {
var index = parent.children.indexOf(child);
if (index != -1) {
parent.children.splice(index, 1);
}
}
/** Adds a child to the parent tutorial. Does *not* set child.parent though.
@param {Tutorial} parent - parent tutorial.
@param {Tutorial} child - New child.
@private
*/
function addChild(parent, child) {
parent.children.push(child);
}
/**
@module jsdoc/tutorial
*/
@ -28,33 +49,32 @@ exports.Tutorial = function(name, content, type) {
};
/** Moves children from current parent to different one.
@param {Tutorial} parent - New parent.
@param {?Tutorial} parent - New parent. If null, the tutorial has no parent.
*/
exports.Tutorial.prototype.setParent = function(parent) {
// removes node from old parent
if (this.parent) {
this.parent.removeChild(this);
removeChild(this.parent, this);
}
this.parent = parent;
this.parent.addChild(this);
if (parent) {
addChild(parent, this);
}
};
/** Removes children from current node.
@param {Tutorial} child - Old child.
*/
exports.Tutorial.prototype.removeChild = function(child) {
var index = this.children.indexOf(child);
if (index != -1) {
this.children.splice(index, 1);
}
child.setParent(null);
};
/** Adds new children to current node.
@param {Tutorial} child - New child.
*/
exports.Tutorial.prototype.addChild = function(child) {
this.children.push(child);
child.setParent(this);
};
/** Prepares source.

View File

@ -11,20 +11,80 @@
var tutorial = require('jsdoc/tutorial'),
fs = require('jsdoc/fs'),
error = require('jsdoc/util/error'),
path = require('path'),
hasOwnProp = Object.prototype.hasOwnProperty,
conf = {},
tutorials = {},
finder = /^(.*)\.(x(?:ht)?ml|html?|md|markdown|json)$/i;
/** checks if `conf` is the metadata for a single tutorial.
* A tutorial's metadata has a property 'title' and/or a property 'children'.
* @param {object} json - the object we want to test (typically from JSON.parse)
* @returns {boolean} whether `json` could be the metadata for a tutorial.
*/
function isTutorialJSON(json) {
// if conf.title exists or conf.children exists, it is metadata for a tutorial
return (json.hasOwnProperty('title') || json.hasOwnProperty('children'));
}
/** Helper function that adds tutorial configuration to the `conf` variable.
* This helps when multiple tutorial configurations are specified in one object,
* or when a tutorial's children are specified as tutorial configurations as
* opposed to an array of tutorial names.
*
* Recurses as necessary to ensure all tutorials are added.
*
* @param {string} name - if `meta` is a configuration for a single tutorial,
* this is that tutorial's name.
* @param {object} meta - object that contains tutorial information.
* Can either be for a single tutorial, or for multiple
* (where each key in `meta` is the tutorial name and each
* value is the information for a single tutorial).
* Additionally, a tutorial's 'children' property may
* either be an array of strings (names of the child tutorials),
* OR an object giving the configuration for the child tutorials.
*/
function addTutorialConf(name, meta) {
var names, i;
if (isTutorialJSON(meta)) {
// if the children are themselves tutorial defintions as opposed to an
// array of strings, add each child.
if (meta.hasOwnProperty('children') && !Array.isArray(meta.children)) {
names = Object.keys(meta.children);
for (i = 0; i < names.length; ++i) {
addTutorialConf(names[i], meta.children[names[i]]);
}
// replace with an array of names.
meta.children = names;
}
// check if the tutorial has already been defined...
if (conf.hasOwnProperty(name)) {
error.handle(new Error("Tutorial " + name + "'s metadata is defined multiple times, only the first will be used."));
} else {
conf[name] = meta;
}
} else {
// it's an object of tutorials, the keys are th etutorial names.
names = Object.keys(meta);
for (i = 0; i < names.length; ++i) {
addTutorialConf(names[i], meta[names[i]]);
}
}
}
/** Adds new tutorial.
@param {tutorial.Tutorial} current - New tutorial.
*/
exports.addTutorial = function(current) {
tutorials[current.name] = current;
if (tutorials.hasOwnProperty(current.name)) {
error.handle(new Error("Tutorial with name " + current.name + " exists more than once, not adding (same name, different file extensions?)"));
} else {
tutorials[current.name] = current;
// default temporary parent
current.setParent(exports.root);
// default temporary parent
current.setParent(exports.root);
}
};
/** Root tutorial.
@ -77,7 +137,8 @@ exports.load = function(_path) {
// configuration file
case 'json':
conf[name] = JSON.parse(content);
var meta = JSON.parse(content);
addTutorialConf(name, meta);
// don't add this as a tutorial
return;
@ -117,7 +178,7 @@ exports.resolve = function() {
if (item.children) {
item.children.forEach(function(child) {
if (!(child in tutorials)) {
require('jsdoc/util/error').handle( new Error("Missing child tutorial: " + child) );
error.handle( new Error("Missing child tutorial: " + child) );
}
else {
tutorials[child].setParent(current);

View File

@ -1,4 +1,226 @@
/*global describe: true, env: true, it: true */
describe("jsdoc/tutorial", function() {
//TODO
});
var tutorial = require('jsdoc/tutorial'),
name = "tuteID",
content = "Tutorial content blah blah blah & <",
tute = new tutorial.Tutorial(name, content, tutorial.TYPES.NOTAVALUE),
par = new tutorial.Tutorial('parent', "# This is the parent tutorial's <em>content & stuff</em> A_B X_Y",
tutorial.TYPES.MARKDOWN),
par2 = new tutorial.Tutorial('parent2', "<h2>This is the second parent tutorial</h2>",
tutorial.TYPES.HTML);
it("module should exist", function() {
expect(tutorial).toBeDefined();
expect(typeof tutorial).toBe("object");
});
it("should export a Tutorial function", function() {
expect(tutorial.Tutorial).toBeDefined();
expect(typeof tutorial.Tutorial).toBe("function");
});
it("should export a TYPES object", function() {
expect(tutorial.TYPES).toBeDefined();
expect(typeof tutorial.TYPES).toBe("object");
});
describe("tutorial.TYPES", function() {
it("should have a HTML property", function() {
expect(tutorial.TYPES.HTML).toBeDefined();
});
it("should have a MARKDOWN property", function() {
expect(tutorial.TYPES.MARKDOWN).toBeDefined();
});
});
describe("Tutorial", function() {
it("should have 'setParent' function", function() {
expect(tutorial.Tutorial.prototype.setParent).toBeDefined();
expect(typeof tutorial.Tutorial.prototype.setParent).toBe("function");
});
it("should have 'removeChild' function", function() {
expect(tutorial.Tutorial.prototype.removeChild).toBeDefined();
expect(typeof tutorial.Tutorial.prototype.removeChild).toBe("function");
});
it("should have 'addChild' function", function() {
expect(tutorial.Tutorial.prototype.addChild).toBeDefined();
expect(typeof tutorial.Tutorial.prototype.addChild).toBe("function");
});
it("should have 'parse' function", function() {
expect(tutorial.Tutorial.prototype.parse).toBeDefined();
expect(typeof tutorial.Tutorial.prototype.parse).toBe("function");
});
it("should have a 'name' property", function() {
expect(tute.name).toBeDefined();
expect(typeof tute.name).toBe("string");
expect(tute.name).toBe(name);
});
it("should have a 'title' property, by default set to to the tute's name", function() {
expect(tute.title).toBeDefined();
expect(typeof tute.title).toBe("string");
expect(tute.title).toBe(name);
// Testing of overriding a tutorial's title in its JSON file is
// covered in tutorial/resolver.js tests.
});
it("should have a 'content' property set to the tutorial's content", function() {
expect(tute.content).toBeDefined();
expect(typeof tute.content).toBe("string");
expect(tute.content).toBe(content);
});
it("should have a 'type' property set to the tutorial's type", function() {
expect(par.type).toBeDefined();
expect(typeof par.type).toBe(typeof tutorial.TYPES.MARKDOWN);
expect(par.type).toBe(tutorial.TYPES.MARKDOWN);
});
it("should have a 'parent' property, initially null", function() {
expect(tute.parent).toBeDefined();
expect(tute.parent).toBe(null);
});
it("should have a 'children' property, an empty array", function() {
expect(tute.children).toBeDefined();
expect(Array.isArray(tute.children)).toBe(true);
expect(tute.children.length).toBe(0);
});
describe("setParent", function() {
it("adding a parent sets the child's 'parent' property", function() {
tute.setParent(par);
expect(tute.parent).toBe(par);
});
it("adding a parent adds the child to the parent's 'children' property", function() {
expect(par.children).toContain(tute);
});
it("re-parenting removes the child from the previous parent", function() {
tute.setParent(par2);
expect(tute.parent).toBe(par2);
expect(par2.children).toContain(tute);
expect(par.children).not.toContain(tute);
});
it("calling setParent with a null parent unsets the child's parent and removes the child from its previous parent", function() {
expect(par2.children).toContain(tute);
tute.setParent(null);
expect(tute.parent).toBe(null);
expect(par2.children).not.toContain(tute);
});
});
describe("addChild", function() {
it("adding a child tutorial adds the child to the parent's 'children' property", function() {
tute.setParent(null);
var n = par.children.length;
par.addChild(tute);
expect(par.children.length).toBe(n + 1);
expect(par.children).toContain(tute);
});
it("adding a child tutorial sets the child's parent to to the parent tutorial", function() {
expect(tute.parent).toBe(par);
});
it("adding a child tutorial removes the child from its old parent", function() {
// tue is currently owned by par; we reparent it to par2
expect(tute.parent).toBe(par);
par2.addChild(tute);
expect(tute.parent).toBe(par2);
expect(par.children).not.toContain(tute);
expect(par2.children).toContain(tute);
});
});
describe("removeChild", function() {
function removeChild() {
par2.removeChild(par);
}
it("removing a tutorial that is not a child silently passes", function() {
var n = par2.children.length;
expect(removeChild).not.toThrow();
expect(par2.children.length).toBe(n);
});
it("removing a child removes the child from the parent's 'children' property", function() {
tute.setParent(par2);
expect(par2.children.length).toBe(1);
par2.removeChild(tute);
expect(par2.children).not.toContain(tute);
expect(par2.children.length).toBe(0);
});
it("removing a child unsets the child's 'parent' property", function() {
expect(tute.parent).toBe(null);
});
});
describe("various inheritance tests with addChild, setParent and removeChild", function() {
it("parenting and unparenting via addChild, setParent and removeChild makes sure inheritance is set accordingly", function() {
// unparent everything.
tute.setParent(null);
par.setParent(null);
par2.setParent(null);
// let tute belong to par
tute.setParent(par);
expect(tute.parent).toBe(par);
expect(par2.children.length).toBe(0);
expect(par.children.length).toBe(1);
expect(par.children[0]).toBe(tute);
// addChild tute to par2. its parent should now be par2, and
// it can't be the child of two parents
par2.addChild(tute);
expect(tute.parent).toBe(par2);
expect(par.children.length).toBe(0);
expect(par2.children.length).toBe(1);
expect(par2.children[0]).toBe(tute);
// removeChild tute from par2. tute should now be unparented.
par2.removeChild(tute);
expect(tute.parent).toBe(null);
expect(par.children.length).toBe(0);
expect(par2.children.length).toBe(0);
});
});
describe("parse", function() {
it("Tutorials with HTML type return content as-is", function() {
expect(par2.parse()).toBe("<h2>This is the second parent tutorial</h2>");
});
it("Tutorials with MARKDOWN type go through the markdown parser, respecting configuration options", function() {
var old = env.conf.markdown;
env.conf.markdown = {parser: 'evilstreak'};
expect(par.parse()).toBe("<h1>This is the parent tutorial&#39;s <em>content & stuff</em> A<em>B X</em>Y</h1>");
env.conf.markdown = {parser: 'gfm'};
expect(par.parse()).toBe("<h1>This is the parent tutorial's <em>content & stuff</em> A_B X_Y</h1>");
env.conf.markdown = old;
});
it("Tutorials with unrecognised type are returned as-is", function() {
expect(tute.parse()).toBe(content);
});
});
});
});

View File

@ -1,33 +1,214 @@
/*global afterEach: true, describe: true, env: true, expect: true, it: true */
describe("jsdoc/tutorial/resolver", function() {
/*jshint evil: true */
// TODO: more tests
var resolver = require('jsdoc/tutorial/resolver'),
tutorial = require('jsdoc/tutorial'),
lenient = !!env.opts.lenient,
log = eval(console.log);
function missingTutorial() {
resolver.load(__dirname + "/test/tutorials/incomplete");
/*jshint evil: true */
it("should exist", function() {
expect(resolver).toBeDefined();
expect(typeof resolver).toEqual('object');
});
it("should export a 'addTutorial' function", function() {
expect(resolver.addTutorial).toBeDefined();
expect(typeof resolver.addTutorial).toEqual("function");
});
it("should export a 'load' function", function() {
expect(resolver.load).toBeDefined();
expect(typeof resolver.load).toEqual("function");
});
it("should export a 'resolve' function", function() {
expect(resolver.resolve).toBeDefined();
expect(typeof resolver.resolve).toEqual("function");
});
it("should export a 'root' tutorial", function() {
expect(resolver.root).toBeDefined();
expect(resolver.root instanceof tutorial.Tutorial).toEqual(true);
});
it("exported 'root' tutorial should export a 'getByName' function", function() {
expect(resolver.root.getByName).toBeDefined();
expect(typeof resolver.root.getByName).toEqual("function");
});
// note: every time we addTutorial or run the resolver, we are *adding*
// to the root tutorial.
// addTutorial
var tute = new tutorial.Tutorial('myTutorial', '', tutorial.TYPES.HTML);
resolver.addTutorial(tute);
describe("addTutorial", function() {
it("should add a default parent of the root tutorial", function() {
expect(tute.parent).toEqual(resolver.root);
});
it("should be added to the root tutorial as a child", function() {
expect(resolver.root.children[0]).toEqual(tute);
});
});
// root.getByName
describe("root.getByName", function() {
it("can retrieve tutorials by name", function() {
expect(resolver.root.getByName('myTutorial')).toEqual(tute);
});
});
// load
resolver.load(__dirname + "/test/tutorials/tutorials");
var childNames = resolver.root.children.map(function (t) { return t.name; }),
test = resolver.root.getByName('test'),
test2 = resolver.root.getByName('test2'),
test3 = resolver.root.getByName('test3'),
test4 = resolver.root.getByName('test4');
test6 = resolver.root.getByName('test6');
describe("load", function() {
it("all tutorials are added, initially as top-level tutorials", function() {
// check they were added
expect(test).toBeDefined();
expect(test2).toBeDefined();
expect(test3).toBeDefined();
expect(test4).toBeDefined();
expect(test6).toBeDefined();
// check they are top-level in resolver.root
expect(childNames.indexOf('test')).not.toEqual(-1);
expect(childNames.indexOf('test2')).not.toEqual(-1);
expect(childNames.indexOf('test3')).not.toEqual(-1);
expect(childNames.indexOf('test4')).not.toEqual(-1);
expect(childNames.indexOf('test6')).not.toEqual(-1);
});
it("non-tutorials are skipped", function() {
expect(resolver.root.getByName('multple')).toBeUndefined();
expect(resolver.root.getByName('test5')).toBeUndefined();
});
it("tutorial types are determined correctly", function() {
// test.html, test2.markdown, test3.html, test4.md, test6.xml
expect(test.type).toEqual(tutorial.TYPES.HTML);
expect(test2.type).toEqual(tutorial.TYPES.MARKDOWN);
expect(test3.type).toEqual(tutorial.TYPES.HTML);
expect(test4.type).toEqual(tutorial.TYPES.MARKDOWN);
expect(test6.type).toEqual(tutorial.TYPES.HTML);
});
});
// resolve
// myTutorial
// test
// |- test2
// |- test6
// |- test3
// |- test4
describe("resolve", function() {
resolver.resolve();
}
it("hierarchy is resolved properly no matter how the children property is defined", function() {
// root has child 'test'
expect(resolver.root.children.length).toEqual(2);
expect(resolver.root.children.indexOf(test)).not.toEqual(-1);
expect(test.parent).toEqual(resolver.root);
afterEach(function() {
env.opts.lenient = lenient;
console.log = log;
// test has child 'test2'
expect(test.children.length).toEqual(1);
expect(test.children[0]).toEqual(test2);
expect(test2.parent).toEqual(test);
// test2 has children test3, test6
expect(test2.children.length).toEqual(2);
expect(test2.children.indexOf(test3)).not.toEqual(-1);
expect(test2.children.indexOf(test6)).not.toEqual(-1);
expect(test3.parent).toEqual(test2);
expect(test6.parent).toEqual(test2);
// test3 has child test4
expect(test3.children.length).toEqual(1);
expect(test3.children[0]).toEqual(test4);
expect(test4.parent).toEqual(test3);
});
it("tutorials without configuration files have titles matching filenames", function() {
// test6.xml didn't have a metadata
expect(test6.title).toEqual('test6');
});
it("tutorials with configuration files have titles matching filenames", function() {
// test.json had info for just test.json
expect(test.title).toEqual("Test tutorial");
});
it("multiple tutorials can appear in a configuration file", function() {
expect(test2.title).toEqual("Test 2");
expect(test3.title).toEqual("Test 3");
expect(test4.title).toEqual("Test 4");
});
});
it("throws an exception for missing tutorials if the lenient option is not enabled", function() {
env.opts.lenient = false;
// error reporting.
describe("Error reporting", function() {
// Tests for error reporting.
function missingTutorial() {
resolver.load(__dirname + "/test/tutorials/incomplete");
resolver.resolve();
}
function duplicateNamedTutorials() {
// can't add a tutorial if another with its name has already been added
resolver.addTutorial(tute);
}
function duplicateDefinedTutorials() {
// can't have a tutorial's metadata defined twice in .json files
resolver.load(__dirname + "/test/tutorials/duplicateDefined");
resolver.resolve();
}
expect(missingTutorial).toThrow();
afterEach(function() {
env.opts.lenient = lenient;
console.log = log;
});
it("throws an exception for missing tutorials if the lenient option is not enabled", function() {
env.opts.lenient = false;
expect(missingTutorial).toThrow();
});
it("doesn't throw an exception for missing tutorials if the lenient option is enabled", function() {
console.log = function() {};
env.opts.lenient = true;
expect(missingTutorial).not.toThrow();
});
it("throws an exception for duplicate-named tutorials (e.g. test.md, test.html) if the lenient option is not enabled", function() {
env.opts.lenient = false;
expect(duplicateNamedTutorials).toThrow();
});
it("doesn't throw an exception for duplicate-named tutorials (e.g. test.md, test.html) if the lenient option is not enabled", function() {
console.log = function() {};
env.opts.lenient = true;
expect(duplicateNamedTutorials).not.toThrow();
});
it("throws an exception for tutorials defined twice in .jsons if the lenient option is not enabled", function() {
env.opts.lenient = false;
expect(duplicateDefinedTutorials).toThrow();
});
it("doesn't throw an exception for tutorials defined twice in .jsons if the lenient option is not enabled", function() {
console.log = function() {};
env.opts.lenient = true;
expect(duplicateDefinedTutorials).not.toThrow();
});
});
it("doesn't throw an exception for missing tutorials if the lenient option is enabled", function() {
console.log = function() {};
env.opts.lenient = true;
expect(missingTutorial).not.toThrow();
});
});
});

View File

@ -0,0 +1 @@
<h1>tutorial ASDF</h1>

View File

@ -0,0 +1 @@
{"title": "Conflicting title"}

View File

@ -0,0 +1,5 @@
{
"asdf": {
"title": "Tutorial Asdf"
}
}

View File

@ -0,0 +1 @@
{"title": "missing child tutorial", "children": ["child"]}

View File

@ -1 +0,0 @@
{"title": "missing child tutorial", "children": ["test2"]}

View File

@ -0,0 +1,12 @@
{
"test2": {
"title": "Test 2",
"children": ["test3", "test6"]
},
"test3": {
"title": "Test 3",
"children": {
"test4": {"title": "Test 4"}
}
}
}

View File

@ -1 +0,0 @@
{"title": "Test 2"}

View File

@ -0,0 +1,3 @@
<h1>Test3.html</h1>
<p>{@link Test}</p>

View File

@ -0,0 +1 @@
# test4.md

View File

@ -0,0 +1 @@
Should not be included as a tutorial.

View File

@ -0,0 +1 @@
<h1>test 6 - has no metadata</h1>