author | Erik Vold <evold@mozilla.com> |
Thu, 19 Dec 2013 14:50:18 -0800 | |
changeset 161377 | f42afc1fab5034cbd08901b94c29239a612b3f55 |
parent 161376 | fc063fb8efdcaef01ba3e8f34892622c31053b15 |
child 161378 | 599100c4ebfe11908fd26b394030e1cb970d2b88 |
child 161397 | 96816b5d72995dd8172e3132f9fe6bad6d211579 |
push id | 25878 |
push user | kwierso@gmail.com |
push date | Fri, 20 Dec 2013 03:09:21 +0000 |
treeherder | mozilla-central@599100c4ebfe [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
bugs | 947501 |
milestone | 29.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/addon-sdk/source/lib/sdk/context-menu.js +++ b/addon-sdk/source/lib/sdk/context-menu.js @@ -19,16 +19,17 @@ const { URL, isValidURI } = require("./u const { WindowTracker, browserWindowIterator } = require("./deprecated/window-utils"); const { isBrowser, getInnerId } = require("./window/utils"); const { Ci } = require("chrome"); const { MatchPattern } = require("./util/match-pattern"); const { Worker } = require("./content/worker"); const { EventTarget } = require("./event/target"); const { emit } = require('./event/core'); const { when } = require('./system/unload'); +const selection = require('./selection'); // All user items we add have this class. const ITEM_CLASS = "addon-context-menu-item"; // Items in the top-level context menu also have this class. const TOPLEVEL_ITEM_CLASS = "addon-context-menu-item-toplevel"; // Items in the overflow submenu also have this class. @@ -199,16 +200,76 @@ let URLContext = Class({ isCurrent: function isCurrent(popupNode) { let url = popupNode.ownerDocument.URL; return internal(this).patterns.some(function (p) p.test(url)); } }); exports.URLContext = URLContext; +// Matches when the user-supplied predicate returns true +let PredicateContext = Class({ + extends: Context, + + initialize: function initialize(predicate) { + let options = validateOptions({ predicate: predicate }, { + predicate: { + is: ["function"], + msg: "predicate must be a function." + } + }); + internal(this).predicate = options.predicate; + }, + + isCurrent: function isCurrent(popupNode) { + return internal(this).predicate(populateCallbackNodeData(popupNode)); + } +}); +exports.PredicateContext = PredicateContext; + +// List all editable types of inputs. Or is it better to have a list +// of non-editable inputs? +let editableInputs = { + email: true, + number: true, + password: true, + search: true, + tel: true, + text: true, + textarea: true, + url: true +}; + +function populateCallbackNodeData(node) { + let window = node.ownerDocument.defaultView; + let data = {}; + + data.documentType = node.ownerDocument.contentType; + + data.documentURL = node.ownerDocument.location.href; + data.targetName = node.nodeName.toLowerCase(); + data.targetID = node.id || null ; + + if ((data.targetName === 'input' && editableInputs[node.type]) || + data.targetName === 'textarea') { + data.isEditable = !node.readOnly && !node.disabled; + } + else { + data.isEditable = node.isContentEditable; + } + + data.selectionText = selection.text; + + data.srcURL = node.src || null; + data.linkURL = node.href || null; + data.value = node.value || null; + + return data; +} + function removeItemFromArray(array, item) { return array.filter(function(i) i !== item); } // Converts anything that isn't false, null or undefined into a string function stringOrNull(val) val ? String(val) : val; // Shared option validation rules for Item and Menu
--- a/addon-sdk/source/lib/sdk/deprecated/unit-test-finder.js +++ b/addon-sdk/source/lib/sdk/deprecated/unit-test-finder.js @@ -9,16 +9,17 @@ module.metadata = { }; const file = require("../io/file"); const memory = require('./memory'); const suites = require('@test/options').allTestModules; const { Loader } = require("sdk/test/loader"); const cuddlefish = require("sdk/loader/cuddlefish"); +let loader = Loader(module); const NOT_TESTS = ['setup', 'teardown']; var TestFinder = exports.TestFinder = function TestFinder(options) { memory.track(this); this.filter = options.filter; this.testInProcess = options.testInProcess === false ? false : true; this.testOutOfProcess = options.testOutOfProcess === true ? true : false; }; @@ -46,21 +47,20 @@ TestFinder.prototype = { filter = function(filename, testname) { return filterFileRegex.test(filename) && ((testname && filterNameRegex) ? filterNameRegex.test(testname) : true); }; } else filter = function() {return true}; - suites.forEach( - function(suite) { + suites.forEach(function(suite) { // Load each test file as a main module in its own loader instance // `suite` is defined by cuddlefish/manifest.py:ManifestBuilder.build - let loader = Loader(module); + let suiteModule; try { suiteModule = cuddlefish.main(loader, suite); } catch (e) { if (!/^Unsupported Application/.test(e.message)) throw e;
--- a/addon-sdk/source/lib/sdk/lang/type.js +++ b/addon-sdk/source/lib/sdk/lang/type.js @@ -124,16 +124,22 @@ exports.isArray = isArray; * (function(){ return isArguments(arguments); })(1, 2, 3); // true * isArguments([1,2,3]); // false */ function isArguments(value) { Object.prototype.toString.call(value) === "[object Arguments]"; } exports.isArguments = isArguments; +let isMap = value => Object.prototype.toString.call(value) === "[object Map]" +exports.isMap = isMap; + +let isSet = value => Object.prototype.toString.call(value) === "[object Set]" +exports.isSet = isSet; + /** * Returns true if it is a primitive `value`. (null, undefined, number, * boolean, string) * @examples * isPrimitive(3) // true * isPrimitive('foo') // true * isPrimitive({ bar: 3 }) // false */
--- a/addon-sdk/source/lib/sdk/selection.js +++ b/addon-sdk/source/lib/sdk/selection.js @@ -310,24 +310,34 @@ function safeGetRange(selection, rangeNu * for text selected in a form field (see bug 85686) */ function getElementWithSelection() { let element = getFocusedElement(); if (!element) return null; - let { value, selectionStart, selectionEnd } = element; + try { + // Accessing selectionStart and selectionEnd on e.g. a button + // results in an exception thrown as per the HTML5 spec. See + // http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#textFieldSelection - let hasSelection = typeof value === "string" && + let { value, selectionStart, selectionEnd } = element; + + let hasSelection = typeof value === "string" && !isNaN(selectionStart) && !isNaN(selectionEnd) && selectionStart !== selectionEnd; - return hasSelection ? element : null; + return hasSelection ? element : null; + } + catch (err) { + return null; + } + } /** * Adds the Selection Listener to the content's window given */ function addSelectionListener(window) { let selection = window.getSelection();
new file mode 100644 --- /dev/null +++ b/addon-sdk/source/lib/sdk/util/sequence.js @@ -0,0 +1,576 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +"use strict"; + +module.metadata = { + "stability": "experimental" +}; + +// Disclamer: +// In this module we'll have some common argument / variable names +// to hint their type or behavior. +// +// - `f` stands for "function" that is intended to be side effect +// free. +// - `p` stands for "predicate" that is function which returns logical +// true or false and is intended to be side effect free. +// - `x` / `y` single item of the sequence. +// - `xs` / `ys` sequence of `x` / `y` items where `x` / `y` signifies +// type of the items in sequence, so sequence is not of the same item. +// - `_` used for argument(s) or variable(s) who's values are ignored. + +const { complement, flip, identity } = require("../lang/functional"); +const { iteratorSymbol } = require("../util/iteration"); +const { isArray, isArguments, isMap, isSet, + isString, isBoolean, isNumber } = require("../lang/type"); + +const Sequence = function Sequence(iterator) { + if (iterator.isGenerator && iterator.isGenerator()) + this[iteratorSymbol] = iterator; + else + throw TypeError("Expected generator argument"); +}; +exports.Sequence = Sequence; + +const polymorphic = dispatch => x => + x === null ? dispatch.null(null) : + x === void(0) ? dispatch.void(void(0)) : + isArray(x) ? (dispatch.array || dispatch.indexed)(x) : + isString(x) ? (dispatch.string || dispatch.indexed)(x) : + isArguments(x) ? (dispatch.arguments || dispatch.indexed)(x) : + isMap(x) ? dispatch.map(x) : + isSet(x) ? dispatch.set(x) : + isNumber(x) ? dispatch.number(x) : + isBoolean(x) ? dispatch.boolean(x) : + dispatch.default(x); + +const nogen = function*() {}; +const empty = () => new Sequence(nogen); +exports.empty = empty; + +const seq = polymorphic({ + null: empty, + void: empty, + array: identity, + string: identity, + arguments: identity, + map: identity, + set: identity, + default: x => x instanceof Sequence ? x : new Sequence(x) +}); +exports.seq = seq; + + + + +// Function to cast seq to string. +const string = (...etc) => "".concat(...etc); +exports.string = string; + +// Function for casting seq to plain object. +const object = (...pairs) => { + let result = {}; + for (let [key, value] of pairs) + result[key] = value; + + return result; +}; +exports.object = object; + +// Takes `getEnumerator` function that returns `nsISimpleEnumerator` +// and creates lazy sequence of it's items. Note that function does +// not take `nsISimpleEnumerator` itslef because that would allow +// single iteration, which would not be consistent with rest of the +// lazy sequences. +const fromEnumerator = getEnumerator => seq(function* () { + const enumerator = getEnumerator(); + while (enumerator.hasMoreElements()) + yield enumerator.getNext(); +}); +exports.fromEnumerator = fromEnumerator; + +// Takes `object` and returns lazy sequence of own `[key, value]` +// pairs (does not include inherited and non enumerable keys). +const pairs = polymorphic({ + null: empty, + void: empty, + map: identity, + indexed: indexed => seq(function* () { + const count = indexed.length; + let index = 0; + while (index < count) { + yield [index, indexed[index]]; + index = index + 1; + } + }), + default: object => seq(function* () { + for (let key of Object.keys(object)) + yield [key, object[key]]; + }) +}); +exports.pairs = pairs; + + +const keys = polymorphic({ + null: empty, + void: empty, + indexed: indexed => seq(function* () { + const count = indexed.length; + let index = 0; + while (index < count) { + yield index; + index = index + 1; + } + }), + map: map => seq(function* () { + for (let [key, _] of map) + yield key; + }), + default: object => seq(function* () { + for (let key of Object.keys(object)) + yield key; + }) +}); +exports.keys = keys; + + +const values = polymorphic({ + null: empty, + void: empty, + set: identity, + indexed: indexed => seq(function* () { + const count = indexed.length; + let index = 0; + while (index < count) { + yield indexed[index]; + index = index + 1; + } + }), + map: map => seq(function* () { + for (let [_, value] of map) yield value; + }), + default: object => seq(function* () { + for (let key of Object.keys(object)) yield object[key]; + }) +}); +exports.values = values; + + + +// Returns a lazy sequence of `x`, `f(x)`, `f(f(x))` etc. +// `f` must be free of side-effects. Note that returned +// sequence is infinite so it must be consumed partially. +// +// Implements clojure iterate: +// http://clojuredocs.org/clojure_core/clojure.core/iterate +const iterate = (f, x) => seq(function* () { + let state = x; + while (true) { + yield state; + state = f(state); + } +}); +exports.iterate = iterate; + +// Returns a lazy sequence of the items in sequence for which `p(item)` +// returns `true`. `p` must be free of side-effects. +// +// Implements clojure filter: +// http://clojuredocs.org/clojure_core/clojure.core/filter +const filter = (p, sequence) => seq(function* () { + if (sequence !== null && sequence !== void(0)) { + for (let item of sequence) { + if (p(item)) + yield item; + } + } +}); +exports.filter = filter; + +// Returns a lazy sequence consisting of the result of applying `f` to the +// set of first items of each sequence, followed by applying f to the set +// of second items in each sequence, until any one of the sequences is +// exhausted. Any remaining items in other sequences are ignored. Function +// `f` should accept number-of-sequences arguments. +// +// Implements clojure map: +// http://clojuredocs.org/clojure_core/clojure.core/map +const map = (f, ...sequences) => seq(function* () { + const count = sequences.length; + // Optimize a single sequence case + if (count === 1) { + let [sequence] = sequences; + if (sequence !== null && sequence !== void(0)) { + for (let item of sequence) + yield f(item); + } + } + else { + // define args array that will be recycled on each + // step to aggregate arguments to be passed to `f`. + let args = []; + // define inputs to contain started generators. + let inputs = []; + + let index = 0; + while (index < count) { + inputs[index] = sequences[index][iteratorSymbol](); + index = index + 1; + } + + // Run loop yielding of applying `f` to the set of + // items at each step until one of the `inputs` is + // exhausted. + let done = false; + while (!done) { + let index = 0; + let value = void(0); + while (index < count && !done) { + ({ done, value }) = inputs[index].next(); + + // If input is not exhausted yet store value in args. + if (!done) { + args[index] = value; + index = index + 1; + } + } + + // If none of the inputs is exhasted yet, `args` contain items + // from each input so we yield application of `f` over them. + if (!done) + yield f(...args); + } + } +}); +exports.map = map; + +// Returns a lazy sequence of the intermediate values of the reduction (as +// per reduce) of sequence by `f`, starting with `initial` value if provided. +// +// Implements clojure reductions: +// http://clojuredocs.org/clojure_core/clojure.core/reductions +const reductions = (...params) => { + const count = params.length; + let hasInitial = false; + let f, initial, source; + if (count === 2) { + ([f, source]) = params; + } + else if (count === 3) { + ([f, initial, source]) = params; + hasInitial = true; + } + else { + throw Error("Invoked with wrong number of arguments: " + count); + } + + const sequence = seq(source); + + return seq(function* () { + let started = hasInitial; + let result = void(0); + + // If initial is present yield it. + if (hasInitial) + yield (result = initial); + + // For each item of the sequence accumulate new result. + for (let item of sequence) { + // If nothing has being yield yet set result to first + // item and yield it. + if (!started) { + started = true; + yield (result = item); + } + // Otherwise accumulate new result and yield it. + else { + yield (result = f(result, item)); + } + } + + // If nothing has being yield yet it's empty sequence and no + // `initial` was provided in which case we need to yield `f()`. + if (!started) + yield f(); + }); +}; +exports.reductions = reductions; + +// `f` should be a function of 2 arguments. If `initial` is not supplied, +// returns the result of applying `f` to the first 2 items in sequence, then +// applying `f` to that result and the 3rd item, etc. If sequence contains no +// items, `f` must accept no arguments as well, and reduce returns the +// result of calling f with no arguments. If sequence has only 1 item, it +// is returned and `f` is not called. If `initial` is supplied, returns the +// result of applying `f` to `initial` and the first item in sequence, then +// applying `f` to that result and the 2nd item, etc. If sequence contains no +// items, returns `initial` and `f` is not called. +// +// Implements clojure reduce: +// http://clojuredocs.org/clojure_core/clojure.core/reduce +const reduce = (...args) => { + const xs = reductions(...args); + let x; + for (x of xs) void(0); + return x; +}; +exports.reduce = reduce; + +const each = (f, sequence) => { + for (let x of seq(sequence)) void(f(x)); +}; +exports.each = each; + + +const inc = x => x + 1; +// Returns the number of items in the sequence. `count(null)` && `count()` +// returns `0`. Also works on strings, arrays, Maps & Sets. + +// Implements clojure count: +// http://clojuredocs.org/clojure_core/clojure.core/count +const count = polymorphic({ + null: _ => 0, + void: _ => 0, + indexed: indexed => indexed.length, + map: map => map.size, + set: set => set.size, + default: xs => reduce(inc, 0, xs) +}); +exports.count = count; + +// Returns `true` if sequence has no items. + +// Implements clojure empty?: +// http://clojuredocs.org/clojure_core/clojure.core/empty_q +const isEmpty = sequence => { + // Treat `null` and `undefined` as empty sequences. + if (sequence === null || sequence === void(0)) + return true; + + // If contains any item non empty so return `false`. + for (let _ of sequence) + return false; + + // If has not returned yet, there was nothing to iterate + // so it's empty. + return true; +}; +exports.isEmpty = isEmpty; + +const and = (a, b) => a && b; + +// Returns true if `p(x)` is logical `true` for every `x` in sequence, else +// `false`. +// +// Implements clojure every?: +// http://clojuredocs.org/clojure_core/clojure.core/every_q +const isEvery = (p, sequence) => { + if (sequence !== null && sequence !== void(0)) { + for (let item of sequence) { + if (!p(item)) + return false; + } + } + return true; +}; +exports.isEvery = isEvery; + +// Returns the first logical true value of (p x) for any x in sequence, +// else `null`. +// +// Implements clojure some: +// http://clojuredocs.org/clojure_core/clojure.core/some +const some = (p, sequence) => { + if (sequence !== null && sequence !== void(0)) { + for (let item of sequence) { + if (p(item)) + return true; + } + } + return null; +}; +exports.some = some; + +// Returns a lazy sequence of the first `n` items in sequence, or all items if +// there are fewer than `n`. +// +// Implements clojure take: +// http://clojuredocs.org/clojure_core/clojure.core/take +const take = (n, sequence) => n <= 0 ? empty() : seq(function* () { + let count = n; + for (let item of sequence) { + yield item; + count = count - 1; + if (count === 0) break; + } +}); +exports.take = take; + +// Returns a lazy sequence of successive items from sequence while +// `p(item)` returns `true`. `p` must be free of side-effects. +// +// Implements clojure take-while: +// http://clojuredocs.org/clojure_core/clojure.core/take-while +const takeWhile = (p, sequence) => seq(function* () { + for (let item of sequence) { + if (!p(item)) + break; + + yield item; + } +}); +exports.takeWhile = takeWhile; + +// Returns a lazy sequence of all but the first `n` items in +// sequence. +// +// Implements clojure drop: +// http://clojuredocs.org/clojure_core/clojure.core/drop +const drop = (n, sequence) => seq(function* () { + if (sequence !== null && sequence !== void(0)) { + let count = n; + for (let item of sequence) { + if (count > 0) + count = count - 1; + else + yield item; + } + } +}); +exports.drop = drop; + +// Returns a lazy sequence of the items in sequence starting from the +// first item for which `p(item)` returns falsy value. +// +// Implements clojure drop-while: +// http://clojuredocs.org/clojure_core/clojure.core/drop-while +const dropWhile = (p, sequence) => seq(function* () { + let keep = false; + for (let item of sequence) { + keep = keep || !p(item); + if (keep) yield item; + } +}); +exports.dropWhile = dropWhile; + +// Returns a lazy sequence representing the concatenation of the +// suplied sequences. +// +// Implements clojure conact: +// http://clojuredocs.org/clojure_core/clojure.core/concat +const concat = (...sequences) => seq(function* () { + for (let sequence of sequences) + for (let item of sequence) + yield item; +}); +exports.concat = concat; + +// Returns the first item in the sequence. +// +// Implements clojure first: +// http://clojuredocs.org/clojure_core/clojure.core/first +const first = sequence => { + if (sequence !== null && sequence !== void(0)) { + for (let item of sequence) + return item; + } + return null; +}; +exports.first = first; + +// Returns a possibly empty sequence of the items after the first. +// +// Implements clojure rest: +// http://clojuredocs.org/clojure_core/clojure.core/rest +const rest = sequence => drop(1, sequence); +exports.rest = rest; + +// Returns the value at the index. Returns `notFound` or `undefined` +// if index is out of bounds. +const nth = (xs, n, notFound) => { + if (n >= 0) { + if (isArray(xs) || isArguments(xs) || isString(xs)) { + return n < xs.length ? xs[n] : notFound; + } + else if (xs !== null && xs !== void(0)) { + let count = n; + for (let x of xs) { + if (count <= 0) + return x; + + count = count - 1; + } + } + } + return notFound; +}; +exports.nth = nth; + +// Return the last item in sequence, in linear time. +// If `sequence` is an array or string or arguments +// returns in constant time. +// Implements clojure last: +// http://clojuredocs.org/clojure_core/clojure.core/last +const last = polymorphic({ + null: _ => null, + void: _ => null, + indexed: indexed => indexed[indexed.length - 1], + map: xs => reduce((_, x) => x, xs), + set: xs => reduce((_, x) => x, xs), + default: xs => reduce((_, x) => x, xs) +}); +exports.last = last; + +// Return a lazy sequence of all but the last `n` (default 1) items +// from the give `xs`. +// +// Implements clojure drop-last: +// http://clojuredocs.org/clojure_core/clojure.core/drop-last +const dropLast = flip((xs, n=1) => seq(function* () { + let ys = []; + for (let x of xs) { + ys.push(x); + if (ys.length > n) + yield ys.shift(); + } +})); +exports.dropLast = dropLast; + +// Returns a lazy sequence of the elements of `xs` with duplicates +// removed +// +// Implements clojure distinct +// http://clojuredocs.org/clojure_core/clojure.core/distinct +const distinct = sequence => seq(function* () { + let items = new Set(); + for (let item of sequence) { + if (!items.has(item)) { + items.add(item); + yield item; + } + } +}); +exports.distinct = distinct; + +// Returns a lazy sequence of the items in `xs` for which +// `p(x)` returns false. `p` must be free of side-effects. +// +// Implements clojure remove +// http://clojuredocs.org/clojure_core/clojure.core/remove +const remove = (p, xs) => filter(complement(p), xs); +exports.remove = remove; + +// Returns the result of applying concat to the result of +// `map(f, xs)`. Thus function `f` should return a sequence. +// +// Implements clojure mapcat +// http://clojuredocs.org/clojure_core/clojure.core/mapcat +const mapcat = (f, sequence) => seq(function* () { + const sequences = map(f, sequence); + for (let sequence of sequences) + for (let item of sequence) + yield item; +}); +exports.mapcat = mapcat;
--- a/addon-sdk/source/python-lib/cuddlefish/__init__.py +++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py @@ -21,24 +21,22 @@ on your system. Please specify one using UPDATE_RDF_FILENAME = "%s.update.rdf" XPI_FILENAME = "%s.xpi" usage = """ %prog [options] command [command-specific options] Supported Commands: - docs - view web-based documentation init - create a sample addon in an empty directory test - run tests run - run program xpi - generate an xpi Internal Commands: - sdocs - export static documentation testcfx - test the cfx tool testex - test all example code testpkgs - test all installed packages testall - test whole environment Experimental and internal commands and options are not supported and may be changed or removed in the future. """ @@ -218,48 +216,41 @@ parser_groups = ( default=[], cmds=['xpi'])), (("", "--stop-on-error",), dict(dest="stopOnError", help="Stop running tests after the first failure", action="store_true", metavar=None, default=False, cmds=['test', 'testex', 'testpkgs'])), - (("", "--override-version",), dict(dest="override_version", - help="Pass in a version string to use in generated docs", - metavar=None, - default=False, - cmds=['sdocs'])), (("", "--check-memory",), dict(dest="check_memory", help="attempts to detect leaked compartments after a test run", action="store_true", default=False, cmds=['test', 'testpkgs', 'testaddons', 'testall'])), (("", "--output-file",), dict(dest="output_file", help="Where to put the finished .xpi", default=None, cmds=['xpi'])), + (("", "--manifest-overload",), dict(dest="manifest_overload", + help="JSON file to overload package.json properties", + default=None, + cmds=['xpi'])), ] ), ("Internal Command-Specific Options", [ (("", "--addons",), dict(dest="addons", help=("paths of addons to install, " "comma-separated"), metavar=None, default=None, cmds=['test', 'run', 'testex', 'testpkgs', 'testall'])), - (("", "--baseurl",), dict(dest="baseurl", - help=("root of static docs tree: " - "for example: 'http://me.com/the_docs/'"), - metavar=None, - default='', - cmds=['sdocs'])), (("", "--test-runner-pkg",), dict(dest="test_runner_pkg", help=("name of package " "containing test runner " "program (default is " "test-harness)"), default="addon-sdk", cmds=['test', 'testex', 'testpkgs', 'testall'])), @@ -621,33 +612,16 @@ def run(arguments=sys.argv[1:], target_c test_all(env_root, defaults=options.__dict__) return elif command == "testcfx": if options.filter: print >>sys.stderr, "The filter option is not valid with the testcfx command" return test_cfx(env_root, options.verbose) return - elif command == "docs": - from cuddlefish.docs import generate - if len(args) > 1: - docs_home = generate.generate_named_file(env_root, filename_and_path=args[1]) - else: - docs_home = generate.generate_local_docs(env_root) - webbrowser.open(docs_home) - return - elif command == "sdocs": - from cuddlefish.docs import generate - filename="" - if options.override_version: - filename = generate.generate_static_docs(env_root, override_version=options.override_version) - else: - filename = generate.generate_static_docs(env_root) - print >>stdout, "Wrote %s." % filename - return elif command not in ["xpi", "test", "run"]: print >>sys.stderr, "Unknown command: %s" % command print >>sys.stderr, "Try using '--help' for assistance." sys.exit(1) target_cfg_json = None if not target_cfg: if not options.pkgdir: @@ -661,16 +635,20 @@ def run(arguments=sys.argv[1:], target_c if not os.path.exists(os.path.join(options.pkgdir, 'package.json')): print >>sys.stderr, ("cannot find 'package.json' in" " %s." % options.pkgdir) sys.exit(1) target_cfg_json = os.path.join(options.pkgdir, 'package.json') target_cfg = packaging.get_config_in_dir(options.pkgdir) + if options.manifest_overload: + for k, v in packaging.load_json_file(options.manifest_overload).items(): + target_cfg[k] = v + # At this point, we're either building an XPI or running Jetpack code in # a Mozilla application (which includes running tests). use_main = False inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory'] enforce_timeouts = False if command == "xpi":
new file mode 100644 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/addons/simplest-test/manifest-overload.json @@ -0,0 +1,3 @@ +{ + "version": "1.0-nightly" +}
--- a/addon-sdk/source/python-lib/cuddlefish/tests/test_init.py +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_init.py @@ -1,13 +1,14 @@ # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. import os, unittest, shutil +import zipfile from StringIO import StringIO from cuddlefish import initializer from cuddlefish.templates import TEST_MAIN_JS, PACKAGE_JSON tests_path = os.path.abspath(os.path.dirname(__file__)) class TestInit(unittest.TestCase): @@ -191,16 +192,31 @@ class TestCfxQuits(unittest.TestCase): def test_cfx_test(self): addon_path = os.path.join(tests_path, "addons", "simplest-test") rc, out, err = self.run_cfx(addon_path, ["test"]) self.assertEqual(rc, 0) self.assertIn("1 of 1 tests passed.", err) self.assertIn("Program terminated successfully.", err) + def test_cfx_xpi(self): + addon_path = os.path.join(tests_path, + "addons", "simplest-test") + rc, out, err = self.run_cfx(addon_path, \ + ["xpi", "--manifest-overload", "manifest-overload.json"]) + self.assertEqual(rc, 0) + # Ensure that the addon version from our manifest overload is used + # in install.rdf + xpi_path = os.path.join(addon_path, "simplest-test.xpi") + xpi = zipfile.ZipFile(xpi_path, "r") + manifest = xpi.read("install.rdf") + self.assertIn("<em:version>1.0-nightly</em:version>", manifest) + xpi.close() + os.remove(xpi_path) + def test_cfx_init(self): # Create an empty test directory addon_path = os.path.abspath(os.path.join(".test_tmp", "test-cfx-init")) if os.path.isdir(addon_path): shutil.rmtree(addon_path) os.makedirs(addon_path) # Fake a call to cfx init
new file mode 100644 --- /dev/null +++ b/addon-sdk/source/test/addons/translators/main.js @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +'use strict'; + +const { Cc, Ci, Cu, Cm, components } = require('chrome'); +const self = require('sdk/self'); +const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {}); + + +exports.testTranslators = function(assert, done) { + AddonManager.getAddonByID(self.id, function(addon) { + let count = 0; + addon.translators.forEach(function({ name }) { + count++; + assert.equal(name, 'Erik Vold', 'The translator keys are correct'); + }); + assert.equal(count, 1, 'The translator key count is correct'); + assert.equal(addon.translators.length, 1, 'The translator key length is correct'); + done(); + }); +} + +require('sdk/test/runner').runTestsFromModule(module);
new file mode 100644 --- /dev/null +++ b/addon-sdk/source/test/addons/translators/package.json @@ -0,0 +1,6 @@ +{ + "id": "test-translators", + "translators": [ + "Erik Vold" + ] +}
--- a/addon-sdk/source/test/test-context-menu.html +++ b/addon-sdk/source/test/test-context-menu.html @@ -1,16 +1,19 @@ <!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> <html> <head> <meta charset="UTF-8"> <title>Context menu test</title> + <style> + p { display: inline-block; } + </style> </head> <body> <p> <img id="image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="> </p> <p> <a id="link" href=""> @@ -47,10 +50,32 @@ <a id="targetlink" target="_blank" href=""> A targetted link. </a> </p> <p> <input type="submit" id="button"> </p> + + <p> + <a class="predicate-test-a" href="#test"> + A link with no ID and an anchor, used by PredicateContext tests. + </a> + </p> + + <p> + <input type="text" id="textbox" value="test value"> + </p> + + <p> + <input type="text" id="readonly-textbox" readonly="true" value="readonly value"> + </p> + + <p> + <input type="text" id="disabled-textbox" disabled="true" value="disabled value"> + </p> + + <p> + <p contenteditable="true" id="editable">This content is editable.</p> + </p> </body> </html>
--- a/addon-sdk/source/test/test-context-menu.js +++ b/addon-sdk/source/test/test-context-menu.js @@ -3130,16 +3130,498 @@ exports.testSelectionInOuterFrameNoMatch test.showMenu(frame.contentDocument.getElementById("text"), function (popup) { test.checkMenu(items, items, []); test.done(); }); }); }; + +// Test that the return value of the predicate function determines if +// item is shown +exports.testPredicateContextControl = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let itemTrue = loader.cm.Item({ + label: "visible", + context: loader.cm.PredicateContext(function () { return true; }) + }); + + let itemFalse = loader.cm.Item({ + label: "hidden", + context: loader.cm.PredicateContext(function () { return false; }) + }); + + test.showMenu(null, function (popup) { + test.checkMenu([itemTrue, itemFalse], [itemFalse], []); + test.done(); + }); +}; + +// Test that the data object has the correct document type +exports.testPredicateContextDocumentType = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.equal(data.documentType, 'text/html'); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(null, function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + +// Test that the data object has the correct document URL +exports.testPredicateContextDocumentURL = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.equal(data.documentURL, TEST_DOC_URL); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(null, function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + + +// Test that the data object has the correct element name +exports.testPredicateContextTargetName = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.targetName, "input"); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("button"), function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + + +// Test that the data object has the correct ID +exports.testPredicateContextTargetIDSet = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.targetID, "button"); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("button"), function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + +// Test that the data object has the correct ID +exports.testPredicateContextTargetIDNotSet = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.targetID, null); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + +// Test that the data object is showing editable correctly for regular text inputs +exports.testPredicateContextTextBoxIsEditable = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.isEditable, true); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("textbox"), function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + +// Test that the data object is showing editable correctly for readonly text inputs +exports.testPredicateContextReadonlyTextBoxIsNotEditable = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.isEditable, false); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("readonly-textbox"), function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + +// Test that the data object is showing editable correctly for disabled text inputs +exports.testPredicateContextDisabledTextBoxIsNotEditable = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.isEditable, false); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("disabled-textbox"), function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + +// Test that the data object is showing editable correctly for text areas +exports.testPredicateContextTextAreaIsEditable = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.isEditable, true); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("textfield"), function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + +// Test that non-text inputs are not considered editable +exports.testPredicateContextButtonIsNotEditable = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.isEditable, false); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("button"), function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + + +// Test that the data object is showing editable correctly +exports.testPredicateContextNonInputIsNotEditable = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.isEditable, false); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("image"), function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + + +// Test that the data object is showing editable correctly for HTML contenteditable elements +exports.testPredicateContextEditableElement = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.isEditable, true); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("editable"), function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + + +// Test that the data object does not have a selection when there is none +exports.testPredicateContextNoSelectionInPage = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.selectionText, null); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(null, function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + +// Test that the data object includes the selected page text +exports.testPredicateContextSelectionInPage = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + // since we might get whitespace + assert.ok(data.selectionText && data.selectionText.search(/^\s*Some text.\s*$/) != -1, + 'Expected "Some text.", got "' + data.selectionText + '"'); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + window.getSelection().selectAllChildren(doc.getElementById("text")); + test.showMenu(null, function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + +// Test that the data object includes the selected input text +exports.testPredicateContextSelectionInTextBox = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + // since we might get whitespace + assert.strictEqual(data.selectionText, "t v"); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + let textbox = doc.getElementById("textbox"); + textbox.focus(); + textbox.setSelectionRange(3, 6); + test.showMenu(textbox, function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + +// Test that the data object has the correct src for an image +exports.testPredicateContextTargetSrcSet = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + let image; + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.srcURL, image.src); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + image = doc.getElementById("image"); + test.showMenu(image, function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + +// Test that the data object has no src for a link +exports.testPredicateContextTargetSrcNotSet = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.srcURL, null); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("link"), function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + + +// Test that the data object has the correct link set +exports.testPredicateContextTargetLinkSet = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + let image; + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.linkURL, TEST_DOC_URL + "#test"); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + +// Test that the data object has no link for an image +exports.testPredicateContextTargetLinkNotSet = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.linkURL, null); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("image"), function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + +// Test that the data object has the value for an input textbox +exports.testPredicateContextTargetValueSet = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + let image; + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.value, "test value"); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("textbox"), function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + +// Test that the data object has no value for an image +exports.testPredicateContextTargetValueNotSet = function (assert, done) { + let test = new TestHelper(assert, done); + let loader = test.newLoader(); + + let items = [loader.cm.Item({ + label: "item", + context: loader.cm.PredicateContext(function (data) { + assert.strictEqual(data.value, null); + return true; + }) + })]; + + test.withTestDoc(function (window, doc) { + test.showMenu(doc.getElementById("image"), function (popup) { + test.checkMenu(items, [], []); + test.done(); + }); + }); +}; + + // NO TESTS BELOW THIS LINE! /////////////////////////////////////////////////// // This makes it easier to run tests by handling things like opening the menu, // opening new windows, making assertions, etc. Methods on |test| can be called // on instances of this class. Don't forget to call done() to end the test! // WARNING: This looks up items in popups by comparing labels, so don't give two // items the same label. function TestHelper(assert, done) {
new file mode 100644 --- /dev/null +++ b/addon-sdk/source/test/test-sequence.js @@ -0,0 +1,1163 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +let { seq, iterate, filter, map, reductions, reduce, count, + isEmpty, every, isEvery, some, take, takeWhile, drop, + dropWhile, concat, first, rest, nth, last, dropLast, + distinct, remove, mapcat, fromEnumerator, string, + object, pairs, keys, values, each + } = require("sdk/util/sequence"); + +const boom = () => { throw new Error("Boom!"); }; +const broken = seq(function*() { + yield 1; + throw new Error("Boom!"); +}); + +exports["test seq"] = assert => { + let xs = seq(function*() { + yield 1; + yield 2; + yield 3; + }); + + assert.deepEqual([...seq(null)], [], "seq of null is empty"); + assert.deepEqual([...seq(void(0))], [], "seq of void is empty"); + assert.deepEqual([...xs], [1, 2, 3], "seq of 1 2 3"); + assert.deepEqual([...seq(xs)], [1, 2, 3], "seq of seq is seq"); + + assert.deepEqual([...seq([])], [], "seq of emtpy array is empty"); + assert.deepEqual([...seq([1])], [1], "seq of lonly array is single element"); + assert.deepEqual([...seq([1, 2, 3])], [1, 2, 3], "seq of array is it's elements"); + + assert.deepEqual([...seq("")], [], "seq of emtpy string is empty"); + assert.deepEqual([...seq("o")], ["o"], "seq of char is single char seq"); + assert.deepEqual([...seq("hello")], ["h", "e", "l", "l", "o"], + "seq of string are chars"); + + assert.deepEqual([...seq(new Set())], [], "seq of emtpy set is empty"); + assert.deepEqual([...seq(new Set([1]))], [1], "seq of lonely set is single"); + assert.deepEqual([...seq(new Set([1, 2, 3]))], [1, 2, 3], "seq of lonely set is single"); + + assert.deepEqual([...seq(new Map())], [], "seq of emtpy map is empty"); + assert.deepEqual([...seq(new Map([[1, 2]]))], [[1, 2]], "seq single mapping is that mapping"); + assert.deepEqual([...seq(new Map([[1, 2], [3, 4], [5, 6]]))], + [[1, 2], [3, 4], [5, 6]], + "seq of map is key value mappings"); + + [function(){}, 1, /foo/, true].forEach(x => { + assert.throws(() => seq(x), "Type is not seq-able"); + }); + + assert.throws(() => [...broken], + /Boom/, + "broken sequence errors propagate"); +}; + +exports["test seq casting"] = assert => { + const xs = seq(function*() { yield 1; yield 2; yield 3; }); + const ys = seq(function*() { yield 1; }); + const zs = seq(function*() {}); + const kvs = seq(function*() { yield ["a", 1]; yield ["b", 2]; }); + const kv = seq(function*() { yield ["a", 1]; }); + + assert.deepEqual([...xs], [1, 2, 3], "cast to array"); + assert.deepEqual([...ys], [1], "cast to of one element"); + assert.deepEqual([...zs], [], "cast empty array"); + + assert.deepEqual(string(...xs), "123", "cast to string"); + assert.deepEqual(string(...ys), "1", "cast to char"); + assert.deepEqual(string(...zs), "", "cast to empty string"); + + assert.deepEqual(new Set(xs), new Set([1, 2, 3]), + "cast to set of items"); + assert.deepEqual(new Set(ys), new Set([1]), + "cast to set of one item"); + assert.deepEqual(new Set(zs), new Set(), + "cast to set of one item"); + + assert.deepEqual(new Map(kvs), new Map([["a", 1], ["b", 2]]), + "cast to map"); + assert.deepEqual(new Map(kv), new Map([["a", 1]]), + "cast to single mapping"); + assert.deepEqual(new Map(zs), new Map(), + "cast to empty map"); + + assert.deepEqual(object(...kvs), {a: 1, b: 2}, + "cast to object"); + assert.deepEqual(object(...kv), {a: 1}, + "cast to single pair"); + assert.deepEqual(object(...zs), {}, + "cast to empty object"); +}; + +exports["test pairs"] = assert => { + assert.deepEqual([...pairs(null)], [], "pairs on null is empty"); + assert.deepEqual([...pairs(void(0))], [], "pairs on void is empty"); + assert.deepEqual([...pairs({})], [], "empty sequence"); + assert.deepEqual([...pairs({a: 1})], [["a", 1]], "single pair"); + assert.deepEqual([...pairs({a: 1, b: 2, c: 3})].sort(), + [["a", 1], ["b", 2], ["c", 3]], + "creates pairs"); + let items = []; + for (let [key, value] of pairs({a: 1, b: 2, c: 3})) + items.push([key, value]); + + assert.deepEqual(items.sort(), + [["a", 1], ["b", 2], ["c", 3]], + "for of works on pairs"); + + + assert.deepEqual([...pairs([])], [], "pairs on empty array is empty"); + assert.deepEqual([...pairs([1])], [[0, 1]], "pairs on array is [index, element]"); + assert.deepEqual([...pairs([1, 2, 3])], + [[0, 1], [1, 2], [2, 3]], + "for arrays it pair of [index, element]"); + + assert.deepEqual([...pairs("")], [], "pairs on empty string is empty"); + assert.deepEqual([...pairs("a")], [[0, "a"]], "pairs on char is [0, char]"); + assert.deepEqual([...pairs("hello")], + [[0, "h"], [1, "e"], [2, "l"], [3, "l"], [4, "o"]], + "for strings it's pair of [index, char]"); + + assert.deepEqual([...pairs(new Map())], + [], + "pairs on empty map is empty"); + assert.deepEqual([...pairs(new Map([[1, 3]]))], + [[1, 3]], + "pairs on single mapping single mapping"); + assert.deepEqual([...pairs(new Map([[1, 2], [3, 4]]))], + [[1, 2], [3, 4]], + "pairs on map returs key vaule pairs"); + + assert.throws(() => pairs(new Set()), + "can't pair set"); + + assert.throws(() => pairs(4), + "can't pair number"); + + assert.throws(() => pairs(true), + "can't pair boolean"); +}; + +exports["test keys"] = assert => { + assert.deepEqual([...keys(null)], [], "keys on null is empty"); + assert.deepEqual([...keys(void(0))], [], "keys on void is empty"); + assert.deepEqual([...keys({})], [], "empty sequence"); + assert.deepEqual([...keys({a: 1})], ["a"], "single key"); + assert.deepEqual([...keys({a: 1, b: 2, c: 3})].sort(), + ["a", "b", "c"], + "all keys"); + + let items = []; + for (let key of keys({a: 1, b: 2, c: 3})) + items.push(key); + + assert.deepEqual(items.sort(), + ["a", "b", "c"], + "for of works on keys"); + + + assert.deepEqual([...keys([])], [], "keys on empty array is empty"); + assert.deepEqual([...keys([1])], [0], "keys on array is indexes"); + assert.deepEqual([...keys([1, 2, 3])], + [0, 1, 2], + "keys on arrays returns indexes"); + + assert.deepEqual([...keys("")], [], "keys on empty string is empty"); + assert.deepEqual([...keys("a")], [0], "keys on char is 0"); + assert.deepEqual([...keys("hello")], + [0, 1, 2, 3, 4], + "keys on strings is char indexes"); + + assert.deepEqual([...keys(new Map())], + [], + "keys on empty map is empty"); + assert.deepEqual([...keys(new Map([[1, 3]]))], + [1], + "keys on single mapping single mapping is single key"); + assert.deepEqual([...keys(new Map([[1, 2], [3, 4]]))], + [1, 3], + "keys on map is keys from map"); + + assert.throws(() => keys(new Set()), + "can't keys set"); + + assert.throws(() => keys(4), + "can't keys number"); + + assert.throws(() => keys(true), + "can't keys boolean"); +}; + +exports["test values"] = assert => { + assert.deepEqual([...values({})], [], "empty sequence"); + assert.deepEqual([...values({a: 1})], [1], "single value"); + assert.deepEqual([...values({a: 1, b: 2, c: 3})].sort(), + [1, 2, 3], + "all values"); + + let items = []; + for (let value of values({a: 1, b: 2, c: 3})) + items.push(value); + + assert.deepEqual(items.sort(), + [1, 2, 3], + "for of works on values"); + + assert.deepEqual([...values([])], [], "values on empty array is empty"); + assert.deepEqual([...values([1])], [1], "values on array elements"); + assert.deepEqual([...values([1, 2, 3])], + [1, 2, 3], + "values on arrays returns elements"); + + assert.deepEqual([...values("")], [], "values on empty string is empty"); + assert.deepEqual([...values("a")], ["a"], "values on char is char"); + assert.deepEqual([...values("hello")], + ["h", "e", "l", "l", "o"], + "values on strings is chars"); + + assert.deepEqual([...values(new Map())], + [], + "values on empty map is empty"); + assert.deepEqual([...values(new Map([[1, 3]]))], + [3], + "keys on single mapping single mapping is single key"); + assert.deepEqual([...values(new Map([[1, 2], [3, 4]]))], + [2, 4], + "values on map is values from map"); + + assert.deepEqual([...values(new Set())], [], "values on empty set is empty"); + assert.deepEqual([...values(new Set([1]))], [1], "values on set is it's items"); + assert.deepEqual([...values(new Set([1, 2, 3]))], + [1, 2, 3], + "values on set is it's items"); + + + assert.throws(() => values(4), + "can't values number"); + + assert.throws(() => values(true), + "can't values boolean"); +}; + +exports["test fromEnumerator"] = assert => { + const { Cc, Ci } = require("chrome"); + const { enumerateObservers, + addObserver, + removeObserver } = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + + + const topic = "sec:" + Math.random().toString(32).substr(2); + const [a, b, c] = [{wrappedJSObject: {}}, + {wrappedJSObject: {}}, + {wrappedJSObject: {}}]; + const unwrap = x => x.wrappedJSObject; + + [a, b, c].forEach(x => addObserver(x, topic, false)); + + const xs = fromEnumerator(() => enumerateObservers(topic)); + const ys = map(unwrap, xs); + + assert.deepEqual([...ys], [a, b, c].map(unwrap), + "all observers are there"); + + removeObserver(b, topic); + + assert.deepEqual([...ys], [a, c].map(unwrap), + "b was removed"); + + removeObserver(a, topic); + + assert.deepEqual([...ys], [c].map(unwrap), + "a was removed"); + + removeObserver(c, topic); + + assert.deepEqual([...ys], [], + "c was removed, now empty"); + + addObserver(a, topic, false); + + assert.deepEqual([...ys], [a].map(unwrap), + "a was added"); + + removeObserver(a, topic); + + assert.deepEqual([...ys], [].map(unwrap), + "a was removed, now empty"); + +}; + +exports["test filter"] = assert => { + const isOdd = x => x % 2; + const odds = seq(function*() { yield 1; yield 3; yield 5; }); + const evens = seq(function*() { yield 2; yield 4; yield 6; }); + const mixed = seq(function*() { + yield 1; + yield 2; + yield 3; + yield 4; + }); + + assert.deepEqual([...filter(isOdd, mixed)], [1, 3], + "filtered odds"); + assert.deepEqual([...filter(isOdd, odds)], [1, 3, 5], + "kept all"); + assert.deepEqual([...filter(isOdd, evens)], [], + "kept none"); + + + let xs = filter(boom, mixed); + assert.throws(() => [...xs], /Boom/, "errors propagate"); + + assert.throws(() => [...filter(isOdd, broken)], /Boom/, + "sequence errors propagate"); +}; + +exports["test filter array"] = assert => { + let isOdd = x => x % 2; + let xs = filter(isOdd, [1, 2, 3, 4]); + let ys = filter(isOdd, [1, 3, 5]); + let zs = filter(isOdd, [2, 4, 6]); + + assert.deepEqual([...xs], [1, 3], "filteres odds"); + assert.deepEqual([...ys], [1, 3, 5], "kept all"); + assert.deepEqual([...zs], [], "kept none"); + assert.ok(!Array.isArray(xs)); +}; + +exports["test filter set"] = assert => { + let isOdd = x => x % 2; + let xs = filter(isOdd, new Set([1, 2, 3, 4])); + let ys = filter(isOdd, new Set([1, 3, 5])); + let zs = filter(isOdd, new Set([2, 4, 6])); + + assert.deepEqual([...xs], [1, 3], "filteres odds"); + assert.deepEqual([...ys], [1, 3, 5], "kept all"); + assert.deepEqual([...zs], [], "kept none"); +}; + +exports["test filter string"] = assert => { + let isUpperCase = x => x.toUpperCase() === x; + let xs = filter(isUpperCase, "aBcDe"); + let ys = filter(isUpperCase, "ABC"); + let zs = filter(isUpperCase, "abcd"); + + assert.deepEqual([...xs], ["B", "D"], "filteres odds"); + assert.deepEqual([...ys], ["A", "B", "C"], "kept all"); + assert.deepEqual([...zs], [], "kept none"); +}; + +exports["test filter lazy"] = assert => { + const x = 1; + let y = 2; + + const xy = seq(function*() { yield x; yield y; }); + const isOdd = x => x % 2; + const actual = filter(isOdd, xy); + + assert.deepEqual([...actual], [1], "only one odd number"); + y = 3; + assert.deepEqual([...actual], [1, 3], "filter is lazy"); +}; + +exports["test filter non sequences"] = assert => { + const False = _ => false; + assert.throws(() => [...filter(False, 1)], + "can't iterate number"); + assert.throws(() => [...filter(False, {a: 1, b:2})], + "can't iterate object"); +}; + +exports["test map"] = assert => { + let inc = x => x + 1; + let xs = seq(function*() { yield 1; yield 2; yield 3; }); + let ys = map(inc, xs); + + assert.deepEqual([...ys], [2, 3, 4], "incremented each item"); + + assert.deepEqual([...map(inc, null)], [], "mapping null is empty"); + assert.deepEqual([...map(inc, void(0))], [], "mapping void is empty"); + assert.deepEqual([...map(inc, new Set([1, 2, 3]))], [2, 3, 4], "maps set items"); +}; + +exports["test map two inputs"] = assert => { + let sum = (x, y) => x + y; + let xs = seq(function*() { yield 1; yield 2; yield 3; }); + let ys = seq(function*() { yield 4; yield 5; yield 6; }); + + let zs = map(sum, xs, ys); + + assert.deepEqual([...zs], [5, 7, 9], "summed numbers"); +}; + +exports["test map diff sized inputs"] = assert => { + let sum = (x, y) => x + y; + let xs = seq(function*() { yield 1; yield 2; yield 3; }); + let ys = seq(function*() { yield 4; yield 5; yield 6; yield 7; yield 8; }); + + let zs = map(sum, xs, ys); + + assert.deepEqual([...zs], [5, 7, 9], "summed numbers"); + assert.deepEqual([...map(sum, ys, xs)], [5, 7, 9], + "index of exhasting input is irrelevant"); +}; + +exports["test map multi"] = assert => { + let sum = (x, y, z, w) => x + y + z + w; + let xs = seq(function*() { yield 1; yield 2; yield 3; yield 4; }); + let ys = seq(function*() { yield 4; yield 5; yield 6; yield 7; yield 8; }); + let zs = seq(function*() { yield 10; yield 11; yield 12; }); + let ws = seq(function*() { yield 0; yield 20; yield 40; yield 60; }); + + let actual = map(sum, xs, ys, zs, ws); + + assert.deepEqual([...actual], [15, 38, 61], "summed numbers"); +}; + +exports["test map errors"] = assert => { + assert.deepEqual([...map(boom, [])], [], + "won't throw if empty"); + + const xs = map(boom, [1, 2, 4]); + + assert.throws(() => [...xs], /Boom/, "propagates errors"); + + assert.throws(() => [...map(x => x, broken)], /Boom/, + "sequence errors propagate"); +}; + +exports["test reductions"] = assert => { + let sum = (...xs) => xs.reduce((x, y) => x + y, 0); + + assert.deepEqual([...reductions(sum, [1, 1, 1, 1])], + [1, 2, 3, 4], + "works with arrays"); + assert.deepEqual([...reductions(sum, 5, [1, 1, 1, 1])], + [5, 6, 7, 8, 9], + "array with initial"); + + assert.deepEqual([...reductions(sum, seq(function*() { + yield 1; + yield 2; + yield 3; + }))], + [1, 3, 6], + "works with sequences"); + + assert.deepEqual([...reductions(sum, 10, seq(function*() { + yield 1; + yield 2; + yield 3; + }))], + [10, 11, 13, 16], + "works with sequences"); + + assert.deepEqual([...reductions(sum, [])], [0], + "invokes accumulator with no args"); + + assert.throws(() => [...reductions(boom, 1, [1])], + /Boom/, + "arg errors errors propagate"); + assert.throws(() => [...reductions(sum, 1, broken)], + /Boom/, + "sequence errors propagate"); +}; + +exports["test reduce"] = assert => { + let sum = (...xs) => xs.reduce((x, y) => x + y, 0); + + assert.deepEqual(reduce(sum, [1, 2, 3, 4, 5]), + 15, + "works with arrays"); + + assert.deepEqual(reduce(sum, seq(function*() { + yield 1; + yield 2; + yield 3; + })), + 6, + "works with sequences"); + + assert.deepEqual(reduce(sum, 10, [1, 2, 3, 4, 5]), + 25, + "works with array & initial"); + + assert.deepEqual(reduce(sum, 5, seq(function*() { + yield 1; + yield 2; + yield 3; + })), + 11, + "works with sequences & initial"); + + assert.deepEqual(reduce(sum, []), 0, "reduce with no args"); + assert.deepEqual(reduce(sum, "a", []), "a", "reduce with initial"); + assert.deepEqual(reduce(sum, 1, [1]), 2, "reduce with single & initial"); + + assert.throws(() => [...reduce(boom, 1, [1])], + /Boom/, + "arg errors errors propagate"); + assert.throws(() => [...reduce(sum, 1, broken)], + /Boom/, + "sequence errors propagate"); +}; + +exports["test each"] = assert => { + const collect = xs => { + let result = []; + each((...etc) => result.push(...etc), xs); + return result; + }; + + assert.deepEqual(collect(null), [], "each ignores null"); + assert.deepEqual(collect(void(0)), [], "each ignores void"); + + assert.deepEqual(collect([]), [], "each ignores empty"); + assert.deepEqual(collect([1]), [1], "each works on single item arrays"); + assert.deepEqual(collect([1, 2, 3, 4, 5]), + [1, 2, 3, 4, 5], + "works with arrays"); + + assert.deepEqual(collect(seq(function*() { + yield 1; + yield 2; + yield 3; + })), + [1, 2, 3], + "works with sequences"); + + assert.deepEqual(collect(""), [], "ignores empty strings"); + assert.deepEqual(collect("a"), ["a"], "works on chars"); + assert.deepEqual(collect("hello"), ["h", "e", "l", "l", "o"], + "works on strings"); + + assert.deepEqual(collect(new Set()), [], "ignores empty sets"); + assert.deepEqual(collect(new Set(["a"])), ["a"], + "works on single item sets"); + assert.deepEqual(collect(new Set([1, 2, 3])), [1, 2, 3], + "works on muti item tests"); + + assert.deepEqual(collect(new Map()), [], "ignores empty maps"); + assert.deepEqual(collect(new Map([["a", 1]])), [["a", 1]], + "works on single mapping maps"); + assert.deepEqual(collect(new Map([[1, 2], [3, 4], [5, 6]])), + [[1, 2], [3, 4], [5, 6]], + "works on muti mapping maps"); + + assert.throws(() => collect({}), "objects arn't supported"); + assert.throws(() => collect(1), "numbers arn't supported"); + assert.throws(() => collect(true), "booleas arn't supported"); +}; + +exports["test count"] = assert => { + assert.equal(count(null), 0, "null counts to 0"); + assert.equal(count(), 0, "undefined counts to 0"); + assert.equal(count([]), 0, "empty array"); + assert.equal(count([1, 2, 3]), 3, "non-empty array"); + assert.equal(count(""), 0, "empty string"); + assert.equal(count("hello"), 5, "non-empty string"); + assert.equal(count(new Map()), 0, "empty map"); + assert.equal(count(new Map([[1, 2], [2, 3]])), 2, "non-empty map"); + assert.equal(count(new Set()), 0, "empty set"); + assert.equal(count(new Set([1, 2, 3, 4])), 4, "non-empty set"); + assert.equal(count(seq(function*() {})), 0, "empty sequence"); + assert.equal(count(seq(function*() { yield 1; yield 2; })), 2, + "non-empty sequence"); + + assert.throws(() => count(broken), + /Boom/, + "sequence errors propagate"); +}; + +exports["test isEmpty"] = assert => { + assert.equal(isEmpty(null), true, "null is empty"); + assert.equal(isEmpty(), true, "undefined is empty"); + assert.equal(isEmpty([]), true, "array is array"); + assert.equal(isEmpty([1, 2, 3]), false, "array isn't empty"); + assert.equal(isEmpty(""), true, "string is empty"); + assert.equal(isEmpty("hello"), false, "non-empty string"); + assert.equal(isEmpty(new Map()), true, "empty map"); + assert.equal(isEmpty(new Map([[1, 2], [2, 3]])), false, "non-empty map"); + assert.equal(isEmpty(new Set()), true, "empty set"); + assert.equal(isEmpty(new Set([1, 2, 3, 4])), false , "non-empty set"); + assert.equal(isEmpty(seq(function*() {})), true, "empty sequence"); + assert.equal(isEmpty(seq(function*() { yield 1; yield 2; })), false, + "non-empty sequence"); + + assert.equal(isEmpty(broken), false, "hasn't reached error"); +}; + +exports["test isEvery"] = assert => { + let isOdd = x => x % 2; + let isTrue = x => x === true; + let isFalse = x => x === false; + + assert.equal(isEvery(isOdd, seq(function*() { + yield 1; + yield 3; + yield 5; + })), true, "all are odds"); + + assert.equal(isEvery(isOdd, seq(function*() { + yield 1; + yield 2; + yield 3; + })), false, "contains even"); + + assert.equal(isEvery(isTrue, seq(function*() {})), true, "true if empty"); + assert.equal(isEvery(isFalse, seq(function*() {})), true, "true if empty"); + + assert.equal(isEvery(isTrue, null), true, "true for null"); + assert.equal(isEvery(isTrue, undefined), true, "true for undefined"); + + assert.throws(() => isEvery(boom, [1, 2]), + /Boom/, + "arg errors errors propagate"); + assert.throws(() => isEvery(x => true, broken), + /Boom/, + "sequence errors propagate"); + + assert.equal(isEvery(x => false, broken), false, + "hasn't reached error"); +}; + +exports["test some"] = assert => { + let isOdd = x => x % 2; + let isTrue = x => x === true; + let isFalse = x => x === false; + + assert.equal(some(isOdd, seq(function*() { + yield 2; + yield 4; + yield 6; + })), null, "all are even"); + + assert.equal(some(isOdd, seq(function*() { + yield 2; + yield 3; + yield 4; + })), true, "contains odd"); + + assert.equal(some(isTrue, seq(function*() {})), null, + "null if empty") + assert.equal(some(isFalse, seq(function*() {})), null, + "null if empty") + + assert.equal(some(isTrue, null), null, "null for null"); + assert.equal(some(isTrue, undefined), null, "null for undefined"); + + assert.throws(() => some(boom, [1, 2]), + /Boom/, + "arg errors errors propagate"); + assert.throws(() => some(x => false, broken), + /Boom/, + "sequence errors propagate"); + + assert.equal(some(x => true, broken), true, + "hasn't reached error"); +}; + +exports["test take"] = assert => { + let xs = seq(function*() { + yield 1; + yield 2; + yield 3; + yield 4; + yield 5; + yield 6; + }); + + assert.deepEqual([...take(3, xs)], [1, 2, 3], "took 3 items"); + assert.deepEqual([...take(3, [1, 2, 3, 4, 5])], [1, 2, 3], + "took 3 from array"); + + let ys = seq(function*() { yield 1; yield 2; }); + assert.deepEqual([...take(3, ys)], [1, 2], "takes at max n"); + assert.deepEqual([...take(3, [1, 2])], [1, 2], + "takes at max n from arary"); + + let empty = seq(function*() {}); + assert.deepEqual([...take(5, empty)], [], "nothing to take"); + + assert.throws(() => [...take(3, broken)], + /Boom/, + "sequence errors propagate"); + + assert.deepEqual([...take(1, broken)], [1], + "hasn't reached error"); +}; + +exports["test iterate"] = assert => { + let inc = x => x + 1; + let nums = iterate(inc, 0); + + assert.deepEqual([...take(5, nums)], [0, 1, 2, 3, 4], "took 5"); + assert.deepEqual([...take(10, nums)], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], "took 10"); + + let xs = iterate(x => x * 3, 2); + assert.deepEqual([...take(4, xs)], [2, 6, 18, 54], "took 4"); + + assert.throws(() => [...iterate(boom, 0)], + /Boom/, + "function exceptions propagate"); +}; + +exports["test takeWhile"] = assert => { + let isNegative = x => x < 0; + let xs = seq(function*() { + yield -2; + yield -1; + yield 0; + yield 1; + yield 2; + yield 3; + }); + + assert.deepEqual([...takeWhile(isNegative, xs)], [-2, -1], + "took until 0"); + + let ys = seq(function*() {}); + assert.deepEqual([...takeWhile(isNegative, ys)], [], + "took none"); + + let zs = seq(function*() { + yield 0; + yield 1; + yield 2; + yield 3; + }); + + assert.deepEqual([...takeWhile(isNegative, zs)], [], + "took none"); + + assert.throws(() => [...takeWhile(boom, zs)], + /Boom/, + "function errors errors propagate"); + assert.throws(() => [...takeWhile(x => true, broken)], + /Boom/, + "sequence errors propagate"); + + assert.deepEqual([...takeWhile(x => false, broken)], + [], + "hasn't reached error"); +}; + +exports["test drop"] = assert => { + let testDrop = xs => { + assert.deepEqual([...drop(2, xs)], + [3, 4], + "dropped two elements"); + + assert.deepEqual([...drop(1, xs)], + [2, 3, 4], + "dropped one"); + + assert.deepEqual([...drop(0, xs)], + [1, 2, 3, 4], + "dropped 0"); + + assert.deepEqual([...drop(-2, xs)], + [1, 2, 3, 4], + "dropped 0 on negative `n`"); + + assert.deepEqual([...drop(5, xs)], + [], + "dropped all items"); + }; + + testDrop([1, 2, 3, 4]); + testDrop(seq(function*() { + yield 1; + yield 2; + yield 3; + yield 4; + })); + + assert.throws(() => [...drop(1, broken)], + /Boom/, + "sequence errors propagate"); +}; + + +exports["test dropWhile"] = assert => { + let isNegative = x => x < 0; + let True = _ => true; + let False = _ => false; + + let test = xs => { + assert.deepEqual([...dropWhile(isNegative, xs)], + [0, 1, 2], + "dropped negative"); + + assert.deepEqual([...dropWhile(True, xs)], + [], + "drop all"); + + assert.deepEqual([...dropWhile(False, xs)], + [-2, -1, 0, 1, 2], + "keep all"); + }; + + test([-2, -1, 0, 1, 2]); + test(seq(function*() { + yield -2; + yield -1; + yield 0; + yield 1; + yield 2; + })); + + assert.throws(() => [...dropWhile(boom, [1, 2, 3])], + /Boom/, + "function errors errors propagate"); + assert.throws(() => [...dropWhile(x => true, broken)], + /Boom/, + "sequence errors propagate"); +}; + + +exports["test concat"] = assert => { + let test = (a, b, c, d) => { + assert.deepEqual([...concat()], + [], + "nothing to concat"); + assert.deepEqual([...concat(a)], + [1, 2, 3], + "concat with nothing returns same as first"); + assert.deepEqual([...concat(a, b)], + [1, 2, 3, 4, 5], + "concat items from both"); + assert.deepEqual([...concat(a, b, a)], + [1, 2, 3, 4, 5, 1, 2, 3], + "concat itself"); + assert.deepEqual([...concat(c)], + [], + "concat of empty is empty"); + assert.deepEqual([...concat(a, c)], + [1, 2, 3], + "concat with empty"); + assert.deepEqual([...concat(c, c, c)], + [], + "concat of empties is empty"); + assert.deepEqual([...concat(c, b)], + [4, 5], + "empty can be in front"); + assert.deepEqual([...concat(d)], + [7], + "concat singular"); + assert.deepEqual([...concat(d, d)], + [7, 7], + "concat singulars"); + + assert.deepEqual([...concat(a, a, b, c, d, c, d, d)], + [1, 2, 3, 1, 2, 3, 4, 5, 7, 7, 7], + "many concats"); + + let ab = concat(a, b); + let abcd = concat(ab, concat(c, d)); + let cdabcd = concat(c, d, abcd); + + assert.deepEqual([...cdabcd], + [7, 1, 2, 3, 4, 5, 7], + "nested concats"); + }; + + test([1, 2, 3], + [4, 5], + [], + [7]); + + test(seq(function*() { yield 1; yield 2; yield 3; }), + seq(function*() { yield 4; yield 5; }), + seq(function*() { }), + seq(function*() { yield 7; })); + + assert.throws(() => [...concat(broken, [1, 2, 3])], + /Boom/, + "function errors errors propagate"); +}; + + +exports["test first"] = assert => { + let test = (xs, empty) => { + assert.equal(first(xs), 1, "returns first"); + assert.equal(first(empty), null, "returns null empty"); + }; + + test("1234", ""); + test([1, 2, 3], []); + test([1, 2, 3], null); + test([1, 2, 3], undefined); + test(seq(function*() { yield 1; yield 2; yield 3; }), + seq(function*() { })); + assert.equal(first(broken), 1, "did not reached error"); +}; + +exports["test rest"] = assert => { + let test = (xs, x, nil) => { + assert.deepEqual([...rest(xs)], ["b", "c"], + "rest items"); + assert.deepEqual([...rest(x)], [], + "empty when singular"); + assert.deepEqual([...rest(nil)], [], + "empty when empty"); + }; + + test("abc", "a", ""); + test(["a", "b", "c"], ["d"], []); + test(seq(function*() { yield "a"; yield "b"; yield "c"; }), + seq(function*() { yield "d"; }), + seq(function*() {})); + test(["a", "b", "c"], ["d"], null); + test(["a", "b", "c"], ["d"], undefined); + + assert.throws(() => [...rest(broken)], + /Boom/, + "sequence errors propagate"); +}; + + +exports["test nth"] = assert => { + let notFound = {}; + let test = xs => { + assert.equal(nth(xs, 0), "h", "first"); + assert.equal(nth(xs, 1), "e", "second"); + assert.equal(nth(xs, 5), void(0), "out of bound"); + assert.equal(nth(xs, 5, notFound), notFound, "out of bound"); + assert.equal(nth(xs, -1), void(0), "out of bound"); + assert.equal(nth(xs, -1, notFound), notFound, "out of bound"); + assert.equal(nth(xs, 4), "o", "5th"); + }; + + let testEmpty = xs => { + assert.equal(nth(xs, 0), void(0), "no first in empty"); + assert.equal(nth(xs, 5), void(0), "no 5th in empty"); + assert.equal(nth(xs, 0, notFound), notFound, "notFound on out of bound"); + }; + + test("hello"); + test(["h", "e", "l", "l", "o"]); + test(seq(function*() { + yield "h"; + yield "e"; + yield "l"; + yield "l"; + yield "o"; + })); + testEmpty(null); + testEmpty(undefined); + testEmpty([]); + testEmpty(""); + testEmpty(seq(function*() {})); + + + assert.throws(() => nth(broken, 1), + /Boom/, + "sequence errors propagate"); + assert.equal(nth(broken, 0), 1, "have not reached error"); +}; + + +exports["test last"] = assert => { + assert.equal(last(null), null, "no last in null"); + assert.equal(last(void(0)), null, "no last in undefined"); + assert.equal(last([]), null, "no last in []"); + assert.equal(last(""), null, "no last in ''"); + assert.equal(last(seq(function*() { })), null, "no last in empty"); + + assert.equal(last("hello"), "o", "last from string"); + assert.equal(last([1, 2, 3]), 3, "last from array"); + assert.equal(last([1]), 1, "last from singular"); + assert.equal(last(seq(function*() { + yield 1; + yield 2; + yield 3; + })), 3, "last from sequence"); + + assert.throws(() => last(broken), + /Boom/, + "sequence errors propagate"); +}; + + +exports["test dropLast"] = assert => { + let test = xs => { + assert.deepEqual([...dropLast(xs)], + [1, 2, 3, 4], + "dropped last"); + assert.deepEqual([...dropLast(0, xs)], + [1, 2, 3, 4, 5], + "dropped none on 0"); + assert.deepEqual([...dropLast(-3, xs)], + [1, 2, 3, 4, 5], + "drop none on negative"); + assert.deepEqual([...dropLast(3, xs)], + [1, 2], + "dropped given number"); + assert.deepEqual([...dropLast(5, xs)], + [], + "dropped all"); + }; + + let testEmpty = xs => { + assert.deepEqual([...dropLast(xs)], + [], + "nothing to drop"); + assert.deepEqual([...dropLast(0, xs)], + [], + "dropped none on 0"); + assert.deepEqual([...dropLast(-3, xs)], + [], + "drop none on negative"); + assert.deepEqual([...dropLast(3, xs)], + [], + "nothing to drop"); + }; + + test([1, 2, 3, 4, 5]); + test(seq(function*() { + yield 1; + yield 2; + yield 3; + yield 4; + yield 5; + })); + testEmpty([]); + testEmpty(""); + testEmpty(seq(function*() {})); + + assert.throws(() => [...dropLast(broken)], + /Boom/, + "sequence errors propagate"); +}; + + +exports["test distinct"] = assert => { + let test = (xs, message) => { + assert.deepEqual([...distinct(xs)], + [1, 2, 3, 4, 5], + message); + }; + + test([1, 2, 1, 3, 1, 4, 1, 5], "works with arrays"); + test(seq(function*() { + yield 1; + yield 2; + yield 1; + yield 3; + yield 1; + yield 4; + yield 1; + yield 5; + }), "works with sequences"); + test(new Set([1, 2, 1, 3, 1, 4, 1, 5]), + "works with sets"); + test(seq(function*() { + yield 1; + yield 2; + yield 2; + yield 2; + yield 1; + yield 3; + yield 1; + yield 4; + yield 4; + yield 4; + yield 1; + yield 5; + }), "works with multiple repeatitions"); + test([1, 2, 3, 4, 5], "work with distinct arrays"); + test(seq(function*() { + yield 1; + yield 2; + yield 3; + yield 4; + yield 5; + }), "works with distinct seqs"); +}; + + +exports["test remove"] = assert => { + let isPositive = x => x > 0; + let test = xs => { + assert.deepEqual([...remove(isPositive, xs)], + [-2, -1, 0], + "removed positives"); + }; + + test([1, -2, 2, -1, 3, 7, 0]); + test(seq(function*() { + yield 1; + yield -2; + yield 2; + yield -1; + yield 3; + yield 7; + yield 0; + })); + + assert.throws(() => [...distinct(broken)], + /Boom/, + "sequence errors propagate"); +}; + + +exports["test mapcat"] = assert => { + let upto = n => seq(function* () { + let index = 0; + while (index < n) { + yield index; + index = index + 1; + } + }); + + assert.deepEqual([...mapcat(upto, [1, 2, 3, 4])], + [0, 0, 1, 0, 1, 2, 0, 1, 2, 3], + "expands given sequence"); + + assert.deepEqual([...mapcat(upto, [0, 1, 2, 0])], + [0, 0, 1], + "expands given sequence"); + + assert.deepEqual([...mapcat(upto, [0, 0, 0])], + [], + "expands given sequence"); + + assert.deepEqual([...mapcat(upto, [])], + [], + "nothing to expand"); + + assert.deepEqual([...mapcat(upto, null)], + [], + "nothing to expand"); + + assert.deepEqual([...mapcat(upto, void(0))], + [], + "nothing to expand"); + + let xs = seq(function*() { + yield 0; + yield 1; + yield 0; + yield 2; + yield 0; + }); + + assert.deepEqual([...mapcat(upto, xs)], + [0, 0, 1], + "expands given sequence"); + + assert.throws(() => [...mapcat(boom, xs)], + /Boom/, + "function errors errors propagate"); + assert.throws(() => [...mapcat(upto, broken)], + /Boom/, + "sequence errors propagate"); +}; + +require("sdk/test").run(exports);
--- a/addon-sdk/source/test/test-tab.js +++ b/addon-sdk/source/test/test-tab.js @@ -4,16 +4,17 @@ const tabs = require("sdk/tabs"); // From addon-kit const windowUtils = require("sdk/deprecated/window-utils"); const { getTabForWindow } = require('sdk/tabs/helpers'); const app = require("sdk/system/xul-app"); const { viewFor } = require("sdk/view/core"); const { getTabId } = require("sdk/tabs/utils"); +const { defer } = require("sdk/lang/functional"); // The primary test tab var primaryTab; // We have an auxiliary tab to test background tabs. var auxTab; // The window for the outer iframe in the primary test page @@ -134,21 +135,23 @@ exports["test behavior on close"] = func done(); }); } }); }; exports["test viewFor(tab)"] = (assert, done) => { - tabs.once("open", tab => { + // Note we defer handlers as length collection is updated after + // handler is invoked, so if test is finished before counnts are + // updated wrong length will show up in followup tests. + tabs.once("open", defer(tab => { const view = viewFor(tab); assert.ok(view, "view is returned"); assert.equal(getTabId(view), tab.id, "tab has a same id"); - tab.close(); - done(); - }); + tab.close(defer(done)); + })); tabs.open({ url: "about:mozilla" }); } require("test").run(exports);
--- a/addon-sdk/source/test/test-windows-common.js +++ b/addon-sdk/source/test/test-windows-common.js @@ -3,16 +3,18 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 'use strict'; const { Loader } = require('sdk/test/loader'); const { browserWindows } = require('sdk/windows'); const { viewFor } = require('sdk/view/core'); const { Ci } = require("chrome"); const { isBrowser, getWindowTitle } = require("sdk/window/utils"); +const { defer } = require("sdk/lang/functional"); + // TEST: browserWindows Iterator exports.testBrowserWindowsIterator = function(assert) { let activeWindowCount = 0; let windows = []; let i = 0; for each (let window in browserWindows) { if (window === browserWindows.activeWindow) @@ -25,16 +27,17 @@ exports.testBrowserWindowsIterator = fun assert.equal(activeWindowCount, 1, 'activeWindow was found in the iterator'); i = 0; for (let j in browserWindows) { assert.equal(j, i++, 'for (x in browserWindows) works'); } }; + exports.testWindowTabsObject_alt = function(assert, done) { let window = browserWindows.activeWindow; window.tabs.open({ url: 'data:text/html;charset=utf-8,<title>tab 2</title>', inBackground: true, onReady: function onReady(tab) { assert.equal(tab.title, 'tab 2', 'Correct new tab title'); assert.notEqual(window.tabs.activeTab, tab, 'Correct active tab'); @@ -53,28 +56,31 @@ exports.testWindowActivateMethod_simple window.activate(); assert.equal(browserWindows.activeWindow, window, 'Active window is active after window.activate() call'); assert.equal(window.tabs.activeTab, tab, 'Active tab is active after window.activate() call'); }; + exports["test getView(window)"] = function(assert, done) { browserWindows.once("open", window => { const view = viewFor(window); assert.ok(view instanceof Ci.nsIDOMWindow, "view is a window"); assert.ok(isBrowser(view), "view is a browser window"); assert.equal(getWindowTitle(view), window.title, "window has a right title"); window.close(); - window.destroy(); - assert.equal(viewFor(window), null, "window view is gone"); - done(); + // Defer handler cause window is destroyed after event is dispatched. + browserWindows.once("close", defer(_ => { + assert.equal(viewFor(window), null, "window view is gone"); + done(); + })); }); browserWindows.open({ url: "data:text/html,<title>yo</title>" }); }; require('sdk/test').run(exports);