summaryrefslogtreecommitdiff
path: root/doc/_extensions/autojsdoc/parser/tests
diff options
context:
space:
mode:
Diffstat (limited to 'doc/_extensions/autojsdoc/parser/tests')
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/README.rst6
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/support.py73
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/test_class.py227
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/test_crap.py75
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/test_module.py173
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/test_namespace.py177
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/test_params.py116
-rw-r--r--doc/_extensions/autojsdoc/parser/tests/test_typespec.py81
8 files changed, 928 insertions, 0 deletions
diff --git a/doc/_extensions/autojsdoc/parser/tests/README.rst b/doc/_extensions/autojsdoc/parser/tests/README.rst
new file mode 100644
index 00000000000..dd862beae66
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/README.rst
@@ -0,0 +1,6 @@
+:orphan:
+
+These files should be run via pytest_, simply install pytest and from the top
+of the Odoo project run ``pytest doc/_extensions``.
+
+.. _pytest: https://pytest.org/
diff --git a/doc/_extensions/autojsdoc/parser/tests/support.py b/doc/_extensions/autojsdoc/parser/tests/support.py
new file mode 100644
index 00000000000..f533f98d90c
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/support.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+import operator
+
+import pyjsparser
+
+from autojsdoc.parser import jsdoc, parser
+
+params = operator.attrgetter('name', 'type', 'doc')
+
+
+def parse(s, source=None):
+ tree = pyjsparser.parse(s)
+ mods = parser.ModuleMatcher(source).visit(tree)
+ post(mods)
+ return mods
+
+def post(mods):
+ modules = dict(BASE_MODULES)
+ modules.update((m.name, m) for m in mods)
+
+ for mod in mods:
+ mod.post_process(modules)
+
+BASE_MODULES = {
+ 'other': jsdoc.ModuleDoc({
+ 'module': 'other',
+ '_members': [
+ ('<exports>', jsdoc.LiteralDoc({'name': 'value', 'value': "ok"})),
+ ],
+ }),
+ 'dep2': jsdoc.ModuleDoc({
+ 'module': 'dep2',
+ '_members': [
+ ('<exports>', jsdoc.LiteralDoc({'value': 42.})),
+ ],
+ }),
+ 'dep3': jsdoc.ModuleDoc({
+ 'module': 'dep3',
+ '_members': [
+ ('<exports>', jsdoc.LiteralDoc({'value': 56.})),
+ ],
+ }),
+ 'Class': jsdoc.ModuleDoc({
+ 'module': 'Class',
+ '_members': [
+ ('<exports>', jsdoc.ClassDoc({
+ 'name': 'Class',
+ 'doc': "Base Class"
+ })),
+ ],
+ }),
+ 'mixins': jsdoc.ModuleDoc({
+ 'module': 'mixins',
+ '_members': [
+ ('<exports>', jsdoc.NSDoc({
+ 'name': 'mixins',
+ '_members': [
+ ('Bob', jsdoc.ClassDoc({'class': "Bob"})),
+ ]
+ })),
+ ],
+ }),
+ 'Mixin': jsdoc.ModuleDoc({
+ 'module': 'Mixin',
+ '_members': [
+ ('<exports>', jsdoc.MixinDoc({
+ '_members': [
+ ('a', jsdoc.FunctionDoc({'function': 'a'})),
+ ]
+ })),
+ ],
+ })
+}
diff --git a/doc/_extensions/autojsdoc/parser/tests/test_class.py b/doc/_extensions/autojsdoc/parser/tests/test_class.py
new file mode 100644
index 00000000000..403ae9b48b7
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/test_class.py
@@ -0,0 +1,227 @@
+# -*- coding: utf-8 -*-
+import pytest
+
+from autojsdoc.parser import jsdoc
+from support import params, parse
+
+def test_classvar():
+ [mod] = parse("""
+ odoo.define('a.A', function(require) {
+ var Class = require('Class');
+ /**
+ * This is my class-kai
+ */
+ var A = Class.extend({});
+ return A;
+ });
+ """)
+ cls = mod.exports
+ assert type(cls) == jsdoc.ClassDoc
+
+ assert type(cls.superclass) == jsdoc.ClassDoc
+ assert cls.superclass.name == 'Class'
+
+ assert cls.name == 'A'
+ assert cls.constructor is None
+ assert cls.properties == []
+ assert cls['doc'] == 'This is my class-kai'
+
+def test_classret():
+ [mod] = parse("""
+ odoo.define('a.A', function(require) {
+ var Class = require('Class');
+ /**
+ * This is my class-kai
+ */
+ return Class.extend({});
+ });
+ """)
+ cls = mod.exports
+ assert type(cls) == jsdoc.ClassDoc
+
+ assert cls.name == ''
+ assert cls.constructor is None
+ assert cls.properties == []
+ assert cls['doc'] == 'This is my class-kai'
+
+def test_methods():
+ [mod] = parse("""
+ odoo.define('a.A', function(require) {
+ var Class = require('Class');
+ return Class.extend({
+ /**
+ * @param {Widget} parent
+ */
+ init: function (parent) {},
+ /**
+ * @returns {Widget}
+ */
+ itself: function () { return this; },
+ /**
+ * @param {MouseEvent} e
+ */
+ _onValidate: function (e) {},
+ });
+ });
+ """)
+ cls = mod.exports
+ assert len(cls.properties) == 3
+ assert cls.constructor
+ # assume methods are in source order
+ [_, init] = cls.properties[0]
+ assert init == cls.constructor
+ assert init.name == 'init'
+ assert not init.is_private
+ assert init.is_constructor
+ [param] = init.params
+ assert params(param) == ('parent', 'Widget', '')
+
+ [_, itself] = cls.properties[1]
+ assert itself.name == 'itself'
+ assert not itself.is_private
+ assert not itself.is_constructor
+ assert not itself.params
+ assert params(itself.return_val) == ('', 'Widget', '')
+
+ [_, _on] = cls.properties[2]
+ assert _on.name == '_onValidate'
+ assert _on.is_private
+ assert not _on.is_constructor
+ [param] = _on.params
+ assert params(param) == ('e', 'MouseEvent', '')
+
+def test_mixin_explicit():
+ [mod] = parse("""
+ odoo.define('a.A', function (require) {
+ var Class = require('Class');
+ var mixins = require('mixins');
+ /**
+ * This is my class-kai
+ * @mixes mixins.Bob
+ */
+ return Class.extend({});
+ });
+ """)
+ cls = mod.exports
+ # FIXME: ClassDoc may want to m2r(mixin, scope)?
+ assert cls.mixins == ['mixins.Bob']
+
+def test_mixin_implicit():
+ [mod] = parse("""
+ odoo.define('a.A', function(require) {
+ var Class = require('Class');
+ var Mixin = require('Mixin');
+ /**
+ * This is my class-kai
+ */
+ return Class.extend(Mixin, { foo: function() {} });
+ });
+ """)
+ cls = mod.exports
+ [mixin] = cls.mixins
+ assert type(mixin) == jsdoc.MixinDoc
+ assert params(mixin.properties[0][1]) == ('a', 'Function', '')
+ assert params(mixin.get_property('a')) == ('a', 'Function', '')
+
+ assert params(cls.get_property('foo')) == ('foo', 'Function', '')
+
+def test_instanciation():
+ [A, a] = parse("""
+ odoo.define('A', function (r) {
+ var Class = r('Class');
+ /**
+ * @class A
+ */
+ return Class.extend({
+ foo: function () {}
+ });
+ });
+ odoo.define('a', function (r) {
+ var A = r('A');
+ var a = new A;
+ return a;
+ });
+ """)
+ assert type(a.exports) == jsdoc.InstanceDoc
+ assert a.exports.cls.name == A.exports.name
+
+def test_non_function_properties():
+ [A] = parse("""
+ odoo.define('A', function (r) {
+ var Class = r('Class');
+ return Class.extend({
+ template: 'thing',
+ a_prop: [1, 2, 3],
+ 'other': {a: 7}
+ });
+ });
+ """)
+ t = A.exports.get_property('template')
+ assert type(t) == jsdoc.PropertyDoc
+ assert params(t) == ('template', 'String', '')
+ assert not t.is_private
+
+def test_non_extend_classes():
+ [mod] = parse("""
+ odoo.define('A', function () {
+ /**
+ * @class
+ */
+ var Class = function () {}
+ return Class;
+ });
+ """)
+ assert type(mod.exports) == jsdoc.ClassDoc
+
+def test_extend():
+ [a, _] = parse("""
+ odoo.define('A', function (require) {
+ var Class = require('Class');
+ return Class.extend({});
+ });
+ odoo.define('B', function (require) {
+ var A = require('A');
+ A.include({
+ /** A property */
+ a: 3,
+ /** A method */
+ b: function () {}
+ });
+ });
+ """)
+ cls = a.exports
+ assert type(cls) == jsdoc.ClassDoc
+ a = cls.get_property('a')
+ assert type(a) == jsdoc.PropertyDoc
+ assert params(a) == ('a', 'Number', 'A property')
+ b = cls.get_property('b')
+ assert type(b) == jsdoc.FunctionDoc
+ assert params(b) == ('b', 'Function', 'A method')
+
+# TODO: also support virtual members?
+# TODO: computed properties?
+@pytest.mark.skip(reason="Need to implement member/var-parsing?")
+def test_members():
+ [mod] = parse("""
+ odoo.define('A', function (r) {
+ var Class = r('Class');
+ return Class.extend({
+ init: function () {
+ /**
+ * This is bob
+ * @var {Foo}
+ */
+ this.foo = 3;
+ this.bar = 42;
+ /**
+ * @member {Baz}
+ */
+ this.baz = null;
+ }
+ });
+ });
+ """)
+ cls = mod.exports
+ assert params(cls.members[0]) == ('foo', 'Foo', 'This is bob')
+ assert params(cls.members[1]) == ('bar', '', '')
+ assert params(cls.members[2]) == ('baz', 'Baz', '')
diff --git a/doc/_extensions/autojsdoc/parser/tests/test_crap.py b/doc/_extensions/autojsdoc/parser/tests/test_crap.py
new file mode 100644
index 00000000000..70063e6dd47
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/test_crap.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+"""
+Test various crap patterns found in Odoo code to ensure they don't blow up
+the parser thingie
+"""
+from autojsdoc.parser import jsdoc
+from support import parse
+
+def test_export_external():
+ [mod] = parse("""
+ odoo.define('module', function () {
+ return $.Deferred().reject();
+ });
+ """)
+ assert isinstance(mod.exports, jsdoc.CommentDoc)
+ assert mod.exports.doc == ''
+
+def test_extend_jq():
+ parse("""
+ odoo.define('a', function (r) {
+ $.extend($.expr[':'], { a: function () {} });
+ $.fn.extend({ a: function () {} });
+ });
+ """)
+
+def test_extend_dynamic():
+ parse("""
+ odoo.define('a', function () {
+ foo.bar.baz[qux + '_external'] = function () {};
+ });
+ """)
+
+def test_extend_deep():
+ parse("""
+ odoo.define('a', function () {
+ var eventHandler = $.summernote.eventHandler;
+ var dom = $.summernote.core.dom;
+ dom.thing = function () {};
+
+ var fn_editor_currentstyle = eventHandler.modules.editor.currentStyle;
+ eventHandler.modules.editor.currentStyle = function () {}
+ });
+ """)
+
+def test_arbitrary():
+ parse("""
+ odoo.define('bob', function () {
+ var page = window.location.href.replace(/^.*\/\/[^\/]+/, '');
+ var mailWidgets = ['mail_followers', 'mail_thread', 'mail_activity', 'kanban_activity'];
+ var bob;
+ var fldj = foo.getTemplate().baz;
+ });
+ """)
+
+def test_prototype():
+ [A, B] = parse("""
+ odoo.define('mod1', function () {
+ var exports = {};
+ exports.Foo = Backbone.Model.extend({});
+ exports.Bar = Backbone.Model.extend({});
+ var BarCollection = Backbone.Collection.extend({
+ model: exports.Bar,
+ });
+ exports.Baz = Backbone.Model.extend({});
+ return exports;
+ });
+ odoo.define('mod2', function (require) {
+ var models = require('mod1');
+ var _super_orderline = models.Bar.prototype;
+ models.Foo = models.Bar.extend({});
+ var _super_order = models.Baz.prototype;
+ models.Bar = models.Baz.extend({});
+ });
+ """)
+
diff --git a/doc/_extensions/autojsdoc/parser/tests/test_module.py b/doc/_extensions/autojsdoc/parser/tests/test_module.py
new file mode 100644
index 00000000000..2a63cc1754c
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/test_module.py
@@ -0,0 +1,173 @@
+# -*- coding: utf-8 -*-
+
+from autojsdoc.parser import jsdoc
+from support import params, parse, BASE_MODULES
+
+
+def test_single():
+ [mod] = parse("""
+ /**
+ * This is a super module!
+ */
+ odoo.define('supermodule', function (req) {
+ var other = req('other');
+ });
+ """)
+ assert mod.name == 'supermodule'
+ assert mod.dependencies == {'other'}
+ assert mod.exports is None
+ assert mod.doc == "This is a super module!"
+ m = mod.get_property('other')
+ assert m.name == 'value', "property is exported value not imported module"
+
+def test_multiple():
+ [mod1, mod2, mod3] = parse("""
+ odoo.define('module1', function (req) {
+ return 1;
+ });
+ odoo.define('module2', function (req) {
+ return req('dep2');
+ });
+ odoo.define('module3', function (req) {
+ var r = req('dep3');
+ return r;
+ });
+ """)
+ assert mod1.name == 'module1'
+ assert mod1.dependencies == set()
+ assert isinstance(mod1.exports, jsdoc.LiteralDoc)
+ assert mod1.exports.value == 1.0
+ assert mod1.exports['sourcemodule'] is mod1
+ assert mod1.doc == ""
+
+ assert mod2.name == 'module2'
+ assert mod2.dependencies == {'dep2'}
+ assert isinstance(mod2.exports, jsdoc.LiteralDoc)
+ assert mod2.exports.value == 42.0
+ assert mod2.doc == ""
+
+ assert mod3.name == 'module3'
+ assert mod3.dependencies == {'dep3'}
+ assert isinstance(mod3.exports, jsdoc.LiteralDoc)
+ assert mod3.exports.value == 56.0
+ assert mod3.doc == ''
+
+def test_func():
+ [mod] = parse("""
+ odoo.define('module', function (d) {
+ /**
+ * @param {Foo} bar this is a bar
+ * @param {Baz} qux this is a qux
+ */
+ return function (bar, qux) {
+ return 42;
+ }
+ });
+ """)
+ exports = mod.exports
+ assert type(exports) == jsdoc.FunctionDoc
+ assert exports['sourcemodule'] is mod
+
+ assert exports.name == ''
+ assert exports.is_constructor == False
+ assert exports.is_private == False
+
+ assert params(exports.params[0]) == ('bar', 'Foo', 'this is a bar')
+ assert params(exports.params[1]) == ('qux', 'Baz', 'this is a qux')
+ assert params(exports.return_val) == ('', '', '')
+
+def test_hoist():
+ [mod] = parse("""
+ odoo.define('module', function() {
+ return foo;
+ /**
+ * @param a_thing
+ */
+ function foo(a_thing) {
+ return 42;
+ }
+ });
+ """)
+ actual = mod.exports
+ assert type(actual) == jsdoc.FunctionDoc
+ [param] = actual.params
+ assert params(param) == ('a_thing', '', '')
+
+def test_export_instance():
+ [mod] = parse("""
+ odoo.define('module', function (require) {
+ var Class = require('Class');
+ /**
+ * Provides an instance of Class
+ */
+ return new Class();
+ });
+ """)
+ assert type(mod.exports) == jsdoc.InstanceDoc
+ assert mod.exports.doc == 'Provides an instance of Class'
+ assert mod.exports['sourcemodule'] is mod
+
+def test_bounce():
+ [m2, m1] = parse("""
+ odoo.define('m2', function (require) {
+ var Item = require('m1');
+ return {
+ Item: Item
+ };
+ });
+ odoo.define('m1', function (require) {
+ var Class = require('Class');
+ var Item = Class.extend({});
+ return Item;
+ });
+ """)
+ assert type(m2.exports) == jsdoc.NSDoc
+ it = m2.exports.get_property('Item')
+ assert type(it) == jsdoc.ClassDoc
+ assert it['sourcemodule'] is m1
+ assert sorted([n for n, _ in m1.properties]) == ['<exports>', 'Class', 'Item']
+
+def test_reassign():
+ [m] = parse("""
+ odoo.define('m', function (require) {
+ var Class = require('Class');
+ /** local class */
+ var Class = Class.extend({});
+ return Class
+ });
+ """)
+ assert m.exports.doc == 'local class'
+ # can't use equality or identity so use class comment...
+ assert m.exports.superclass.doc == 'Base Class'
+
+def test_attr():
+ [m1, m2] = parse("""
+ odoo.define('m1', function (require) {
+ var Class = require('Class');
+ var Item = Class.extend({});
+ return {Item: Item};
+ });
+ odoo.define('m2', function (require) {
+ var Item = require('m1').Item;
+ Item.include({});
+ return Item.extend({});
+ });
+ """)
+ assert type(m2.exports) == jsdoc.ClassDoc
+ # that these two are resolved separately may be an issue at one point (?)
+ assert m2.exports.superclass.to_dict() == m1.exports.get_property('Item').to_dict()
+
+def test_nothing_implicit():
+ [m] = parse("""
+ odoo.define('m', function () {
+ });
+ """)
+ assert m.exports is None
+
+def test_nothing_explicit():
+ [m] = parse("""
+ odoo.define('m', function () {
+ return;
+ });
+ """)
+ assert m.exports is None
diff --git a/doc/_extensions/autojsdoc/parser/tests/test_namespace.py b/doc/_extensions/autojsdoc/parser/tests/test_namespace.py
new file mode 100644
index 00000000000..54cbcf8cbb4
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/test_namespace.py
@@ -0,0 +1,177 @@
+# -*- coding: utf-8 -*-
+from autojsdoc.parser import jsdoc
+from support import parse, params
+
+
+def test_empty():
+ [mod] = parse("""
+ odoo.define('a.ns', function (r) {
+ return {};
+ });
+ """)
+ assert type(mod.exports) == jsdoc.NSDoc
+ assert mod.exports.properties == []
+
+def test_inline():
+ [mod] = parse("""
+ odoo.define('a.ns', function (r) {
+ return {
+ /**
+ * a thing
+ * @type {Boolean}
+ */
+ a: true
+ }
+ });
+ """)
+ assert isinstance(mod.exports, jsdoc.NSDoc)
+ [(n, p)] = mod.exports.properties
+ assert n == 'a'
+ assert params(p) == ('a', 'Boolean', 'a thing')
+
+def test_header():
+ [mod] = parse("""
+ odoo.define('a.ns', function (r) {
+ /**
+ * @property {Boolean} a a thing
+ */
+ return { a: true }
+ });
+ """)
+ assert isinstance(mod.exports, jsdoc.NSDoc)
+ [(_, p)] = mod.exports.properties
+ assert params(p) == ('a', 'Boolean', 'a thing')
+
+def test_header_conflict():
+ """ should the header or the inline comment take precedence? """
+ [mod] = parse("""
+ odoo.define('a.ns', function (r) {
+ /**
+ * @property {Boolean} a a thing
+ */
+ return {
+ /** @type {String} */
+ a: true
+ }
+ });
+ """)
+ assert isinstance(mod.exports, jsdoc.NSDoc)
+ [(_, p)] = mod.exports.properties
+ assert params(p) == ('a', 'Boolean', 'a thing')
+
+def test_mixin():
+ [mod] = parse("""
+ odoo.define('a.mixin', function (r) {
+ /**
+ * @mixin
+ */
+ return {
+ /**
+ * @returns {Number} a number
+ */
+ do_thing: function other() { return 42; }
+ }
+ });
+ """)
+ assert isinstance(mod.exports, jsdoc.MixinDoc)
+ [(n, p)] = mod.exports.properties
+ assert n == 'do_thing'
+ assert params(p) == ('other', 'Function', '')
+ assert params(p.return_val) == ('', 'Number', 'a number')
+
+def test_literal():
+ [mod] = parse("""
+ odoo.define('a.ns', function (r) {
+ /** whop whop */
+ return {
+ 'a': 1,
+ /** wheee */
+ 'b': 2,
+ };
+ });
+ """)
+ assert mod.exports.doc == 'whop whop'
+ [(_1, a), (_2, b)] = mod.exports.properties
+ assert params(a) == ('a', 'Number', '')
+ assert params(b) == ('b', 'Number', 'wheee')
+
+def test_fill_ns():
+ [mod] = parse("""
+ odoo.define('a.ns', function (r) {
+ var Class = r('Class');
+ var ns = {};
+ /** ok */
+ ns.a = 1;
+ /** @type {String} */
+ ns['b'] = 2;
+ /** Ike */
+ ns.c = Class.extend({});
+ ns.d = function () {}
+ return ns;
+ });
+ """)
+ ns = mod.exports
+ assert type(ns) == jsdoc.NSDoc
+ [(_a, a), (_b, b), (_c, c), (_d, d)] = ns.properties
+ assert params(a) == ('a', 'Number', 'ok')
+ assert params(b) == ('b', 'String', '')
+ assert type(c) == jsdoc.ClassDoc
+ assert type(d) == jsdoc.FunctionDoc
+
+def test_extend_other():
+ [o, b] = parse("""
+ odoo.define('a.ns', function () {
+ /** @name outer */
+ return {
+ /** @name inner */
+ a: {}
+ };
+ });
+ odoo.define('b', function (r) {
+ var o = r('a.ns');
+ var Class = r('Class');
+ /** Class 1 */
+ o.a.b = Class.extend({m_b: 1});
+ /** Class 2 */
+ o.a['c'] = Class.extend({m_c: 1});
+ });
+ """)
+ [(_, m)] = o.exports.properties
+ assert type(m) == jsdoc.NSDoc
+
+ b = m.get_property('b')
+ assert type(b) == jsdoc.ClassDoc
+ assert b.get_property('m_b')
+ assert b.doc == 'Class 1'
+
+ c = m.get_property('c')
+ assert type(c) == jsdoc.ClassDoc
+ assert c.get_property('m_c')
+ assert c.doc == 'Class 2'
+
+def test_ns_variables():
+ [mod] = parse("""
+ odoo.define('A', function (r) {
+ var Class = r('Class');
+ var Thing = Class.extend({});
+ return {
+ Thing: Thing
+ };
+ });
+ """)
+ p = mod.exports.get_property('Thing')
+ assert type(p) == jsdoc.ClassDoc
+
+def test_diff():
+ """ Have the NS key and the underlying object differ
+ """
+ [mod] = parse("""
+ odoo.define('mod', function (r) {
+ var Class = r('Class');
+ var Foo = Class.extend({});
+ return { Class: Foo };
+ });
+ """)
+ c = mod.exports.get_property('Class')
+ assert type(c) == jsdoc.ClassDoc
+ assert c.name == 'Foo'
diff --git a/doc/_extensions/autojsdoc/parser/tests/test_params.py b/doc/_extensions/autojsdoc/parser/tests/test_params.py
new file mode 100644
index 00000000000..c51139b8cb3
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/test_params.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+from autojsdoc.parser.jsdoc import ParamDoc
+
+"Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+
+def test_basic():
+ d = ParamDoc("Lorem ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': 'Lorem',
+ 'type': '',
+ 'optional': False,
+ 'default': None,
+ 'doc': 'ipsum dolor sit amet, consectetur adipiscing elit.',
+ }
+
+ d = ParamDoc("{Lorem} ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': 'ipsum',
+ 'type': 'Lorem',
+ 'optional': False,
+ 'default': None,
+ 'doc': 'dolor sit amet, consectetur adipiscing elit.',
+ }
+
+def test_optional():
+ d = ParamDoc("[Lorem] ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': 'Lorem',
+ 'type': '',
+ 'optional': True,
+ 'default': None,
+ 'doc': 'ipsum dolor sit amet, consectetur adipiscing elit.',
+ }
+
+ d = ParamDoc("[Lorem=42] ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': 'Lorem',
+ 'type': '',
+ 'optional': True,
+ 'default': "42",
+ 'doc': 'ipsum dolor sit amet, consectetur adipiscing elit.',
+ }
+
+ d = ParamDoc("{Lorem} [ipsum] dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': 'ipsum',
+ 'type': 'Lorem',
+ 'optional': True,
+ 'default': None,
+ 'doc': 'dolor sit amet, consectetur adipiscing elit.',
+ }
+
+ d = ParamDoc("{Lorem} [ipsum=42] dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': 'ipsum',
+ 'type': 'Lorem',
+ 'optional': True,
+ 'default': '42',
+ 'doc': 'dolor sit amet, consectetur adipiscing elit.',
+ }
+
+def test_returns():
+ d = ParamDoc("{} ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': '',
+ 'type': '',
+ 'optional': False,
+ 'default': None,
+ 'doc': 'ipsum dolor sit amet, consectetur adipiscing elit.',
+ }
+ d = ParamDoc("{Lorem} ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d == {
+ 'name': '',
+ 'type': 'Lorem',
+ 'optional': False,
+ 'default': None,
+ 'doc': 'ipsum dolor sit amet, consectetur adipiscing elit.',
+ }
+
+def test_odd():
+ d = ParamDoc("{jQuery} [$target] the node where content will be prepended").to_dict()
+ assert d == {
+ 'name': '$target',
+ 'type': 'jQuery',
+ 'optional': True,
+ 'default': None,
+ 'doc': 'the node where content will be prepended',
+ }
+
+ d = ParamDoc("""{htmlString} [content] DOM element,
+ array of elements, HTML string or jQuery object to prepend to $target""").to_dict()
+ assert d == {
+ 'name': 'content',
+ 'type': 'htmlString',
+ 'optional': True,
+ 'default': None,
+ 'doc': "DOM element,\n array of elements, HTML string or jQuery object to prepend to $target",
+ }
+
+ d = ParamDoc("{Boolean} [options.in_DOM] true if $target is in the DOM").to_dict()
+ assert d == {
+ 'name': 'options.in_DOM',
+ 'type': 'Boolean',
+ 'optional': True,
+ 'default': None,
+ 'doc': 'true if $target is in the DOM',
+ }
+
+ d = ParamDoc("Lorem\n ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d['doc'] == 'ipsum dolor sit amet, consectetur adipiscing elit.'
+
+ d = ParamDoc("Lorem - ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d['doc'] == 'ipsum dolor sit amet, consectetur adipiscing elit.'
+
+ d = ParamDoc("Lorem : ipsum dolor sit amet, consectetur adipiscing elit.").to_dict()
+ assert d['doc'] == 'ipsum dolor sit amet, consectetur adipiscing elit.'
diff --git a/doc/_extensions/autojsdoc/parser/tests/test_typespec.py b/doc/_extensions/autojsdoc/parser/tests/test_typespec.py
new file mode 100644
index 00000000000..d90fe7995dd
--- /dev/null
+++ b/doc/_extensions/autojsdoc/parser/tests/test_typespec.py
@@ -0,0 +1,81 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+import pytest
+
+from autojsdoc.parser import types
+
+def test_parser():
+ assert types.parse("MouseEvent|TouchEvent") == types.Alt([
+ types.Type('MouseEvent', []),
+ types.Type('TouchEvent', []),
+ ])
+
+ assert types.parse('Deferred<Object>') == types.Alt([
+ types.Type('Deferred', [
+ types.Alt([types.Type('Object', [])])
+ ])
+ ])
+
+ assert types.parse('Object|undefined') == types.Alt([
+ types.Type('Object', []),
+ types.Literal('undefined'),
+ ])
+ assert types.parse('any[]') == types.Alt([
+ types.Type('Array', [types.Type('any', [])])
+ ])
+ assert types.parse("'xml' | 'less'") == types.Alt([
+ types.Literal('xml'),
+ types.Literal('less'),
+ ])
+ assert types.parse('Foo<Bar | Bar[] | null>') == types.Alt([
+ types.Type('Foo', [types.Alt([
+ types.Type('Bar', []),
+ types.Type('Array', [types.Type('Bar', [])]),
+ types.Literal('null'),
+ ])])
+ ])
+
+def test_tokens():
+ toks = list(types.tokenize('A'))
+ assert toks == [(types.NAME, 'A')]
+
+ toks = list(types.tokenize('"foo" | "bar"'))
+ assert toks == [(types.LITERAL, 'foo'), (types.OP, '|'), (types.LITERAL, 'bar')]
+
+ toks = list(types.tokenize('1 or 2'))
+ assert toks == [(types.LITERAL, 1), (types.OP, '|'), (types.LITERAL, 2)]
+
+ toks = list(types.tokenize('a.b.c.d'))
+ assert toks == [
+ (types.NAME, 'a'), (types.OP, '.'),
+ (types.NAME, 'b'), (types.OP, '.'),
+ (types.NAME, 'c'), (types.OP, '.'),
+ (types.NAME, 'd'),
+ ]
+
+ toks = list(types.tokenize('Function<String, Object>'))
+ assert toks == [
+ (types.NAME, 'Function'),
+ (types.OP, '<'),
+ (types.NAME, 'String'),
+ (types.OP, ','),
+ (types.NAME, 'Object'),
+ (types.OP, '>')
+ ]
+
+def test_peekable():
+ p = types.Peekable(range(5))
+
+ assert p.peek() == 0
+ assert next(p) == 0, "consuming should yield previously peeked value"
+ next(p)
+ next(p)
+ assert next(p) == 3
+ assert p.peek() == 4
+ next(p)
+ assert p.peek(None) is None
+ with pytest.raises(StopIteration):
+ p.peek()
+ with pytest.raises(StopIteration):
+ next(p)