aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTom Christie2011-06-25 14:53:48 +0100
committerTom Christie2011-06-25 14:53:48 +0100
commitd3557bdcd0f4d6a7106267ea0f8a3024f21fd9f9 (patch)
tree05c49fa0f7d78721cad77656e1952553380420ac
parent50efa106529e03eda5607e6c9323838432ce755f (diff)
parent0626b618ad0d3c5f8fbb6ab3facce440647fb3c0 (diff)
downloaddjango-rest-framework-d3557bdcd0f4d6a7106267ea0f8a3024f21fd9f9.tar.bz2
Allow HEAD method
-rw-r--r--djangorestframework/compat.py12
-rw-r--r--djangorestframework/tests/content.py10
-rw-r--r--djangorestframework/tests/methods.py6
-rw-r--r--djangorestframework/tests/renderers.py9
4 files changed, 33 insertions, 4 deletions
diff --git a/djangorestframework/compat.py b/djangorestframework/compat.py
index 0274511a..f09b7cc3 100644
--- a/djangorestframework/compat.py
+++ b/djangorestframework/compat.py
@@ -67,6 +67,14 @@ except ImportError:
# django.views.generic.View (Django >= 1.3)
try:
from django.views.generic import View
+ if not hasattr(View, 'head'):
+ # First implementation of Django class-based views did not include head method
+ # in base View class - https://code.djangoproject.com/ticket/15688
+ class ViewPlusHead(View):
+ def head(self, request, *args, **kwargs):
+ return self.get(request, *args, **kwargs)
+ View = ViewPlusHead
+
except ImportError:
from django import http
from django.utils.functional import update_wrapper
@@ -145,6 +153,8 @@ except ImportError:
#)
return http.HttpResponseNotAllowed(allowed_methods)
+ def head(self, request, *args, **kwargs):
+ return self.get(request, *args, **kwargs)
try:
import markdown
@@ -193,4 +203,4 @@ try:
return md.convert(text)
except ImportError:
- apply_markdown = None \ No newline at end of file
+ apply_markdown = None
diff --git a/djangorestframework/tests/content.py b/djangorestframework/tests/content.py
index ee3597a4..83ad72d0 100644
--- a/djangorestframework/tests/content.py
+++ b/djangorestframework/tests/content.py
@@ -6,7 +6,6 @@ from djangorestframework.compat import RequestFactory
from djangorestframework.mixins import RequestMixin
from djangorestframework.parsers import FormParser, MultiPartParser, PlainTextParser
-
class TestContentParsing(TestCase):
def setUp(self):
self.req = RequestFactory()
@@ -16,6 +15,11 @@ class TestContentParsing(TestCase):
view.request = self.req.get('/')
self.assertEqual(view.DATA, None)
+ def ensure_determines_no_content_HEAD(self, view):
+ """Ensure view.DATA returns None for HEAD request."""
+ view.request = self.req.head('/')
+ self.assertEqual(view.DATA, None)
+
def ensure_determines_form_content_POST(self, view):
"""Ensure view.DATA returns content for POST request with form content."""
form_data = {'qwerty': 'uiop'}
@@ -50,6 +54,10 @@ class TestContentParsing(TestCase):
"""Ensure view.DATA returns None for GET request with no content."""
self.ensure_determines_no_content_GET(RequestMixin())
+ def test_standard_behaviour_determines_no_content_HEAD(self):
+ """Ensure view.DATA returns None for HEAD request."""
+ self.ensure_determines_no_content_HEAD(RequestMixin())
+
def test_standard_behaviour_determines_form_content_POST(self):
"""Ensure view.DATA returns content for POST request with form content."""
self.ensure_determines_form_content_POST(RequestMixin())
diff --git a/djangorestframework/tests/methods.py b/djangorestframework/tests/methods.py
index d8f0d919..c3a3a28d 100644
--- a/djangorestframework/tests/methods.py
+++ b/djangorestframework/tests/methods.py
@@ -24,3 +24,9 @@ class TestMethodOverloading(TestCase):
view = RequestMixin()
view.request = self.req.post('/', {view._METHOD_PARAM: 'DELETE'})
self.assertEqual(view.method, 'DELETE')
+
+ def test_HEAD_is_a_valid_method(self):
+ """HEAD requests identified"""
+ view = RequestMixin()
+ view.request = self.req.head('/')
+ self.assertEqual(view.method, 'HEAD')
diff --git a/djangorestframework/tests/renderers.py b/djangorestframework/tests/renderers.py
index d403873f..569eb640 100644
--- a/djangorestframework/tests/renderers.py
+++ b/djangorestframework/tests/renderers.py
@@ -55,6 +55,13 @@ class RendererIntegrationTests(TestCase):
self.assertEquals(resp.content, RENDERER_A_SERIALIZER(DUMMYCONTENT))
self.assertEquals(resp.status_code, DUMMYSTATUS)
+ def test_head_method_serializes_no_content(self):
+ """No response must be included in HEAD requests."""
+ resp = self.client.head('/')
+ self.assertEquals(resp.status_code, DUMMYSTATUS)
+ self.assertEquals(resp['Content-Type'], RendererA.media_type)
+ self.assertEquals(resp.content, '')
+
def test_default_renderer_serializes_content_on_accept_any(self):
"""If the Accept header is set to */* the default renderer should serialize the response."""
resp = self.client.get('/', HTTP_ACCEPT='*/*')
@@ -83,8 +90,6 @@ class RendererIntegrationTests(TestCase):
resp = self.client.get('/', HTTP_ACCEPT='foo/bar')
self.assertEquals(resp.status_code, 406)
-
-
_flat_repr = '{"foo": ["bar", "baz"]}'
_indented_repr = """{
='#n398'>398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582
var ngdoc = require('ngdoc.js');
var DOM = require('dom.js').DOM;

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', function(){
        expect(new Doc('\nHello: World! @ignore. $abc').keywords()).toEqual('$abc hello world');
        expect(new Doc('The `ng:class-odd` and').keywords()).toEqual('and ng:class-odd the');
      });

      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('c');
        expect(ngdoc.metadata([d2])[0].shortName).toEqual('ng:c');
        expect(ngdoc.metadata([d3])[0].shortName).toEqual('more text');
      });

      it('should have depth information', 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].depth).toEqual(2);
        expect(ngdoc.metadata([d2])[0].depth).toEqual(2);
        expect(ngdoc.metadata([d3])[0].depth).toEqual(1);
      });

    });

    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(
            '@param {*} a short\n' +
            '@param {Type} b med\n' +
            '@param {Class=} [c=2] long\nline');
        doc.parse();
        expect(doc.param).toEqual([
           {name:'a', description:'<p>short</p>', type:'*', optional:false, 'default':undefined},
           {name:'b', description:'<p>med</p>', type:'Type', optional:false, 'default':undefined},
           {name:'c', description:'<p>long\nline</p>', type:'Class', optional:true, 'default':'2'}
         ]);
      });

      it('should parse return', function(){
        var doc = new Doc('@returns {Type} text *bold*.');
        doc.parse();
        expect(doc.returns).toEqual({
          type: 'Type',
          description: '<p>text <em>bold</em>.</p>'
        });
      });

      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 escape <doc:source> element', function(){
        var doc = new Doc('@description before <doc:example>' +
            '<doc:source>\n<>\n</doc:source></doc:example> after');
        doc.parse();
        expect(doc.description).toContain('<p>before </p><doc:example>' +
            '<pre class="doc-source">\n&lt;&gt;\n</pre></doc:example><p>after</p>');
      });

      it('should escape <doc:scenario> element', function(){
        var doc = new Doc('@description before <doc:example>' +
            '<doc:scenario>\n<>\n</doc:scenario></doc:example> after');
        doc.parse();
        expect(doc.description).toContain('<p>before </p><doc:example>' +
            '<pre class="doc-scenario">\n&lt;&gt;\n</pre></doc:example><p>after</p>');
      });

      it('should store all links', function() {
        var doc = new Doc('@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];};
        }
        function noop(){}
        function doc(type, name){
          return {
              id: name,
              ngdoc: type,
              keywords: noop
          };
        }

        var angular_widget = doc('overview', 'angular.widget');
        var angular_x = doc('function', 'angular.x');
        var angular_y = doc('property', 'angular.y');

        it('should put angular.fn() in front of angular.widget, etc', function(){
          expect(ngdoc.metadata([angular_widget, angular_y, angular_x]).map(property('id')))
            .toEqual(['angular.x', 'angular.y', 'angular.widget' ]);
        });
      });
    });
  });

  describe('markdown', function(){
    it('should replace angular in markdown', function(){
      expect(new Doc().markdown('<angular/>')).
        toEqual('<p><tt>&lt;angular/&gt;</tt></p>');
    });

    it('should not replace anything in <pre>, but escape the html escape the content', function(){
      expect(new Doc().markdown('bah x\n<pre>\n<b>angular</b>.k\n</pre>\n asdf x')).
        toEqual(
            '<p>bah x</p>' +
            '<div ng:non-bindable><pre class="brush: js; html-script: true;">\n' +
            '&lt;b&gt;angular&lt;/b&gt;.k\n' +
            '</pre></div>' +
            '<p>asdf x</p>');
    });

    it('should ignore doc widgets', function() {
      expect(new Doc().markdown('text<doc:example>do not touch</doc:example>')).
        toEqual('<p>text</p><doc:example>do not touch</doc:example>');

      expect(new Doc().markdown('text<doc:tutorial-instructions>do not touch</doc:tutorial-instructions>')).
        toEqual('<p>text</p><doc:tutorial-instructions>do not touch</doc:tutorial-instructions>');
    });

    it('should ignore doc widgets with params', function() {
      expect(new Doc().markdown('text<doc:tutorial-instructions id="10" show="true">do not touch</doc:tutorial-instructions>')).
        toEqual('<p>text</p><doc:tutorial-instructions id="10" show="true">do not touch</doc:tutorial-instructions>');
    });

    it('should replace text between two <pre></pre> tags', function() {
      expect(new Doc().markdown('<pre>x</pre># One<pre>b</pre>')).
        toMatch('</div><h1>One</h1><div');
    });

    it('should ignore nested doc widgets', function() {
      expect(new Doc().markdown(
        'before<doc:tutorial-instructions>\n' +
          '<doc:tutorial-instruction id="git-mac" name="Git on Mac/Linux">' +
          '\ngit bla bla\n</doc:tutorial-instruction>\n' +
        '</doc:tutorial-instructions>')).toEqual(

        '<p>before</p><doc:tutorial-instructions>\n' +
          '<doc:tutorial-instruction id="git-mac" name="Git on Mac/Linux">\n' +
          'git bla bla\n' +
          '</doc:tutorial-instruction>\n' +
        '</doc:tutorial-instructions>');
      });

    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('<p>first line\n' +
                'second line</p>\n\n' +
                '<pre><code>third line\n' +
                ' fourth line\n</code></pre>\n\n' +
                '<p>fifth line</p>');
    });

    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('<p>first line</p>\n\n' +
                '<pre><code>second line\n' +
                'third line\n' +
                ' fourth line\n</code></pre>\n\n' +
                '<p>fifth line</p>');
    });


  });

  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: 'angular.service.abc', name: 'angular.service.abc', section: 'api'});
      var methodA = new Doc({name: 'methodA', methodOf: 'angular.service.abc'});
      var methodB = new Doc({name: 'methodB', methodOf: 'angular.service.abc'});
      var propA = new Doc({name: 'propA', propertyOf: 'angular.service.abc'});
      var propB = new Doc({name: 'propB', propertyOf: 'angular.service.abc'});
      var docs = [methodB, methodA, propB, propA, parent]; // keep wrong order;
      ngdoc.merge(docs);
      expect(docs.length).toEqual(1);
      expect(docs[0].id).toEqual('angular.service.abc');
      expect(docs[0].methods).toEqual([methodA, methodB]);
      expect(docs[0].properties).toEqual([propA, propB]);
    });



    describe('links checking', function() {
      var docs;
      beforeEach(function() {
        spyOn(console, 'log');
        docs = [new Doc({section: 'api', id: 'fake.id1', links: ['non-existing-link']}),
                new Doc({section: 'api', id: 'fake.id2'}),
                new Doc({section: 'api', id: 'fake.id3'})];
      });

      it('should log warning when any link doesn\'t exist', function() {
        ngdoc.merge(docs);
        expect(console.log).toHaveBeenCalled();
        expect(console.log.argsForCall[0][0]).toContain('WARNING:');
      });

      it('should say which link doesn\'t exist', function() {
        ngdoc.merge(docs);
        expect(console.log.argsForCall[0][0]).toContain('non-existing-link');
      });

      it('should say where is the non-existing link', function() {
        ngdoc.merge(docs);
        expect(console.log.argsForCall[0][0]).toContain('api/fake.id1');
      });
    });
  });

  ////////////////////////////////////////

  describe('TAG', function(){
    describe('@param', function(){
      it('should parse with no default', function(){
        var doc = new Doc('@param {(number|string)} number Number \n to format.');
        doc.parse();
        expect(doc.param).toEqual([{
          type : '(number|string)',
          name : 'number',
          optional: false,
          'default' : undefined,
          description : '<p>Number \nto format.</p>' }]);
      });

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

    describe('@requires', function() {
      it('should parse more @requires tag into array', function() {
        var doc = new Doc('@requires $service for \n`A`\n@requires $another for `B`');
        doc.ngdoc = 'service';
        doc.parse();
        expect(doc.requires).toEqual([
          {name:'$service', text:'<p>for \n<code>A</code></p>'},
          {name:'$another', text:'<p>for <code>B</code></p>'}]);
        expect(doc.html()).toContain('<a href="#!/api/angular.service.$service">$service</a>');
        expect(doc.html()).toContain('<a href="#!/api/angular.service.$another">$another</a>');
        expect(doc.html()).toContain('<p>for \n<code>A</code></p>');
        expect(doc.html()).toContain('<p>for <code>B</code></p>');
      });
    });

    describe('@property', function() {
      it('should parse @property tags into array', function() {
        var doc = new Doc("@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");
        expect(function() { doc.parse(); }).
          toThrow(new Error("Not a valid 'property' format: fake"));
      });

      it('should parse @property with type', function() {
        var doc = new Doc("@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("@property {string} name desc rip tion");
        doc.parse();
        expect(doc.properties[0].name).toEqual('name');
        expect(doc.properties[0].description).toEqual('<p>desc rip tion</p>');
      });

      it('should parse @property with type and description both', function() {
        var doc = new Doc("@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('<p>desc rip tion</p>');
      });

    });

    describe('@returns', function() {
      it('should not parse @returns without type', function() {
        var doc = new Doc("@returns lala");
        expect(doc.parse).toThrow();
      });

      it('should parse @returns with type and description', function() {
        var doc = new Doc("@returns {string} descrip tion");
        doc.parse();
        expect(doc.returns).toEqual({type: 'string', description: '<p>descrip tion</p>'});
      });

      it('should transform description of @returns with markdown', function() {
        var doc = new Doc("@returns {string} descrip *tion*");
        doc.parse();
        expect(doc.returns).toEqual({type: 'string', description: '<p>descrip <em>tion</em></p>'});
      });

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

    describe('@description', function(){
      it('should support pre blocks', function(){
        var doc = new Doc("@description <pre><b>abc</b></pre>");
        doc.parse();
        expect(doc.description).
          toBe('<div ng:non-bindable><pre class="brush: js; html-script: true;">&lt;b&gt;abc&lt;/b&gt;</pre></div>');
      });

      it('should support multiple pre blocks', function() {
        var doc = new Doc("@description foo \n<pre>abc</pre>\n#bah\nfoo \n<pre>cba</pre>");
        doc.parse();
        expect(doc.description).
          toBe('<p>foo </p>' +
               '<div ng:non-bindable><pre class="brush: js;">abc</pre></div>' +
               '<h1>bah</h1>\n\n' +
               '<p>foo </p>' +
               '<div ng:non-bindable><pre class="brush: js;">cba</pre></div>');

      });

      it('should support nested @link annotations with or without description', function() {
        var doc = new Doc("@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 <a href="#!/api/angular.foo"><code>angular.foo</code></a>');
        expect(doc.description).
          toContain('da <a href="#!/api/angular.foo"><code>bar foo bar</code></a>');
        expect(doc.description).
          toContain('dad<a href="#!/api/angular.foo"><code>angular.foo</code></a>');
        expect(doc.description).
          toContain('<a href="#!/api/angular.directive.ng:foo"><code>ng:foo</code></a>');
        expect(doc.description).
          toContain('<a href="http://angularjs.org">http://angularjs.org</a>');
        expect(doc.description).
          toContain('<a href="./static.html">./static.html</a>');
      });

      it('should support line breaks in @link', function(){
        var doc = new Doc("@description " +
            '{@link\napi/angular.foo\na\nb}');
        doc.parse();
        expect(doc.description).
          toContain('<a href="#!/api/angular.foo"><code>a b</code></a>');
      });

    });

    describe('@example', function(){
      it('should not remove {{}}', function(){
        var doc = new Doc('@example text {{ abc }}');
        doc.parse();
        expect(doc.example).toEqual('<p>text {{ abc }}</p>');
      });

      it('should support doc:example', function(){
        var doc = new Doc('@ngdoc overview\n@example \n' +
            '<doc:example>\n' +
            ' <doc:source><escapeme></doc:source>\n' +
            ' <doc:scenario><scenario></doc:scenario>\n' +
            '</doc:example>').parse();
        var html = doc.html();
        expect(html).toContain('<pre class="doc-source">&lt;escapeme&gt;</pre>');
        expect(html).toContain('<pre class="doc-scenario">&lt;scenario&gt;</pre>');
        expect(doc.scenarios).toEqual(['<scenario>']);
      });
    });

    describe('@deprecated', function() {
      it('should parse @deprecated', function() {
        var doc = new Doc('@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('@this I am self.');
        doc.ngdoc = 'filter';
        doc.parse();
        expect(doc.html()).toContain('<h3>Method\'s <code>this</code></h3>\n' +
            '<div>' +
            '<p>I am self.</p>' +
            '</div>\n');
        expect(doc.html()).toContain('<h3>Method\'s <code>this</code></h3>\n' +
            '<div><p>I am self.</p></div>');
      });
    });
  });

  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('<h2>heading</h2>');
        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', 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('some.name([a][, b], c)'); //TODO(i) the comma position here is lame
        expect(dom).toContain('param desc');
        expect(dom).toContain('(optional="xxx")');
        expect(dom).toContain('return desc');
      });
    });

    describe('filter', function(){
      it('should format', function(){
        var doc = new Doc({
          ngdoc:'formatter',
          shortName:'myFilter',
          param: [
            {name:'a'},
            {name:'b'}
          ]
        });
        doc.html_usage_filter(dom);
        expect(dom).toContain('myFilter_expression | myFilter:b');
        expect(dom).toContain('angular.filter.myFilter(a, b)');
      });
    });

    describe('validator', function(){
      it('should format', function(){
        var doc = new Doc({
          ngdoc:'validator',
          shortName:'myValidator',
          param: [
            {name:'a'},
            {name:'b'}
          ]
        });
        doc.html_usage_validator(dom);
        expect(dom).toContain('ng:validate="myValidator:b"');
        expect(dom).toContain('angular.validator.myValidator(a, b)');
      });
    });

    describe('formatter', function(){
      it('should format', function(){
        var doc = new Doc({
          ngdoc:'formatter',
          shortName:'myFormatter',
          param: [
            {name:'a'},
          ]
        });
        doc.html_usage_formatter(dom);
        expect(dom).toContain('ng:format="myFormatter:a"');
        expect(dom).toContain('var userInputString = angular.formatter.myFormatter.format(modelValue, a);');
        expect(dom).toContain('var modelValue = angular.formatter.myFormatter.parse(userInputString, a);');
      });
    });

    describe('property', function(){
      it('should format', function(){
        var doc = new Doc({
          ngdoc:'property',
          name:'myProp',
          returns:{type: 'type', description: 'description'}
        });
        doc.html_usage_property(dom);
        expect(dom).toContain('myProp');
        expect(dom).toContain('type');
        expect(dom).toContain('description');
      });
    });
  });

});