<commit_before>'use strict';

var expect = require('chai').expect;
var modified = require('../');

var node_path = require('path');
var node_http = require('http');
var node_fs = require('fs');

var fs = require('fs-sync');

var cache_file_1 = node_path.join( __dirname, '1.cache' );
var cache_file_2 = node_path.join( __dirname, '2.cache' );
var cache_file_3 = node_path.join( __dirname, '3.cache' );

// Fake etag
var ETAG = String( Math.random() * 1000000 );
var RESPONSE = {
    a: 1,
    b: 2
}

var s = node_http.createServer(function (req, res) {
    if ( req.headers['if-none-match'] === ETAG ) {
        res.writeHead(304, {
            'Etag': ETAG
        });

    } else {
        res.writeHead(200, {
            Etag: ETAG
        });

        res.write(JSON.stringify(RESPONSE));
    }

    res.end();
});

var server_port;

function try_listen (port) {
    console.log('try listen port:', port);

    try {
        s.listen(port);
        server_port = port;
    } catch(e) {
        console.log('>> already in use.');
        listen(port + 1);
    }
};

try_listen(19238);

describe("complex", function(){
    it("will get statusCode 200 when there's no cache", function(done){
        var request = modified({
            cacheMapper: function (options, callback) {
                callback(null, cache_file_1 );
            }
        });

        request('http://localhost:' + server_port, function (err, res, body) {
            // done();
            expect(res.statusCode).to.equal(200);

            var json = JSON.parse(body);
            expect(json.a).to.equal(1);

            request('http://localhost:' + server_port, function (err, res, body) {
                // done();
                expect(res.statusCode).to.equal(304);

                var json = JSON.parse(body);
                expect(json.a).to.equal(1);

                fs.remove(cache_file_1);
                fs.remove(cache_file_1 + '.cached-data');
                done();
            });
        });
    });

    it("will get statusCode 304 when already cached", function(done){
        var request = modified({
            cacheMapper: function (options, callback) {
                callback(null, cache_file_2 );
            }
        });

        var cached_data = cache_file_2 + '.cached-data';

        fs.write(
            cache_file_2,
            JSON.stringify({
                etag: ETAG
            })
        );

        fs.write(
            cached_data,
            JSON.stringify({
                a: 3
            })
        );

        request('http://localhost:' + server_port, function (err, res, body) {
            done();

            expect(res.statusCode).to.equal(304);

            var json = JSON.parse(body);
            expect(json.a).to.equal(3);

            fs.remove(cache_file_2);
            fs.remove(cached_data);
        });
    });
});

describe(".pipe()", function(){
    it("without cache", function(done){
        var request = modified({
            cacheMapper: function (options, callback) {
                callback(null, cache_file_3 );
            }
        });

        var req = request.get('http://localhost:' + server_port);

        var pipe_to = node_path.join(__dirname, 'piped');

        req.pipe(node_fs.createWriteStream( pipe_to ));

        req.on('complete', function () {
            expect(fs.read(pipe_to)).to.equal('{"a":1,"b":2}');
            fs.remove(cache_file_3);
            fs.remove(cache_file_3 + '.cached-data');
            fs.remove(pipe_to);
            done();
        })
    });

    it("google logo", function(done){
        var url = 'http://www.google.com.hk/images/srpr/logo11w.png';
        var request = modified();
        var req = request(url);

        req.pipe(node_fs.createWriteStream( 'logo.png' ));
        req.on('complete', function (res) {
            done();
        });
    });
});

<commit_msg>#1: update test cases; fix the unapproching of google image on travis-ci<commit_after>'use strict';

var expect = require('chai').expect;
var modified = require('../');

var node_path = require('path');
var node_http = require('http');
var node_url = require('url');
var node_fs = require('fs');
var async = require('async');

var fs = require('fs-sync');

var cache_file_1 = node_path.join( __dirname, '1.cache' );
var cache_file_2 = node_path.join( __dirname, '2.cache' );
var cache_file_3 = node_path.join( __dirname, '3.cache' );
var source_png = node_path.join( __dirname, 'fixtures', 'modified.png' );

// Fake etag
var ETAG = String( Math.random() * 1000000 );
var RESPONSE = {
    a: 1,
    b: 2
}

var s = node_http.createServer(function (req, res) {
    if ( req.headers['if-none-match'] === ETAG ) {
        res.writeHead(304, {
            'Etag': ETAG
        });

        res.end();

    } else {
        res.writeHead(200, {
            Etag: ETAG
        });

        var url = node_url.parse(req.url);

        if ( url.pathname === '/modified.png' ) {
            var stream = node_fs.createReadStream( source_png );
            stream.pipe(res);

            stream.on('end', function () {
                res.end();
            });

        } else {
            res.write(JSON.stringify(RESPONSE));
            res.end();
        }
    }
});

var server_port;

function try_listen (port) {
    console.log('try listen port:', port);

    try {
        s.listen(port);
        server_port = port;
    } catch(e) {
        console.log('>> already in use.');
        listen(port + 1);
    }
};

try_listen(19238);

describe("complex", function(){
    it("will get statusCode 200 when there's no cache", function(done){
        var request = modified({
            cacheMapper: function (options, callback) {
                callback(null, cache_file_1 );
            }
        });

        request('http://localhost:' + server_port, function (err, res, body) {
            // done();
            expect(res.statusCode).to.equal(200);

            var json = JSON.parse(body);
            expect(json.a).to.equal(1);

            request('http://localhost:' + server_port, function (err, res, body) {
                // done();
                expect(res.statusCode).to.equal(304);

                var json = JSON.parse(body);
                expect(json.a).to.equal(1);

                fs.remove(cache_file_1);
                fs.remove(cache_file_1 + '.cached-data');
                done();
            });
        });
    });

    it("will get statusCode 304 when already cached", function(done){
        var request = modified({
            cacheMapper: function (options, callback) {
                callback(null, cache_file_2 );
            }
        });

        var cached_data = cache_file_2 + '.cached-data';

        fs.write(
            cache_file_2,
            JSON.stringify({
                etag: ETAG
            })
        );

        fs.write(
            cached_data,
            JSON.stringify({
                a: 3
            })
        );

        request('http://localhost:' + server_port, function (err, res, body) {
            done();

            expect(res.statusCode).to.equal(304);

            var json = JSON.parse(body);
            expect(json.a).to.equal(3);

            fs.remove(cache_file_2);
            fs.remove(cached_data);
        });
    });
});

describe(".pipe()", function(){
    it("without cache", function(done){
        var request = modified({
            cacheMapper: function (options, callback) {
                callback(null, cache_file_3 );
            }
        });

        var req = request.get('http://localhost:' + server_port);

        var pipe_to = node_path.join(__dirname, 'piped');

        req.pipe(node_fs.createWriteStream( pipe_to ));

        req.on('complete', function () {
            expect(fs.read(pipe_to)).to.equal('{"a":1,"b":2}');
            fs.remove(cache_file_3);
            fs.remove(cache_file_3 + '.cached-data');
            fs.remove(pipe_to);
            done();
        })
    });

    it("google logo", function(done){
        var url = 'http://localhost:' + server_port + '/modified.png';
        var write_to = node_path.join(__dirname, 'modified.png');

        var request = modified();
        var req = request(url);

        req.pipe(node_fs.createWriteStream( write_to ));
        req.on('complete', function (res) {
            async.parallel([
                function (done) {
                    node_fs.stat(write_to, done);
                },

                function (done){
                    node_fs.stat(source_png, done);
                }

            ], function (err, data) {
                expect(err).to.equal(null);
                expect(data[0].size).to.equal(data[1].size);
                done();
            });
        });
    });
});

<|endoftext|>


<commit_before>var observer = require("../lib/abaaso.js").abaaso.observer;<commit_msg>Created observer unit tests<commit_after>var observer = require("../lib/abaaso.js").abaaso.observer,
    sample   = {};

sample.obj   = {id: "test"};
sample.event = "custom";
sample.fn    = function () { void(0); };
sample.id    = "handler";
sample.scope = sample.obj;
sample.state = "all";

exports["add"] = {
	setUp: function (done) {
		done();
	},
	direct: function (test) {
		test.expect(3);
		test.throws(function () { observer.add(sample.obj, undefined, sample.fn, sample.id, sample.scope, sample.state); }, Error, "Event is undefined");
		test.throws(function () { observer.add(sample.obj, sample.event, undefined, sample.id, sample.scope, sample.state); }, Error, "Handler is not a function");
		test.equal(observer.add(sample.obj, sample.event, sample.fn, sample.id, sample.scope, sample.state), sample.obj, "Should match");
		test.done();
	}
};

exports["list"] = {
	setUp: function (done) {
		done();
	},
	direct: function (test) {
		test.expect(2);
		test.equal(observer.list("invalid", sample.event).hasOwnProperty(sample.state), false, "Should be false");
		test.equal(observer.list(sample.obj, sample.event).hasOwnProperty(sample.state), true, "Should be true");
		test.done();
	}
};

exports["fire"] = {
	setUp: function (done) {
		done();
	},
	direct: function (test) {
		test.expect(1);
		test.equal(observer.fire(sample.obj, sample.event), sample.obj, "Should match");
		test.done();
	}
};

exports["remove"] = {
	setUp: function (done) {
		done();
	},
	direct: function (test) {
		test.expect(1);
		test.equal(observer.remove(sample.obj, sample.event, sample.id, sample.state), sample.obj, "Should match");
		test.done();
	}
};

exports["once"] = {
	setUp: function (done) {
		done();
	},
	direct: function (test) {
		test.expect(5);
		test.throws(function () { observer.once(sample.obj, undefined, sample.fn, sample.id, sample.scope, sample.state); }, Error, "Event is undefined");
		test.throws(function () { observer.once(sample.obj, sample.event, undefined, sample.id, sample.scope, sample.state); }, Error, "Handler is not a function");
		test.equal(observer.once(sample.obj, sample.event, sample.fn, sample.id, sample.scope, sample.state), sample.obj, "Should match");
		test.equal(observer.fire(sample.obj, sample.event), sample.obj, "Should match");
		test.equal(observer.list(sample.obj, sample.event)[sample.state].hasOwnProperty(sample.id), false, "Should be false");
		test.done();
	}
};<|endoftext|>


<commit_before>import h from 'hyperscript';

// Utils
import elToStr from '../utils/elementToString';
import c from '../utils/prefixClass';
import { scrollPct, scrollToBottom } from '../utils/scrollElement';

// Bindery
import Book from '../Book';
import Page from '../Page';

// paginate
import Scheduler from './Scheduler';
import orderPages from './orderPages';
import annotatePages from './annotatePages';
import breadcrumbCloner from './breadcrumbCloner';

const MAXIMUM_PAGE_LIMIT = 9999;

const last = arr => arr[arr.length - 1];

const paginate = ({ content, rules, success, progress, error, isDebuggable }) => {
  // SETUP
  const start = window.performance.now();
  const state = {
    breadcrumb: [], // Stack representing which element we're currently inside
    pages: [],
    book: new Book(),
  };
  const scheduler = new Scheduler(isDebuggable);
  const measureArea = document.body.appendChild(h(c('.measure-area')));
  if (isDebuggable) measureArea.classList.add(c('debug'));

  const cloneBreadcrumb = breadcrumbCloner(rules);

  const applyNewPageRules = (pg) => {
    rules.forEach((rule) => {
      if (rule.afterPageCreated) rule.afterPageCreated(pg, state);
    });
  };

  const makeNewPage = () => {
    const newPage = new Page();
    const shouldScroll = isDebuggable && scrollPct(measureArea) > 0.9;
    measureArea.appendChild(newPage.element);
    if (shouldScroll) scrollToBottom(measureArea);

    applyNewPageRules(newPage);
    return newPage;
  };

  // Creates clones for ever level of tag
  // we were in when we overflowed the last page
  const continueOnNewPage = () => {
    if (state.currentPage && state.currentPage.hasOverflowed()) {
      console.warn('Bindery: Page overflowing', state.currentPage.element);
      if (!state.currentPage.suppressErrors) {
        throw Error('Bindery: Moved to new page when last one is still overflowing');
      }
    }

    if (state.pages.length === 500) {
      console.warn('Bindery: More than 500 pages, performance may be slow.');
    } else if (state.pages.length === 1000) {
      console.warn('Bindery: More than 1000 pages, performance may be slow.');
    } else if (state.pages.length > MAXIMUM_PAGE_LIMIT) {
      error('Maximum page count exceeded');
      throw Error('Bindery: Maximum page count exceeded. Suspected runaway layout.');
    }

    state.breadcrumb = cloneBreadcrumb(state.breadcrumb);
    const newPage = makeNewPage();
    state.pages.push(newPage);
    state.currentPage = newPage;
    if (state.breadcrumb[0]) {
      newPage.flowContent.appendChild(state.breadcrumb[0]);
    }

    // make sure the cloned page is valid.
    if (newPage.hasOverflowed()) {
      const suspect = last(state.breadcrumb);
      if (suspect) {
        console.error(`Bindery: NextPage already overflowing, probably due to a style set on ${elToStr(suspect)}. It may not fit on the page.`);
        suspect.parentNode.removeChild(suspect);
      } else {
        console.error('Bindery: NextPage already overflowing.');
      }
    }

    progress(state.pages.length);

    return newPage;
  };

  const applyBeforeAddRules = (element) => {
    let addedElement = element;
    rules.forEach((rule) => {
      if (!rule.selector) return;
      if (addedElement.matches(rule.selector) && rule.beforeAdd) {
        addedElement = rule.beforeAdd(addedElement, state, continueOnNewPage);
      }
    });
  };

  const afterAddRules = rules.filter(r => r.selector && r.afterAdd);
  const applyAfterAddRules = (originalElement) => {
    let addedElement = originalElement;
    afterAddRules.forEach((rule) => {
      if (addedElement.matches(rule.selector)) {
        addedElement = rule.afterAdd(
          addedElement,
          state,
          continueOnNewPage,
          function overflowCallback(problemElement) {
          // TODO:
          // While this does catch overflows, it introduces a few new bugs.
          // It is pretty aggressive to move the entire node to the next page.
          // - 1. there is no guarentee it will fit on the new page
          // - 2. if it has childNodes, those side effects will not be undone,
          // which means footnotes will get left on previous page.
          // - 3. if it is a large paragraph, it will leave a large gap. the
          // ideal approach would be to only need to invalidate
          // the last line of text.
            problemElement.parentNode.removeChild(problemElement);
            continueOnNewPage();
            const lastEl = last(state.breadcrumb);
            lastEl.appendChild(problemElement);
            return rule.afterAdd(problemElement, state, continueOnNewPage, () => {
              console.log(`Couldn't apply ${rule.name} to ${elToStr(problemElement)}. Caused overflows twice.`);
            });
          }
        );
      }
    });
  };
  const afterBindRules = (book) => {
    rules.forEach((rule) => {
      if (rule.afterBind) {
        book.pages.forEach((page) => {
          rule.afterBind(page, book);
        });
      }
    });
  };

  // Walk up the tree to see if we can safely
  // insert a split into this node.
  const selectorsNotToSplit = rules
    .filter(rule => rule.avoidSplit)
    .map(rule => rule.selector);

  const isSplittable = (node) => {
    if (selectorsNotToSplit.some(sel => node.matches(sel))) {
      if (node.hasAttribute('data-bindery-did-move')) {
        return true; // don't split it again.
      }
      if (node.classList.contains(c('continuation'))) {
        return true; // don't split it again.
      }
      return false;
    }
    if (node.parentElement) {
      return isSplittable(node.parentElement);
    }
    return true;
  };

  // TODO: Once a node is moved to a new page, it should
  // no longer trigger another move. otherwise tall elements
  // will trigger endlessly get shifted to the next page
  const moveNodeToNextPage = (nodeToMove) => {
    // So this node won't get cloned. TODO: this is unclear
    state.breadcrumb.pop();

    // find the nearest splittable parent
    let willMove = nodeToMove;
    const pathToRestore = [];
    while (!isSplittable(last(state.breadcrumb))) {
      // console.log('Not OK to split:', last(state.breadcrumb));
      willMove = state.breadcrumb.pop();
      pathToRestore.unshift(willMove);
    }

    // console.log('OK to split:', last(state.breadcrumb));
    // console.log('Will move:', willMove);
    willMove.setAttribute('data-bindery-did-move', true);

    const parent = willMove.parentNode;
    // remove the unsplittable node and subnodes
    parent.removeChild(willMove);

    // TODO: step back even further if the
    // to avoid leaving otherwise empty nodes behind
    if (last(state.breadcrumb).textContent.trim() === '') {
      // console.log('Leaving empty node', last(state.breadcrumb));
      parent.appendChild(willMove);
      willMove = state.breadcrumb.pop();
      pathToRestore.unshift(willMove);
      willMove.parentNode.removeChild(willMove);
    }

    if (state.currentPage.isEmpty) {
      // state.currentPage.element.style.background = 'red';
      // nodeToMove.style.background = 'orange';
      // willMove.style.background = 'yellow';
      // console.log('moving node', nodeToMove);
      // console.log('interpreted node', willMove);
      // throw Error('moving from empty page');
    }

    // create new page and clone context onto it
    continueOnNewPage();

    // append node as first in new page
    last(state.breadcrumb).appendChild(willMove);

    // restore subpath
    pathToRestore.forEach((restore) => {
      state.breadcrumb.push(restore);
    });
    state.breadcrumb.push(nodeToMove);
  };

  const addTextNode = (textNode, doneCallback, undoAddTextNode) => {
    last(state.breadcrumb).appendChild(textNode);

    if (state.currentPage.hasOverflowed()) {
      textNode.parentNode.removeChild(textNode);
      undoAddTextNode();
    } else {
      scheduler.throttle(doneCallback);
    }
  };

  // Adds an text node by incrementally adding words
  // until it just barely doesnt overflow
  const addTextNodeIncremental = (originalNode, doneCallback, undoAddTextNode) => {
    let originalText = originalNode.nodeValue;
    let textNode = originalNode;
    last(state.breadcrumb).appendChild(textNode);

    if (!state.currentPage.hasOverflowed()) {
      scheduler.throttle(doneCallback);
      return;
    }

    let pos = 0;

    const splitTextStep = () => {
      textNode.nodeValue = originalText.substr(0, pos);

      if (state.currentPage.hasOverflowed()) {
        // Back out to word boundary
        if (originalText.charAt(pos) === ' ') pos -= 1; // TODO: redundant
        while (originalText.charAt(pos) !== ' ' && pos > 0) pos -= 1;

        if (pos < 1) {
          textNode.nodeValue = originalText;
          textNode.parentNode.removeChild(textNode);
          undoAddTextNode();
          return;
        }

        const fittingText = originalText.substr(0, pos);
        const overflowingText = originalText.substr(pos);
        textNode.nodeValue = fittingText;
        originalText = overflowingText;

        pos = 0;

        // Start on new page
        continueOnNewPage();

        // Continue working with clone
        textNode = document.createTextNode(originalText);
        last(state.breadcrumb).appendChild(textNode);

        // If the remainder fits there, we're done
        if (!state.currentPage.hasOverflowed()) {
          scheduler.throttle(doneCallback);
          return;
        }

        scheduler.throttle(splitTextStep);
        return;
      }
      if (pos > originalText.length - 1) {
        scheduler.throttle(doneCallback);
        return;
      }

      pos += 1;
      while (originalText.charAt(pos) !== ' ' && pos < originalText.length) pos += 1;

      scheduler.throttle(splitTextStep);
    };

    splitTextStep();
  };

  const addTextChild = (parent, child, next) => {
    const forceAddTextNode = () => {
      last(state.breadcrumb).appendChild(child);
      state.currentPage.suppressErrors = true;
      continueOnNewPage();
      scheduler.throttle(next);
    };
    if (isSplittable(parent)) {
      const undoAddTextNode = () => {
        moveNodeToNextPage(parent);
        addTextNodeIncremental(child, next, forceAddTextNode);
      };
      addTextNodeIncremental(child, next, undoAddTextNode);
    } else {
      const undoAddTextNode = () => {
        moveNodeToNextPage(parent);
        addTextNode(child, next, forceAddTextNode);
      };
      addTextNode(child, next, undoAddTextNode);
    }
  };

  const addElementChild = (parent, child, next) => {
    if (child.tagName === 'SCRIPT') {
      next(); // skips
      return;
    }

    applyBeforeAddRules(child);

    const addedChildrenSuccess = () => {
      // We're now done with this element and its children,
      // so we pop up a level
      const addedChild = state.breadcrumb.pop();

      // If this child didn't fit any contents on this page,
      // but did have contents on a previous page
      // we should never have added it.
      // TODO: Catch this earlier.
      if (false && addedChild.classList.contains(c('continuation'))
        && addedChild.children.length === 0) {
        addedChild.parentNode.removeChild(addedChild);
      } else {
        // TODO: AfterAdd rules may want to access original child, not split second half
        applyAfterAddRules(addedChild);
      }

      if (state.currentPage.hasOverflowed()) {
        // console.log('Bindery: Added element despite overflowing');
      }
      next();
    };

    scheduler.throttle(() => {
      addElementNode(child, addedChildrenSuccess);
    });
  };

  // Adds an element node by clearing its childNodes, then inserting them
  // one by one recursively until thet overflow the page
  const addElementNode = (node, doneCallback) => {
    if (state.currentPage.hasOverflowed()) {
      // console.error('Bindery: Trying to add node to a page that\'s already overflowing');
      state.currentPage.suppressErrors = true;
      continueOnNewPage();
    }

    // Add this node to the current page or context
    if (!state.breadcrumb[0]) state.currentPage.flowContent.appendChild(node);
    else last(state.breadcrumb).appendChild(node);

    state.breadcrumb.push(node);

    const childNodes = [...node.childNodes];
    node.innerHTML = '';

    // Overflows when empty
    if (state.currentPage.hasOverflowed()) {
      moveNodeToNextPage(node);
    }

    let index = 0;
    const addNext = () => {
      if (!(index < childNodes.length)) {
        doneCallback();
        return;
      }
      const child = childNodes[index];
      index += 1;

      if (child.nodeType === Node.TEXT_NODE) {
        addTextChild(node, child, addNext);
      } else if (child.nodeType === Node.ELEMENT_NODE) {
        addElementChild(node, child, addNext);
      } else {
        addNext(); // Skip comments and unknown nodes
      }
    };
    // kick it off
    addNext();
  };

  const finishPagination = () => {
    document.body.removeChild(measureArea);

    const orderedPages = orderPages(state.pages, makeNewPage);
    annotatePages(orderedPages);

    state.book.pages = orderedPages;
    state.book.setCompleted();

    afterBindRules(state.book);

    const end = window.performance.now();
    console.log(`Bindery: Pages created in ${(end - start) / 1000}s`);

    success(state.book);
  };

  content.style.margin = 0;
  content.style.padding = 0;
  continueOnNewPage();
  addElementNode(content, finishPagination);
};

export default paginate;
<commit_msg>Wait for images to load<commit_after>import h from 'hyperscript';

// Utils
import elToStr from '../utils/elementToString';
import c from '../utils/prefixClass';
import { scrollPct, scrollToBottom } from '../utils/scrollElement';

// Bindery
import Book from '../Book';
import Page from '../Page';

// paginate
import Scheduler from './Scheduler';
import orderPages from './orderPages';
import annotatePages from './annotatePages';
import breadcrumbCloner from './breadcrumbCloner';

const MAXIMUM_PAGE_LIMIT = 9999;

const last = arr => arr[arr.length - 1];

const paginate = ({ content, rules, success, progress, error, isDebuggable }) => {
  // SETUP
  const start = window.performance.now();
  const state = {
    breadcrumb: [], // Stack representing which element we're currently inside
    pages: [],
    book: new Book(),
  };
  const scheduler = new Scheduler(isDebuggable);
  const measureArea = document.body.appendChild(h(c('.measure-area')));
  if (isDebuggable) measureArea.classList.add(c('debug'));

  const cloneBreadcrumb = breadcrumbCloner(rules);

  const applyNewPageRules = (pg) => {
    rules.forEach((rule) => {
      if (rule.afterPageCreated) rule.afterPageCreated(pg, state);
    });
  };

  const makeNewPage = () => {
    const newPage = new Page();
    const shouldScroll = isDebuggable && scrollPct(measureArea) > 0.9;
    measureArea.appendChild(newPage.element);
    if (shouldScroll) scrollToBottom(measureArea);

    applyNewPageRules(newPage);
    return newPage;
  };

  // Creates clones for ever level of tag
  // we were in when we overflowed the last page
  const continueOnNewPage = () => {
    if (state.currentPage && state.currentPage.hasOverflowed()) {
      console.warn('Bindery: Page overflowing', state.currentPage.element);
      if (!state.currentPage.suppressErrors) {
        throw Error('Bindery: Moved to new page when last one is still overflowing');
      }
    }

    if (state.pages.length === 500) {
      console.warn('Bindery: More than 500 pages, performance may be slow.');
    } else if (state.pages.length === 1000) {
      console.warn('Bindery: More than 1000 pages, performance may be slow.');
    } else if (state.pages.length > MAXIMUM_PAGE_LIMIT) {
      error('Maximum page count exceeded');
      throw Error('Bindery: Maximum page count exceeded. Suspected runaway layout.');
    }

    state.breadcrumb = cloneBreadcrumb(state.breadcrumb);
    const newPage = makeNewPage();
    state.pages.push(newPage);
    state.currentPage = newPage;
    if (state.breadcrumb[0]) {
      newPage.flowContent.appendChild(state.breadcrumb[0]);
    }

    // make sure the cloned page is valid.
    if (newPage.hasOverflowed()) {
      const suspect = last(state.breadcrumb);
      if (suspect) {
        console.error(`Bindery: NextPage already overflowing, probably due to a style set on ${elToStr(suspect)}. It may not fit on the page.`);
        suspect.parentNode.removeChild(suspect);
      } else {
        console.error('Bindery: NextPage already overflowing.');
      }
    }

    progress(state.pages.length);

    return newPage;
  };

  const applyBeforeAddRules = (element) => {
    let addedElement = element;
    rules.forEach((rule) => {
      if (!rule.selector) return;
      if (addedElement.matches(rule.selector) && rule.beforeAdd) {
        addedElement = rule.beforeAdd(addedElement, state, continueOnNewPage);
      }
    });
  };

  const afterAddRules = rules.filter(r => r.selector && r.afterAdd);
  const applyAfterAddRules = (originalElement) => {
    let addedElement = originalElement;
    afterAddRules.forEach((rule) => {
      if (addedElement.matches(rule.selector)) {
        addedElement = rule.afterAdd(
          addedElement,
          state,
          continueOnNewPage,
          function overflowCallback(problemElement) {
          // TODO:
          // While this does catch overflows, it introduces a few new bugs.
          // It is pretty aggressive to move the entire node to the next page.
          // - 1. there is no guarentee it will fit on the new page
          // - 2. if it has childNodes, those side effects will not be undone,
          // which means footnotes will get left on previous page.
          // - 3. if it is a large paragraph, it will leave a large gap. the
          // ideal approach would be to only need to invalidate
          // the last line of text.
            problemElement.parentNode.removeChild(problemElement);
            continueOnNewPage();
            const lastEl = last(state.breadcrumb);
            lastEl.appendChild(problemElement);
            return rule.afterAdd(problemElement, state, continueOnNewPage, () => {
              console.log(`Couldn't apply ${rule.name} to ${elToStr(problemElement)}. Caused overflows twice.`);
            });
          }
        );
      }
    });
  };
  const afterBindRules = (book) => {
    rules.forEach((rule) => {
      if (rule.afterBind) {
        book.pages.forEach((page) => {
          rule.afterBind(page, book);
        });
      }
    });
  };

  // Walk up the tree to see if we can safely
  // insert a split into this node.
  const selectorsNotToSplit = rules
    .filter(rule => rule.avoidSplit)
    .map(rule => rule.selector);

  const isSplittable = (node) => {
    if (selectorsNotToSplit.some(sel => node.matches(sel))) {
      if (node.hasAttribute('data-bindery-did-move')) {
        return true; // don't split it again.
      }
      if (node.classList.contains(c('continuation'))) {
        return true; // don't split it again.
      }
      return false;
    }
    if (node.parentElement) {
      return isSplittable(node.parentElement);
    }
    return true;
  };

  // TODO: Once a node is moved to a new page, it should
  // no longer trigger another move. otherwise tall elements
  // will trigger endlessly get shifted to the next page
  const moveNodeToNextPage = (nodeToMove) => {
    // So this node won't get cloned. TODO: this is unclear
    state.breadcrumb.pop();

    // find the nearest splittable parent
    let willMove = nodeToMove;
    const pathToRestore = [];
    while (!isSplittable(last(state.breadcrumb))) {
      // console.log('Not OK to split:', last(state.breadcrumb));
      willMove = state.breadcrumb.pop();
      pathToRestore.unshift(willMove);
    }

    // console.log('OK to split:', last(state.breadcrumb));
    // console.log('Will move:', willMove);
    willMove.setAttribute('data-bindery-did-move', true);

    const parent = willMove.parentNode;
    // remove the unsplittable node and subnodes
    parent.removeChild(willMove);

    // TODO: step back even further if the
    // to avoid leaving otherwise empty nodes behind
    if (last(state.breadcrumb).textContent.trim() === '') {
      // console.log('Leaving empty node', last(state.breadcrumb));
      parent.appendChild(willMove);
      willMove = state.breadcrumb.pop();
      pathToRestore.unshift(willMove);
      willMove.parentNode.removeChild(willMove);
    }

    if (state.currentPage.isEmpty) {
      // state.currentPage.element.style.background = 'red';
      // nodeToMove.style.background = 'orange';
      // willMove.style.background = 'yellow';
      // console.log('moving node', nodeToMove);
      // console.log('interpreted node', willMove);
      // throw Error('moving from empty page');
    }

    // create new page and clone context onto it
    continueOnNewPage();

    // append node as first in new page
    last(state.breadcrumb).appendChild(willMove);

    // restore subpath
    pathToRestore.forEach((restore) => {
      state.breadcrumb.push(restore);
    });
    state.breadcrumb.push(nodeToMove);
  };

  const addTextNode = (textNode, doneCallback, undoAddTextNode) => {
    last(state.breadcrumb).appendChild(textNode);

    if (state.currentPage.hasOverflowed()) {
      textNode.parentNode.removeChild(textNode);
      undoAddTextNode();
    } else {
      scheduler.throttle(doneCallback);
    }
  };

  // Adds an text node by incrementally adding words
  // until it just barely doesnt overflow
  const addTextNodeIncremental = (originalNode, doneCallback, undoAddTextNode) => {
    let originalText = originalNode.nodeValue;
    let textNode = originalNode;
    last(state.breadcrumb).appendChild(textNode);

    if (!state.currentPage.hasOverflowed()) {
      scheduler.throttle(doneCallback);
      return;
    }

    let pos = 0;

    const splitTextStep = () => {
      textNode.nodeValue = originalText.substr(0, pos);

      if (state.currentPage.hasOverflowed()) {
        // Back out to word boundary
        if (originalText.charAt(pos) === ' ') pos -= 1; // TODO: redundant
        while (originalText.charAt(pos) !== ' ' && pos > 0) pos -= 1;

        if (pos < 1) {
          textNode.nodeValue = originalText;
          textNode.parentNode.removeChild(textNode);
          undoAddTextNode();
          return;
        }

        const fittingText = originalText.substr(0, pos);
        const overflowingText = originalText.substr(pos);
        textNode.nodeValue = fittingText;
        originalText = overflowingText;

        pos = 0;

        // Start on new page
        continueOnNewPage();

        // Continue working with clone
        textNode = document.createTextNode(originalText);
        last(state.breadcrumb).appendChild(textNode);

        // If the remainder fits there, we're done
        if (!state.currentPage.hasOverflowed()) {
          scheduler.throttle(doneCallback);
          return;
        }

        scheduler.throttle(splitTextStep);
        return;
      }
      if (pos > originalText.length - 1) {
        scheduler.throttle(doneCallback);
        return;
      }

      pos += 1;
      while (originalText.charAt(pos) !== ' ' && pos < originalText.length) pos += 1;

      scheduler.throttle(splitTextStep);
    };

    splitTextStep();
  };

  const addTextChild = (parent, child, next) => {
    const forceAddTextNode = () => {
      last(state.breadcrumb).appendChild(child);
      state.currentPage.suppressErrors = true;
      continueOnNewPage();
      scheduler.throttle(next);
    };
    if (isSplittable(parent)) {
      const undoAddTextNode = () => {
        moveNodeToNextPage(parent);
        addTextNodeIncremental(child, next, forceAddTextNode);
      };
      addTextNodeIncremental(child, next, undoAddTextNode);
    } else {
      const undoAddTextNode = () => {
        moveNodeToNextPage(parent);
        addTextNode(child, next, forceAddTextNode);
      };
      addTextNode(child, next, undoAddTextNode);
    }
  };

  const addElementChild = (parent, child, next) => {
    if (child.tagName === 'SCRIPT') {
      next(); // skips
      return;
    }

    if (child.tagName === 'IMG') {
      if (!child.complete) {
        console.log(`Bindery: Waiting for image '${child.src}'`);
        child.addEventListener('load', () => {
          addElementChild(parent, child, next);
        });
        return;
      }
    }

    applyBeforeAddRules(child);

    const addedChildrenSuccess = () => {
      // We're now done with this element and its children,
      // so we pop up a level
      const addedChild = state.breadcrumb.pop();

      // If this child didn't fit any contents on this page,
      // but did have contents on a previous page
      // we should never have added it.
      // TODO: Catch this earlier.
      if (false && addedChild.classList.contains(c('continuation'))
        && addedChild.children.length === 0) {
        addedChild.parentNode.removeChild(addedChild);
      } else {
        // TODO: AfterAdd rules may want to access original child, not split second half
        applyAfterAddRules(addedChild);
      }

      if (state.currentPage.hasOverflowed()) {
        // console.log('Bindery: Added element despite overflowing');
      }
      next();
    };

    scheduler.throttle(() => {
      addElementNode(child, addedChildrenSuccess);
    });
  };

  // Adds an element node by clearing its childNodes, then inserting them
  // one by one recursively until thet overflow the page
  const addElementNode = (node, doneCallback) => {
    if (state.currentPage.hasOverflowed()) {
      // console.error('Bindery: Trying to add node to a page that\'s already overflowing');
      state.currentPage.suppressErrors = true;
      continueOnNewPage();
    }

    // Add this node to the current page or context
    if (!state.breadcrumb[0]) state.currentPage.flowContent.appendChild(node);
    else last(state.breadcrumb).appendChild(node);

    state.breadcrumb.push(node);

    const childNodes = [...node.childNodes];
    node.innerHTML = '';

    // Overflows when empty
    if (state.currentPage.hasOverflowed()) {
      moveNodeToNextPage(node);
    }

    let index = 0;
    const addNext = () => {
      if (!(index < childNodes.length)) {
        doneCallback();
        return;
      }
      const child = childNodes[index];
      index += 1;

      if (child.nodeType === Node.TEXT_NODE) {
        addTextChild(node, child, addNext);
      } else if (child.nodeType === Node.ELEMENT_NODE) {
        addElementChild(node, child, addNext);
      } else {
        addNext(); // Skip comments and unknown nodes
      }
    };
    // kick it off
    addNext();
  };

  const finishPagination = () => {
    document.body.removeChild(measureArea);

    const orderedPages = orderPages(state.pages, makeNewPage);
    annotatePages(orderedPages);

    state.book.pages = orderedPages;
    state.book.setCompleted();

    afterBindRules(state.book);

    const end = window.performance.now();
    console.log(`Bindery: Pages created in ${(end - start) / 1000}s`);

    success(state.book);
  };

  content.style.margin = 0;
  content.style.padding = 0;
  continueOnNewPage();
  addElementNode(content, finishPagination);
};

export default paginate;
<|endoftext|>


<commit_before>(function ( $ ) {

    function WheelOfFortune (element, options) {
        this.$el = $(element);
        this.options = options;
        this._generate();
    };

    WheelOfFortune.prototype = {
        _generate: function() {
            var self = this;

            var canvas = self.canvas = document.createElement('canvas');
            var context = self.ctx = canvas.getContext('2d');

            self.$el.children().remove();

            self.$wheel = $(canvas).css({
                "position": 'absolute',
                "width": '100%',
                "height": '100%',
                "z-index": 100
            });

            self.$pointer = $("

").css({
                "position": "absolute"
            });

            self.$el.append(self.$wheel);
            self.$el.append(self.$pointer);

            self._setRequestAnimFrame();
            self._init();
            self._drawTriangles();
        },
        _drawTriangles: function() {
            var self = this;

            self._clear();

            var context = self.ctx;
            var options = self.options;
            var radius = self.radius;

            var centerX = 0;
            var centerY = 0;

            var count = options.elements.length;

            var startAngle = 0;
            var stepAngle = 2 * Math.PI / count;

            var endAngle = 0;

            var colors = options.colors;
            var colorIndex = 0;

            for(i = 0; i < count; i++) {
                endAngle = startAngle + stepAngle;
                self._drawTriangle(centerX, centerY, radius, startAngle, endAngle, colors[colorIndex]);
                startAngle = endAngle;

                colorIndex = (colorIndex == colors.length - 1) ? 0 : colorIndex + 1;
            }

            self._drawElements();

        },
        _drawTriangle: function(centerX, centerY, radius, startAngle, endAngle, color) {
            var context = this.ctx;
            context.beginPath();
            context.moveTo(centerX, centerY);
            context.arc(centerX, centerY, radius, startAngle, endAngle, false);
            context.lineTo(centerX, centerY);
            context.closePath();
            context.fillStyle = color;
            context.fill();
        },
        _drawElements: function() {
            var self = this;
            var context = self.ctx;
            var options = self.options;

            var elements = options.elements;
            var count = elements.length;

            context.save();

            var innerAngle = 2 * Math.PI / count;
            var angle = (Math.PI - innerAngle) / 2;
            var h = Math.sin(angle) * (self.radius - options.fontSize);
            var x = Math.sin(angle) * h;
            var y = Math.cos(angle) * h;

            for(i = 0; i < count; i++) {
                context.translate(x, y);
                context.rotate(-angle);
                context.textAlign = 'center';
                context.fillStyle = options.fontColor;
                context.font = options.fontSize + "px " + options.fontFamily;
                context.fillText(elements[i], 0, options.fontSize / 2);

                context.rotate(angle);
                context.translate(-x, -y);
                context.rotate(innerAngle);
            }

            context.restore();
        },
        _init: function() {
            var self = this;
            var context = self.ctx;
            var canvas = self.canvas;

            var options = self.options;

            var count = options.elements.length;
            var stepAngle = 2 * Math.PI / count;

            self.stopAtIndex = options.stopAtIndex || Math.floor(Math.random() * (count + 1));
            self.distanceTraveled = 0;
            self.pathLength =
                (options.numberOfRotations * 2 * Math.PI) - (stepAngle / 2) - (stepAngle * self.stopAtIndex);
            self.currentSpeed = self.rotationSpeed = options.rotationSpeed * Math.PI / 180;
            self.minimalSpeed = options.minimalSpeed * Math.PI / 180;

            var innerWidth = self.$wheel.innerWidth();
            var innerHeight = self.$wheel.innerHeight();
            self.radius = (innerWidth < innerHeight) ? innerWidth / 2 : innerHeight / 2;

            canvas.setAttribute('width', innerWidth);
            canvas.setAttribute('height', innerHeight);

            context.translate(canvas.width / 2, canvas.height / 2);

            self.$pointer.css({
                "background-color": options.pointerColor,
                "width": options.pointerWidth + "px",
                "height": options.pointerHeight,
                "left": (innerWidth / 2 + self.radius - options.pointerWheelOverlap) + "px",
                "top": ((innerHeight - options.pointerHeight) / 2) + "px",
                "z-index": 200
            });

            self._clear();
        },
        _clear: function() {
            var self = this;
            var context = self.ctx;
            var canvas = self.canvas;

            context.clearRect(-canvas.width, -canvas.height, canvas.width * 2, canvas.height * 2);
        },
        _setRequestAnimFrame: function() {
            var self = this;

            self.requestAnimFrame = (function(callback) {
                return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame ||
                        window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
                            function(callback) {
                                window.setTimeout(callback, 1000 / 60);
                            };
            })();
        },
        _rotate: function(previousTime, previousAngle) {
            var self = this;
            var canvas = self.canvas;
            var context = self.ctx;
            var options = self.options;
            var rotationSpeed = self.currentSpeed;

            if(!options.constantSpeed && self.currentSpeed > self.minimalSpeed) {
                var treshold = options.brakeTreshold;
                var traveledPercentage = (self.distanceTraveled / self.pathLength);

                rotationSpeed = self.currentSpeed =
                    (traveledPercentage < treshold) ?
                        self.rotationSpeed :
                        (1 - (traveledPercentage - treshold) * (1 / (1 - treshold))) * self.rotationSpeed;
            }

            var currentTime = (new Date()).getTime();
            var timeDelta = currentTime - previousTime;
            var rotationAngle =  (rotationSpeed * timeDelta / 1000);

            self.distanceTraveled += rotationAngle;

            if(self.distanceTraveled >= self.pathLength) {
                rotationAngle -= (self.distanceTraveled - self.pathLength);
                context.rotate(rotationAngle);
                self._drawTriangles();
                options.onFinished.call(self, options.elements[self.stopAtIndex]);
                return;
            }

            context.rotate(rotationAngle);

            self._drawTriangles();

            self.requestAnimFrame.call(window, function() {
                self._rotate(currentTime, rotationAngle);
            });
        },
        spin: function() {
            var self = this;
            var startTime = (new Date()).getTime();
            self._rotate(startTime, 0);
        },
        destroy: function() {
            var self = this;

            self.$el.children().remove();
            self.$el.removeData('wheelOfFortune');
        },
        reset: function() {
            var self = this;
            var wheelOfFortune;

            self._generate();
        }
    };

    $.fn.wheelOfFortune = function(options) {
        function get() {
            var wheelOfFortune = $.data(this, 'wheelOfFortune');

            if (!wheelOfFortune) {
                wheelOfFortune = new WheelOfFortune(this, $.extend({}, options));
                $.data(this, 'wheelOfFortune', wheelOfFortune);
            }

            return wheelOfFortune;
        };

        var runCommand = function() {
            var wheelOfFortune = $.data(this, 'wheelOfFortune');
            wheelOfFortune[options]();
        }

        if (typeof options === 'string') {
            this.each(runCommand);
            return;
        }

        options = options || {};
        options = $.extend({}, $.fn.wheelOfFortune.defaults, options);
        return this.each(get);
    };

    $.fn.wheelOfFortune.defaults = {
        /**
         * Elements for circle
         * @type {Array}
         */
        elements: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
        /**
         * Stop at this element's index
         * If undefined, stops at random index
         * @type {Number} [zero based index]
         */
        stopAtIndex: null,
        /**
         * Background colors of i-th triangle,
         * circles if count > length(colors)
         * @type {Array} [of colors]
         */
        colors: ["#3A8E98", "#FFFFFF"],
        /**
         * Starting rotation speed in [deg/s]
         * @type {Number} [deg/s]
         */
        rotationSpeed: 360,
        /**
         * Max rotation numbers
         * @type {Number} [full circles]
         */
        numberOfRotations: 3,
        /**
         * Rotates at the same speed whole time if true
         * If false, speed decreases before stopping
         * @type {Boolean}
         */
        constantSpeed: false,
        /**
         * Percentage (distanceTraveled / pathLength) after speed begins to decrease
         * @type {Decimal} [0 - 1]
         */
        brakeTreshold: 0.5,
        /**
         * Minimal speed
         * @type {Number} [deg]
         */
        minimalSpeed: 10,
        /**
         * CSS - font size specification
         * @type {Number} [px]
         */
        fontSize: 60,
        /**
         * CSS - font family
         * @type {String}
         */
        fontFamily: 'Arial',
        /**
         * CSS - font color
         * @type {String}
         */
        fontColor: '#000',
        /**
         * Color of pointer
         * @type {String}
         */
        pointerColor: '#000',
        /**
         * Pointer width
         * @type {Number} [px]
         */
        pointerWidth: 60,
        /**
         * Pointer height
         * @type {Number} [px]
         */
        pointerHeight: 20,
        /**
         * Overlap of pointer over wheel image
         * @type {Number} [px]
         */
        pointerWheelOverlap: 20,
        /**
         * Function executed after rotation has finished
         */
        onFinished: function(selectedValue) {}
    };

} ( jQuery ));<commit_msg>device pixel ration included<commit_after>(function ( $ ) {

    function WheelOfFortune (element, options) {
        this.$el = $(element);
        this.options = options;
        this._generate();
    };

    WheelOfFortune.prototype = {
        _generate: function() {
            var self = this;

            var canvas = self.canvas = document.createElement('canvas');
            var context = self.ctx = canvas.getContext('2d');

            self.$el.children().remove();

            self.$wheel = $(canvas).css({
                "position": 'absolute',
                "width": '100%',
                "height": '100%',
                "z-index": 100
            });

            self.$pointer = $("

").css({
                "position": "absolute"
            });

            self.$el.append(self.$wheel);
            self.$el.append(self.$pointer);

            self._setRequestAnimFrame();
            self._init();
            self._drawTriangles();
        },
        _drawTriangles: function() {
            var self = this;

            self._clear();

            var context = self.ctx;
            var options = self.options;
            var radius = self.radius;

            var centerX = 0;
            var centerY = 0;

            var count = options.elements.length;

            var startAngle = 0;
            var stepAngle = 2 * Math.PI / count;

            var endAngle = 0;

            var colors = options.colors;
            var colorIndex = 0;

            for(i = 0; i < count; i++) {
                endAngle = startAngle + stepAngle;
                self._drawTriangle(centerX, centerY, radius, startAngle, endAngle, colors[colorIndex]);
                startAngle = endAngle;

                colorIndex = (colorIndex == colors.length - 1) ? 0 : colorIndex + 1;
            }

            self._drawElements();

        },
        _drawTriangle: function(centerX, centerY, radius, startAngle, endAngle, color) {
            var context = this.ctx;
            context.beginPath();
            context.moveTo(centerX, centerY);
            context.arc(centerX, centerY, radius, startAngle, endAngle, false);
            context.lineTo(centerX, centerY);
            context.closePath();
            context.fillStyle = color;
            context.fill();
        },
        _drawElements: function() {
            var self = this;
            var context = self.ctx;
            var options = self.options;

            var elements = options.elements;
            var count = elements.length;

            context.save();

            var innerAngle = 2 * Math.PI / count;
            var angle = (Math.PI - innerAngle) / 2;
            var h = Math.sin(angle) * (self.radius - options.fontSize);
            var x = Math.sin(angle) * h;
            var y = Math.cos(angle) * h;

            for(i = 0; i < count; i++) {
                context.translate(x, y);
                context.rotate(-angle);
                context.textAlign = 'center';
                context.fillStyle = options.fontColor;
                context.font = options.fontSize + "px " + options.fontFamily;
                context.fillText(elements[i], 0, options.fontSize / 2);

                context.rotate(angle);
                context.translate(-x, -y);
                context.rotate(innerAngle);
            }

            context.restore();
        },
        _init: function() {
            var self = this;
            var context = self.ctx;
            var canvas = self.canvas;

            var options = self.options;

            var count = options.elements.length;
            var stepAngle = 2 * Math.PI / count;

            self.stopAtIndex = options.stopAtIndex || Math.floor(Math.random() * (count + 1));
            self.distanceTraveled = 0;
            self.pathLength =
                (options.numberOfRotations * 2 * Math.PI) - (stepAngle / 2) - (stepAngle * self.stopAtIndex);
            self.currentSpeed = self.rotationSpeed = options.rotationSpeed * Math.PI / 180;
            self.minimalSpeed = options.minimalSpeed * Math.PI / 180;

            var innerWidth = self.$wheel.innerWidth();
            var innerHeight = self.$wheel.innerHeight();
            self.radius = (innerWidth < innerHeight) ? innerWidth / 2 : innerHeight / 2;

            var devicePixelRatio = window.devicePixelRatio || 1;

            canvas.setAttribute('width', innerWidth * devicePixelRatio);
            canvas.setAttribute('height', innerHeight * devicePixelRatio);

            context.translate(canvas.width / 2, canvas.height / 2);
            context.scale(devicePixelRatio, devicePixelRatio);

            self.$pointer.css({
                "background-color": options.pointerColor,
                "width": options.pointerWidth + "px",
                "height": options.pointerHeight,
                "left": (innerWidth / 2 + self.radius - options.pointerWheelOverlap) + "px",
                "top": ((innerHeight - options.pointerHeight) / 2) + "px",
                "z-index": 200
            });

            self._clear();
        },
        _clear: function() {
            var self = this;
            var context = self.ctx;
            var canvas = self.canvas;

            context.clearRect(-canvas.width, -canvas.height, canvas.width * 2, canvas.height * 2);
        },
        _setRequestAnimFrame: function() {
            var self = this;

            self.requestAnimFrame = (function(callback) {
                return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame ||
                        window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
                            function(callback) {
                                window.setTimeout(callback, 1000 / 60);
                            };
            })();
        },
        _rotate: function(previousTime, previousAngle) {
            var self = this;
            var canvas = self.canvas;
            var context = self.ctx;
            var options = self.options;
            var rotationSpeed = self.currentSpeed;

            if(!options.constantSpeed && self.currentSpeed > self.minimalSpeed) {
                var treshold = options.brakeTreshold;
                var traveledPercentage = (self.distanceTraveled / self.pathLength);

                rotationSpeed = self.currentSpeed =
                    (traveledPercentage < treshold) ?
                        self.rotationSpeed :
                        (1 - (traveledPercentage - treshold) * (1 / (1 - treshold))) * self.rotationSpeed;
            }

            var currentTime = (new Date()).getTime();
            var timeDelta = currentTime - previousTime;
            var rotationAngle =  (rotationSpeed * timeDelta / 1000);

            self.distanceTraveled += rotationAngle;

            if(self.distanceTraveled >= self.pathLength) {
                rotationAngle -= (self.distanceTraveled - self.pathLength);
                context.rotate(rotationAngle);
                self._drawTriangles();
                options.onFinished.call(self, options.elements[self.stopAtIndex]);
                return;
            }

            context.rotate(rotationAngle);

            self._drawTriangles();

            self.requestAnimFrame.call(window, function() {
                self._rotate(currentTime, rotationAngle);
            });
        },
        spin: function() {
            var self = this;
            var startTime = (new Date()).getTime();
            self._rotate(startTime, 0);
        },
        destroy: function() {
            var self = this;

            self.$el.children().remove();
            self.$el.removeData('wheelOfFortune');
        },
        reset: function() {
            var self = this;
            var wheelOfFortune;

            self._generate();
        }
    };

    $.fn.wheelOfFortune = function(options) {
        function get() {
            var wheelOfFortune = $.data(this, 'wheelOfFortune');

            if (!wheelOfFortune) {
                wheelOfFortune = new WheelOfFortune(this, $.extend({}, options));
                $.data(this, 'wheelOfFortune', wheelOfFortune);
            }

            return wheelOfFortune;
        };

        var runCommand = function() {
            var wheelOfFortune = $.data(this, 'wheelOfFortune');
            wheelOfFortune[options]();
        }

        if (typeof options === 'string') {
            this.each(runCommand);
            return;
        }

        options = options || {};
        options = $.extend({}, $.fn.wheelOfFortune.defaults, options);
        return this.each(get);
    };

    $.fn.wheelOfFortune.defaults = {
        /**
         * Elements for circle
         * @type {Array}
         */
        elements: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
        /**
         * Stop at this element's index
         * If undefined, stops at random index
         * @type {Number} [zero based index]
         */
        stopAtIndex: null,
        /**
         * Background colors of i-th triangle,
         * circles if count > length(colors)
         * @type {Array} [of colors]
         */
        colors: ["#3A8E98", "#FFFFFF"],
        /**
         * Starting rotation speed in [deg/s]
         * @type {Number} [deg/s]
         */
        rotationSpeed: 360,
        /**
         * Max rotation numbers
         * @type {Number} [full circles]
         */
        numberOfRotations: 3,
        /**
         * Rotates at the same speed whole time if true
         * If false, speed decreases before stopping
         * @type {Boolean}
         */
        constantSpeed: false,
        /**
         * Percentage (distanceTraveled / pathLength) after speed begins to decrease
         * @type {Decimal} [0 - 1]
         */
        brakeTreshold: 0.5,
        /**
         * Minimal speed
         * @type {Number} [deg]
         */
        minimalSpeed: 10,
        /**
         * CSS - font size specification
         * @type {Number} [px]
         */
        fontSize: 60,
        /**
         * CSS - font family
         * @type {String}
         */
        fontFamily: 'Arial',
        /**
         * CSS - font color
         * @type {String}
         */
        fontColor: '#000',
        /**
         * Color of pointer
         * @type {String}
         */
        pointerColor: '#000',
        /**
         * Pointer width
         * @type {Number} [px]
         */
        pointerWidth: 60,
        /**
         * Pointer height
         * @type {Number} [px]
         */
        pointerHeight: 20,
        /**
         * Overlap of pointer over wheel image
         * @type {Number} [px]
         */
        pointerWheelOverlap: 20,
        /**
         * Function executed after rotation has finished
         */
        onFinished: function(selectedValue) {}
    };

} ( jQuery ));<|endoftext|>


<commit_before>var expect = chai.expect;
var assert = chai.assert;

describe('Flywight Pattern', function() {
    it('should exist', function() {
        assert.isDefined(Flyweight);
    });
    it('should have Color property in the prototype object of the constructor', function() {
        assert.property(Flyweight.prototype, 'Color');
    });
    it('Color property should have default value "green"', function() {
        assert.equal(Flyweight.prototype.Color, 'green');
    });
    it('should have Name property in the prototype object of the constructor', function() {
        assert.property(Flyweight.prototype, 'Name');
    });
    it('Name property should have default value "Ball"', function() {
        assert.equal(Flyweight.prototype.Name, 'Ball');
    });

    it('should have Shape property in the prototype object of the contructor', function() {
        assert.property(Flyweight.prototype, 'Shape');
    });
    it('Name property should have default value "Circle"', function() {
        assert.equal(Flyweight.prototype.Shape, 'Circle');
    });
    it('a new object should be able to overwrite the default values', function() {
        var testFlyweight = new Flyweight();
        testFlyweight.Color = 'blue';
        testFlyweight.Name = 'Ballon';
        testFlyweight.Shape = 'Oval';
        assert.equal(testFlyweight.Color, 'blue');
        assert.equal(testFlyweight.Name, 'Ballon');
        assert.equal(testFlyweight.Shape, 'Oval');
    });
});
<commit_msg>fix typo in one of the flyweight tests<commit_after>var expect = chai.expect;
var assert = chai.assert;

describe('Flyweight Pattern', function() {
    it('should exist', function() {
        assert.isDefined(Flyweight);
    });
    it('should have Color property in the prototype object of the constructor', function() {
        assert.property(Flyweight.prototype, 'Color');
    });
    it('Color property should have default value "green"', function() {
        assert.equal(Flyweight.prototype.Color, 'green');
    });
    it('should have Name property in the prototype object of the constructor', function() {
        assert.property(Flyweight.prototype, 'Name');
    });
    it('Name property should have default value "Ball"', function() {
        assert.equal(Flyweight.prototype.Name, 'Ball');
    });

    it('should have Shape property in the prototype object of the constructor', function() {
        assert.property(Flyweight.prototype, 'Shape');
    });
    it('Name property should have default value "Circle"', function() {
        assert.equal(Flyweight.prototype.Shape, 'Circle');
    });
    it('a new object should be able to overwrite the default values', function() {
        var testFlyweight = new Flyweight();
        testFlyweight.Color = 'blue';
        testFlyweight.Name = 'Balloon';
        testFlyweight.Shape = 'Oval';
        assert.equal(testFlyweight.Color, 'blue');
        assert.equal(testFlyweight.Name, 'Balloon');
        assert.equal(testFlyweight.Shape, 'Oval');
    });
});
<|endoftext|>


<commit_before>import chai from 'chai';
import sinonChai from 'sinon-chai';
import sinon from 'sinon';
chai.use(sinonChai);

import CableCar from '../src/cableCar';

describe('CableCar', () => {

  const mockCreateFunc = sinon.stub();
  global.ActionCable = {
    createConsumer: () => { return { subscriptions: { create: mockCreateFunc } }}
  };

  const mockStore = { dispatch: sinon.spy() };

  describe('when ActionCable is undefined', () => {
    it('throws a descriptive error', () => {
      let ac = global.ActionCable;
      global.ActionCable = undefined;
      var fn = () => { return new CableCar('channel', mockStore) };
      chai.expect(fn).to.throw('CableCar tried to connect to ActionCable but ActionCable is not defined');
      global.ActionCable = ac;
    });
  });

  describe('constructor', () => {
    it('sets the channel', () => {
      let cc = new CableCar('channel', mockStore);
      chai.expect(cc.channel).to.eq('channel');
    });

    it('sets the store', () => {
      let cc = new CableCar('channel', mockStore);
      chai.expect(cc.store).to.eq(mockStore);
    });
  });

  describe('#initialize', () => {
    it('creates an ActionCable subscription with proper args', () => {
      let cc = new CableCar('channel', mockStore);
      chai.expect(mockCreateFunc).to.have.been.calledWith({ channel: 'channel' }, {
        initialized: cc.initialized,
        connected: cc.connected,
        disconnected: cc.disconnected,
        received: cc.received,
        rejected: cc.rejected,
      });
    });
  });

});<commit_msg>Fixes broken specs<commit_after>/* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */
/* global describe, it */

import chai from 'chai';
import sinonChai from 'sinon-chai';
import sinon from 'sinon';

import CableCar from '../src/cableCar';

chai.use(sinonChai);

describe('CableCar', () => {
  const mockCreateFunc = sinon.stub();
  global.ActionCable = {
    createConsumer: () => ({ subscriptions: { create: mockCreateFunc } }),
  };

  const mockStore = { dispatch: sinon.spy() };

  describe('when ActionCable is undefined', () => {
    it('throws a descriptive error', () => {
      const ac = global.ActionCable;
      global.ActionCable = undefined;
      const fn = () => new CableCar('channel', mockStore);
      chai.expect(fn).to.throw('CableCar tried to connect to ActionCable but ActionCable is not defined');
      global.ActionCable = ac;
    });
  });

  describe('constructor', () => {
    it('sets the channel and options', () => {
      const cc = new CableCar(mockStore, 'channel', { opt1: 5 });
      chai.expect(cc.params.channel).to.eq('channel');
      chai.expect(cc.params.opt1).to.eq(5);
    });

    it('sets the store', () => {
      const cc = new CableCar(mockStore, 'channel', { opt1: 5 });
      chai.expect(cc.store).to.eq(mockStore);
    });
  });

  describe('#initialize', () => {
    it('creates an ActionCable subscription with proper args', () => {
      const cc = new CableCar(mockStore, 'channel', { opt1: 5 });
      chai.expect(mockCreateFunc).to.have.been.calledWith({ channel: 'channel', opt1: 5 }, {
        initialized: cc.initialized,
        connected: cc.connected,
        disconnected: cc.disconnected,
        received: cc.received,
        rejected: cc.rejected,
      });
    });
  });
});
<|endoftext|>


<commit_before>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';
import { Picker } from 'react-native';

import globalStyles, { DARKER_GREY, SUSSOL_ORANGE } from '../globalStyles';

/**
 * A single selection dropdown menu implemented as a simple light-weight wrapper over the
 * native Picker component.
 *
 * @param {Array.<string>} values A list of values to render in the dropdown selection.
 * @param {string} selectedValue The currently selected value.
 * @param {Func}   onValueChange On selection callback handler.
 * @param {object} style Optional additional component styling.
 * @param {string} headerValue Optional value for a header within the popup.
 * @param {bool}   isDisabled  Optional inidicator that this dropdown is disabled.
 */
export const DropDown = React.memo(
  ({ values, selectedValue, onValueChange, style, headerValue, isDisabled }) => {
    const header = React.useMemo(
      () => (
        <Picker.Item key={headerValue} label={headerValue} enabled={false} color={DARKER_GREY} />
      ),
      [headerValue]
    );
    const items = React.useMemo(
      () =>
        values.map(value => (
          <Picker.Item key={value} label={value} value={value} color={SUSSOL_ORANGE} />
        )),
      []
    );
    const withHeader = React.useMemo(() => [header, ...items], [header, items]);

    return (
      <Picker
        selectedValue={selectedValue}
        mode="dropdown"
        onValueChange={onValueChange}
        style={{ ...globalStyles.picker, ...globalStyles.pickerText, ...style }}
        enabled={!isDisabled}
      >
        {headerValue ? withHeader : items}
      </Picker>
    );
  }
);

export default DropDown;

DropDown.defaultProps = {
  style: {},
  selectedValue: null,
  headerValue: '',
  isDisabled: false,
};

DropDown.propTypes = {
  values: PropTypes.array.isRequired,
  selectedValue: PropTypes.string,
  onValueChange: PropTypes.func.isRequired,
  style: PropTypes.object,
  headerValue: PropTypes.string,
  isDisabled: PropTypes.bool,
};
<commit_msg>Add update to DropDown<commit_after>/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';
import { Picker } from 'react-native';

import globalStyles, { DARKER_GREY, SUSSOL_ORANGE } from '../globalStyles';

/**
 * A single selection dropdown menu implemented as a simple light-weight wrapper over the
 * native Picker component.
 *
 * @param {Array.<string>} values A list of values to render in the dropdown selection.
 * @param {string} selectedValue The currently selected value.
 * @param {Func}   onValueChange On selection callback handler.
 * @param {object} style Optional additional component styling.
 * @param {string} headerValue Optional value for a header within the popup.
 * @param {bool}   isDisabled  Optional inidicator that this dropdown is disabled.
 */
export const DropDown = React.memo(
  ({ values, selectedValue, onValueChange, style, headerValue, isDisabled }) => {
    const onChange = React.useCallback(
      (value, index) => {
        if (headerValue && index === 0) return;
        onValueChange(value, index);
      },
      [onValueChange]
    );

    const header = React.useMemo(
      () => (
        <Picker.Item key={headerValue} label={headerValue} enabled={false} color={DARKER_GREY} />
      ),
      [headerValue]
    );
    const items = React.useMemo(
      () =>
        values.map(value => (
          <Picker.Item key={value} label={value} value={value} color={SUSSOL_ORANGE} />
        )),
      [values]
    );
    const withHeader = React.useMemo(() => [header, ...items], [header, items]);

    return (
      <Picker
        selectedValue={selectedValue}
        mode="dropdown"
        onValueChange={onChange}
        style={{ ...globalStyles.picker, ...globalStyles.pickerText, ...style }}
        enabled={!isDisabled}
      >
        {headerValue ? withHeader : items}
      </Picker>
    );
  }
);

export default DropDown;

DropDown.defaultProps = {
  style: {},
  selectedValue: null,
  headerValue: '',
  isDisabled: false,
};

DropDown.propTypes = {
  values: PropTypes.array.isRequired,
  selectedValue: PropTypes.string,
  onValueChange: PropTypes.func.isRequired,
  style: PropTypes.object,
  headerValue: PropTypes.string,
  isDisabled: PropTypes.bool,
};
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import reduxThunk from 'redux-thunk';

import App from './components/app';
import Signin from './components/auth/signin';
import Signout from './components/auth/signout';
import reducers from './reducers';

const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(reducers)}>
    <Router history={browserHistory}>
      <Route path="/" component={App}>
       <Route path="/signin" component={Signin} />
       <Route path="/signout" component={Signout} />
       <Route path="/signup" component={Signup} />
      </Route>
    </Router>
  </Provider>
  , document.querySelector('.container'));
<commit_msg>Add forgotten import statement<commit_after>import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import reduxThunk from 'redux-thunk';

import App from './components/app';
import Signin from './components/auth/signin';
import Signout from './components/auth/signout';
import Signup from './components/auth/signup';
import reducers from './reducers';

const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(reducers)}>
    <Router history={browserHistory}>
      <Route path="/" component={App}>
       <Route path="/signin" component={Signin} />
       <Route path="/signout" component={Signout} />
       <Route path="/signup" component={Signup} />
      </Route>
    </Router>
  </Provider>
  , document.querySelector('.container'));
<|endoftext|>


<commit_before>/*global test,regex,strictEqual,module,ok*/

module('backrefs');

test('can add backref in simplet case', function () {
    'use strict';
    regex()
        .literals('lit')
          .capture('group')
        .reference('group')
        .call(function (rb) {
            strictEqual(rb.peek(), '(lit)\\1');
        });
});

test('can get actual idx of capture', function () {
    'use strict';
    regex()
        .literals('a').capture('g1')
        .literals('b').capture('g2')
        .reference('g2')
        .call(function (rb) {
            strictEqual(rb.peek(), '(a)(b)\\2');
            strictEqual(rb.exec('abb').g2, 'b');
            ok(rb.test('abb'));
            ok(!rb.test('aba'));
            ok(!rb.test('ab'));
        });
});

test('toTerm (closing seqs, eithers, etc) maintains the backref', function () {
    'use strict';
    regex()
        .seq()
            .literals('lit')
              .capture('group')
            .reference('group')
            .call(function (rb) {
                strictEqual(rb.peek(), '(lit)\\1');
            })
        .end()
        .call(function (rb) {
            ok(rb._terms[0].backrefs, 'wheres the backref?');
            strictEqual(rb.peek(), '(lit)\\1');
        });
});

test('capturing a sequence updates backreferences', function () {
    'use strict';
    regex()
        .seq()
            .literals('lit')
              .capture('innerGroup')
            .reference('innerGroup')
            .call(function (rb) {
                strictEqual(rb.peek(), '(lit)\\1');
            })
        .end()
        .capture('outerGroup')
        .call(function (rb) {
            strictEqual(rb.peek(), '((lit)\\2)');
            strictEqual(rb.exec('litlit').innerGroup, 'lit');
            strictEqual(rb.exec('litlit').outerGroup, 'litlit');
        });
});

test('capturing a sequence updates ALL backrefs, for that matter', function () {
    'use strict';
    regex()
        .seq()
            .literals('lit')
              .capture('innerGroup')
            .reference('innerGroup')
            .reference('innerGroup')
            .call(function (rb) {
                strictEqual(rb.peek(), '(lit)\\1\\1');
            })
        .end()
        .capture('outerGroup')
        .call(function (rb) {
            strictEqual(rb.peek(), '((lit)\\2\\2)');
            strictEqual(rb.exec('litlitlit').innerGroup, 'lit');
            strictEqual(rb.exec('litlitlit').outerGroup, 'litlitlit');
        });
});

test('can make a backref to capture groups from parent', function () {
    'use strict';
    regex()
        .literals('prequel').capture('beforeSeq')
        .seq()
            .reference('beforeSeq')
        .end()
        .call(function (rb) {
            strictEqual(rb.peek(), '(prequel)\\1');
        });
});
<commit_msg>test: don't update backrefs from groups if not necessary<commit_after>/*global test,regex,strictEqual,module,ok*/

module('backrefs');

test('can add backref in simplet case', function () {
    'use strict';
    regex()
        .literals('lit')
          .capture('group')
        .reference('group')
        .call(function (rb) {
            strictEqual(rb.peek(), '(lit)\\1');
        });
});

test('can get actual idx of capture', function () {
    'use strict';
    regex()
        .literals('a').capture('g1')
        .literals('b').capture('g2')
        .reference('g2')
        .call(function (rb) {
            strictEqual(rb.peek(), '(a)(b)\\2');
            strictEqual(rb.exec('abb').g2, 'b');
            ok(rb.test('abb'));
            ok(!rb.test('aba'));
            ok(!rb.test('ab'));
        });
});

test('toTerm (closing seqs, eithers, etc) maintains the backref', function () {
    'use strict';
    regex()
        .seq()
            .literals('lit')
              .capture('group')
            .reference('group')
            .call(function (rb) {
                strictEqual(rb.peek(), '(lit)\\1');
            })
        .end()
        .call(function (rb) {
            ok(rb._terms[0].backrefs, 'wheres the backref?');
            strictEqual(rb.peek(), '(lit)\\1');
        });
});

test('capturing a sequence updates backreferences', function () {
    'use strict';
    regex()
        .seq()
            .literals('lit')
              .capture('innerGroup')
            .reference('innerGroup')
            .call(function (rb) {
                strictEqual(rb.peek(), '(lit)\\1');
            })
        .end()
        .capture('outerGroup')
        .call(function (rb) {
            strictEqual(rb.peek(), '((lit)\\2)');
            strictEqual(rb.exec('litlit').innerGroup, 'lit');
            strictEqual(rb.exec('litlit').outerGroup, 'litlit');
        });
});

test('capturing a sequence updates ALL backrefs, for that matter', function () {
    'use strict';
    regex()
        .seq()
            .literals('lit')
              .capture('innerGroup')
            .reference('innerGroup')
            .reference('innerGroup')
            .call(function (rb) {
                strictEqual(rb.peek(), '(lit)\\1\\1');
            })
        .end()
        .capture('outerGroup')
        .call(function (rb) {
            strictEqual(rb.peek(), '((lit)\\2\\2)');
            strictEqual(rb.exec('litlitlit').innerGroup, 'lit');
            strictEqual(rb.exec('litlitlit').outerGroup, 'litlitlit');
        });
});

test('can make a backref to capture groups from parent', function () {
    'use strict';
    regex()
        .literals('prequel').capture('beforeSeq')
        .seq()
            .reference('beforeSeq')
        .end()
        .call(function (rb) {
            strictEqual(rb.peek(), '(prequel)\\1');
        });
});

test('backrefs to parent capture groups dont get updated on capturing', function () {
    'use strict';
    regex()
        .literals('prequel').capture('beforeSeq')
        .seq()
            .reference('beforeSeq')
        .end()
        .call(function (rb) {
            strictEqual(rb.peek(), '(prequel)\\1');
        })
        .capture()
        .call(function (rb) {
            strictEqual(rb.peek(), '(prequel)(\\1)');
        });
});
<|endoftext|>


<commit_before>Prado.WebUI = Class.create();

Prado.WebUI.PostBackControl = Class.create();

Prado.WebUI.PostBackControl.prototype =
{
	initialize : function(options)
	{

		this._elementOnClick = null, //capture the element's onclick function

		this.element = $(options.ID);
		Prado.Registry.set(options.ID, this);
		if(this.element)
		{
			// Issue 181
		    this.element.stopObserving();
			if(this.onInit)
				this.onInit(options);
		}
	},

	onInit : function(options)
	{
		if(typeof(this.element.onclick)=="function")
		{
			this._elementOnClick = this.element.onclick.bind(this.element);
			this.element.onclick = null;
		}
		Event.observe(this.element, "click", this.elementClicked.bindEvent(this,options));
	},

	elementClicked : function(event, options)
	{
		var src = Event.element(event);
		var doPostBack = true;
		var onclicked = null;

		if(this._elementOnClick)
		{
			var onclicked = this._elementOnClick(event);
			if(typeof(onclicked) == "boolean")
				doPostBack = onclicked;
		}
		if(doPostBack && !Prado.Element.isDisabled(src))
			this.onPostBack(event,options);
		if(typeof(onclicked) == "boolean" && !onclicked)
			Event.stop(event);
	},

	onPostBack : function(event, options)
	{
		Prado.PostBack(event,options);
	}
};

Prado.WebUI.TButton = Class.extend(Prado.WebUI.PostBackControl);
Prado.WebUI.TLinkButton = Class.extend(Prado.WebUI.PostBackControl);
Prado.WebUI.TCheckBox = Class.extend(Prado.WebUI.PostBackControl);
Prado.WebUI.TBulletedList = Class.extend(Prado.WebUI.PostBackControl);
Prado.WebUI.TImageMap = Class.extend(Prado.WebUI.PostBackControl);

/**
 * TImageButton client-side behaviour. With validation, Firefox needs
 * to capture the x,y point of the clicked image in hidden form fields.
 */
Prado.WebUI.TImageButton = Class.extend(Prado.WebUI.PostBackControl);
Object.extend(Prado.WebUI.TImageButton.prototype,
{
	/**
	 * Override parent onPostBack function, tried to add hidden forms
	 * inputs to capture x,y clicked point.
	 */
	onPostBack : function(event, options)
	{
		this.addXYInput(event,options);
		Prado.PostBack(event, options);
		this.removeXYInput(event,options);
	},

	/**
	 * Add hidden inputs to capture the x,y point clicked on the image.
	 * @param event DOM click event.
	 * @param array image button options.
	 */
	addXYInput : function(event,options)
	{
		var imagePos = this.element.cumulativeOffset();
		var clickedPos = [event.clientX, event.clientY];
		var x = clickedPos[0]-imagePos[0]+1;
		var y = clickedPos[1]-imagePos[1]+1;
		x = x < 0 ? 0 : x;
		y = y < 0 ? 0 : y;
		var id = options['EventTarget'];
		var x_input = $(id+"_x");
		var y_input = $(id+"_y");
		if(x_input)
		{
			x_input.value = x;
		}
		else
		{
			x_input = INPUT({type:'hidden',name:id+'_x','id':id+'_x',value:x});
			this.element.parentNode.appendChild(x_input);
		}
		if(y_input)
		{
			y_input.value = y;
		}
		else
		{
			y_input = INPUT({type:'hidden',name:id+'_y','id':id+'_y',value:y});
			this.element.parentNode.appendChild(y_input);
		}
	},

	/**
	 * Remove hidden inputs for x,y-click capturing
	 * @param event DOM click event.
	 * @param array image button options.
	 */
	removeXYInput : function(event,options)
	{
		var id = options['EventTarget'];
		this.element.parentNode.removeChild($(id+"_x"));
		this.element.parentNode.removeChild($(id+"_y"));
	}
});

/**
 * Radio button, only initialize if not already checked.
 */
Prado.WebUI.TRadioButton = Class.extend(Prado.WebUI.PostBackControl);
Prado.WebUI.TRadioButton.prototype.onRadioButtonInitialize = Prado.WebUI.TRadioButton.prototype.initialize;
Object.extend(Prado.WebUI.TRadioButton.prototype,
{
	initialize : function(options)
	{
		this.element = $(options['ID']);
		if(this.element)
		{
			if(!this.element.checked)
				this.onRadioButtonInitialize(options);
		}
	}
});

Prado.WebUI.TTextBox = Class.extend(Prado.WebUI.PostBackControl,
{
	onInit : function(options)
	{
		this.options=options;
		if(options['TextMode'] != 'MultiLine')
			Event.observe(this.element, "keydown", this.handleReturnKey.bind(this));
		if(this.options['AutoPostBack']==true)
			Event.observe(this.element, "change", Prado.PostBack.bindEvent(this,options));
	},

	handleReturnKey : function(e)
	{
		 if(Event.keyCode(e) == Event.KEY_RETURN)
        {
			var target = Event.element(e);
			if(target)
			{
				if(this.options['AutoPostBack']==true)
				{
					Event.fireEvent(target, "change");
					Event.stop(e);
				}
				else
				{
					if(this.options['CausesValidation'] && typeof(Prado.Validation) != "undefined")
					{
						if(!Prado.Validation.validate(this.options['FormID'], this.options['ValidationGroup'], $(this.options['ID'])))
							return Event.stop(e);
					}
				}
			}
		}
	}
});

Prado.WebUI.TListControl = Class.extend(Prado.WebUI.PostBackControl,
{
	onInit : function(options)
	{
		Event.observe(this.element, "change", Prado.PostBack.bindEvent(this,options));
	}
});

Prado.WebUI.TListBox = Class.extend(Prado.WebUI.TListControl);
Prado.WebUI.TDropDownList = Class.extend(Prado.WebUI.TListControl);

Prado.WebUI.DefaultButton = Class.create();
Prado.WebUI.DefaultButton.prototype =
{
	initialize : function(options)
	{
        // Issue 181
		$(options['Panel']).stopObserving();
		this.options = options;
		this._event = this.triggerEvent.bindEvent(this);
		Event.observe(options['Panel'], 'keydown', this._event);
	},

	triggerEvent : function(ev, target)
	{
		var enterPressed = Event.keyCode(ev) == Event.KEY_RETURN;
		var isTextArea = Event.element(ev).tagName.toLowerCase() == "textarea";
		if(enterPressed && !isTextArea)
		{
			var defaultButton = $(this.options['Target']);
			if(defaultButton)
			{
				this.triggered = true;
				$('PRADO_POSTBACK_TARGET').value = this.options.EventTarget;
				Event.fireEvent(defaultButton, this.options['Event']);
				Event.stop(ev);
			}
		}
	}
};

Prado.WebUI.TTextHighlighter=Class.create();
Prado.WebUI.TTextHighlighter.prototype=
{
	initialize:function(id)
	{
		if(!window.clipboardData) return;
		var options =
		{
			href : 'javascript:;/'+'/copy code to clipboard',
			onclick : 'Prado.WebUI.TTextHighlighter.copy(this)',
			onmouseover : 'Prado.WebUI.TTextHighlighter.hover(this)',
			onmouseout : 'Prado.WebUI.TTextHighlighter.out(this)'
		}
		var div = DIV({className:'copycode'}, A(options, 'Copy Code'));
		document.write(DIV(null,div).innerHTML);
	}
};

Object.extend(Prado.WebUI.TTextHighlighter,
{
	copy : function(obj)
	{
		var parent = obj.parentNode.parentNode.parentNode;
		var text = '';
		for(var i = 0; i < parent.childNodes.length; i++)
		{
			var node = parent.childNodes[i];
			if(node.innerText)
				text += node.innerText == 'Copy Code' ? '' : node.innerText;
			else
				text += node.nodeValue;
		}
		if(text.length > 0)
			window.clipboardData.setData("Text", text);
	},

	hover : function(obj)
	{
		obj.parentNode.className = "copycode copycode_hover";
	},

	out : function(obj)
	{
		obj.parentNode.className = "copycode";
	}
});

Prado.WebUI.TCheckBoxList = Base.extend(
{
	constructor : function(options)
	{
		Prado.Registry.set(options.ListID, this);
		for(var i = 0; i<options.ItemCount; i++)
		{
			var checkBoxOptions = Object.extend(
			{
				ID : options.ListID+"_c"+i,
				EventTarget : options.ListName+"$c"+i
			}, options);
			new Prado.WebUI.TCheckBox(checkBoxOptions);
		}
	}
});

Prado.WebUI.TRadioButtonList = Base.extend(
{
	constructor : function(options)
	{
		Prado.Registry.set(options.ListID, this);
		for(var i = 0; i<options.ItemCount; i++)
		{
			var radioButtonOptions = Object.extend(
			{
				ID : options.ListID+"_c"+i,
				EventTarget : options.ListName+"$c"+i
			}, options);
			new Prado.WebUI.TRadioButton(radioButtonOptions);
		}
	}
});
<commit_msg>fix for #319<commit_after>Prado.WebUI = Class.create();

Prado.WebUI.PostBackControl = Class.create();

Prado.WebUI.PostBackControl.prototype =
{
	initialize : function(options)
	{

		this._elementOnClick = null, //capture the element's onclick function

		this.element = $(options.ID);
		Prado.Registry.set(options.ID, this);
		if(this.element)
		{
			// Issue 181
		    this.element.stopObserving();
			if(this.onInit)
				this.onInit(options);
		}
	},

	onInit : function(options)
	{
		if(typeof(this.element.onclick)=="function")
		{
			this._elementOnClick = this.element.onclick.bind(this.element);
			this.element.onclick = null;
		}
		Event.observe(this.element, "click", this.elementClicked.bindEvent(this,options));
	},

	elementClicked : function(event, options)
	{
		var src = Event.element(event);
		var doPostBack = true;
		var onclicked = null;

		if(this._elementOnClick)
		{
			var onclicked = this._elementOnClick(event);
			if(typeof(onclicked) == "boolean")
				doPostBack = onclicked;
		}
		if(doPostBack && !Prado.Element.isDisabled(src))
			this.onPostBack(event,options);
		if(typeof(onclicked) == "boolean" && !onclicked)
			Event.stop(event);
	},

	onPostBack : function(event, options)
	{
		Prado.PostBack(event,options);
	}
};

Prado.WebUI.TButton = Class.extend(Prado.WebUI.PostBackControl);
Prado.WebUI.TLinkButton = Class.extend(Prado.WebUI.PostBackControl);
Prado.WebUI.TCheckBox = Class.extend(Prado.WebUI.PostBackControl);
Prado.WebUI.TBulletedList = Class.extend(Prado.WebUI.PostBackControl);
Prado.WebUI.TImageMap = Class.extend(Prado.WebUI.PostBackControl);

/**
 * TImageButton client-side behaviour. With validation, Firefox needs
 * to capture the x,y point of the clicked image in hidden form fields.
 */
Prado.WebUI.TImageButton = Class.extend(Prado.WebUI.PostBackControl);
Object.extend(Prado.WebUI.TImageButton.prototype,
{
	/**
	 * Override parent onPostBack function, tried to add hidden forms
	 * inputs to capture x,y clicked point.
	 */
	onPostBack : function(event, options)
	{
		this.addXYInput(event,options);
		Prado.PostBack(event, options);
		this.removeXYInput(event,options);
	},

	/**
	 * Add hidden inputs to capture the x,y point clicked on the image.
	 * @param event DOM click event.
	 * @param array image button options.
	 */
	addXYInput : function(event,options)
	{
		var imagePos = this.element.cumulativeOffset();
		var clickedPos = [event.clientX, event.clientY];
		var x = clickedPos[0]-imagePos[0]+1;
		var y = clickedPos[1]-imagePos[1]+1;
		x = x < 0 ? 0 : x;
		y = y < 0 ? 0 : y;
		var id = options['EventTarget'];
		var x_input = $(id+"_x");
		var y_input = $(id+"_y");
		if(x_input)
		{
			x_input.value = x;
		}
		else
		{
			x_input = INPUT({type:'hidden',name:id+'_x','id':id+'_x',value:x});
			this.element.parentNode.appendChild(x_input);
		}
		if(y_input)
		{
			y_input.value = y;
		}
		else
		{
			y_input = INPUT({type:'hidden',name:id+'_y','id':id+'_y',value:y});
			this.element.parentNode.appendChild(y_input);
		}
	},

	/**
	 * Remove hidden inputs for x,y-click capturing
	 * @param event DOM click event.
	 * @param array image button options.
	 */
	removeXYInput : function(event,options)
	{
		var id = options['EventTarget'];
		this.element.parentNode.removeChild($(id+"_x"));
		this.element.parentNode.removeChild($(id+"_y"));
	}
});

/**
 * Radio button, only initialize if not already checked.
 */
Prado.WebUI.TRadioButton = Class.extend(Prado.WebUI.PostBackControl);
Prado.WebUI.TRadioButton.prototype.onRadioButtonInitialize = Prado.WebUI.TRadioButton.prototype.initialize;
Object.extend(Prado.WebUI.TRadioButton.prototype,
{
	initialize : function(options)
	{
		this.element = $(options['ID']);
		if(this.element)
		{
			if(!this.element.checked)
				this.onRadioButtonInitialize(options);
		}
	}
});

Prado.WebUI.TTextBox = Class.extend(Prado.WebUI.PostBackControl,
{
	onInit : function(options)
	{
		this.options=options;
		if(options['TextMode'] != 'MultiLine')
			Event.observe(this.element, "keydown", this.handleReturnKey.bind(this));
		if(this.options['AutoPostBack']==true)
			Event.observe(this.element, "change", Prado.PostBack.bindEvent(this,options));
	},

	handleReturnKey : function(e)
	{
		 if(Event.keyCode(e) == Event.KEY_RETURN)
        {
			var target = Event.element(e);
			if(target)
			{
				if(this.options['AutoPostBack']==true)
				{
					Event.fireEvent(target, "change");
					Event.stop(e);
				}
				else
				{
					if(this.options['CausesValidation'] && typeof(Prado.Validation) != "undefined")
					{
						if(!Prado.Validation.validate(this.options['FormID'], this.options['ValidationGroup'], $(this.options['ID'])))
							return Event.stop(e);
					}
				}
			}
		}
	}
});

Prado.WebUI.TListControl = Class.extend(Prado.WebUI.PostBackControl,
{
	onInit : function(options)
	{
		Event.observe(this.element, "change", Prado.PostBack.bindEvent(this,options));
	}
});

Prado.WebUI.TListBox = Class.extend(Prado.WebUI.TListControl);
Prado.WebUI.TDropDownList = Class.extend(Prado.WebUI.TListControl);

Prado.WebUI.DefaultButton = Class.create();
Prado.WebUI.DefaultButton.prototype =
{
	initialize : function(options)
	{
        // Issue 181
		$(options['Panel']).stopObserving();
		this.options = options;
		this._event = this.triggerEvent.bindEvent(this);
		Event.observe(options['Panel'], 'keydown', this._event);
	},

	triggerEvent : function(ev, target)
	{
		var enterPressed = Event.keyCode(ev) == Event.KEY_RETURN;
		var isTextArea = Event.element(ev).tagName.toLowerCase() == "textarea";
		if(enterPressed && !isTextArea)
		{
			var defaultButton = $(this.options['Target']);
			if(defaultButton)
			{
				this.triggered = true;
				Event.fireEvent(defaultButton, this.options['Event']);
				Event.stop(ev);
			}
		}
	}
};

Prado.WebUI.TTextHighlighter=Class.create();
Prado.WebUI.TTextHighlighter.prototype=
{
	initialize:function(id)
	{
		if(!window.clipboardData) return;
		var options =
		{
			href : 'javascript:;/'+'/copy code to clipboard',
			onclick : 'Prado.WebUI.TTextHighlighter.copy(this)',
			onmouseover : 'Prado.WebUI.TTextHighlighter.hover(this)',
			onmouseout : 'Prado.WebUI.TTextHighlighter.out(this)'
		}
		var div = DIV({className:'copycode'}, A(options, 'Copy Code'));
		document.write(DIV(null,div).innerHTML);
	}
};

Object.extend(Prado.WebUI.TTextHighlighter,
{
	copy : function(obj)
	{
		var parent = obj.parentNode.parentNode.parentNode;
		var text = '';
		for(var i = 0; i < parent.childNodes.length; i++)
		{
			var node = parent.childNodes[i];
			if(node.innerText)
				text += node.innerText == 'Copy Code' ? '' : node.innerText;
			else
				text += node.nodeValue;
		}
		if(text.length > 0)
			window.clipboardData.setData("Text", text);
	},

	hover : function(obj)
	{
		obj.parentNode.className = "copycode copycode_hover";
	},

	out : function(obj)
	{
		obj.parentNode.className = "copycode";
	}
});

Prado.WebUI.TCheckBoxList = Base.extend(
{
	constructor : function(options)
	{
		Prado.Registry.set(options.ListID, this);
		for(var i = 0; i<options.ItemCount; i++)
		{
			var checkBoxOptions = Object.extend(
			{
				ID : options.ListID+"_c"+i,
				EventTarget : options.ListName+"$c"+i
			}, options);
			new Prado.WebUI.TCheckBox(checkBoxOptions);
		}
	}
});

Prado.WebUI.TRadioButtonList = Base.extend(
{
	constructor : function(options)
	{
		Prado.Registry.set(options.ListID, this);
		for(var i = 0; i<options.ItemCount; i++)
		{
			var radioButtonOptions = Object.extend(
			{
				ID : options.ListID+"_c"+i,
				EventTarget : options.ListName+"$c"+i
			}, options);
			new Prado.WebUI.TRadioButton(radioButtonOptions);
		}
	}
});
<|endoftext|>


<commit_before>var config = {}

config.connection = {}
config.records = {}

config.connection.sfuser = '';
config.connection.sfpass = '';
config.connection.clientId = '';
config.connection.clientSecret = '';
config.connection.redirectUri = 'http://localhost:3000/oauth/_callback';

/*
  Some tests for the tooling API are difficult to automate as there is no
  way to obtain IDs that are needed for the calls. The best approach
  is to find the ids for the following below so that the test can run
  successfully. If left null, the test will be skipped.
*/

// to run tests for getCustomField, enter the ID of a custom field from a custom object
config.records.custsomFieldId;
// to run tests for getApexLog, enter the ID of a log from the Debug Logs page
config.records.apexLogId;

module.exports = config;<commit_msg>added parameters for timeout and show debug<commit_after>var config = {}

config.connection = {}
config.records = {}

// mocha timeout for the entire suite
config.timeout = 10000;
// displays results from salesforce api calls
config.debug = false;

config.connection.sfuser = '';
config.connection.sfpass = '';
config.connection.clientId = '';
config.connection.clientSecret = '';
config.connection.redirectUri = 'http://localhost:3000/oauth/_callback';

/*
  Some tests for the tooling API are difficult to automate as there is no
  way to obtain IDs that are needed for the calls. The best approach
  is to find the ids for the following below so that the test can run
  successfully. If left null, the test will be skipped.
*/

// to run tests for getCustomField, enter the ID of a custom field from a custom object
config.records.custsomFieldId;
// to run tests for getApexLog, enter the ID of a log from the Debug Logs page
config.records.apexLogId;

module.exports = config;<|endoftext|>