UNPKG

funcunit

Version:
510 lines (428 loc) 14 kB
var process = require("./process"), tnd = require("../tags/helpers/typeNameDescription"), getParent = require("../tags/helpers/getParent"), assert = require("assert"), tags = require("../tags/tags"), Handlebars = require("handlebars"), finalizeDocMap = require("./finalize_doc_map"), fs = require("fs"), path = require("path"); var propertyTag = { codeMatch: function( code ) { return code.match(/(\w+)\s*[:=]\s*/) && !code.match(/(\w+)\s*[:=]\s*function\(([^\)]*)/); }, code: function( code, scope, docMap ) { var parts = code.match(/(\w+)\s*[:=]\s*/); if ( parts ) { var parentAndName = getParent.andName({ parents: "*", useName: ["constructor","static","prototype","function"], scope: scope, docMap: docMap, name: parts[1] }); return { name: parentAndName.name, parent: parentAndName.parent, type: "property" }; } }, add: function(line, curData, scope, docMap){ var data = tnd(line); this.types = data.types this.description = data.description; var parentAndName = getParent.andName({ parents: "*", useName: ["constructor","static","prototype","function"], scope: scope, docMap: docMap, name: data.name }); this.name = parentAndName.name; this.parent = parentAndName.parent; this.type = "property"; }, parentTypes: ["constructor"], useName: true }; describe("documentjs/lib/process", function(){ describe(".comment", function(){ it("adds to parent",function(){ var docMap = {Foo: {name: "Foo",type: "constructor"}}; process.comment({ comment: "@property {Object} tags Tags for something", scope: docMap.Foo, docMap: docMap, docObject: {}, tags: {property: propertyTag} },function(newDoc, newScope){ assert.equal(newScope, docMap.Foo, "same scope scope"); assert.equal(newDoc.name, "Foo.tags"); }); }); it("change scope", function(){ var tags = { constructor: { add : function(){ this.name = "constructed"; this.type = "constructor"; return ["scope",this]; } }, parent: { add: function(){ this.parent = "parented" } }, property: propertyTag }; var docMap = {Foo: {name: "Foo",type: "constructor"}}, props = {}; process.comment({ comment: ["@constructor", "@parent tang"], scope: docMap.Foo, docMap: docMap, docObject: props, tags: tags },function(newDoc, newScope){ assert.equal(newDoc, newScope, "new doc item is new scope"); assert.equal(newDoc, props, "props is the new doc object"); assert.deepEqual(newDoc,{ name: "constructed", type: "constructor", parent: "parented", body: "", description: "" }); }); }); var example = { add: function(line){ return { lines: [] }; }, addMore: function(line, curData) { curData.lines.push(line); }, end: function( curData ){ this.body += "```\n"+curData.lines.join("\n")+"\n```\n"; } }; it("is able to end a current tag", function(){ var docMap = {Foo: {name: "Foo",type: "constructor"}}; process.comment({ comment: [ "@property {Object} tags Tags for something", "description", "", "body", "@example", "_.extend()", "@example", "_.clone()", "@body", "endbody" ].join("\n"), scope: docMap.Foo, docMap: docMap, docObject: {}, tags: { property: propertyTag, example: example, body: { add: function( line ) { return ["default","body"]; } } } },function(newDoc, newScope){ assert.equal(newDoc.body, '\nbody\n```\n_.extend()\n```\n```\n_.clone()\n```\nendbody\n'); }); }); it("ends a current tag that is the last tag",function(){ var docMap = {Foo: {name: "Foo",type: "constructor"}}; process.comment({ comment: [ "@property {Object} tags Tags for something", "description", "", "body", "@example", "_.extend()", ].join("\n"), scope: docMap.Foo, docMap: docMap, docObject: {}, tags: { property: propertyTag, example: example, body: { add: function( line ) { return ["default","body"]; } } } },function(newDoc, newScope){ assert.equal(newDoc.body, '\nbody\n```\n_.extend()\n```\n'); }); }); it("handles indentation", function(done){ fs.readFile( path.join(__dirname,"test","indentation.md"), function(err, content){ if(err) { return done(err); } var docMap = {}; process.comment({ comment: ""+content, scope: {}, docMap: docMap, docObject: {} },function(newDoc, newScope){ var options = newDoc.params[0].types[0].options, func = options[0].types[0], returns = func.returns; // return indentation assert.deepEqual(returns.types[0], {type:"Boolean"},"return indented inside function option"); assert.deepEqual(newDoc.returns.types[0], {type: "String"}, "not indented normal return still works"); // option var barOptions = options[1].types[0].options; assert.deepEqual(barOptions, [ {name: "first", types: [{type: "String"}], description: "\n"}, {name: "second", types: [{type: "String"}], description: "\n"} ]); // param assert.equal(func.params[0].description, "newName description.\n", "params in params"); // context / @this assert.equal(func.context.description,"An object\na\n", "a description"); done(); }); }); }); }); it(".code",function(){ var tags = { constructor: { codeMatch: /some constructor/, code: function(code, scope, objects){ return { type: "constructor", name: "Bar" }; }, codeScope: true }, property: propertyTag }; var docMap = {Foo: {name: "Foo",type: "constructor"}}; process.code({ code: "some constructor", docMap: docMap, scope: docMap.Foo, tags: tags }, function(constructorDoc, constructorScope){ assert.equal(constructorDoc, constructorScope, "scope is the constructor"); process.code({ code: "prop = 'something'", scope: constructorScope, docMap: docMap, tags: tags }, function(propDoc, propScope){ assert.equal(propScope, constructorScope, "prop doesn't change scope"); assert.equal(propDoc.name,"Bar.prop"); assert.equal(propDoc.parent,"Bar"); }); }); }); var makeDescription = function( comment, cb ){ var docMap = {Foo: {name: "Foo",type: "constructor"}}, props = {}; var tags = { constructor: { add : function(){ this.name = "constructed"; } } }; process.comment({ comment: comment, scope: docMap.Foo, docMap: docMap, docObject: props, tags: tags },cb); }; it("description",function(){ makeDescription( ["This is a description.", "Another line."], function(newDoc){ assert.equal(newDoc.description, "This is a description.\nAnother line.\n") }); }); it("description then body",function(){ makeDescription( ["This is a description.", "Another line.", "", "the body"], function(newDoc){ assert.equal(newDoc.description, "This is a description.\nAnother line.\n"); assert.equal(newDoc.body, "\nthe body\n"); }); }); // no longer works because @prototype is fixed, but not sure how to still errors this without creating // an evil tag /*it.only("process.file errors if name is changed", function(){ assert.throws(function(){ process.file("/** @constructor foo.bar *"+"/\n// \n/** @add foo.bar\n@prototype *"+"/",{},"foo.js"); }, function(e){ console.log(e); return e.message.indexOf("Changing name") >= 0; }); });*/ it("@prototype adds its own object", function(){ var docMap = {}; process.file("/** @constructor foo.bar *"+"/\n// \n/** @add foo.bar\n@prototype *"+"/",docMap,"foo.js"); assert.ok(docMap["foo.bar"], "foo.bar exists"); assert.ok(docMap["foo.bar.prototype"], "foo.bar.prototype exists"); }); it("processing mustache files", function(){ var docMap = {}; var originalRenderer = function(){}; originalRenderer.layout = function(data){ return data.content; }; originalRenderer.Handlebars =Handlebars; process.file("{{name}}",docMap,"foo.mustache"); assert.ok(docMap.foo.renderer, "got renderer"); var result = docMap.foo.renderer(docMap.foo, originalRenderer); assert.equal(result,"foo", "got back holler"); }); it("end is not called twice", function(){ var docMap = {}; var timesCalled = 0; tags.foo = {done: function(){ timesCalled++; }}; process.file("/** @constructor foo.bar *"+"/\n// \n/** @add foo.bar *"+"/",docMap,"foo.js"); assert.equal(timesCalled, 0, "done should only be called at the end"); finalizeDocMap(docMap,tags); assert.equal(timesCalled, 1, "done should only be called at the end"); }); it("can document a module with multiple exports", function(done){ fs.readFile(path.join(__dirname,"test","module_with_multiple_exports.js"), function(err, data){ if(err) { return done(err); } var docMap = {}; process.file(""+data,docMap,"utils/math.js"); assert.ok(docMap["utils/math"], "got the module"); assert.ok(docMap["utils/math.sum"], "got the sum docObject"); assert.ok(docMap["utils/math.constants"], "got the constants docObject"); done(); }); }); it("can document a module that exports a single function", function(done){ fs.readFile(path.join(__dirname,"test","module_with_single_export_function.js"), function(err, data){ if(err) { return done(err); } var docMap = {}; process.file(""+data,docMap,"utils/add.js"); assert.equal(docMap["utils/add"].types[0].params[0].name, "first", "got a param"); assert.equal(docMap["utils/add"].types[0].params[1].name, "second", "got a param"); done(); }); }); it("@function and @property assumes a parent name", function(done){ fs.readFile(path.join(__dirname,"test","function_assumes_parent_name.js"), function(err, data){ if(err) { return done(err); } var docMap = {}; process.file(""+data,docMap,"utils/date-helpers.js"); //console.log(docMap); assert.ok(docMap["util/date-helpers"], "date-helpers object"); assert.ok(docMap["util/date-helpers.isTomorrow"], "util/date-helpers.isTomorrow object"); assert.ok(docMap["util/date-helpers.isYesterday"], "util/date-helpers.isYesterday object"); assert.ok(docMap["util/date-helpers.isNext"], "util/date-helpers.isNext object"); assert.ok(docMap["util/date-helpers.tomorrow"], "util/date-helpers.tomorrow object"); assert.ok(docMap["util/date-helpers.yesterday"], "util/date-helpers.yesterday object"); assert.ok(docMap["util/date-helpers.next"], "util/date-helpers.next object"); // assert parents assert.equal(docMap["util/date-helpers.isTomorrow"].parent ,"util/date-helpers", "util/date-helpers.isTomorrow parent"); assert.equal(docMap["util/date-helpers.isYesterday"].parent ,"util/date-helpers", "util/date-helpers.isYesterday parent"); assert.equal(docMap["util/date-helpers.isNext"].parent ,"util/date-helpers", "util/date-helpers.isNext parent"); assert.equal(docMap["util/date-helpers.tomorrow"].parent ,"util/date-helpers", "util/date-helpers.tomorrow parent"); assert.equal(docMap["util/date-helpers.yesterday"].parent ,"util/date-helpers", "util/date-helpers.yesterday parent"); assert.equal(docMap["util/date-helpers.next"].parent ,"util/date-helpers", "util/date-helpers.next parent"); done(); }); }); it("process.getComments is able to get a comment directly after another comment (#62)", function(done){ fs.readFile(path.join(__dirname,"test","comment_after_comment.js"), function(err, data){ if(err) { return done(err); } var result = process.getComments(""+data); assert.deepEqual([ { comment: ["a",""], code: "", line: 0}, { comment: ["b",""], code: "", line: 3}, { comment: ["c "], code: "", line: 6}, { comment: ["d",""], code: 'foo = "bar";', line: 8, codeLine: 11}, { comment: ["e",""], code: '', line: 12} ], result); done(); }); }); it("process.file provides filename and line if available to tags", function(done){ var count = 0; tags.filetest = { add: function(line, curData, scope, docMap){ this.type = "filetest"; this.name ="filetest"+(++count); assert.ok(this.src,"a src"); assert.equal(typeof this.line, "number","a line"); } }; fs.readFile(path.join(__dirname,"test","filename_and_line.js"), function(err, data){ if(err) { return done(err); } var docMap = {}; process.file(""+data,docMap,"utils/date-helpers.js"); done(); }); }); it(".code keeps options.docObject's src and line", function(done){ var count = 0; tags.filetest = { add: function(line, curData, scope, docMap){ this.type = "filetest"; this.name ="filetest"+(++count); assert.ok(this.src,"a src"); assert.ok(this.codeLine, "got the codeLine"); assert.equal(typeof this.line, "number","a line"); }, codeMatch: function( code ) { return true; }, code: function( code, scope, docMap ) { return { type: "filetest" }; } }; fs.readFile(path.join(__dirname,"test","filename_and_line.js"), function(err, data){ if(err) { return done(err); } var docMap = {}; process.file(""+data,docMap,"utils/date-helpers.js"); done(); }); }); });