<commit_before>import localforage from "localforage";
import axios from "axios";
import Defaults from "./defaults";

function hasValue(value, array) {
  return (
    array.filter(v => {
      return value.value === v.value;
    }).length > 0
  );
}

function asMulti(object, mKey) {
  return Object.keys(object).map(key => {
    return {
      value: key,
      name: `randomizer.${mKey}.options.${key}`
    };
  });
}

export default {
  namespaced: true,
  state: {
    ...Defaults,
    options: {},
    preset_map: {},
    initializing: true
  },
  getters: {},
  actions: {
    getItemSettings({ commit, dispatch }) {
      return axios
        .get(`/randomizer/settings`)
        .then(response => {
          commit("updateItemSettings", response.data);
        })
        .then(() =>
          Promise.all([
            dispatch("load", ["preset", "setPreset"]),
            dispatch("load", ["glitches_required", "setGlitchesRequired"]),
            dispatch("load", ["item_placement", "setItemPlacement"]),
            dispatch("load", ["dungeon_items", "setDungeonItems"]),
            dispatch("load", ["accessibility", "setAccessibility"]),
            dispatch("load", ["goal", "setGoal"]),
            dispatch("load", ["tower_open", "setTowerOpen"]),
            dispatch("load", ["ganon_open", "setGanonOpen"]),
            dispatch("load", ["world_state", "setWorldState"]),
            dispatch("load", ["entrance_shuffle", "setEntranceShuffle"]),
            dispatch("load", ["boss_shuffle", "setBossShuffle"]),
            dispatch("load", ["enemy_shuffle", "setEnemyShuffle"]),
            dispatch("load", ["hints", "setHints"]),
            dispatch("load", ["weapons", "setWeapons"]),
            dispatch("load", ["item_pool", "setItemPool"]),
            dispatch("load", ["item_functionality", "setItemFunctionality"]),
            dispatch("load", ["enemy_damage", "setEnemyDamage"]),
            dispatch("load", ["enemy_health", "setEnemyHealth"]),
            dispatch("load", ["spoilers", "setSpoiler"])
          ])
        )
        .then(() => {
          commit("setInitalizing", false);
        });
    },
    setPreset({ commit, state }, preset) {
      if (
        preset.value !== "custom" &&
        typeof state.preset_map[preset.value] !== "undefined"
      ) {
        commit(
          "setGlitchesRequired",
          state.preset_map[preset.value]["glitches_required"]
        );
        commit(
          "setItemPlacement",
          state.preset_map[preset.value]["item_placement"]
        );
        commit(
          "setDungeonItems",
          state.preset_map[preset.value]["dungeon_items"]
        );
        commit(
          "setAccessibility",
          state.preset_map[preset.value]["accessibility"]
        );
        commit("setGoal", state.preset_map[preset.value]["goal"]);
        commit("setTowerOpen", state.preset_map[preset.value]["tower_open"]);
        commit("setGanonOpen", state.preset_map[preset.value]["ganon_open"]);
        commit("setWorldState", state.preset_map[preset.value]["world_state"]);
        commit(
          "setEntranceShuffle",
          state.preset_map[preset.value]["entrance_shuffle"]
        );
        commit(
          "setBossShuffle",
          state.preset_map[preset.value]["boss_shuffle"]
        );
        commit(
          "setEnemyShuffle",
          state.preset_map[preset.value]["enemy_shuffle"]
        );
        commit("setHints", state.preset_map[preset.value]["hints"]);
        commit("setWeapons", state.preset_map[preset.value]["weapons"]);
        commit("setItemPool", state.preset_map[preset.value]["item_pool"]);
        commit(
          "setItemFunctionality",
          state.preset_map[preset.value]["item_functionality"]
        );
        commit(
          "setEnemyDamage",
          state.preset_map[preset.value]["enemy_damage"]
        );
        commit(
          "setEnemyHealth",
          state.preset_map[preset.value]["enemy_health"]
        );
      }

      commit("setPreset", preset);
    },
    async load({ commit, state }, [key, mutate]) {
      const value = await localforage.getItem(`randomizer.${key}`);
      if (value !== null && hasValue(value, state.options[key])) {
        commit(mutate, value);
      }
    },
    setGlitchesRequired({ commit, state }, value) {
      commit("setGlitchesRequired", value);

      if (
        ["none", "no_logic"].indexOf(state.glitches_required.value) === -1 &&
        state.entrance_shuffle.value !== "none"
      ) {
        commit("setEntranceShuffle", "none");
      }
    },
    setItemPlacement({ commit, state }, value) {
      commit("setItemPlacement", value);

      if (
        state.item_placement.value !== "advanced" &&
        state.entrance_shuffle.value !== "none"
      ) {
        commit("setEntranceShuffle", "none");
      }
      if (
        state.item_placement.value !== "advanced" &&
        state.item_pool.value === "crowd_control"
      ) {
        commit("setItemPool", "expert");
      }
    },
    setDungeonItems({ commit, state }, value) {
      commit("setDungeonItems", value);

      if (
        ["full", "standard"].indexOf(state.dungeon_items.value) === -1 &&
        state.entrance_shuffle.value !== "none"
      ) {
        commit("setEntranceShuffle", "none");
      }
    },
    setGoal({ commit, state }, value) {
      commit("setGoal", value);

      if (state.goal.value === "dungeons") {
        commit("setGanonOpen", "7");
      }
    },
    setGanonOpen({ commit, state }, value) {
      commit("setGanonOpen", value);

      if (state.ganon_open.value !== "7" && state.goal.value === "dungeons") {
        commit("setGoal", "ganon");
      }
    },
    setWorldState({ commit }, value) {
      commit("setWorldState", value);
    },
    setEntranceShuffle({ commit, state }, value) {
      commit("setEntranceShuffle", value);

      // rules when ER is enabled, hopefully we can reduce this section to be
      // completely removed in the future.
      if (state.entrance_shuffle.value !== "none") {
        if (
          ["none", "no_logic"].indexOf(state.glitches_required.value) === -1
        ) {
          commit("setGlitchesRequired", "none");
        }

        if (state.item_placement.value !== "advanced") {
          commit("setItemPlacement", "advanced");
        }

        if (["full", "standard"].indexOf(state.dungeon_items.value) === -1) {
          commit("setDungeonItems", "standard");
        }
      }
    },
    setItemPool({ commit, state }, value) {
      commit("setItemPool", value);

      if (
        state.item_pool.value === "crowd_control" &&
        state.item_placement.value !== "advanced"
      ) {
        commit("setItemPlacement", "advanced");
      }
    }
  },
  mutations: {
    updateItemSettings(
      state,
      {
        presets,
        glitches_required,
        item_placement,
        dungeon_items,
        accessibility,
        goals,
        tower_open,
        ganon_open,
        world_state,
        entrance_shuffle,
        boss_shuffle,
        enemy_shuffle,
        hints,
        weapons,
        item_pool,
        item_functionality,
        enemy_damage,
        enemy_health,
        spoiler
      }
    ) {
      state.options.preset = asMulti(presets, "preset");
      state.options.glitches_required = asMulti(
        glitches_required,
        "glitches_required"
      );
      state.options.item_placement = asMulti(item_placement, "item_placement");
      state.options.dungeon_items = asMulti(dungeon_items, "dungeon_items");
      state.options.accessibility = asMulti(accessibility, "accessibility");
      state.options.goal = asMulti(goals, "goal");
      state.options.tower_open = asMulti(tower_open, "tower_open");
      state.options.ganon_open = asMulti(ganon_open, "ganon_open");
      state.options.world_state = asMulti(world_state, "world_state");
      state.options.entrance_shuffle = asMulti(
        entrance_shuffle,
        "entrance_shuffle"
      );
      state.options.boss_shuffle = asMulti(boss_shuffle, "boss_shuffle");
      state.options.enemy_shuffle = asMulti(enemy_shuffle, "enemy_shuffle");
      state.options.hints = asMulti(hints, "hints");
      state.options.weapons = asMulti(weapons, "weapons");
      state.options.item_pool = asMulti(item_pool, "item_pool");
      state.options.item_functionality = asMulti(
        item_functionality,
        "item_functionality"
      );
      state.options.enemy_damage = asMulti(enemy_damage, "enemy_damage");
      state.options.enemy_health = asMulti(enemy_health, "enemy_health");
      state.options.spoiler = asMulti(spoiler, "spoiler");
      state.preset_map = presets;
    },
    setPreset(state, value) {
      if (typeof value === "string") {
        value = state.options.preset.find(o => o.value === value);
      }
      state.preset = value;
      localforage.setItem("randomizer.preset", value);
    },
    setGlitchesRequired(state, value) {
      if (typeof value === "string") {
        value = state.options.glitches_required.find(o => o.value === value);
      }
      state.glitches_required = value;
      localforage.setItem("randomizer.glitches_required", value);
    },
    setItemPlacement(state, value) {
      if (typeof value === "string") {
        value = state.options.item_placement.find(o => o.value === value);
      }
      state.item_placement = value;
      localforage.setItem("randomizer.item_placement", value);
    },
    setDungeonItems(state, value) {
      if (typeof value === "string") {
        value = state.options.dungeon_items.find(o => o.value === value);
      }
      state.dungeon_items = value;
      localforage.setItem("randomizer.dungeon_items", value);
    },
    setAccessibility(state, value) {
      if (typeof value === "string") {
        value = state.options.accessibility.find(o => o.value === value);
      }
      state.accessibility = value;
      localforage.setItem("randomizer.accessibility", value);
    },
    setGoal(state, value) {
      if (typeof value === "string") {
        value = state.options.goal.find(o => o.value === value);
      }
      state.goal = value;
      localforage.setItem("randomizer.goal", value);
    },
    setTowerOpen(state, value) {
      if (typeof value === "string") {
        value = state.options.tower_open.find(o => o.value === value);
      }
      state.tower_open = value;
      localforage.setItem("randomizer.tower_open", value);
    },
    setGanonOpen(state, value) {
      if (typeof value === "string") {
        value = state.options.ganon_open.find(o => o.value === value);
      }
      state.ganon_open = value;
      localforage.setItem("randomizer.ganon_open", value);
    },
    setWorldState(state, value) {
      if (typeof value === "string") {
        value = state.options.world_state.find(o => o.value === value);
      }
      state.world_state = value;
      localforage.setItem("randomizer.world_state", value);
    },
    setEntranceShuffle(state, value) {
      if (typeof value === "string") {
        value = state.options.entrance_shuffle.find(o => o.value === value);
      }
      state.entrance_shuffle = value;
      localforage.setItem("randomizer.entrance_shuffle", value);
    },
    setBossShuffle(state, value) {
      if (typeof value === "string") {
        value = state.options.boss_shuffle.find(o => o.value === value);
      }
      state.boss_shuffle = value;
      localforage.setItem("randomizer.boss_shuffle", value);
    },
    setEnemyShuffle(state, value) {
      if (typeof value === "string") {
        value = state.options.enemy_shuffle.find(o => o.value === value);
      }
      state.enemy_shuffle = value;
      localforage.setItem("randomizer.enemy_shuffle", value);
    },
    setHints(state, value) {
      if (typeof value === "string") {
        value = state.options.hints.find(o => o.value === value);
      }
      state.hints = value;
      localforage.setItem("randomizer.hints", value);
    },
    setWeapons(state, value) {
      if (typeof value === "string") {
        value = state.options.weapons.find(o => o.value === value);
      }
      state.weapons = value;
      localforage.setItem("randomizer.weapons", value);
    },
    setItemPool(state, value) {
      if (typeof value === "string") {
        value = state.options.item_pool.find(o => o.value === value);
      }
      state.item_pool = value;
      localforage.setItem("randomizer.item_pool", value);
    },
    setItemFunctionality(state, value) {
      if (typeof value === "string") {
        value = state.options.item_functionality.find(o => o.value === value);
      }
      state.item_functionality = value;
      localforage.setItem("randomizer.item_functionality", value);
    },
    setEnemyDamage(state, value) {
      if (typeof value === "string") {
        value = state.options.enemy_damage.find(o => o.value === value);
      }
      state.enemy_damage = value;
      localforage.setItem("randomizer.enemy_damage", value);
    },
    setEnemyHealth(state, value) {
      if (typeof value === "string") {
        value = state.options.enemy_health.find(o => o.value === value);
      }
      state.enemy_health = value;
      localforage.setItem("randomizer.enemy_health", value);
    },
    setSpoiler(state, value) {
      if (typeof value === "string") {
        value = state.options.spoiler.find(o => o.value === value);
      }
      state.spoiler = value;
      localforage.setItem("randomizer.spoiler", value);
    },
    setInitalizing(state, init) {
      state.initializing = init;
    }
  }
};
<commit_msg>Update randomizer.js<commit_after>import localforage from "localforage";
import axios from "axios";
import Defaults from "./defaults";

function hasValue(value, array) {
  return (
    array.filter(v => {
      return value.value === v.value;
    }).length > 0
  );
}

function asMulti(object, mKey) {
  return Object.keys(object).map(key => {
    return {
      value: key,
      name: `randomizer.${mKey}.options.${key}`
    };
  });
}

export default {
  namespaced: true,
  state: {
    ...Defaults,
    options: {},
    preset_map: {},
    initializing: true
  },
  getters: {},
  actions: {
    getItemSettings({ commit, dispatch }) {
      return axios
        .get(`/randomizer/settings`)
        .then(response => {
          commit("updateItemSettings", response.data);
        })
        .then(() =>
          Promise.all([
            dispatch("load", ["preset", "setPreset"]),
            dispatch("load", ["glitches_required", "setGlitchesRequired"]),
            dispatch("load", ["item_placement", "setItemPlacement"]),
            dispatch("load", ["dungeon_items", "setDungeonItems"]),
            dispatch("load", ["accessibility", "setAccessibility"]),
            dispatch("load", ["goal", "setGoal"]),
            dispatch("load", ["tower_open", "setTowerOpen"]),
            dispatch("load", ["ganon_open", "setGanonOpen"]),
            dispatch("load", ["world_state", "setWorldState"]),
            dispatch("load", ["entrance_shuffle", "setEntranceShuffle"]),
            dispatch("load", ["boss_shuffle", "setBossShuffle"]),
            dispatch("load", ["enemy_shuffle", "setEnemyShuffle"]),
            dispatch("load", ["hints", "setHints"]),
            dispatch("load", ["weapons", "setWeapons"]),
            dispatch("load", ["item_pool", "setItemPool"]),
            dispatch("load", ["item_functionality", "setItemFunctionality"]),
            dispatch("load", ["enemy_damage", "setEnemyDamage"]),
            dispatch("load", ["enemy_health", "setEnemyHealth"]),
            dispatch("load", ["spoilers", "setSpoiler"])
          ])
        )
        .then(() => {
          commit("setInitalizing", false);
        });
    },
    setPreset({ commit, state }, preset) {
      if (
        preset.value !== "custom" &&
        typeof state.preset_map[preset.value] !== "undefined"
      ) {
        commit(
          "setGlitchesRequired",
          state.preset_map[preset.value]["glitches_required"]
        );
        commit(
          "setItemPlacement",
          state.preset_map[preset.value]["item_placement"]
        );
        commit(
          "setDungeonItems",
          state.preset_map[preset.value]["dungeon_items"]
        );
        commit(
          "setAccessibility",
          state.preset_map[preset.value]["accessibility"]
        );
        commit("setGoal", state.preset_map[preset.value]["goal"]);
        commit("setTowerOpen", state.preset_map[preset.value]["tower_open"]);
        commit("setGanonOpen", state.preset_map[preset.value]["ganon_open"]);
        commit("setWorldState", state.preset_map[preset.value]["world_state"]);
        commit(
          "setEntranceShuffle",
          state.preset_map[preset.value]["entrance_shuffle"]
        );
        commit(
          "setBossShuffle",
          state.preset_map[preset.value]["boss_shuffle"]
        );
        commit(
          "setEnemyShuffle",
          state.preset_map[preset.value]["enemy_shuffle"]
        );
        commit("setHints", state.preset_map[preset.value]["hints"]);
        commit("setWeapons", state.preset_map[preset.value]["weapons"]);
        commit("setItemPool", state.preset_map[preset.value]["item_pool"]);
        commit(
          "setItemFunctionality",
          state.preset_map[preset.value]["item_functionality"]
        );
        commit(
          "setEnemyDamage",
          state.preset_map[preset.value]["enemy_damage"]
        );
        commit(
          "setEnemyHealth",
          state.preset_map[preset.value]["enemy_health"]
        );
      }

      commit("setPreset", preset);
    },
    async load({ commit, state }, [key, mutate]) {
      const value = await localforage.getItem(`randomizer.${key}`);
      if (value !== null && hasValue(value, state.options[key])) {
        commit(mutate, value);
      }
    },
    setGlitchesRequired({ commit, state }, value) {
      commit("setGlitchesRequired", value);

      if (
        ["none", "no_logic"].indexOf(state.glitches_required.value) === -1 &&
        state.entrance_shuffle.value !== "none"
      ) {
        commit("setEntranceShuffle", "none");
      }
    },
    setItemPlacement({ commit, state }, value) {
      commit("setItemPlacement", value);

      if (
        state.item_placement.value !== "advanced" &&
        state.entrance_shuffle.value !== "none"
      ) {
        commit("setEntranceShuffle", "none");
      }
      if (
        state.item_placement.value !== "advanced" &&
        state.item_pool.value === "crowd_control"
      ) {
        commit("setItemPool", "expert");
      }
    },
    setDungeonItems({ commit, state }, value) {
      commit("setDungeonItems", value);

      if (
        ["full", "standard"].indexOf(state.dungeon_items.value) === -1 &&
        state.entrance_shuffle.value !== "none"
      ) {
        commit("setEntranceShuffle", "none");
      }
    },
    setGoal({ commit, state }, value) {
      commit("setGoal", value);

      if (state.goal.value === "dungeons") {
        commit("setGanonOpen", "7");
      }
    },
    setGanonOpen({ commit, state }, value) {
      commit("setGanonOpen", value);

      if (state.ganon_open.value !== "7" && state.goal.value === "dungeons") {
        commit("setGoal", "ganon");
      }
    },
    setWorldState({ commit }, value) {
      commit("setWorldState", value);
    },
    setEntranceShuffle({ commit, state }, value) {
      commit("setEntranceShuffle", value);

      // rules when ER is enabled, hopefully we can reduce this section to be
      // completely removed in the future.
      if (state.entrance_shuffle.value !== "none") {
        if (
          ["none", "no_logic"].indexOf(state.glitches_required.value) === -1
        ) {
          commit("setGlitchesRequired", "none");
        }

        if (state.item_placement.value !== "advanced") {
          commit("setItemPlacement", "advanced");
        }

        if (["full", "standard"].indexOf(state.dungeon_items.value) === -1) {
          commit("setDungeonItems", "standard");
        }
      }
    },
    setItemPool({ commit, state }, value) {
      commit("setItemPool", value);

      if (
        state.item_pool.value === "crowd_control" &&
        state.item_placement.value !== "advanced"
      ) {
        commit("setItemPlacement", "advanced");
      }
    }
  },
  mutations: {
    updateItemSettings(
      state,
      {
        presets,
        glitches_required,
        item_placement,
        dungeon_items,
        accessibility,
        goals,
        tower_open,
        ganon_open,
        world_state,
        entrance_shuffle,
        boss_shuffle,
        enemy_shuffle,
        hints,
        weapons,
        item_pool,
        item_functionality,
        enemy_damage,
        enemy_health,
        spoilers
      }
    ) {
      state.options.preset = asMulti(presets, "preset");
      state.options.glitches_required = asMulti(
        glitches_required,
        "glitches_required"
      );
      state.options.item_placement = asMulti(item_placement, "item_placement");
      state.options.dungeon_items = asMulti(dungeon_items, "dungeon_items");
      state.options.accessibility = asMulti(accessibility, "accessibility");
      state.options.goal = asMulti(goals, "goal");
      state.options.tower_open = asMulti(tower_open, "tower_open");
      state.options.ganon_open = asMulti(ganon_open, "ganon_open");
      state.options.world_state = asMulti(world_state, "world_state");
      state.options.entrance_shuffle = asMulti(
        entrance_shuffle,
        "entrance_shuffle"
      );
      state.options.boss_shuffle = asMulti(boss_shuffle, "boss_shuffle");
      state.options.enemy_shuffle = asMulti(enemy_shuffle, "enemy_shuffle");
      state.options.hints = asMulti(hints, "hints");
      state.options.weapons = asMulti(weapons, "weapons");
      state.options.item_pool = asMulti(item_pool, "item_pool");
      state.options.item_functionality = asMulti(
        item_functionality,
        "item_functionality"
      );
      state.options.enemy_damage = asMulti(enemy_damage, "enemy_damage");
      state.options.enemy_health = asMulti(enemy_health, "enemy_health");
      state.options.spoiler = asMulti(spoilers, "spoiler");
      state.preset_map = presets;
    },
    setPreset(state, value) {
      if (typeof value === "string") {
        value = state.options.preset.find(o => o.value === value);
      }
      state.preset = value;
      localforage.setItem("randomizer.preset", value);
    },
    setGlitchesRequired(state, value) {
      if (typeof value === "string") {
        value = state.options.glitches_required.find(o => o.value === value);
      }
      state.glitches_required = value;
      localforage.setItem("randomizer.glitches_required", value);
    },
    setItemPlacement(state, value) {
      if (typeof value === "string") {
        value = state.options.item_placement.find(o => o.value === value);
      }
      state.item_placement = value;
      localforage.setItem("randomizer.item_placement", value);
    },
    setDungeonItems(state, value) {
      if (typeof value === "string") {
        value = state.options.dungeon_items.find(o => o.value === value);
      }
      state.dungeon_items = value;
      localforage.setItem("randomizer.dungeon_items", value);
    },
    setAccessibility(state, value) {
      if (typeof value === "string") {
        value = state.options.accessibility.find(o => o.value === value);
      }
      state.accessibility = value;
      localforage.setItem("randomizer.accessibility", value);
    },
    setGoal(state, value) {
      if (typeof value === "string") {
        value = state.options.goal.find(o => o.value === value);
      }
      state.goal = value;
      localforage.setItem("randomizer.goal", value);
    },
    setTowerOpen(state, value) {
      if (typeof value === "string") {
        value = state.options.tower_open.find(o => o.value === value);
      }
      state.tower_open = value;
      localforage.setItem("randomizer.tower_open", value);
    },
    setGanonOpen(state, value) {
      if (typeof value === "string") {
        value = state.options.ganon_open.find(o => o.value === value);
      }
      state.ganon_open = value;
      localforage.setItem("randomizer.ganon_open", value);
    },
    setWorldState(state, value) {
      if (typeof value === "string") {
        value = state.options.world_state.find(o => o.value === value);
      }
      state.world_state = value;
      localforage.setItem("randomizer.world_state", value);
    },
    setEntranceShuffle(state, value) {
      if (typeof value === "string") {
        value = state.options.entrance_shuffle.find(o => o.value === value);
      }
      state.entrance_shuffle = value;
      localforage.setItem("randomizer.entrance_shuffle", value);
    },
    setBossShuffle(state, value) {
      if (typeof value === "string") {
        value = state.options.boss_shuffle.find(o => o.value === value);
      }
      state.boss_shuffle = value;
      localforage.setItem("randomizer.boss_shuffle", value);
    },
    setEnemyShuffle(state, value) {
      if (typeof value === "string") {
        value = state.options.enemy_shuffle.find(o => o.value === value);
      }
      state.enemy_shuffle = value;
      localforage.setItem("randomizer.enemy_shuffle", value);
    },
    setHints(state, value) {
      if (typeof value === "string") {
        value = state.options.hints.find(o => o.value === value);
      }
      state.hints = value;
      localforage.setItem("randomizer.hints", value);
    },
    setWeapons(state, value) {
      if (typeof value === "string") {
        value = state.options.weapons.find(o => o.value === value);
      }
      state.weapons = value;
      localforage.setItem("randomizer.weapons", value);
    },
    setItemPool(state, value) {
      if (typeof value === "string") {
        value = state.options.item_pool.find(o => o.value === value);
      }
      state.item_pool = value;
      localforage.setItem("randomizer.item_pool", value);
    },
    setItemFunctionality(state, value) {
      if (typeof value === "string") {
        value = state.options.item_functionality.find(o => o.value === value);
      }
      state.item_functionality = value;
      localforage.setItem("randomizer.item_functionality", value);
    },
    setEnemyDamage(state, value) {
      if (typeof value === "string") {
        value = state.options.enemy_damage.find(o => o.value === value);
      }
      state.enemy_damage = value;
      localforage.setItem("randomizer.enemy_damage", value);
    },
    setEnemyHealth(state, value) {
      if (typeof value === "string") {
        value = state.options.enemy_health.find(o => o.value === value);
      }
      state.enemy_health = value;
      localforage.setItem("randomizer.enemy_health", value);
    },
    setSpoiler(state, value) {
      if (typeof value === "string") {
        value = state.options.spoiler.find(o => o.value === value);
      }
      state.spoiler = value;
      localforage.setItem("randomizer.spoiler", value);
    },
    setInitalizing(state, init) {
      state.initializing = init;
    }
  }
};
<|endoftext|>


<commit_before>/*
 Copyright 2017 Paul Zuehlcke - paul.zuehlcke.de

 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.
 */

import LWCommand from "../LWCommand";
import UsageError from "../UsageError";

export default class CalcCMD extends LWCommand {
    constructor() {
        super("calc", "Calculates a simple math expression", "calc <expression>", "TheBiochemic", true);
    }

    run(args) {
        args = args.join("");
        if (args.length > 0) {
            let expResult = CalcCMD.parseExpression(args);
            if (expResult === undefined || isNaN(expResult))
                throw new UsageError("This is not a valid expression!");
            return expResult.toString();
        }
        else
            throw new UsageError();
    }

    static parseExpression(expression) {
        let regex = /(\d|[.()]|[+\-*\/]|[\^|&%]|(==|!=))/;
        let exclude = /([a-z]|[A-Z])/;
        if (regex.test(expression) && !exclude.test(expression)) {
            return eval(expression);
        }
    }
}
<commit_msg>Simplified regex exclusion and made it more precise (in Calc Command)<commit_after>/*
 Copyright 2017 Paul Zuehlcke - paul.zuehlcke.de

 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.
 */

import LWCommand from "../LWCommand";
import UsageError from "../UsageError";

export default class CalcCMD extends LWCommand {
    constructor() {
        super("calc", "Calculates a simple math expression", "calc <expression>", "TheBiochemic", true);
    }

    run(args) {
        args = args.join("");
        if (args.length > 0) {
            let expResult = CalcCMD.parseExpression(args);
            if (expResult === undefined || isNaN(expResult))
                throw new UsageError("This is not a valid expression!");
            return expResult.toString();
        }
        else
            throw new UsageError();
    }

    static parseExpression(expression) {
        let regex = /([^\d\.\(\)\+\-\*\/\^\|\&\%\=\!]|([^=!]=[^=]))/;
        if (!regex.test(expression)) {
            return eval(expression);
        }
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

(function () {
    "use strict";

    var parse = require("./parser").parse;

    /**
     * The ParserManager manages parsing, normalization and analysis of layer
     * names into asset specifications. The config parameter can be used to enable
     * svg and webp parsing if the "svg-enabled" and "webp-enabled" parameters are
     * set, resp.
     *
     * @constructor
     * @param {object} config
     */
    function ParserManager(config) {
        this._config = config || {};

        this._supportedUnits = {
            "in": true,
            "cm": true,
            "px": true,
            "mm": true
        };

        this._supportedExtensions = {
            "jpg": true,
            "png": true,
            "gif": true,
            "svg": !!this._config["svg-enabled"],
            "webp": !!this._config["webp-enabled"]
        };
    }

    /**
     * Set of supported units of measurement.
     *
     * @type {{string: boolean}}
     */
    ParserManager.prototype._supportedUnits = null;

    /**
     * Set of supported units of file extensions.
     *
     * @type {{string: boolean}}
     */
    ParserManager.prototype._supportedExtensions = null;

    /**
     * Parse a layer name into a non-empty array of file specification parts.
     * If a given layer specification part can be parsed into a file specification,
     * then the resulting object has at least "file" and "extension" properties,
     * and possibly also "quality", "width", "height", "widthUnit" and "heightUnit"
     * properties as well. Otherwise, if a given layer name part (or the entire
     * layer name) can't be parsed into a file specification, then the resulting
     * object just has a single "name" property, which is the same as the input
     * string.
     *
     * @private
     * @param {string} layerName
     * @returns {Array.<{name: string} | {file: string, extension: string}>}
     */
    ParserManager.prototype._parseLayerName = function (layerName) {
        return parse(layerName);
    };

    /**
     * Normalize the properties of the given component. Updates the object in place.
     *
     * @private
     * @param {Component} component
     */
    ParserManager.prototype._normalizeComponent = function (component) {
        if (component.hasOwnProperty("extension")) {
            var extension = component.extension.toLowerCase();

            if (extension === "jpeg") {
                extension = "jpg";
            }

            component.extension = extension;

            if (component.hasOwnProperty("quality")) {
                var quality = component.quality;
                if (quality[quality.length - 1] === "%") {
                    quality = parseInt(quality.substring(0, quality.length - 1), 10);
                } else if (extension === "png" && quality[quality.length - 1] === "a") {
                    // normalize png24a -> png32
                    quality = parseInt(quality.substring(0, quality.length - 1), 10);
                    quality += 8;
                } else {
                    quality = parseInt(quality, 10);

                    if (extension !== "png") {
                        quality *= 10;
                    }
                }

                component.quality = quality;
            }
        }

        if (component.hasOwnProperty("widthUnit")) {
            component.widthUnit = component.widthUnit.toLowerCase();
        }

        if (component.hasOwnProperty("heightUnit")) {
            component.heightUnit = component.heightUnit.toLowerCase();
        }
    };

    /**
     * Analyze the component, returning a list of errors.
     *
     * @private
     * @param {Component} component
     * @return {Array.<string>} The possibly empty list of analysis errors.
     */
    ParserManager.prototype._analyzeComponent = function (component) {
        var errors = [];

        if (component.scale === 0) {
            errors.push("Invalid scale: 0%");
        }

        if (component.width === 0) {
            errors.push("Invalid width: 0");
        }

        if (component.height === 0) {
            errors.push("Invalid height: 0");
        }

        if (component.widthUnit && !this._supportedUnits[component.widthUnit]) {
            errors.push("Invalid width unit: " + component.widthUnit);
        }

        if (component.heightUnit && !this._supportedUnits[component.heightUnit]) {
            errors.push("Invalid height unit: " + component.heightUnit);
        }

        if (component.extension && !this._supportedExtensions[component.extension]) {
            errors.push("Unsupported extension: " + component.extension);
        }

        if (component.hasOwnProperty("quality")) {
            var quality = component.quality,
                invalidQuality = false;

            switch (component.extension) {
            case "jpg":
            case "webp":
                if (quality < 1 || quality > 100) {
                    invalidQuality = true;
                }
                break;
            case "png":
                if (!(quality === 8 || quality === 24 || quality === 32)) {
                    invalidQuality = true;
                }
                break;
            default:
                invalidQuality = true;
            }

            if (invalidQuality) {
                errors.push("Invalid quality: " + quality);
            }
        }

        return errors;
    };

    /**
     * Parse a layer name, returning a list of objects that contain a parsed
     * component and a list of errors encountered while analyzing that component.
     * The component denotes a valid asset iff there are no analysis errors and
     * the component contains either a "file" property (if it is a "basic"
     * component that describes a single asset) or a "default" property (if it
     * is default component that is used to derive non-basic components.)
     *
     * @param {string} layerName
     * @return {Array.<{component: Component, errors: Array.<string>}>}
     */
    ParserManager.prototype.analyzeLayerName = function (layerName) {
        var components;

        try {
            components = this._parseLayerName(layerName);
        } catch (parseError) {
            return [{
                component: { name: layerName },
                errors: [parseError.message]
            }];
        }

        return components.map(function (component) {
            this._normalizeComponent(component);

            return {
                component: component,
                errors: this._analyzeComponent(component)
            };
        }, this);
    };

    module.exports = ParserManager;
}());
<commit_msg>SVG is baaaaack...<commit_after>/*
 * Copyright (c) 2014 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

(function () {
    "use strict";

    var parse = require("./parser").parse;

    /**
     * The ParserManager manages parsing, normalization and analysis of layer
     * names into asset specifications. The config parameter can be used to enable
     * svg and webp parsing if the "svg-enabled" and "webp-enabled" parameters are
     * set, resp.
     *
     * @constructor
     * @param {object} config
     */
    function ParserManager(config) {
        this._config = config || {};

        this._supportedUnits = {
            "in": true,
            "cm": true,
            "px": true,
            "mm": true
        };

        this._supportedExtensions = {
            "jpg": true,
            "png": true,
            "gif": true,
            "svg": this._config.hasOwnProperty("svg-enabled") ? !!this._config["svg-enabled"] : true,
            "webp": !!this._config["webp-enabled"]
        };
    }

    /**
     * Set of supported units of measurement.
     *
     * @type {{string: boolean}}
     */
    ParserManager.prototype._supportedUnits = null;

    /**
     * Set of supported units of file extensions.
     *
     * @type {{string: boolean}}
     */
    ParserManager.prototype._supportedExtensions = null;

    /**
     * Parse a layer name into a non-empty array of file specification parts.
     * If a given layer specification part can be parsed into a file specification,
     * then the resulting object has at least "file" and "extension" properties,
     * and possibly also "quality", "width", "height", "widthUnit" and "heightUnit"
     * properties as well. Otherwise, if a given layer name part (or the entire
     * layer name) can't be parsed into a file specification, then the resulting
     * object just has a single "name" property, which is the same as the input
     * string.
     *
     * @private
     * @param {string} layerName
     * @returns {Array.<{name: string} | {file: string, extension: string}>}
     */
    ParserManager.prototype._parseLayerName = function (layerName) {
        return parse(layerName);
    };

    /**
     * Normalize the properties of the given component. Updates the object in place.
     *
     * @private
     * @param {Component} component
     */
    ParserManager.prototype._normalizeComponent = function (component) {
        if (component.hasOwnProperty("extension")) {
            var extension = component.extension.toLowerCase();

            if (extension === "jpeg") {
                extension = "jpg";
            }

            component.extension = extension;

            if (component.hasOwnProperty("quality")) {
                var quality = component.quality;
                if (quality[quality.length - 1] === "%") {
                    quality = parseInt(quality.substring(0, quality.length - 1), 10);
                } else if (extension === "png" && quality[quality.length - 1] === "a") {
                    // normalize png24a -> png32
                    quality = parseInt(quality.substring(0, quality.length - 1), 10);
                    quality += 8;
                } else {
                    quality = parseInt(quality, 10);

                    if (extension !== "png") {
                        quality *= 10;
                    }
                }

                component.quality = quality;
            }
        }

        if (component.hasOwnProperty("widthUnit")) {
            component.widthUnit = component.widthUnit.toLowerCase();
        }

        if (component.hasOwnProperty("heightUnit")) {
            component.heightUnit = component.heightUnit.toLowerCase();
        }
    };

    /**
     * Analyze the component, returning a list of errors.
     *
     * @private
     * @param {Component} component
     * @return {Array.<string>} The possibly empty list of analysis errors.
     */
    ParserManager.prototype._analyzeComponent = function (component) {
        var errors = [];

        if (component.scale === 0) {
            errors.push("Invalid scale: 0%");
        }

        if (component.width === 0) {
            errors.push("Invalid width: 0");
        }

        if (component.height === 0) {
            errors.push("Invalid height: 0");
        }

        if (component.widthUnit && !this._supportedUnits[component.widthUnit]) {
            errors.push("Invalid width unit: " + component.widthUnit);
        }

        if (component.heightUnit && !this._supportedUnits[component.heightUnit]) {
            errors.push("Invalid height unit: " + component.heightUnit);
        }

        if (component.extension && !this._supportedExtensions[component.extension]) {
            errors.push("Unsupported extension: " + component.extension);
        }

        if (component.hasOwnProperty("quality")) {
            var quality = component.quality,
                invalidQuality = false;

            switch (component.extension) {
            case "jpg":
            case "webp":
                if (quality < 1 || quality > 100) {
                    invalidQuality = true;
                }
                break;
            case "png":
                if (!(quality === 8 || quality === 24 || quality === 32)) {
                    invalidQuality = true;
                }
                break;
            default:
                invalidQuality = true;
            }

            if (invalidQuality) {
                errors.push("Invalid quality: " + quality);
            }
        }

        return errors;
    };

    /**
     * Parse a layer name, returning a list of objects that contain a parsed
     * component and a list of errors encountered while analyzing that component.
     * The component denotes a valid asset iff there are no analysis errors and
     * the component contains either a "file" property (if it is a "basic"
     * component that describes a single asset) or a "default" property (if it
     * is default component that is used to derive non-basic components.)
     *
     * @param {string} layerName
     * @return {Array.<{component: Component, errors: Array.<string>}>}
     */
    ParserManager.prototype.analyzeLayerName = function (layerName) {
        var components;

        try {
            components = this._parseLayerName(layerName);
        } catch (parseError) {
            return [{
                component: { name: layerName },
                errors: [parseError.message]
            }];
        }

        return components.map(function (component) {
            this._normalizeComponent(component);

            return {
                component: component,
                errors: this._analyzeComponent(component)
            };
        }, this);
    };

    module.exports = ParserManager;
}());
<|endoftext|>


<commit_before>#!/usr/bin/env node

var program = require('commander');
var mkdirp = require('mkdirp');
var os = require('os');
var fs = require('fs');
var path = require('path');
var inflection = require('../templates/js/lib/inflection');

var pkg = require('../package.json');

var version = pkg.version;

function list(val) {
    return val.split(' ').map(String);
}

// CLI

program
        .version(version)
        .usage('[options] [dir]')
        .option('-i, --init <appname>', 'create caminte application')
        .option('-g, --generate <modelname]', 'generate data model')
        .option('-s, --server', 'runs caminte server')
        .option('-j, --jade', 'add jade engine support (defaults to ejs)')
        .option('-H, --hogan', 'add hogan.js engine support')
        .option('-c, --css <engine>', 'add css <engine> support (less|stylus|compass) (defaults to plain css)')
        .option('-f, --force', 'force on non-empty directory')
        .parse(process.argv);

// Path

var destination_path = './';

// end-of-line code

var eol = os.EOL;

// Template engine

program.template = 'ejs';

if (program.jade) {
    program.template = 'jade';
}
if (program.hogan) {
    program.template = 'hjs';
}

if (!program.init && !program.generate && !program.server) {
    program.help();
} else if (program.init) {
    destination_path += program.init;
    createApplication(destination_path);
} else if (program.generate) {
    destination_path += 'models';
    createModel(destination_path);
} else if (program.server) {
    destination_path += 'bin/www';
    if (fs.existsSync(destination_path)) {
        require(path.resolve(destination_path));
    } else {
        abort('caminte application ' + destination_path + ' file not found!');
    }
}

function load_template(name) {
    return fs.readFileSync(path.join(__dirname, '..', 'templates', name), 'utf-8');
}

var index = load_template('js/routes/index.js');
var rest = load_template('js/routes/rest.js');

// css
var css = fs.readFileSync(__dirname + '/../templates/css/style.css', 'utf-8');
var less = fs.readFileSync(__dirname + '/../templates/css/style.less', 'utf-8');
var stylus = fs.readFileSync(__dirname + '/../templates/css/style.styl', 'utf-8');
var compass = fs.readFileSync(__dirname + '/../templates/css/style.scss', 'utf-8');

var app = fs.readFileSync(__dirname + '/../templates/js/app.js', 'utf-8');
var www = fs.readFileSync(__dirname + '/../templates/js/www', 'utf-8');
var cfg = fs.readFileSync(__dirname + '/../templates/js/config.js', 'utf-8');

var inflection = fs.readFileSync(__dirname + '/../templates/js/lib/inflection.js', 'utf-8');
var pager = fs.readFileSync(__dirname + '/../templates/js/lib/pager.js', 'utf-8');
var tools = fs.readFileSync(__dirname + '/../templates/js/lib/tools.js', 'utf-8');
var xml = fs.readFileSync(__dirname + '/../templates/js/lib/xml.js', 'utf-8');

// Generate application
function createApplication(path) {
    emptyDirectory(path, function(empty) {
        if (empty || program.force) {
            createApplicationAt(path);
        } else {
            program.confirm('destination is not empty, continue? ', function(ok) {
                if (ok) {
                    process.stdin.destroy();
                    createApplicationAt(path);
                } else {
                    abort('aborting');
                }
            });
        }
    });
}

// Generate Model
function createModel(root) {
    var modelName = program.generate.toLowerCase().capitalize();
    var pathToModel = path.resolve(root + '/' + modelName + '.js');
    existsFile(pathToModel, function(empty) {
        if (!empty || program.force) {
            createModelAt(root, modelName);
        } else {
            program.confirm('model is exists, continue? ', function(ok) {
                if (ok) {
                    process.stdin.destroy();
                    createModelAt(root, modelName);
                } else {
                    abort('aborting');
                }
            });
        }
    });
}

/**
 * Create model at the given `name`.
 *
 * @param {String} pathToModels
 * @param {String} modelName
 */
function createModelAt(pathToModels, modelName) {
    var modelFile = path.normalize(pathToModels + '/' + modelName + '.js');
    var modelTemplate = fs.readFileSync(__dirname + '/../templates/model/Model.ejs', 'utf-8');
    // mkdir(path + '/models');
    var fields = parseFields();
    modelTemplate = modelTemplate.replace('{fields}', fields.join(',\n'));
    modelTemplate = modelTemplate.replace(/{name}/gi, modelName);
    modelTemplate = modelTemplate.replace('{nameToLowerCase}', modelName.toLowerCase());
    write(modelFile, modelTemplate);
}

// parse Fields
function parseFields() {
    var fields = [];
    for (var i in process.argv) {
        // Skip the first two - Node and app.js path
        if (i > 3) {
            var fdata = process.argv[i].split(':');
            var field = '';
            if (fdata[1]) {
                var type = fdata[1].toLowerCase();
                switch (type) {
                    case 'int':
                    case 'integer':
                    case 'double':
                    case 'number':
                        type = 'type : Number';
                        break;
                    case 'bool':
                    case 'boolean':
                        type = 'type : Boolean';
                        break;
                    case 'str':
                    case 'string':
                        type = 'type : String';
                        break;
                    case 'text':
                        type = 'type : schema.Text';
                        break;
                    case 'json':
                        type = 'type : schema.JSON';
                        break;
                    default:
                        type = 'type : String';
                }
                field += type;
            }
            if (fdata[2]) {
                field += ", 'default' : " + fdata[2];
            }
            fields.push('           ' + fdata[0] + ' : { ' + field + ' }');
        }
    }
    return fields;
}

/**
 * Create application at the given directory `path`.
 *
 * @param {String} path
 */
function createApplicationAt(path) {
    console.log();
    process.on('exit', function() {
        console.log();
        console.log('   install dependencies:');
        console.log('     $ cd %s && npm install', path);
        console.log();
        console.log('   run the app:');
        console.log('     $ DEBUG=' + program.init + ' ./bin/www');
        console.log();
    });

    mkdir(path, function() {
        mkdir(path + '/models');
        mkdir(path + '/public');
        mkdir(path + '/public/js');
        mkdir(path + '/public/images');
        mkdir(path + '/public/css', function() {
            switch (program.css) {
                case 'less':
                    write(path + '/public/css/style.less', less);
                    break;
                case 'stylus':
                    write(path + '/public/css/style.styl', stylus);
                    break;
                case 'compass':
                    write(path + '/public/css/style.scss', compass);
                    break;
                default:
                    write(path + '/public/css/style.css', css);
            }
        });

        mkdir(path + '/routes', function() {
            write(path + '/routes/index.js', index);
            write(path + '/routes/rest.js', rest);
        });

        mkdir(path + '/lib', function() {
            write(path + '/lib/inflection.js', inflection);
            write(path + '/lib/pager.js', pager);
            write(path + '/lib/tools.js', tools);
            write(path + '/lib/xml.js', xml);
        });

        mkdir(path + '/views', function() {
            switch (program.template) {
                case 'ejs':
                    copy_template('ejs/index.ejs', path + '/views/index.ejs');
                    copy_template('ejs/error.ejs', path + '/views/error.ejs');
                    break;
                case 'jade':
                    copy_template('jade/index.jade', path + '/views/index.jade');
                    copy_template('jade/layout.jade', path + '/views/layout.jade');
                    copy_template('jade/error.jade', path + '/views/error.jade');
                    break;
                case 'hjs':
                    copy_template('hogan/index.hjs', path + '/views/index.hjs');
                    copy_template('hogan/error.hjs', path + '/views/error.hjs');
                    break;
            }
        });

        // CSS Engine support
        switch (program.css) {
            case 'less':
                app = app.replace('{css}', eol + 'app.use(require(\'less-middleware\')({ src: path.join(__dirname, \'public\') }));');
                break;
            case 'stylus':
                app = app.replace('{css}', eol + 'app.use(require(\'stylus\').middleware(path.join(__dirname, \'public\')));');
                break;
            case 'compass':
                app = app.replace('{css}', eol + 'app.use(require(\'node-compass\')({mode: \'expanded\'}));');
                break;
            default:
                app = app.replace('{css}', '');
        }

        // Template support
        app = app.replace('{views}', program.template);

        // package.json
        var pkg = {
            name: program.init
            , version: '0.0.1'
            , private: true
            , scripts: {start: 'node ./bin/www'}
            , dependencies: {
                'caminte': '~0.0.14',
                'express': '~3.4.8',
                'static-favicon': '~1.0.0',
                'morgan': '~1.0.0',
                'cookie-parser': '~1.0.1',
                'body-parser': '~1.0.0',
                'debug': '~0.7.4'
            }
        };

        switch (program.template) {
            case 'jade':
                pkg.dependencies['jade'] = '~1.3.0';
                break;
            case 'ejs':
                pkg.dependencies['ejs'] = '~0.8.5';
                break;
            case 'hjs':
                pkg.dependencies['hjs'] = '~0.0.6';
                break;
            default:
        }

        // CSS Engine support
        switch (program.css) {
            case 'less':
                pkg.dependencies['less-middleware'] = '0.1.15';
                break;
            case 'compass':
                pkg.dependencies['node-compass'] = '0.2.3';
                break;
            case 'stylus':
                pkg.dependencies['stylus'] = '0.42.3';
                break;
            default:
        }

        write(path + '/package.json', JSON.stringify(pkg, null, 2));
        write(path + '/app.js', app);
        mkdir(path + '/bin', function() {
            www = www.replace('{app}', program.init);
            write(path + '/bin/www', www, 0755);
        });
        mkdir(path + '/config', function() {
            write(path + '/config/index.js', cfg, 0755);
        });
    });
}

function copy_template(from, to) {
    from = path.join(__dirname, '..', 'templates', from);
    write(to, fs.readFileSync(from, 'utf-8'));
}

/**
 * Check if the given directory `path` is empty.
 *
 * @param {String} path
 * @param {Function} fn
 */
function emptyDirectory(path, fn) {
    fs.readdir(path, function(err, files) {
        if (err && 'ENOENT' !== err.code)
            throw err;
        fn(!files || !files.length);
    });
}

/**
 * Check if the given file `path` exists.
 *
 * @param {String} path
 * @param {Function} fn
 */
function existsFile(path, fn) {
    fs.exists(path, function(exists) {
        fn(exists);
    });
}

/**
 * echo str > path.
 *
 * @param {String} path
 * @param {String} str
 * @param {Number} mode
 */
function write(path, str, mode) {
    fs.writeFile(path, str, {mode: mode || 0666});
    console.log('   \x1b[36mcreate\x1b[0m : ' + path);
}

/**
 * Mkdir -p.
 *
 * @param {String} path
 * @param {Function} fn
 */
function mkdir(path, fn) {
    mkdirp(path, 0755, function(err) {
        if (err)
            throw err;
        console.log('   \033[36mcreate\033[0m : ' + path);
        fn && fn();
    });
}

/**
 * Exit with the given `str`.
 *
 * @param {String} str
 */
function abort(str) {
    console.error(str);
    process.exit(1);
}
<commit_msg>Fix field type<commit_after>#!/usr/bin/env node

var program = require('commander');
var mkdirp = require('mkdirp');
var os = require('os');
var fs = require('fs');
var path = require('path');
var inflection = require('../templates/js/lib/inflection');

var pkg = require('../package.json');

var version = pkg.version;

function list(val) {
    return val.split(' ').map(String);
}

// CLI

program
        .version(version)
        .usage('[options] [dir]')
        .option('-i, --init <appname>', 'create caminte application')
        .option('-g, --generate <modelname]', 'generate data model')
        .option('-s, --server', 'runs caminte server')
        .option('-j, --jade', 'add jade engine support (defaults to ejs)')
        .option('-H, --hogan', 'add hogan.js engine support')
        .option('-c, --css <engine>', 'add css <engine> support (less|stylus|compass) (defaults to plain css)')
        .option('-f, --force', 'force on non-empty directory')
        .parse(process.argv);

// Path

var destination_path = './';

// end-of-line code

var eol = os.EOL;

// Template engine

program.template = 'ejs';

if (program.jade) {
    program.template = 'jade';
}
if (program.hogan) {
    program.template = 'hjs';
}

if (!program.init && !program.generate && !program.server) {
    program.help();
} else if (program.init) {
    destination_path += program.init;
    createApplication(destination_path);
} else if (program.generate) {
    destination_path += 'models';
    createModel(destination_path);
} else if (program.server) {
    destination_path += 'bin/www';
    if (fs.existsSync(destination_path)) {
        require(path.resolve(destination_path));
    } else {
        abort('caminte application ' + destination_path + ' file not found!');
    }
}

function load_template(name) {
    return fs.readFileSync(path.join(__dirname, '..', 'templates', name), 'utf-8');
}

var index = load_template('js/routes/index.js');
var rest = load_template('js/routes/rest.js');

// css
var css = fs.readFileSync(__dirname + '/../templates/css/style.css', 'utf-8');
var less = fs.readFileSync(__dirname + '/../templates/css/style.less', 'utf-8');
var stylus = fs.readFileSync(__dirname + '/../templates/css/style.styl', 'utf-8');
var compass = fs.readFileSync(__dirname + '/../templates/css/style.scss', 'utf-8');

var app = fs.readFileSync(__dirname + '/../templates/js/app.js', 'utf-8');
var www = fs.readFileSync(__dirname + '/../templates/js/www', 'utf-8');
var cfg = fs.readFileSync(__dirname + '/../templates/js/config.js', 'utf-8');

var inflection = fs.readFileSync(__dirname + '/../templates/js/lib/inflection.js', 'utf-8');
var pager = fs.readFileSync(__dirname + '/../templates/js/lib/pager.js', 'utf-8');
var tools = fs.readFileSync(__dirname + '/../templates/js/lib/tools.js', 'utf-8');
var xml = fs.readFileSync(__dirname + '/../templates/js/lib/xml.js', 'utf-8');

// Generate application
function createApplication(path) {
    emptyDirectory(path, function(empty) {
        if (empty || program.force) {
            createApplicationAt(path);
        } else {
            program.confirm('destination is not empty, continue? ', function(ok) {
                if (ok) {
                    process.stdin.destroy();
                    createApplicationAt(path);
                } else {
                    abort('aborting');
                }
            });
        }
    });
}

// Generate Model
function createModel(root) {
    var modelName = program.generate.toLowerCase().capitalize();
    var pathToModel = path.resolve(root + '/' + modelName + '.js');
    existsFile(pathToModel, function(empty) {
        if (!empty || program.force) {
            createModelAt(root, modelName);
        } else {
            program.confirm('model is exists, continue? ', function(ok) {
                if (ok) {
                    process.stdin.destroy();
                    createModelAt(root, modelName);
                } else {
                    abort('aborting');
                }
            });
        }
    });
}

/**
 * Create model at the given `name`.
 *
 * @param {String} pathToModels
 * @param {String} modelName
 */
function createModelAt(pathToModels, modelName) {
    var modelFile = path.normalize(pathToModels + '/' + modelName + '.js');
    var modelTemplate = fs.readFileSync(__dirname + '/../templates/model/Model.ejs', 'utf-8');
    // mkdir(path + '/models');
    var fields = parseFields();
    modelTemplate = modelTemplate.replace('{fields}', fields.join(',\n'));
    modelTemplate = modelTemplate.replace(/{name}/gi, modelName);
    modelTemplate = modelTemplate.replace('{nameToLowerCase}', modelName.toLowerCase());
    write(modelFile, modelTemplate);
}

// parse Fields
function parseFields() {
    var fields = [];
    for (var i in process.argv) {
        // Skip the first two - Node and app.js path
        if (i > 3) {
            var fdata = process.argv[i].split(':');
            var field = '';
            if (fdata[1]) {
                var type = 'type : String';
                switch (type) {
                    case 'int':
                    case 'integer':
                    case 'double':
                    case 'number':
                        type = 'type : Number';
                        break;
                    case 'bool':
                    case 'boolean':
                        type = 'type : Boolean';
                        break;
                    case 'str':
                    case 'string':
                        type = 'type : String';
                        break;
                    case 'text':
                        type = 'type : schema.Text';
                        break;
                    case 'json':
                        type = 'type : schema.JSON';
                        break;
                    default:
                        type = 'type : String';
                }
                field += type;
            }
            if (fdata[2]) {
                field += ", 'default' : " + fdata[2];
            }
            fields.push('           ' + fdata[0] + ' : { ' + field + ' }');
        }
    }
    return fields;
}

/**
 * Create application at the given directory `path`.
 *
 * @param {String} path
 */
function createApplicationAt(path) {
    console.log();
    process.on('exit', function() {
        console.log();
        console.log('   install dependencies:');
        console.log('     $ cd %s && npm install', path);
        console.log();
        console.log('   run the app:');
        console.log('     $ DEBUG=' + program.init + ' ./bin/www');
        console.log();
    });

    mkdir(path, function() {
        mkdir(path + '/models');
        mkdir(path + '/public');
        mkdir(path + '/public/js');
        mkdir(path + '/public/images');
        mkdir(path + '/public/css', function() {
            switch (program.css) {
                case 'less':
                    write(path + '/public/css/style.less', less);
                    break;
                case 'stylus':
                    write(path + '/public/css/style.styl', stylus);
                    break;
                case 'compass':
                    write(path + '/public/css/style.scss', compass);
                    break;
                default:
                    write(path + '/public/css/style.css', css);
            }
        });

        mkdir(path + '/routes', function() {
            write(path + '/routes/index.js', index);
            write(path + '/routes/rest.js', rest);
        });

        mkdir(path + '/lib', function() {
            write(path + '/lib/inflection.js', inflection);
            write(path + '/lib/pager.js', pager);
            write(path + '/lib/tools.js', tools);
            write(path + '/lib/xml.js', xml);
        });

        mkdir(path + '/views', function() {
            switch (program.template) {
                case 'ejs':
                    copy_template('ejs/index.ejs', path + '/views/index.ejs');
                    copy_template('ejs/error.ejs', path + '/views/error.ejs');
                    break;
                case 'jade':
                    copy_template('jade/index.jade', path + '/views/index.jade');
                    copy_template('jade/layout.jade', path + '/views/layout.jade');
                    copy_template('jade/error.jade', path + '/views/error.jade');
                    break;
                case 'hjs':
                    copy_template('hogan/index.hjs', path + '/views/index.hjs');
                    copy_template('hogan/error.hjs', path + '/views/error.hjs');
                    break;
            }
        });

        // CSS Engine support
        switch (program.css) {
            case 'less':
                app = app.replace('{css}', eol + 'app.use(require(\'less-middleware\')({ src: path.join(__dirname, \'public\') }));');
                break;
            case 'stylus':
                app = app.replace('{css}', eol + 'app.use(require(\'stylus\').middleware(path.join(__dirname, \'public\')));');
                break;
            case 'compass':
                app = app.replace('{css}', eol + 'app.use(require(\'node-compass\')({mode: \'expanded\'}));');
                break;
            default:
                app = app.replace('{css}', '');
        }

        // Template support
        app = app.replace('{views}', program.template);

        // package.json
        var pkg = {
            name: program.init
            , version: '0.0.1'
            , private: true
            , scripts: {start: 'node ./bin/www'}
            , dependencies: {
                'caminte': '~0.0.14',
                'express': '~3.4.8',
                'static-favicon': '~1.0.0',
                'morgan': '~1.0.0',
                'cookie-parser': '~1.0.1',
                'body-parser': '~1.0.0',
                'debug': '~0.7.4'
            }
        };

        switch (program.template) {
            case 'jade':
                pkg.dependencies['jade'] = '~1.3.0';
                break;
            case 'ejs':
                pkg.dependencies['ejs'] = '~0.8.5';
                break;
            case 'hjs':
                pkg.dependencies['hjs'] = '~0.0.6';
                break;
            default:
        }

        // CSS Engine support
        switch (program.css) {
            case 'less':
                pkg.dependencies['less-middleware'] = '0.1.15';
                break;
            case 'compass':
                pkg.dependencies['node-compass'] = '0.2.3';
                break;
            case 'stylus':
                pkg.dependencies['stylus'] = '0.42.3';
                break;
            default:
        }

        write(path + '/package.json', JSON.stringify(pkg, null, 2));
        write(path + '/app.js', app);
        mkdir(path + '/bin', function() {
            www = www.replace('{app}', program.init);
            write(path + '/bin/www', www, 0755);
        });
        mkdir(path + '/config', function() {
            write(path + '/config/index.js', cfg, 0755);
        });
    });
}

function copy_template(from, to) {
    from = path.join(__dirname, '..', 'templates', from);
    write(to, fs.readFileSync(from, 'utf-8'));
}

/**
 * Check if the given directory `path` is empty.
 *
 * @param {String} path
 * @param {Function} fn
 */
function emptyDirectory(path, fn) {
    fs.readdir(path, function(err, files) {
        if (err && 'ENOENT' !== err.code)
            throw err;
        fn(!files || !files.length);
    });
}

/**
 * Check if the given file `path` exists.
 *
 * @param {String} path
 * @param {Function} fn
 */
function existsFile(path, fn) {
    fs.exists(path, function(exists) {
        fn(exists);
    });
}

/**
 * echo str > path.
 *
 * @param {String} path
 * @param {String} str
 * @param {Number} mode
 */
function write(path, str, mode) {
    fs.writeFile(path, str, {mode: mode || 0666});
    console.log('   \x1b[36mcreate\x1b[0m : ' + path);
}

/**
 * Mkdir -p.
 *
 * @param {String} path
 * @param {Function} fn
 */
function mkdir(path, fn) {
    mkdirp(path, 0755, function(err) {
        if (err)
            throw err;
        console.log('   \033[36mcreate\033[0m : ' + path);
        fn && fn();
    });
}

/**
 * Exit with the given `str`.
 *
 * @param {String} str
 */
function abort(str) {
    console.error(str);
    process.exit(1);
}
<|endoftext|>


<commit_before>'use strict';

import { async } from 'metal';
import dom from 'metal-dom';
import Modal from '../src/Modal';

describe('Modal', function() {
	var modal;

	afterEach(function() {
		if (modal) {
			modal.dispose();
		}
	});

	it('should render with default state', function() {
		modal = new Modal();
		var header = modal.element.querySelector('.modal-header');
		var body = modal.element.querySelector('.modal-body');
		var footer = modal.element.querySelector('.modal-footer');
		assert.strictEqual('', header.innerHTML);
		assert.strictEqual('', body.innerHTML);
		assert.strictEqual('', footer.innerHTML);
		assert.strictEqual('block', modal.element.style.display);
	});

	it('should render with header only', function() {
		modal = new Modal({
			header: 'header'
		});
		var headerElement = modal.element.querySelector('.modal-header');
		var body = modal.element.querySelector('.modal-body').innerHTML;
		var footer = modal.element.querySelector('.modal-footer').innerHTML;
		assert.strictEqual('header', headerElement.childNodes[1].textContent);
		assert.ok(dom.hasClass(headerElement.childNodes[0], 'close'));
		assert.strictEqual('', body);
		assert.strictEqual('', footer);
	});

	it('should render with body only', function() {
		modal = new Modal({
			body: 'body'
		});
		var header = modal.element.querySelector('.modal-header').innerHTML;
		var body = modal.element.querySelector('.modal-body').innerHTML;
		var footer = modal.element.querySelector('.modal-footer').innerHTML;
		assert.strictEqual('', header);
		assert.strictEqual('body', body);
		assert.strictEqual('', footer);
	});

	it('should render with footer only', function() {
		modal = new Modal({
			footer: 'footer'
		});
		var header = modal.element.querySelector('.modal-header').innerHTML;
		var body = modal.element.querySelector('.modal-body').innerHTML;
		var footer = modal.element.querySelector('.modal-footer').innerHTML;
		assert.strictEqual('', header);
		assert.strictEqual('', body);
		assert.strictEqual('footer', footer);
	});

	it('should cause dom repaint when body state changes', function(done) {
		modal = new Modal();
		modal.body = 'body';
		async.nextTick(function() {
			var body = modal.element.querySelector('.modal-body').innerHTML;
			assert.strictEqual('body', body);
			done();
		});
	});

	it('should cause dom repaint when header state changes', function(done) {
		modal = new Modal();
		modal.header = 'header';
		async.nextTick(function() {
			var header = modal.element.querySelector('.modal-header');
			assert.strictEqual('header', header.childNodes[1].textContent);
			assert.ok(dom.hasClass(header.childNodes[0], 'close'));
			done();
		});
	});

	it('should cause dom repaint when footer state changes', function(done) {
		modal = new Modal();
		modal.footer = 'footer';
		async.nextTick(function() {
			var footer = modal.element.querySelector('.modal-footer').innerHTML;
			assert.strictEqual('footer', footer);
			done();
		});
	});

	it('should cause dom repaint when visible state changes', function(done) {
		modal = new Modal();
		modal.visible = false;
		async.nextTick(function() {
			assert.strictEqual('none', modal.element.style.display);

			modal.visible = true;
			async.nextTick(function() {
				assert.strictEqual('block', modal.element.style.display);
				done();
			});
		});
	});

	it('should show and hide overlay when overlay state changes', function(done) {
		modal = new Modal();
		modal.overlay = false;
		async.nextTick(function() {
			assert.ok(!modal.overlayElement.parentNode);
			modal.overlay = true;
			async.nextTick(function() {
				assert.ok(modal.overlayElement.parentNode);
				done();
			});
		});
	});

	it('should close on clicking close icon', function(done) {
		modal = new Modal({
			header: 'header'
		});
		dom.triggerEvent(modal.element.querySelector('.close'), 'click');
		async.nextTick(function() {
			assert.ok(!modal.visible);
			done();
		});
	});

	it('should close modal when press the escape key', function(done) {
		modal = new Modal();
		dom.triggerEvent(document, 'keyup');
		async.nextTick(function() {
			assert.ok(modal.visible);
			dom.triggerEvent(document, 'keyup', {
				keyCode: 27
			});
			async.nextTick(function() {
				assert.ok(!modal.visible);
				done();
			});
		});
	});

	it('should not close modal when press escape key and the hideOnEscape state is not true', function(done) {
		modal = new Modal({
			hideOnEscape: false
		});
		dom.triggerEvent(document, 'keyup', {
			keyCode: 27
		});
		async.nextTick(function() {
			assert.ok(modal.visible);
			done();
		});
	});

	it('should close modal overlay when modal closes', function(done) {
		modal = new Modal();
		modal.hide();
		async.nextTick(function() {
			assert.ok(!modal.overlayElement.parentNode);
			done();
		});
	});

	it('should only show modal overlay when modal is visible', function(done) {
		modal = new Modal({
			visible: false
		});
		assert.ok(!modal.overlayElement.parentNode);
		modal.visible = true;
		async.nextTick(function() {
			assert.ok(modal.overlayElement.parentNode);
			done();
		});
	});

	it('should set the "role" HTML attribute to "dialog" by default', function() {
		modal = new Modal();
		assert.strictEqual('dialog', modal.element.childNodes[0].getAttribute('role'));
	});

	it('should set the "role" HTML attribute to value specified by the "role" state', function() {
		modal = new Modal({
			role: 'alertdialog'
		});
		assert.strictEqual('alertdialog', modal.element.childNodes[0].getAttribute('role'));
	});

	it('should set the header and body ids to values specified by the user', function() {
		modal = new Modal({
			bodyId: 'body',
			header: 'My Header',
			headerId: 'header'
		});
		assert.ok(modal.element.querySelector('#header'));
		assert.ok(modal.element.querySelector('#body'));
	});

	describe('Automatic Focus', function() {
		it('should automatically focus close button', function() {
			modal = new Modal({
				header: 'My Header'
			});
			assert.strictEqual(modal.element.querySelector('.close'), document.activeElement);
		});

		it('should not automatically focus any element if "autoFocus" state is set to "false"', function() {
			var prevActiveElement = document.activeElement;
			modal = new Modal({
				autoFocus: false,
				header: 'My Header'
			});
			assert.strictEqual(prevActiveElement, document.activeElement);
		});

		it('should automatically focus internal element that matches selector specified by "autoFocus" state', function() {
			modal = new Modal({
				autoFocus: '.body-btn',
				body: '<button class="body-btn">Body Button</button>'
			});
			assert.strictEqual(modal.element.querySelector('.body-btn'), document.activeElement);
		});

		it('should not automatically focus "autoFocus" element if modal is rendered invisible', function() {
			var prevActiveElement = document.activeElement;
			modal = new Modal({
				autoFocus: '.body-btn',
				body: '<button class="body-btn">Body Button</button>',
				visible: false
			});
			assert.strictEqual(prevActiveElement, document.activeElement);
		});

		it('should automatically focus "autoFocus" element when modal becomes visible', function(done) {
			var element = document.createElement('button');
			dom.enterDocument(element);
			modal = new Modal({
				autoFocus: '.body-btn',
				body: '<button class="body-btn">Body Button</button>',
				visible: false
			});

			modal.visible = true;
			modal.once('stateChanged', function() {
				assert.strictEqual(modal.element.querySelector('.body-btn'), document.activeElement);
				dom.exitDocument(element);
				done();
			});
		});
	});

	describe('Refocus Last Element', function() {
		it('should refocus the previously active element when modal is hidden', function(done) {
			var element = document.createElement('button');
			dom.enterDocument(element);
			element.focus();

			modal = new Modal({
				header: 'My Header',
				visible: false
			});

			modal.visible = true;
			modal.once('stateChanged', function() {
				assert.notStrictEqual(element, document.activeElement);
				modal.visible = false;
				modal.once('stateChanged', function() {
					assert.strictEqual(element, document.activeElement);
					dom.exitDocument(element);
					done();
				});
			});
		});

		it('should refocus the previously active element when modal is hidden and element changes', function(done) {
			var element = document.createElement('button');
			dom.enterDocument(element);
			element.focus();

			modal = new Modal({
				header: 'My Header',
				visible: false
			});

			modal.visible = true;
			modal.once('stateChanged', function() {
				assert.notStrictEqual(element, document.activeElement);
				var oldElement = modal.element;
				modal.element = document.createElement('div');
				dom.exitDocument(oldElement);
				assert.notStrictEqual(element, document.activeElement);

				modal.visible = false;
				modal.once('stateChanged', function() {
					assert.strictEqual(element, document.activeElement);
					dom.exitDocument(element);
					done();
				});
			});
		});
	});

	describe('Restrict Focus', function() {
		var outsideElement;

		beforeEach(function() {
			outsideElement = document.createElement('button');
			dom.enterDocument(outsideElement);
		});

		afterEach(function() {
			dom.exitDocument(outsideElement);
		});

		it('should not allow focusing elements outside the modal while it\'s visible', function() {
			modal = new Modal();

			// Focus and call the `focus` event manually, since the test browser's window may not have
			// focus at the moment, and it's not guaranteed that the event will fire autimatically.
			outsideElement.focus();
			dom.triggerEvent(outsideElement, 'focus');
			assert.notStrictEqual(outsideElement, document.activeElement);
			assert.strictEqual(modal.element.querySelector('.modal-dialog'), document.activeElement);
		});

		it('should not restrict focusing outside modal if not visible', function() {
			modal = new Modal({
				visible: false
			});

			outsideElement.focus();
			dom.triggerEvent(outsideElement, 'focus');
			assert.strictEqual(outsideElement, document.activeElement);
		});

		it('should restrict/unrestrict focusing outside modal as visibility changes', function(done) {
			modal = new Modal();

			modal.visible = false;
			modal.once('stateChanged', function() {
				outsideElement.focus();
				dom.triggerEvent(outsideElement, 'focus');
				assert.strictEqual(outsideElement, document.activeElement);

				modal.visible = true;
				modal.once('stateChanged', function() {
					outsideElement.focus();
					dom.triggerEvent(outsideElement, 'focus');
					assert.notStrictEqual(outsideElement, document.activeElement);
					assert.strictEqual(modal.element.querySelector('.modal-dialog'), document.activeElement);
					done();
				});
			});
		});

		it('should not restrict focusing outside modal if "overlay" is false', function() {
			modal = new Modal({
				overlay: false
			});

			outsideElement.focus();
			dom.triggerEvent(outsideElement, 'focus');
			assert.strictEqual(outsideElement, document.activeElement);
		});

		it('should not restrict focusing inside modal', function() {
			modal = new Modal({
				body: '<button class="body-btn">Body Button</button>',
			});

			var element = modal.element.querySelector('.body-btn');
			element.focus();
			dom.triggerEvent(element, 'focus');
			assert.strictEqual(element, document.activeElement);
		});
	});

	it('should modal progressive enchance always as hidden', function() {
		var data = {
			id: 'modal',
			elementClasses: 'centered',
			header: 'header',
			headerId: 'header',
			body: 'body',
			bodyId: 'header',
			footer: 'footer',
			overlay: true,
			role: 'dialog'
		};
		var element = document.createElement('div');
		IncrementalDOM.patch(element, () => Modal.TEMPLATE(data));
		var outerHTML = element.innerHTML;

		modal = new Modal({
			element: element.childNodes[0],
			elementClasses: 'centered',
			header: 'header',
			headerId: 'header',
			body: 'body',
			bodyId: 'header',
			footer: 'footer',
			visible: false
		});

		assert.strictEqual(modal.element.outerHTML, outerHTML);
	});

	it('should change to visible when decorated and visible is true', function() {
		var data = {
			id: 'modal',
			elementClasses: 'centered',
			header: 'header',
			body: 'body',
			footer: 'footer',
			overlay: true
		};
		var element = document.createElement('div');
		IncrementalDOM.patch(element, () => Modal.TEMPLATE(data));

		modal = new Modal({
			element: element.childNodes[0],
			header: 'header',
			body: 'body',
			footer: 'footer',
			visible: true
		});

		assert.ok(!dom.hasClass(modal.element, 'hidden'));
	});

	it('should handle calling template without passing "elementClasses"', function() {
		var data = {
			header: 'header',
			body: 'body',
			footer: 'footer',
			overlay: true
		};
		var element = document.createElement('div');
		IncrementalDOM.patch(element, () => Modal.TEMPLATE(data));

		element = element.childNodes[0];
		assert.strictEqual('modal', element.className);
	});

	it('should set "visible" state to false when "hide" method is called', function() {
		modal = new Modal();
		modal.hide();
		assert.ok(!modal.visible);
	});

	it('should set "visible" state to true when "show" method is called', function() {
		modal = new Modal({
			visible: false
		});
		modal.show();
		assert.ok(modal.visible);
	});
});
<commit_msg>Fixes failing test<commit_after>'use strict';

import { async } from 'metal';
import dom from 'metal-dom';
import Modal from '../src/Modal';

describe('Modal', function() {
	var modal;

	afterEach(function() {
		if (modal) {
			modal.dispose();
		}
	});

	it('should render with default state', function() {
		modal = new Modal();
		var header = modal.element.querySelector('.modal-header');
		var body = modal.element.querySelector('.modal-body');
		var footer = modal.element.querySelector('.modal-footer');
		assert.strictEqual('', header.innerHTML);
		assert.strictEqual('', body.innerHTML);
		assert.strictEqual('', footer.innerHTML);
		assert.strictEqual('block', modal.element.style.display);
	});

	it('should render with header only', function() {
		modal = new Modal({
			header: 'header'
		});
		var headerElement = modal.element.querySelector('.modal-header');
		var body = modal.element.querySelector('.modal-body').innerHTML;
		var footer = modal.element.querySelector('.modal-footer').innerHTML;
		assert.strictEqual('header', headerElement.childNodes[1].textContent);
		assert.ok(dom.hasClass(headerElement.childNodes[0], 'close'));
		assert.strictEqual('', body);
		assert.strictEqual('', footer);
	});

	it('should render with body only', function() {
		modal = new Modal({
			body: 'body'
		});
		var header = modal.element.querySelector('.modal-header').innerHTML;
		var body = modal.element.querySelector('.modal-body').innerHTML;
		var footer = modal.element.querySelector('.modal-footer').innerHTML;
		assert.strictEqual('', header);
		assert.strictEqual('body', body);
		assert.strictEqual('', footer);
	});

	it('should render with footer only', function() {
		modal = new Modal({
			footer: 'footer'
		});
		var header = modal.element.querySelector('.modal-header').innerHTML;
		var body = modal.element.querySelector('.modal-body').innerHTML;
		var footer = modal.element.querySelector('.modal-footer').innerHTML;
		assert.strictEqual('', header);
		assert.strictEqual('', body);
		assert.strictEqual('footer', footer);
	});

	it('should cause dom repaint when body state changes', function(done) {
		modal = new Modal();
		modal.body = 'body';
		async.nextTick(function() {
			var body = modal.element.querySelector('.modal-body').innerHTML;
			assert.strictEqual('body', body);
			done();
		});
	});

	it('should cause dom repaint when header state changes', function(done) {
		modal = new Modal();
		modal.header = 'header';
		async.nextTick(function() {
			var header = modal.element.querySelector('.modal-header');
			assert.strictEqual('header', header.childNodes[1].textContent);
			assert.ok(dom.hasClass(header.childNodes[0], 'close'));
			done();
		});
	});

	it('should cause dom repaint when footer state changes', function(done) {
		modal = new Modal();
		modal.footer = 'footer';
		async.nextTick(function() {
			var footer = modal.element.querySelector('.modal-footer').innerHTML;
			assert.strictEqual('footer', footer);
			done();
		});
	});

	it('should cause dom repaint when visible state changes', function(done) {
		modal = new Modal();
		modal.visible = false;
		async.nextTick(function() {
			assert.strictEqual('none', modal.element.style.display);

			modal.visible = true;
			async.nextTick(function() {
				assert.strictEqual('block', modal.element.style.display);
				done();
			});
		});
	});

	it('should show and hide overlay when overlay state changes', function(done) {
		modal = new Modal();
		modal.overlay = false;
		async.nextTick(function() {
			assert.ok(!modal.overlayElement.parentNode);
			modal.overlay = true;
			async.nextTick(function() {
				assert.ok(modal.overlayElement.parentNode);
				done();
			});
		});
	});

	it('should close on clicking close icon', function(done) {
		modal = new Modal({
			header: 'header'
		});
		dom.triggerEvent(modal.element.querySelector('.close'), 'click');
		async.nextTick(function() {
			assert.ok(!modal.visible);
			done();
		});
	});

	it('should close modal when press the escape key', function(done) {
		modal = new Modal();
		dom.triggerEvent(document, 'keyup');
		async.nextTick(function() {
			assert.ok(modal.visible);
			dom.triggerEvent(document, 'keyup', {
				keyCode: 27
			});
			async.nextTick(function() {
				assert.ok(!modal.visible);
				done();
			});
		});
	});

	it('should not close modal when press escape key and the hideOnEscape state is not true', function(done) {
		modal = new Modal({
			hideOnEscape: false
		});
		dom.triggerEvent(document, 'keyup', {
			keyCode: 27
		});
		async.nextTick(function() {
			assert.ok(modal.visible);
			done();
		});
	});

	it('should close modal overlay when modal closes', function(done) {
		modal = new Modal();
		modal.hide();
		async.nextTick(function() {
			assert.ok(!modal.overlayElement.parentNode);
			done();
		});
	});

	it('should only show modal overlay when modal is visible', function(done) {
		modal = new Modal({
			visible: false
		});
		assert.ok(!modal.overlayElement.parentNode);
		modal.visible = true;
		async.nextTick(function() {
			assert.ok(modal.overlayElement.parentNode);
			done();
		});
	});

	it('should set the "role" HTML attribute to "dialog" by default', function() {
		modal = new Modal();
		assert.strictEqual('dialog', modal.element.childNodes[0].getAttribute('role'));
	});

	it('should set the "role" HTML attribute to value specified by the "role" state', function() {
		modal = new Modal({
			role: 'alertdialog'
		});
		assert.strictEqual('alertdialog', modal.element.childNodes[0].getAttribute('role'));
	});

	it('should set the header and body ids to values specified by the user', function() {
		modal = new Modal({
			bodyId: 'body',
			header: 'My Header',
			headerId: 'header'
		});
		assert.ok(modal.element.querySelector('#header'));
		assert.ok(modal.element.querySelector('#body'));
	});

	describe('Automatic Focus', function() {
		it('should automatically focus close button', function() {
			modal = new Modal({
				header: 'My Header'
			});
			assert.strictEqual(modal.element.querySelector('.close'), document.activeElement);
		});

		it('should not automatically focus any element if "autoFocus" state is set to "false"', function() {
			var prevActiveElement = document.activeElement;
			modal = new Modal({
				autoFocus: false,
				header: 'My Header'
			});
			assert.strictEqual(prevActiveElement, document.activeElement);
		});

		it('should automatically focus internal element that matches selector specified by "autoFocus" state', function() {
			modal = new Modal({
				autoFocus: '.body-btn',
				body: '<button class="body-btn">Body Button</button>'
			});
			assert.strictEqual(modal.element.querySelector('.body-btn'), document.activeElement);
		});

		it('should not automatically focus "autoFocus" element if modal is rendered invisible', function() {
			var prevActiveElement = document.activeElement;
			modal = new Modal({
				autoFocus: '.body-btn',
				body: '<button class="body-btn">Body Button</button>',
				visible: false
			});
			assert.strictEqual(prevActiveElement, document.activeElement);
		});

		it('should automatically focus "autoFocus" element when modal becomes visible', function(done) {
			var element = document.createElement('button');
			dom.enterDocument(element);
			modal = new Modal({
				autoFocus: '.body-btn',
				body: '<button class="body-btn">Body Button</button>',
				visible: false
			});

			modal.visible = true;
			modal.once('stateChanged', function() {
				assert.strictEqual(modal.element.querySelector('.body-btn'), document.activeElement);
				dom.exitDocument(element);
				done();
			});
		});
	});

	describe('Refocus Last Element', function() {
		it('should refocus the previously active element when modal is hidden', function(done) {
			var element = document.createElement('button');
			dom.enterDocument(element);
			element.focus();

			modal = new Modal({
				header: 'My Header',
				visible: false
			});

			modal.visible = true;
			modal.once('stateChanged', function() {
				assert.notStrictEqual(element, document.activeElement);
				modal.visible = false;
				modal.once('stateChanged', function() {
					assert.strictEqual(element, document.activeElement);
					dom.exitDocument(element);
					done();
				});
			});
		});

		it('should refocus the previously active element when modal is hidden and element changes', function(done) {
			var element = document.createElement('button');
			dom.enterDocument(element);
			element.focus();

			modal = new Modal({
				header: 'My Header',
				visible: false
			});

			modal.visible = true;
			modal.once('stateChanged', function() {
				assert.notStrictEqual(element, document.activeElement);
				var oldElement = modal.element;
				modal.element = document.createElement('div');
				dom.exitDocument(oldElement);
				assert.notStrictEqual(element, document.activeElement);

				modal.visible = false;
				modal.once('stateChanged', function() {
					assert.strictEqual(element, document.activeElement);
					dom.exitDocument(element);
					done();
				});
			});
		});
	});

	describe('Restrict Focus', function() {
		var outsideElement;

		beforeEach(function() {
			outsideElement = document.createElement('button');
			dom.enterDocument(outsideElement);
		});

		afterEach(function() {
			dom.exitDocument(outsideElement);
		});

		it('should not allow focusing elements outside the modal while it\'s visible', function() {
			modal = new Modal();

			// Focus and call the `focus` event manually, since the test browser's window may not have
			// focus at the moment, and it's not guaranteed that the event will fire autimatically.
			outsideElement.focus();
			dom.triggerEvent(outsideElement, 'focus');
			assert.notStrictEqual(outsideElement, document.activeElement);
			assert.strictEqual(modal.element.querySelector('.modal-dialog'), document.activeElement);
		});

		it('should not restrict focusing outside modal if not visible', function() {
			modal = new Modal({
				visible: false
			});

			outsideElement.focus();
			dom.triggerEvent(outsideElement, 'focus');
			assert.strictEqual(outsideElement, document.activeElement);
		});

		it('should restrict/unrestrict focusing outside modal as visibility changes', function(done) {
			modal = new Modal();

			modal.visible = false;
			modal.once('stateChanged', function() {
				outsideElement.focus();
				dom.triggerEvent(outsideElement, 'focus');
				assert.strictEqual(outsideElement, document.activeElement);

				modal.visible = true;
				modal.once('stateChanged', function() {
					outsideElement.focus();
					dom.triggerEvent(outsideElement, 'focus');
					assert.notStrictEqual(outsideElement, document.activeElement);
					assert.strictEqual(modal.element.querySelector('.modal-dialog'), document.activeElement);
					done();
				});
			});
		});

		it('should not restrict focusing outside modal if "overlay" is false', function() {
			modal = new Modal({
				overlay: false
			});

			outsideElement.focus();
			dom.triggerEvent(outsideElement, 'focus');
			assert.strictEqual(outsideElement, document.activeElement);
		});

		it('should not restrict focusing inside modal', function() {
			modal = new Modal({
				body: '<button class="body-btn">Body Button</button>',
			});

			var element = modal.element.querySelector('.body-btn');
			element.focus();
			dom.triggerEvent(element, 'focus');
			assert.strictEqual(element, document.activeElement);
		});
	});

	it('should progressive enhance always as hidden', function() {
		var data = {
			body: 'body',
			bodyId: 'header',
			elementClasses: 'centered',
			footer: 'footer',
			header: 'header',
			headerId: 'header',
			id: 'modal',
			noCloseButton: true,
			overlay: true,
			role: 'dialog'
		};
		var element = document.createElement('div');
		IncrementalDOM.patch(element, () => Modal.TEMPLATE(data));
		var outerHTML = element.innerHTML;

		modal = new Modal({
			body: 'body',
			bodyId: 'header',
			element: element.childNodes[0],
			elementClasses: 'centered',
			footer: 'footer',
			header: 'header',
			headerId: 'header',
			noCloseButton: true,
			visible: false
		});

		assert.strictEqual(modal.element.outerHTML, outerHTML);
	});

	it('should change to visible when decorated and visible is true', function() {
		var data = {
			id: 'modal',
			elementClasses: 'centered',
			header: 'header',
			body: 'body',
			footer: 'footer',
			overlay: true
		};
		var element = document.createElement('div');
		IncrementalDOM.patch(element, () => Modal.TEMPLATE(data));

		modal = new Modal({
			element: element.childNodes[0],
			header: 'header',
			body: 'body',
			footer: 'footer',
			visible: true
		});

		assert.ok(!dom.hasClass(modal.element, 'hidden'));
	});

	it('should handle calling template without passing "elementClasses"', function() {
		var data = {
			header: 'header',
			body: 'body',
			footer: 'footer',
			overlay: true
		};
		var element = document.createElement('div');
		IncrementalDOM.patch(element, () => Modal.TEMPLATE(data));

		element = element.childNodes[0];
		assert.strictEqual('modal', element.className);
	});

	it('should set "visible" state to false when "hide" method is called', function() {
		modal = new Modal();
		modal.hide();
		assert.ok(!modal.visible);
	});

	it('should set "visible" state to true when "show" method is called', function() {
		modal = new Modal({
			visible: false
		});
		modal.show();
		assert.ok(modal.visible);
	});
});
<|endoftext|>