<commit_before>import { remote } from 'webdriverio';
import Mugshot from 'mugshot';
import WebdriverIOAdapter from 'mugshot-webdriverio';
import path from 'path';
import fs from 'fs';
import { expect } from 'chai';
import {
  runnerAfter,
  runnerBefore,
  runnerBeforeEach,
  runnerIt,
  runnerDescribe
} from './test-runner.js';

const { BROWSER = 'chrome', SELENIUM_HOST = 'selenium' } = process.env;

let suiteNesting = 0;

// These will hold root suite level instances. Since most, if not all test
// runners run tests inside of a suite sequentially and since we only set
// up the browser once per root test suite, these should be "thread safe".
let rootSuiteBrowser, rootSuiteMugshot;

/**
 * Run your acceptance tests in a fresh Selenium session.
 *
 * Nested calls will preserve the root session.
 *
 * Tests and hooks will receive the browser instance.
 *
 * @param {String} name
 * @param {() => void} definition
 */
export function describe(name, definition) {
  suiteNesting++;

  runnerDescribe(name, function() {
    // We only want to set up hooks once - for the root suite.
    suiteNesting === 1 && setupHooks();

    definition();
  });

  suiteNesting--;
}

/**
 * @param {(Webdriver) => Promise} definition
 */
export function beforeEach(definition) {
  runnerBeforeEach(function() {
    return definition(rootSuiteBrowser);
  });
}

/**
 * Run a test with optional coverage report.
 *
 * @param {String} name
 * @param {(Webdriver) => Promise|undefined} definition
 */
export function it(name, definition) {
  runnerIt(name, function() {
    const promise = Promise.resolve(definition(rootSuiteBrowser));
    const testName = this.test.fullTitle();

    if (process.env.NODE_ENV !== 'tests') {
      return promise;
    }

    return promise.then(() => collectCoverage(testName));
  });
}

/**
 * Perform a visual test alongside a normal test.
 *
 * The visual test will not be performed if the test in `definition` fails.
 *
 * @param {String} name The name of the test. The screenshot will be taken under
 *   the full test name (including any parent suite's name(s)).
 * @param {(Webdriver) => Promise|undefined} definition
 * @param {CSSSelector} selector
 */
export function vit(name, definition, selector = '.todoapp') {
  runnerIt(name, function() {
    const promise = Promise.resolve(definition(rootSuiteBrowser));
    const testName = getSafeFilename(this.test.fullTitle());

    // Don't want to make debugging tests more noisy than it needs to be.
    if (process.env.DEBUG) {
      promise.then(() => checkForVisualChanges(testName, selector));
    }

    if (process.env.NODE_ENV === 'tests') {
      promise.then(() => collectCoverage(testName));
    }

    return promise;
  });
}

/**
 * @param {String} name
 * @param {CSSSelector} selector
 *
 * @returns {Promise<undefined>}
 */
function checkForVisualChanges(name, selector = 'body > *') {
  return new Promise((resolve, reject) => {
    rootSuiteMugshot.test({ name, selector }, (err, result) => {
      if (err) {
        return reject(err);
      }

      expect(result.isEqual, 'Visual changes detected. Check screenshots').to.be.true;

      return resolve();
    });
  });
}

/**
 * @param {string} testName
 *
 * @returns {Promise<undefined>}
 */
function collectCoverage(testName) {
  const safeTestName = getSafeFilename(testName);

  return rootSuiteBrowser.execute(function getCoverage() {
    return JSON.stringify(window.__coverage__);
  }).then(({ value: coverage }) => {
    fs.writeFileSync(
      path.join(__dirname, 'results', 'coverage', `${BROWSER}_${safeTestName}.json`),
      coverage
    );
  });
}

/**
 * Turn the given file name into something that's safe to save on the FS.
 *
 * @param {String} fileName
 *
 * @returns {String}
 */
function getSafeFilename(fileName) {
  return fileName
    .replace(/\//g, '_')
    .replace(/ /g, '_')
    .toLowerCase();
}

function setupHooks() {
  runnerBefore(function connectToSelenium() {
    this.timeout(10 * 1000);

    const options = {
      host: SELENIUM_HOST,
      desiredCapabilities: { browserName: BROWSER }
    };

    rootSuiteBrowser = remote(options).init();

    const adapter = new WebdriverIOAdapter(rootSuiteBrowser);

    rootSuiteMugshot = new Mugshot(adapter, {
      rootDirectory: path.join(__dirname, 'screenshots', BROWSER),
      acceptFirstBaseline: false
    });

    return rootSuiteBrowser;
  });

  runnerAfter(function endSession() {
    return rootSuiteBrowser.end();
  });

  runnerBeforeEach(function waitForRender() {
    return rootSuiteBrowser.url('http://app:3000/')
    // Wait for webpack to build the app.
      .then(() => rootSuiteBrowser.waitForVisible('.todoapp', 5 * 1000));
  });
}
<commit_msg>Take screenshots when NOT debugging<commit_after>import { remote } from 'webdriverio';
import Mugshot from 'mugshot';
import WebdriverIOAdapter from 'mugshot-webdriverio';
import path from 'path';
import fs from 'fs';
import { expect } from 'chai';
import {
  runnerAfter,
  runnerBefore,
  runnerBeforeEach,
  runnerIt,
  runnerDescribe
} from './test-runner.js';

const { BROWSER = 'chrome', SELENIUM_HOST = 'selenium' } = process.env;

let suiteNesting = 0;

// These will hold root suite level instances. Since most, if not all test
// runners run tests inside of a suite sequentially and since we only set
// up the browser once per root test suite, these should be "thread safe".
let rootSuiteBrowser, rootSuiteMugshot;

/**
 * Run your acceptance tests in a fresh Selenium session.
 *
 * Nested calls will preserve the root session.
 *
 * Tests and hooks will receive the browser instance.
 *
 * @param {String} name
 * @param {() => void} definition
 */
export function describe(name, definition) {
  suiteNesting++;

  runnerDescribe(name, function() {
    // We only want to set up hooks once - for the root suite.
    suiteNesting === 1 && setupHooks();

    definition();
  });

  suiteNesting--;
}

/**
 * @param {(Webdriver) => Promise} definition
 */
export function beforeEach(definition) {
  runnerBeforeEach(function() {
    return definition(rootSuiteBrowser);
  });
}

/**
 * Run a test with optional coverage report.
 *
 * @param {String} name
 * @param {(Webdriver) => Promise|undefined} definition
 */
export function it(name, definition) {
  runnerIt(name, function() {
    const promise = Promise.resolve(definition(rootSuiteBrowser));
    const testName = this.test.fullTitle();

    if (process.env.NODE_ENV !== 'tests') {
      return promise;
    }

    return promise.then(() => collectCoverage(testName));
  });
}

/**
 * Perform a visual test alongside a normal test.
 *
 * The visual test will not be performed if the test in `definition` fails.
 *
 * @param {String} name The name of the test. The screenshot will be taken under
 *   the full test name (including any parent suite's name(s)).
 * @param {(Webdriver) => Promise|undefined} definition
 * @param {CSSSelector} selector
 */
export function vit(name, definition, selector = '.todoapp') {
  runnerIt(name, function() {
    const promise = Promise.resolve(definition(rootSuiteBrowser));
    const testName = getSafeFilename(this.test.fullTitle());

    // Don't want to make debugging tests more noisy than it needs to be.
    if (!process.env.DEBUG) {
      promise = promise.then(() => checkForVisualChanges(testName, selector));
    }

    if (process.env.NODE_ENV === 'tests') {
      promise.then(() => collectCoverage(testName));
    }

    return promise;
  });
}

/**
 * @param {String} name
 * @param {CSSSelector} selector
 *
 * @returns {Promise<undefined>}
 */
function checkForVisualChanges(name, selector = 'body > *') {
  return new Promise((resolve, reject) => {
    rootSuiteMugshot.test({ name, selector }, (err, result) => {
      if (err) {
        return reject(err);
      }

      expect(result.isEqual, 'Visual changes detected. Check screenshots').to.be.true;

      return resolve();
    });
  });
}

/**
 * @param {string} testName
 *
 * @returns {Promise<undefined>}
 */
function collectCoverage(testName) {
  const safeTestName = getSafeFilename(testName);

  return rootSuiteBrowser.execute(function getCoverage() {
    return JSON.stringify(window.__coverage__);
  }).then(({ value: coverage }) => {
    fs.writeFileSync(
      path.join(__dirname, 'results', 'coverage', `${BROWSER}_${safeTestName}.json`),
      coverage
    );
  });
}

/**
 * Turn the given file name into something that's safe to save on the FS.
 *
 * @param {String} fileName
 *
 * @returns {String}
 */
function getSafeFilename(fileName) {
  return fileName
    .replace(/\//g, '_')
    .replace(/ /g, '_')
    .toLowerCase();
}

function setupHooks() {
  runnerBefore(function connectToSelenium() {
    this.timeout(10 * 1000);

    const options = {
      host: SELENIUM_HOST,
      desiredCapabilities: { browserName: BROWSER }
    };

    rootSuiteBrowser = remote(options).init();

    const adapter = new WebdriverIOAdapter(rootSuiteBrowser);

    rootSuiteMugshot = new Mugshot(adapter, {
      rootDirectory: path.join(__dirname, 'screenshots', BROWSER),
      acceptFirstBaseline: false
    });

    return rootSuiteBrowser;
  });

  runnerAfter(function endSession() {
    return rootSuiteBrowser.end();
  });

  runnerBeforeEach(function waitForRender() {
    return rootSuiteBrowser.url('http://app:3000/')
    // Wait for webpack to build the app.
      .then(() => rootSuiteBrowser.waitForVisible('.todoapp', 5 * 1000));
  });
}
<|endoftext|>


<commit_before>//
// Copyright (c) 2017 DrSmugleaf
//

"use strict"
const BlackjackDeck = require("../../../../src/commands/minigames/blackjack/deck")
const BlackjackGame = require("../../../../src/commands/minigames/blackjack/game")
const BlackjackPlayer = require("../../../../src/commands/minigames/blackjack/player")

const expect = require("chai").expect

describe("Blackjack Game", function() {

  var game
  beforeEach(function() {
    game = new BlackjackGame({ dealerID: global.client.user.id, deck: "french", decks: 1 })
    game._reset()
  })

  afterEach(function() {
    game.removePlayer(global.client.user.id)
  })

  describe("constructor", function() {
    it("should create a new blackjack game", function() {
      expect(game.dealer).to.be.an.instanceof(BlackjackPlayer)
      expect(game.dealer.id).to.equal(global.client.user.id)
      expect(game.deck).to.be.an.instanceof(BlackjackDeck)
      expect(game.playerCount).to.equal(0)
      expect(game.timeLimit).to.be.a("number")
      expect(game.timeout).to.be.null
      expect(game.started).to.be.false
      expect(game.listenerCount("action")).to.be.above(0)
    })
  })

  describe("add player", function() {
    it("should add a player to the game and return its BlackjackPlayer instance", function() {
      const player = game.addPlayer(global.client.user.id)
      expect(game.hasPlayer(global.client.user.id)).to.be.true
      expect(game.playerCount).to.equal(1)
      expect(player).to.be.an.instanceof(BlackjackPlayer)
    })
  })

  describe("get player", function() {
    it("should return undefined if the player doesn't exist", function() {
      expect(game.getPlayer(global.client.user.id)).to.be.undefined
    })
    it("should return the player's BlackjackPlayer instance if it does exist", function() {
      game.addPlayer(global.client.user.id)
      expect(game.getPlayer(global.client.user.id)).to.be.an.instanceof(BlackjackPlayer)
    })
  })

  describe("has player", function() {
    it("should return false if the player doesn't exist", function() {
      expect(game.hasPlayer(global.client.user.id)).to.be.false
    })
    it("should return true if the player does exist", function() {
      game.addPlayer(global.client.user.id)
      expect(game.hasPlayer(global.client.user.id)).to.be.true
    })
  })

  describe("remove player", function() {
    it("should remove the player from the game", function() {
      game.addPlayer(global.client.user.id)
      game.removePlayer(global.client.user.id)
      expect(game.hasPlayer(global.client.user.id)).to.be.false
    })
  })

  describe("process turn, 1 player", function() {

    var player
    beforeEach(function() {
      if(!game.hasPlayer(global.client.user.id)) game.addPlayer(global.client.user.id)
      player = game.getPlayer(global.client.user.id)
    })

    afterEach(function() {
      game._reset()
    })

    describe("action: hit", function() {
      it("should have started be false, instant turn ending", function() {
        player.action = "hit"
        expect(game.started).to.be.false
      })
      it("should give 1 card to the player", function() {
        player.action = "hit"
        expect(player.hand.cards.length).to.equal(1)
      })
    })

    describe("action: stand", function() {
      it("should have started be false, instant turn ending", function() {
        player.action = "stand"
        expect(game.started).to.be.false
      })
      it("should end the game with a player win, loss or tie", function() {
        var win, lose, tie
        game
          .on("win", () => win = true)
          .on("lose", () => lose = true)
          .on("tie", () => tie = true)

        player.action = "stand"
        expect(win || lose || tie).to.be.true
      })
      it("should give the dealer 1 card", function() {
        player.action = "stand"
        expect(game.dealer.hand.cards.length).to.equal(1)
      })
      it("should give the player 2 cards", function() {
        player.action = "stand"
        expect(player.hand.cards.length).to.equal(2)
      })
    })

    describe("action: none", function() {
      it("should have started be false, instant turn ending", function() {
        player.action = null
        game.processTurn()
        expect(game.started).to.be.false
      })
      it("should kick the player from the game", function() {
        player.action = null
        game.processTurn()
        expect(game.hasPlayer(global.client.user.id)).to.be.false
      })
    })
  })

  describe("process turn, 2 players", function() {

    var player1, player2
    beforeEach(function() {
      if(!game.hasPlayer(global.client.user.id)) game.addPlayer(global.client.user.id)
      if(!game.hasPlayer(global.client.options.owner)) game.addPlayer(global.client.options.owner)
      player1 = game.getPlayer(global.client.user.id)
      player2 = game.getPlayer(global.client.options.owner)
    })

    afterEach(function() {
      game._reset()
    })

    describe("1 player actions", function() {
      describe("action: hit", function() {

        beforeEach(function() {
          player1.action = "hit"
        })

        it("should start the turn", function() {
          expect(game.started).to.be.true
          expect(game.timeout).to.be.ok
        })
        it("shouldn't allow the player's action to be changed afterwards", function() {
          player1.action = "stand"
          expect(player1.action).to.equal("hit")
        })
        it("shouldn't give cards to the player", function() {
          expect(player1.hand.cards.length).to.equal(0)
        })
      })

      describe("action: stand", function() {

        beforeEach(function() {
          player1.action = "stand"
        })

        it("should start the turn", function() {
          expect(game.started).to.be.true
          expect(game.timeout).to.be.ok
        })
        it("shouldn't allow the player's action to be changed afterwards", function() {
          player1.action = "hit"
          expect(player1.action).to.equal("stand")
        })
        it("shouldn't give cards to the player", function() {
          expect(player1.hand.cards.length).to.equal(0)
        })
      })

      describe("action: none", function() {
        it("shouldn't start the turn", function() {
          expect(player1.action).to.be.null
          expect(game.started).to.be.false
          expect(game.timeout).to.not.be.ok
        })
      })
    })

    describe("2 player actions", function() {
      describe("actions: hit, none", function() {

        beforeEach(function() {
          player1.action = "hit"
          player2.action = null
        })

        it("should start the turn", function() {
          expect(game.started).to.be.true
          expect(game.timeout).to.be.ok
        })
        it("shouldn't allow the first player's action to be changed afterwards", function() {
          player1.action = "stand"
          expect(player1.action).to.equal("hit")
        })
        it("shouldn't give cards to the first player", function() {
          expect(player1.hand.cards.length).to.equal(0)
        })

        describe("after turn time limit", function() {

          beforeEach(function() {
            game.processTurn()
          })

          it("should give 1 card to the first player", function() {
            expect(player1.hand.cards.length).to.equal(1)
          })
          it("should kick the second player from the game", function() {
            expect(game.getPlayer(global.client.options.owner)).to.be.undefined
          })
        })
      })

      describe("actions: hit, hit", function() {

        beforeEach(function() {
          player1.action = "hit"
          player2.action = "hit"
        })

        it("should end the turn", function() {
          expect(game.started).to.be.false
          expect(game.timeout).to.be.null
        })
        it("should give 1 card to each player", function() {
          expect(player1.hand.cards.length).to.equal(1)
          expect(player2.hand.cards.length).to.equal(1)
        })
        it("should make both player's actions be null", function() {
          expect(player1.action).to.be.null
          expect(player2.action).to.be.null
        })
        it("should allow both players actions to be changed afterwards, turn is over", function() {
          player1.action = "stand"
          expect(player1.action).to.equal("stand")
          player1.action = null
          player2.action = "stand"
          expect(player2.action).to.equal("stand")
          player2.action = null
        })
        it("should not end the game", function() {
          var win, lose, tie
          game
            .on("win", () => win = true)
            .on("lose", () => lose = true)
            .on("tie", () => tie = true)

          player1.action = "hit"
          player2.action = "hit"
          expect(win || lose || tie).to.be.undefined
        })
      })

      describe("actions: stand, none", function() {

        beforeEach(function() {
          player1.action = "stand"
          player2.action = null
        })

        it("should start the turn", function() {
          expect(game.started).to.be.true
          expect(game.timeout).to.be.ok
        })
        it("shouldn't allow the first player's action to be changed afterwards", function() {
          player1.action = "hit"
          expect(player1.action).to.equal("stand")
        })
        it("shouldn't give cards to the dealer", function() {
          expect(game.dealer.hand.cards.length).to.equal(0)
        })

        describe("after turn time limit", function() {
          it("should end the game with a first player win, loss or tie", function() {
            var win, lose, tie
            game
              .on("win", () => win = true)
              .on("lose", () => lose = true)
              .on("tie", () => tie = true)

            game.processTurn()
            expect(win || lose || tie).to.be.true
          })
          it("should kick the second player from the game", function() {
            game.processTurn()
            expect(game.getPlayer(global.client.options.owner)).to.be.undefined
          })

          describe("after round end", function() {

            beforeEach(function() {
              game.processTurn()
            })

            it("should give 1 card to the dealer", function() {
              expect(game.dealer.hand.cards.length).to.equal(1)
            })
            it("should give 2 cards to the first player", function() {
              expect(player1.hand.cards.length).to.equal(2)
            })
          })
        })
      })

      describe("actions: stand, stand", function() {

        beforeEach(function() {
          player1.action = "stand"
          player2.action = "stand"
        })

        it("should end the turn", function() {
          expect(game.started).to.be.false
          expect(game.timeout).to.be.null
        })
        it("should make both player's actions be null", function() {
          expect(player1.action).to.be.null
          expect(player2.action).to.be.null
        })
        it("should allow both player actions to be changed afterwards, turn is over", function() {
          player1.action = "hit"
          expect(player1.action).to.equal("hit")
          player1.action = null
          player2.action = "hit"
          expect(player2.action).to.equal("hit")
          player2.action = null
        })
        it("should end the game with both players winning, losing or tying", function() {
          var player1end
          var player2end
          game
            .on("win", (player) => player.id === player1.id ? player1end = true : player2end = true)
            .on("lose", (player) => player.id === player1.id ? player1end = true : player2end = true)
            .on("tie", (player) => player.id === player1.id ? player1end = true : player2end = true)

          player1.action = "stand"
          player2.action = "stand"
          expect(player1end && player2end).to.be.true
        })

        describe("after round end", function() {
          it("should give 1 card to the dealer", function() {
            expect(game.dealer.hand.cards.length).to.equal(1)
          })
          it("should give 2 cards to both players", function() {
            expect(player1.hand.cards.length).to.equal(2)
            expect(player2.hand.cards.length).to.equal(2)
          })
        })
      })

      describe("actions: hit, stand", function() {

        beforeEach(function() {
          player1.action = "hit"
          player2.action = "stand"
        })

        it("should end the turn", function() {
          expect(game.started).to.be.false
          expect(game.timeout).to.be.null
        })
        it("should make the first player's action be null", function() {
          expect(player1.action).to.be.null
        })
        it("should keep the second player's action as 'stand'", function() {
          expect(player2.action).to.equal("stand")
        })
        it("should allow the first player's action to be changed afterwards", function() {
          player2.action = null // otherwise the game ends
          player1.action = "stand"
          expect(player1.action).to.equal("stand")
          player1.action = null
        })
        it("shouldn't allow the second player's action to be changed afterwards: 'stand'", function() {
          player2.action = "hit"
          expect(player2.action).to.equal("stand")
          player2.action = null
        })
      })
    })
  })
})
<commit_msg>Fixed Blackjack unit tests<commit_after>//
// Copyright (c) 2017 DrSmugleaf
//

"use strict"
const BlackjackDeck = require("../../../../src/commands/minigames/blackjack/deck")
const BlackjackGame = require("../../../../src/commands/minigames/blackjack/game")
const BlackjackPlayer = require("../../../../src/commands/minigames/blackjack/player")

const expect = require("chai").expect

describe("Blackjack Game", function() {

  var game, player
  beforeEach(function() {
    game = new BlackjackGame({
      dealerID: global.client.user.id, deck: "french", decks: 1, player: global.client.user.id
    })
    game.start()
    player = game.getPlayer(global.client.user.id)
    game._reset()
  })

  afterEach(function() {
    game.removePlayer(global.client.user.id)
  })

  describe("constructor", function() {
    it("should create a new blackjack game", function() {
      expect(game.dealer).to.be.an.instanceof(BlackjackPlayer)
      expect(game.dealer.id).to.equal(global.client.user.id)
      expect(game.deck).to.be.an.instanceof(BlackjackDeck)
      expect(game.playerCount).to.equal(1)
      expect(game.timeLimit).to.be.a("number")
      expect(game.timeout).to.be.null
      expect(game.started).to.be.false
      expect(game.listenerCount("action")).to.be.above(0)
    })
  })

  describe("add player", function() {
    it("should add a player to the game and return its BlackjackPlayer instance", function() {
      const player = game.addPlayer(global.client.options.owner)
      expect(game.hasPlayer(global.client.user.id)).to.be.true
      expect(game.playerCount).to.equal(2)
      expect(player).to.be.an.instanceof(BlackjackPlayer)
    })
  })

  describe("get player", function() {
    it("should return the player's BlackjackPlayer instance if the player exists", function() {
      expect(game.getPlayer(global.client.user.id)).to.be.an.instanceof(BlackjackPlayer)
    })
    it("should return undefined if the player doesn't exist", function() {
      game.removePlayer(global.client.user.id)
      expect(game.getPlayer(global.client.user.id)).to.be.undefined
    })
  })

  describe("has player", function() {
    it("should return true if the player exists", function() {
      game.addPlayer(global.client.user.id)
      expect(game.hasPlayer(global.client.user.id)).to.be.true
    })
    it("should return false if the player doesn't exist", function() {
      game.removePlayer(global.client.user.id)
      expect(game.hasPlayer(global.client.user.id)).to.be.false
    })
  })

  describe("remove player", function() {
    it("should remove the player from the game", function() {
      game.removePlayer(global.client.user.id)
      expect(game.hasPlayer(global.client.user.id)).to.be.false
    })
  })

  describe("process turn, 1 player", function() {
    describe("action: hit", function() {
      it("should have started be false, instant turn ending", function() {
        player.action = "hit"
        expect(game.started).to.be.false
      })
      it("should give 1 card to the player", function() {
        player.action = "hit"
        expect(player.hand.cards.length).to.equal(1)
      })
    })

    describe("action: stand", function() {
      it("should have started be false, instant turn ending", function() {
        player.action = "stand"
        expect(game.started).to.be.false
      })
      it("should end the game with a player win, loss or tie", function() {
        var win, lose, tie
        game
          .on("win", () => win = true)
          .on("lose", () => lose = true)
          .on("tie", () => tie = true)

        player.action = "stand"
        expect(win || lose || tie).to.be.true
      })
      it("should give the dealer 1 card", function() {
        player.action = "stand"
        expect(game.dealer.hand.cards.length).to.equal(1)
      })
      it("should give the player 2 cards", function() {
        player.action = "stand"
        expect(player.hand.cards.length).to.equal(2)
      })
    })

    describe("action: none", function() {
      it("should have started be false, instant turn ending", function() {
        player.action = null
        game.processTurn()
        expect(game.started).to.be.false
      })
      it("should kick the player from the game", function() {
        player.action = null
        game.processTurn()
        expect(game.hasPlayer(global.client.user.id)).to.be.false
      })
    })
  })

  describe("process turn, 2 players", function() {

    var player1, player2
    beforeEach(function() {
      player1 = player
      if(!game.hasPlayer(global.client.options.owner)) game.addPlayer(global.client.options.owner)
      game.start()
      player2 = game.getPlayer(global.client.options.owner)
      game._reset()
    })

    afterEach(function() {
      game._reset()
    })

    describe("1 player actions", function() {
      describe("action: hit", function() {

        beforeEach(function() {
          player1.action = "hit"
        })

        it("should start the turn", function() {
          expect(game.started).to.be.true
          expect(game.timeout).to.be.ok
        })
        it("shouldn't allow the player's action to be changed afterwards", function() {
          player1.action = "stand"
          expect(player1.action).to.equal("hit")
        })
        it("shouldn't give cards to the player", function() {
          expect(player1.hand.cards.length).to.equal(0)
        })
      })

      describe("action: stand", function() {

        beforeEach(function() {
          player1.action = "stand"
        })

        it("should start the turn", function() {
          expect(game.started).to.be.true
          expect(game.timeout).to.be.ok
        })
        it("shouldn't allow the player's action to be changed afterwards", function() {
          player1.action = "hit"
          expect(player1.action).to.equal("stand")
        })
        it("shouldn't give cards to the player", function() {
          expect(player1.hand.cards.length).to.equal(0)
        })
      })

      describe("action: none", function() {
        it("shouldn't start the turn", function() {
          expect(player1.action).to.be.null
          expect(game.started).to.be.false
          expect(game.timeout).to.not.be.ok
        })
      })
    })

    describe("2 player actions", function() {
      describe("actions: hit, none", function() {

        beforeEach(function() {
          player1.action = "hit"
          player2.action = null
        })

        it("should start the turn", function() {
          expect(game.started).to.be.true
          expect(game.timeout).to.be.ok
        })
        it("shouldn't allow the first player's action to be changed afterwards", function() {
          player1.action = "stand"
          expect(player1.action).to.equal("hit")
        })
        it("shouldn't give cards to the first player", function() {
          expect(player1.hand.cards.length).to.equal(0)
        })

        describe("after turn time limit", function() {

          beforeEach(function() {
            game.processTurn()
          })

          it("should give 1 card to the first player", function() {
            expect(player1.hand.cards.length).to.equal(1)
          })
          it("should kick the second player from the game", function() {
            expect(game.getPlayer(global.client.options.owner)).to.be.undefined
          })
        })
      })

      describe("actions: hit, hit", function() {

        beforeEach(function() {
          player1.action = "hit"
          player2.action = "hit"
        })

        it("should end the turn", function() {
          expect(game.started).to.be.false
          expect(game.timeout).to.be.null
        })
        it("should give 1 card to each player", function() {
          expect(player1.hand.cards.length).to.equal(1)
          expect(player2.hand.cards.length).to.equal(1)
        })
        it("should make both player's actions be null", function() {
          expect(player1.action).to.be.null
          expect(player2.action).to.be.null
        })
        it("should allow both players actions to be changed afterwards, turn is over", function() {
          player1.action = "stand"
          expect(player1.action).to.equal("stand")
          player1.action = null
          player2.action = "stand"
          expect(player2.action).to.equal("stand")
          player2.action = null
        })
        it("should not end the game", function() {
          var win, lose, tie
          game
            .on("win", () => win = true)
            .on("lose", () => lose = true)
            .on("tie", () => tie = true)

          player1.action = "hit"
          player2.action = "hit"
          expect(win || lose || tie).to.be.undefined
        })
      })

      describe("actions: stand, none", function() {

        beforeEach(function() {
          player1.action = "stand"
          player2.action = null
        })

        it("should start the turn", function() {
          expect(game.started).to.be.true
          expect(game.timeout).to.be.ok
        })
        it("shouldn't allow the first player's action to be changed afterwards", function() {
          player1.action = "hit"
          expect(player1.action).to.equal("stand")
        })
        it("shouldn't give cards to the dealer", function() {
          expect(game.dealer.hand.cards.length).to.equal(0)
        })

        describe("after turn time limit", function() {
          it("should end the game with a first player win, loss or tie", function() {
            var win, lose, tie
            game
              .on("win", () => win = true)
              .on("lose", () => lose = true)
              .on("tie", () => tie = true)

            game.processTurn()
            expect(win || lose || tie).to.be.true
          })
          it("should kick the second player from the game", function() {
            game.processTurn()
            expect(game.getPlayer(global.client.options.owner)).to.be.undefined
          })

          describe("after round end", function() {

            beforeEach(function() {
              game.processTurn()
            })

            it("should give 1 card to the dealer", function() {
              expect(game.dealer.hand.cards.length).to.equal(1)
            })
            it("should give 2 cards to the first player", function() {
              expect(player1.hand.cards.length).to.equal(2)
            })
          })
        })
      })

      describe("actions: stand, stand", function() {

        beforeEach(function() {
          player1.action = "stand"
          player2.action = "stand"
        })

        it("should end the turn", function() {
          expect(game.started).to.be.false
          expect(game.timeout).to.be.null
        })
        it("should make both player's actions be null", function() {
          expect(player1.action).to.be.null
          expect(player2.action).to.be.null
        })
        it("should allow both player actions to be changed afterwards, turn is over", function() {
          player1.action = "hit"
          expect(player1.action).to.equal("hit")
          player1.action = null
          player2.action = "hit"
          expect(player2.action).to.equal("hit")
          player2.action = null
        })
        it("should end the game with both players winning, losing or tying", function() {
          var player1end
          var player2end
          game
            .on("win", (player) => player.id === player1.id ? player1end = true : player2end = true)
            .on("lose", (player) => player.id === player1.id ? player1end = true : player2end = true)
            .on("tie", (player) => player.id === player1.id ? player1end = true : player2end = true)

          player1.action = "stand"
          player2.action = "stand"
          expect(player1end && player2end).to.be.true
        })

        describe("after round end", function() {
          it("should give 1 card to the dealer", function() {
            expect(game.dealer.hand.cards.length).to.equal(1)
          })
          it("should give 2 cards to both players", function() {
            expect(player1.hand.cards.length).to.equal(2)
            expect(player2.hand.cards.length).to.equal(2)
          })
        })
      })

      describe("actions: hit, stand", function() {

        beforeEach(function() {
          player1.action = "hit"
          player2.action = "stand"
        })

        it("should end the turn", function() {
          expect(game.started).to.be.false
          expect(game.timeout).to.be.null
        })
        it("should make the first player's action be null", function() {
          expect(player1.action).to.be.null
        })
        it("should keep the second player's action as 'stand'", function() {
          expect(player2.action).to.equal("stand")
        })
        it("should allow the first player's action to be changed afterwards", function() {
          player2.action = null // otherwise the game ends
          player1.action = "stand"
          expect(player1.action).to.equal("stand")
          player1.action = null
        })
        it("shouldn't allow the second player's action to be changed afterwards: 'stand'", function() {
          player2.action = "hit"
          expect(player2.action).to.equal("stand")
          player2.action = null
        })
      })
    })
  })
})
<|endoftext|>


<commit_before>define([
    'model/Player'
], function(Player) {

  'use strict';

  var GRID_DIMENSIONS = {
    width: 24,
    height: 20
  };
  var ROOMS = {
    1: {background: 'blue', doors: {east: 2, south: 3}},
    2: {background: 'red', doors: {west: 1, south: 4}},
    3: {background: 'yellow', doors: {east: 4, north: 1}},
    4: {background: 'green', doors: {west: 3, north: 2}}
  };

  var _viewAdpt;
  var _player;

  function getGridSpecs() {
    return GRID_DIMENSIONS;
  }

  function getGateways() {
    return {north: 1,
            east: 2,
            south: 3,
            west: 4}
  }

  function start() {
    _player = new Player();
    _player.installViewAdpt(_viewAdpt.makePlayerViewAdpt(_player));
    _viewAdpt.loadRoom(ROOMS[1]);
  }

  function onDoorVisit(doorID) {
    _viewAdpt.loadRoom(ROOM[doorID]);
  }

  return function GameModel(viewAdpt) {
    console.log('constructing a game model');

    _viewAdpt = viewAdpt;

    this.getGateways = getGateways;
    this.getGridSpecs = getGridSpecs;
    this.start = start;
  }
});

<commit_msg>made onDoorVisit public<commit_after>define([
    'model/Player'
], function(Player) {

  'use strict';

  var GRID_DIMENSIONS = {
    width: 24,
    height: 20
  };
  var ROOMS = {
    1: {background: 'blue', doors: {east: 2, south: 3}},
    2: {background: 'red', doors: {west: 1, south: 4}},
    3: {background: 'yellow', doors: {east: 4, north: 1}},
    4: {background: 'green', doors: {west: 3, north: 2}}
  };

  var _viewAdpt;
  var _player;

  function getGridSpecs() {
    return GRID_DIMENSIONS;
  }

  function getGateways() {
    return {north: 1,
            east: 2,
            south: 3,
            west: 4}
  }

  function start() {
    _player = new Player();
    _player.installViewAdpt(_viewAdpt.makePlayerViewAdpt(_player));
    _viewAdpt.loadRoom(ROOMS[1]);
  }

  function onDoorVisit(doorID) {
    _viewAdpt.loadRoom(ROOM[doorID]);
  }

  return function GameModel(viewAdpt) {
    console.log('constructing a game model');

    _viewAdpt = viewAdpt;

    this.getGateways = getGateways;
    this.getGridSpecs = getGridSpecs;
    this.onDoorVisit = onDoorVisit;
    this.start = start;
  }
});

<|endoftext|>


<commit_before>import shouldCompress from './background/shouldCompress'
import patchContentSecurity from './background/patchContentSecurity'
import getHeaderValue from './background/getHeaderIntValue'
import parseUrl from './utils/parseUrl'
import deferredStateStorage from './utils/deferredStateStorage'
import defaultState from './defaults'
import axios from 'axios'

chrome.storage.local.get(storedState => {
  const storage = deferredStateStorage()
  let setupOpen
  let state
  let pageUrl
  let compressed
  let isWebpSupported

  if (/compressor\.bandwidth-hero\.com/i.test(storedState.proxyUrl)) {
    chrome.storage.local.set({ ...storedState, proxyUrl: '' })
  }

  setState({ ...defaultState, ...storedState })

  checkWebpSupport().then(isSupported => {
    isWebpSupported = isSupported
  })

  async function checkWebpSupport() {
    if (!self.createImageBitmap) return false

    const webpData = 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA='
    const blob = await fetch(webpData).then(r => r.blob())
    return self.createImageBitmap(blob).then(() => true, () => false)
  }

  /**
   * Sync state.
   */
  function setState(newState) {
    if (chrome.browserAction.setIcon && (!state || state.enabled !== newState.enabled)) {
      chrome.browserAction.setIcon({
        path: newState.enabled ? 'assets/icon-128.png' : 'assets/icon-128-disabled.png'
      })
    }
    state = newState
  }

  function checkSetup() {
    if (
      !setupOpen &&
      state.enabled &&
      (state.proxyUrl === '' || /compressor\.bandwidth-hero\.com/i.test(state.proxyUrl))
    ) {
      chrome.tabs.create({ url: 'setup.html' })
      setupOpen = true
    }
  }

  /**
   * Intercept image loading request and decide if we need to compress it.
   */
  function onBeforeRequestListener({ url }) {
    checkSetup()
    if (
      shouldCompress({
        imageUrl: url,
        pageUrl,
        compressed,
        proxyUrl: state.proxyUrl,
        disabledHosts: state.disabledHosts,
        enabled: state.enabled
      })
    ) {
      compressed.add(url)
      let redirectUrl = `${state.proxyUrl}?url=${encodeURIComponent(url)}`
      if (!isWebpSupported) redirectUrl += '&jpeg=1'
      if (!state.convertBw) redirectUrl += '&bw=0'
      if (state.compressionLevel) {
        redirectUrl += '&l=' + parseInt(state.compressionLevel, 10)
      }
      if (!isFirefox()) return { redirectUrl }
      // Firefox allows onBeforeRequest event listener to return a Promise
      // and perform redirect when this Promise is resolved.
      // This allows us to run HEAD request before redirecting to compression
      // to make sure that the image should be compressed.
      return axios.head(url).then(res => {
        if (
          res.status === 200 &&
          res.headers['content-length'] > 1024 &&
          res.headers['content-type'] &&
          res.headers['content-type'].startsWith('image')
        ) {
          return { redirectUrl }
        }
      })
    }
  }

  /**
   * Firefox user agent always has "rv:" and "Gecko"
   * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox
   */
  function isFirefox() {
    return /rv\:.*Gecko/.test(window.navigator.userAgent)
  }

  /**
   * Retrieve saved bytes info from response headers, update statistics in
   * app storage and notify UI about state changes.
   */
  function onCompletedListener({ responseHeaders }) {
    const bytesSaved = getHeaderValue(responseHeaders, 'x-bytes-saved')
    const bytesProcessed = getHeaderValue(responseHeaders, 'x-original-size')
    if (bytesSaved !== false && bytesProcessed !== false) {
      state.statistics.filesProcessed += 1
      state.statistics.bytesProcessed += bytesProcessed
      state.statistics.bytesSaved += bytesSaved

      storage.set(state)
      chrome.runtime.sendMessage(state)
    }
  }

  /**
   * Patch document's content security policy headers so that it will allow
   * images loading from our compression proxy URL.
   */
  function onHeadersReceivedListener({ responseHeaders }) {
    return {
      responseHeaders: patchContentSecurity(responseHeaders, state.proxyUrl)
    }
  }

  chrome.runtime.onMessage.addListener(setState)
  chrome.webRequest.onBeforeRequest.addListener(
    onBeforeRequestListener,
    {
      urls: ['<all_urls>'],
      types: ['image']
    },
    ['blocking']
  )
  chrome.webRequest.onCompleted.addListener(
    onCompletedListener,
    {
      urls: ['<all_urls>'],
      types: ['image']
    },
    ['responseHeaders']
  )
  chrome.webRequest.onHeadersReceived.addListener(
    onHeadersReceivedListener,
    {
      urls: ['<all_urls>'],
      types: ['main_frame', 'sub_frame', 'xmlhttprequest']
    },
    ['blocking', 'responseHeaders']
  )
  chrome.tabs.onActivated.addListener(({ tabId }) => {
    chrome.tabs.get(tabId, tab => (pageUrl = parseUrl(tab.url).hostname))
  })
  chrome.tabs.onUpdated.addListener(() => (compressed = new Set()))
  chrome.runtime.onInstalled.addListener(checkSetup)
})
<commit_msg>HTTP 405 handling on Axios head and 'imageset' responseType inclusion in Firefox. Fixes gawker sites (e.g. gizmodo) and techcrunch.<commit_after>import shouldCompress from './background/shouldCompress'
import patchContentSecurity from './background/patchContentSecurity'
import getHeaderValue from './background/getHeaderIntValue'
import parseUrl from './utils/parseUrl'
import deferredStateStorage from './utils/deferredStateStorage'
import defaultState from './defaults'
import axios from 'axios'

chrome.storage.local.get(storedState => {
  const storage = deferredStateStorage()
  let setupOpen
  let state
  let pageUrl
  let compressed
  let isWebpSupported

  if (/compressor\.bandwidth-hero\.com/i.test(storedState.proxyUrl)) {
    chrome.storage.local.set({ ...storedState, proxyUrl: '' })
  }

  setState({ ...defaultState, ...storedState })

  checkWebpSupport().then(isSupported => {
    isWebpSupported = isSupported
  })

  async function checkWebpSupport() {
    if (!self.createImageBitmap) return false

    const webpData = 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA='
    const blob = await fetch(webpData).then(r => r.blob())
    return self.createImageBitmap(blob).then(() => true, () => false)
  }

  /**
   * Sync state.
   */
  function setState(newState) {
    if (chrome.browserAction.setIcon && (!state || state.enabled !== newState.enabled)) {
      chrome.browserAction.setIcon({
        path: newState.enabled ? 'assets/icon-128.png' : 'assets/icon-128-disabled.png'
      })
    }
    state = newState
  }

  function checkSetup() {
    if (
      !setupOpen &&
      state.enabled &&
      (state.proxyUrl === '' || /compressor\.bandwidth-hero\.com/i.test(state.proxyUrl))
    ) {
      chrome.tabs.create({ url: 'setup.html' })
      setupOpen = true
    }
  }

  /**
   * Intercept image loading request and decide if we need to compress it.
   */
  function onBeforeRequestListener({ url }) {
    checkSetup()
    if (
      shouldCompress({
        imageUrl: url,
        pageUrl,
        compressed,
        proxyUrl: state.proxyUrl,
        disabledHosts: state.disabledHosts,
        enabled: state.enabled
      })
    ) {
      compressed.add(url)
      let redirectUrl = `${state.proxyUrl}?url=${encodeURIComponent(url)}`
      if (!isWebpSupported) redirectUrl += '&jpeg=1'
      if (!state.convertBw) redirectUrl += '&bw=0'
      if (state.compressionLevel) {
        redirectUrl += '&l=' + parseInt(state.compressionLevel, 10)
      }
      if (!isFirefox()) return { redirectUrl }
      // Firefox allows onBeforeRequest event listener to return a Promise
      // and perform redirect when this Promise is resolved.
      // This allows us to run HEAD request before redirecting to compression
      // to make sure that the image should be compressed.
      return axios.head(url).then(res => {
        if (
          res.status === 200 &&
          res.headers['content-length'] > 1024 &&
          res.headers['content-type'] &&
          res.headers['content-type'].startsWith('image')
        ) {
          return { redirectUrl }
        }
      }).catch(error => {
          console.error(error)
          if(error.response.status === 405)//HEAD method not allowed
          {
              return { redirectUrl }
          }
      })
    }
  }

  /**
   * Firefox user agent always has "rv:" and "Gecko"
   * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent/Firefox
   */
  function isFirefox() {
    return /rv\:.*Gecko/.test(window.navigator.userAgent)
  }

  /**
   * Retrieve saved bytes info from response headers, update statistics in
   * app storage and notify UI about state changes.
   */
  function onCompletedListener({ responseHeaders }) {
    const bytesSaved = getHeaderValue(responseHeaders, 'x-bytes-saved')
    const bytesProcessed = getHeaderValue(responseHeaders, 'x-original-size')
    if (bytesSaved !== false && bytesProcessed !== false) {
      state.statistics.filesProcessed += 1
      state.statistics.bytesProcessed += bytesProcessed
      state.statistics.bytesSaved += bytesSaved

      storage.set(state)
      chrome.runtime.sendMessage(state)
    }
  }

  /**
   * Patch document's content security policy headers so that it will allow
   * images loading from our compression proxy URL.
   */
  function onHeadersReceivedListener({ responseHeaders }) {
    return {
      responseHeaders: patchContentSecurity(responseHeaders, state.proxyUrl)
    }
  }

  chrome.runtime.onMessage.addListener(setState)
  chrome.webRequest.onBeforeRequest.addListener(
    onBeforeRequestListener,
    {
      urls: ['<all_urls>'],
      types: isFirefox() ? ['imageset', 'image'] : ['image']
    },
    ['blocking']
  )
  chrome.webRequest.onCompleted.addListener(
    onCompletedListener,
    {
      urls: ['<all_urls>'],
      types: isFirefox() ? ['imageset', 'image'] : ['image']
    },
    ['responseHeaders']
  )
  chrome.webRequest.onHeadersReceived.addListener(
    onHeadersReceivedListener,
    {
      urls: ['<all_urls>'],
      types: ['main_frame', 'sub_frame', 'xmlhttprequest']
    },
    ['blocking', 'responseHeaders']
  )
  chrome.tabs.onActivated.addListener(({ tabId }) => {
    chrome.tabs.get(tabId, tab => (pageUrl = parseUrl(tab.url).hostname))
  })
  chrome.tabs.onUpdated.addListener(() => (compressed = new Set()))
  chrome.runtime.onInstalled.addListener(checkSetup)
})
<|endoftext|>


<commit_before>$.fn.romoDatepicker = function() {
  return $.map(this, function(element) {
    return new RomoDatepicker(element);
  });
}

var RomoDatepicker = function(element) {
  this.elem = $(element);
  this.defaultFormat = 'yyyy-mm-dd'
  this.monthNames = [
    "January", "February", "March", "April", "May", "June",
    "July", "August", "September", "October", "November", "December"
  ]
  this.defaultLeftArrowClass  = '';
  this.defaultRightArrowClass = '';
  this.defaultIndicatorClass  = '';
  this.itemSelector = 'TD.romo-datepicker-day:not(.disabled)';
  this.calTable = $();
  this.date = undefined;
  this.today = new Date;

  this.doInit();
  this.doSetFormat();
  this.doSetDate(this.elem.val());
  this.doBindDropdown();
  this.doBuildUI();

  this.elem.trigger('datepicker:ready', [this]);
}

RomoDatepicker.prototype.doInit = function() {
  // override as needed
}

RomoDatepicker.prototype.doSetFormat = function() {
  var format = this.elem.data('romo-datepicker-format') || this.defaultFormat;
  this.formatValues = this._parseFormatValues(format);
}

RomoDatepicker.prototype.doSetDate = function(value) {
  this.date = this._parseDate(value);
  if (this.date !== undefined) {
    this.elem.val(this._formatDate(this.date));
  } else {
    this.elem.val(value);
  }
}

RomoDatepicker.prototype.doBindDropdown = function() {
  this.elem.addClass('romo');
  this.elem.attr('data-romo-dropdown-disable-toggle', 'true');
  if (this.elem.data('romo-dropdown-width') === undefined) {
    this.elem.attr('data-romo-dropdown-width', 'elem');
  }
  this.romoDropdown = this.elem.romoDropdown()[0];

  this.romoDropdown.doSetPopupZIndex(this.elem);
  this.romoDropdown.bodyElem.addClass('romo-datepicker-calendar');
  this.romoDropdown.elem.on('dropdown:popupOpen', $.proxy(this.onPopupOpen, this));
  this.romoDropdown.elem.on('dropdown:popupClose', $.proxy(this.onPopupClose, this));

  this.romoDropdown.elem.on('dropdown:toggle', $.proxy(function(e, dropdown) {
    this.elem.trigger('datepicker:dropdown:toggle', [dropdown, this]);
  }, this));
  this.romoDropdown.elem.on('dropdown:popupOpen', $.proxy(function(e, dropdown) {
    this.elem.trigger('datepicker:dropdown:popupOpen', [dropdown, this]);
  }, this));
  this.romoDropdown.elem.on('dropdown:popupClose', $.proxy(function(e, dropdown) {
    this.elem.trigger('datepicker:dropdown:popupClose', [dropdown, this]);
  }, this));

  this.elem.on('datepicker:triggerToggle', $.proxy(function(e) {
    this.romoDropdown.elem.trigger('dropdown:triggerToggle', []);
  }, this));
  this.elem.on('datepicker:triggerPopupOpen', $.proxy(function(e) {
    this.romoDropdown.elem.trigger('dropdown:triggerPopupOpen', []);
  }, this));
  this.elem.on('datepicker:triggerPopupClose', $.proxy(function(e) {
    this.romoDropdown.elem.trigger('dropdown:triggerPopupClose', []);
  }, this));
}

RomoDatepicker.prototype.doBuildUI = function() {
  this.calTable = this._buildCalendar();
  this.romoDropdown.bodyElem.html('');
  this.romoDropdown.bodyElem.append(this.calTable);

  this.calTable.find('.romo-datepicker-prev').on('click', $.proxy(this.onPrevClick, this));
  this.calTable.find('.romo-datepicker-next').on('click', $.proxy(this.onNextClick, this));
}

RomoDatepicker.prototype.doRefreshUI = function(date) {
  this._refreshCalendar(date || this.date || (new Date));
  this.calTable.find(this.itemSelector).on('hover', $.proxy(this.onItemHover, this));
  this.calTable.find(this.itemSelector).on('click', $.proxy(this.onItemClick, this));
}

RomoDatepicker.prototype.doRefreshToPrevMonth = function() {
  var date = this.refreshDate || this.date || (new Date);
  var year = date.getUTCFullYear();
  var month = date.getUTCMonth() - 1;
  if (month < 0) {
    year -= 1;
    month = 11;
  }

  this.doRefreshUI(this._UTCDate(year, month, 1));
}

RomoDatepicker.prototype.doRefreshToNextMonth = function() {
  var date = this.refreshDate || this.date || (new Date);
  var year = date.getUTCFullYear();
  var month = date.getUTCMonth() + 1;
  if (month > 11) {
    year += 1;
    month = 0;
  }

  this.doRefreshUI(this._UTCDate(year, month, 1));
}

RomoDatepicker.prototype.doSelectHighlightedItem = function() {
  var prevValue = this.elem.val();
  var newValue = this.calTable.find('TD.romo-datepicker-highlight').data('romo-datepicker-value');

  this.romoDropdown.doPopupClose();
  this.elem.trigger('datepicker:itemSelected', [newValue, prevValue, this]);

  if (newValue !== prevValue) {
    this.doSetDate(newValue);

    this.elem.trigger('change');
    this.elem.trigger('datepicker:change', [newValue, prevValue, this]);
  }
}

RomoDatepicker.prototype.onPopupOpen = function(e) {
  if (this.elem.hasClass('disabled') === false) {
    this.doSetDate(this.elem.val());
    this.doRefreshUI();
  }
}

RomoDatepicker.prototype.onPopupClose = function(e) {
  this._highlightItem($());
}

RomoDatepicker.prototype.onItemHover = function(e) {
  if (e !== undefined) {
    e.preventDefault();
  }
  this._highlightItem($(e.target));
}

RomoDatepicker.prototype.onItemClick = function(e) {
  if (e !== undefined) {
    e.preventDefault();
  }
  this.doSelectHighlightedItem();
}

RomoDatepicker.prototype.onPrevClick = function(e) {
  if (e !== undefined) {
    e.preventDefault();
  }
  this.doRefreshToPrevMonth();
}

RomoDatepicker.prototype.onNextClick = function(e) {
  if (e !== undefined) {
    e.preventDefault();
  }
  this.doRefreshToNextMonth();
}

RomoDatepicker.prototype._refreshCalendar = function(date) {
  this.calTable.find('.romo-datepicker-title').html(this._buildCalendarTitle(date));
  this.calTable.find('tbody').empty().append(this._buildCalendarBody(date));
  this.refreshDate = date;
}

RomoDatepicker.prototype._buildCalendar = function() {
  var table = $('

');
  table.append(this._buildCalendarHeader());
  table.append($('

'));
  return table;
}

RomoDatepicker.prototype._buildCalendarHeader = function() {
  var header = $('
</thead');

  var row = $('

');
  row.append($(''));
  row.append($('	'));
  row.append($(''));
  header.append(row);

  row = $('

');
  row.append($('Su	'));
  row.append($('M	'));
  row.append($('T	'));
  row.append($('W	'));
  row.append($('Th	'));
  row.append($('F	'));
  row.append($('S	'));
  header.append(row);

  return header;
}

RomoDatepicker.prototype._buildCalendarTitle = function(date) {
  return this.monthNames[date.getUTCMonth()] + ' ' + date.getUTCFullYear().toString();
}

RomoDatepicker.prototype._buildCalendarBody = function(date) {
  var ty = this.today.getUTCFullYear();
  var tm = this.today.getUTCMonth();
  var td = this.today.getUTCDate();
  var year = date.getUTCFullYear();
  var month = date.getUTCMonth();
  var day = date.getUTCDate();
  var fomdow = this._UTCDate(year, month, 1).getUTCDay(); // first-of-the-month day-of-the-week
  if (fomdow == 0) {
    fomdow = 7;  // don't start calendar on the first-of-the-month, show last week of prev month
  }
  var iDate = this._UTCDate(year, month, 1 - fomdow);
  var iWeek = 0;
  var html = [];

  while (iWeek < 6) { // render 6 weeks in the calendar
    var y = iDate.getUTCFullYear();
    var m = iDate.getUTCMonth();
    var d = iDate.getUTCDate();
    var dow = iDate.getUTCDay();
    var cls = [];

    if (dow === 0) {
      html.push('
');
    }

    cls.push('romo-datepicker-day');
    if (dow === 0 || dow === 6) {
      cls.push('romo-datepicker-day-weekend');
    }
    if (y !== year || m !== month) {
      cls.push('romo-datepicker-day-other');
    }
    if (y === ty && m === tm && d === td) {
      cls.push('romo-datepicker-day-today');
    }
    if (this.date &&
        y === this.date.getUTCFullYear() &&
        m === this.date.getUTCMonth() &&
        d === this.date.getUTCDate()) {
      cls.push('selected');
    }

    html.push('');
    html.push(d.toString());
    html.push('	');

    if (dow === 6) {
      html.push('
');
      iWeek += 1;
    }
    iDate.setUTCDate(iDate.getUTCDate()+1);
  }

  return $(html.join(''));
}

RomoDatepicker.prototype._addIndicatorToElem = function() {
  // TODO, if opt and indicator icon class
  // create icon
  // append to elem
}

RomoDatepicker.prototype._highlightItem = function(item) {
  this.calTable.find('TD.romo-datepicker-highlight').removeClass('romo-datepicker-highlight');
  item.addClass('romo-datepicker-highlight');
}

RomoDatepicker.prototype._formatDate = function(date) {
  var year = date.getUTCFullYear();
  var month = date.getUTCMonth() + 1;
  var day = date.getUTCDate();

  return this.formatValues.reduce(function(prev, curr) {
    switch (curr) {
      case "yyyy":
      case "yyy":
        prev += year.toString();
        break;
      case "yy":
      case "y":
        prev += year.toString().slice(-2);
        break;
      case "mm":
        prev += ("00"+ month.toString()).slice(-2); // pad 2 with "0"s
        break;
      case "m":
        prev += month.toString();
        break;
      case "dd":
        prev += ("00"+ day.toString()).slice(-2); // pad 2 with "0"s
        break;
      case "d":
        prev += day.toString();
        break;
      default:
        prev += curr; // delimeter, pass-thru
    }
    return prev;
  }, '');
}

RomoDatepicker.prototype._parseFormatValues = function(value) {
  var regex, matches;

  regex = /^([m]{1,2})([^md]+)([d]{1,2})([^dy]+)([y]{2,4})$/; // mm dd yyyy or mm dd yy
  matches = this._regexMatches(value, regex);
  if (matches.length === 5) {
    return matches;
  }

  regex = /^([y]{3,4})([^ym]+)([m]{1,2})([^md]+)([d]{1,2})$/; // yyyy mm dd
  matches = this._regexMatches(value, regex);
  if (matches.length === 5) {
    return matches;
  }

  return ['yyyy', '-', 'mm', '-', 'dd'];
}

RomoDatepicker.prototype._parseDate = function(value) {
  if (value.trim() === '') {
    return undefined;
  }

  var dateValues = this._parseDateValues(value.trim());
  if (dateValues.length === 0) {
    return undefined;
  }

  var year = parseInt(dateValues[0]);
  if (year < 0) {
    return undefined;
  }
  if (dateValues[0].length > 2 && year < 100) {
    return undefined;
  }
  if (dateValues[0].length === 2 && year < 100) {
    year = this._currentYear() - (this._currentYear() % 1000) + year;
  }

  var month = parseInt(dateValues[1]) - 1;
  if (month < 0 || month > 11) {
    return undefined;
  }

  var day = parseInt(dateValues[2]);
  var date = this._UTCDate(year, month, day);
  if (date.getUTCMonth() !== month) {
    return undefined;
  }

  return date;
}

RomoDatepicker.prototype._parseDateValues = function(value) {
  var regex, matches;

  regex = /^([0-9]{1,2})[\/\-\.]+([0-9]{1,2})[\/\-\.]+([0-9]{2,4})$/; // mm dd yyyy or mm dd yy
  matches = this._regexMatches(value, regex);
  if (matches.length === 3) {
    return [matches[2], matches[0], matches[1]];
  }

  regex = /^([0-9]{3,4})[\/\-\.]+([0-9]{1,2})[\/\-\.]+([0-9]{1,2})$/; // yyyy mm dd
  matches = this._regexMatches(value, regex);
  if (matches.length === 3) {
    return matches;
  }

  regex = /^([0-9]{1,2})[\/\-\.]+([0-9]{1,2})$/; // mm dd
  matches = this._regexMatches(value, regex);
  if (matches.length === 2) {
    return [this._currentYear(), matches[0], matches[1]];
  }

  return [];
}

RomoDatepicker.prototype._regexMatches = function(value, regex) {
  if (regex.test(value) === true) {
    return regex.exec(value).slice(1);
  }
  return [];
}

RomoDatepicker.prototype._currentYear = function() {
  return (new Date).getUTCFullYear();
}

RomoDatepicker.prototype._UTCDate = function(year, month, day) {
  return new Date(Date.UTC.apply(Date, [year, month, day]));
}

Romo.onInitUI(function(e) {
  $(e.target).find('[data-romo-datepicker-auto="true"]').romoDatepicker();
});
<commit_msg>datepicker: trigger events on calendar ui refreshes<commit_after>$.fn.romoDatepicker = function() {
  return $.map(this, function(element) {
    return new RomoDatepicker(element);
  });
}

var RomoDatepicker = function(element) {
  this.elem = $(element);
  this.defaultFormat = 'yyyy-mm-dd'
  this.monthNames = [
    "January", "February", "March", "April", "May", "June",
    "July", "August", "September", "October", "November", "December"
  ]
  this.defaultLeftArrowClass  = '';
  this.defaultRightArrowClass = '';
  this.defaultIndicatorClass  = '';
  this.itemSelector = 'TD.romo-datepicker-day:not(.disabled)';
  this.calTable = $();
  this.date = undefined;
  this.today = new Date;

  this.doInit();
  this.doSetFormat();
  this.doSetDate(this.elem.val());
  this.doBindDropdown();
  this.doBuildUI();

  this.elem.trigger('datepicker:ready', [this]);
}

RomoDatepicker.prototype.doInit = function() {
  // override as needed
}

RomoDatepicker.prototype.doSetFormat = function() {
  var format = this.elem.data('romo-datepicker-format') || this.defaultFormat;
  this.formatValues = this._parseFormatValues(format);
}

RomoDatepicker.prototype.doSetDate = function(value) {
  this.date = this._parseDate(value);
  if (this.date !== undefined) {
    this.elem.val(this._formatDate(this.date));
  } else {
    this.elem.val(value);
  }
}

RomoDatepicker.prototype.doBindDropdown = function() {
  this.elem.addClass('romo');
  this.elem.attr('data-romo-dropdown-disable-toggle', 'true');
  if (this.elem.data('romo-dropdown-width') === undefined) {
    this.elem.attr('data-romo-dropdown-width', 'elem');
  }
  this.romoDropdown = this.elem.romoDropdown()[0];

  this.romoDropdown.doSetPopupZIndex(this.elem);
  this.romoDropdown.bodyElem.addClass('romo-datepicker-calendar');
  this.romoDropdown.elem.on('dropdown:popupOpen', $.proxy(this.onPopupOpen, this));
  this.romoDropdown.elem.on('dropdown:popupClose', $.proxy(this.onPopupClose, this));

  this.romoDropdown.elem.on('dropdown:toggle', $.proxy(function(e, dropdown) {
    this.elem.trigger('datepicker:dropdown:toggle', [dropdown, this]);
  }, this));
  this.romoDropdown.elem.on('dropdown:popupOpen', $.proxy(function(e, dropdown) {
    this.elem.trigger('datepicker:dropdown:popupOpen', [dropdown, this]);
  }, this));
  this.romoDropdown.elem.on('dropdown:popupClose', $.proxy(function(e, dropdown) {
    this.elem.trigger('datepicker:dropdown:popupClose', [dropdown, this]);
  }, this));

  this.elem.on('datepicker:triggerToggle', $.proxy(function(e) {
    this.romoDropdown.elem.trigger('dropdown:triggerToggle', []);
  }, this));
  this.elem.on('datepicker:triggerPopupOpen', $.proxy(function(e) {
    this.romoDropdown.elem.trigger('dropdown:triggerPopupOpen', []);
  }, this));
  this.elem.on('datepicker:triggerPopupClose', $.proxy(function(e) {
    this.romoDropdown.elem.trigger('dropdown:triggerPopupClose', []);
  }, this));
}

RomoDatepicker.prototype.doBuildUI = function() {
  this.calTable = this._buildCalendar();
  this.romoDropdown.bodyElem.html('');
  this.romoDropdown.bodyElem.append(this.calTable);

  this.calTable.find('.romo-datepicker-prev').on('click', $.proxy(this.onPrevClick, this));
  this.calTable.find('.romo-datepicker-next').on('click', $.proxy(this.onNextClick, this));
}

RomoDatepicker.prototype.doRefreshUI = function(date) {
  var rDate = date || this.date || (new Date);
  this._refreshCalendar(rDate);
  this.elem.trigger('datepicker:refresh', [rDate, this]);

  this.calTable.find(this.itemSelector).on('hover', $.proxy(this.onItemHover, this));
  this.calTable.find(this.itemSelector).on('click', $.proxy(this.onItemClick, this));
}

RomoDatepicker.prototype.doRefreshToPrevMonth = function() {
  var date = this.refreshDate || this.date || (new Date);
  var year = date.getUTCFullYear();
  var month = date.getUTCMonth() - 1;
  if (month < 0) {
    year -= 1;
    month = 11;
  }

  var pDate = this._UTCDate(year, month, 1);
  this.doRefreshUI(pDate);
  this.elem.trigger('datepicker:prevRefresh', [pDate, this]);
}

RomoDatepicker.prototype.doRefreshToNextMonth = function() {
  var date = this.refreshDate || this.date || (new Date);
  var year = date.getUTCFullYear();
  var month = date.getUTCMonth() + 1;
  if (month > 11) {
    year += 1;
    month = 0;
  }

  var nDate = this._UTCDate(year, month, 1);
  this.doRefreshUI(nDate);
  this.elem.trigger('datepicker:nextRefresh', [nDate, this]);
}

RomoDatepicker.prototype.doSelectHighlightedItem = function() {
  var prevValue = this.elem.val();
  var newValue = this.calTable.find('TD.romo-datepicker-highlight').data('romo-datepicker-value');

  this.romoDropdown.doPopupClose();
  this.elem.trigger('datepicker:itemSelected', [newValue, prevValue, this]);

  if (newValue !== prevValue) {
    this.doSetDate(newValue);

    this.elem.trigger('change');
    this.elem.trigger('datepicker:change', [newValue, prevValue, this]);
  }
}

RomoDatepicker.prototype.onPopupOpen = function(e) {
  if (this.elem.hasClass('disabled') === false) {
    this.doSetDate(this.elem.val());
    this.doRefreshUI();
  }
}

RomoDatepicker.prototype.onPopupClose = function(e) {
  this._highlightItem($());
}

RomoDatepicker.prototype.onItemHover = function(e) {
  if (e !== undefined) {
    e.preventDefault();
  }
  this._highlightItem($(e.target));
}

RomoDatepicker.prototype.onItemClick = function(e) {
  if (e !== undefined) {
    e.preventDefault();
  }
  this.doSelectHighlightedItem();
}

RomoDatepicker.prototype.onPrevClick = function(e) {
  if (e !== undefined) {
    e.preventDefault();
  }
  this.doRefreshToPrevMonth();
}

RomoDatepicker.prototype.onNextClick = function(e) {
  if (e !== undefined) {
    e.preventDefault();
  }
  this.doRefreshToNextMonth();
}

RomoDatepicker.prototype._refreshCalendar = function(date) {
  this.calTable.find('.romo-datepicker-title').html(this._buildCalendarTitle(date));
  this.calTable.find('tbody').empty().append(this._buildCalendarBody(date));
  this.refreshDate = date;
}

RomoDatepicker.prototype._buildCalendar = function() {
  var table = $('

');
  table.append(this._buildCalendarHeader());
  table.append($('

'));
  return table;
}

RomoDatepicker.prototype._buildCalendarHeader = function() {
  var header = $('
</thead');

  var row = $('

');
  row.append($(''));
  row.append($('	'));
  row.append($(''));
  header.append(row);

  row = $('

');
  row.append($('Su	'));
  row.append($('M	'));
  row.append($('T	'));
  row.append($('W	'));
  row.append($('Th	'));
  row.append($('F	'));
  row.append($('S	'));
  header.append(row);

  return header;
}

RomoDatepicker.prototype._buildCalendarTitle = function(date) {
  return this.monthNames[date.getUTCMonth()] + ' ' + date.getUTCFullYear().toString();
}

RomoDatepicker.prototype._buildCalendarBody = function(date) {
  var ty = this.today.getUTCFullYear();
  var tm = this.today.getUTCMonth();
  var td = this.today.getUTCDate();
  var year = date.getUTCFullYear();
  var month = date.getUTCMonth();
  var day = date.getUTCDate();
  var fomdow = this._UTCDate(year, month, 1).getUTCDay(); // first-of-the-month day-of-the-week
  if (fomdow == 0) {
    fomdow = 7;  // don't start calendar on the first-of-the-month, show last week of prev month
  }
  var iDate = this._UTCDate(year, month, 1 - fomdow);
  var iWeek = 0;
  var html = [];

  while (iWeek < 6) { // render 6 weeks in the calendar
    var y = iDate.getUTCFullYear();
    var m = iDate.getUTCMonth();
    var d = iDate.getUTCDate();
    var dow = iDate.getUTCDay();
    var cls = [];

    if (dow === 0) {
      html.push('
');
    }

    cls.push('romo-datepicker-day');
    if (dow === 0 || dow === 6) {
      cls.push('romo-datepicker-day-weekend');
    }
    if (y !== year || m !== month) {
      cls.push('romo-datepicker-day-other');
    }
    if (y === ty && m === tm && d === td) {
      cls.push('romo-datepicker-day-today');
    }
    if (this.date &&
        y === this.date.getUTCFullYear() &&
        m === this.date.getUTCMonth() &&
        d === this.date.getUTCDate()) {
      cls.push('selected');
    }

    html.push('');
    html.push(d.toString());
    html.push('	');

    if (dow === 6) {
      html.push('
');
      iWeek += 1;
    }
    iDate.setUTCDate(iDate.getUTCDate()+1);
  }

  return $(html.join(''));
}

RomoDatepicker.prototype._addIndicatorToElem = function() {
  // TODO, if opt and indicator icon class
  // create icon
  // append to elem
}

RomoDatepicker.prototype._highlightItem = function(item) {
  this.calTable.find('TD.romo-datepicker-highlight').removeClass('romo-datepicker-highlight');
  item.addClass('romo-datepicker-highlight');
}

RomoDatepicker.prototype._formatDate = function(date) {
  var year = date.getUTCFullYear();
  var month = date.getUTCMonth() + 1;
  var day = date.getUTCDate();

  return this.formatValues.reduce(function(prev, curr) {
    switch (curr) {
      case "yyyy":
      case "yyy":
        prev += year.toString();
        break;
      case "yy":
      case "y":
        prev += year.toString().slice(-2);
        break;
      case "mm":
        prev += ("00"+ month.toString()).slice(-2); // pad 2 with "0"s
        break;
      case "m":
        prev += month.toString();
        break;
      case "dd":
        prev += ("00"+ day.toString()).slice(-2); // pad 2 with "0"s
        break;
      case "d":
        prev += day.toString();
        break;
      default:
        prev += curr; // delimeter, pass-thru
    }
    return prev;
  }, '');
}

RomoDatepicker.prototype._parseFormatValues = function(value) {
  var regex, matches;

  regex = /^([m]{1,2})([^md]+)([d]{1,2})([^dy]+)([y]{2,4})$/; // mm dd yyyy or mm dd yy
  matches = this._regexMatches(value, regex);
  if (matches.length === 5) {
    return matches;
  }

  regex = /^([y]{3,4})([^ym]+)([m]{1,2})([^md]+)([d]{1,2})$/; // yyyy mm dd
  matches = this._regexMatches(value, regex);
  if (matches.length === 5) {
    return matches;
  }

  return ['yyyy', '-', 'mm', '-', 'dd'];
}

RomoDatepicker.prototype._parseDate = function(value) {
  if (value.trim() === '') {
    return undefined;
  }

  var dateValues = this._parseDateValues(value.trim());
  if (dateValues.length === 0) {
    return undefined;
  }

  var year = parseInt(dateValues[0]);
  if (year < 0) {
    return undefined;
  }
  if (dateValues[0].length > 2 && year < 100) {
    return undefined;
  }
  if (dateValues[0].length === 2 && year < 100) {
    year = this._currentYear() - (this._currentYear() % 1000) + year;
  }

  var month = parseInt(dateValues[1]) - 1;
  if (month < 0 || month > 11) {
    return undefined;
  }

  var day = parseInt(dateValues[2]);
  var date = this._UTCDate(year, month, day);
  if (date.getUTCMonth() !== month) {
    return undefined;
  }

  return date;
}

RomoDatepicker.prototype._parseDateValues = function(value) {
  var regex, matches;

  regex = /^([0-9]{1,2})[\/\-\.]+([0-9]{1,2})[\/\-\.]+([0-9]{2,4})$/; // mm dd yyyy or mm dd yy
  matches = this._regexMatches(value, regex);
  if (matches.length === 3) {
    return [matches[2], matches[0], matches[1]];
  }

  regex = /^([0-9]{3,4})[\/\-\.]+([0-9]{1,2})[\/\-\.]+([0-9]{1,2})$/; // yyyy mm dd
  matches = this._regexMatches(value, regex);
  if (matches.length === 3) {
    return matches;
  }

  regex = /^([0-9]{1,2})[\/\-\.]+([0-9]{1,2})$/; // mm dd
  matches = this._regexMatches(value, regex);
  if (matches.length === 2) {
    return [this._currentYear(), matches[0], matches[1]];
  }

  return [];
}

RomoDatepicker.prototype._regexMatches = function(value, regex) {
  if (regex.test(value) === true) {
    return regex.exec(value).slice(1);
  }
  return [];
}

RomoDatepicker.prototype._currentYear = function() {
  return (new Date).getUTCFullYear();
}

RomoDatepicker.prototype._UTCDate = function(year, month, day) {
  return new Date(Date.UTC.apply(Date, [year, month, day]));
}

Romo.onInitUI(function(e) {
  $(e.target).find('[data-romo-datepicker-auto="true"]').romoDatepicker();
});
<|endoftext|>


<commit_before>/* src/background.js
 * Originally created 3/10/2017 by DaAwesomeP
 * This is the background task file of the extension
 * https://github.com/DaAwesomeP/tab-counter
 *
 * Copyright 2017-present DaAwesomeP
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* global _ */

const updateIcon = async function updateIcon () {
  // Get tab counter setting
  let settings = await browser.storage.local.get()
  let counterPreference = settings.counter || 0

  // Get current tab to update badge in
  let currentTab = (await browser.tabs.query({ currentWindow: true, active: true }))[0]

  if (counterPreference === 0) { // Badge shows current window
    // Get tabs in current window
    let currentWindow = await browser.tabs.query({ currentWindow: true })
    if (typeof currentTab !== 'undefined') {
      browser.browserAction.setBadgeText({
        text: (await currentWindow).length.toString(),
        tabId: currentTab.id
      })
    }
  } else if (counterPreference === 1) { // Badge shows total of all windows
    // Get tabs in all windows
    let countAll = await browser.tabs.query({})
    if (typeof currentTab !== 'undefined') {
      browser.browserAction.setBadgeText({
        text: (await countAll).length.toString(),
        tabId: currentTab.id
      })
    }
  } else if (counterPreference === 2) { // Badge shows both (Firefox limits to about 4 characters based on width)
    // Get both tabs in current window and in all windows
    let currentWindow = await browser.tabs.query({ currentWindow: true })
    let countAll = await browser.tabs.query({})
    if (typeof currentTab !== 'undefined') {
      browser.browserAction.setBadgeText({
        text: `${(await currentWindow).length}/${(await countAll).length}`,
        tabId: currentTab.id
      })
    }
  }
}

// Prevent from firing too frequently or flooding at a window or restore
const lazyUpdateIcon = _.debounce(updateIcon, 250)

// Will be error if tab has been removed, so wait 150ms;
// onActivated fires slightly before onRemoved,
// but tab is gone during onActivated.
// Must be a function to avoid event parameter errors
const update = function update () { setTimeout(lazyUpdateIcon, 150) }

// Init badge for when addon starts and not yet loaded tabs
browser.browserAction.setBadgeText({text: 'wait'})
browser.browserAction.setBadgeBackgroundColor({color: '#000000'})

// Watch for tab and window events five seconds after browser startup
setTimeout(() => {
  browser.tabs.onActivated.addListener(update)
  browser.tabs.onAttached.addListener(update)
  browser.tabs.onCreated.addListener(update)
  browser.tabs.onDetached.addListener(update)
  browser.tabs.onMoved.addListener(update)
  browser.tabs.onReplaced.addListener(update)
  browser.tabs.onRemoved.addListener(update)
  browser.tabs.onUpdated.addListener(update)
  browser.windows.onFocusChanged.addListener(update)
}, 5000)

// Load and apply icon and badge color settings
const checkSettings = async function checkSettings () {
  // Get settings object
  let settings = await browser.storage.local.get()

  // Perform settings upgrade (placeholder, no breaking changes yet)
  if (settings.hasOwnProperty('version')) {
    if (settings.version !== browser.runtime.getManifest().version) {
      // Upgrade
    }
  } else {
    // New
  }

  // Apply badge color or use default
  if (settings.hasOwnProperty('badgeColor')) browser.browserAction.setBadgeBackgroundColor({color: settings.badgeColor})
  else browser.browserAction.setBadgeBackgroundColor({color: '#000000'})

  // Apply icon selection or use default
  if (settings.hasOwnProperty('icon')) browser.browserAction.setIcon({path: `icons/${settings.icon}`})
  else browser.browserAction.setIcon({path: 'icons/tabcounter.plain.min.svg'})
}

// Load settings and update badge at app start
const applyAll = async function applyAll () {
  await checkSettings()  // Icon and badge color
  await update()         // Badge text options
}
checkSettings()

// Listen for settings changes and update color, icon, and badge text instantly
browser.storage.onChanged.addListener(applyAll)
<commit_msg>use tooltip<commit_after>/* src/background.js
 * Originally created 3/10/2017 by DaAwesomeP
 * This is the background task file of the extension
 * https://github.com/DaAwesomeP/tab-counter
 *
 * Copyright 2017-present DaAwesomeP
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* global _ */

const updateIcon = async function updateIcon () {
  // Get tab counter setting
  let settings = await browser.storage.local.get()
  let counterPreference = settings.counter || 0

  // Get current tab to update badge in
  let currentTab = (await browser.tabs.query({ currentWindow: true, active: true }))[0]

  // Get both tabs in current window and in all windows
  let currentWindow = (await browser.tabs.query({ currentWindow: true })).length.toString()
  let countAll = (await browser.tabs.query({})).length.toString()
  if (typeof currentTab !== 'undefined') {
    let text
    if (counterPreference === 0) text = currentWindow // Badge shows current window
    else if (counterPreference === 1) text = countAll // Badge shows total of all windows
    else if (counterPreference === 2) text = `${currentWindow}/${countAll}` // Badge shows both (Firefox limits to about 4 characters based on width)

    // Update the badge
    browser.browserAction.setBadgeText({
      text: text,
      tabId: currentTab.id
    })

    // Update the tooltip
    browser.browserAction.setTitle({
      title: `Tab Counter\nThis window:  ${currentWindow}\nAll windows: ${countAll}`,
      tabId: currentTab.id
    })
  }
}

// Prevent from firing too frequently or flooding at a window or restore
const lazyUpdateIcon = _.debounce(updateIcon, 250)

// Will be error if tab has been removed, so wait 150ms;
// onActivated fires slightly before onRemoved,
// but tab is gone during onActivated.
// Must be a function to avoid event parameter errors
const update = function update () { setTimeout(lazyUpdateIcon, 150) }

// Init badge for when addon starts and not yet loaded tabs
browser.browserAction.setBadgeText({text: 'wait'})
browser.browserAction.setBadgeBackgroundColor({color: '#000000'})

// Watch for tab and window events five seconds after browser startup
setTimeout(() => {
  browser.tabs.onActivated.addListener(update)
  browser.tabs.onAttached.addListener(update)
  browser.tabs.onCreated.addListener(update)
  browser.tabs.onDetached.addListener(update)
  browser.tabs.onMoved.addListener(update)
  browser.tabs.onReplaced.addListener(update)
  browser.tabs.onRemoved.addListener(update)
  browser.tabs.onUpdated.addListener(update)
  browser.windows.onFocusChanged.addListener(update)
}, 5000)

// Load and apply icon and badge color settings
const checkSettings = async function checkSettings () {
  // Get settings object
  let settings = await browser.storage.local.get()

  // Perform settings upgrade (placeholder, no breaking changes yet)
  if (settings.hasOwnProperty('version')) {
    if (settings.version !== browser.runtime.getManifest().version) {
      // Upgrade
    }
  } else {
    // New
  }

  // Apply badge color or use default
  if (settings.hasOwnProperty('badgeColor')) browser.browserAction.setBadgeBackgroundColor({color: settings.badgeColor})
  else browser.browserAction.setBadgeBackgroundColor({color: '#000000'})

  // Apply icon selection or use default
  if (settings.hasOwnProperty('icon')) browser.browserAction.setIcon({path: `icons/${settings.icon}`})
  else browser.browserAction.setIcon({path: 'icons/tabcounter.plain.min.svg'})
}

// Load settings and update badge at app start
const applyAll = async function applyAll () {
  await checkSettings()  // Icon and badge color
  await update()         // Badge text options
}
checkSettings()

// Listen for settings changes and update color, icon, and badge text instantly
browser.storage.onChanged.addListener(applyAll)
<|endoftext|>


<commit_before>#!/usr/node/bin/node
// Copyright (c) 2013, Joyent, Inc. All rights reserved.

var lookupPath = process.env['LOOKUP_FILE'] || '../etc/lookup.json';
var lookup = require(lookupPath); // maps uuid->login
var mantaFileSave = require('manta-compute-bin').mantaFileSave;
var mod_ipaddr = require('ipaddr.js');
var mod_bunyan = require('bunyan');
var mod_carrier = require('carrier');
var mod_fs = require('fs');
var mod_manta = require('manta');
var mod_screen = require('screener');
var mod_vasync = require('vasync');

var files = {}; // keeps track of all the files we create
var ERROR = false;
var tmpdir = '/var/tmp';

/*
 * Allow all headers, then auth and x-forwarded-for headers will be removed
 * after applying this whitelist. The IP in the x-forwarded-for header will
 * be used for the top-level remoteAddress IP.
 */
var whitelist = {
        'req': {
                'method': 'string',
                'url': 'string', // renamed 'request-uri' see RFC 2616
                'headers': true, // XXX see above
                'httpVersion': 'string',
                'caller': {
                        'login': 'string'
                }
        },
        'res': {
                'statusCode': 'number',
                'headers': true
        }
};

function shouldProcess(record) {
        return (record.audit &&
                record.req.url !== '/ping' &&
                typeof (record.req.owner) !== 'undefined');
}

function sanitize(record) {
        var output = mod_screen.screen(record, whitelist);
        if (output.req && output.req.headers) {
                var ip = output.req.headers['x-forwarded-for'] || '169.254.0.1';
                var ipaddr = mod_ipaddr.parse(ip);
                if (ipaddr.kind() === 'ipv4') {
                        output['remoteAddress'] =
                                ipaddr.toIPv4MappedAddress().toString();
                } else {
                        output['remoteAddress'] = ipaddr.toString();
                }
                output.req['request-uri'] = output.req['url'];
                delete output.req.headers['x-forwarded-for'];
                delete output.req['url'];
                delete output.req.headers['authorization'];
        }
        return (output);
}

function write(owner, record, cb) {
        var path = tmpdir + '/' + owner;
        var output = JSON.stringify(record) + '\n';

        if (!files[owner]) {
                files[owner] = true;
                mod_fs.writeFile(path, output, function (err) {
                        if (err) {
                                cb(err);
                                return;
                        }
                        cb();
                });
        } else {
                mod_fs.appendFile(path, output, function (err) {
                        if (err) {
                                cb(err);
                                return;
                        }
                        cb();
                });
        }

}

function saveAll(cb) {
        function save(owner, callback) {
                var login = lookup[owner];
                var key = '/' + login + process.env['ACCESS_DEST'];
                var headers = {
                        'content-type': process.env['HEADER_CONTENT_TYPE']
                };

                if (!login) {
                        console.warn('No login found for UUID ' + owner);
                        return;
                }

                mantaFileSave({
                        client: client,
                        filename: tmpdir + '/' + owner,
                        key: key,
                        headers: headers,
                        log: log,
                        iostream: 'stderr'
                }, function saveCB(err) {
                        if (err) {
                                callback(err);
                                return;
                        }
                        callback(null, key);
                });
        }

        var log = new mod_bunyan({
                name: 'deliver-audit',
                level: 'fatal',
                stream: 'stderr'
        });

        var client = mod_manta.createClient({
                sign: null,
                url: process.env['MANTA_URL']
        });

        mod_vasync.forEachParallel({
                func: save,
                inputs: Object.keys(files)
        }, function (err, results) {
                client.close();
                if (err) {
                        cb(err);
                        return;
                }
                cb(null, results);
        });
}

function main() {
        var carry = mod_carrier.carry(process.openStdin());
        var barrier = mod_vasync.barrier();

        function onLine(line) {
                var record;
                console.log(line); // re-emit each line for aggregation

                // since bunyan logs may contain lines such as
                // [ Nov 28 21:35:27 Enabled. ]
                // we need to ignore them
                if (line[0] != '{') {
                        return;
                }

                try {
                        record = JSON.parse(line);
                } catch (e) {
                        return;
                }

                if (!shouldProcess(record)) {
                        return;
                }

                var output = sanitize(record);
                barrier.start(line);
                write(record.req.owner, output, function (err) {
                        if (err) {
                                console.warn(err.message);
                        }
                        barrier.done(line);
                });
        }

        function onDrain() {
                saveAll(function (err, results) {
                        if (err) {
                                console.warn(err);
                                process.exit(1);
                        }
                });
        }

        carry.once('end', barrier.done.bind(barrier, 'lines'));
        barrier.once('drain', onDrain);

        barrier.start('lines');
        carry.on('line', onLine);
}

main();
<commit_msg>MANTA-1463 request metering producing empty reports<commit_after>#!/usr/node/bin/node
// Copyright (c) 2013, Joyent, Inc. All rights reserved.

var lookupPath = process.env['LOOKUP_FILE'] || '../etc/lookup.json';
var lookup = require(lookupPath); // maps uuid->login
var mantaFileSave = require('manta-compute-bin').mantaFileSave;
var mod_ipaddr = require('ipaddr.js');
var mod_bunyan = require('bunyan');
var mod_carrier = require('carrier');
var mod_fs = require('fs');
var mod_manta = require('manta');
var mod_screen = require('screener');
var mod_vasync = require('vasync');

var files = {}; // keeps track of all the files we create
var ERROR = false;
var tmpdir = '/var/tmp';

/*
 * Allow all headers, then auth and x-forwarded-for headers will be removed
 * after applying this whitelist. The IP in the x-forwarded-for header will
 * be used for the top-level remoteAddress IP.
 */
var whitelist = {
        'req': {
                'method': 'string',
                'url': 'string', // renamed 'request-uri' see RFC 2616
                'headers': true, // XXX see above
                'httpVersion': 'string',
                'caller': {
                        'login': 'string'
                }
        },
        'res': {
                'statusCode': 'number',
                'headers': true
        }
};

function shouldProcess(record) {
        return (record.audit &&
                record.req.url !== '/ping' &&
                typeof (record.req.owner) !== 'undefined');
}

function sanitize(record) {
        var output = mod_screen.screen(record, whitelist);
        if (output.req && output.req.headers) {
                var ip = output.req.headers['x-forwarded-for'] || '169.254.0.1';
                var ipaddr = mod_ipaddr.parse(ip);
                if (ipaddr.kind() === 'ipv4') {
                        output['remoteAddress'] =
                                ipaddr.toIPv4MappedAddress().toString();
                } else {
                        output['remoteAddress'] = ipaddr.toString();
                }
                output.req['request-uri'] = output.req['url'];
                delete output.req.headers['x-forwarded-for'];
                delete output.req['url'];
                delete output.req.headers['authorization'];
        }
        return (output);
}

function write(owner, record, cb) {
        var path = tmpdir + '/' + owner;
        var output = JSON.stringify(record) + '\n';

        if (!files[owner]) {
                files[owner] = true;
                mod_fs.writeFile(path, output, function (err) {
                        if (err) {
                                cb(err);
                                return;
                        }
                        cb();
                });
        } else {
                mod_fs.appendFile(path, output, function (err) {
                        if (err) {
                                cb(err);
                                return;
                        }
                        cb();
                });
        }

}

function saveAll(cb) {
        function save(owner, callback) {
                var login = lookup[owner];
                var key = '/' + login + process.env['ACCESS_DEST'];
                var headers = {
                        'content-type': process.env['HEADER_CONTENT_TYPE']
                };

                if (!login) {
                        console.warn('No login found for UUID ' + owner);
                        return;
                }

                mantaFileSave({
                        client: client,
                        filename: tmpdir + '/' + owner,
                        key: key,
                        headers: headers,
                        log: log,
                        iostream: 'stderr'
                }, function saveCB(err) {
                        if (err) {
                                callback(err);
                                return;
                        }
                        callback(null, key);
                });
        }
        var errors = [];
        var log = new mod_bunyan({
                name: 'deliver-audit',
                level: 'fatal',
                stream: 'stderr'
        });

        var client = mod_manta.createClient({
                sign: null,
                url: process.env['MANTA_URL']
        });

        var queue = mod_vasync.queue(save, 50);
        queue.drain = function () {
                client.close();
                if (errors.length) {
                        cb(errors);
                        return;
                }
                cb();
        };
        queue.push(Object.keys(files), function (err) {
                if (err) {
                        errors.push(err);
                }
        });
}

function main() {
        var carry = mod_carrier.carry(process.openStdin());
        var barrier = mod_vasync.barrier();

        function onLine(line) {
                var record;
                console.log(line); // re-emit each line for aggregation

                // since bunyan logs may contain lines such as
                // [ Nov 28 21:35:27 Enabled. ]
                // we need to ignore them
                if (line[0] != '{') {
                        return;
                }

                try {
                        record = JSON.parse(line);
                } catch (e) {
                        return;
                }

                if (!shouldProcess(record)) {
                        return;
                }

                var output = sanitize(record);
                barrier.start(line);
                write(record.req.owner, output, function (err) {
                        if (err) {
                                console.warn(err.message);
                        }
                        barrier.done(line);
                });
        }

        function onDrain() {
                saveAll(function (err) {
                        if (err) {
                                console.warn(err);
                                process.exit(1);
                        }
                });
        }

        carry.once('end', barrier.done.bind(barrier, 'lines'));
        barrier.once('drain', onDrain);

        barrier.start('lines');
        carry.on('line', onLine);
}

main();
<|endoftext|>