diff --git a/lib/jsdoc/tutorial/resolver.js b/lib/jsdoc/tutorial/resolver.js index 0a54bbb2..2314c86e 100644 --- a/lib/jsdoc/tutorial/resolver.js +++ b/lib/jsdoc/tutorial/resolver.js @@ -97,7 +97,7 @@ exports.root = new tutorial.Tutorial('', ''); @return {tutorial.Tutorial} Tutorial instance. */ exports.root.getByName = function(name) { - return tutorials[name]; + return hasOwnProp.call(tutorials, name) && tutorials[name]; }; /** Load tutorials from given path. @@ -162,7 +162,7 @@ exports.resolve = function() { for (var name in conf) { if ( hasOwnProp.call(conf, name) ) { // TODO: should we complain about this? - if (!(name in tutorials)) { + if (!hasOwnProp.call(tutorials, name)) { continue; } @@ -177,7 +177,7 @@ exports.resolve = function() { // add children if (item.children) { item.children.forEach(function(child) { - if (!(child in tutorials)) { + if (!hasOwnProp.call(tutorials, child)) { error.handle( new Error("Missing child tutorial: " + child) ); } else { diff --git a/lib/jsdoc/util/templateHelper.js b/lib/jsdoc/util/templateHelper.js index ef036c1a..50d4dc2b 100644 --- a/lib/jsdoc/util/templateHelper.js +++ b/lib/jsdoc/util/templateHelper.js @@ -103,7 +103,7 @@ var longnameToUrl = exports.longnameToUrl = linkMap.longnameToUrl; var linkto = exports.linkto = function(longname, linktext, cssClass) { var classString = cssClass ? util.format(' class="%s"', cssClass) : ''; var text = linktext || longname; - var url = longnameToUrl[longname]; + var url = hasOwnProp.call(longnameToUrl, longname) && longnameToUrl[longname]; if (!url) { return text; @@ -358,7 +358,7 @@ function toLink(longname, content, monospace) { } else { // the actual longname is stored in `url` if there was a delimiter. - url = linkMap.longnameToUrl[longname]; + url = hasOwnProp.call(linkMap.longnameToUrl, longname) && linkMap.longnameToUrl[longname]; } content = content || longname; @@ -395,7 +395,7 @@ var tutorialToUrl = exports.tutorialToUrl = function(tutorial) { var url; // define the URL if necessary - if (!tutorialLinkMap.nameToUrl[node.name]) { + if (!hasOwnProp.call(tutorialLinkMap.nameToUrl, node.name)) { url = 'tutorial-' + getUniqueFilename(node.name); tutorialLinkMap.nameToUrl[node.name] = url; tutorialLinkMap.urlToName[url] = node.name; diff --git a/test/specs/jsdoc/tutorial/resolver.js b/test/specs/jsdoc/tutorial/resolver.js index 5d1260b0..5a4eef13 100644 --- a/test/specs/jsdoc/tutorial/resolver.js +++ b/test/specs/jsdoc/tutorial/resolver.js @@ -8,32 +8,32 @@ describe("jsdoc/tutorial/resolver", function() { /*jshint evil: true */ it("should exist", function() { expect(resolver).toBeDefined(); - expect(typeof resolver).toEqual('object'); + expect(typeof resolver).toBe('object'); }); it("should export a 'addTutorial' function", function() { expect(resolver.addTutorial).toBeDefined(); - expect(typeof resolver.addTutorial).toEqual("function"); + expect(typeof resolver.addTutorial).toBe("function"); }); it("should export a 'load' function", function() { expect(resolver.load).toBeDefined(); - expect(typeof resolver.load).toEqual("function"); + expect(typeof resolver.load).toBe("function"); }); it("should export a 'resolve' function", function() { expect(resolver.resolve).toBeDefined(); - expect(typeof resolver.resolve).toEqual("function"); + expect(typeof resolver.resolve).toBe("function"); }); it("should export a 'root' tutorial", function() { expect(resolver.root).toBeDefined(); - expect(resolver.root instanceof tutorial.Tutorial).toEqual(true); + expect(resolver.root instanceof tutorial.Tutorial).toBe(true); }); it("exported 'root' tutorial should export a 'getByName' function", function() { expect(resolver.root.getByName).toBeDefined(); - expect(typeof resolver.root.getByName).toEqual("function"); + expect(typeof resolver.root.getByName).toBe("function"); }); // note: every time we addTutorial or run the resolver, we are *adding* @@ -45,18 +45,26 @@ describe("jsdoc/tutorial/resolver", function() { describe("addTutorial", function() { it("should add a default parent of the root tutorial", function() { - expect(tute.parent).toEqual(resolver.root); + expect(tute.parent).toBe(resolver.root); }); it("should be added to the root tutorial as a child", function() { - expect(resolver.root.children[0]).toEqual(tute); + expect(resolver.root.children).toContain(tute); }); }); // root.getByName describe("root.getByName", function() { it("can retrieve tutorials by name", function() { - expect(resolver.root.getByName('myTutorial')).toEqual(tute); + expect(resolver.root.getByName('myTutorial')).toBe(tute); + }); + + it("returns nothing for non-existent tutorials", function() { + expect(resolver.root.getByName('asdf')).toBeFalsy(); + }); + + it("is careful with tutorials whose names are reserved keywords in JS", function() { + expect(resolver.root.getByName('prototype')).toBeFalsy(); }); }); @@ -66,8 +74,9 @@ describe("jsdoc/tutorial/resolver", function() { 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'); + test4 = resolver.root.getByName('test4'), + test6 = resolver.root.getByName('test6'), + constr = resolver.root.getByName('constructor'); describe("load", function() { @@ -78,33 +87,39 @@ describe("jsdoc/tutorial/resolver", function() { expect(test3).toBeDefined(); expect(test4).toBeDefined(); expect(test6).toBeDefined(); + expect(constr).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); + expect(childNames).toContain('test'); + expect(childNames).toContain('test2'); + expect(childNames).toContain('test3'); + expect(childNames).toContain('test4'); + expect(childNames).toContain('test6'); + }); + + it("tutorials with names equal to reserved keywords in JS still function as expected", function() { + expect(constr instanceof tutorial.Tutorial).toBe(true); }); it("non-tutorials are skipped", function() { - expect(resolver.root.getByName('multple')).toBeUndefined(); - expect(resolver.root.getByName('test5')).toBeUndefined(); + expect(resolver.root.getByName('multiple')).toBeFalsy(); + expect(resolver.root.getByName('test5')).toBeFalsy(); }); - 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); + expect(test.type).toBe(tutorial.TYPES.HTML); + expect(test2.type).toBe(tutorial.TYPES.MARKDOWN); + expect(test3.type).toBe(tutorial.TYPES.HTML); + expect(test4.type).toBe(tutorial.TYPES.MARKDOWN); + expect(test6.type).toBe(tutorial.TYPES.HTML); + expect(constr.type).toBe(tutorial.TYPES.MARKDOWN); }); }); // resolve // myTutorial + // constructor // test // |- test2 // |- test6 @@ -114,42 +129,44 @@ describe("jsdoc/tutorial/resolver", 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); + expect(resolver.root.children.length).toBe(3); + expect(resolver.root.children).toContain(test); + expect(resolver.root.children).toContain(constr); + expect(test.parent).toBe(resolver.root); + expect(constr.parent).toBe(resolver.root); // test has child 'test2' - expect(test.children.length).toEqual(1); - expect(test.children[0]).toEqual(test2); - expect(test2.parent).toEqual(test); + expect(test.children.length).toBe(1); + expect(test.children).toContain(test2); + expect(test2.parent).toBe(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); + expect(test2.children.length).toBe(2); + expect(test2.children).toContain(test3); + expect(test2.children).toContain(test6); + expect(test3.parent).toBe(test2); + expect(test6.parent).toBe(test2); // test3 has child test4 - expect(test3.children.length).toEqual(1); - expect(test3.children[0]).toEqual(test4); - expect(test4.parent).toEqual(test3); + expect(test3.children.length).toBe(1); + expect(test3.children).toContain(test4); + expect(test4.parent).toBe(test3); }); it("tutorials without configuration files have titles matching filenames", function() { // test6.xml didn't have a metadata - expect(test6.title).toEqual('test6'); + expect(test6.title).toBe('test6'); }); - it("tutorials with configuration files have titles matching filenames", function() { + it("tutorials with configuration files have titles as specified in configuration", function() { // test.json had info for just test.json - expect(test.title).toEqual("Test tutorial"); + expect(test.title).toBe("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"); + expect(test2.title).toBe("Test 2"); + expect(test3.title).toBe("Test 3"); + expect(test4.title).toBe("Test 4"); }); }); diff --git a/test/specs/jsdoc/util/doop.js b/test/specs/jsdoc/util/doop.js index c0f53292..217303de 100644 --- a/test/specs/jsdoc/util/doop.js +++ b/test/specs/jsdoc/util/doop.js @@ -1,4 +1,73 @@ /*global describe: true, it: true */ -describe("jsdoc/util/doop", function() { - // TODO -}); \ No newline at end of file +describe('jsdoc/util/doop', function() { + var doop = require('jsdoc/util/doop'); + + it('should exist', function() { + expect(doop).toBeDefined(); + expect(typeof doop).toBe('object'); + }); + + it('should export a doop function', function() { + expect(doop.doop).toBeDefined(); + expect(typeof doop.doop).toBe('function'); + }); + + // deep-clones a simple object. + describe('doop', function() { + it("should return the input object if it's simple (boolan, string etc) or a function", function() { + // .toBe uses === to test. + + // test a number... + expect(doop.doop(3)).toBe(3); + // test a string... + expect(doop.doop('asdf')).toBe('asdf'); + // test a boolean... + expect(doop.doop(true)).toBe(true); + // test a function... + var f = function () {}; + expect(doop.doop(f)).toBe(f); + }); + + it("should return a clone of an array", function() { + var inp = [1,2,3], + out = doop.doop(inp); + // toEqual is a comparison on properties; toBe is === comparison. + expect(inp).toEqual(out); + expect(inp).not.toBe(out); + }); + + it("should return a clone of an object", function() { + var inp = {a:1, b:2, 'asdf-fdsa': 3}; + out = doop.doop(inp); + // toEqual is a comparison on properties; toBe is === comparison. + expect(inp).toEqual(out); + expect(inp).not.toBe(out); + }); + + // checks that a === b if it's not an object or array (or it's af function); + // otherwise recurses down into keys and compares them. + function compareForEquality(a, b) { + if (a instanceof Object && a.constructor != Function) { + // if it's an object and not a function, it should clone. + var keys = Object.keys(a); + expect(Object.keys(a)).toEqual(Object.keys(b)); + for (var i = 0; i < keys.length; ++i) { + compareForEquality(a[keys[i]], b[keys[i]]); + } + } else { + // otherwise, it should be exactly equal. + expect(a).toBe(b); + } + } + + it("should clone recursively", function() { + var inp = {a:1, b:2, 'asdf-fdsa': {a: 'fdsa', b: [1,2,3]}}; + out = doop.doop(inp); + // toEqual is a comparison on properties; toBe is === comparison. + expect(inp).toEqual(out); + expect(inp).not.toBe(out); + // double-check + compareForEquality(inp, out); + }); + }); +}); diff --git a/test/specs/jsdoc/util/error.js b/test/specs/jsdoc/util/error.js index 4d0a8981..4b77e789 100644 --- a/test/specs/jsdoc/util/error.js +++ b/test/specs/jsdoc/util/error.js @@ -15,8 +15,7 @@ describe("jsdoc/util/error", function() { describe("handle", function() { /*jshint evil: true */ - var lenient = !!env.opts.lenient, - log = eval(console.log); + var lenient = !!env.opts.lenient; function handleError() { handle( new Error("foo") ); @@ -28,7 +27,6 @@ describe("jsdoc/util/error", function() { afterEach(function() { env.opts.lenient = lenient; - console.log = log; }); it("should re-throw errors by default", function() { @@ -43,7 +41,7 @@ describe("jsdoc/util/error", function() { it("should not re-throw errors if lenient mode is enabled", function() { env.opts.lenient = true; - console.log = function() {}; + spyOn(console, 'log'); expect(handleError).not.toThrow(); }); diff --git a/test/specs/jsdoc/util/templateHelper.js b/test/specs/jsdoc/util/templateHelper.js index 54bd9a55..56b235c8 100644 --- a/test/specs/jsdoc/util/templateHelper.js +++ b/test/specs/jsdoc/util/templateHelper.js @@ -2,139 +2,168 @@ var hasOwnProp = Object.prototype.hasOwnProperty; describe("jsdoc/util/templateHelper", function() { - var helper = require('jsdoc/util/templateHelper'); + var helper = require('jsdoc/util/templateHelper'), + doclet = require('jsdoc/doclet'), + resolver = require('jsdoc/tutorial/resolver'); helper.registerLink('test', 'path/to/test.html'); - helper.registerLink('test."long blah"/blah', 'path/to/test_long_blah_blah.html'); it("should exist", function() { expect(helper).toBeDefined(); - expect(typeof helper).toEqual('object'); + expect(typeof helper).toBe('object'); }); it("should export a 'setTutorials' function", function() { expect(helper.setTutorials).toBeDefined(); - expect(typeof helper.setTutorials).toEqual("function"); + expect(typeof helper.setTutorials).toBe("function"); }); it("should export a 'globalName' property", function() { expect(helper.globalName).toBeDefined(); - expect(typeof helper.globalName).toEqual("string"); + expect(typeof helper.globalName).toBe("string"); }); it("should export a 'fileExtension' property", function() { expect(helper.fileExtension).toBeDefined(); - expect(typeof helper.fileExtension).toEqual("string"); + expect(typeof helper.fileExtension).toBe("string"); }); it("should export a 'scopeToPunc' property", function() { expect(helper.scopeToPunc).toBeDefined(); - expect(typeof helper.scopeToPunc).toEqual("object"); + expect(typeof helper.scopeToPunc).toBe("object"); }); it("should export a 'getUniqueFilename' function", function() { expect(helper.getUniqueFilename).toBeDefined(); - expect(typeof helper.getUniqueFilename).toEqual("function"); + expect(typeof helper.getUniqueFilename).toBe("function"); }); it("should export a 'longnameToUrl' property", function() { expect(helper.longnameToUrl).toBeDefined(); - expect(typeof helper.longnameToUrl).toEqual("object"); + expect(typeof helper.longnameToUrl).toBe("object"); }); it("should export a 'linkto' function", function() { expect(helper.linkto).toBeDefined(); - expect(typeof helper.linkto).toEqual("function"); + expect(typeof helper.linkto).toBe("function"); }); it("should export an 'htmlsafe' function", function() { expect(helper.htmlsafe).toBeDefined(); - expect(typeof helper.htmlsafe).toEqual("function"); + expect(typeof helper.htmlsafe).toBe("function"); }); it("should export a 'find' function", function() { expect(helper.find).toBeDefined(); - expect(typeof helper.find).toEqual("function"); + expect(typeof helper.find).toBe("function"); }); it("should export a 'getMembers' function", function() { expect(helper.getMembers).toBeDefined(); - expect(typeof helper.getMembers).toEqual("function"); + expect(typeof helper.getMembers).toBe("function"); }); it("should export a 'getAttribs' function", function() { expect(helper.getAttribs).toBeDefined(); - expect(typeof helper.getAttribs).toEqual("function"); + expect(typeof helper.getAttribs).toBe("function"); }); it("should export a 'getSignatureTypes' function", function() { expect(helper.getSignatureTypes).toBeDefined(); - expect(typeof helper.getSignatureTypes).toEqual("function"); + expect(typeof helper.getSignatureTypes).toBe("function"); }); it("should export a 'getSignatureParams' function", function() { expect(helper.getSignatureParams).toBeDefined(); - expect(typeof helper.getSignatureParams).toEqual("function"); + expect(typeof helper.getSignatureParams).toBe("function"); }); it("should export a 'getSignatureReturns' function", function() { expect(helper.getSignatureReturns).toBeDefined(); - expect(typeof helper.getSignatureReturns).toEqual("function"); + expect(typeof helper.getSignatureReturns).toBe("function"); }); it("should export a 'getAncestorLinks' function", function() { expect(helper.getAncestorLinks).toBeDefined(); - expect(typeof helper.getAncestorLinks).toEqual("function"); + expect(typeof helper.getAncestorLinks).toBe("function"); }); it("should export a 'prune' function", function() { expect(helper.prune).toBeDefined(); - expect(typeof helper.prune).toEqual("function"); + expect(typeof helper.prune).toBe("function"); }); it("should export a 'registerLink' function", function() { expect(helper.registerLink).toBeDefined(); - expect(typeof helper.registerLink).toEqual("function"); + expect(typeof helper.registerLink).toBe("function"); }); it("should export a 'tutorialToUrl' function", function() { expect(helper.tutorialToUrl).toBeDefined(); - expect(typeof helper.tutorialToUrl).toEqual("function"); + expect(typeof helper.tutorialToUrl).toBe("function"); }); it("should export a 'toTutorial' function", function() { expect(helper.toTutorial).toBeDefined(); - expect(typeof helper.toTutorial).toEqual("function"); + expect(typeof helper.toTutorial).toBe("function"); }); it("should export a 'resolveLinks' function", function() { expect(helper.resolveLinks).toBeDefined(); - expect(typeof helper.resolveLinks).toEqual("function"); + expect(typeof helper.resolveLinks).toBe("function"); + }); + + it("should export a 'resolveAuthorLinks' function", function() { + expect(helper.resolveAuthorLinks).toBeDefined(); + expect(typeof helper.resolveAuthorLinks).toBe("function"); }); it("should export a 'createLink' function", function() { expect(helper.createLink).toBeDefined(); - expect(typeof helper.createLink).toEqual("function"); + expect(typeof helper.createLink).toBe("function"); }); - xdescribe("setTutorials", function() { - // TODO + describe("setTutorials", function() { + // used in tutorialToUrl, toTutorial. + it("setting tutorials to null causes all tutorial lookups to fail", function() { + // bit of a dodgy test but the best I can manage. setTutorials doesn't do much. + helper.setTutorials(null); + // should throw error: no 'getByName' in tutorials. + expect(function () { return helper.tutorialToUrl('asdf') }).toThrow('Cannot call method "getByName" of null'); + }); + + it("setting tutorials to the root tutorial object lets lookups work", function() { + var lenient = !!env.opts.lenient; + spyOn(console, 'log'); + + // tutorial doesn't exist, we want to muffle that error + env.opts.lenient = true; + + helper.setTutorials(resolver.root); + spyOn(resolver.root, 'getByName'); + helper.tutorialToUrl('asdf'); + expect(resolver.root.getByName).toHaveBeenCalled(); + + env.opts.lenient = lenient; + }); }); describe("globalName", function() { it("should equal 'global'", function() { - expect(helper.globalName).toEqual('global'); + expect(helper.globalName).toBe('global'); }); }); describe("fileExtension", function() { it("should equal '.html'", function() { - expect(helper.fileExtension).toEqual('.html'); + expect(helper.fileExtension).toBe('.html'); }); }); - xdescribe("scopeToPunc", function() { - // TODO + describe("scopeToPunc", function() { + it("should map 'static' to '.', 'inner', to '~', 'instance' to '#'", function() { + expect(helper.scopeToPunc).toEqual({static: '.', inner: '~', instance: '#'}); + }); }); // disabled because Jasmine appears to execute this code twice, which causes getUniqueFilename @@ -144,7 +173,7 @@ describe("jsdoc/util/templateHelper", function() { // inner members) it('should convert a simple string into the string plus the default extension', function() { var filename = helper.getUniqueFilename('BackusNaur'); - expect(filename).toEqual('BackusNaur.html'); + expect(filename).toBe('BackusNaur.html'); }); it('should convert a string with slashes into the text following the last slash plus the default extension', function() { @@ -157,7 +186,7 @@ describe("jsdoc/util/templateHelper", function() { var filename1 = helper.getUniqueFilename(name); var filename2 = helper.getUniqueFilename(name); - expect(filename1).not.toEqual(filename2); + expect(filename1).not.toBe(filename2); }); it('should not consider the same name with different letter case to be unique', function() { @@ -166,12 +195,28 @@ describe("jsdoc/util/templateHelper", function() { var filename1 = helper.getUniqueFilename(camel); var filename2 = helper.getUniqueFilename(pascal); - expect( filename1.toLowerCase() ).not.toEqual( filename2.toLowerCase() ); + expect( filename1.toLowerCase() ).not.toBe( filename2.toLowerCase() ); }); }); - xdescribe("longnameToUrl", function() { - // TODO + describe("longnameToUrl", function() { + it("is an object", function() { + expect(typeof helper.longnameToUrl).toBe('object'); + }); + + it("has an entry added into it by calling registerLink", function() { + helper.registerLink('MySymbol', 'asdf.html'); + expect(helper.longnameToUrl.MySymbol).toBeDefined(); + expect(helper.longnameToUrl.MySymbol).toBe('asdf.html'); + + delete helper.longnameToUrl.MySymbol; + }); + + it("adding an entry to it allows me to link with linkto", function() { + helper.longnameToUrl.foo2 = 'bar.html'; + expect(helper.linkto('foo2')).toBe('foo2'); + delete helper.longnameToUrl.foo2; + }); }); describe("linkto", function() { @@ -185,38 +230,50 @@ describe("jsdoc/util/templateHelper", function() { it('returns the longname if only the longname is specified and has no URL', function() { var link = helper.linkto('example'); - expect(link).toEqual('example'); + expect(link).toBe('example'); }); it('returns the link text if only the link text is specified', function() { var link = helper.linkto(null, 'link text'); - expect(link).toEqual('link text'); + expect(link).toBe('link text'); }); it('returns the link text if the longname does not have a URL, and both the longname and ' + 'link text are specified', function() { var link = helper.linkto('example', 'link text'); - expect(link).toEqual('link text'); + expect(link).toBe('link text'); }); it('uses the longname as the link text if no link text is provided', function() { var link = helper.linkto('linktoTest'); - expect(link).toEqual('linktoTest'); + expect(link).toBe('linktoTest'); }); it('uses the link text if it is specified', function() { var link = helper.linkto('linktoTest', 'link text'); - expect(link).toEqual('link text'); + expect(link).toBe('link text'); }); it('includes a "class" attribute in the link if a class is specified', function() { var link = helper.linkto('linktoTest', 'link text', 'myclass'); - expect(link).toEqual('link text'); + expect(link).toBe('link text'); + }); + + it("is careful with longnames that are reserved words in JS", function() { + // we don't have a registered link for 'constructor' so it should return the text 'link text'. + var link = helper.linkto('constructor', 'link text'); + expect(typeof link).toBe('string'); + expect(link).toBe('link text'); }); }); - xdescribe("htmlsafe", function() { - // TODO + describe("htmlsafe", function() { + // turns < into < (doesn't do > or & etc...) + it('should convert all occurences of < to <', function() { + var inp = '

Potentially dangerous.

', + out = helper.htmlsafe(inp); + expect(out).toBe('<h1>Potentially dangerous.</h1>'); + }); }); describe("find", function() { @@ -348,20 +405,210 @@ describe("jsdoc/util/templateHelper", function() { }); }); - xdescribe("getAttribs", function() { - // TODO + describe("getAttribs", function() { + var doc, attribs; + + it('should return an array of strings', function() { + doc = new doclet.Doclet('/** ljklajsdf */', {}); + attribs = helper.getAttribs(doc); + expect(Array.isArray(attribs)).toBe(true); + }); + + // tests is an object of test[doclet src] = + // if false, we expect attribs to either not contain anything in whatNotToContain, + // or be empty (if whatNotToContain was not provided). + function doTests(tests, whatNotToContain) { + for (var src in tests) { + if (tests.hasOwnProperty(src)) { + doc = new doclet.Doclet('/** ' + src + ' */', {}); + attribs = helper.getAttribs(doc); + if (tests[src]) { + expect(attribs).toContain(tests[src]); + } else { + if (whatNotToContain !== undefined) { + if (Array.isArray(whatNotToContain)) { + for (var i = 0; i < whatNotToContain.length; ++i) { + expect(attribs).not.toContain(whatNotToContain[i]); + } + } + } else { + expect(attribs.length).toBe(0); + } + } + } + } + } + + it('should detect if a doclet is virtual', function() { + var tests = { + 'My constant. \n @virtual': 'virtual', + 'asdf': false + }; + doTests(tests); + }); + + it("should detect if a doclet's access is not public", function() { + var tests = {'@private': 'private', + '@access private': 'private', + '@protected': 'protected', + '@access protected': 'protected', + '@public': false, + '@access public': false, + 'asdf': false + }; + doTests(tests); + }); + + it("should detect if a doclet's scope is inner or static AND it is a function or member or constant", function() { + var tests = { + // by default these are members + '@inner': 'inner', + '@instance': false, + '@global': false, + '@static': 'static', + '@name Asdf.fdsa': 'static', + '@name Outer~inner': 'inner', + '@name Fdsa#asdf': false, + '@name .log': false, + // some tests with functions and constants + '@const Asdf#FOO': false, + '@const Asdf\n@inner': 'inner', + '@function Asdf#myFunction': false, + '@function Fdsa.MyFunction': 'static', + '@function Fdsa': false, + // these are not functions or members or constants, they should not have their scope recorded. + '@namespace Fdsa\n@inner': false, + '@class asdf': false + }; + doTests(tests, ['inner', 'static', 'global', 'instance']); + }); + + it("should detect if a doclet is readonly (and its kind is 'member')", function() { + var tests = { + 'asdf\n @readonly': 'readonly', + 'asdf': false, + '@name Fdsa#foo\n@readonly': 'readonly', + // kind is not 'member'. + '@const asdf\n@readonly': false, + '@function asdf\n@readonly': false, + '@function Asdf#bar\n@readonly': false + }; + doTests(tests, 'readonly'); + }); + + it("should detect if the doclet is a for constant", function() { + var tests = { + 'Enum. @enum\n@constant': 'constant', + '@function Foo#BAR\n@const': 'constant', + '@const Asdf': 'constant' + }; + doTests(tests, 'constant'); + }); + + it("should detect multiple attributes", function() { + var doc = new doclet.Doclet('/** @const module:fdsa~FOO\n@readonly\n@private */', {}); + attribs = helper.getAttribs(doc); + expect(attribs).toContain('private'); + //expect(attribs).toContain('readonly'); // kind is 'constant' not 'member'. + expect(attribs).toContain('constant'); + expect(attribs).toContain('inner'); + }); }); - xdescribe("getSignatureTypes", function() { - // TODO + describe("getSignatureTypes", function() { + // returns links to allowed types for a doclet. + it("returns an empty array if the doclet has no specified type", function() { + var doc = new doclet.Doclet('/** @const ASDF */', {}), + types = helper.getSignatureTypes(doc); + + expect(Array.isArray(types)).toBe(true); + expect(types.length).toBe(0); + }); + + it("returns a string array of the doclet's types", function() { + var doc = new doclet.Doclet('/** @const {number|Array.} ASDF */', {}), + types = helper.getSignatureTypes(doc); + + expect(types.length).toBe(2); + expect(types).toContain('number'); + expect(types).toContain(helper.htmlsafe('Array.')); // should be HTML safe + }); + + it("creates links for types if relevant", function() { + // make some links. + helper.longnameToUrl.MyClass = 'MyClass.html'; + + var doc = new doclet.Doclet('/** @const {MyClass} ASDF */', {}), + types = helper.getSignatureTypes(doc); + expect(types.length).toBe(1); + expect(types).toContain('MyClass'); + + delete helper.longnameToUrl.MyClass; + }); + + it("uses the cssClass parameter for links if it is provided", function() { + // make some links. + helper.longnameToUrl.MyClass = 'MyClass.html'; + + var doc = new doclet.Doclet('/** @const {MyClass} ASDF */', {}), + types = helper.getSignatureTypes(doc, 'myCSSClass'); + expect(types.length).toBe(1); + expect(types).toContain('MyClass'); + + delete helper.longnameToUrl.MyClass; + }); }); - xdescribe("getSignatureParams", function() { - // TODO + describe("getSignatureParams", function() { + // retrieves parameter names. + // if css class is provided, optional parameters are wrapped in a with that class. + it("returns an empty array if the doclet has no specified type", function() { + var doc = new doclet.Doclet('/** @function myFunction */', {}), + params = helper.getSignatureParams(doc); + expect(Array.isArray(params)).toBe(true); + expect(params.length).toBe(0); + }); + + it("returns a string array of the doclet's parameter names", function() { + var doc = new doclet.Doclet('/** @function myFunction\n @param {string} foo - asdf. */', {}), + params = helper.getSignatureParams(doc); + expect(params.length).toBe(1); + expect(params).toContain('foo'); + }); + + it("wraps optional parameters in if optClass is provided", function() { + var doc = new doclet.Doclet( + '/** @function myFunction\n' + + ' * @param {boolean} foo - explanation.\n' + + ' * @param {number} [bar=1] - another explanation.\n' + + ' * @param {string} [baz] - another explanation.\n' + + ' */', {}), + params = helper.getSignatureParams(doc, 'cssClass'); + + expect(params.length).toBe(3); + expect(params).toContain('foo'); + expect(params).toContain('bar'); + expect(params).toContain('baz'); + }); + + it("doesn't wrap optional parameters in if optClass is not provided", function() { + var doc = new doclet.Doclet( + '/** @function myFunction\n' + + ' * @param {boolean} foo - explanation.\n' + + ' * @param {number} [bar=1] - another explanation.\n' + + ' * @param {string} [baz] - another explanation.\n' + + ' */', {}), + params = helper.getSignatureParams(doc); + + expect(params.length).toBe(3); + expect(params).toContain('foo'); + expect(params).toContain('bar'); + expect(params).toContain('baz'); + }); }); describe("getSignatureReturns", function() { - // TODO: more tests + // retrieves links to types that the member can return. it("returns a value with correctly escaped HTML", function() { var mockDoclet = { @@ -377,13 +624,116 @@ describe("jsdoc/util/templateHelper", function() { }; var html = helper.getSignatureReturns(mockDoclet); - expect( html.indexOf('Array.') ).toEqual(-1); - expect( html.indexOf('Array.<string>') ).toBeGreaterThan(-1); + expect(html).not.toContain('Array.'); + expect(html).toContain('Array.<string>'); + }); + + it("returns an empty array if the doclet has no returns", function() { + var doc = new doclet.Doclet('/** @function myFunction */', {}), + returns = helper.getSignatureReturns(doc); + + expect(Array.isArray(returns)).toBe(true); + expect(returns.length).toBe(0); + }); + + it("returns an empty array if the doclet has @returns but with no type", function() { + var doc = new doclet.Doclet('/** @function myFunction\n@returns an interesting result.*/', {}), + returns = helper.getSignatureReturns(doc); + + expect(Array.isArray(returns)).toBe(true); + expect(returns.length).toBe(0); + }); + + it("creates links for return types if relevant", function() { + // make some links. + helper.longnameToUrl.MyClass = 'MyClass.html'; + + var doc = new doclet.Doclet('/** @function myFunction\n@returns {number|MyClass} an interesting result.*/', {}), + returns = helper.getSignatureReturns(doc); + + expect(returns.length).toBe(2); + expect(returns).toContain('MyClass'); + expect(returns).toContain('number'); + + delete helper.longnameToUrl.MyClass; + }); + + it("uses the cssClass parameter for links if it is provided", function() { + // make some links. + helper.longnameToUrl.MyClass = 'MyClass.html'; + + var doc = new doclet.Doclet('/** @function myFunction\n@returns {number|MyClass} an interesting result.*/', {}), + returns = helper.getSignatureReturns(doc, 'myCssClass'); + + expect(returns.length).toBe(2); + expect(returns).toContain('MyClass'); + expect(returns).toContain('number'); + + delete helper.longnameToUrl.MyClass; }); }); - xdescribe("getAncestorLinks", function() { - // TODO + describe("getAncestorLinks", function() { + // make a hierarchy. + var lackeys = new doclet.Doclet('/** @member lackeys\n@memberof module:mafia/gangs.Sharks~Henchman\n@instance*/', {}), + henchman = new doclet.Doclet('/** @class Henchman\n@memberof module:mafia/gangs.Sharks\n@inner */', {}), + gang = new doclet.Doclet('/** @namespace module:mafia/gangs.Sharks */', {}), + mafia = new doclet.Doclet('/** @module mafia/gangs */', {}), + data = require('taffydb').taffy([lackeys, henchman, gang, mafia]); + + // register some links + it("returns an empty array if there are no ancestors", function() { + var links = helper.getAncestorLinks(data, mafia); + expect(Array.isArray(links)).toBe(true); + expect(links.length).toBe(0); + }); + + it("returns an array of ancestor names (with preceding punctuation) if there are ancestors, the direct ancestor with following punctuation too", function() { + var links = helper.getAncestorLinks(data, lackeys); + expect(links.length).toBe(3); + expect(links).toContain('~Henchman#'); + expect(links).toContain('.Sharks'); + expect(links).toContain('mafia/gangs'); + + links = helper.getAncestorLinks(data, henchman); + expect(links.length).toBe(2); + expect(links).toContain('.Sharks~'); + expect(links).toContain('mafia/gangs'); + + links = helper.getAncestorLinks(data, gang); + expect(links.length).toBe(1); + expect(links).toContain('mafia/gangs.'); + }); + + it("adds links if they exist", function() { + // register some links + helper.longnameToUrl['module:mafia/gangs'] = 'mafia_gangs.html'; + helper.longnameToUrl['module:mafia/gangs.Sharks~Henchman'] = 'henchman.html'; + + var links = helper.getAncestorLinks(data, lackeys); + expect(links.length).toBe(3); + expect(links).toContain('~Henchman#'); + expect(links).toContain('.Sharks'); + expect(links).toContain('mafia/gangs'); + + delete helper.longnameToUrl['module:mafia/gangs']; + delete helper.longnameToUrl['module:mafia/gangs.Sharks~Henchman']; + }); + + it("adds cssClass to any link", function() { + // register some links + helper.longnameToUrl['module:mafia/gangs'] = 'mafia_gangs.html'; + helper.longnameToUrl['module:mafia/gangs.Sharks~Henchman'] = 'henchman.html'; + + var links = helper.getAncestorLinks(data, lackeys, 'myClass'); + expect(links.length).toBe(3); + expect(links).toContain('~Henchman#'); + expect(links).toContain('.Sharks'); + expect(links).toContain('mafia/gangs'); + + delete helper.longnameToUrl['module:mafia/gangs']; + delete helper.longnameToUrl['module:mafia/gangs.Sharks~Henchman']; + }); }); describe("prune", function() { @@ -436,31 +786,42 @@ describe("jsdoc/util/templateHelper", function() { }); }); - xdescribe("registerLink", function() { - // TODO + describe("registerLink", function() { + it("adds an entry to exports.longnameToUrl", function() { + helper.longnameToUrl.MySymbol = 'asdf.html'; + + expect(helper.longnameToUrl.MySymbol).toBeDefined(); + expect(helper.longnameToUrl.MySymbol).toBe('asdf.html'); + + delete helper.longnameToUrl.MySymbol; + }); + + it("allows linkto to work", function() { + helper.registerLink('MySymbol', 'asdf.html'); + + expect(helper.linkto('MySymbol')).toBe('MySymbol'); + + delete helper.longnameToUrl.MySymbol; + }); }); describe("tutorialToUrl", function() { /*jshint evil: true */ - // TODO: more tests - - var lenient = !!env.opts.lenient, - log = eval(console.log); + var lenient = !!env.opts.lenient; function missingTutorial() { var url = helper.tutorialToUrl("be-a-perfect-person-in-just-three-days"); } beforeEach(function() { - var root = require('jsdoc/tutorial/resolver').root; - helper.setTutorials(root); + spyOn(console, 'log'); + helper.setTutorials(resolver.root); }); afterEach(function() { helper.setTutorials(null); env.opts.lenient = lenient; - console.log = log; }); it('throws an exception if the tutorial is missing and the lenient option is not enabled', function() { @@ -469,20 +830,46 @@ describe("jsdoc/util/templateHelper", function() { }); it('does not throw an exception if the tutorial is missing and the lenient option is enabled', function() { - console.log = function() {}; env.opts.lenient = true; expect(missingTutorial).not.toThrow(); }); + + it("does not return a tutorial if its name is a reserved JS keyword and it doesn't exist", function() { + env.opts.lenient = false; + expect(function () { helper.tutorialToUrl('prototype') }).toThrow(); + }); + + it("creates links to tutorials if they exist", function() { + // NOTE: we have to set lenient = true here because otherwise JSDoc will + // cry when trying to resolve the same set of tutorials twice (once + // for the tutorials tests, and once here). + env.opts.lenient = true; + + // load the tutorials we already have for the tutorials tests + resolver.load(__dirname + "/test/tutorials/tutorials"); + resolver.resolve(); + + var url = helper.tutorialToUrl('test'); + expect(typeof url).toBe('string'); + expect(url).toBe('tutorial-test.html'); + }); + + it("creates links for tutorials where the name is a reserved JS keyword", function() { + var url = helper.tutorialToUrl('constructor'); + expect(typeof url).toBe('string'); + expect(url).toBe('tutorial-constructor.html'); + }); + + it("returns the same link if called multiple times on the same tutorial", function() { + expect(helper.tutorialToUrl('test2')).toBe(helper.tutorialToUrl('test2')); + }); }); describe("toTutorial", function() { /*jshint evil: true */ - // TODO: more tests - - var lenient = !!env.opts.lenient, - log = eval(console.log); + var lenient = !!env.opts.lenient; function missingParam() { helper.toTutorial(); @@ -490,7 +877,11 @@ describe("jsdoc/util/templateHelper", function() { afterEach(function() { env.opts.lenient = lenient; - console.log = log; + helper.setTutorials(null); + }); + + beforeEach(function () { + helper.setTutorials(resolver.root); }); it('throws an exception if the first param is missing and the lenient option is not enabled', function() { @@ -500,11 +891,69 @@ describe("jsdoc/util/templateHelper", function() { }); it('does not throw an exception if the first param is missing and the lenient option is enabled', function() { - console.log = function() {}; + spyOn(console, 'log'); env.opts.lenient = true; expect(missingParam).not.toThrow(); }); + + // missing tutorials + it("returns the tutorial name if it's missing and no missingOpts is provided", function() { + helper.setTutorials(resolver.root); + var link = helper.toTutorial('asdf'); + expect(link).toBe('asdf'); + }); + + it("returns the tutorial name wrapped in missingOpts.tag if provided and the tutorial is missing", function() { + var link = helper.toTutorial('asdf', 'lkjklasdf', {tag: 'span'}); + expect(link).toBe('asdf'); + }); + + it("returns the tutorial name wrapped in missingOpts.tag with class missingOpts.classname if provided and the tutorial is missing", function() { + var link = helper.toTutorial('asdf', 'lkjklasdf', {classname: 'missing'}); + expect(link).toBe('asdf'); + + link = helper.toTutorial('asdf', 'lkjklasdf', {tag: 'span', classname: 'missing'}); + expect(link).toBe('asdf'); + }); + + it("prefixes the tutorial name with missingOpts.prefix if provided and the tutorial is missing", function() { + var link = helper.toTutorial('asdf', 'lkjklasdf', {tag: 'span', classname: 'missing', prefix: 'TODO-'}); + expect(link).toBe('TODO-asdf'); + + link = helper.toTutorial('asdf', 'lkjklasdf', {prefix: 'TODO-'}); + expect(link).toBe('TODO-asdf'); + + link = helper.toTutorial('asdf', 'lkjklasdf', {prefix: 'TODO-', classname: 'missing'}); + expect(link).toBe('TODO-asdf'); + }); + + // now we do non-missing tutorials. + it("returns a link to the tutorial if not missing", function() { + // NOTE: we have to set lenient = true here because otherwise JSDoc will + // cry when trying to resolve the same set of tutorials twice (once + // for the tutorials tests, and once here). + env.opts.lenient = true; + spyOn(console, 'log'); + + // load the tutorials we already have for the tutorials tests + resolver.load(__dirname + "/test/tutorials/tutorials"); + resolver.resolve(); + + + var link = helper.toTutorial('constructor', 'The Constructor tutorial'); + expect(link).toBe('The Constructor tutorial'); + }); + + it("uses the tutorial's title for the link text if no content parameter is provided", function() { + var link = helper.toTutorial('test'); + expect(link).toBe('Test tutorial'); + }); + + it("does not apply any of missingOpts if the tutorial was found", function() { + var link = helper.toTutorial('test', '', {tag: 'span', classname: 'missing', prefix: 'TODO-'}); + expect(link).toBe('Test tutorial'); + }); }); // couple of convenience functions letting me set conf variables and restore @@ -532,89 +981,95 @@ describe("jsdoc/util/templateHelper", function() { var input = 'This is a {@link test}.', output = helper.resolveLinks(input); - expect(output).toEqual('This is a test.'); + expect(output).toBe('This is a test.'); }); it('should translate {@link unknown} into a simple text.', function() { var input = 'This is a {@link unknown}.', output = helper.resolveLinks(input); - expect(output).toEqual('This is a unknown.'); + expect(output).toBe('This is a unknown.'); }); it('should translate {@link test} into a HTML links multiple times.', function() { var input = 'This is a {@link test} and {@link test}.', output = helper.resolveLinks(input); - expect(output).toEqual('This is a test and test.'); + expect(output).toBe('This is a test and test.'); }); it('should translate [hello there]{@link test} into a HTML link with the custom content.', function() { var input = 'This is a [hello there]{@link test}.', output = helper.resolveLinks(input); - expect(output).toEqual('This is a hello there.'); + expect(output).toBe('This is a hello there.'); }); it('should ignore [hello there].', function() { var input = 'This is a [hello there].', output = helper.resolveLinks(input); - expect(output).toEqual(input); + expect(output).toBe(input); }); it('should translate http links in the tag', function() { var input = 'Link to {@link http://github.com}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to http://github.com'); + expect(output).toBe('Link to http://github.com'); }); it('should translate ftp links in the tag', function() { var input = 'Link to {@link ftp://foo.bar}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to ftp://foo.bar'); + expect(output).toBe('Link to ftp://foo.bar'); }); it('should allow pipe to be used as delimiter between href and text (external link)', function() { var input = 'Link to {@link http://github.com|Github}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to Github'); + expect(output).toBe('Link to Github'); }); it('should allow pipe to be used as delimiter between href and text (symbol link)', function() { var input = 'Link to {@link test|Test}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to Test'); + expect(output).toBe('Link to Test'); }); it('should allow first space to be used as delimiter between href and text (external link)', function() { var input = 'Link to {@link http://github.com Github}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to Github'); + expect(output).toBe('Link to Github'); }); it('should allow first space to be used as delimiter between href and text (symbol link)', function() { var input = 'Link to {@link test My Caption}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to My Caption'); + expect(output).toBe('Link to My Caption'); }); it('if pipe and space are present in link tag, use pipe as the delimiter', function() { var input = 'Link to {@link test|My Caption}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to My Caption'); + expect(output).toBe('Link to My Caption'); }); it('Test of {@linkcode } which should be in monospace', function() { var input = 'Link to {@linkcode test}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to test'); + expect(output).toBe('Link to test'); }); it('Test of {@linkplain } which should be in normal font', function() { var input = 'Link to {@linkplain test}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to test'); + expect(output).toBe('Link to test'); + }); + + it('should be careful with linking to links whose names are reserved JS keywords', function() { + var input = 'Link to {@link constructor}', + output = helper.resolveLinks(input); + expect(output).toBe('Link to constructor'); }); // conf.monospaceLinks. check that @@ -623,7 +1078,7 @@ describe("jsdoc/util/templateHelper", function() { var storage = setConfTemplatesVariables({monospaceLinks: true}); var input = 'Link to {@link test}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to test'); + expect(output).toBe('Link to test'); restoreConfTemplates(storage); }); @@ -632,7 +1087,7 @@ describe("jsdoc/util/templateHelper", function() { var storage = setConfTemplatesVariables({monospaceLinks: true}); var input = 'Link to {@linkcode test}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to test'); + expect(output).toBe('Link to test'); restoreConfTemplates(storage); }); @@ -640,7 +1095,7 @@ describe("jsdoc/util/templateHelper", function() { var storage = setConfTemplatesVariables({monospaceLinks: true}); var input = 'Link to {@linkplain test}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to test'); + expect(output).toBe('Link to test'); restoreConfTemplates(storage); }); @@ -650,7 +1105,7 @@ describe("jsdoc/util/templateHelper", function() { var storage = setConfTemplatesVariables({cleverLinks: true}); var input = 'Link to {@link test}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to test'); + expect(output).toBe('Link to test'); restoreConfTemplates(storage); }); @@ -658,7 +1113,7 @@ describe("jsdoc/util/templateHelper", function() { var storage = setConfTemplatesVariables({cleverLinks: true}); var input = 'Link to {@link http://github.com}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to http://github.com'); + expect(output).toBe('Link to http://github.com'); restoreConfTemplates(storage); }); @@ -667,7 +1122,7 @@ describe("jsdoc/util/templateHelper", function() { var storage = setConfTemplatesVariables({cleverLinks: true}); var input = 'Link to {@linkcode test}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to test'); + expect(output).toBe('Link to test'); restoreConfTemplates(storage); }); @@ -675,7 +1130,7 @@ describe("jsdoc/util/templateHelper", function() { var storage = setConfTemplatesVariables({cleverLinks: true}); var input = 'Link to {@linkplain test}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to test'); + expect(output).toBe('Link to test'); restoreConfTemplates(storage); }); @@ -685,7 +1140,7 @@ describe("jsdoc/util/templateHelper", function() { var storage = setConfTemplatesVariables({cleverLinks: true, monospaceLinks: true}); var input = 'Link to {@link test} and {@link http://github.com}', output = helper.resolveLinks(input); - expect(output).toEqual('Link to test and http://github.com'); + expect(output).toBe('Link to test and http://github.com'); restoreConfTemplates(storage); }); @@ -700,7 +1155,7 @@ describe("jsdoc/util/templateHelper", function() { }, url = helper.createLink(mockDoclet); - expect(url).toEqual('global.html#foo'); + expect(url).toBe('global.html#foo'); }); it('should create a url for a namespace.', function() { @@ -711,7 +1166,7 @@ describe("jsdoc/util/templateHelper", function() { }, url = helper.createLink(mockDoclet); - expect(url).toEqual('foo.html'); + expect(url).toBe('foo.html'); }); it('should create a url for a member of a namespace.', function() { @@ -723,7 +1178,7 @@ describe("jsdoc/util/templateHelper", function() { }, url = helper.createLink(mockDoclet); - expect(url).toEqual('ns.html#foo'); + expect(url).toBe('ns.html#foo'); }); var nestedNamespaceDoclet = { @@ -737,12 +1192,12 @@ describe("jsdoc/util/templateHelper", function() { it('should create a url for a member of a nested namespace.', function() { nestedNamespaceUrl = helper.createLink(nestedNamespaceDoclet); - expect(nestedNamespaceUrl).toEqual('ns1.ns2.html#foo'); + expect(nestedNamespaceUrl).toBe('ns1.ns2.html#foo'); }); it('should return the same value when called twice with the same doclet.', function() { var newUrl = helper.createLink(nestedNamespaceDoclet); - expect(newUrl).toEqual(nestedNamespaceUrl); + expect(newUrl).toBe(nestedNamespaceUrl); }); it('should create a url for a name with invalid characters.', function() { @@ -757,4 +1212,25 @@ describe("jsdoc/util/templateHelper", function() { expect(url).toEqual('_.html#"*foo"'); }); }); + + describe("resolveAuthorLinks", function() { + // convert Jane Doe to a mailto link. + it('should convert email addresses in angle brackets *after* a name to mailto links', function() { + var str = ' John Doe ', + out = helper.resolveAuthorLinks(str); + expect(out).toBe('John Doe'); + }); + + it('should HTML-safe author names', function() { + var str = ' John ', + out = helper.resolveAuthorLinks(str); + expect(out).toBe('' + helper.htmlsafe('John'); + }); + + it('should simply return the input string, HTML-safe, if no email is detected', function() { + var str = 'John Doe ', + out = helper.resolveAuthorLinks(str); + expect(out).toBe(helper.htmlsafe(str)); + }); + }); }); diff --git a/test/tutorials/tutorials/constructor.md b/test/tutorials/tutorials/constructor.md new file mode 100644 index 00000000..2f181f08 --- /dev/null +++ b/test/tutorials/tutorials/constructor.md @@ -0,0 +1 @@ +This tutorial has a tricksy name to make sure we are not loading Array.constructor or Object.constructor.