<commit_before>import R from 'ramda';
import Bacon from 'baconjs';
import { getActionStream } from './dispatcher'

const hasMiddlewareType = (concat, type, middleware) => (
  R.is(Function, middleware[type])
);

const concatMiddlewareByType = (concat, type, middleware) => (
  concat(middleware[type])
);

const concatMiddleware = R.cond([
  [R.complement(R.nthArg(2)), R.F],
  [hasMiddlewareType, concatMiddlewareByType]
]);

// pluggables before store reducer.
const preReduces = (() => {
  let array = [];
  return (middleware) => (
    array = concatMiddleware(
      R.concat(array), 'getPreReduce', middleware) || array
  );
})();

// pluggables after store reducer.
const postReduces = (() => {
  let array = [];
  return (middleware) => (
    array = concatMiddleware(
      R.concat(array), 'getPostReduce', middleware) || array
  );
})();

const setPrePostReduce = R.pipe(
  R.tap(preReduces),
  postReduces
);

const mapPreArgs = (...args) => (
  R.merge({
    action: args[0],
    state: args[1]
  },
  args[2])
);

const mergeNextState = (reducerArgs, nextState) => (
  R.merge(reducerArgs, {
    nextState: nextState
  })
);

const wrapReducer = (getReducer) => (() => {
  let pluggable = getReducer();
  return {
    input: pluggable.input,
    output: pluggable.input.toProperty({})
      .sampledBy(pluggable.output, mergeNextState)
  }
});

const plugStreams = (fromStream, getPluggable) => {
  let pluggable = getPluggable();
  pluggable.input.plug(fromStream);
  return pluggable.output;
};

const plugPreReducerPost = (name, getReducer, reducerArgs) => (
  R.reduce(plugStreams,
    // pass action and store states,
    Bacon.when(reducerArgs, mapPreArgs)
      // merge in the store name.
      .map(R.merge({ name: name })),
    // to pre-reduce middlewares, reducer then post-reduce.
    R.flatten([preReduces(), wrapReducer(getReducer), postReduces()])
  )
  // get the reduced state.
  .map(R.prop('nextState'))
);

const getStoreProperties = R.pipe(
  R.map(R.invoker(0, 'getProperty')),
  Bacon.combineTemplate
);

export const applyMiddleware = (...args) => {
  // loop through an array of middlewares.
  R.forEach(setPrePostReduce, args);
};

export const createStore = (name, getReducer, otherStores = {}) => {
  let storeStream = new Bacon.Bus(),
      valveStream = new Bacon.Bus(),
      storeProperty = storeStream.toProperty(null),
      valveProperty = valveStream.toProperty(false),
      otherProperties = getStoreProperties(otherStores),
      actionStream = getActionStream().holdWhen(valveProperty),
      reducerArgs = [actionStream, storeProperty, otherProperties];

  valveStream.plug(Bacon.mergeAll(
    // hold and queue up other actions.
    actionStream.map(R.always(true)),
    // release the next action after reducing.
    storeStream.map(R.always(false))
  ));

  storeStream.plug(
    // store name, reducer and
    // an array of action and store states.
    plugPreReducerPost(name, getReducer, reducerArgs)
  );

  return {
    getProperty: () => storeProperty
  };
};
<commit_msg>Pass store name to middleware<commit_after>import R from 'ramda';
import Bacon from 'baconjs';
import { getActionStream } from './dispatcher'

const hasMiddlewareType = (concat, type, middleware) => (
  R.is(Function, middleware[type])
);

const concatMiddlewareByType = (concat, type, middleware) => (
  concat(middleware[type])
);

const concatMiddleware = R.cond([
  [R.complement(R.nthArg(2)), R.F],
  [hasMiddlewareType, concatMiddlewareByType]
]);

// pluggables before store reducer.
const preReduces = (() => {
  let array = [];
  return (middleware) => (
    array = concatMiddleware(
      R.concat(array), 'getPreReduce', middleware) || array
  );
})();

// pluggables after store reducer.
const postReduces = (() => {
  let array = [];
  return (middleware) => (
    array = concatMiddleware(
      R.concat(array), 'getPostReduce', middleware) || array
  );
})();

const setPrePostReduce = R.pipe(
  R.tap(preReduces),
  postReduces
);

const mapPreArgs = (...args) => (
  R.merge({
    action: args[0],
    state: args[1]
  },
  args[2])
);

const mergeNextState = (reducerArgs, nextState) => (
  R.merge(reducerArgs, {
    nextState: nextState
  })
);

const wrapReducer = (getReducer) => (() => {
  let pluggable = getReducer();
  return {
    input: pluggable.input,
    output: pluggable.input.toProperty({})
      .sampledBy(pluggable.output, mergeNextState)
  }
});

const plugStreams = R.curry((name, fromStream, getPluggable) => {
  let pluggable = getPluggable(name);
  pluggable.input.plug(fromStream);
  return pluggable.output;
});

const plugPreReducerPost = (name, getReducer, reducerArgs) => (
  // pass the store name to middlewares.
  R.reduce(plugStreams(name),
    // pass action and store states,
    Bacon.when(reducerArgs, mapPreArgs)
      // merge in the store name.
      .map(R.merge({ name: name })),
    // to pre-reduce middlewares, reducer then post-reduce.
    R.flatten([preReduces(), wrapReducer(getReducer), postReduces()])
  )
  // get the reduced state.
  .map(R.prop('nextState'))
);

const getStoreProperties = R.pipe(
  R.map(R.invoker(0, 'getProperty')),
  Bacon.combineTemplate
);

export const applyMiddleware = (...args) => {
  // loop through an array of middlewares.
  R.forEach(setPrePostReduce, args);
};

export const createStore = (name, getReducer, otherStores = {}) => {
  let storeStream = new Bacon.Bus(),
      valveStream = new Bacon.Bus(),
      storeProperty = storeStream.toProperty(null),
      valveProperty = valveStream.toProperty(false),
      otherProperties = getStoreProperties(otherStores),
      actionStream = getActionStream().holdWhen(valveProperty),
      reducerArgs = [actionStream, storeProperty, otherProperties];

  valveStream.plug(Bacon.mergeAll(
    // hold and queue up other actions.
    actionStream.map(R.always(true)),
    // release the next action after reducing.
    storeStream.map(R.always(false))
  ));

  storeStream.plug(
    // store name, reducer and
    // an array of action and store states.
    plugPreReducerPost(name, getReducer, reducerArgs)
  );

  return {
    getProperty: () => storeProperty
  };
};
<|endoftext|>


<commit_before>'use strict';

// define main constants
var ROOT       = 'LSA.';
var STORE      = window.localStorage;
var EXPIRE_KEY = '--exp=';

/**
 * Util functions
 * @private
 * @type {Object}
 */
var util = {
    remove: function(key) {
        try {
            STORE.removeItem(key.replace(EXPIRE_KEY, ''));
            STORE.removeItem(key);
        } catch(error) {
            throw error;
        }
    },
    flush: function(index) {
        Object.keys(STORE).map(key => {
            if (key.indexOf(index) === 0) {
                util.remove(key);
            }
        });
    },
    flushExpired: function(index) {
        if (Object.keys(STORE).length > 0) {
            Object.keys(STORE).map(key => {
                if (key.indexOf(index) === 0 && key.indexOf(EXPIRE_KEY) !== -1) {
                    if (parseInt(STORE.getItem(key)) < Date.now()) {
                        util.remove(key);
                    }
                }
            });
        }
    }
}

/**
 * Define a new store
 * @param  {string} name The name of the store
 * @return {Store}       The store instance
 */
var Store = function(name) {
    var storeName;

    function __construct(name) {
        try {
            storeName = validateStoreName(name);
            flushExpired();
        } catch(error) {
            throw error;
        }
    }

    /**
     * Set a defined key with the given value
     * @param  {string} key          The name of the value
     * @param  {mixed}  data         The data to store, can be any datatype
     * @param  {int}    [expiresAt]  When should this value expire
     * @return {void}
     */
    function set(key, data, expiresAt) {
        try {
            if (expires !== void(0) && typeof expires === 'number') {
                STORE.setItem(storeName + key + EXPIRE_KEY, expiresAt.toString());
            }

            STORE.setItem(storeName + key, decodeObjectString(data));

            return true;
        } catch(error) {
            throw error;
            return false;
        }
    }

    /**
     * Get the value stored under the given key, or the whole store
     * @param  {string} [key] Index defined in @set
     * @return {mixed}        Stored value
     */
    function get(key) {
        try {
            if (key !== undefined) {
                var result = encodeObjectString(STORE.getItem(storeName + key));
                return result !== null ? result : false;
            }
        } catch(error) {
            return false;
            throw error;
        }
    }

    /**
     * Check if the given key exists in the store
     * @param  {string}  key  index of the value to test
     * @return {boolean}      Key defined or not
     */
    function isset(key) {
        return get(key) !== false ? true : false;
    }

    /**
     * Flush all values, that are stored in the store instance
     * @return {void}
     */
    function flush() {
        util.flush(storeName);
    }

    /**
     * Remove an item, by the given key and its expire if set
     * @param  {string} key  Index of the value
     * @return {void}
     */
    function remove(key) {
        util.remove(key);
    }

    /**
     * flush expired values defined in this store
     * @return {void}
     */
    function flushExpired() {
        util.flushExpired(storeName);
    }

    /**
     * generate a store name
     * @param  {string} storeName  The name of the store instance
     * @return {string}            The generated name
     */
    function validateStoreName(storeName) {
        if (storeName === void(0)) throw new TypeError('Please provide a storename');
        if (typeof(storeName) !== 'string') throw new TypeError('The storename has to be a string');

        return storeName = ROOT + storeName + '-';
    }

    /**
     * decode a value if its type is a object
     * @private
     * @param  {mixed}  data  Value to decode
     * @return {mixed}        The decoded value
     */
    function decodeObjectString(data) {
        if (typeof data == 'object') {
            return JSON.stringify(data);
        }

        return data;
    }

    /**
     * encode a value if the value is a object
     * @param  {mixed} data  The value to encode
     * @return {mixed}       The encoded value
     */
    function encodeObjectString(data) {
        if (typeof data == 'string') {
            return JSON.parse(data);
        }

        return data;
    }

    __construct(name);

    // define public interface
    return {
        set    : set,
        get    : get,
        flush  : flush,
        remove : remove,
        isset  : isset
    }
}

// static store methods

/**
 * remove all items from the whole store
 * @return {void}
 */
Store.flush = function() {
    util.flush(ROOT);
}

/**
 * remove all expired items from the whole store
 * @return {void}
 */
Store.flushExpired = function() {
    util.flushExpired(ROOT);
}

// export the module
if (typeof module !== 'undefined' && module.exports) {
    module.exports = Store;
}
<commit_msg>added documentation and fixed the util functions<commit_after>'use strict';

// define main constants
var ROOT       = 'LSA.';
var STORE      = window.localStorage;
var EXPIRE_KEY = '--exp=';

/**
 * Util functions
 * @private
 * @type {Object}
 */
var util = {
    remove: function(key) {
        try {
            STORE.removeItem(key.replace(EXPIRE_KEY, ''));
            STORE.removeItem(key);
        } catch(error) {
            throw error;
        }
    },
    flush: function(index) {
        Object.keys(STORE).map(function(key) {
            if (key.indexOf(index) === 0) {
                util.remove(key);
            }
        });
    },
    flushExpired: function(index) {
        if (Object.keys(STORE).length > 0) {
            Object.keys(STORE).map(function(key) {
                if (key.indexOf(index) === 0 && key.indexOf(EXPIRE_KEY) !== -1) {
                    if (parseInt(STORE.getItem(key)) < Date.now()) {
                        util.remove(key);
                    }
                }
            });
        }
    }
}

/**
 * Define a new store
 * @class
 * @param  {string} name The name of the store
 * @return {Store}       The store instance
 */
var Store = function(name) {
    var storeName;

    function __construct(name) {
        try {
            storeName = validateStoreName(name);
            flushExpired();
        } catch(error) {
            throw error;
        }
    }

    /**
     * Set a defined key with the given value
     * @param  {string} key          The name of the value
     * @param  {mixed}  data         The data to store, can be any datatype
     * @param  {int}    [expiresAt]  When should this value expire
     * @return {void}
     */
    function set(key, data, expiresAt) {
        try {
            if (expiresAt !== undefined && typeof expiresAt === 'number') {
                STORE.setItem(storeName + key + EXPIRE_KEY, expiresAt.toString());
            }

            STORE.setItem(storeName + key, decodeObjectString(data));

            return true;
        } catch(error) {
            throw error;
            return false;
        }
    }

    /**
     * Get the value stored under the given key, or the whole store
     * @param  {string} [key] Index defined in @set
     * @return {mixed}        Stored value
     */
    function get(key) {
        try {
            if (key !== undefined) {
                var result = encodeObjectString(STORE.getItem(storeName + key));
                return result !== null ? result : false;
            } else {
                var result = {};

                Object.keys(STORE).map(function(key) {
                    if (key.indexOf(storeName) == 0 && key.indexOf(EXPIRE_KEY) === -1) {
                        var varKey = key.replace(storeName, '');
                        result[varKey] = get(varKey);
                    }
                });

                return result;
            }
        } catch(error) {
            console.error(error)
            throw error;
            return false;
        }
    }

    /**
     * Check if the given key exists in the store
     * @param  {string}  key  index of the value to test
     * @return {boolean}      Key defined or not
     */
    function isset(key) {
        return get(key) !== false ? true : false;
    }

    /**
     * Flush all values, that are stored in the store instance
     * @return {void}
     */
    function flush() {
        util.flush(storeName);
    }

    /**
     * Remove an item, by the given key and its expire if set
     * @param  {string} key  Index of the value
     * @return {void}
     */
    function remove(key) {
        util.remove(storeName + key);
    }

    /**
     * flush expired values defined in this store
     * @return {void}
     */
    function flushExpired() {
        util.flushExpired(storeName);
    }

    /**
     * generate a store name
     * @param  {string} storeName  The name of the store instance
     * @return {string}            The generated name
     */
    function validateStoreName(storeName) {
        if (storeName === void(0)) throw new TypeError('Please provide a storename');
        if (typeof(storeName) !== 'string') throw new TypeError('The storename has to be a string');

        return storeName = ROOT + storeName + '-';
    }

    /**
     * decode a value if its type is a object
     * @private
     * @param  {mixed}  data  Value to decode
     * @return {mixed}        The decoded value
     */
    function decodeObjectString(data) {
        if (typeof data == 'object') {
            return JSON.stringify(data);
        }

        return data;
    }

    /**
     * encode a value if the value is a object
     * @param  {mixed} data  The value to encode
     * @return {mixed}       The encoded value
     */
    function encodeObjectString(data) {
        if (typeof data == 'string') {
            try {
                return JSON.parse(data);
            } catch(e) {
            }
        }

        return data;
    }

    __construct(name);

    // define public interface
    return {
        set    : set,
        get    : get,
        flush  : flush,
        remove : remove,
        isset  : isset
    }
}

// static store methods

/**
 * remove all items from the whole store
 * @return {void}
 */
Store.flush = function() {
    util.flush(ROOT);
}

/**
 * remove all expired items from the whole store
 * @return {void}
 */
Store.flushExpired = function() {
    util.flushExpired(ROOT);
}

// remove the expired values
Store.flushExpired();

// export the module
if (typeof module !== 'undefined' && module.exports) {
    module.exports = Store;
}
<|endoftext|>


<commit_before>"use strict";

const CleanCSS = require("clean-css");
const glob = require("glob-concat");
const nodeSASS = "chmod +x node_modules/node-sass/bin/node-sass && node_modules/node-sass/bin/node-sass";
const parseAssets = require(__dirname + "/utility/parseAssets.js");
const path = require("path");
const postCSS = "chmod +x node_modules/postcss/lib/postcss.js && node node_modules/postcss/lib/postcss.js";
const sh = require("shelljs");
const minify = new CleanCSS({compatibility: "ie9"}).minify;

module.exports = function style(deps, root, config) {
	const cssFiles = [];
	const styles = parseAssets(deps);
	const sassFiles = [];
	const tmpAppCSS = root + "/tmp/app.scss";

	styles.forEach((filePath, i) => {
		styles[i] = filePath.replace("./", root + "/");
	});

	const styleFiles = glob.sync(styles);

	styleFiles.forEach((file) => {
		path.extname(file) === ".css" && cssFiles.push(minify(file));
		path.extname(file) === ".scss" && sassFiles.push(file);
	});

	sh.exec("touch " + "dist/app.css");
	sh.exec("node_modules/stylelint/bin/stylelint.js --config " + root + "/" + config + " " + root + "/app/**/*.scss");

	sh.cat(sassFiles).to(tmpAppCSS);
	sh.exec(nodeSASS + " -q --output-style compressed --include-path scss " + tmpAppCSS + " " + tmpAppCSS);
	sh.exec(postCSS + " --use autoprefixer -b 'last 5 versions' < " + tmpAppCSS);

	cssFiles.push(tmpAppCSS);
	sh.cat(cssFiles).to("dist/app.css");
};
<commit_msg>Fixed bug<commit_after>"use strict";

const CleanCSS = require("clean-css");
const cleanCSS = new CleanCSS({compatibility: "ie9"});
const glob = require("glob-concat");
const nodeSASS = "chmod +x node_modules/node-sass/bin/node-sass && node_modules/node-sass/bin/node-sass";
const parseAssets = require(__dirname + "/utility/parseAssets.js");
const path = require("path");
const postCSS = "chmod +x node_modules/postcss/lib/postcss.js && node node_modules/postcss/lib/postcss.js";
const sh = require("shelljs");

module.exports = function style(deps, root, config) {
	const cssFiles = [];
	const styles = parseAssets(deps);
	const sassFiles = [];
	const tmpAppCSS = root + "/tmp/app.scss";

	styles.forEach((filePath, i) => {
		styles[i] = filePath.replace("./", root + "/");
	});

	const styleFiles = glob.sync(styles);

	styleFiles.forEach((file) => {
		path.extname(file) === ".css" && cssFiles.push(cleanCSS.minify(file));
		path.extname(file) === ".scss" && sassFiles.push(file);
	});

	sh.exec("touch " + "dist/app.css");
	sh.exec("node_modules/stylelint/bin/stylelint.js --config " + root + "/" + config + " " + root + "/app/**/*.scss");

	sh.cat(sassFiles).to(tmpAppCSS);
	sh.exec(nodeSASS + " -q --output-style compressed --include-path scss " + tmpAppCSS + " " + tmpAppCSS);
	sh.exec(postCSS + " --use autoprefixer -b 'last 5 versions' < " + tmpAppCSS);

	cssFiles.push(tmpAppCSS);
	sh.cat(cssFiles).to("dist/app.css");
};
<|endoftext|>


<commit_before>import m from "mithril";

import state from "./state.js";

export default function Table() {
    return {
        view(vnode) {
            var data = vnode.attrs.data,
                keys = Object.keys(data);

            return m("div",
                m("h2", vnode.attrs.name),
                m("table",
                    m("thead",
                        m("tr",
                            m("th", "Name"),
                            Object.keys(data[keys[0]]).map((key) => m("th", key))
                        )
                    ),
                    keys.map((key) => {
                        return m("tr", {
                                onclick : (e) => (state[vnode.attrs.key] = { name : key, data : data[key] })
                            },
                            m("td", key),
                            Object.keys(data[key]).map((field) => m("td", data[key][field]))
                        );
                    })
                )
            );
        }
    };
};
<commit_msg>Don't redraw if choice didn't change<commit_after>import m from "mithril";

import state from "./state.js";

export default function Table() {
    return {
        view(vnode) {
            var data = vnode.attrs.data,
                keys = Object.keys(data);

            return m("div",
                m("h2", vnode.attrs.name),
                m("table",
                    m("thead",
                        m("tr",
                            m("th", "Name"),
                            Object.keys(data[keys[0]]).map((key) => m("th", key))
                        )
                    ),
                    keys.map((key) => {
                        return m("tr", {
                                onclick : (e) => {
                                    if(state[vnode.attrs.key] && key === state[vnode.attrs.key].name) {
                                        e.redraw = false;

                                        return;
                                    }

                                    state[vnode.attrs.key] = {
                                        name : key,
                                        data : data[key]
                                    };
                                }
                            },
                            m("td", key),
                            Object.keys(data[key]).map((field) => m("td", data[key][field]))
                        );
                    })
                )
            );
        }
    };
};
<|endoftext|>


<commit_before>// @flow

/*
 * Interfaces
 */

// A task that either returns, or errors
opaque type Task<
  +Arg,
  +Inbound,
  +InboundError = mixed,
  +Result = Inbound,
  +Error = InboundError
>: $ReadOnly<{
  // This declaration is the public API
  kind: 'regular',
  type: string,
  payload: Arg,

  map<ResultPrime>(
    successTransform: (Result) => ResultPrime
  ): Task<Arg, Inbound, InboundError, ResultPrime, Error>,

  bimap<ResultPrime, ErrorPrime>(
    successTransform: (Result) => ResultPrime,
    errorTransform: (Error) => ErrorPrime
  ): Task<Arg, Inbound, InboundError, ResultPrime, ErrorPrime>,

  chain<ResultPrime, ErrorPrime>(
    chainTransform: (Result) => Task<*, *, *, ResultPrime, ErrorPrime>
  ): Task<Arg, Inbound, InboundError, ResultPrime, Error | ErrorPrime>
}> = $ReadOnly<{
  // `kind` is private
  kind: 'regular',
  type: string,
  payload: Arg,

  /*
   * This is a little tricky. This `run` takes a lambda and calls either
   * the success or error handlers based on the result. We need this so
   * we can substitute applying effectful functions for mocking results
   * in test.
   */
  run<S, E>(
    (
      ((S) => mixed, (E) => mixed) => mixed,
      (S) => mixed,
      (E) => mixed
    ) => mixed,
    (Result) => mixed,
    (Error) => mixed
  ): mixed,

  map<ResultPrime>(
    successTransform: (Result) => ResultPrime
  ): Task<Arg, Inbound, InboundError, ResultPrime, Error>,

  bimap<ResultPrime, ErrorPrime>(
    successTransform: (Result) => ResultPrime,
    errorTransform: (Error) => ErrorPrime
  ): Task<Arg, Inbound, InboundError, ResultPrime, ErrorPrime>,

  chain<ResultPrime, ErrorPrime>(
    chainTransform: (Result) => Task<*, *, *, ResultPrime, ErrorPrime>
  ): Task<Arg, Inbound, InboundError, ResultPrime, Error | ErrorPrime>
}>;

export type TaskCreator<
  Arg,
  Inbound,
  InboundError = mixed,
  Result = Inbound,
  Error = InboundError
> = Arg => Task<Arg, Inbound, InboundError>;

export type AnyTask = Task<any, any, any>;

/**
 * A group of tasks, all of different types
 */
export type AnyTasks = $ReadOnlyArray<AnyTask>;

// For tasks whose type must be disambiguated from their use
// (because they were dynamically hoisted using `withTask`, for instance).
export type MixedTask = Task<mixed, mixed>;
export type MixedTasks = $ReadOnlyArray<MixedTask>;

type Callback<Error, Result> = (err?: Error, res: Result) => mixed;

/**
 * Returns a task-creator from a function that returns a promise.
 */
export function fromPromise<Arg, +Inbound>(
  fn: Arg => Promise<Inbound>,
  label: string
): Arg => Task<Arg, Inbound, mixed, Inbound, mixed> {
  return outbound =>
    taskCreator_(
      (success, error) => fn(outbound).then(success, error),
      outbound,
      label
    );
}

/**
 * `Task.fromCallback`
 *
 * Turn a node-style callback function:
 *     `(arg, cb: (err, res) => void) => void`)
 * into a task creator of the same type.
 *
 * Uses the second arg as a label for debugging.
 */
export function fromCallback<Arg, +Inbound, +ErrorT>(
  fn: (Arg, Callback<ErrorT, Inbound>) => mixed,
  label: string
): Arg => Task<Arg, Inbound, ErrorT> {
  return (outbound: Arg) =>
    taskCreator_(
      (success, error) =>
        fn(outbound, (err, result) => (err ? error(err) : success(result))),
      outbound,
      label
    );
}

// legacy API
// You probably want to use `Task.fromCallback` or
// `Task.fromPromise` instead.
export function taskCreator<Arg, +Inbound, +ErrorT>(
  fn: (Arg, (Inbound) => mixed, (ErrorT) => mixed) => mixed,
  label: string
): Arg => Task<Arg, Inbound, ErrorT> {
  return (outbound: Arg) =>
    taskCreator_(
      (success, error) => fn(outbound, success, error),
      outbound,
      label
    );
}

/*
 * Implementation
 */

// although this is global, we drain it
// between dispatches to the store.
// you can think of this queue as "thread local."
let globalTaskQueue: Task<mixed, mixed>[] = [];

// used for debugging
let enableStackCapture = true;
let lastWithTaskCall: Error | null = null;
const IMPROPER_TASK_USAGE = `Tasks should not be added outside of reducers.`;

/*
 * Use this to create a new task
 */
function taskCreator_<Arg, Inbound, InboundError>(
  effector: ((Inbound) => mixed, (InboundError) => mixed) => mixed,
  payload: Arg,
  label: string
): Task<Arg, Inbound, InboundError, Inbound, InboundError> {
  // Instrument the task with reporting!
  const effectorPrime = (success, error) => {
    reportEffects('start', newTask, payload);
    return effector(
      result => {
        reportEffects('success', newTask, result);
        return success(result);
      },
      reason => {
        reportEffects('error', newTask, reason);
        return error(reason);
      }
    );
  };

  effectorPrime.payload = payload;
  effectorPrime.type = label;

  const newTask = _task(
    payload,
    (runEffect, success, error) => runEffect(effectorPrime, success, error),
    label
  );

  return newTask;
}

// Internal task constructor.
// Note that payload is only kept around for testing/debugging purposes
function _task<Arg, Inbound, InboundError, Result, Error>(
  payload: Arg,
  next: (
    runEffect: <S, E>(
      ((S) => mixed, (E) => mixed) => mixed,
      (S) => mixed,
      (E) => mixed
    ) => mixed,
    (Result) => mixed,
    (Error) => mixed
  ) => mixed,
  label: string
): Task<Arg, Inbound, InboundError, Result, Error> {
  return {
    label,
    type: label,
    payload,

    /*
     * Given the effector (or a mock), kicks off the task.
     * You (the end user) probably don't need to call this
     * directly. The middleware should handle it.
     */
    run: next,

    /*
     * Task Methods
     */

    chain,
    map,
    bimap
  };

  function map<ResultPrime>(
    successTransform: Result => ResultPrime
  ): Task<Arg, Inbound, InboundError, ResultPrime, Error> {
    return _task(
      payload,
      (runEffect, success, error) =>
        next(
          runEffect,
          (result: Result) => success(successTransform(result)),
          error
        ),
      label
    );
  }

  function bimap<ResultPrime, ErrorPrime>(
    successTransform: Result => ResultPrime,
    errorTransform: Error => ErrorPrime
  ): Task<Arg, Inbound, InboundError, ResultPrime, ErrorPrime> {
    return _task(
      payload,
      (runEffect, success, error) =>
        next(
          runEffect,
          (result: Result) => success(successTransform(result)),
          (reason: Error) => error(errorTransform(reason))
        ),
      label
    );
  }

  function chain<ResultPrime, ErrorPrime>(
    chainTransform: Result => Task<*, *, *, ResultPrime, ErrorPrime>
  ): Task<Arg, Inbound, InboundError, ResultPrime, Error | ErrorPrime> {
    return _task(
      payload,
      (runEffect, success, error) =>
        next(
          runEffect,
          (result: Result) => {
            const chainTask = chainTransform(result);
            return chainTask.run(runEffect, success, error);
          },
          error
        ),
      `Chain(${label})`
    );
  }
}

const CACHED_PROMISE: Promise<void> = Promise.resolve();
const makeDispatchAsync = (dispatch: Function) => (action: Object) =>
  CACHED_PROMISE.then(() => dispatch(action));

/*
 * You need to install this middleware for tasks to have their handlers run.
 *
 * You probably do not want to use this middleware within your test environment.
 * Instead, use `drainTasksForTesting` to retrieve and make assertions about them.
 */
export const taskMiddleware = (store: {dispatch: Function}) => (
  next: Function
) => (action: Object) => {
  if (!module.hot && enableStackCapture && globalTaskQueue.length > 0) {
    const err = lastWithTaskCall;
    lastWithTaskCall = null;
    throw err;
  }

  next(action);
  const dispatch = makeDispatchAsync(store.dispatch);

  if (globalTaskQueue.length > 0) {
    const taskResolutions = globalTaskQueue.map(task =>
      runTaskActual(task, dispatch, dispatch)
    );

    globalTaskQueue = [];
    lastWithTaskCall = null;
    return Promise.all(taskResolutions);
  }

  return CACHED_PROMISE;
};

type MixedToMixed = mixed => mixed;

// Run the task with the proper effect
function runTaskActual(task, success, error) {
  if (typeof task.run !== 'function') {
    throw new Error('Attempted to run something that is not a task.');
  }
  return task.run(
    (f, s, e) => f(s, e),
    // unsafe coerce this because it doesn't matter
    ((success: any): MixedToMixed),
    ((error: any): MixedToMixed)
  );
}

/**
 * Use this function in your reducer to add tasks to an action handler.
 * The task will be lifted up to the top of your app.
 */
export function withTasks<State>(state: State, tasks: AnyTasks): State {
  if (!module.hot && enableStackCapture && !lastWithTaskCall) {
    lastWithTaskCall = trace(IMPROPER_TASK_USAGE);
  }
  if (tasks instanceof Array) {
    globalTaskQueue = globalTaskQueue.concat(tasks);
  } else {
    globalTaskQueue.push(tasks);
  }
  return state;
}

/**
 * A helpful alias for providing just one task.
 * `withTask(state, task1)` is the same as `withTasks(state, [task1])`.
 */
export const withTask: <State>(
  state: State,
  task: AnyTask
) => State = (withTasks: any);

/*
 * This function should only be used in test environments to make assertions about
 * tasks as part of the test. Application code should not be mucking around with
 * the list of tasks. If you want to display information about tasks in your component,
 * add that information to your state tree when you create the task.
 */
export function drainTasksForTesting(): $ReadOnlyArray<MixedTask> {
  const drained = globalTaskQueue;
  globalTaskQueue = [];
  lastWithTaskCall = null;
  return drained;
}

/*
 * Run a task, using `simulator` for bi-application.
 * Simulator recieves:
 * 1. a function that kicks off a side effect, with success and error handers.
 * 2. a success handler to call with a mocked response.
 * 3. an error handler to call with a mocked out response.
 */
export function simulateTask<Inbound, InboundError, Result, Error, S, E>(
  someTask: Task<*, Inbound, InboundError, Result, Error>,
  simulator: (
    ((S) => mixed, (E) => mixed) => mixed,
    (S) => mixed,
    (E) => mixed
  ) => mixed
): Result | Error {
  let returned = null;
  const setReturned = val => {
    returned = val;
  };
  someTask.run(simulator, setReturned, setReturned);
  return returned;
}

export function succeedTaskWithValues<Result>(
  someTask: Task<*, *, *, Result>,
  values: $ReadOnlyArray<any>
): Result | null {
  let index: number = 0;
  let returned = null;
  const setReturned = val => {
    returned = val;
  };
  someTask.run(
    (_, s) => {
      if (index >= values.length) {
        throw new Error('Not enough values were provided!');
      }
      const returned = s(values[index]);
      index += 1;
      return returned;
    },
    setReturned,
    setReturned
  );
  return returned;
}

/*
 * Get the value of a task, always providing the given value as the inbound result.
 * If your task uses `.chain` or `Task.all`, you probbaly
 */
export function succeedTaskInTest<Inbound, Result>(
  someTask: Task<*, Inbound, *, Result, *>,
  value: Inbound
): Result {
  let returned;
  const setReturned = (val: Result) => {
    returned = val;
  };
  someTask.run((_, s, e) => s(value), setReturned, setReturned);
  if (typeof returned === 'undefined') {
    throw new Error('Success handler was never called!');
  }
  return returned;
}

export function errorTaskInTest<InboundError, ErrorT>(
  someTask: Task<*, *, InboundError, *, ErrorT>,
  value: InboundError
): ErrorT {
  let returned;
  const setReturned = (val: ErrorT) => {
    returned = val;
  };
  someTask.run((_1, _2, e) => e(value), setReturned, setReturned);
  if (typeof returned === 'undefined') {
    throw new Error('Success handler was never called!');
  }
  return returned;
}

/*
 * In order to make it easy to track down incorrect uses for `withTask`, we capture exception
 * objects for every call to withTask. This has some performance overhead, so you'll
 * probably want to disable it in production.
 */
export function disableStackCapturing(): void {
  enableStackCapture = false;
}

/*
 * Record the inputs/outputs of all tasks, for debugging or inspecting.
 * This feature should not be used to implement runtime behavior.
 */
let reportEffects: (event: string, task: any, payload: mixed) => void = (
  event: string,
  task: any,
  payload: mixed
) => {};

export function reportTasksForTesting(
  fn: (event: string, task: MixedTask, payload: mixed) => void
): void {
  reportEffects = fn;
}

// type level utils functions needed for Task.all
type ExtractArg = <O>(Task<O, *>) => O;
type ExtractResult = <R>(Task<*, *, *, R>) => R;
type ExtractError = <E>(Task<*, *, *, *, E>) => E;

/*
 * Task.all combinator. Acts just like `Promise.all`.
 */
export function all<AllTasks: $ReadOnlyArray<Task<mixed, mixed>>>(
  tasks: AllTasks
): Task<
  $TupleMap<AllTasks, ExtractArg>,
  *,
  *,
  $TupleMap<AllTasks, ExtractResult>,
  mixed
> {
  return _task(
    tasks.map(task => task.payload),
    (
      runEffect,
      success: ($TupleMap<AllTasks, ExtractResult>) => mixed,
      error
    ) => {
      if (tasks.length === 0) {
        return success([]);
      }
      const accumulated = Array(tasks.length);
      let complete = 0;
      let errorValue = null;

      function allSuccess(index) {
        return value => {
          if (errorValue) {
            return;
          }
          accumulated[index] = value;
          complete += 1;
          if (complete === tasks.length) {
            return success(accumulated);
          }
        };
      }

      function anyError(err) {
        if (err) {
          return;
        }
        errorValue = err;
        return error(errorValue);
      }

      return Promise.all(
        tasks.map((task, index) =>
          task.run(runEffect, allSuccess(index), anyError)
        )
      );
    },

    'Task.all(' + tasks.map(({type}) => type).join(', ') + ')'
  );
}

// Nice aliases:
// `import Task from 'react-palm/tasks';`
export default {
  all,
  fromCallback,
  fromPromise
};

/*
 * Helpers
 */
function trace(message: string): Error {
  try {
    throw new Error(message);
  } catch (e) {
    return e;
  }
}
<commit_msg>Fix: type errors against hot module reloading<commit_after>// @flow

/*
 * Interfaces
 */

// A task that either returns, or errors
opaque type Task<
  +Arg,
  +Inbound,
  +InboundError = mixed,
  +Result = Inbound,
  +Error = InboundError
>: $ReadOnly<{
  // This declaration is the public API
  kind: 'regular',
  type: string,
  payload: Arg,

  map<ResultPrime>(
    successTransform: (Result) => ResultPrime
  ): Task<Arg, Inbound, InboundError, ResultPrime, Error>,

  bimap<ResultPrime, ErrorPrime>(
    successTransform: (Result) => ResultPrime,
    errorTransform: (Error) => ErrorPrime
  ): Task<Arg, Inbound, InboundError, ResultPrime, ErrorPrime>,

  chain<ResultPrime, ErrorPrime>(
    chainTransform: (Result) => Task<*, *, *, ResultPrime, ErrorPrime>
  ): Task<Arg, Inbound, InboundError, ResultPrime, Error | ErrorPrime>
}> = $ReadOnly<{
  // `kind` is private
  kind: 'regular',
  type: string,
  payload: Arg,

  /*
   * This is a little tricky. This `run` takes a lambda and calls either
   * the success or error handlers based on the result. We need this so
   * we can substitute applying effectful functions for mocking results
   * in test.
   */
  run<S, E>(
    (
      ((S) => mixed, (E) => mixed) => mixed,
      (S) => mixed,
      (E) => mixed
    ) => mixed,
    (Result) => mixed,
    (Error) => mixed
  ): mixed,

  map<ResultPrime>(
    successTransform: (Result) => ResultPrime
  ): Task<Arg, Inbound, InboundError, ResultPrime, Error>,

  bimap<ResultPrime, ErrorPrime>(
    successTransform: (Result) => ResultPrime,
    errorTransform: (Error) => ErrorPrime
  ): Task<Arg, Inbound, InboundError, ResultPrime, ErrorPrime>,

  chain<ResultPrime, ErrorPrime>(
    chainTransform: (Result) => Task<*, *, *, ResultPrime, ErrorPrime>
  ): Task<Arg, Inbound, InboundError, ResultPrime, Error | ErrorPrime>
}>;

export type TaskCreator<
  Arg,
  Inbound,
  InboundError = mixed,
  Result = Inbound,
  Error = InboundError
> = Arg => Task<Arg, Inbound, InboundError>;

export type AnyTask = Task<any, any, any>;

/**
 * A group of tasks, all of different types
 */
export type AnyTasks = $ReadOnlyArray<AnyTask>;

// For tasks whose type must be disambiguated from their use
// (because they were dynamically hoisted using `withTask`, for instance).
export type MixedTask = Task<mixed, mixed>;
export type MixedTasks = $ReadOnlyArray<MixedTask>;

type Callback<Error, Result> = (err?: Error, res: Result) => mixed;

/**
 * Returns a task-creator from a function that returns a promise.
 */
export function fromPromise<Arg, +Inbound>(
  fn: Arg => Promise<Inbound>,
  label: string
): Arg => Task<Arg, Inbound, mixed, Inbound, mixed> {
  return outbound =>
    taskCreator_(
      (success, error) => fn(outbound).then(success, error),
      outbound,
      label
    );
}

/**
 * `Task.fromCallback`
 *
 * Turn a node-style callback function:
 *     `(arg, cb: (err, res) => void) => void`)
 * into a task creator of the same type.
 *
 * Uses the second arg as a label for debugging.
 */
export function fromCallback<Arg, +Inbound, +ErrorT>(
  fn: (Arg, Callback<ErrorT, Inbound>) => mixed,
  label: string
): Arg => Task<Arg, Inbound, ErrorT> {
  return (outbound: Arg) =>
    taskCreator_(
      (success, error) =>
        fn(outbound, (err, result) => (err ? error(err) : success(result))),
      outbound,
      label
    );
}

// legacy API
// You probably want to use `Task.fromCallback` or
// `Task.fromPromise` instead.
export function taskCreator<Arg, +Inbound, +ErrorT>(
  fn: (Arg, (Inbound) => mixed, (ErrorT) => mixed) => mixed,
  label: string
): Arg => Task<Arg, Inbound, ErrorT> {
  return (outbound: Arg) =>
    taskCreator_(
      (success, error) => fn(outbound, success, error),
      outbound,
      label
    );
}

/*
 * Implementation
 */

// although this is global, we drain it
// between dispatches to the store.
// you can think of this queue as "thread local."
let globalTaskQueue: Task<mixed, mixed>[] = [];

// used for debugging
let enableStackCapture = true;
let lastWithTaskCall: Error | null = null;
const IMPROPER_TASK_USAGE = `Tasks should not be added outside of reducers.`;

/*
 * Use this to create a new task
 */
function taskCreator_<Arg, Inbound, InboundError>(
  effector: ((Inbound) => mixed, (InboundError) => mixed) => mixed,
  payload: Arg,
  label: string
): Task<Arg, Inbound, InboundError, Inbound, InboundError> {
  // Instrument the task with reporting!
  const effectorPrime = (success, error) => {
    reportEffects('start', newTask, payload);
    return effector(
      result => {
        reportEffects('success', newTask, result);
        return success(result);
      },
      reason => {
        reportEffects('error', newTask, reason);
        return error(reason);
      }
    );
  };

  effectorPrime.payload = payload;
  effectorPrime.type = label;

  const newTask = _task(
    payload,
    (runEffect, success, error) => runEffect(effectorPrime, success, error),
    label
  );

  return newTask;
}

// Internal task constructor.
// Note that payload is only kept around for testing/debugging purposes
function _task<Arg, Inbound, InboundError, Result, Error>(
  payload: Arg,
  next: (
    runEffect: <S, E>(
      ((S) => mixed, (E) => mixed) => mixed,
      (S) => mixed,
      (E) => mixed
    ) => mixed,
    (Result) => mixed,
    (Error) => mixed
  ) => mixed,
  label: string
): Task<Arg, Inbound, InboundError, Result, Error> {
  return {
    label,
    type: label,
    payload,

    /*
     * Given the effector (or a mock), kicks off the task.
     * You (the end user) probably don't need to call this
     * directly. The middleware should handle it.
     */
    run: next,

    /*
     * Task Methods
     */

    chain,
    map,
    bimap
  };

  function map<ResultPrime>(
    successTransform: Result => ResultPrime
  ): Task<Arg, Inbound, InboundError, ResultPrime, Error> {
    return _task(
      payload,
      (runEffect, success, error) =>
        next(
          runEffect,
          (result: Result) => success(successTransform(result)),
          error
        ),
      label
    );
  }

  function bimap<ResultPrime, ErrorPrime>(
    successTransform: Result => ResultPrime,
    errorTransform: Error => ErrorPrime
  ): Task<Arg, Inbound, InboundError, ResultPrime, ErrorPrime> {
    return _task(
      payload,
      (runEffect, success, error) =>
        next(
          runEffect,
          (result: Result) => success(successTransform(result)),
          (reason: Error) => error(errorTransform(reason))
        ),
      label
    );
  }

  function chain<ResultPrime, ErrorPrime>(
    chainTransform: Result => Task<*, *, *, ResultPrime, ErrorPrime>
  ): Task<Arg, Inbound, InboundError, ResultPrime, Error | ErrorPrime> {
    return _task(
      payload,
      (runEffect, success, error) =>
        next(
          runEffect,
          (result: Result) => {
            const chainTask = chainTransform(result);
            return chainTask.run(runEffect, success, error);
          },
          error
        ),
      `Chain(${label})`
    );
  }
}

const CACHED_PROMISE: Promise<void> = Promise.resolve();
const makeDispatchAsync = (dispatch: Function) => (action: Object) =>
  CACHED_PROMISE.then(() => dispatch(action));

/*
 * You need to install this middleware for tasks to have their handlers run.
 *
 * You probably do not want to use this middleware within your test environment.
 * Instead, use `drainTasksForTesting` to retrieve and make assertions about them.
 */
export const taskMiddleware = (store: {dispatch: Function}) => (
  next: Function
) => (action: Object) => {
  if (!(module: any).hot && enableStackCapture && globalTaskQueue.length > 0) {
    const err = lastWithTaskCall;
    lastWithTaskCall = null;
    throw err;
  }

  next(action);
  const dispatch = makeDispatchAsync(store.dispatch);

  if (globalTaskQueue.length > 0) {
    const taskResolutions = globalTaskQueue.map(task =>
      runTaskActual(task, dispatch, dispatch)
    );

    globalTaskQueue = [];
    lastWithTaskCall = null;
    return Promise.all(taskResolutions);
  }

  return CACHED_PROMISE;
};

type MixedToMixed = mixed => mixed;

// Run the task with the proper effect
function runTaskActual(task, success, error) {
  if (typeof task.run !== 'function') {
    throw new Error('Attempted to run something that is not a task.');
  }
  return task.run(
    (f, s, e) => f(s, e),
    // unsafe coerce this because it doesn't matter
    ((success: any): MixedToMixed),
    ((error: any): MixedToMixed)
  );
}

/**
 * Use this function in your reducer to add tasks to an action handler.
 * The task will be lifted up to the top of your app.
 */
export function withTasks<State>(state: State, tasks: AnyTasks): State {
  if (!(module: any).hot && enableStackCapture && !lastWithTaskCall) {
    lastWithTaskCall = trace(IMPROPER_TASK_USAGE);
  }
  if (tasks instanceof Array) {
    globalTaskQueue = globalTaskQueue.concat(tasks);
  } else {
    globalTaskQueue.push(tasks);
  }
  return state;
}

/**
 * A helpful alias for providing just one task.
 * `withTask(state, task1)` is the same as `withTasks(state, [task1])`.
 */
export const withTask: <State>(
  state: State,
  task: AnyTask
) => State = (withTasks: any);

/*
 * This function should only be used in test environments to make assertions about
 * tasks as part of the test. Application code should not be mucking around with
 * the list of tasks. If you want to display information about tasks in your component,
 * add that information to your state tree when you create the task.
 */
export function drainTasksForTesting(): $ReadOnlyArray<MixedTask> {
  const drained = globalTaskQueue;
  globalTaskQueue = [];
  lastWithTaskCall = null;
  return drained;
}

/*
 * Run a task, using `simulator` for bi-application.
 * Simulator recieves:
 * 1. a function that kicks off a side effect, with success and error handers.
 * 2. a success handler to call with a mocked response.
 * 3. an error handler to call with a mocked out response.
 */
export function simulateTask<Inbound, InboundError, Result, Error, S, E>(
  someTask: Task<*, Inbound, InboundError, Result, Error>,
  simulator: (
    ((S) => mixed, (E) => mixed) => mixed,
    (S) => mixed,
    (E) => mixed
  ) => mixed
): Result | Error {
  let returned = null;
  const setReturned = val => {
    returned = val;
  };
  someTask.run(simulator, setReturned, setReturned);
  return returned;
}

export function succeedTaskWithValues<Result>(
  someTask: Task<*, *, *, Result>,
  values: $ReadOnlyArray<any>
): Result | null {
  let index: number = 0;
  let returned = null;
  const setReturned = val => {
    returned = val;
  };
  someTask.run(
    (_, s) => {
      if (index >= values.length) {
        throw new Error('Not enough values were provided!');
      }
      const returned = s(values[index]);
      index += 1;
      return returned;
    },
    setReturned,
    setReturned
  );
  return returned;
}

/*
 * Get the value of a task, always providing the given value as the inbound result.
 * If your task uses `.chain` or `Task.all`, you probbaly
 */
export function succeedTaskInTest<Inbound, Result>(
  someTask: Task<*, Inbound, *, Result, *>,
  value: Inbound
): Result {
  let returned;
  const setReturned = (val: Result) => {
    returned = val;
  };
  someTask.run((_, s, e) => s(value), setReturned, setReturned);
  if (typeof returned === 'undefined') {
    throw new Error('Success handler was never called!');
  }
  return returned;
}

export function errorTaskInTest<InboundError, ErrorT>(
  someTask: Task<*, *, InboundError, *, ErrorT>,
  value: InboundError
): ErrorT {
  let returned;
  const setReturned = (val: ErrorT) => {
    returned = val;
  };
  someTask.run((_1, _2, e) => e(value), setReturned, setReturned);
  if (typeof returned === 'undefined') {
    throw new Error('Success handler was never called!');
  }
  return returned;
}

/*
 * In order to make it easy to track down incorrect uses for `withTask`, we capture exception
 * objects for every call to withTask. This has some performance overhead, so you'll
 * probably want to disable it in production.
 */
export function disableStackCapturing(): void {
  enableStackCapture = false;
}

/*
 * Record the inputs/outputs of all tasks, for debugging or inspecting.
 * This feature should not be used to implement runtime behavior.
 */
let reportEffects: (event: string, task: any, payload: mixed) => void = (
  event: string,
  task: any,
  payload: mixed
) => {};

export function reportTasksForTesting(
  fn: (event: string, task: MixedTask, payload: mixed) => void
): void {
  reportEffects = fn;
}

// type level utils functions needed for Task.all
type ExtractArg = <O>(Task<O, *>) => O;
type ExtractResult = <R>(Task<*, *, *, R>) => R;
type ExtractError = <E>(Task<*, *, *, *, E>) => E;

/*
 * Task.all combinator. Acts just like `Promise.all`.
 */
export function all<AllTasks: $ReadOnlyArray<Task<mixed, mixed>>>(
  tasks: AllTasks
): Task<
  $TupleMap<AllTasks, ExtractArg>,
  *,
  *,
  $TupleMap<AllTasks, ExtractResult>,
  mixed
> {
  return _task(
    tasks.map(task => task.payload),
    (
      runEffect,
      success: ($TupleMap<AllTasks, ExtractResult>) => mixed,
      error
    ) => {
      if (tasks.length === 0) {
        return success([]);
      }
      const accumulated = Array(tasks.length);
      let complete = 0;
      let errorValue = null;

      function allSuccess(index) {
        return value => {
          if (errorValue) {
            return;
          }
          accumulated[index] = value;
          complete += 1;
          if (complete === tasks.length) {
            return success(accumulated);
          }
        };
      }

      function anyError(err) {
        if (err) {
          return;
        }
        errorValue = err;
        return error(errorValue);
      }

      return Promise.all(
        tasks.map((task, index) =>
          task.run(runEffect, allSuccess(index), anyError)
        )
      );
    },

    'Task.all(' + tasks.map(({type}) => type).join(', ') + ')'
  );
}

// Nice aliases:
// `import Task from 'react-palm/tasks';`
export default {
  all,
  fromCallback,
  fromPromise
};

/*
 * Helpers
 */
function trace(message: string): Error {
  try {
    throw new Error(message);
  } catch (e) {
    return e;
  }
}
<|endoftext|>


<commit_before>(function() {

    /**
     * Normalizes line endings by removing all \r (carriage return) characters.
     * In IE, newlines in textareas are stored as \r\n instead of just \n, which screws up length calculations.
     * All other browsers automatically remove \r characters when you set the value of the textarea.
     * @returns {string}
     */
    String.prototype.norm = function() {
        return this.replace(/\r/g, '');
    };

    pavlov.specify.extendAssertions({
        isOneOf: function(actual, expected, message) {
            ok(expected.indexOf(actual) !== -1, message);
        }
    });

    pavlov.specify('jQuery Caret Plugin', function() {

        var _e = function(tagName) {
            return document.createElement(tagName);
        };

        var _s = function(obj) {
            return JSON.stringify(obj, null, '    ');
        };

        var _support = {
            normalizesNewlines: (function () {
                var textarea = _e('textarea');
                var text = 'abc\r\ndef';
                textarea.value = text;
                return textarea.value.length === text.length - 1;
            }())
        };

        var $fixture = $('#qunit-fixture');

        var _single = 'abcdefghijklmnop',
            _multi = 'abcdefg\r\nhijklmnop',
            _short = 'abc',
            _long = 'abcdefghijklmnopqrstuvwxyz',
            _empty = '';

        describe('All Plugins', function() {
            var $input, $textarea;

            // befores and afters:

            before(function() {
                $input = $('<input/>').appendTo($fixture);
                $textarea = $('<textarea/>').appendTo($fixture);
            });

            // plugins:

            describe('Caret', function() {
                describe('Get', function() {
                    // Note: Every browser handles automatic caret placement a little differently,
                    // which means our tests need to accommodate both placement strategies
                    // (at the beginning and end of the input/textarea).

                    it("Returns undefined when the jQuery object does not contain an input or textarea element", function() {
                        assert($().caret()).isUndefined();
                        assert($({}).caret()).isUndefined();
                        assert($('<div/>').caret()).isUndefined();
                        assert($([ _e('span'), _e('div'), _e('button') ]).caret()).isUndefined();
                    });

                    describe('<input>', function() {
                        it("Returns the caret position of the first input or textarea element in the jQuery object", function() {
                            var span = _e('span'),
                                div = _e('div'),
                                button = _e('button');

                            // IE throws a hissy fit if the inputs aren't inserted into the DOM before calling
                            // .focus(), .createTextRange(), etc.
                            var input3 = $('<input/>').appendTo($fixture).val('abc').caret(3)[0],
                                input4 = $('<input/>').appendTo($fixture).val('defg').caret(4)[0],
                                input5 = $('<input/>').appendTo($fixture).val('12345').caret(5)[0];

                            assert($([ span, div, button, input3 ]).caret()).equals(3);
                            assert($([ input3, input4, input5 ]).caret()).equals(3);
                            assert($([ input5, input4, input3 ]).caret()).equals(5);
                        });
                    });

                    describe('<input>', function() {
                        it("Returns zero (0) when no value has been set", function() {
                            assert($input.caret()).equals(0);
                        });
                    });

                    describe('<textarea>', function() {
                        it("Returns zero (0) when no value has been set", function() {
                            assert($textarea.caret()).equals(0);
                        });
                    });

                    describe('<input>', function() {
                        it("Returns zero (0) or value.length when a value has been set", function() {
                            // In IE and FF, the caret remains at index 0 the first time a value is set;
                            // the second time a value is set, the caret moves to the end of the input/textarea.
                            assert($input.val(_single).caret()).isOneOf([ 0, _single.length ]);
                            assert($input.val(_single).caret()).isOneOf([ 0, _single.length ]);
                        });
                    });

                    describe('<textarea>', function() {
                        it("Returns zero (0) or value.length when a value has been set", function() {
                            // In IE and FF, the caret remains at index 0 the first time a value is set;
                            // the second time a value is set, the caret moves to the end of the input/textarea.
                            assert($textarea.val(_multi).caret()).isOneOf([ 0, _multi.norm().length ]);
                            assert($textarea.val(_multi).caret()).isOneOf([ 0, _multi.norm().length ]);
                        });
                    });

                    describe('<input>', function() {
                        it("Returns zero (0) or value.length when the input's value changes", function() {
                            assert($input.val(_short).caret()).isOneOf([ 0, _short.length ]);
                            assert($input.val(_long).caret()).isOneOf([ 0, _long.length ]);
                            assert($input.val(_short).caret()).isOneOf([ 0, _short.length ]);
                            assert($input.val(_empty).caret()).isOneOf([ 0, _empty.length ]);
                        });
                    });

                    describe('<textarea>', function() {
                        it("Returns zero (0) or value.length when the textarea's value changes", function() {
                            assert($textarea.val(_short).caret()).isOneOf([ 0, _short.length ]);
                            assert($textarea.val(_long).caret()).isOneOf([ 0, _long.length ]);
                            assert($textarea.val(_short).caret()).isOneOf([ 0, _short.length ]);
                            assert($textarea.val(_empty).caret()).isOneOf([ 0, _empty.length ]);
                        });
                    });
                });

                describe('Set', function() {
                    describe('<input>', function() {
                        var text = _single,
                            len = text.length,
                            mid = Math.floor(len / 2);

                        it("Gets the same position that was set", function() {
                            assert($input.val(text).caret(0).caret()).equals(0);
                            assert($input.val(text).caret(1).caret()).equals(1);
                            assert($input.val(text).caret(mid - 2).caret()).equals(mid - 2);
                            assert($input.val(text).caret(mid - 1).caret()).equals(mid - 1);
                            assert($input.val(text).caret(mid).caret()).equals(mid);
                            assert($input.val(text).caret(mid + 1).caret()).equals(mid + 1);
                            assert($input.val(text).caret(mid + 2).caret()).equals(mid + 2);
                            assert($input.val(text).caret(len - 1).caret()).equals(len - 1);
                            assert($input.val(text).caret(len).caret()).equals(len);
                        });

                        it("Enforces length boundary", function() {
                            assert($input.val(text).caret(len + 1).caret()).equals(len);
                            assert($input.val(text).caret(len + 2).caret()).equals(len);
                        });

                        it("Allows negative position", function() {
                            assert($input.val(text).caret(-1).caret()).equals(len - 1);
                            assert($input.val(text).caret(-2).caret()).equals(len - 2);
                        });

                        it("Converts floating point values to integers", function() {
                            assert($input.val(text).caret(1.5).caret()).equals(1.0);
                            assert($input.val(text).caret(2.5).caret()).equals(2.0);
                        });
                    });

                    describe('<textarea>', function() {
                        var text = _multi,
                            len = text.norm().length,
                            mid = text.indexOf('\n');

                        it("Gets the same position that was set", function() {
                            assert($textarea.val(text).caret(0).caret()).equals(0);
                            assert($textarea.val(text).caret(1).caret()).equals(1);
                            assert($textarea.val(text).caret(mid - 2).caret()).equals(mid - 2);
                            assert($textarea.val(text).caret(mid - 1).caret()).equals(mid - 1);
                            assert($textarea.val(text).caret(mid).caret()).equals(mid);
                            assert($textarea.val(text).caret(mid + 1).caret()).equals(mid + 1);
                            assert($textarea.val(text).caret(mid + 2).caret()).equals(mid + 2);
                            assert($textarea.val(text).caret(len - 1).caret()).equals(len - 1);
                            assert($textarea.val(text).caret(len).caret()).equals(len);
                        });

                        it("Enforces length boundary", function() {
                            assert($textarea.val(text).caret(len + 1).caret()).equals(len);
                            assert($textarea.val(text).caret(len + 2).caret()).equals(len);
                        });

                        it("Allows negative position", function() {
                            assert($textarea.val(text).caret(-1).caret()).equals(len - 1);
                            assert($textarea.val(text).caret(-2).caret()).equals(len - 2);
                        });

                        it("Converts floating point values to integers", function() {
                            assert($textarea.val(text).caret(1.5).caret()).equals(1);
                            assert($textarea.val(text).caret(2.5).caret()).equals(2);
                        });
                    });
                });

                describe('Insert', function() {
                    describe('<input>', function() {
                        it("Prepends text", function() {
                            assert($input.val('abc').caret(0).caret('123').val()).equals('123abc');
                        });

                        it("Appends text", function() {
                            assert($input.val('abc').caret(3).caret('123').val()).equals('abc123');
                        });

                        it("Inserts text", function() {
                            assert($input.val('abcdef').caret(3).caret('123').val()).equals('abc123def');
                        });

                        it("Respects maxlength attribute", function() {
                            $input.attr('maxlength', 5);
                            assert($input.val('abc').caret(0).caret('123').val()).equals('12abc');
                            assert($input.val('abc').caret(3).caret('123').val()).equals('abc12');
                            assert($input.val('abcde').caret(0).caret('123').val()).equals('abcde');
                            assert($input.val('abcde').caret(3).caret('123').val()).equals('abcde');
                            assert($input.val('abcde').caret(5).caret('123').val()).equals('abcde');
                        });
                    });

                    describe('<textarea>', function() {
                        it("Prepends text", function() {
                            assert($textarea.val('abc\ndef').caret(0).caret('123').val()).equals('123abc\ndef');
                        });

                        it("Appends text", function() {
                            assert($textarea.val('abc\ndef').caret(7).caret('123').val()).equals('abc\ndef123');
                        });

                        it("Inserts text", function() {
                            assert($textarea.val('abc\ndef').caret(3).caret('123').val()).equals('abc123\ndef');
                            assert($textarea.val('abc\ndef').caret(4).caret('123').val()).equals('abc\n123def');
                        });

                        it("Respects maxlength attribute", function() {
                            $textarea.attr('maxlength', 9);
                            assert($textarea.val('abc\ndef').caret(0).caret('123').val()).equals('12abc\ndef');
                            assert($textarea.val('abc\ndef').caret(3).caret('123').val()).equals('abc12\ndef');
                            assert($textarea.val('abc\ndef').caret(4).caret('123').val()).equals('abc\n12def');
                            assert($textarea.val('abc\ndef\ng').caret(0).caret('123').val()).equals('abc\ndef\ng');
                            assert($textarea.val('abc\ndef\ng').caret(3).caret('123').val()).equals('abc\ndef\ng');
                            assert($textarea.val('abc\ndef\ng').caret(5).caret('123').val()).equals('abc\ndef\ng');
                        });
                    });
                });
            });

            describe('Range', function() {
                describe('Get', function () {
                    describe('<textarea>', function() {
                        it("Returns the correct range", function() {
                            assert(_s($textarea.val('abc\ndef').range(0, 7).range())).equals(_s({ start: 0, end: 7, length: 7, text: 'abc\ndef' }));
                            assert(_s($textarea.val('abc\ndef').range(3, 4).range())).equals(_s({ start: 3, end: 4, length: 1, text: '\n' }));
                        });
                    });
                });

                describe('Set', function () {
                    describe('<textarea>', function() {
                        it("Accepts a single argument", function() {
                            assert(_s($textarea.val('abc\ndef').range(0).range())).equals(_s($textarea.val('abc\ndef').range(0, 7).range()));
                            assert(_s($textarea.val('abc\ndef').range(3).range())).equals(_s($textarea.val('abc\ndef').range(3, 7).range()));
                        });
                    });
                });
            });
        });

    });

}());<commit_msg>Minor - Added TODO comment to tests.js<commit_after>(function() {

    /**
     * Normalizes line endings by removing all \r (carriage return) characters.
     * In IE, newlines in textareas are stored as \r\n instead of just \n, which screws up length calculations.
     * All other browsers automatically remove \r characters when you set the value of the textarea.
     * @returns {string}
     */
    String.prototype.norm = function() {
        return this.replace(/\r/g, '');
    };

    pavlov.specify.extendAssertions({
        isOneOf: function(actual, expected, message) {
            ok(expected.indexOf(actual) !== -1, message);
        }
    });

    pavlov.specify('jQuery Caret Plugin', function() {

        var _e = function(tagName) {
            return document.createElement(tagName);
        };

        var _s = function(obj) {
            return JSON.stringify(obj, null, '    ');
        };

        var _support = {
            normalizesNewlines: (function () {
                var textarea = _e('textarea');
                var text = 'abc\r\ndef';
                textarea.value = text;
                return textarea.value.length === text.length - 1;
            }())
        };

        var $fixture = $('#qunit-fixture');

        var _single = 'abcdefghijklmnop',
            _multi = 'abcdefg\r\nhijklmnop',
            _short = 'abc',
            _long = 'abcdefghijklmnopqrstuvwxyz',
            _empty = '';

        describe('All Plugins', function() {
            var $input, $textarea;

            // befores and afters:

            before(function() {
                $input = $('<input/>').appendTo($fixture);
                $textarea = $('<textarea/>').appendTo($fixture);
            });

            // plugins:

            describe('Caret', function() {
                describe('Get', function() {
                    // Note: Every browser handles automatic caret placement a little differently,
                    // which means our tests need to accommodate both placement strategies
                    // (at the beginning and end of the input/textarea).

                    it("Returns undefined when the jQuery object does not contain an input or textarea element", function() {
                        assert($().caret()).isUndefined();
                        assert($({}).caret()).isUndefined();
                        assert($('<div/>').caret()).isUndefined();
                        assert($([ _e('span'), _e('div'), _e('button') ]).caret()).isUndefined();
                    });

                    describe('<input>', function() {
                        it("Returns the caret position of the first input or textarea element in the jQuery object", function() {
                            var span = _e('span'),
                                div = _e('div'),
                                button = _e('button');

                            // IE throws a hissy fit if the inputs aren't inserted into the DOM before calling
                            // .focus(), .createTextRange(), etc.
                            var input3 = $('<input/>').appendTo($fixture).val('abc').caret(3)[0],
                                input4 = $('<input/>').appendTo($fixture).val('defg').caret(4)[0],
                                input5 = $('<input/>').appendTo($fixture).val('12345').caret(5)[0];

                            assert($([ span, div, button, input3 ]).caret()).equals(3);
                            assert($([ input3, input4, input5 ]).caret()).equals(3);
                            assert($([ input5, input4, input3 ]).caret()).equals(5);
                        });
                    });

                    describe('<input>', function() {
                        it("Returns zero (0) when no value has been set", function() {
                            assert($input.caret()).equals(0);
                        });
                    });

                    describe('<textarea>', function() {
                        it("Returns zero (0) when no value has been set", function() {
                            assert($textarea.caret()).equals(0);
                        });
                    });

                    describe('<input>', function() {
                        it("Returns zero (0) or value.length when a value has been set", function() {
                            // In IE and FF, the caret remains at index 0 the first time a value is set;
                            // the second time a value is set, the caret moves to the end of the input/textarea.
                            assert($input.val(_single).caret()).isOneOf([ 0, _single.length ]);
                            assert($input.val(_single).caret()).isOneOf([ 0, _single.length ]);
                        });
                    });

                    describe('<textarea>', function() {
                        it("Returns zero (0) or value.length when a value has been set", function() {
                            // In IE and FF, the caret remains at index 0 the first time a value is set;
                            // the second time a value is set, the caret moves to the end of the input/textarea.
                            assert($textarea.val(_multi).caret()).isOneOf([ 0, _multi.norm().length ]);
                            assert($textarea.val(_multi).caret()).isOneOf([ 0, _multi.norm().length ]);
                        });
                    });

                    describe('<input>', function() {
                        it("Returns zero (0) or value.length when the input's value changes", function() {
                            assert($input.val(_short).caret()).isOneOf([ 0, _short.length ]);
                            assert($input.val(_long).caret()).isOneOf([ 0, _long.length ]);
                            assert($input.val(_short).caret()).isOneOf([ 0, _short.length ]);
                            assert($input.val(_empty).caret()).isOneOf([ 0, _empty.length ]);
                        });
                    });

                    describe('<textarea>', function() {
                        it("Returns zero (0) or value.length when the textarea's value changes", function() {
                            assert($textarea.val(_short).caret()).isOneOf([ 0, _short.length ]);
                            assert($textarea.val(_long).caret()).isOneOf([ 0, _long.length ]);
                            assert($textarea.val(_short).caret()).isOneOf([ 0, _short.length ]);
                            assert($textarea.val(_empty).caret()).isOneOf([ 0, _empty.length ]);
                        });
                    });
                });

                describe('Set', function() {
                    describe('<input>', function() {
                        var text = _single,
                            len = text.length,
                            mid = Math.floor(len / 2);

                        it("Gets the same position that was set", function() {
                            assert($input.val(text).caret(0).caret()).equals(0);
                            assert($input.val(text).caret(1).caret()).equals(1);
                            assert($input.val(text).caret(mid - 2).caret()).equals(mid - 2);
                            assert($input.val(text).caret(mid - 1).caret()).equals(mid - 1);
                            assert($input.val(text).caret(mid).caret()).equals(mid);
                            assert($input.val(text).caret(mid + 1).caret()).equals(mid + 1);
                            assert($input.val(text).caret(mid + 2).caret()).equals(mid + 2);
                            assert($input.val(text).caret(len - 1).caret()).equals(len - 1);
                            assert($input.val(text).caret(len).caret()).equals(len);
                        });

                        it("Enforces length boundary", function() {
                            assert($input.val(text).caret(len + 1).caret()).equals(len);
                            assert($input.val(text).caret(len + 2).caret()).equals(len);
                        });

                        it("Allows negative position", function() {
                            assert($input.val(text).caret(-1).caret()).equals(len - 1);
                            assert($input.val(text).caret(-2).caret()).equals(len - 2);
                        });

                        it("Converts floating point values to integers", function() {
                            assert($input.val(text).caret(1.5).caret()).equals(1.0);
                            assert($input.val(text).caret(2.5).caret()).equals(2.0);
                        });
                    });

                    describe('<textarea>', function() {
                        var text = _multi,
                            len = text.norm().length,
                            mid = text.indexOf('\n');

                        it("Gets the same position that was set", function() {
                            assert($textarea.val(text).caret(0).caret()).equals(0);
                            assert($textarea.val(text).caret(1).caret()).equals(1);
                            assert($textarea.val(text).caret(mid - 2).caret()).equals(mid - 2);
                            assert($textarea.val(text).caret(mid - 1).caret()).equals(mid - 1);
                            assert($textarea.val(text).caret(mid).caret()).equals(mid);
                            assert($textarea.val(text).caret(mid + 1).caret()).equals(mid + 1);
                            assert($textarea.val(text).caret(mid + 2).caret()).equals(mid + 2);
                            assert($textarea.val(text).caret(len - 1).caret()).equals(len - 1);
                            assert($textarea.val(text).caret(len).caret()).equals(len);
                        });

                        it("Enforces length boundary", function() {
                            assert($textarea.val(text).caret(len + 1).caret()).equals(len);
                            assert($textarea.val(text).caret(len + 2).caret()).equals(len);
                        });

                        it("Allows negative position", function() {
                            assert($textarea.val(text).caret(-1).caret()).equals(len - 1);
                            assert($textarea.val(text).caret(-2).caret()).equals(len - 2);
                        });

                        it("Converts floating point values to integers", function() {
                            assert($textarea.val(text).caret(1.5).caret()).equals(1);
                            assert($textarea.val(text).caret(2.5).caret()).equals(2);
                        });
                    });
                });

                describe('Insert', function() {
                    describe('<input>', function() {
                        it("Prepends text", function() {
                            assert($input.val('abc').caret(0).caret('123').val()).equals('123abc');
                        });

                        it("Appends text", function() {
                            assert($input.val('abc').caret(3).caret('123').val()).equals('abc123');
                        });

                        it("Inserts text", function() {
                            assert($input.val('abcdef').caret(3).caret('123').val()).equals('abc123def');
                        });

                        it("Respects maxlength attribute", function() {
                            $input.attr('maxlength', 5);
                            assert($input.val('abc').caret(0).caret('123').val()).equals('12abc');
                            assert($input.val('abc').caret(3).caret('123').val()).equals('abc12');
                            assert($input.val('abcde').caret(0).caret('123').val()).equals('abcde');
                            assert($input.val('abcde').caret(3).caret('123').val()).equals('abcde');
                            assert($input.val('abcde').caret(5).caret('123').val()).equals('abcde');
                        });
                    });

                    describe('<textarea>', function() {
                        it("Prepends text", function() {
                            assert($textarea.val('abc\ndef').caret(0).caret('123').val()).equals('123abc\ndef');
                        });

                        it("Appends text", function() {
                            assert($textarea.val('abc\ndef').caret(7).caret('123').val()).equals('abc\ndef123');
                        });

                        it("Inserts text", function() {
                            assert($textarea.val('abc\ndef').caret(3).caret('123').val()).equals('abc123\ndef');
                            assert($textarea.val('abc\ndef').caret(4).caret('123').val()).equals('abc\n123def');
                        });

                        it("Respects maxlength attribute", function() {
                            $textarea.attr('maxlength', 9);
                            assert($textarea.val('abc\ndef').caret(0).caret('123').val()).equals('12abc\ndef');
                            assert($textarea.val('abc\ndef').caret(3).caret('123').val()).equals('abc12\ndef');
                            assert($textarea.val('abc\ndef').caret(4).caret('123').val()).equals('abc\n12def');
                            assert($textarea.val('abc\ndef\ng').caret(0).caret('123').val()).equals('abc\ndef\ng');
                            assert($textarea.val('abc\ndef\ng').caret(3).caret('123').val()).equals('abc\ndef\ng');
                            assert($textarea.val('abc\ndef\ng').caret(5).caret('123').val()).equals('abc\ndef\ng');
                        });
                    });
                });
            });

            describe('Range', function() {
                describe('Get', function () {
                    describe('<textarea>', function() {
                        it("Returns the correct range", function() {
                            assert(_s($textarea.val('abc\ndef').range(0, 7).range())).equals(_s({ start: 0, end: 7, length: 7, text: 'abc\ndef' }));
                            assert(_s($textarea.val('abc\ndef').range(3, 4).range())).equals(_s({ start: 3, end: 4, length: 1, text: '\n' }));
                        });
                    });
                });

                describe('Set', function () {
                    describe('<textarea>', function() {
                        it("Accepts a single argument", function() {
                            assert(_s($textarea.val('abc\ndef').range(0).range())).equals(_s($textarea.val('abc\ndef').range(0, 7).range()));
                            assert(_s($textarea.val('abc\ndef').range(3).range())).equals(_s($textarea.val('abc\ndef').range(3, 7).range()));
                        });
                    });

                    // TODO: Test $.fn.range(-3, -8)
                });
            });
        });

    });

}());<|endoftext|>