diff --git a/app/common/lib/siteSuggestions.js b/app/common/lib/siteSuggestions.js index 9cfc0def82c..567ce4a70c0 100644 --- a/app/common/lib/siteSuggestions.js +++ b/app/common/lib/siteSuggestions.js @@ -102,8 +102,9 @@ const tokenizeInput = (data) => { } } - if (url && isUrl(url)) { - const parsedUrl = urlParse(url.toLowerCase()) + const parsedUrl = typeof url === 'string' && isUrl(url) && urlParse(url.toLowerCase()) + + if (parsedUrl && (parsedUrl.hash || parsedUrl.host || parsedUrl.pathname || parsedUrl.query || parsedUrl.protocol)) { if (parsedUrl.hash) { parts.push(parsedUrl.hash.slice(1)) } diff --git a/js/muonTest.entry.js b/js/muonTest.entry.js index d449a61a192..1245f5b7f39 100644 --- a/js/muonTest.entry.js +++ b/js/muonTest.entry.js @@ -1,81 +1,49 @@ -const urlParse = require('../app/common/urlParse') -const urlUtil = require('./lib/urlutil') -const suggestion = require('../app/common/lib/suggestion') const assert = require('assert') - -const assertEqual = (actual, expected, name) => { - const elem = document.createElement('div') - elem.id = name - elem.innerText = 'fail' - - try { - assert.deepEqual(actual, expected) - elem.innerText = 'success' - } catch (e) { - elem.innerText = JSON.stringify(actual) +const tests = require('../test/muon-native/').run + +const testRunner = (successKey) => { + const failures = [] + let assertCount = 0 + + const assertWrap = (assertType, assertion, failText) => { + assertCount++ + try { + assertion() + } catch (e) { + failures.push({ assertCount, assertType, failText }) + } } - document.body.appendChild(elem) -} - -const defaultParsedUrl = { - hash: '', - host: '', - hostname: '', - href: '', - origin: '', - path: '/', - pathname: '/', - port: '', - protocol: 'http:', - query: '', - search: '' -} - -const runTests = () => { - assertEqual(urlParse('http://bing.com'), - Object.assign(defaultParsedUrl, { - host: 'bing.com', - hostname: 'bing.com', - origin: 'http://bing.com/', - href: 'http://bing.com/' - }), 'urlParseSimple') - assertEqual(urlParse('https://brave.com:333/test?abc=123&def#fff'), - { - host: 'brave.com:333', - hostname: 'brave.com', - origin: 'https://brave.com:333/', - protocol: 'https:', - port: '333', - hash: '#fff', - pathname: '/test', - path: '/test?abc=123&def', - search: '?abc=123&def', - query: 'abc=123&def', - href: 'https://brave.com:333/test?abc=123&def#fff' - }, 'urlParseComplex') - - assertEqual(urlParse('http://brave.com%60x.code-fu.org/'), - Object.assign(defaultParsedUrl, { - host: 'brave.com%60x.code-fu.org', - hostname: 'brave.com%60x.code-fu.org', - href: 'http://brave.com%60x.code-fu.org/', - origin: 'http://brave.com%60x.code-fu.org/' - }), 'urlParseIssue10270') + const done = () => { + const elem = document.createElement('div') + // selector must match the pattern used in muonTest.js + elem.id = successKey.replace(/[^a-zA-Z0-9_-]/g, '_') + // passFail is the critical element, it has to contain either 'PASS' or 'FAIL' + const passFail = document.createElement('span') + passFail.className = 'passFail' + passFail.innerText = 'PASS' + const desc = document.createElement('span') + desc.innerText = ` ${successKey}` + const failure = document.createElement('div') + failure.className = 'failure' + if (failures.length) { + passFail.innerText = 'FAIL' + for (let fail of failures) { + failure.innerText += `#${fail.assertCount} ${fail.assertType}: ${fail.failText}\n` + } + } + elem.appendChild(passFail) + elem.appendChild(desc) + elem.appendChild(failure) + document.body.appendChild(elem) + } - assertEqual(urlUtil.getOrigin('http://www.brave.com/foo'), 'http://www.brave.com', 'getOriginSimple') - assertEqual(urlUtil.getOrigin('file:///aaa'), 'file:///', 'getOriginFile') - assertEqual(urlUtil.getOrigin('http://brave.com:333/foo'), 'http://brave.com:333', 'getOriginWithPort') - assertEqual(urlUtil.getOrigin('http://127.0.0.1:443/?test=1#abc'), 'http://127.0.0.1:443', 'getOriginIP') - assertEqual(urlUtil.getOrigin('about:preferences#abc'), 'about:preferences', 'getOriginAbout') - assertEqual(urlUtil.getOrigin('http://http/test'), 'http://http', 'getOriginSchemeHost') - assertEqual(urlUtil.getOrigin(''), null, 'getOriginNull') - assertEqual(urlUtil.getOrigin('abc'), null, 'getOriginInvalid') - console.log(suggestion.isSimpleDomainNameValue('http://http/test')) - assertEqual(suggestion.isSimpleDomainNameValue('http://test.com') && - suggestion.isSimpleDomainNameValue('http://test.com/') && - suggestion.isSimpleDomainNameValue('http://test.com#') && - !suggestion.isSimpleDomainNameValue('http://test.com/test'), true, 'suggestionSimpleCheck') + return [ 'ok', 'equal', 'deepEqual', 'strictEqual' ].reduce((wrappedAssert, fn) => { + wrappedAssert[fn] = (actual, expected) => { + assertWrap(fn, () => { assert[fn](actual, expected) }, actual) + } + return wrappedAssert + }, { done }) } -runTests() +tests(testRunner) diff --git a/package.json b/package.json index 714122026bd..3dca37e820e 100644 --- a/package.json +++ b/package.json @@ -177,6 +177,7 @@ "less": "^2.5.3", "less-loader": "^2.2.1", "level": "^2.1.1", + "lolex": "~1.3.2", "mkdirp": "^0.5.1", "mocha": "^2.3.4", "mockery": "^2.1.0", diff --git a/test/lib/brave.js b/test/lib/brave.js index 23339e62d88..3600d2e6260 100644 --- a/test/lib/brave.js +++ b/test/lib/brave.js @@ -365,7 +365,7 @@ var exports = { }) this.app.client.addCommand('waitForTextValue', function (selector, text) { - logVerbose('waitForSelectedText("' + selector + '", "' + text + '")') + logVerbose('waitForTextValue("' + selector + '", "' + text + '")') return this .waitForVisible(selector) .waitUntil(function () { diff --git a/test/misc-components/muonTest.js b/test/misc-components/muonTest.js index 3b212cacf86..16a8bad0ac4 100644 --- a/test/misc-components/muonTest.js +++ b/test/misc-components/muonTest.js @@ -3,6 +3,7 @@ const Brave = require('../lib/brave') const testUrl = 'chrome-extension://mnojpmjdmbbfmejpflffifhffcmidifd/muon-tests.html' +const testCases = require('../muon-native').collect() function * setup (client) { yield client.waitForUrl(Brave.newTabUrl).waitForBrowserWindow() @@ -16,24 +17,46 @@ describe('muon tests', function () { .tabByIndex(0) .url(testUrl) }) - it('muon.url.parse', function * () { - yield this.app.client - .waitForTextValue('#urlParseSimple', 'success') - .waitForTextValue('#urlParseComplex', 'success') - .waitForTextValue('#urlParseIssue10270', 'success') - }) - it('urlUtil.getOrigin', function * () { - yield this.app.client - .waitForTextValue('#getOriginSimple', 'success') - .waitForTextValue('#getOriginFile', 'success') - .waitForTextValue('#getOriginWithPort', 'success') - .waitForTextValue('#getOriginIP', 'success') - .waitForTextValue('#getOriginAbout', 'success') - .waitForTextValue('#getOriginNull', 'success') - .waitForTextValue('#getOriginInvalid', 'success') - }) - it('suggestion', function * () { - yield this.app.client - .waitForTextValue('#suggestionSimpleCheck', 'success') - }) + + const makeKey = (key, ext) => `${key} → ${ext}` + // selector must match the pattern used in muonTest.entry.js + const makeSelector = (key) => `#${key.replace(/[^a-zA-Z0-9_-]/g, '_')}` + + const executeTests = (name, successKey, tests) => { + const runnableTests = Object.keys(tests).filter((k) => typeof tests[k] === 'function') + if (runnableTests.length) { + // actual tests, with a `function` + for (let testName of runnableTests) { + it(testName, function * () { + const selector = makeSelector(makeKey(successKey, testName)) + yield this.app.client.waitForVisible(selector).then(() => { + // got the element for this particular test, check the pass/fail and report from there + return this.app.client.getText(`${selector}>.passFail`).then((value) => { + if (Array.isArray(value)) { + // it's possible to have multiple tests with the same key, mainly due to the + // selector character clobbering, these show up as an array of elements + throw new Error(`Multiple tests with same selector "${selector}", please modify their names`) + } + if (value !== 'PASS') { + return this.app.client.getText(`${selector}>.failure`).then((value) => { + // attempt to get at least some of the error message from the .failure element to report back + throw new Error(value) + }) + } + }) + }) + }) + } + } + + const testGroups = Object.keys(tests).filter((k) => typeof tests[k] === 'object') + for (let groupKey of testGroups) { + // groups of tests, nested objects presumably containing functions + describe(groupKey, () => { + executeTests(groupKey, makeKey(successKey, groupKey), tests[groupKey]) + }) + } + } + + executeTests('muon', 'muon', testCases) }) diff --git a/test/muon-native/index.js b/test/muon-native/index.js new file mode 100644 index 00000000000..1b6b438269b --- /dev/null +++ b/test/muon-native/index.js @@ -0,0 +1,89 @@ +// test cases in this directory, should be resolvable with `require('./X')` +const testCases = [ + 'urlParseTest', + 'urlutilTest', + 'suggestionTest', + 'siteSuggestionTest' +] + +// load all of the tests into a single large nested object +const collect = () => { + const testList = {} + for (let testCase of testCases) { + testList[testCase] = require(`./${testCase}`) + } + return testList +} + +const run = (testRunner) => { + // first collect an array of ordered runnable tests, each is async-ish in that it has a + // callback but the underlying test may not, we just treat them all that way + const runQueue = [] + + const buildRunQueue = (successKey, tests) => { + const ctx = {} // for `this` within a current nested block + + const makeTestExecutor = (testName, key) => { + const beforeAfter = testName === 'before' || testName === 'after' + const testFn = tests[testName] + + runQueue.push((cb) => { + const thisRunner = testRunner(key) + // before and after are async if they have 1 arg, a callback + // all other tests take a `test` argument first and if they have a second argument it's a callback + if (testFn.length > (beforeAfter ? 0 : 1)) { + const _cb = (err) => { + thisRunner.ok(!err) + thisRunner.done() + cb() + } + testFn.call(ctx, beforeAfter ? _cb : thisRunner, beforeAfter ? undefined : _cb) + } else { + testFn.call(ctx, beforeAfter ? undefined : thisRunner) + thisRunner.done() + cb() + } + }) + } + + // before() always goes first within a block + if (typeof tests.before === 'function') { + makeTestExecutor('before', `${successKey} → before`) + } + + for (let name of Object.keys(tests)) { + if (name === 'before' || name === 'after') { + continue + } + let key = `${successKey} → ${name}` + if (typeof tests[name] === 'object') { + buildRunQueue(key, tests[name]) // recursive for a nested block of tests + } else { + makeTestExecutor(name, key) + } + } + + // after() always goes last within a block + if (typeof tests.after === 'function') { + makeTestExecutor('after', `${successKey} → after`) + } + } + + buildRunQueue('muon', collect()) + + // now we have a queue of ordered tests, execute in order, async-ish + const executeQueue = () => { + if (!runQueue.length) { + return + } + const fn = runQueue.shift() + fn(() => { + setImmediate(executeQueue) + }) + } + + executeQueue() +} + +module.exports.collect = collect +module.exports.run = run diff --git a/test/muon-native/siteSuggestionTest.js b/test/muon-native/siteSuggestionTest.js new file mode 100644 index 00000000000..ed8078deacb --- /dev/null +++ b/test/muon-native/siteSuggestionTest.js @@ -0,0 +1 @@ +module.exports = require('../unit/app/common/lib/siteSuggestionsTestComponents') diff --git a/test/muon-native/suggestionTest.js b/test/muon-native/suggestionTest.js new file mode 100644 index 00000000000..c0640fa2dcb --- /dev/null +++ b/test/muon-native/suggestionTest.js @@ -0,0 +1,12 @@ +// lazy load requires for dual use in and outside muon +const suggestion = () => require('../../app/common/lib/suggestion') + +module.exports = { + suggestionSimpleCheck: (test) => { + test.deepEqual(suggestion().isSimpleDomainNameValue('http://test.com') && + suggestion().isSimpleDomainNameValue('http://test.com/') && + suggestion().isSimpleDomainNameValue('http://test.com#') && + !suggestion().isSimpleDomainNameValue('http://test.com/test'), + true) + } +} diff --git a/test/muon-native/urlParseTest.js b/test/muon-native/urlParseTest.js new file mode 100644 index 00000000000..086c9528ad9 --- /dev/null +++ b/test/muon-native/urlParseTest.js @@ -0,0 +1,50 @@ +// lazy load requires for dual use in and outside muon +const urlParse = () => require('../../app/common/urlParse') + +const defaultParsedUrl = { + hash: '', + host: '', + hostname: '', + href: '', + origin: '', + path: '/', + pathname: '/', + port: '', + protocol: 'http:', + query: '', + search: '' +} + +module.exports = { + urlParseSimple: (test) => { + test.deepEqual(urlParse()('http://bing.com'), Object.assign(defaultParsedUrl, { + host: 'bing.com', + hostname: 'bing.com', + origin: 'http://bing.com/', + href: 'http://bing.com/' + })) + }, + urlParseComplex: (test) => { + test.deepEqual(urlParse()('https://brave.com:333/test?abc=123&def#fff'), { + host: 'brave.com:333', + hostname: 'brave.com', + origin: 'https://brave.com:333/', + protocol: 'https:', + port: '333', + hash: '#fff', + pathname: '/test', + path: '/test?abc=123&def', + search: '?abc=123&def', + query: 'abc=123&def', + href: 'https://brave.com:333/test?abc=123&def#fff' + }) + }, + urlParseIssue10270: (test) => { + test.deepEqual(urlParse()('http://brave.com%60x.code-fu.org/'), Object.assign(defaultParsedUrl, { + host: 'brave.com%60x.code-fu.org', + hostname: 'brave.com%60x.code-fu.org', + href: 'http://brave.com%60x.code-fu.org/', + origin: 'http://brave.com%60x.code-fu.org/' + })) + } +} diff --git a/test/muon-native/urlutilTest.js b/test/muon-native/urlutilTest.js new file mode 100644 index 00000000000..8789d34d752 --- /dev/null +++ b/test/muon-native/urlutilTest.js @@ -0,0 +1 @@ +module.exports = require('../unit/lib/urlutilTestComponents') diff --git a/test/unit/app/common/lib/siteSuggestionsTest.js b/test/unit/app/common/lib/siteSuggestionsTest.js index 39593a8db00..8846be53ef1 100644 --- a/test/unit/app/common/lib/siteSuggestionsTest.js +++ b/test/unit/app/common/lib/siteSuggestionsTest.js @@ -1,55 +1,10 @@ -/* global describe, before, after, it */ -const {tokenizeInput, init, query, add} = require('../../../../../app/common/lib/siteSuggestions') -const assert = require('assert') -const Immutable = require('immutable') -const sinon = require('sinon') +/* global describe, before, after */ + +const runMuonCompatibleTests = require('../../../runMuonCompatibleTests') +const components = require('./siteSuggestionsTestComponents') const fakeElectron = require('../../../lib/fakeElectron') const mockery = require('mockery') -const site1 = { - location: 'https://www.bradrichter.co/bad_numbers/3', - title: 'Do not use 3 for items because it is prime' -} -const site2 = { - location: 'https://www.brave.com', - title: 'No really, take back the web' -} -const site3 = { - location: 'https://www.bradrichter.co/bad_numbers/5', - title: 'Do not use 5 it is so bad, try 6 instead. Much better.' -} - -const site4 = { - location: 'https://www.designers.com/brad', - title: 'Brad Saves The World!', - count: 50 -} - -// Same as site4 but added after in init, should be ignored. -const site5 = { - location: 'https://www.designers.com/brad', - title: 'Brad Saves The World!' -} - -// Compares 2 sites via deepEqual while first clearing out cached data -const siteEqual = (actual, expected) => { - assert.equal(actual.constructor, expected.constructor) - if (expected.constructor === Array) { - assert.equal(actual.length, expected.length) - for (let i = 0; i < actual.length; i++) { - assert.deepEqual(actual[i].delete('parsedUrl').toJS(), expected[i].delete('parsedUrl').toJS()) - } - } else { - const a = Object.assign({}, actual) - delete a.parsedUrl - const e = Object.assign({}, expected) - delete e.parsedUrl - assert.deepEqual(a, e) - } -} - -require('../../../braveUnit') - describe('siteSuggestions lib', function () { before(function () { mockery.enable({ @@ -62,304 +17,6 @@ describe('siteSuggestions lib', function () { after(function () { mockery.disable() }) - describe('tokenizeInput', function () { - it('empty string has no tokens', function () { - assert.deepEqual(tokenizeInput(''), []) - }) - it('undefined has no tokens', function () { - assert.deepEqual(tokenizeInput(null), []) - }) - it('null has no tokens', function () { - assert.deepEqual(tokenizeInput(undefined), []) - }) - it('lowercases tokens', function () { - assert.deepEqual(tokenizeInput('BRaD HaTES PRIMES'), ['brad', 'hates', 'primes']) - }) - it('includes protocol', function () { - assert.deepEqual(tokenizeInput('https://bradrichter.co/I/hate/primes.html'), ['bradrichter', 'co', 'i', 'hate', 'primes', 'html', 'https:']) - }) - it('includes query', function () { - assert.deepEqual(tokenizeInput('https://bradrichter.co/I/hate/primes.html?test=abc&test2=abcd'), ['bradrichter', 'co', 'i', 'hate', 'primes', 'html', 'test', 'abc', 'test2', 'abcd', 'https:']) - }) - it('does not include hash', function () { - assert.deepEqual(tokenizeInput('https://bradrichter.co/I/hate/primes.html?test=abc#testing'), ['testing', 'bradrichter', 'co', 'i', 'hate', 'primes', 'html', 'test', 'abc', 'https:']) - }) - it('spaces get tokenized', function () { - assert.deepEqual(tokenizeInput('brad\thates primes'), ['brad', 'hates', 'primes']) - }) - it('periods get tokenized', function () { - assert.deepEqual(tokenizeInput('brad.hates.primes'), ['brad', 'hates', 'primes']) - }) - it('/ gets tokenized', function () { - assert.deepEqual(tokenizeInput('brad/hates/primes'), ['brad', 'hates', 'primes']) - }) - it('\\ gets tokenized', function () { - assert.deepEqual(tokenizeInput('brad\\hates\\primes'), ['brad', 'hates', 'primes']) - }) - it('can tokenize site objects', function () { - assert.deepEqual(tokenizeInput(Immutable.fromJS(site1)), ['do', 'not', 'use', '3', 'for', 'items', 'because', 'it', 'is', 'prime', 'www', 'bradrichter', 'co', 'bad_numbers', '3', 'https:']) - }) - it('non URLs get tokenized', function () { - assert.deepEqual(tokenizeInput('hello world Greatest...Boss...Ever'), ['hello', 'world', 'greatest', 'boss', 'ever']) - }) - }) - - const checkResult = (inputQuery, expectedResults, cb) => { - query(inputQuery).then((results) => { - siteEqual(results, expectedResults) - cb() - }) - } - - describe('not initialized query', function () { - it('returns no results if not initialized', function (cb) { - checkResult('hello', [], cb) - }) - }) - - describe('query', function () { - let sites - before(function (cb) { - sites = Immutable.fromJS([site1, site2, site3, site4, site5]) - this.clock = sinon.useFakeTimers() - init(sites).then(cb.bind(null, null)) - this.clock.tick(1510) - }) - after(function () { - this.clock.restore() - }) - it('can query with empty string', function (cb) { - checkResult('', [], cb) - }) - it('can query with null', function (cb) { - checkResult(null, [], cb) - }) - it('can query with undefined', function (cb) { - checkResult(undefined, [], cb) - }) - it('returns an empty array when there are no matches', function (cb) { - checkResult('hello', [], cb) - }) - it('returns matched result on an exact token', function (cb) { - checkResult('bradrichter', [sites.get(0), sites.get(2)], cb) - }) - it('returns matched result on a token prefix', function (cb) { - checkResult('brad', [sites.get(0), sites.get(2), sites.get(3).delete('count')], cb) - }) - it('returns no results on input that has a token as a prefix', function (cb) { - checkResult('bradrichterhatesprimes.com', [], cb) - }) - it('can query on title', function (cb) { - checkResult('back', [sites.get(1)], cb) - }) - it('can query on multiple tokens in different order', function (cb) { - checkResult('back really', [sites.get(1)], cb) - }) - it('all tokens must match, not just some', function (cb) { - checkResult('brave brad', [], cb) - }) - }) - - describe('query', function () { - describe('sorts results by location', function () { - before(function (cb) { - const sites = Immutable.fromJS([{ - location: 'https://brave.com/twi' - }, { - location: 'https://twitter.com/brave' - }, { - location: 'https://twitter.com/brianbondy' - }, { - location: 'https://twitter.com/_brianclif' - }, { - location: 'https://twitter.com/cezaraugusto' - }, { - location: 'https://bbondy.com/twitter' - }, { - location: 'https://twitter.com' - }, { - location: 'https://twitter.com/i/moments' - }]) - init(sites).then(cb.bind(null, null)) - }) - it('orders shortest match first', function (cb) { - query('twitter.com').then((results) => { - siteEqual(results[0], Immutable.fromJS({ location: 'https://twitter.com' })) - cb() - }) - }) - it('matches prefixes first', function (cb) { - query('twi').then((results) => { - siteEqual(results[0], Immutable.fromJS({ location: 'https://twitter.com' })) - cb() - }) - }) - it('closest to the left match wins', function (cb) { - query('twitter.com brian').then((results) => { - siteEqual(results[0], Immutable.fromJS({ location: 'https://twitter.com/brianbondy' })) - cb() - }) - }) - it('matches based on tokens and not exactly', function (cb) { - query('twitter.com/moments').then((results) => { - siteEqual(results[0], Immutable.fromJS({ location: 'https://twitter.com/i/moments' })) - cb() - }) - }) - }) - describe('sorts results by count', function () { - describe('with lastAccessedTime', function () { - before(function (cb) { - const lastAccessedTime = 1494958046427 - this.page2 = { - location: 'https://brave.com/page2', - lastAccessedTime, - count: 20 - } - const sites = Immutable.fromJS([{ - location: 'https://brave.com/page1', - lastAccessedTime, - count: 5 - }, this.page2, { - location: 'https://brave.com/page3', - lastAccessedTime, - count: 2 - }]) - init(sites).then(cb.bind(null, null)) - }) - it('highest count first', function (cb) { - query('https://brave.com/page').then((results) => { - siteEqual(results[0], Immutable.fromJS(this.page2)) - cb() - }) - }) - }) - describe('without last access time', function () { - before(function (cb) { - this.page2 = { - location: 'https://brave.com/page2', - count: 20 - } - const sites = Immutable.fromJS([{ - location: 'https://brave.com/page1', - count: 5 - }, this.page2, { - location: 'https://brave.com/page3', - count: 2 - }]) - init(sites).then(cb.bind(null, null)) - }) - it('highest count first', function (cb) { - query('https://brave.com/page').then((results) => { - siteEqual(results[0], Immutable.fromJS(this.page2)) - cb() - }) - }) - }) - }) - describe('sorts results by lastAccessTime', function () { - describe('with counts', function () { - before(function (cb) { - this.site = { - location: 'https://bravebrowser.com/page2', - lastAccessedTime: 1494958046427, // most recent - count: 1 - } - const sites = Immutable.fromJS([{ - location: 'https://bravez.com/page1', - lastAccessedTime: 1, - count: 1 - }, { - location: 'https://bravebrowser.com/page1', - lastAccessedTime: 1494957046426, - count: 1 - }, this.site, { - location: 'https://bravebrowser.com/page3', - lastAccessedTime: 1494957046437, - count: 1 - }]) - init(sites).then(cb.bind(null, null)) - }) - it('items with lastAccessTime of 1 get ignored (signifies preloaded default)', function (cb) { - query('https://bravez.com/page').then((results) => { - assert.equal(results.length, 0) - cb() - }) - }) - it('most recently accessed get sorted first', function (cb) { - query('bravebrowser').then((results) => { - siteEqual(results[0], Immutable.fromJS(this.site)) - cb() - }) - }) - }) - describe('without counts', function () { - before(function (cb) { - this.site = { - location: 'https://bravebrowser.com/page2', - lastAccessedTime: 1494958046427 // most recent - } - const sites = Immutable.fromJS([{ - location: 'https://bravez.com/page1', - lastAccessedTime: 1 - }, { - location: 'https://bravebrowser.com/page1', - lastAccessedTime: 1494957046426 - }, this.site, { - location: 'https://bravebrowser.com/page3', - lastAccessedTime: 1494957046437 - }]) - init(sites).then(cb.bind(null, null)) - }) - it('items with lastAccessTime of 1 get ignored (signifies preloaded default)', function (cb) { - query('https://bravez.com/page').then((results) => { - assert.equal(results.length, 0) - cb() - }) - }) - it('most recently accessed get sorted first', function (cb) { - query('bravebrowser').then((results) => { - siteEqual(results[0], Immutable.fromJS(this.site)) - cb() - }) - }) - }) - }) - }) - - describe('add sites after init', function () { - before(function (cb) { - const sites = [site1, site2, site3, site4] - init(sites).then(() => { - add({ - location: 'https://slack.com' - }) - }).then(cb.bind(null, null)) - }) - it('can be found', function (cb) { - checkResult('slack', [Immutable.fromJS({ location: 'https://slack.com' })], cb) - }) - it('adding twice results in 1 result only with latest results', function (cb) { - const newSite = { - location: 'https://slack.com', - count: 30, - title: 'SlickSlack' - } - add(newSite) - checkResult('slack', [Immutable.fromJS(newSite)], cb) - }) - it('can add simple strings', function (cb) { - add({ - location: 'https://slashdot.org' - }) - checkResult('slash', [Immutable.fromJS({ location: 'https://slashdot.org' })], cb) - }) - it('can add Immutable objects', function (cb) { - add(Immutable.fromJS({ - location: 'https://microsoft.com' - })) - checkResult('micro', [Immutable.fromJS({ location: 'https://microsoft.com' })], cb) - }) - }) + runMuonCompatibleTests('urlutil', components) }) diff --git a/test/unit/app/common/lib/siteSuggestionsTestComponents.js b/test/unit/app/common/lib/siteSuggestionsTestComponents.js new file mode 100644 index 00000000000..76591ae4df0 --- /dev/null +++ b/test/unit/app/common/lib/siteSuggestionsTestComponents.js @@ -0,0 +1,338 @@ +const {tokenizeInput, init, query, add} = require('../../../../../app/common/lib/siteSuggestions') +const Immutable = require('immutable') +const lolex = require('lolex') + +const site1 = { + location: 'https://www.bradrichter.co/bad_numbers/3', + title: 'Do not use 3 for items because it is prime' +} +const site2 = { + location: 'https://www.brave.com', + title: 'No really, take back the web' +} +const site3 = { + location: 'https://www.bradrichter.co/bad_numbers/5', + title: 'Do not use 5 it is so bad, try 6 instead. Much better.' +} +const site4 = { + location: 'https://www.designers.com/brad', + title: 'Brad Saves The World!', + count: 50 +} +// Same as site4 but added after in init, should be ignored. +const site5 = { + location: 'https://www.designers.com/brad', + title: 'Brad Saves The World!' +} +const sites = Immutable.fromJS([site1, site2, site3, site4, site5]) + +// Compares 2 sites via deepEqual while first clearing out cached data +const siteEqual = (test, actual, expected) => { + test.equal(actual.constructor, expected.constructor) + if (expected.constructor === Array) { + test.equal(actual.length, expected.length) + for (let i = 0; i < actual.length; i++) { + test.deepEqual(actual[i].delete('parsedUrl').toJS(), expected[i].delete('parsedUrl').toJS()) + } + } else { + const a = Object.assign({}, actual) + delete a.parsedUrl + const e = Object.assign({}, expected) + delete e.parsedUrl + test.deepEqual(a, e) + } +} + +const checkResult = (test, inputQuery, expectedResults, cb) => { + return query(inputQuery).then((results) => { + siteEqual(test, results, expectedResults) + cb() + }) +} + +module.exports = { + 'tokenizeInput': { + 'empty string has no tokens': (test) => { + test.deepEqual(tokenizeInput(''), []) + }, + 'undefined has no tokens': (test) => { + test.deepEqual(tokenizeInput(null), []) + }, + 'null has no tokens': (test) => { + test.deepEqual(tokenizeInput(undefined), []) + }, + 'lowercases tokens': (test) => { + test.deepEqual(tokenizeInput('BRaD HaTES PRIMES'), ['brad', 'hates', 'primes']) + }, + 'includes protocol': (test) => { + test.deepEqual(tokenizeInput('https://bradrichter.co/I/hate/primes.html'), ['bradrichter', 'co', 'i', 'hate', 'primes', 'html', 'https:']) + }, + 'includes query': (test) => { + test.deepEqual(tokenizeInput('https://bradrichter.co/I/hate/primes.html?test=abc&test2=abcd'), ['bradrichter', 'co', 'i', 'hate', 'primes', 'html', 'test', 'abc', 'test2', 'abcd', 'https:']) + }, + 'does not include hash': (test) => { + test.deepEqual(tokenizeInput('https://bradrichter.co/I/hate/primes.html?test=abc#testing'), ['testing', 'bradrichter', 'co', 'i', 'hate', 'primes', 'html', 'test', 'abc', 'https:']) + }, + 'spaces get tokenized': (test) => { + test.deepEqual(tokenizeInput('brad\thates primes'), ['brad', 'hates', 'primes']) + }, + 'periods get tokenized': (test) => { + test.deepEqual(tokenizeInput('brad.hates.primes'), ['brad', 'hates', 'primes']) + }, + 'forward slash gets tokenized': (test) => { + test.deepEqual(tokenizeInput('brad/hates/primes'), ['brad', 'hates', 'primes']) + }, + 'backslash gets tokenized': (test) => { + test.deepEqual(tokenizeInput('brad\\hates\\primes'), ['brad', 'hates', 'primes']) + }, + 'can tokenize site objects': (test) => { + test.deepEqual(tokenizeInput(Immutable.fromJS(site1)), ['do', 'not', 'use', '3', 'for', 'items', 'because', 'it', 'is', 'prime', 'www', 'bradrichter', 'co', 'bad_numbers', '3', 'https:']) + }, + 'non URLs get tokenized': (test) => { + test.deepEqual(tokenizeInput('hello world Greatest...Boss...Ever'), ['hello', 'world', 'greatest', 'boss', 'ever']) + } + }, + + 'not initialized query': { + 'returns no results if not initialized': (test, cb) => { + checkResult(test, 'hello', [], cb).catch(cb) + } + }, + + 'basic query': { + before: function (cb) { + this.clock = lolex.install() + init(sites).then(cb.bind(null, null)) + this.clock.tick(1510) + }, + after: function () { + this.clock.uninstall() + }, + 'can query with empty string': (test, cb) => { + checkResult(test, '', [], cb).catch(cb) + }, + 'can query with null': (test, cb) => { + checkResult(test, null, [], cb).catch(cb) + }, + 'can query with undefined': (test, cb) => { + checkResult(test, undefined, [], cb).catch(cb) + }, + 'returns an empty array when there are no matches': (test, cb) => { + checkResult(test, 'hello', [], cb).catch(cb) + }, + 'returns matched result on an exact token': (test, cb) => { + checkResult(test, 'bradrichter', [sites.get(0), sites.get(2)], cb).catch(cb) + }, + 'returns matched result on a token prefix': (test, cb) => { + checkResult(test, 'brad', [sites.get(0), sites.get(2), sites.get(3).delete('count')], cb).catch(cb) + }, + 'returns no results on input that has a token as a prefix': (test, cb) => { + checkResult(test, 'bradrichterhatesprimes.com', [], cb).catch(cb) + }, + 'can query on title': (test, cb) => { + checkResult(test, 'back', [sites.get(1)], cb).catch(cb) + }, + 'can query on multiple tokens in different order': (test, cb) => { + checkResult(test, 'back really', [sites.get(1)], cb).catch(cb) + }, + 'all tokens must match, not just some': (test, cb) => { + checkResult(test, 'brave brad', [], cb).catch(cb) + } + }, + + 'query': { + 'sorts results by location': { + before: (cb) => { + const sites = Immutable.fromJS([{ + location: 'https://brave.com/twi' + }, { + location: 'https://twitter.com/brave' + }, { + location: 'https://twitter.com/brianbondy' + }, { + location: 'https://twitter.com/_brianclif' + }, { + location: 'https://twitter.com/cezaraugusto' + }, { + location: 'https://bbondy.com/twitter' + }, { + location: 'https://twitter.com' + }, { + location: 'https://twitter.com/i/moments' + }]) + init(sites).then(cb.bind(null, null)) + }, + 'orders shortest match first': (test, cb) => { + query('twitter.com').then((results) => { + siteEqual(test, results[0], Immutable.fromJS({ location: 'https://twitter.com' })) + cb() + }).catch(cb) + }, + 'matches prefixes first': (test, cb) => { + query('twi').then((results) => { + siteEqual(test, results[0], Immutable.fromJS({ location: 'https://twitter.com' })) + cb() + }).catch(cb) + }, + 'closest to the left match wins': (test, cb) => { + query('twitter.com brian').then((results) => { + siteEqual(test, results[0], Immutable.fromJS({ location: 'https://twitter.com/brianbondy' })) + cb() + }).catch(cb) + }, + 'matches based on tokens and not exactly': (test, cb) => { + query('twitter.com/moments').then((results) => { + siteEqual(test, results[0], Immutable.fromJS({ location: 'https://twitter.com/i/moments' })) + cb() + }).catch(cb) + } + }, + 'sorts results by count': { + 'with lastAccessedTime': { + before: function (cb) { + const lastAccessedTime = 1494958046427 + this.page2 = { + location: 'https://brave.com/page2', + lastAccessedTime, + count: 20 + } + const sites = Immutable.fromJS([{ + location: 'https://brave.com/page1', + lastAccessedTime, + count: 5 + }, this.page2, { + location: 'https://brave.com/page3', + lastAccessedTime, + count: 2 + }]) + init(sites).then(cb.bind(null, null)) + }, + 'highest count first': function (test, cb) { + query('https://brave.com/page').then((results) => { + siteEqual(test, results[0], Immutable.fromJS(this.page2)) + cb() + }).catch(cb) + } + }, + 'without last access time': { + before: function (cb) { + this.page2 = { + location: 'https://brave.com/page2', + count: 20 + } + const sites = Immutable.fromJS([{ + location: 'https://brave.com/page1', + count: 5 + }, this.page2, { + location: 'https://brave.com/page3', + count: 2 + }]) + init(sites).then(cb.bind(null, null)) + }, + 'highest count first': function (test, cb) { + query('https://brave.com/page').then((results) => { + siteEqual(test, results[0], Immutable.fromJS(this.page2)) + cb() + }).catch(cb) + } + } + }, + 'sorts results by lastAccessTime': { + 'with counts': { + before: function (cb) { + this.site = { + location: 'https://bravebrowser.com/page2', + lastAccessedTime: 1494958046427, // most recent + count: 1 + } + const sites = Immutable.fromJS([{ + location: 'https://bravez.com/page1', + lastAccessedTime: 1, + count: 1 + }, { + location: 'https://bravebrowser.com/page1', + lastAccessedTime: 1494957046426, + count: 1 + }, this.site, { + location: 'https://bravebrowser.com/page3', + lastAccessedTime: 1494957046437, + count: 1 + }]) + init(sites).then(cb.bind(null, null)) + }, + 'items with lastAccessTime of 1 get ignored (signifies preloaded default)': (test, cb) => { + query('https://bravez.com/page').then((results) => { + test.equal(results.length, 0) + cb() + }).catch(cb) + }, + 'most recently accessed get sorted first': function (test, cb) { + query('bravebrowser').then((results) => { + siteEqual(test, results[0], Immutable.fromJS(this.site)) + cb() + }).catch(cb) + } + }, + 'without counts': { + before: function (cb) { + this.site = { + location: 'https://bravebrowser.com/page2', + lastAccessedTime: 1494958046427 // most recent + } + const sites = Immutable.fromJS([{ + location: 'https://bravez.com/page1', + lastAccessedTime: 1 + }, { + location: 'https://bravebrowser.com/page1', + lastAccessedTime: 1494957046426 + }, this.site, { + location: 'https://bravebrowser.com/page3', + lastAccessedTime: 1494957046437 + }]) + init(sites).then(cb.bind(null, null)) + }, + 'items with lastAccessTime of 1 get ignored (signifies preloaded default)': (test, cb) => { + query('https://bravez.com/page').then((results) => { + test.equal(results.length, 0) + cb() + }).catch(cb) + }, + 'most recently accessed get sorted first': function (test, cb) { + query('bravebrowser').then((results) => { + siteEqual(test, results[0], Immutable.fromJS(this.site)) + cb() + }).catch(cb) + } + } + } + }, + + 'add sites after init': { + before: (cb) => { + const sites = [site1, site2, site3, site4] + init(sites).then(() => { + add({ location: 'https://slack.com' }) + }).then(cb.bind(null, null)) + }, + 'can be found': (test, cb) => { + checkResult(test, 'slack', [Immutable.fromJS({ location: 'https://slack.com' })], cb).catch(cb) + }, + 'adding twice results in 1 result only with latest results': (test, cb) => { + const newSite = { + location: 'https://slack.com', + count: 30, + title: 'SlickSlack' + } + add(newSite) + checkResult(test, 'slack', [Immutable.fromJS(newSite)], cb).catch(cb) + }, + 'can add simple strings': (test, cb) => { + add({ location: 'https://slashdot.org' }) + checkResult(test, 'slash', [Immutable.fromJS({ location: 'https://slashdot.org' })], cb).catch(cb) + }, + 'can add Immutable objects': (test, cb) => { + add(Immutable.fromJS({ location: 'https://microsoft.com' })) + checkResult(test, 'micro', [Immutable.fromJS({ location: 'https://microsoft.com' })], cb).catch(cb) + } + } +} diff --git a/test/unit/lib/urlutilTest.js b/test/unit/lib/urlutilTest.js index 459094e951d..c103852f11c 100644 --- a/test/unit/lib/urlutilTest.js +++ b/test/unit/lib/urlutilTest.js @@ -1,565 +1,4 @@ -/* global describe, it */ -const urlUtil = require('../../../js/lib/urlutil') -const assert = require('assert') +const runMuonCompatibleTests = require('../runMuonCompatibleTests') +const components = require('./urlutilTestComponents') -require('../braveUnit') - -describe('urlutil', function () { - describe('getScheme', function () { - it('null for empty', function () { - assert.equal(urlUtil.getScheme('/file/path/to/file'), null) - }) - it('localhost: for localhost', function () { - assert.equal(urlUtil.getScheme('localhost://127.0.0.1'), 'localhost:') - }) - it('gets scheme with :', function () { - assert.equal(urlUtil.getScheme('data:datauri'), 'data:') - }) - it('host:port is not recognized as a scheme', function () { - assert.equal(urlUtil.getScheme('localhost:8089'), null) - }) - it('gets scheme with ://', function () { - assert.equal(urlUtil.getScheme('http://www.brave.com'), 'http://') - }) - }) - - describe('prependScheme', function () { - it('returns null when input is null', function () { - assert.equal(urlUtil.prependScheme(null), null) - }) - it('returns undefined when input is undefined', function () { - assert.equal(urlUtil.prependScheme(), undefined) - }) - it('prepends file:// to absolute file path', function () { - assert.equal(urlUtil.prependScheme('/file/path/to/file'), 'file:///file/path/to/file') - }) - it('defaults to http://', function () { - assert.equal(urlUtil.prependScheme('www.brave.com'), 'http://www.brave.com') - }) - it('keeps schema if already exists', function () { - assert.equal(urlUtil.prependScheme('https://www.brave.com'), 'https://www.brave.com') - }) - }) - - describe('isNotURL', function () { - describe('returns false when input:', function () { - it('is a valid URL', function () { - assert.equal(urlUtil.isNotURL('brave.com'), false) - }) - it('is an absolute file path without scheme', function () { - assert.equal(urlUtil.isNotURL('/file/path/to/file'), false) - }) - it('is an absolute file path with scheme', function () { - assert.equal(urlUtil.isNotURL('file:///file/path/to/file'), false) - }) - describe('for special pages', function () { - it('is a data URI', function () { - assert.equal(urlUtil.isNotURL('data:text/html,hi'), false) - }) - it('is a view source URL', function () { - assert.equal(urlUtil.isNotURL('view-source://url-here'), false) - }) - it('is a mailto link', function () { - assert.equal(urlUtil.isNotURL('mailto:brian@brave.com'), false) - }) - it('is an about page', function () { - assert.equal(urlUtil.isNotURL('about:preferences'), false) - }) - it('is a chrome-extension page', function () { - assert.equal(urlUtil.isNotURL('chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/cast_sender.js'), false) - }) - it('is a magnet URL', function () { - assert.equal(urlUtil.isNotURL('chrome://gpu'), false) - }) - it('is a chrome page', function () { - assert.equal(urlUtil.isNotURL('magnet:?xt=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C'), false) - }) - }) - it('contains a hostname and port number', function () { - assert.equal(urlUtil.isNotURL('someBraveServer:8089'), false) - }) - it('starts or ends with whitespace', function () { - assert.equal(urlUtil.isNotURL(' http://brave.com '), false) - assert.equal(urlUtil.isNotURL('\n\nhttp://brave.com\n\n'), false) - assert.equal(urlUtil.isNotURL('\t\thttp://brave.com\t\t'), false) - }) - it('is a URL which contains basic auth user/pass', function () { - assert.equal(urlUtil.isNotURL('http://username:password@example.com'), false) - }) - it('is localhost (case-insensitive)', function () { - assert.equal(urlUtil.isNotURL('LoCaLhOsT'), false) - }) - it('is a hostname (not a domain)', function () { - assert.equal(urlUtil.isNotURL('http://computer001/phpMyAdmin'), false) - }) - it('ends with period (input contains a forward slash and domain)', function () { - assert.equal(urlUtil.isNotURL('brave.com/test/cc?_ri_=3vv-8-e.'), false) - }) - it('is a string with whitespace but has schema', function () { - assert.equal(urlUtil.isNotURL('https://wwww.brave.com/test space.jpg'), false) - }) - it('has custom protocol', function () { - assert.equal(urlUtil.isNotURL('brave://test'), false) - }) - }) - - describe('returns true when input:', function () { - it('is null or undefined', function () { - assert.equal(urlUtil.isNotURL(), true) - assert.equal(urlUtil.isNotURL(null), true) - }) - it('is not a string', function () { - assert.equal(urlUtil.isNotURL(false), true) - assert.equal(urlUtil.isNotURL(333.449), true) - }) - it('is a quoted string', function () { - assert.equal(urlUtil.isNotURL('"search term here"'), true) - }) - it('is a pure string (no TLD)', function () { - assert.equal(urlUtil.isNotURL('brave'), true) - }) - describe('search query', function () { - it('starts with ?', function () { - assert.equal(urlUtil.isNotURL('?brave'), true) - }) - it('has a question mark followed by a space', function () { - assert.equal(urlUtil.isNotURL('? brave'), true) - }) - it('starts with .', function () { - assert.equal(urlUtil.isNotURL('.brave'), true) - }) - it('ends with . (input does NOT contain a forward slash)', function () { - assert.equal(urlUtil.isNotURL('brave.'), true) - }) - it('ends with period (input contains only a forward slash)', function () { - assert.equal(urlUtil.isNotURL('brave/com/test/cc?_ri_=3vv-8-e.'), true) - }) - }) - it('is a string with schema but invalid domain name', function () { - assert.equal(urlUtil.isNotURL('https://www.bra ve.com/test space.jpg'), true) - }) - it('contains more than one word', function () { - assert.equal(urlUtil.isNotURL('brave is cool'), true) - }) - it('is not an about page / view source / data URI / mailto / etc', function () { - assert.equal(urlUtil.isNotURL('not-a-chrome-extension:'), true) - assert.equal(urlUtil.isNotURL('mailtoo:'), true) - }) - it('is a URL (without protocol) which contains basic auth user/pass', function () { - assert.equal(urlUtil.isNotURL('username:password@example.com'), true) - }) - it('has space in schema', function () { - assert.equal(urlUtil.isNotURL('https ://brave.com'), true) - }) - }) - }) - - describe('getUrlFromInput', function () { - it('returns empty string when input is null', function () { - assert.equal(urlUtil.getUrlFromInput(null), '') - }) - it('returns empty string when input is undefined', function () { - assert.equal(urlUtil.getUrlFromInput(), '') - }) - it('calls prependScheme', function () { - assert.equal(urlUtil.getUrlFromInput('/file/path/to/file'), 'file:///file/path/to/file') - }) - }) - - describe('isURL', function () { - it('returns !isNotURL', function () { - assert.equal(urlUtil.isURL('brave.com'), !urlUtil.isNotURL('brave.com')) - assert.equal(urlUtil.isURL('brave is cool'), !urlUtil.isNotURL('brave is cool')) - assert.equal(urlUtil.isURL('mailto:brian@brave.com'), !urlUtil.isNotURL('mailto:brian@brave.com')) - }) - }) - - describe('isFileType', function () { - it('relative file', function () { - assert.equal(urlUtil.isFileType('/file/abc/test.pdf', 'pdf'), true) - }) - it('relative path', function () { - assert.equal(urlUtil.isFileType('/file/abc/test', 'pdf'), false) - }) - it('JPG URL', function () { - assert.equal(urlUtil.isFileType('http://example.com/test/ABC.JPG?a=b#test', 'jpg'), true) - }) - it('non-JPG URL', function () { - assert.equal(urlUtil.isFileType('http://example.com/test/jpg', 'jpg'), false) - }) - it('invalid URL', function () { - assert.equal(urlUtil.isFileType('foo', 'jpg'), false) - }) - }) - - describe('toPDFJSLocation', function () { - const baseUrl = 'chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/' - it('pdf', function () { - assert.equal(urlUtil.toPDFJSLocation('http://abc.com/test.pdf'), baseUrl + 'content/web/viewer.html?file=http%3A%2F%2Fabc.com%2Ftest.pdf') - }) - it('non-pdf', function () { - assert.equal(urlUtil.toPDFJSLocation('http://abc.com/test.pdf.txt'), 'http://abc.com/test.pdf.txt') - }) - it('file url', function () { - assert.equal(urlUtil.toPDFJSLocation('file://abc.com/test.pdf.txt'), 'file://abc.com/test.pdf.txt') - }) - it('empty', function () { - assert.equal(urlUtil.toPDFJSLocation(''), '') - }) - }) - - describe('getPDFViewerUrl', function () { - const baseUrl = 'chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/content/web/viewer.html?file=' - it('regular url', function () { - assert.equal(urlUtil.getPDFViewerUrl('http://example.com'), baseUrl + 'http%3A%2F%2Fexample.com') - }) - it('file url', function () { - assert.equal(urlUtil.getPDFViewerUrl('file:///Users/yan/some files/test.pdf'), baseUrl + 'file%3A%2F%2F%2FUsers%2Fyan%2Fsome%20files%2Ftest.pdf') - }) - }) - - describe('getHostname', function () { - it('returns undefined if the URL is invalid', function () { - assert.equal(urlUtil.getHostname(null), undefined) - }) - it('returns the host field (including port number)', function () { - assert.equal(urlUtil.getHostname('https://brave.com:8080/test/'), 'brave.com:8080') - }) - it('allows you to exclude the port number', function () { - assert.equal(urlUtil.getHostname('https://brave.com:8080/test/', true), 'brave.com') - }) - }) - - describe('getHostnamePatterns', function () { - it('gets bare domain hostname patterns', function () { - // XXX: *.com probably should be excluded - assert.deepEqual(urlUtil.getHostnamePatterns('http://brave.com'), - ['brave.com', '*.com', 'brave.*']) - }) - it('gets subdomain hostname patterns', function () { - assert.deepEqual(urlUtil.getHostnamePatterns('https://bar.brave.com'), - ['bar.brave.com', - '*.brave.com', - 'bar.*.com', - 'bar.brave.*']) - assert.deepEqual(urlUtil.getHostnamePatterns('https://foo.bar.brave.com'), - ['foo.bar.brave.com', - '*.bar.brave.com', - 'foo.*.brave.com', - 'foo.bar.*.com', - 'foo.bar.brave.*', - '*.brave.com']) - }) - }) - - describe('getLocationIfPDF', function () { - it('gets location for PDF JS URL', function () { - assert.equal(urlUtil.getLocationIfPDF('chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/https://www.blackhat.co…king-Kernel-Address-Space-Layout-Randomization-KASLR-With-Intel-TSX-wp.pdf'), - 'https://www.blackhat.co…king-Kernel-Address-Space-Layout-Randomization-KASLR-With-Intel-TSX-wp.pdf') - }) - it('gets location for PDF JS viewer URL', function () { - assert.equal(urlUtil.getLocationIfPDF('chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/content/web/viewer.html?file=http%3A%2F%2Funec.edu.az%2Fapplication%2Fuploads%2F2014%2F12%2Fpdf-sample.pdf'), - 'http://unec.edu.az/application/uploads/2014/12/pdf-sample.pdf') - }) - it('does not remove wayback machine url location for PDF JS URL', function () { - assert.equal(urlUtil.getLocationIfPDF('chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/https://web.archive.org/web/20160106152308/http://stlab.adobe.com/wiki/images/d/d3/Test.pdf'), - 'https://web.archive.org/web/20160106152308/http://stlab.adobe.com/wiki/images/d/d3/Test.pdf') - }) - it('does not modify location for non-pdf URL', function () { - assert.equal(urlUtil.getLocationIfPDF('https://www.blackhat.co…king-Kernel-Address-Space-Layout-Randomization-KASLR-With-Intel-TSX-wp.pdf'), - 'https://www.blackhat.co…king-Kernel-Address-Space-Layout-Randomization-KASLR-With-Intel-TSX-wp.pdf') - assert.equal(urlUtil.getLocationIfPDF('chrome-extension://blank'), 'chrome-extension://blank') - assert.equal(urlUtil.getLocationIfPDF(null), null) - }) - it('gets location for file: PDF URL', function () { - let url = 'chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/file:///Users/yan/Downloads/test.pdf' - assert.equal(urlUtil.getLocationIfPDF(url), 'file:///Users/yan/Downloads/test.pdf') - }) - }) - - describe('getDefaultFaviconUrl', function () { - it('returns empty string if input is not a URL', function () { - assert.equal(urlUtil.getDefaultFaviconUrl('invalid-url-goes-here'), '') - }) - it('returns the default favicon URL when given a valid URL', function () { - assert.equal(urlUtil.getDefaultFaviconUrl('https://brave.com'), 'https://brave.com/favicon.ico') - }) - it('includes the port in the response when given a valid URL with a port number', function () { - assert.equal(urlUtil.getDefaultFaviconUrl('https://brave.com:8080'), 'https://brave.com:8080/favicon.ico') - }) - }) - - describe('getPunycodeUrl', function () { - it('returns original string if input is ASCII', function () { - assert.equal(urlUtil.getPunycodeUrl('invalid-url-goes-here'), 'invalid-url-goes-here') - }) - it('returns punycode ASCII string if input is non-ASCII', function () { - assert.equal(urlUtil.getPunycodeUrl('ebаy.com'), 'xn--eby-7cd.com') - }) - it('returns the punycode URL when given a valid URL', function () { - assert.equal(urlUtil.getPunycodeUrl('http://brave:brave@ebаy.com:1234/brave#brave'), 'http://brave:brave@xn--eby-7cd.com:1234/brave#brave') - }) - it('returns the punycode URL when given a URL contains @', function () { - assert.equal(urlUtil.getPunycodeUrl('ebаy.com/@ebаy.com'), 'xn--eby-7cd.com/@xn--eby-7cd.com') - }) - }) - - describe('isPotentialPhishingUrl', function () { - it('returns false if input is not a URL', function () { - assert.equal(urlUtil.isPotentialPhishingUrl(null), false) - }) - it('returns false if input is a regular URL', function () { - assert.equal(urlUtil.isPotentialPhishingUrl(' https://google.com'), false) - }) - it('returns true if input is a data URL', function () { - assert.equal(urlUtil.isPotentialPhishingUrl('data:text/html,'), true) - }) - it('returns true if input is a blob URL', function () { - assert.equal(urlUtil.isPotentialPhishingUrl(' BLOB:foo '), true) - }) - }) - - describe('isFileScheme', function () { - describe('returns true when input:', function () { - it('is an absolute file path with scheme', function () { - assert.equal(urlUtil.isFileScheme('file:///file/path/to/file'), true) - }) - }) - describe('returns false when input:', function () { - it('is an absolute file path without scheme', function () { - assert.equal(urlUtil.isFileScheme('/file/path/to/file'), false) - }) - it('is a URL', function () { - assert.equal(urlUtil.isFileScheme('http://brave.com'), false) - }) - it('has custom protocol', function () { - assert.equal(urlUtil.isFileScheme('brave://test'), false) - }) - }) - }) - - describe('getDisplayHost', function () { - it('url is http', function () { - const result = urlUtil.getDisplayHost('http://brave.com') - assert.equal(result, 'brave.com') - }) - - it('url is https', function () { - const result = urlUtil.getDisplayHost('https://brave.com') - assert.equal(result, 'brave.com') - }) - - it('url is file', function () { - const result = urlUtil.getDisplayHost('file://brave.text') - assert.equal(result, 'file://brave.text') - }) - }) - - describe('getOrigin', function () { - it('returns file:/// for any file url', function () { - assert.strictEqual(urlUtil.getOrigin('file://'), 'file:///') - assert.strictEqual(urlUtil.getOrigin('file:///'), 'file:///') - assert.strictEqual(urlUtil.getOrigin('file:///some'), 'file:///') - assert.strictEqual(urlUtil.getOrigin('file:///some/'), 'file:///') - assert.strictEqual(urlUtil.getOrigin('file:///some/path'), 'file:///') - }) - it('gets URL origin for simple url', function () { - assert.strictEqual(urlUtil.getOrigin('https://abc.bing.com'), 'https://abc.bing.com') - }) - it('gets URL origin for url with port', function () { - assert.strictEqual(urlUtil.getOrigin('https://bing.com:443/?test=1#abc'), 'https://bing.com:443') - }) - it('gets URL origin for IP host', function () { - assert.strictEqual(urlUtil.getOrigin('http://127.0.0.1:443/?test=1#abc'), 'http://127.0.0.1:443') - }) - it('gets URL origin for slashless protocol URL', function () { - assert.strictEqual(urlUtil.getOrigin('about:test/foo'), 'about:test') - }) - it('returns null for invalid URL', function () { - assert.strictEqual(urlUtil.getOrigin('abc'), null) - }) - it('returns null for empty URL', function () { - assert.strictEqual(urlUtil.getOrigin(''), null) - }) - it('returns null for null URL', function () { - assert.strictEqual(urlUtil.getOrigin(null), null) - }) - it('returns correct result for URL with hostname that is a scheme', function () { - assert.strictEqual(urlUtil.getOrigin('http://http/test'), 'http://http') - }) - }) - - describe('stripLocation', function () { - it('null scenario', function () { - const result = urlUtil.stripLocation(null) - assert.equal(result, '') - }) - - it('empty string', function () { - const result = urlUtil.stripLocation('') - assert.equal(result, '') - }) - - it('normal url without # or /', function () { - const result = urlUtil.stripLocation('https://brave.com') - assert.equal(result, 'https://brave.com') - }) - - it('normal url with # but not at the end', function () { - const result = urlUtil.stripLocation('https://brave.com#title') - assert.equal(result, 'https://brave.com#title') - }) - - it('normal url with # at the end', function () { - const result = urlUtil.stripLocation('https://brave.com#') - assert.equal(result, 'https://brave.com') - }) - - it('normal url with / at the end', function () { - const result = urlUtil.stripLocation('https://brave.com/') - assert.equal(result, 'https://brave.com') - }) - - it('normal url with /# at the end', function () { - const result = urlUtil.stripLocation('https://brave.com/#') - assert.equal(result, 'https://brave.com') - }) - - it('normal url with white space at the end', function () { - const result = urlUtil.stripLocation('https://brave.com ') - assert.equal(result, 'https://brave.com') - }) - }) - - describe('parseFaviconDataUrl', function () { - it('null scenario', function () { - const result = urlUtil.parseFaviconDataUrl(null) - assert.equal(result, null) - }) - it('empty string', function () { - const result = urlUtil.parseFaviconDataUrl('') - assert.equal(result, null) - }) - it('regular URL', function () { - const result = urlUtil.parseFaviconDataUrl('http://example.com') - assert.equal(result, null) - }) - it('non-image data URL', function () { - const result = urlUtil.parseFaviconDataUrl('data:text/plain;charset=UTF-8;page=21,the%20data:1234,5678') - assert.equal(result, null) - }) - it('non-base64 data URL', function () { - const result = urlUtil.parseFaviconDataUrl('data:image/jpg,foo') - assert.equal(result, null) - }) - it('no-extension data URL', function () { - const result = urlUtil.parseFaviconDataUrl('data:image/;base64,foo') - assert.equal(result, null) - }) - it('valid jpg', function () { - const jpg = 'data:image/jpeg;base64,' + - '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDADIiJSwlHzIsKSw4NTI7S31RS0VFS5ltc1p9tZ++u7Kf' + - 'r6zI4f/zyNT/16yv+v/9////////wfD/////////////2wBDATU4OEtCS5NRUZP/zq/O////////' + - '////////////////////////////////////////////////////////////wAARCAAYAEADAREA' + - '//AhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAQMAAgQF/8QAJRABAAIBBAEEAgMAAAAAAAAAAQIR' + - '//AAMSITEEEyJBgTORUWFx/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAA' + - '//AAD/2gAMAwEAAhEDEQA/AOgM52xQDrjvAV5Xv0vfKUALlTQfeBm0HThMNHXkL0Lw/swN5qgA8yT4' + - '//MCS1OEOJV8mBz9Z05yfW8iSx7p4j+jA1aD6Wj7ZMzstsfvAas4UyRHvjrAkC9KhpLMClQntlqFc2' + - '//X1gUj4viwVObKrddH9YDoHvuujAEuNV+bLwFS8XxdSr+Cq3Vf+4F5RgQl6ZR2p1eAzU/HX80YBYy' + - '//JLCuexwJCO2O1bwCRidAfWBSctswbI12GAJT3yiwFR7+MBjGK2g/WAJR3FdF84E2rK5VR0YH/9k=' - const expected = '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDADIiJSwlHzIsKSw4NTI7S31RS0VFS5ltc1p9tZ++u7Kf' + - 'r6zI4f/zyNT/16yv+v/9////////wfD/////////////2wBDATU4OEtCS5NRUZP/zq/O////////' + - '////////////////////////////////////////////////////////////wAARCAAYAEADAREA' + - '//AhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAQMAAgQF/8QAJRABAAIBBAEEAgMAAAAAAAAAAQIR' + - '//AAMSITEEEyJBgTORUWFx/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAA' + - '//AAD/2gAMAwEAAhEDEQA/AOgM52xQDrjvAV5Xv0vfKUALlTQfeBm0HThMNHXkL0Lw/swN5qgA8yT4' + - '//MCS1OEOJV8mBz9Z05yfW8iSx7p4j+jA1aD6Wj7ZMzstsfvAas4UyRHvjrAkC9KhpLMClQntlqFc2' + - '//X1gUj4viwVObKrddH9YDoHvuujAEuNV+bLwFS8XxdSr+Cq3Vf+4F5RgQl6ZR2p1eAzU/HX80YBYy' + - '//JLCuexwJCO2O1bwCRidAfWBSctswbI12GAJT3yiwFR7+MBjGK2g/WAJR3FdF84E2rK5VR0YH/9k=' - const result = urlUtil.parseFaviconDataUrl(jpg) - assert.deepEqual(result, {data: expected, ext: 'jpeg'}) - }) - it('valid png', function () { - const png = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU//5ErkJggg==' - const result = urlUtil.parseFaviconDataUrl(png) - assert.deepEqual(result, {data: 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU//5ErkJggg==', ext: 'png'}) - }) - }) - - describe('isInternalUrl', function () { - it('null scenario', function () { - const result = urlUtil.isInternalUrl(null) - assert.equal(result, false) - }) - it('localhost', function () { - const result = urlUtil.isInternalUrl('http://localhost:399/abc') - assert.equal(result, true) - }) - it('localhost.com', function () { - const result = urlUtil.isInternalUrl('http://localhost.com:399/abc') - assert.equal(result, false) - }) - it('invalid URL', function () { - const result = urlUtil.isInternalUrl('adsf') - assert.equal(result, false) - }) - it('local IP', function () { - const result = urlUtil.isInternalUrl('http://192.168.1.255:3000') - assert.equal(result, true) - const result2 = urlUtil.isInternalUrl('http://127.0.0.1/') - assert.equal(result2, true) - }) - it('remote IP', function () { - const result = urlUtil.isInternalUrl('http://54.0.0.1:3000') - assert.equal(result, false) - }) - it('local IPv6', function () { - const result = urlUtil.isInternalUrl('https://[::1]:3000') - assert.equal(result, true) - const result2 = urlUtil.isInternalUrl('http://[fe80::c12:79e9:bd20:31e1]/') - assert.equal(result2, true) - }) - it('remote IPv6', function () { - const result = urlUtil.isInternalUrl('http://[2001:4860:4860::8888]:8000') - assert.equal(result, false) - }) - it('.local URL', function () { - const result = urlUtil.isInternalUrl('https://adsf.local') - assert.equal(result, true) - }) - }) - - describe('isUrlPDF', function () { - it('null case', function () { - const result = urlUtil.isUrlPDF(null) - assert.equal(result, false) - }) - - it('url is not pdf', function () { - const result = urlUtil.isUrlPDF('https://clifton.io') - assert.equal(result, false) - }) - - it('url is pdf', function () { - const result = urlUtil.isUrlPDF('chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/http://www.test.com/test.pdf') - assert.equal(result, true) - }) - }) - - describe('getUrlFromPDFUrl', function () { - it('null case', function () { - const result = urlUtil.getUrlFromPDFUrl(null) - assert.equal(result, null) - }) - - it('url is not PDF', function () { - const result = urlUtil.getUrlFromPDFUrl('https://clifton.io') - assert.equal(result, 'https://clifton.io') - }) - - it('url is pdf', function () { - const result = urlUtil.getUrlFromPDFUrl('chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/http://www.test.com/test.pdf') - assert.equal(result, 'http://www.test.com/test.pdf') - }) - }) -}) +runMuonCompatibleTests('urlutil', components) diff --git a/test/unit/lib/urlutilTestComponents.js b/test/unit/lib/urlutilTestComponents.js new file mode 100644 index 00000000000..dc797f708b9 --- /dev/null +++ b/test/unit/lib/urlutilTestComponents.js @@ -0,0 +1,566 @@ +// lazy load requires for dual use in and outside muon +const urlUtil = () => require('../../../js/lib/urlutil') + +module.exports = { + 'getScheme': { + 'null for empty': (test) => { + test.equal(urlUtil().getScheme('/file/path/to/file'), null) + }, + 'localhost: for localhost': (test) => { + test.equal(urlUtil().getScheme('localhost://127.0.0.1'), 'localhost:') + }, + 'gets scheme with :': (test) => { + test.equal(urlUtil().getScheme('data:datauri'), 'data:') + }, + 'host:port is not recognized as a scheme': (test) => { + test.equal(urlUtil().getScheme('localhost:8089'), null) + }, + 'gets scheme with ://': (test) => { + test.equal(urlUtil().getScheme('http://www.brave.com'), 'http://') + } + }, + + 'prependScheme': { + 'returns null when input is null': (test) => { + test.equal(urlUtil().prependScheme(null), null) + }, + 'returns undefined when input is undefined': (test) => { + test.equal(urlUtil().prependScheme(), undefined) + }, + 'prepends file:// to absolute file path': (test) => { + test.equal(urlUtil().prependScheme('/file/path/to/file'), 'file:///file/path/to/file') + }, + 'defaults to http://': (test) => { + test.equal(urlUtil().prependScheme('www.brave.com'), 'http://www.brave.com') + }, + 'keeps schema if already exists': (test) => { + test.equal(urlUtil().prependScheme('https://www.brave.com'), 'https://www.brave.com') + } + }, + + 'isNotURL': { + 'returns false when input:': { + 'is a valid URL': (test) => { + test.equal(urlUtil().isNotURL('brave.com'), false) + }, + 'is an absolute file path without scheme': (test) => { + test.equal(urlUtil().isNotURL('/file/path/to/file'), false) + }, + 'is an absolute file path with scheme': (test) => { + test.equal(urlUtil().isNotURL('file:///file/path/to/file'), false) + }, + 'for special pages': { + 'is a data URI': (test) => { + test.equal(urlUtil().isNotURL('data:text/html,hi'), false) + }, + 'is a view source URL': (test) => { + test.equal(urlUtil().isNotURL('view-source://url-here'), false) + }, + 'is a mailto link': (test) => { + test.equal(urlUtil().isNotURL('mailto:brian@brave.com'), false) + }, + 'is an about page': (test) => { + test.equal(urlUtil().isNotURL('about:preferences'), false) + }, + 'is a chrome-extension page': (test) => { + test.equal(urlUtil().isNotURL('chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/cast_sender.js'), false) + }, + 'is a magnet URL': (test) => { + test.equal(urlUtil().isNotURL('chrome://gpu'), false) + }, + 'is a chrome page': (test) => { + test.equal(urlUtil().isNotURL('magnet:?xt=urn:sha1:YNCKHTQCWBTRNJIV4WNAE52SJUQCZO5C'), false) + } + }, + 'contains a hostname and port number': (test) => { + test.equal(urlUtil().isNotURL('someBraveServer:8089'), false) + }, + 'starts or ends with whitespace': (test) => { + test.equal(urlUtil().isNotURL(' http://brave.com '), false) + test.equal(urlUtil().isNotURL('\n\nhttp://brave.com\n\n'), false) + test.equal(urlUtil().isNotURL('\t\thttp://brave.com\t\t'), false) + }, + 'is a URL which contains basic auth user/pass': (test) => { + test.equal(urlUtil().isNotURL('http://username:password@example.com'), false) + }, + 'is localhost (case-insensitive)': (test) => { + test.equal(urlUtil().isNotURL('LoCaLhOsT'), false) + }, + 'is a hostname (not a domain)': (test) => { + test.equal(urlUtil().isNotURL('http://computer001/phpMyAdmin'), false) + }, + 'ends with period (input contains a forward slash and domain)': (test) => { + test.equal(urlUtil().isNotURL('brave.com/test/cc?_ri_=3vv-8-e.'), false) + }, + 'is a string with whitespace but has schema': (test) => { + test.equal(urlUtil().isNotURL('https://wwww.brave.com/test space.jpg'), false) + }, + 'has custom protocol': (test) => { + test.equal(urlUtil().isNotURL('brave://test'), false) + } + }, + + 'returns true when input:': { + 'is null or undefined': (test) => { + test.equal(urlUtil().isNotURL(), true) + test.equal(urlUtil().isNotURL(null), true) + }, + 'is not a string': (test) => { + test.equal(urlUtil().isNotURL(false), true) + test.equal(urlUtil().isNotURL(333.449), true) + }, + 'is a quoted string': (test) => { + test.equal(urlUtil().isNotURL('"search term here"'), true) + }, + 'is a pure string (no TLD)': (test) => { + test.equal(urlUtil().isNotURL('brave'), true) + }, + 'search query': { + 'starts with question mark': (test) => { + test.equal(urlUtil().isNotURL('?brave'), true) + }, + 'has a question mark followed by a space': (test) => { + test.equal(urlUtil().isNotURL('? brave'), true) + }, + 'starts with period': (test) => { + test.equal(urlUtil().isNotURL('.brave'), true) + }, + 'ends with period (input does NOT contain a forward slash)': (test) => { + test.equal(urlUtil().isNotURL('brave.'), true) + }, + 'ends with period (input contains only a forward slash)': (test) => { + test.equal(urlUtil().isNotURL('brave/com/test/cc?_ri_=3vv-8-e.'), true) + } + }, + 'is a string with schema but invalid domain name': (test) => { + test.equal(urlUtil().isNotURL('https://www.bra ve.com/test space.jpg'), true) + }, + 'contains more than one word': (test) => { + test.equal(urlUtil().isNotURL('brave is cool'), true) + }, + 'is not an about page / view source / data URI / mailto / etc': (test) => { + test.equal(urlUtil().isNotURL('not-a-chrome-extension:'), true) + test.equal(urlUtil().isNotURL('mailtoo:'), true) + }, + 'is a URL (without protocol) which contains basic auth user/pass': (test) => { + test.equal(urlUtil().isNotURL('username:password@example.com'), true) + }, + 'has space in schema': (test) => { + test.equal(urlUtil().isNotURL('https ://brave.com'), true) + } + } + }, + + 'getUrlFromInput': { + 'returns empty string when input is null': (test) => { + test.equal(urlUtil().getUrlFromInput(null), '') + }, + 'returns empty string when input is undefined': (test) => { + test.equal(urlUtil().getUrlFromInput(), '') + }, + 'calls prependScheme': (test) => { + test.equal(urlUtil().getUrlFromInput('/file/path/to/file'), 'file:///file/path/to/file') + } + }, + + 'isURL': { + 'returns !isNotURL': (test) => { + test.equal(urlUtil().isURL('brave.com'), !urlUtil().isNotURL('brave.com')) + test.equal(urlUtil().isURL('brave is cool'), !urlUtil().isNotURL('brave is cool')) + test.equal(urlUtil().isURL('mailto:brian@brave.com'), !urlUtil().isNotURL('mailto:brian@brave.com')) + } + }, + + 'isFileType': { + 'relative file': (test) => { + test.equal(urlUtil().isFileType('/file/abc/test.pdf', 'pdf'), true) + }, + 'relative path': (test) => { + test.equal(urlUtil().isFileType('/file/abc/test', 'pdf'), false) + }, + 'JPG URL': (test) => { + test.equal(urlUtil().isFileType('http://example.com/test/ABC.JPG?a=b#test', 'jpg'), true) + }, + 'non-JPG URL': (test) => { + test.equal(urlUtil().isFileType('http://example.com/test/jpg', 'jpg'), false) + }, + 'invalid URL': (test) => { + test.equal(urlUtil().isFileType('foo', 'jpg'), false) + } + }, + + 'toPDFJSLocation': { + 'pdf': (test) => { + const baseUrl = 'chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/' + test.equal(urlUtil().toPDFJSLocation('http://abc.com/test.pdf'), baseUrl + 'content/web/viewer.html?file=http%3A%2F%2Fabc.com%2Ftest.pdf') + }, + 'non-pdf': (test) => { + test.equal(urlUtil().toPDFJSLocation('http://abc.com/test.pdf.txt'), 'http://abc.com/test.pdf.txt') + }, + 'file url': (test) => { + test.equal(urlUtil().toPDFJSLocation('file://abc.com/test.pdf.txt'), 'file://abc.com/test.pdf.txt') + }, + 'empty': (test) => { + test.equal(urlUtil().toPDFJSLocation(''), '') + } + }, + + 'getPDFViewerUrl': { + 'regular url': (test) => { + const baseUrl = 'chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/content/web/viewer.html?file=' + test.equal(urlUtil().getPDFViewerUrl('http://example.com'), baseUrl + 'http%3A%2F%2Fexample.com') + }, + 'file url': (test) => { + const baseUrl = 'chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/content/web/viewer.html?file=' + test.equal(urlUtil().getPDFViewerUrl('file:///Users/yan/some files/test.pdf'), baseUrl + 'file%3A%2F%2F%2FUsers%2Fyan%2Fsome%20files%2Ftest.pdf') + } + }, + + 'getHostname': { + 'returns undefined if the URL is invalid': (test) => { + test.equal(urlUtil().getHostname(null), undefined) + }, + 'returns the host field (including port number)': (test) => { + test.equal(urlUtil().getHostname('https://brave.com:8080/test/'), 'brave.com:8080') + }, + 'allows you to exclude the port number': (test) => { + test.equal(urlUtil().getHostname('https://brave.com:8080/test/', true), 'brave.com') + } + }, + + 'getHostnamePatterns': { + 'gets bare domain hostname patterns': (test) => { + // XXX: *.com probably should be excluded + test.deepEqual(urlUtil().getHostnamePatterns('http://brave.com'), + ['brave.com', '*.com', 'brave.*']) + }, + 'gets subdomain hostname patterns': (test) => { + test.deepEqual(urlUtil().getHostnamePatterns('https://bar.brave.com'), + ['bar.brave.com', + '*.brave.com', + 'bar.*.com', + 'bar.brave.*']) + test.deepEqual(urlUtil().getHostnamePatterns('https://foo.bar.brave.com'), + ['foo.bar.brave.com', + '*.bar.brave.com', + 'foo.*.brave.com', + 'foo.bar.*.com', + 'foo.bar.brave.*', + '*.brave.com']) + } + }, + + 'getLocationIfPDF': { + 'gets location for PDF JS URL': (test) => { + test.equal(urlUtil().getLocationIfPDF('chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/https://www.blackhat.co…king-Kernel-Address-Space-Layout-Randomization-KASLR-With-Intel-TSX-wp.pdf'), + 'https://www.blackhat.co…king-Kernel-Address-Space-Layout-Randomization-KASLR-With-Intel-TSX-wp.pdf') + }, + 'gets location for PDF JS viewer URL': (test) => { + test.equal(urlUtil().getLocationIfPDF('chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/content/web/viewer.html?file=http%3A%2F%2Funec.edu.az%2Fapplication%2Fuploads%2F2014%2F12%2Fpdf-sample.pdf'), + 'http://unec.edu.az/application/uploads/2014/12/pdf-sample.pdf') + }, + 'does not remove wayback machine url location for PDF JS URL': (test) => { + test.equal(urlUtil().getLocationIfPDF('chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/https://web.archive.org/web/20160106152308/http://stlab.adobe.com/wiki/images/d/d3/Test.pdf'), + 'https://web.archive.org/web/20160106152308/http://stlab.adobe.com/wiki/images/d/d3/Test.pdf') + }, + 'does not modify location for non-pdf URL': (test) => { + test.equal(urlUtil().getLocationIfPDF('https://www.blackhat.co…king-Kernel-Address-Space-Layout-Randomization-KASLR-With-Intel-TSX-wp.pdf'), + 'https://www.blackhat.co…king-Kernel-Address-Space-Layout-Randomization-KASLR-With-Intel-TSX-wp.pdf') + test.equal(urlUtil().getLocationIfPDF('chrome-extension://blank'), 'chrome-extension://blank') + test.equal(urlUtil().getLocationIfPDF(null), null) + }, + 'gets location for file: PDF URL': (test) => { + let url = 'chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/file:///Users/yan/Downloads/test.pdf' + test.equal(urlUtil().getLocationIfPDF(url), 'file:///Users/yan/Downloads/test.pdf') + } + }, + + 'getDefaultFaviconUrl': { + 'returns empty string if input is not a URL': (test) => { + test.equal(urlUtil().getDefaultFaviconUrl('invalid-url-goes-here'), '') + }, + 'returns the default favicon URL when given a valid URL': (test) => { + test.equal(urlUtil().getDefaultFaviconUrl('https://brave.com'), 'https://brave.com/favicon.ico') + }, + 'includes the port in the response when given a valid URL with a port number': (test) => { + test.equal(urlUtil().getDefaultFaviconUrl('https://brave.com:8080'), 'https://brave.com:8080/favicon.ico') + } + }, + + 'getPunycodeUrl': { + 'returns original string if input is ASCII': (test) => { + test.equal(urlUtil().getPunycodeUrl('invalid-url-goes-here'), 'invalid-url-goes-here') + }, + 'returns punycode ASCII string if input is non-ASCII': (test) => { + test.equal(urlUtil().getPunycodeUrl('ebаy.com'), 'xn--eby-7cd.com') + }, + 'returns the punycode URL when given a valid URL': (test) => { + test.equal(urlUtil().getPunycodeUrl('http://brave:brave@ebаy.com:1234/brave#brave'), 'http://brave:brave@xn--eby-7cd.com:1234/brave#brave') + }, + 'returns the punycode URL when given a URL contains @': (test) => { + test.equal(urlUtil().getPunycodeUrl('ebаy.com/@ebаy.com'), 'xn--eby-7cd.com/@xn--eby-7cd.com') + } + }, + + 'isPotentialPhishingUrl': { + 'returns false if input is not a URL': (test) => { + test.equal(urlUtil().isPotentialPhishingUrl(null), false) + }, + 'returns false if input is a regular URL': (test) => { + test.equal(urlUtil().isPotentialPhishingUrl(' https://google.com'), false) + }, + 'returns true if input is a data URL': (test) => { + test.equal(urlUtil().isPotentialPhishingUrl('data:text/html,'), true) + }, + 'returns true if input is a blob URL': (test) => { + test.equal(urlUtil().isPotentialPhishingUrl(' BLOB:foo '), true) + } + }, + + 'isFileScheme': { + 'returns true when input:': { + 'is an absolute file path with scheme': (test) => { + test.equal(urlUtil().isFileScheme('file:///file/path/to/file'), true) + } + }, + 'returns false when input:': { + 'is an absolute file path without scheme': (test) => { + test.equal(urlUtil().isFileScheme('/file/path/to/file'), false) + }, + 'is a URL': (test) => { + test.equal(urlUtil().isFileScheme('http://brave.com'), false) + }, + 'has custom protocol': (test) => { + test.equal(urlUtil().isFileScheme('brave://test'), false) + } + } + }, + + 'getDisplayHost': { + 'url is http': (test) => { + const result = urlUtil().getDisplayHost('http://brave.com') + test.equal(result, 'brave.com') + }, + + 'url is https': (test) => { + const result = urlUtil().getDisplayHost('https://brave.com') + test.equal(result, 'brave.com') + }, + + 'url is file': (test) => { + const result = urlUtil().getDisplayHost('file://brave.text') + test.equal(result, 'file://brave.text') + } + }, + + 'getOrigin': { + 'returns file:/// for any file url': (test) => { + test.strictEqual(urlUtil().getOrigin('file://'), 'file:///') + test.strictEqual(urlUtil().getOrigin('file:///'), 'file:///') + test.strictEqual(urlUtil().getOrigin('file:///some'), 'file:///') + test.strictEqual(urlUtil().getOrigin('file:///some/'), 'file:///') + test.strictEqual(urlUtil().getOrigin('file:///some/path'), 'file:///') + }, + 'gets URL origin for simple url': (test) => { + test.strictEqual(urlUtil().getOrigin('https://abc.bing.com'), 'https://abc.bing.com') + }, + 'gets URL origin for url with port': (test) => { + test.strictEqual(urlUtil().getOrigin('https://bing.com:443/?test=1#abc'), 'https://bing.com:443') + }, + 'gets URL origin for IP host': (test) => { + test.strictEqual(urlUtil().getOrigin('http://127.0.0.1:443/?test=1#abc'), 'http://127.0.0.1:443') + }, + 'gets URL origin for about:': (test) => { + test.strictEqual(urlUtil().getOrigin('about:preferences#abc'), 'about:preferences') + }, + 'gets URL origin for slashless protocol URL': (test) => { + test.strictEqual(urlUtil().getOrigin('about:test/foo'), 'about:test') + }, + 'returns null for invalid URL': (test) => { + test.strictEqual(urlUtil().getOrigin('abc'), null) + }, + 'returns null for empty URL': (test) => { + test.strictEqual(urlUtil().getOrigin(''), null) + }, + 'returns null for null URL': (test) => { + test.strictEqual(urlUtil().getOrigin(null), null) + }, + 'returns correct result for URL with hostname that is a scheme': (test) => { + test.strictEqual(urlUtil().getOrigin('http://http/test'), 'http://http') + } + }, + + 'stripLocation': { + 'null scenario': (test) => { + const result = urlUtil().stripLocation(null) + test.equal(result, '') + }, + + 'empty string': (test) => { + const result = urlUtil().stripLocation('') + test.equal(result, '') + }, + + 'normal url without hash or slash': (test) => { + const result = urlUtil().stripLocation('https://brave.com') + test.equal(result, 'https://brave.com') + }, + + 'normal url with hash but not at the end': (test) => { + const result = urlUtil().stripLocation('https://brave.com#title') + test.equal(result, 'https://brave.com#title') + }, + + 'normal url with hash at the end': (test) => { + const result = urlUtil().stripLocation('https://brave.com#') + test.equal(result, 'https://brave.com') + }, + + 'normal url with slash at the end': (test) => { + const result = urlUtil().stripLocation('https://brave.com/') + test.equal(result, 'https://brave.com') + }, + + 'normal url with slash hash at the end': (test) => { + const result = urlUtil().stripLocation('https://brave.com/#') + test.equal(result, 'https://brave.com') + }, + + 'normal url with white space at the end': (test) => { + const result = urlUtil().stripLocation('https://brave.com ') + test.equal(result, 'https://brave.com') + } + }, + + 'parseFaviconDataUrl': { + 'null scenario': (test) => { + const result = urlUtil().parseFaviconDataUrl(null) + test.equal(result, null) + }, + 'empty string': (test) => { + const result = urlUtil().parseFaviconDataUrl('') + test.equal(result, null) + }, + 'regular URL': (test) => { + const result = urlUtil().parseFaviconDataUrl('http://example.com') + test.equal(result, null) + }, + 'non-image data URL': (test) => { + const result = urlUtil().parseFaviconDataUrl('data:text/plain;charset=UTF-8;page=21,the%20data:1234,5678') + test.equal(result, null) + }, + 'non-base64 data URL': (test) => { + const result = urlUtil().parseFaviconDataUrl('data:image/jpg,foo') + test.equal(result, null) + }, + 'no-extension data URL': (test) => { + const result = urlUtil().parseFaviconDataUrl('data:image/;base64,foo') + test.equal(result, null) + }, + 'valid jpg': (test) => { + const jpg = 'data:image/jpeg;base64,' + + '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDADIiJSwlHzIsKSw4NTI7S31RS0VFS5ltc1p9tZ++u7Kf' + + 'r6zI4f/zyNT/16yv+v/9////////wfD/////////////2wBDATU4OEtCS5NRUZP/zq/O////////' + + '////////////////////////////////////////////////////////////wAARCAAYAEADAREA' + + '//AhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAQMAAgQF/8QAJRABAAIBBAEEAgMAAAAAAAAAAQIR' + + '//AAMSITEEEyJBgTORUWFx/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAA' + + '//AAD/2gAMAwEAAhEDEQA/AOgM52xQDrjvAV5Xv0vfKUALlTQfeBm0HThMNHXkL0Lw/swN5qgA8yT4' + + '//MCS1OEOJV8mBz9Z05yfW8iSx7p4j+jA1aD6Wj7ZMzstsfvAas4UyRHvjrAkC9KhpLMClQntlqFc2' + + '//X1gUj4viwVObKrddH9YDoHvuujAEuNV+bLwFS8XxdSr+Cq3Vf+4F5RgQl6ZR2p1eAzU/HX80YBYy' + + '//JLCuexwJCO2O1bwCRidAfWBSctswbI12GAJT3yiwFR7+MBjGK2g/WAJR3FdF84E2rK5VR0YH/9k=' + const expected = '/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDADIiJSwlHzIsKSw4NTI7S31RS0VFS5ltc1p9tZ++u7Kf' + + 'r6zI4f/zyNT/16yv+v/9////////wfD/////////////2wBDATU4OEtCS5NRUZP/zq/O////////' + + '////////////////////////////////////////////////////////////wAARCAAYAEADAREA' + + '//AhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAQMAAgQF/8QAJRABAAIBBAEEAgMAAAAAAAAAAQIR' + + '//AAMSITEEEyJBgTORUWFx/8QAFAEBAAAAAAAAAAAAAAAAAAAAAP/EABQRAQAAAAAAAAAAAAAAAAAA' + + '//AAD/2gAMAwEAAhEDEQA/AOgM52xQDrjvAV5Xv0vfKUALlTQfeBm0HThMNHXkL0Lw/swN5qgA8yT4' + + '//MCS1OEOJV8mBz9Z05yfW8iSx7p4j+jA1aD6Wj7ZMzstsfvAas4UyRHvjrAkC9KhpLMClQntlqFc2' + + '//X1gUj4viwVObKrddH9YDoHvuujAEuNV+bLwFS8XxdSr+Cq3Vf+4F5RgQl6ZR2p1eAzU/HX80YBYy' + + '//JLCuexwJCO2O1bwCRidAfWBSctswbI12GAJT3yiwFR7+MBjGK2g/WAJR3FdF84E2rK5VR0YH/9k=' + const result = urlUtil().parseFaviconDataUrl(jpg) + test.deepEqual(result, {data: expected, ext: 'jpeg'}) + }, + 'valid png': (test) => { + const png = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU//5ErkJggg==' + const result = urlUtil().parseFaviconDataUrl(png) + test.deepEqual(result, {data: 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU//5ErkJggg==', ext: 'png'}) + } + }, + + 'isInternalUrl': { + 'null scenario': (test) => { + const result = urlUtil().isInternalUrl(null) + test.equal(result, false) + }, + 'localhost': (test) => { + const result = urlUtil().isInternalUrl('http://localhost:399/abc') + test.equal(result, true) + }, + 'localhost.com': (test) => { + const result = urlUtil().isInternalUrl('http://localhost.com:399/abc') + test.equal(result, false) + }, + 'invalid URL': (test) => { + const result = urlUtil().isInternalUrl('adsf') + test.equal(result, false) + }, + 'local IP': (test) => { + const result = urlUtil().isInternalUrl('http://192.168.1.255:3000') + test.equal(result, true) + const result2 = urlUtil().isInternalUrl('http://127.0.0.1/') + test.equal(result2, true) + }, + 'remote IP': (test) => { + const result = urlUtil().isInternalUrl('http://54.0.0.1:3000') + test.equal(result, false) + }, + 'local IPv6': (test) => { + const result = urlUtil().isInternalUrl('https://[::1]:3000') + test.equal(result, true) + const result2 = urlUtil().isInternalUrl('http://[fe80::c12:79e9:bd20:31e1]/') + test.equal(result2, true) + }, + 'remote IPv6': (test) => { + const result = urlUtil().isInternalUrl('http://[2001:4860:4860::8888]:8000') + test.equal(result, false) + }, + '.local URL': (test) => { + const result = urlUtil().isInternalUrl('https://adsf.local') + test.equal(result, true) + } + }, + + 'isUrlPDF': { + 'null case': (test) => { + const result = urlUtil().isUrlPDF(null) + test.equal(result, false) + }, + + 'url is not pdf': (test) => { + const result = urlUtil().isUrlPDF('https://clifton.io') + test.equal(result, false) + }, + + 'url is pdf': (test) => { + const result = urlUtil().isUrlPDF('chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/http://www.test.com/test.pdf') + test.equal(result, true) + } + }, + + 'getUrlFromPDFUrl': { + 'null case': (test) => { + const result = urlUtil().getUrlFromPDFUrl(null) + test.equal(result, null) + }, + + 'url is not PDF': (test) => { + const result = urlUtil().getUrlFromPDFUrl('https://clifton.io') + test.equal(result, 'https://clifton.io') + }, + + 'url is pdf': (test) => { + const result = urlUtil().getUrlFromPDFUrl('chrome-extension://jdbefljfgobbmcidnmpjamcbhnbphjnb/http://www.test.com/test.pdf') + test.equal(result, 'http://www.test.com/test.pdf') + } + } +} diff --git a/test/unit/runMuonCompatibleTests.js b/test/unit/runMuonCompatibleTests.js new file mode 100644 index 00000000000..0a257963d25 --- /dev/null +++ b/test/unit/runMuonCompatibleTests.js @@ -0,0 +1,31 @@ +/* global describe, it, before, after */ + +const assert = require('assert') +require('./braveUnit') + +const executeTests = (name, tests) => { + describe(name, function () { + const runnableTests = Object.keys(tests).filter((k) => typeof tests[k] === 'function') + if (runnableTests.length) { + for (let testName of runnableTests) { + if (testName === 'before') { + before(tests[testName]) + } else if (testName === 'after') { + after(tests[testName]) + } else { + const wrapper = tests[testName].length > 1 + ? function (cb) { tests[testName].call(this, assert, cb) } + : function () { tests[testName].call(this, assert) } + it(testName, wrapper) + } + } + } + + const testGroups = Object.keys(tests).filter((k) => typeof tests[k] === 'object') + for (let groupName of testGroups) { + executeTests(groupName, tests[groupName]) + } + }) +} + +module.exports = executeTests