var ngdoc = require('../src/ngdoc.js'); var DOM = require('../src/dom.js').DOM; var gruntUtil = require('../../lib/grunt/utils.js'); describe('ngdoc', function() { var Doc = ngdoc.Doc; var dom; beforeEach(function() { dom = new DOM(); this.addMatchers({ toContain: function(text) { this.actual = this.actual.toString(); return this.actual.indexOf(text) > -1; } }); }); describe('Doc', function() { describe('metadata', function() { it('should find keywords and filter ignored words', function() { expect(new Doc('\nHello: World! @ignore. $abc').keywords()).toEqual('$abc hello world'); expect(new Doc('The `ng:class-odd` and').keywords()).toEqual('ng:class-odd'); }); it('should get property and methods', function() { var doc = new Doc('Document'); doc.properties.push(new Doc('Proprety')); doc.properties.push(new Doc('Method')); expect(doc.keywords()).toEqual('document method proprety'); }); it('should have shortName', function() { var d1 = new Doc('@name a.b.c').parse(); var d2 = new Doc('@name a.b.ng-c').parse(); var d3 = new Doc('@name some text: more text').parse(); expect(ngdoc.metadata([d1])[0].shortName).toEqual('a.b.c'); expect(ngdoc.metadata([d2])[0].shortName).toEqual('a.b.ng-c'); expect(ngdoc.metadata([d3])[0].shortName).toEqual('more text'); }); }); describe('parse', function() { it('should convert @names into properties', function() { var doc = new Doc('\n@name name\n@desc\ndesc\ndesc2\n@dep\n'); doc.parse(); expect(doc.name).toEqual('name'); expect(doc.desc).toEqual('desc\ndesc2'); expect(doc.dep).toEqual(''); }); it('should parse parameters', function() { var doc = new Doc( '@name a\n' + '@param {*} a short\n' + '@param {Type} b med\n' + '@param {Class=} [c=2] long\nline\n' + '@param {function(number, string=)} d fn with optional arguments'); doc.parse(); expect(doc.param).toEqual([ {name:'a', description:'

short

\n
', type:'*', optional:false, 'default':undefined}, {name:'b', description:'

med

\n
', type:'Type', optional:false, 'default':undefined}, {name:'c', description:'

long\nline

\n
', type:'Class', optional:true, 'default':'2'}, {name:'d', description:'

fn with optional arguments

\n
', type: 'function(number, string=)', optional: false, 'default':undefined} ]); }); it('should parse return', function() { var doc = new Doc('@name a\n@returns {Type} text *bold*.'); doc.parse(); expect(doc.returns).toEqual({ type: 'Type', description: '

text bold.

\n
' }); }); it('should parse filename', function() { var doc = new Doc('@name friendly name', 'docs/a.b.ngdoc', 1); doc.parse(0); expect(doc.id).toEqual('a.b'); expect(doc.name).toEqual('friendly name'); }); it('should store all links', function() { var doc = new Doc('@name a\n@description {@link api/angular.link}'); doc.parse(); expect(doc.links).toContain('api/angular.link'); }); describe('convertUrlToAbsolute', function() { var doc; beforeEach(function() { doc = new Doc({section: 'section'}); }); it('should not change absolute url', function() { expect(doc.convertUrlToAbsolute('guide/index')).toEqual('guide/index'); }); it('should prepend current section to relative url', function() { expect(doc.convertUrlToAbsolute('angular.widget')).toEqual('section/angular.widget'); }); it('should change id to index if not specified', function() { expect(doc.convertUrlToAbsolute('guide/')).toEqual('guide/index'); }); }); describe('sorting', function() { function property(name) { return function(obj) {return obj[name];}; } var dev_guide_overview = new Doc({ngdoc:'overview', id:'dev_guide.overview', text: ''}); var dev_guide_bootstrap = new Doc({ngdoc:'function', id:'dev_guide.bootstrap', text: ''}); it('should put angular.fn() in front of dev_guide.overview, etc', function() { expect(ngdoc.metadata([dev_guide_overview, dev_guide_bootstrap]).map(property('id'))) .toEqual(['dev_guide.overview', 'dev_guide.bootstrap']); }); }); }); }); describe('markdown', function() { it('should not replace anything in
, but escape the html escape the content', function() {
      expect(new Doc().markdown('bah x\n
\nangular.k\n
\n asdf x')). toEqual( '

bah x\n' + '

\n' +
            '<b>angular</b>.k\n' +
            '
\n' + ' asdf x

\n
'); }); it('should wrap everything inside a container tag', function() { var doc = new Doc('@name superman').parse(); var content = doc.markdown('hello'); expect(content).toMatch('

hello

\n
'); }); it('should use the content before a colon as the name prefix for the className of the tag container', function() { var doc = new Doc('@name super: man').parse(); var content = doc.markdown('hello'); expect(content).toMatch('

hello

\n
'); }); it('should replace text between two
 tags', function() {
      expect(new Doc().markdown('
x
\n# One\n
b
')). toMatch('
\n

One

\n/); }); it('should ignore nested doc widgets', function() { expect(new Doc().markdown( 'before\n
\n' + '
' + '\ngit bla bla\n
\n' + '
')).toEqual( '

before

\n
\n' + '
\n' + 'git bla bla\n' + '
\n' + '
'); }); it('should unindent text before processing based on the second line', function() { expect(new Doc().markdown('first line\n' + ' second line\n\n' + ' third line\n' + ' fourth line\n\n' + ' fifth line')). toMatch('

first line\n' + 'second line

\n' + '
third line\n' +
                ' fourth line
\n' + '

fifth line

\n'); }); it('should unindent text before processing based on the first line', function() { expect(new Doc().markdown(' first line\n\n' + ' second line\n' + ' third line\n' + ' fourth line\n\n' + ' fifth line')). toMatch('

first line

\n' + '
second line\n' +
                'third line\n' +
                ' fourth line
\n' + '

fifth line

\n
'); }); describe('inline annotations', function() { it('should convert inline docs annotations into proper HTML', function() { expect(new Doc().markdown( "
\n//!annotate supertext\n
\n
" ) ).toContain('data-popover data-content="supertext"') }); it('should allow for a custom regular expression for matching', function() { expect(new Doc().markdown( "
\n//!annotate=\"soon\" supertext\n

soon

\n
" ) ).toContain('data-popover data-content="supertext" data-title="Info">soon') }); it('should allow for a custom title to be set', function() { expect(new Doc().markdown( "
\n//!annotate=\"soon\" coming soon|supertext\n

soon

\n
" ) ).toContain('data-popover data-content="supertext" data-title="coming soon">soon') }); }); }); describe('trim', function() { var trim = ngdoc.trim; it('should remove leading/trailing space', function() { expect(trim(' \nabc\n ')).toEqual('abc'); }); it('should remove leading space on every line', function() { expect(trim('\n 1\n 2\n 3\n')).toEqual('1\n 2\n 3'); }); }); describe('merge', function() { it('should merge child with parent', function() { var parent = new Doc({id: 'ng.abc', name: 'ng.abc', section: 'api'}); var methodA = new Doc({name: 'methodA', methodOf: 'ng.abc'}); var methodB = new Doc({name: 'methodB', methodOf: 'ng.abc'}); var propA = new Doc({name: 'propA', propertyOf: 'ng.abc'}); var propB = new Doc({name: 'propB', propertyOf: 'ng.abc'}); var eventA = new Doc({name: 'eventA', eventOf: 'ng.abc'}); var eventB = new Doc({name: 'eventB', eventOf: 'ng.abc'}); var docs = [methodB, methodA, eventB, eventA, propA, propB, parent]; // keep wrong order; ngdoc.merge(docs); expect(docs.length).toEqual(1); expect(docs[0].id).toEqual('ng.abc'); expect(docs[0].methods).toEqual([methodA, methodB]); expect(docs[0].events).toEqual([eventA, eventB]); expect(docs[0].properties).toEqual([propA, propB]); }); }); describe('checkBrokenLinks', function() { var docs; beforeEach(function() { spyOn(console, 'log'); docs = [new Doc({section: 'api', id: 'fake.id1', anchors: ['one']}), new Doc({section: 'api', id: 'fake.id2'}), new Doc({section: 'api', id: 'fake.id3'})]; }); it('should log warning when a linked page does not exist', function() { docs.push(new Doc({section: 'api', id: 'with-broken.link', links: ['non-existing-link']})) ngdoc.checkBrokenLinks(docs); expect(console.log).toHaveBeenCalled(); var warningMsg = console.log.argsForCall[0][0] expect(warningMsg).toContain('WARNING:'); expect(warningMsg).toContain('non-existing-link'); expect(warningMsg).toContain('api/with-broken.link'); }); it('should log warning when a linked anchor does not exist', function() { docs.push(new Doc({section: 'api', id: 'with-broken.link', links: ['api/fake.id1#non-existing']})) ngdoc.checkBrokenLinks(docs); expect(console.log).toHaveBeenCalled(); var warningMsg = console.log.argsForCall[0][0] expect(warningMsg).toContain('WARNING:'); expect(warningMsg).toContain('non-existing'); expect(warningMsg).toContain('api/with-broken.link'); }); }); describe('api section', function() { it('should render a "view source" button with link to the source in master', function() { var doc = new Doc({ id: 'ng.abc', name: 'ng.abc', section: 'api', ngdoc: 'service', file: 'fooService.js', line: '333' }); if (gruntUtil.getVersion().full.indexOf('-') === -1) { expect(doc.html().match(/^()/)[1]).toMatch( /

Number \nto format.

\n' }]); }); it('should parse with default and optional', function() { var doc = new Doc('@name a\n@param {(number|string)=} [fractionSize=2] desc'); doc.parse(); expect(doc.param).toEqual([{ type : '(number|string)', name : 'fractionSize', optional: true, 'default' : '2', description : '

desc

\n
' }]); }); }); describe('@requires', function() { it('should parse more @requires tag into array', function() { var doc = new Doc('@name a\n@requires $service for \n`A`\n@requires $another for `B`'); doc.ngdoc = 'service'; doc.parse(); expect(doc.requires).toEqual([ {name:'$service', text:'

for \nA

\n
'}, {name:'$another', text:'

for B

\n
'}]); expect(doc.html()).toContain('
$service'); expect(doc.html()).toContain('$another'); expect(doc.html()).toContain('

for \nA

'); expect(doc.html()).toContain('

for B

'); }); }); describe('@scope', function() { it('should state the new scope will be created', function() { var doc = new Doc('@name a\n@scope'); doc.ngdoc = 'directive'; doc.parse(); expect(doc.scope).toEqual(''); expect(doc.html()).toContain('This directive creates new scope.'); }); }); describe('@priority', function() { it('should state the priority', function() { var doc = new Doc('@name a\n@priority 123'); doc.ngdoc = 'directive'; doc.parse(); expect(doc.priority).toEqual('123'); expect(doc.html()).toContain('This directive executes at priority level 123.'); }); }); describe('@property', function() { it('should parse @property tags into array', function() { var doc = new Doc("@name a\n@property {type} name1 desc\n@property {type} name2 desc"); doc.parse(); expect(doc.properties.length).toEqual(2); }); it('should not parse @property without a type', function() { var doc = new Doc("@property fake", 'test.js', '44'); expect(function() { doc.parse(); }). toThrow(new Error("Not a valid 'property' format: fake (found in: test.js:44)")); }); it('should parse @property with type', function() { var doc = new Doc("@name a\n@property {string} name"); doc.parse(); expect(doc.properties[0].name).toEqual('name'); expect(doc.properties[0].type).toEqual('string'); }); it('should parse @property with optional description', function() { var doc = new Doc("@name a\n@property {string} name desc rip tion"); doc.parse(); expect(doc.properties[0].name).toEqual('name'); expect(doc.properties[0].description).toEqual('

desc rip tion

\n
'); }); it('should parse @property with type and description both', function() { var doc = new Doc("@name a\n@property {bool} name desc rip tion"); doc.parse(); expect(doc.properties[0].name).toEqual('name'); expect(doc.properties[0].type).toEqual('bool'); expect(doc.properties[0].description).toEqual('

desc rip tion

\n
'); }); }); describe('@returns', function() { it('should not parse @returns without type', function() { var doc = new Doc("@returns lala"); expect(function() { doc.parse(); }). toThrow(); }); it('should not parse @returns with invalid type', function() { var doc = new Doc("@returns {xx}x} lala", 'test.js', 34); expect(function() { doc.parse(); }). toThrow(new Error("Not a valid 'returns' format: {xx}x} lala (found in: test.js:34)")); }); it('should parse @returns with type and description', function() { var doc = new Doc("@name a\n@returns {string} descrip tion"); doc.parse(); expect(doc.returns).toEqual({type: 'string', description: '

descrip tion

\n
'}); }); it('should parse @returns with complex type and description', function() { var doc = new Doc("@name a\n@returns {function(string, number=)} description"); doc.parse(); expect(doc.returns).toEqual({type: 'function(string, number=)', description: '

description

\n
'}); }); it('should transform description of @returns with markdown', function() { var doc = new Doc("@name a\n@returns {string} descrip *tion*"); doc.parse(); expect(doc.returns).toEqual({type: 'string', description: '

descrip tion

\n
'}); }); it('should support multiline content', function() { var doc = new Doc("@name a\n@returns {string} description\n new line\n another line"); doc.parse(); expect(doc.returns). toEqual({type: 'string', description: '

description\nnew line\nanother line

\n
'}); }); }); describe('@description', function() { it('should support pre blocks', function() { var doc = new Doc("@name a\n@description
abc
"); doc.parse(); expect(doc.description). toBe('
<b>abc</b>
\n
'); }); it('should support multiple pre blocks', function() { var doc = new Doc("@name a\n@description foo \n
abc
\n#bah\nfoo \n
cba
"); doc.parse(); expect(doc.description). toBe('

foo \n' + '

abc
\n' + '

bah

\n' + '

foo \n' + '

cba
\n
'); }); it('should support nested @link annotations with or without description', function() { var doc = new Doc("@name a\n@description " + 'foo {@link angular.foo}\n\n da {@link angular.foo bar foo bar } \n\n' + 'dad{@link angular.foo}\n\n' + 'external{@link http://angularjs.org}\n\n' + 'external{@link ./static.html}\n\n' + '{@link angular.directive.ng-foo ng:foo}'); doc.section = 'api'; doc.parse(); expect(doc.description). toContain('foo angular.foo'); expect(doc.description). toContain('da bar foo bar'); expect(doc.description). toContain('dadangular.foo'); expect(doc.description). toContain('ng:foo'); expect(doc.description). toContain('http://angularjs.org'); expect(doc.description). toContain('./static.html'); }); it('should support line breaks in @link', function() { var doc = new Doc("@name a\n@description " + '{@link\napi/angular.foo\na\nb}'); doc.parse(); expect(doc.description). toContain('a b'); }); }); describe('@example', function() { it('should not remove {{}}', function() { var doc = new Doc('@name a\n@example text {{ abc }}'); doc.parse(); expect(doc.example).toEqual('

text {{ abc }}

\n
'); }); }); describe('@deprecated', function() { it('should parse @deprecated', function() { var doc = new Doc('@name a\n@deprecated Replaced with foo.'); doc.parse(); expect(doc.deprecated).toBe('Replaced with foo.'); }); }); describe('@this', function() { it('should render @this', function() { var doc = new Doc('@name a\n@this I am self.'); doc.ngdoc = 'filter'; doc.parse(); expect(doc.html()).toContain('

Method\'s this

\n' + '
' + '
' + '

I am self.

\n' + '
' + '
\n'); expect(doc.html()).toContain('

Method\'s this

\n' + '

I am self.

\n
'); }); }); describe('@animations', function() { it('should render @this', function() { var doc = new Doc('@name a\n@animations\nenter - Add text\nleave - Remove text\n'); doc.ngdoc = 'filter'; doc.parse(); expect(doc.html()).toContain( '

Animations

\n' + '
' + '' + '
'); }); }); }); describe('usage', function() { describe('overview', function() { it('should supress description heading', function() { var doc = new Doc('@ngdoc overview\n@name angular\n@description\n#heading\ntext'); doc.parse(); expect(doc.html()).toContain('text'); expect(doc.html()).toContain('

heading

'); expect(doc.html()).not.toContain('Description'); }); }); describe('function', function() { it('should format', function() { var doc = new Doc({ ngdoc:'function', name:'some.name', param: [ {name:'a', type: 'string', optional: true}, {name:'b', type: 'someType', optional: true, 'default': '"xxx"'}, {name:'c', type: 'string', description: 'param desc'} ], returns: {type: 'number', description: 'return desc'} }); doc.html_usage_function(dom); expect(dom).toContain('name([a][, b], c)'); //TODO(i) the comma position here is lame expect(dom).toContain('param desc'); expect(dom).toContain('(optional)'); expect(dom).toContain('return desc'); }); }); describe('filter', function() { it('should format', function() { var doc = new Doc({ ngdoc:'formatter', shortName:'myFilter', param: [ {name:'a', type:'string'}, {name:'b', type:'string'} ] }); doc.html_usage_filter(dom); expect(dom).toContain('myFilter_expression | myFilter:b'); expect(dom).toContain('$filter(\'myFilter\')(a, b)'); }); }); describe('property', function() { it('should format', function() { var doc = new Doc({ ngdoc:'property', name:'myProp', type:'string', returns:{type: 'type', description: 'description'} }); doc.html_usage_property(dom); expect(dom).toContain('myProp'); expect(dom).toContain('type'); expect(dom).toContain('description'); }); }); }); });