# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import os import unittest from cuddlefish.docs.apiparser import parse_hunks, ParseError tests_path = os.path.abspath(os.path.dirname(__file__)) static_files_path = os.path.join(tests_path, "static-files") class ParserTests(unittest.TestCase): def pathname(self, filename): return os.path.join(static_files_path, "docs", filename) def parse_text(self, text): return list(parse_hunks(text)) def parse(self, pathname): return self.parse_text(open(pathname).read()) def test_parser(self): parsed = self.parse(self.pathname("APIsample.md")) #for i,h in enumerate(parsed): # print i, h self.assertEqual(parsed[0], ("version", 4)) self.assertEqual(parsed[1], ("markdown", """\ # Title # Some text here """)) self.assertEqual(parsed[2][0], "api-json") p_test = parsed[2][1] self.assertEqual(p_test["name"], "test") self.assertEqual(p_test["type"], "function") self.assertEqual(p_test["signature"], "test(argOne, argTwo, \ argThree, options)") self.assertEqual(p_test["description"], "This is a function which does nothing in \ particular.") r = p_test["returns"] self.assertEqual(r["datatype"], "object") self.assertEqual(r["description"], "") self.assertEqual(len(r["props"]), 2) self.assertEqual(r["props"][0]["datatype"], "string") self.assertEqual(r["props"][0]["description"], "First string") self.assertEqual(r["props"][1]["datatype"], "url") self.assertEqual(r["props"][1]["description"], "First URL") self.assertEqual(p_test["params"][0], {"name": "argOne", "required": True, "datatype": "string", "description": "This is the first argument.", "line_number": 15, }) self.assertEqual(p_test["params"][1], {"name": "argTwo", "required": False, "datatype": "bool", "description": "This is the second argument.", "line_number": 16, }) self.assertEqual(p_test["params"][2], {"name": "argThree", "required": False, "default": "default", "datatype": "uri", "line_number": 17, "description": """\ This is the third and final argument. And this is a test of the ability to do multiple lines of text.""", }) p3 = p_test["params"][3] self.assertEqual(p3["name"], "options") self.assertEqual(p3["required"], False) self.failIf("type" in p3) self.assertEqual(p3["description"], "Options Bag") self.assertEqual(p3["props"][0], {"name": "style", "required": False, "datatype": "string", "description": "Some style information.", "line_number": 22, }) self.assertEqual(p3["props"][1], {"name": "secondToLastOption", "required": False, "default": "True", "datatype": "bool", "description": "The last property.", "line_number": 23, }) self.assertEqual(p3["props"][2]["name"], "lastOption") self.assertEqual(p3["props"][2]["required"], False) self.assertEqual(p3["props"][2]["datatype"], "uri") self.assertEqual(p3["props"][2]["description"], """\ And this time we have A multiline description Written as haiku""") self.assertEqual(parsed[3][0], "markdown") self.assertEqual(parsed[3][1], "\n\nThis text appears between the \ API blocks.\n\n") self.assertEqual(parsed[4][0], "api-json") p_test = parsed[4][1] expected = {'line_number': 32, 'name': 'append', 'params': [{'props':[{'line_number': 37, 'required': False, 'datatype': 'uri', 'name': 'icon', 'description': 'The HREF of an icon to show as the \ method of accessing your features slideBar'}, {'line_number': 38, 'required': False, 'datatype': 'string/xml', 'name': 'html', 'description': 'The content of the feature, either \ as an HTML string,\nor an E4X document fragment.'}, {'line_number': 41, 'required': False, 'datatype': 'uri', 'name': 'url', 'description': 'The url to load into the content area \ of the feature'}, {'line_number': 42, 'required': False, 'datatype': 'int', 'name': 'width', 'description': 'Width of the content area and the \ selected slide size'}, {'line_number': 43, 'required': False, 'datatype': 'bool', 'name': 'persist', 'description': 'Default slide behavior when being \ selected as follows:\nIf true: blah; If false: double blah.'}, {'line_number': 46, 'required': False, 'datatype': 'bool', 'name': 'autoReload', 'description': 'Automatically reload content on \ select'}, {'line_number': 47, 'required': False, 'datatype': 'function', 'name': 'onClick', 'description': 'Callback when the icon is \ clicked'}, {'line_number': 48, 'required': False, 'datatype': 'function', 'name': 'onSelect', 'description': 'Callback when the feature is selected'}, {'line_number': 49, 'required': False, 'datatype': 'function', 'name': 'onReady', 'description': 'Callback when featured is loaded'}], 'line_number': 35, 'required': True, 'name': 'options', 'description': 'Pass in all of your options here.'}], 'signature': 'append(options)', 'type': 'function', 'description': 'This is a list of options to specify modifications to your \ slideBar instance.'} self.assertEqual(p_test, expected) self.assertEqual(parsed[6][0], "api-json") p_test = parsed[6][1] self.assertEqual(p_test["name"], "cool-func.dot") self.assertEqual(p_test["signature"], "cool-func.dot(howMuch, double, \ options, onemore, options2)") self.assertEqual(p_test["returns"]["description"], """\ A value telling you just how cool you are. A boa-constructor! This description can go on for a while, and can even contain some **realy** fancy things. Like `code`, or even ~~~~{.javascript} // Some code! ~~~~""") self.assertEqual(p_test["params"][2]["props"][0], {"name": "callback", "required": True, "datatype": "function", "line_number": 67, "description": "The callback", }) self.assertEqual(p_test["params"][2]["props"][1], {"name": "random", "required": False, "datatype": "bool", "line_number": 68, "description": "Do something random?", }) p_test = parsed[8][1] self.assertEqual(p_test["signature"],"random()") # tests for classes #1) empty class p_test = parsed[10][1] self.assertEqual(len(p_test), 4) self.assertEqual(p_test["name"], "empty-class") self.assertEqual(p_test["description"], "This class contains nothing.") self.assertEqual(p_test["type"], "class") # 2) class with just one ctor p_test = parsed[12][1] self.assertEqual(len(p_test), 5) self.assertEqual(p_test["name"], "only-one-ctor") self.assertEqual(p_test["description"], "This class contains only \ one constructor.") self.assertEqual(p_test["type"], "class") constructors = p_test["constructors"] self.assertEqual(len(constructors), 1) self._test_class_constructor(constructors[0], "one-constructor") # 3) class with 2 ctors p_test = parsed[14][1] self.assertEqual(len(p_test), 5) self.assertEqual(p_test["name"], "two-ctors") self.assertEqual(p_test["description"], "This class contains two \ constructors.") self.assertEqual(p_test["type"], "class") constructors = p_test["constructors"] self.assertEqual(len(constructors), 2) self._test_class_constructor(constructors[0], "one-constructor") self._test_class_constructor(constructors[1], "another-constructor") # 4) class with ctor + method p_test = parsed[16][1] self.assertEqual(len(p_test), 6) self.assertEqual(p_test["name"], "ctor-and-method") self.assertEqual(p_test["description"], "This class contains one \ constructor and one method.") self.assertEqual(p_test["type"], "class") constructors = p_test["constructors"] self.assertEqual(len(constructors), 1) self._test_class_constructor(constructors[0], "one-constructor") methods = p_test["methods"] self.assertEqual(len(methods), 1) self._test_class_method(methods[0]) # 5) class with ctor + method + property p_test = parsed[18][1] self.assertEqual(len(p_test), 8) self.assertEqual(p_test["name"], "ctor-method-prop-event") self.assertEqual(p_test["description"], "This class contains one \ constructor, one method, one property and an event.") self.assertEqual(p_test["type"], "class") constructors = p_test["constructors"] self.assertEqual(len(constructors), 1) self._test_class_constructor(constructors[0], "one-constructor") methods = p_test["methods"] self.assertEqual(len(methods), 1) self._test_class_method(methods[0]) properties = p_test["properties"] self.assertEqual(len(properties), 1) self._test_class_property(properties[0]) events = p_test["events"] self.assertEqual(len(events), 1) self._test_class_event(events[0]) self.assertEqual(parsed[-1][0], "markdown") self.assertEqual(parsed[-1][1], "\n\nSome more text here, \ at the end of the file.\n\n") def _test_class_constructor(self, constructor, name): self.assertEqual(constructor["type"], "constructor") self.assertEqual(constructor["name"], name) params = constructor["params"] self.assertEqual(len(params), 1) self.assertEqual(params[0]["name"], "options") self.assertEqual(params[0]["description"], "An object-bag of goodies.") def _test_class_method(self, method): self.assertEqual(method["type"], "method") self.assertEqual(method["name"], "a-method") self.assertEqual(method["description"], "Does things.") params = method["params"] self.assertEqual(len(params), 1) self.assertEqual(params[0]["name"], "options") self.assertEqual(params[0]["description"], "An argument.") def _test_class_property(self, prop): self.assertEqual(prop["type"], "property") self.assertEqual(prop["name"], "a-property") self.assertEqual(prop["description"], "Represents stuff.") self.assertEqual(prop["datatype"], "bool") def _test_class_event(self, event): self.assertEqual(event["type"], "event") self.assertEqual(event["name"], "message") self.assertEqual(event["description"], "Event emitted when the \ content script sends a message to the add-on.") arguments = event["arguments"] self.assertEqual(len(arguments), 1) argument = arguments[0] self.assertEqual(argument["datatype"], "JSON") self.assertEqual(argument["description"], "The message itself as a \ JSON-serialized object.") def test_missing_return_propname(self): md = '''\ @method This is a function which does nothing in particular. @returns {object} @prop {string} First string, but the property name is missing @prop {url} First URL, same problem @param argOne {string} This is the first argument. ''' self.assertRaises(ParseError, self.parse_text, md) def test_missing_return_proptype(self): md = '''\ @method This is a function which does nothing in particular. @returns {object} @prop untyped It is an error to omit the type of a return property. @param argOne {string} This is the first argument. @param [argTwo=True] {bool} This is the second argument. ''' self.assertRaises(ParseError, self.parse_text, md) def test_return_propnames(self): md = '''\ @method This is a function which does nothing in particular. @returns {object} @prop firststring {string} First string. @prop [firsturl] {url} First URL, not always provided. @param argOne {string} This is the first argument. @param [argTwo=True] {bool} This is the second argument. ''' parsed = self.parse_text(md) r = parsed[1][1]["returns"] self.assertEqual(r["props"][0]["name"], "firststring") self.assertEqual(r["props"][0], {"name": "firststring", "datatype": "string", "description": "First string.", "required": True, "line_number": 5, # 1-indexed }) self.assertEqual(r["props"][1], {"name": "firsturl", "datatype": "url", "description": "First URL, not always provided.", "required": False, "line_number": 6, }) def test_return_description_1(self): md = '''\ @method This is a function which does nothing in particular. @returns {object} A one-line description. @prop firststring {string} First string. @prop [firsturl] {url} First URL, not always provided. @param argOne {string} This is the first argument. @param [argTwo=True] {bool} This is the second argument. ''' parsed = self.parse_text(md) r = parsed[1][1]["returns"] self.assertEqual(r["description"], "A one-line description.") def test_return_description_2(self): md = '''\ @method This is a function which does nothing in particular. @returns {object} A six-line description which is consistently indented by two spaces except for this line and preserves the following empty line from which a two-space indentation will be removed. @prop firststring {string} First string. @prop [firsturl] {url} First URL, not always provided. @param argOne {string} This is the first argument. @param [argTwo=True] {bool} This is the second argument. ''' parsed = self.parse_text(md) r = parsed[1][1]["returns"] self.assertEqual(r["description"], "A six-line description\n" "which is consistently indented by two spaces\n" " except for this line\n" "and preserves the following empty line\n" "\n" "from which a two-space indentation will be removed.") def test_return_description_3(self): md = '''\ @method This is a function which does nothing in particular. @returns A one-line untyped description. @param argOne {string} This is the first argument. @param [argTwo=True] {bool} This is the second argument. ''' parsed = self.parse_text(md) r = parsed[1][1]["returns"] self.assertEqual(r["description"], "A one-line untyped description.") # if the return value was supposed to be an array, the correct syntax # would not have any @prop tags: # @returns {array} # Array consists of two elements, a string and a url... def test_return_array(self): md = '''\ @method This is a function which returns an array. @returns {array} Array consists of two elements, a string and a url. @param argOne {string} This is the first argument. @param [argTwo=True] {bool} This is the second argument. ''' parsed = self.parse_text(md) r = parsed[1][1]["returns"] self.assertEqual(r["description"], "Array consists of two elements, a string and a url.") def test_bad_default_on_required_parameter(self): md = '''\ @method This is a function which does nothing in particular. @returns something @param argOne=ILLEGAL {string} Mandatory parameters do not take defaults. @param [argTwo=Chicago] {string} This is the second argument. ''' self.assertRaises(ParseError, self.parse_text, md) def test_missing_apitype(self): md = '''\ Sorry, you must have a @method or something before the description. Putting it after the description is not good enough @method @returns something ''' self.assertRaises(ParseError, self.parse_text, md) def test_missing_param_propname(self): md = '''\ @method This is a function which does nothing in particular. @param p1 {object} This is a parameter. @prop {string} Oops, props must have a name. ''' self.assertRaises(ParseError, self.parse_text, md) def test_missing_param_proptype(self): md = '''\ @method This is a function which does nothing in particular. @param p1 {object} This is a parameter. @prop name Oops, props must have a type. ''' self.assertRaises(ParseError, self.parse_text, md) def test_property(self): md = '''\ @property {foo} An object property named test of type foo. ''' parsed = self.parse_text(md) self.assertEqual(parsed[1][0], 'api-json') actual_api_json_obj = parsed[1][1] expected_api_json_obj = { 'line_number': 1, 'datatype': 'foo', 'type': 'property', 'name': 'test', 'description': "An object property named test of type foo." } self.assertEqual(actual_api_json_obj, expected_api_json_obj) def test_property_no_type(self): md = '''\ @property This property needs to specify a type! ''' self.assertRaises(ParseError, self.parse_text, md) def test_missing_api_closing_tag(self): md = '''\ @class This is a class with a missing closing tag. ''' self.assertRaises(ParseError, self.parse_text, md) if __name__ == "__main__": unittest.main()