diff options
Diffstat (limited to 'tests/unit')
-rw-r--r-- | tests/unit/insertSorted.js | 76 | ||||
-rw-r--r-- | tests/unit/jsParse.js | 194 | ||||
-rw-r--r-- | tests/unit/markup.js | 143 | ||||
-rw-r--r-- | tests/unit/params.js | 32 | ||||
-rw-r--r-- | tests/unit/url.js | 77 |
5 files changed, 522 insertions, 0 deletions
diff --git a/tests/unit/insertSorted.js b/tests/unit/insertSorted.js new file mode 100644 index 0000000..610aeed --- /dev/null +++ b/tests/unit/insertSorted.js @@ -0,0 +1,76 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + +// Test cases for Util.insertSorted + +const JsUnit = imports.jsUnit; + +// Needed so that Util can bring some UI stuff +// we don't actually use +const Environment = imports.ui.environment; +Environment.init(); +const Util = imports.misc.util; + +function assertArrayEquals(errorMessage, array1, array2) { + JsUnit.assertEquals(errorMessage + ' length', + array1.length, array2.length); + for (let j = 0; j < array1.length; j++) { + JsUnit.assertEquals(errorMessage + ' item ' + j, + array1[j], array2[j]); + } +} + +function cmp(one, two) { + return one-two; +} + +let arrayInt = [1, 2, 3, 5, 6]; +Util.insertSorted(arrayInt, 4, cmp); + +assertArrayEquals('first test', [1,2,3,4,5,6], arrayInt); + +// no comparator, integer sorting is implied +Util.insertSorted(arrayInt, 3); + +assertArrayEquals('second test', [1,2,3,3,4,5,6], arrayInt); + +let obj1 = { a: 1 }; +let obj2 = { a: 2, b: 0 }; +let obj3 = { a: 2, b: 1 }; +let obj4 = { a: 3 }; + +function objCmp(one, two) { + return one.a - two.a; +} + +let arrayObj = [obj1, obj3, obj4]; + +// obj2 compares equivalent to obj3, should be +// inserted before +Util.insertSorted(arrayObj, obj2, objCmp); + +assertArrayEquals('object test', [obj1, obj2, obj3, obj4], arrayObj); + +function checkedCmp(one, two) { + if (typeof one != 'number' || + typeof two != 'number') + throw new TypeError('Invalid type passed to checkedCmp'); + + return one-two; +} + +let arrayEmpty = []; + +// check that no comparisons are made when +// inserting in a empty array +Util.insertSorted(arrayEmpty, 3, checkedCmp); + +// Insert at the end and check that we don't +// access past it +Util.insertSorted(arrayEmpty, 4, checkedCmp); +Util.insertSorted(arrayEmpty, 5, checkedCmp); + +// Some more insertions +Util.insertSorted(arrayEmpty, 2, checkedCmp); +Util.insertSorted(arrayEmpty, 1, checkedCmp); + +assertArrayEquals('checkedCmp test', [1, 2, 3, 4, 5], arrayEmpty); diff --git a/tests/unit/jsParse.js b/tests/unit/jsParse.js new file mode 100644 index 0000000..468138b --- /dev/null +++ b/tests/unit/jsParse.js @@ -0,0 +1,194 @@ +/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + +// Test cases for MessageTray URLification + +const JsUnit = imports.jsUnit; + +const Environment = imports.ui.environment; +Environment.init(); + +const JsParse = imports.misc.jsParse; + +const HARNESS_COMMAND_HEADER = "let imports = obj;" + + "let global = obj;" + + "let Main = obj;" + + "let foo = obj;" + + "let r = obj;"; + +const testsFindMatchingQuote = [ + { input: '"double quotes"', + output: 0 }, + { input: '\'single quotes\'', + output: 0 }, + { input: 'some unquoted "some quoted"', + output: 14 }, + { input: '"mixed \' quotes\'"', + output: 0 }, + { input: '"escaped \\" quote"', + output: 0 } +]; +const testsFindMatchingSlash = [ + { input: '/slash/', + output: 0 }, + { input: '/slash " with $ funny ^\' stuff/', + output: 0 }, + { input: 'some unslashed /some slashed/', + output: 15 }, + { input: '/escaped \\/ slash/', + output: 0 } +]; +const testsFindMatchingBrace = [ + { input: '[square brace]', + output: 0 }, + { input: '(round brace)', + output: 0 }, + { input: '([()][nesting!])', + output: 0 }, + { input: '[we have "quoted [" braces]', + output: 0 }, + { input: '[we have /regex [/ braces]', + output: 0 }, + { input: '([[])[] mismatched braces ]', + output: 1 } +]; +const testsGetExpressionOffset = [ + { input: 'abc.123', + output: 0 }, + { input: 'foo().bar', + output: 0 }, + { input: 'foo(bar', + output: 4 }, + { input: 'foo[abc.match(/"/)]', + output: 0 } +]; +const testsGetDeclaredConstants = [ + { input: 'const foo = X; const bar = Y;', + output: ['foo', 'bar'] }, + { input: 'const foo=X; const bar=Y', + output: ['foo', 'bar'] } +]; +const testsIsUnsafeExpression = [ + { input: 'foo.bar', + output: false }, + { input: 'foo[\'bar\']', + output: false }, + { input: 'foo["a=b=c".match(/=/)', + output: false }, + { input: 'foo[1==2]', + output: false }, + { input: '(x=4)', + output: true }, + { input: '(x = 4)', + output: true }, + { input: '(x;y)', + output: true } +]; +const testsModifyScope = [ + "foo['a", + "foo()['b'", + "obj.foo()('a', 1, 2, 'b')().", + "foo.[.", + "foo]]]()))].", + "123'ab\"", + "Main.foo.bar = 3; bar.", + "(Main.foo = 3).", + "Main[Main.foo+=-1]." +]; + + + +// Utility function for comparing arrays +function assertArrayEquals(errorMessage, array1, array2) { + JsUnit.assertEquals(errorMessage + ' length', + array1.length, array2.length); + for (let j = 0; j < array1.length; j++) { + JsUnit.assertEquals(errorMessage + ' item ' + j, + array1[j], array2[j]); + } +} + +// +// Test javascript parsing +// + +for (let i = 0; i < testsFindMatchingQuote.length; i++) { + let text = testsFindMatchingQuote[i].input; + let match = JsParse.findMatchingQuote(text, text.length - 1); + + JsUnit.assertEquals('Test testsFindMatchingQuote ' + i, + match, testsFindMatchingQuote[i].output); +} + +for (let i = 0; i < testsFindMatchingSlash.length; i++) { + let text = testsFindMatchingSlash[i].input; + let match = JsParse.findMatchingSlash(text, text.length - 1); + + JsUnit.assertEquals('Test testsFindMatchingSlash ' + i, + match, testsFindMatchingSlash[i].output); +} + +for (let i = 0; i < testsFindMatchingBrace.length; i++) { + let text = testsFindMatchingBrace[i].input; + let match = JsParse.findMatchingBrace(text, text.length - 1); + + JsUnit.assertEquals('Test testsFindMatchingBrace ' + i, + match, testsFindMatchingBrace[i].output); +} + +for (let i = 0; i < testsGetExpressionOffset.length; i++) { + let text = testsGetExpressionOffset[i].input; + let match = JsParse.getExpressionOffset(text, text.length - 1); + + JsUnit.assertEquals('Test testsGetExpressionOffset ' + i, + match, testsGetExpressionOffset[i].output); +} + +for (let i = 0; i < testsGetDeclaredConstants.length; i++) { + let text = testsGetDeclaredConstants[i].input; + let match = JsParse.getDeclaredConstants(text); + + assertArrayEquals('Test testsGetDeclaredConstants ' + i, + match, testsGetDeclaredConstants[i].output); +} + +for (let i = 0; i < testsIsUnsafeExpression.length; i++) { + let text = testsIsUnsafeExpression[i].input; + let unsafe = JsParse.isUnsafeExpression(text); + + JsUnit.assertEquals('Test testsIsUnsafeExpression ' + i, + unsafe, testsIsUnsafeExpression[i].output); +} + +// +// Test safety of eval to get completions +// + +for (let i = 0; i < testsModifyScope.length; i++) { + let text = testsModifyScope[i]; + // We need to use var here for the with statement + var obj = {}; + + // Just as in JsParse.getCompletions, we will find the offset + // of the expression, test whether it is unsafe, and then eval it. + let offset = JsParse.getExpressionOffset(text, text.length - 1); + if (offset >= 0) { + text = text.slice(offset); + + let matches = text.match(/(.*)\.(.*)/); + if (matches) { + let [expr, base, attrHead] = matches; + + if (!JsParse.isUnsafeExpression(base)) { + with (obj) { + try { + eval(HARNESS_COMMAND_HEADER + base); + } catch (e) { + JsUnit.assertNotEquals("Code '" + base + "' is valid code", e.constructor, SyntaxError); + } + } + } + } + } + let propertyNames = Object.getOwnPropertyNames(obj); + JsUnit.assertEquals("The context '" + JSON.stringify(obj) + "' was not modified", propertyNames.length, 0); +} diff --git a/tests/unit/markup.js b/tests/unit/markup.js new file mode 100644 index 0000000..603ca81 --- /dev/null +++ b/tests/unit/markup.js @@ -0,0 +1,143 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +// Test cases for MessageList markup parsing + +const JsUnit = imports.jsUnit; +const Pango = imports.gi.Pango; + +const Environment = imports.ui.environment; +Environment.init(); + +const Main = imports.ui.main; // unused, but needed to break dependency loop +const MessageList = imports.ui.messageList; + +// Assert that @input, assumed to be markup, gets "fixed" to @output, +// which is valid markup. If @output is null, @input is expected to +// convert to itself +function assertConverts(input, output) { + if (!output) + output = input; + let fixed = MessageList._fixMarkup(input, true); + JsUnit.assertEquals(output, fixed); + + let parsed = false; + try { + Pango.parse_markup(fixed, -1, ''); + parsed = true; + } catch (e) {} + JsUnit.assertEquals(true, parsed); +} + +// Assert that @input, assumed to be plain text, gets escaped to @output, +// which is valid markup. +function assertEscapes(input, output) { + let fixed = MessageList._fixMarkup(input, false); + JsUnit.assertEquals(output, fixed); + + let parsed = false; + try { + Pango.parse_markup(fixed, -1, ''); + parsed = true; + } catch (e) {} + JsUnit.assertEquals(true, parsed); +} + + + +// CORRECT MARKUP + +assertConverts('foo'); +assertEscapes('foo', 'foo'); + +assertConverts('<b>foo</b>'); +assertEscapes('<b>foo</b>', '<b>foo</b>'); + +assertConverts('something <i>foo</i>'); +assertEscapes('something <i>foo</i>', 'something <i>foo</i>'); + +assertConverts('<u>foo</u> something'); +assertEscapes('<u>foo</u> something', '<u>foo</u> something'); + +assertConverts('<b>bold</b> <i>italic <u>and underlined</u></i>'); +assertEscapes('<b>bold</b> <i>italic <u>and underlined</u></i>', '<b>bold</b> <i>italic <u>and underlined</u></i>'); + +assertConverts('this & that'); +assertEscapes('this & that', 'this &amp; that'); + +assertConverts('this < that'); +assertEscapes('this < that', 'this &lt; that'); + +assertConverts('this < that > the other'); +assertEscapes('this < that > the other', 'this &lt; that &gt; the other'); + +assertConverts('this <<i>that</i>>'); +assertEscapes('this <<i>that</i>>', 'this &lt;<i>that</i>&gt;'); + +assertConverts('<b>this</b> > <i>that</i>'); +assertEscapes('<b>this</b> > <i>that</i>', '<b>this</b> > <i>that</i>'); + + + +// PARTIALLY CORRECT MARKUP +// correct bits are kept, incorrect bits are escaped + +// unrecognized entity +assertConverts('<b>smile</b> ☺!', '<b>smile</b> &#9786;!'); +assertEscapes('<b>smile</b> ☺!', '<b>smile</b> &#9786;!'); + +// stray '&'; this is really a bug, but it's easier to do it this way +assertConverts('<b>this</b> & <i>that</i>', '<b>this</b> & <i>that</i>'); +assertEscapes('<b>this</b> & <i>that</i>', '<b>this</b> & <i>that</i>'); + +// likewise with stray '<' +assertConverts('this < that', 'this < that'); +assertEscapes('this < that', 'this < that'); + +assertConverts('<b>this</b> < <i>that</i>', '<b>this</b> < <i>that</i>'); +assertEscapes('<b>this</b> < <i>that</i>', '<b>this</b> < <i>that</i>'); + +assertConverts('this < that > the other', 'this < that > the other'); +assertEscapes('this < that > the other', 'this < that > the other'); + +assertConverts('this <<i>that</i>>', 'this <<i>that</i>>'); +assertEscapes('this <<i>that</i>>', 'this <<i>that</i>>'); + +// unknown tags +assertConverts('<unknown>tag</unknown>', '<unknown>tag</unknown>'); +assertEscapes('<unknown>tag</unknown>', '<unknown>tag</unknown>'); + +// make sure we check beyond the first letter +assertConverts('<bunknown>tag</bunknown>', '<bunknown>tag</bunknown>'); +assertEscapes('<bunknown>tag</bunknown>', '<bunknown>tag</bunknown>'); + +// with mix of good and bad, we keep the good and escape the bad +assertConverts('<i>known</i> and <unknown>tag</unknown>', '<i>known</i> and <unknown>tag</unknown>'); +assertEscapes('<i>known</i> and <unknown>tag</unknown>', '<i>known</i> and <unknown>tag</unknown>'); + + + +// FULLY INCORRECT MARKUP +// (fall back to escaping the whole thing) + +// tags not matched up +assertConverts('<b>in<i>com</i>plete', '<b>in<i>com</i>plete'); +assertEscapes('<b>in<i>com</i>plete', '<b>in<i>com</i>plete'); + +assertConverts('in<i>com</i>plete</b>', 'in<i>com</i>plete</b>'); +assertEscapes('in<i>com</i>plete</b>', 'in<i>com</i>plete</b>'); + +// we don't support attributes, and it's too complicated to try +// to escape both start and end tags, so we just treat it as bad +assertConverts('<b>good</b> and <b style=\'bad\'>bad</b>', '<b>good</b> and <b style='bad'>bad</b>'); +assertEscapes('<b>good</b> and <b style=\'bad\'>bad</b>', '<b>good</b> and <b style='bad'>bad</b>'); + +// this is just syntactically invalid +assertConverts('<b>unrecognized</b stuff>', '<b>unrecognized</b stuff>'); +assertEscapes('<b>unrecognized</b stuff>', '<b>unrecognized</b stuff>'); + +// mismatched tags +assertConverts('<b>mismatched</i>', '<b>mismatched</i>'); +assertEscapes('<b>mismatched</i>', '<b>mismatched</i>'); + +assertConverts('<b>mismatched/unknown</bunknown>', '<b>mismatched/unknown</bunknown>'); +assertEscapes('<b>mismatched/unknown</bunknown>', '<b>mismatched/unknown</bunknown>'); diff --git a/tests/unit/params.js b/tests/unit/params.js new file mode 100644 index 0000000..6ac4cc1 --- /dev/null +++ b/tests/unit/params.js @@ -0,0 +1,32 @@ +const JsUnit = imports.jsUnit; +const Params = imports.misc.params; + +function assertParamsEqual(params, expected) { + for (let p in params) { + JsUnit.assertTrue(p in expected); + JsUnit.assertEquals(params[p], expected[p]); + } +} + +let defaults = { + foo: 'This is a test', + bar: null, + baz: 42 +}; + +assertParamsEqual( + Params.parse(null, defaults), + defaults); + +assertParamsEqual( + Params.parse({ bar: 23 }, defaults), + { foo: 'This is a test', bar: 23, baz: 42 }); + +JsUnit.assertRaises( + () => { + Params.parse({ extraArg: 'quz' }, defaults); + }); + +assertParamsEqual( + Params.parse({ extraArg: 'quz' }, defaults, true), + { foo: 'This is a test', bar: null, baz: 42, extraArg: 'quz' }); diff --git a/tests/unit/url.js b/tests/unit/url.js new file mode 100644 index 0000000..84aecc9 --- /dev/null +++ b/tests/unit/url.js @@ -0,0 +1,77 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +// Test cases for MessageTray URLification + +const JsUnit = imports.jsUnit; + +const Environment = imports.ui.environment; +Environment.init(); + +const Util = imports.misc.util; + +const tests = [ + { input: 'This is a test', + output: [] }, + { input: 'This is http://www.gnome.org a test', + output: [ { url: 'http://www.gnome.org', pos: 8 } ] }, + { input: 'This is http://www.gnome.org', + output: [ { url: 'http://www.gnome.org', pos: 8 } ] }, + { input: 'http://www.gnome.org a test', + output: [ { url: 'http://www.gnome.org', pos: 0 } ] }, + { input: 'http://www.gnome.org', + output: [ { url: 'http://www.gnome.org', pos: 0 } ] }, + { input: 'Go to http://www.gnome.org.', + output: [ { url: 'http://www.gnome.org', pos: 6 } ] }, + { input: 'Go to http://www.gnome.org/.', + output: [ { url: 'http://www.gnome.org/', pos: 6 } ] }, + { input: '(Go to http://www.gnome.org!)', + output: [ { url: 'http://www.gnome.org', pos: 7 } ] }, + { input: 'Use GNOME (http://www.gnome.org).', + output: [ { url: 'http://www.gnome.org', pos: 11 } ] }, + { input: 'This is a http://www.gnome.org/path test.', + output: [ { url: 'http://www.gnome.org/path', pos: 10 } ] }, + { input: 'This is a www.gnome.org scheme-less test.', + output: [ { url: 'www.gnome.org', pos: 10 } ] }, + { input: 'This is a www.gnome.org/scheme-less test.', + output: [ { url: 'www.gnome.org/scheme-less', pos: 10 } ] }, + { input: 'This is a http://www.gnome.org:99/port test.', + output: [ { url: 'http://www.gnome.org:99/port', pos: 10 } ] }, + { input: 'This is an ftp://www.gnome.org/ test.', + output: [ { url: 'ftp://www.gnome.org/', pos: 11 } ] }, + { input: 'https://www.gnome.org/(some_url,_with_very_unusual_characters)', + output: [ { url: 'https://www.gnome.org/(some_url,_with_very_unusual_characters)', pos: 0 } ] }, + { input: 'https://www.gnome.org/(some_url_with_unbalanced_parenthesis', + output: [ { url: 'https://www.gnome.org/', pos: 0 } ] }, + { input: 'https://www.gnome.org/ plus trailing junk', + output: [ { url: 'https://www.gnome.org/', pos: 0 } ] }, + + { input: 'Visit http://www.gnome.org/ and http://developer.gnome.org', + output: [ { url: 'http://www.gnome.org/', pos: 6 }, + { url: 'http://developer.gnome.org', pos: 32 } ] }, + + { input: 'This is not.a.domain test.', + output: [ ] }, + { input: 'This is not:a.url test.', + output: [ ] }, + { input: 'This is not:/a.url/ test.', + output: [ ] }, + { input: 'This is not:/a.url/ test.', + output: [ ] }, + { input: 'This is not@a.url/ test.', + output: [ ] }, + { input: 'This is surely@not.a/url test.', + output: [ ] } +]; + +for (let i = 0; i < tests.length; i++) { + let match = Util.findUrls(tests[i].input); + + JsUnit.assertEquals('Test ' + i + ' match length', + match.length, tests[i].output.length); + for (let j = 0; j < match.length; j++) { + JsUnit.assertEquals('Test ' + i + ', match ' + j + ' url', + match[j].url, tests[i].output[j].url); + JsUnit.assertEquals('Test ' + i + ', match ' + j + ' position', + match[j].pos, tests[i].output[j].pos); + } +} |