<commit_before>(function() {
  const api = 'https://emojityper.appspot.com';

  function historicData() {
    let p = window.fetch('https://emojityper.com/res/js/emojimap.js').then(out => out.text());
    p = p.then(raw => {
      const first = raw.indexOf('{');
      if (first === -1) {
        throw new Error('invalid emojimap');
      }
      // FIXME: never do this :O
      eval('var emojimap = ' + raw.substr(first));
      return emojimap;
    })
    p = p.then(emojimap => {
      const out = {};
      Object.keys(emojimap).forEach(string => {
        const data = emojimap[string];
        string = string.replace(/\s+/g, '').replace(/[’’]/g, '');
        if (!(string in out)) {
          out[string] = [];
        }
        out[string].push(...data.map(details => details.emoji));
      });

      return out;
    });
    return p;
  }

  function futureData() {
    return window.fetch(api + '/popular').then(out => out.json());
  }

  // const data = historicData();
  const data = futureData();
  let indexed = data.then(emojimap => {
    const prefixLength = 3;   // generate prefixes up to this length
    const maxSuggestions = 10;  // only generate this many suggestions
    const prefixSuggest = {};

    for (let k in emojimap) {
      const prefix = k.substr(0, prefixLength);
      for (let i = 1; i <= prefix.length; ++i) {
        const part = prefix.substr(0, i);
        let opts = prefixSuggest[part];
        if (!opts) {
          opts = prefixSuggest[part] = [];
        }
        if (opts.length < maxSuggestions) {
          opts.push(k);
        }
      }
    }

    return function(typed) {
      typed = typed.toLowerCase();
      const rest = typed.substr(prefixLength);
      let all = prefixSuggest[typed.substr(0, prefixLength)] || [];

      if (rest) {
        all = all.filter(word => word.substr(prefixLength).startsWith(rest));
      }
      all = all.map(word => {
        return {'name': word, 'options': emojimap[word] || []};
      });

      return all.length ? all : null;
    }
  });

  let timeout;
  const performRequest = (text, prefix) => {
    if (!text) {
      window.emojimanager.callback(null);
      return;
    }

    function send(out) {
      if (timeout === localTimeout) {
        window.emojimanager.callback(out);
      } else {
        console.debug('got results for old query');
      }
    }

    // TODO: only send extra query if there's not enough results, or the user hits 'more'
    window.clearTimeout(timeout);
    localTimeout = window.setTimeout(_ => {
      const data = new FormData();
      data.set('q', text);
      data.set('prefix', prefix)
      window.fetch(api + '/query', {method: 'POST', data}).then(out => out.json()).then(send);
    }, 2000);
    timeout = localTimeout;

    indexed.then(suggest => {
      let results = suggest(text);
      if (!prefix) {
        results = results.filter(result => result['name'] === text);
      }
      // FIXME: don't return results if request has changed
      send(results);
    }).catch(err => {
      console.warn('some error in suggest', err);
    });
  };

  window.emojimanager = {
    callback: function() {},
    request(text, prefix) {
      window.requestAnimationFrame(_ => {
        performRequest(text, prefix);
      });
    },
    submit(name, value) {
      // const data = `name=${name}&emoji=${value}`;
      // FIXME: we've disabled CORS for now; the response is actually useless anyway
      // FIXME: however, the client now thinks this succeeds even when it 400s
      const data = new FormData();
      data.set('name', name);
      data.set('emoji', value);
      return window.fetch(api + '/name', {method: 'POST', data, mode: 'no-cors'});
    },
  };

}())

<commit_msg>set => append for Safari/others<commit_after>(function() {
  const api = 'https://emojityper.appspot.com';

  function historicData() {
    let p = window.fetch('https://emojityper.com/res/js/emojimap.js').then(out => out.text());
    p = p.then(raw => {
      const first = raw.indexOf('{');
      if (first === -1) {
        throw new Error('invalid emojimap');
      }
      // FIXME: never do this :O
      eval('var emojimap = ' + raw.substr(first));
      return emojimap;
    })
    p = p.then(emojimap => {
      const out = {};
      Object.keys(emojimap).forEach(string => {
        const data = emojimap[string];
        string = string.replace(/\s+/g, '').replace(/[’’]/g, '');
        if (!(string in out)) {
          out[string] = [];
        }
        out[string].push(...data.map(details => details.emoji));
      });

      return out;
    });
    return p;
  }

  function futureData() {
    return window.fetch(api + '/popular').then(out => out.json());
  }

  // const data = historicData();
  const data = futureData();
  let indexed = data.then(emojimap => {
    const prefixLength = 3;   // generate prefixes up to this length
    const maxSuggestions = 10;  // only generate this many suggestions
    const prefixSuggest = {};

    for (let k in emojimap) {
      const prefix = k.substr(0, prefixLength);
      for (let i = 1; i <= prefix.length; ++i) {
        const part = prefix.substr(0, i);
        let opts = prefixSuggest[part];
        if (!opts) {
          opts = prefixSuggest[part] = [];
        }
        if (opts.length < maxSuggestions) {
          opts.push(k);
        }
      }
    }

    return function(typed) {
      typed = typed.toLowerCase();
      const rest = typed.substr(prefixLength);
      let all = prefixSuggest[typed.substr(0, prefixLength)] || [];

      if (rest) {
        all = all.filter(word => word.substr(prefixLength).startsWith(rest));
      }
      all = all.map(word => {
        return {'name': word, 'options': emojimap[word] || []};
      });

      return all.length ? all : null;
    }
  });

  let timeout;
  const performRequest = (text, prefix) => {
    if (!text) {
      window.emojimanager.callback(null);
      return;
    }

    function send(out) {
      if (timeout === localTimeout) {
        window.emojimanager.callback(out);
      } else {
        console.debug('got results for old query');
      }
    }

    // TODO: only send extra query if there's not enough results, or the user hits 'more'
    window.clearTimeout(timeout);
    localTimeout = window.setTimeout(_ => {
      const data = new FormData();
      data.append('q', text);
      data.append('prefix', prefix)
      window.fetch(api + '/query', {method: 'POST', data}).then(out => out.json()).then(send);
    }, 2000);
    timeout = localTimeout;

    indexed.then(suggest => {
      let results = suggest(text);
      if (!prefix) {
        results = results.filter(result => result['name'] === text);
      }
      // FIXME: don't return results if request has changed
      send(results);
    }).catch(err => {
      console.warn('some error in suggest', err);
    });
  };

  window.emojimanager = {
    callback: function() {},
    request(text, prefix) {
      window.requestAnimationFrame(_ => {
        performRequest(text, prefix);
      });
    },
    submit(name, value) {
      // const data = `name=${name}&emoji=${value}`;
      // FIXME: we've disabled CORS for now; the response is actually useless anyway
      // FIXME: however, the client now thinks this succeeds even when it 400s
      const data = new FormData();
      data.append('name', name);
      data.append('emoji', value);
      return window.fetch(api + '/name', {method: 'POST', data, mode: 'no-cors'});
    },
  };

}())

<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import { Client as BugsnagClient } from 'bugsnag-react-native';

import {
  INTERNAL_TO_EXTERNAL,
  RECORD_TYPES,
  REQUISITION_STATUSES,
  REQUISITION_TYPES,
  SEQUENCE_KEYS,
  STATUSES,
  SYNC_TYPES,
  TRANSACTION_BATCH_TYPES,
  TRANSACTION_TYPES,
} from './syncTranslators';
import { CHANGE_TYPES } from '../database';
import { SETTINGS_KEYS } from '../settings';

const { SUPPLYING_STORE_NAME_ID, THIS_STORE_ID, SYNC_URL, SYNC_SITE_NAME } = SETTINGS_KEYS;

const bugsnagClient = new BugsnagClient();

const getDateString = date => {
  let returnDate = '0000-00-00';
  if (date && typeof date === 'object') returnDate = date.toISOString().slice(0, 10);

  return `${returnDate}T00:00:00`;
};

function getTimeString(date) {
  if (!date || typeof date !== 'object') return '00:00:00';
  return date.toTimeString().substring(0, 8);
}

/**
 * Tries to get a value that is known to potentially lead to crash.
 * If path on the record returns null, throw an error with prototype extended with
 * |canDeleteSyncOut| set to true to indicates that sync should continue.
 *
 * @param {object}  record  The object to get properties from.
 * @param {string}  path    The path on that object safely try.
 * @return {any}            Whatever variable was stored at path, if no error thrown.
 */
const safeGet = (record, path) => {
  const pathSegments = path.split('.');
  let currentPath = 'record';
  let nestedProp = record;
  for (let i = 0; i < pathSegments.length; i += 1) {
    const segment = pathSegments[i];
    currentPath += `.${segment}`; // Build up path to know at what point potential errors occur.
    try {
      nestedProp = nestedProp[segment];
    } catch (error) {
      error.canDeleteSyncOut = true; // Safe to delete |syncOut|.
      error.message = `Error on object getter on path "${currentPath}", original message: ${
        error.message
      }`;
      throw error; // Bubble error to next handler.
    }
  }
  return nestedProp;
};

/**
 * Turn an internal database object into data representing a record in the
 * mSupply primary server, ready for sync.
 *
 * @param   {Settings}      settings    Access to local settings.
 * @param   {string}        recordType  Internal type of record being synced.
 * @param   {Realm.object}  record      The record being synced.
 * @return  {object}                    The data to sync (in the form of upstream record).
 */
const generateSyncData = (settings, recordType, record) => {
  switch (recordType) {
    case 'ItemBatch': {
      return {
        ID: record.id,
        store_ID: settings.get(THIS_STORE_ID),
        item_ID: record.itemId,
        pack_size: String(record.packSize),
        expiry_date: getDateString(record.expiryDate),
        batch: record.batch,
        available: String(record.numberOfPacks),
        quantity: String(record.numberOfPacks),
        stock_on_hand_tot: String(record.totalQuantity),
        cost_price: String(record.costPrice),
        sell_price: String(record.sellPrice),
        total_cost: String(record.costPrice * record.numberOfPacks),
        name_ID: settings.get(SUPPLYING_STORE_NAME_ID),
      };
    }
    case 'NumberSequence': {
      const thisStoreId = settings.get(THIS_STORE_ID);
      return {
        ID: record.id,
        name: SEQUENCE_KEYS.translate(record.sequenceKey, INTERNAL_TO_EXTERNAL, thisStoreId),
        value: String(record.highestNumberUsed),
      };
    }
    case 'NumberToReuse': {
      const thisStoreId = settings.get(THIS_STORE_ID);
      return {
        ID: record.id,
        name: SEQUENCE_KEYS.translate(record.sequenceKey, INTERNAL_TO_EXTERNAL, thisStoreId),
        number_to_use: String(record.number),
      };
    }
    case 'Requisition': {
      return {
        ID: record.id,
        date_entered: getDateString(record.entryDate),
        user_ID: record.enteredById,
        name_ID: record.otherStoreName && record.otherStoreName.id,
        status: REQUISITION_STATUSES.translate(record.status, INTERNAL_TO_EXTERNAL),
        daysToSupply: String(record.daysToSupply),
        store_ID: settings.get(THIS_STORE_ID),
        serial_number: record.serialNumber,
        requester_reference: record.requesterReference,
        comment: record.comment,
        type: REQUISITION_TYPES.translate(record.type, INTERNAL_TO_EXTERNAL),
      };
    }
    case 'RequisitionItem': {
      return {
        ID: record.id,
        requisition_ID: record.requisitionId,
        item_ID: record.itemId,
        stock_on_hand: String(record.stockOnHand),
        daily_usage: String(record.dailyUsage),
        suggested_quantity: String(record.suggestedQuantity),
        actualQuan: String(record.suppliedQuantity),
        line_number: String(record.sortIndex),
        Cust_stock_order: String(record.requiredQuantity),
        comment: record.comment,
      };
    }
    case 'Stocktake': {
      return {
        ID: record.id,
        Description: record.name,
        stock_take_date: getDateString(record.stocktakeDate),
        stock_take_time: getTimeString(record.stocktakeDate),
        created_by_ID: record.createdBy && record.createdBy.id,
        status: STATUSES.translate(record.status, INTERNAL_TO_EXTERNAL),
        finalised_by_ID: record.finalisedBy && record.finalisedBy.id,
        invad_additions_ID: record.additions && record.additions.id,
        invad_reductions_ID: record.reductions && record.reductions.id,
        store_ID: settings.get(THIS_STORE_ID),
        comment: record.comment,
        stock_take_created_date: getDateString(record.createdDate),
        serial_number: record.serialNumber,
      };
    }
    case 'StocktakeBatch': {
      return {
        ID: record.id,
        stock_take_ID: record.stocktake && record.stocktake.id,
        item_line_ID: record.itemBatchId,
        snapshot_qty: String(record.snapshotNumberOfPacks),
        snapshot_packsize: String(record.packSize),
        stock_take_qty: String(record.countedNumberOfPacks),
        line_number: String(record.sortIndex),
        expiry: getDateString(record.expiryDate),
        cost_price: String(record.costPrice),
        sell_price: String(record.sellPrice),
        Batch: record.batch,
        item_ID: record.itemId,
      };
    }
    case 'Transaction': {
      return {
        ID: record.id,
        name_ID: record.otherParty && record.otherParty.id,
        invoice_num: record.serialNumber,
        comment: record.comment,
        entry_date: getDateString(record.entryDate),
        type: TRANSACTION_TYPES.translate(record.type, INTERNAL_TO_EXTERNAL),
        status: STATUSES.translate(record.status, INTERNAL_TO_EXTERNAL),
        mode: 'store',
        total: String(record.totalPrice),
        their_ref: record.theirRef,
        confirm_date: getDateString(record.confirmDate),
        subtotal: String(record.totalPrice),
        user_ID: record.enteredBy && record.enteredBy.id,
        category_ID: record.category && record.category.id,
        confirm_time: getTimeString(record.confirmDate),
        store_ID: settings.get(THIS_STORE_ID),
        requisition_ID:
          record.linkedRequisition && record.linkedRequisition.id
            ? record.linkedRequisition.id
            : undefined,
      };
    }
    case 'TransactionBatch': {
      const { transaction } = record;
      return {
        ID: record.id,
        transaction_ID: record.transaction.id,
        item_ID: record.itemId,
        batch: record.batch,
        price_extension: String(record.totalPrice),
        note: record.note,
        cost_price: String(record.costPrice),
        sell_price: String(record.sellPrice),
        expiry_date: getDateString(record.expiryDate),
        pack_size: String(record.packSize),
        quantity: String(record.numberOfPacks),
        // |item_line_ID| can be null due to server merge bug in v3.83.
        item_line_ID: safeGet(record, 'itemBatch.id'),
        line_number: String(record.sortIndex),
        item_name: record.itemName,
        is_from_inventory_adjustment: transaction.isInventoryAdjustment,
        type: TRANSACTION_BATCH_TYPES.translate(transaction.type, INTERNAL_TO_EXTERNAL),
      };
    }
    default:
      throw new Error('Sync out record type not supported.');
  }
};

/**
 * Returns a json object fulfilling the requirements of the mSupply primary sync
 * server, based on a given |syncOutRecord|.
 *
 * @param   {Realm}         database       The local database.
 * @param   {Settings}      settings       Access to local settings.
 * @param   {Realm.object}  syncOutRecord  The sync out record to be translated.
 * @return  {object}                       The generated json object, ready to sync.
 */
export const generateSyncJson = (database, settings, syncOutRecord) => {
  if (!syncOutRecord || !syncOutRecord.isValid()) {
    throw new Error('Missing sync out record');
  }
  if (!syncOutRecord.recordType || !syncOutRecord.id || !syncOutRecord.recordId) {
    throw new Error('Malformed sync out record');
  }
  const { recordType, recordId, changeType } = syncOutRecord;
  const storeId = settings.get(THIS_STORE_ID);
  // Create the JSON object to sync.
  const syncJson = {
    SyncID: syncOutRecord.id,
    RecordType: RECORD_TYPES.translate(recordType, INTERNAL_TO_EXTERNAL),
    RecordID: recordId,
    SyncType: SYNC_TYPES.translate(changeType, INTERNAL_TO_EXTERNAL),
    StoreID: storeId,
  };
  if (changeType === CHANGE_TYPES.DELETE) {
    return syncJson; // Don't need record data for deletions.
  }

  let syncData;
  if (syncOutRecord.changeType === 'delete') {
    // If record has been deleted, only sync the ID.
    syncData = { ID: recordId };
  } else {
    // Get the record the |syncOutRecord| refers to from the database.
    const recordResults = database.objects(recordType).filtered('id == $0', recordId);
    if (!recordResults || recordResults.length === 0) {
      // No such record
      throw new Error(`${recordType} with id = ${recordId} missing`);
    } else if (recordResults.length > 1) {
      // Duplicate records
      throw new Error(`Multiple ${recordType} records with id = ${recordId}`);
    }
    const record = recordResults[0];

    // Generate the appropriate data for the sync object to carry, representing the
    // record in its upstream form.
    try {
      syncData = generateSyncData(settings, recordType, record);
    } catch (error) {
      // There was an error with data (e.g. caused by null object).
      const siteName = settings.get(SYNC_SITE_NAME);
      const syncUrl = settings.get(SYNC_URL);
      const originalMessage = error.message;

      // Change error message to be helpful in bugsnag.
      error.message =
        `SYNC OUT ERROR. siteName: ${siteName}, serverUrl: ${syncUrl}, ` +
        `syncOutRecord.id: ${syncOutRecord.id}, storeId: ${storeId} changeType: ${changeType}, ` +
        `recordType: ${recordType}, recordId: ${recordId}, message: ${originalMessage}`;

      // Ping the error off to bugsnag.
      bugsnagClient.notify(error);

      // Make a nicer message for users and throw it again.
      error.message = `There was an error syncing. Contact mSupply mobile support. ${originalMessage}`;
      throw error;
    }
  }

  // Attach the record data to the json object.
  syncJson.Data = syncData;
  return syncJson;
};

export default generateSyncJson;
<commit_msg>Add additional fields to outgoingSync<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import { Client as BugsnagClient } from 'bugsnag-react-native';

import {
  INTERNAL_TO_EXTERNAL,
  RECORD_TYPES,
  REQUISITION_STATUSES,
  REQUISITION_TYPES,
  SEQUENCE_KEYS,
  STATUSES,
  SYNC_TYPES,
  TRANSACTION_BATCH_TYPES,
  TRANSACTION_TYPES,
} from './syncTranslators';
import { CHANGE_TYPES } from '../database';
import { SETTINGS_KEYS } from '../settings';

const { SUPPLYING_STORE_NAME_ID, THIS_STORE_ID, SYNC_URL, SYNC_SITE_NAME } = SETTINGS_KEYS;

const bugsnagClient = new BugsnagClient();

const getDateString = date => {
  let returnDate = '0000-00-00';
  if (date && typeof date === 'object') returnDate = date.toISOString().slice(0, 10);

  return `${returnDate}T00:00:00`;
};

function getTimeString(date) {
  if (!date || typeof date !== 'object') return '00:00:00';
  return date.toTimeString().substring(0, 8);
}

/**
 * Tries to get a value that is known to potentially lead to crash.
 * If path on the record returns null, throw an error with prototype extended with
 * |canDeleteSyncOut| set to true to indicates that sync should continue.
 *
 * @param {object}  record  The object to get properties from.
 * @param {string}  path    The path on that object safely try.
 * @return {any}            Whatever variable was stored at path, if no error thrown.
 */
const safeGet = (record, path) => {
  const pathSegments = path.split('.');
  let currentPath = 'record';
  let nestedProp = record;
  for (let i = 0; i < pathSegments.length; i += 1) {
    const segment = pathSegments[i];
    currentPath += `.${segment}`; // Build up path to know at what point potential errors occur.
    try {
      nestedProp = nestedProp[segment];
    } catch (error) {
      error.canDeleteSyncOut = true; // Safe to delete |syncOut|.
      error.message = `Error on object getter on path "${currentPath}", original message: ${
        error.message
      }`;
      throw error; // Bubble error to next handler.
    }
  }
  return nestedProp;
};

/**
 * Turn an internal database object into data representing a record in the
 * mSupply primary server, ready for sync.
 *
 * @param   {Settings}      settings    Access to local settings.
 * @param   {string}        recordType  Internal type of record being synced.
 * @param   {Realm.object}  record      The record being synced.
 * @return  {object}                    The data to sync (in the form of upstream record).
 */
const generateSyncData = (settings, recordType, record) => {
  switch (recordType) {
    case 'ItemBatch': {
      return {
        ID: record.id,
        store_ID: settings.get(THIS_STORE_ID),
        item_ID: record.itemId,
        pack_size: String(record.packSize),
        expiry_date: getDateString(record.expiryDate),
        batch: record.batch,
        available: String(record.numberOfPacks),
        quantity: String(record.numberOfPacks),
        stock_on_hand_tot: String(record.totalQuantity),
        cost_price: String(record.costPrice),
        sell_price: String(record.sellPrice),
        total_cost: String(record.costPrice * record.numberOfPacks),
        name_ID: settings.get(SUPPLYING_STORE_NAME_ID),
      };
    }
    case 'NumberSequence': {
      const thisStoreId = settings.get(THIS_STORE_ID);
      return {
        ID: record.id,
        name: SEQUENCE_KEYS.translate(record.sequenceKey, INTERNAL_TO_EXTERNAL, thisStoreId),
        value: String(record.highestNumberUsed),
      };
    }
    case 'NumberToReuse': {
      const thisStoreId = settings.get(THIS_STORE_ID);
      return {
        ID: record.id,
        name: SEQUENCE_KEYS.translate(record.sequenceKey, INTERNAL_TO_EXTERNAL, thisStoreId),
        number_to_use: String(record.number),
      };
    }
    case 'Requisition': {
      return {
        ID: record.id,
        date_entered: getDateString(record.entryDate),
        user_ID: record.enteredById,
        name_ID: record.otherStoreName && record.otherStoreName.id,
        status: REQUISITION_STATUSES.translate(record.status, INTERNAL_TO_EXTERNAL),
        daysToSupply: String(record.daysToSupply),
        store_ID: settings.get(THIS_STORE_ID),
        serial_number: record.serialNumber,
        requester_reference: record.requesterReference,
        comment: record.comment,
        type: REQUISITION_TYPES.translate(record.type, INTERNAL_TO_EXTERNAL),
        programID: record.program.id,
        periodID: record.period.id,
      };
    }
    case 'RequisitionItem': {
      return {
        ID: record.id,
        requisition_ID: record.requisitionId,
        item_ID: record.itemId,
        stock_on_hand: String(record.stockOnHand),
        daily_usage: String(record.dailyUsage),
        suggested_quantity: String(record.suggestedQuantity),
        actualQuan: String(record.suppliedQuantity),
        line_number: String(record.sortIndex),
        Cust_stock_order: String(record.requiredQuantity),
        comment: record.comment,
        optionID: record.option.id,
      };
    }
    case 'Stocktake': {
      return {
        ID: record.id,
        Description: record.name,
        stock_take_date: getDateString(record.stocktakeDate),
        stock_take_time: getTimeString(record.stocktakeDate),
        created_by_ID: record.createdBy && record.createdBy.id,
        status: STATUSES.translate(record.status, INTERNAL_TO_EXTERNAL),
        finalised_by_ID: record.finalisedBy && record.finalisedBy.id,
        invad_additions_ID: record.additions && record.additions.id,
        invad_reductions_ID: record.reductions && record.reductions.id,
        store_ID: settings.get(THIS_STORE_ID),
        comment: record.comment,
        stock_take_created_date: getDateString(record.createdDate),
        serial_number: record.serialNumber,
        programID: record.program.id,
      };
    }
    case 'StocktakeBatch': {
      return {
        ID: record.id,
        stock_take_ID: record.stocktake && record.stocktake.id,
        item_line_ID: record.itemBatchId,
        snapshot_qty: String(record.snapshotNumberOfPacks),
        snapshot_packsize: String(record.packSize),
        stock_take_qty: String(record.countedNumberOfPacks),
        line_number: String(record.sortIndex),
        expiry: getDateString(record.expiryDate),
        cost_price: String(record.costPrice),
        sell_price: String(record.sellPrice),
        Batch: record.batch,
        item_ID: record.itemId,
        optionID: record.option.id,
      };
    }
    case 'Transaction': {
      return {
        ID: record.id,
        name_ID: record.otherParty && record.otherParty.id,
        invoice_num: record.serialNumber,
        comment: record.comment,
        entry_date: getDateString(record.entryDate),
        type: TRANSACTION_TYPES.translate(record.type, INTERNAL_TO_EXTERNAL),
        status: STATUSES.translate(record.status, INTERNAL_TO_EXTERNAL),
        mode: 'store',
        total: String(record.totalPrice),
        their_ref: record.theirRef,
        confirm_date: getDateString(record.confirmDate),
        subtotal: String(record.totalPrice),
        user_ID: record.enteredBy && record.enteredBy.id,
        category_ID: record.category && record.category.id,
        confirm_time: getTimeString(record.confirmDate),
        store_ID: settings.get(THIS_STORE_ID),
        requisition_ID:
          record.linkedRequisition && record.linkedRequisition.id
            ? record.linkedRequisition.id
            : undefined,
      };
    }
    case 'TransactionBatch': {
      const { transaction } = record;
      return {
        ID: record.id,
        transaction_ID: record.transaction.id,
        item_ID: record.itemId,
        batch: record.batch,
        price_extension: String(record.totalPrice),
        note: record.note,
        cost_price: String(record.costPrice),
        sell_price: String(record.sellPrice),
        expiry_date: getDateString(record.expiryDate),
        pack_size: String(record.packSize),
        quantity: String(record.numberOfPacks),
        // |item_line_ID| can be null due to server merge bug in v3.83.
        item_line_ID: safeGet(record, 'itemBatch.id'),
        line_number: String(record.sortIndex),
        item_name: record.itemName,
        is_from_inventory_adjustment: transaction.isInventoryAdjustment,
        type: TRANSACTION_BATCH_TYPES.translate(transaction.type, INTERNAL_TO_EXTERNAL),
      };
    }
    default:
      throw new Error('Sync out record type not supported.');
  }
};

/**
 * Returns a json object fulfilling the requirements of the mSupply primary sync
 * server, based on a given |syncOutRecord|.
 *
 * @param   {Realm}         database       The local database.
 * @param   {Settings}      settings       Access to local settings.
 * @param   {Realm.object}  syncOutRecord  The sync out record to be translated.
 * @return  {object}                       The generated json object, ready to sync.
 */
export const generateSyncJson = (database, settings, syncOutRecord) => {
  if (!syncOutRecord || !syncOutRecord.isValid()) {
    throw new Error('Missing sync out record');
  }
  if (!syncOutRecord.recordType || !syncOutRecord.id || !syncOutRecord.recordId) {
    throw new Error('Malformed sync out record');
  }
  const { recordType, recordId, changeType } = syncOutRecord;
  const storeId = settings.get(THIS_STORE_ID);
  // Create the JSON object to sync.
  const syncJson = {
    SyncID: syncOutRecord.id,
    RecordType: RECORD_TYPES.translate(recordType, INTERNAL_TO_EXTERNAL),
    RecordID: recordId,
    SyncType: SYNC_TYPES.translate(changeType, INTERNAL_TO_EXTERNAL),
    StoreID: storeId,
  };
  if (changeType === CHANGE_TYPES.DELETE) {
    return syncJson; // Don't need record data for deletions.
  }

  let syncData;
  if (syncOutRecord.changeType === 'delete') {
    // If record has been deleted, only sync the ID.
    syncData = { ID: recordId };
  } else {
    // Get the record the |syncOutRecord| refers to from the database.
    const recordResults = database.objects(recordType).filtered('id == $0', recordId);
    if (!recordResults || recordResults.length === 0) {
      // No such record
      throw new Error(`${recordType} with id = ${recordId} missing`);
    } else if (recordResults.length > 1) {
      // Duplicate records
      throw new Error(`Multiple ${recordType} records with id = ${recordId}`);
    }
    const record = recordResults[0];

    // Generate the appropriate data for the sync object to carry, representing the
    // record in its upstream form.
    try {
      syncData = generateSyncData(settings, recordType, record);
    } catch (error) {
      // There was an error with data (e.g. caused by null object).
      const siteName = settings.get(SYNC_SITE_NAME);
      const syncUrl = settings.get(SYNC_URL);
      const originalMessage = error.message;

      // Change error message to be helpful in bugsnag.
      error.message =
        `SYNC OUT ERROR. siteName: ${siteName}, serverUrl: ${syncUrl}, ` +
        `syncOutRecord.id: ${syncOutRecord.id}, storeId: ${storeId} changeType: ${changeType}, ` +
        `recordType: ${recordType}, recordId: ${recordId}, message: ${originalMessage}`;

      // Ping the error off to bugsnag.
      bugsnagClient.notify(error);

      // Make a nicer message for users and throw it again.
      error.message = `There was an error syncing. Contact mSupply mobile support. ${originalMessage}`;
      throw error;
    }
  }

  // Attach the record data to the json object.
  syncJson.Data = syncData;
  return syncJson;
};

export default generateSyncJson;
<|endoftext|>


<commit_before>const fs = require('fs');
const webdriverio = require('webdriverio');

const {server} = require('../common');
const AddonManager = require('../../addon-manager');

const selenium = require('selenium-standalone');

const options = {
  desiredCapabilities: {
    browserName: 'firefox',
    acceptInsecureCerts: true,
    'moz:firefoxOptions': {
      args: ['-headless'],
    },
  },
};

describe('basic browser tests', function() {
  const originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
  beforeEach(() => {
    // Starting up and interacting with a browser takes forever
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 1000;
  });
  afterEach(async () => {
    jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
    try {
      await AddonManager.uninstallAddon('virtual-things-adapter', true, false);
    } catch (e) {
      console.warn('Unable to cleanup virtual-thing-adapter', e);
    }
  });

  it('creates a user', async () => {
    const url = `https://localhost:${server.address().port}`;

    await new Promise((res, rej) => {
      selenium.install(function(err) {
        if (err) {
          rej(err);
        } else {
          res();
        }
      });
    });

    const child = await new Promise((res, rej) => {
      selenium.start(function(err, child) {
        if (err) {
          rej(err);
        } else {
          res(child);
        }
      });
    });
    const browser = webdriverio
      .remote(options)
      .init();

    let stepNumber = 0;
    async function saveStepScreen(step) {
      let stepStr = stepNumber.toString();
      if (stepStr.length < 2) {
        stepStr = `0${stepStr}`;
      }

      if (!fs.existsSync('browser-test-output')) {
        fs.mkdirSync('browser-test-output');
      }
      await browser.saveScreenshot(
        `browser-test-output/${stepStr}-${step}.png`);
      stepNumber += 1;
    }

    await browser.url(url);

    await browser.waitForExist('#name', 5000);
    await browser.setValue('#name', 'Test User');
    await browser.setValue('#email', 'test@example.com');
    await browser.setValue('#password', 'rosebud');
    await browser.setValue('#confirm-password', 'rosebud');

    await saveStepScreen('create-user');

    await browser.click('#create-user-button');

    const newUrl = await browser.getUrl();
    expect(newUrl.endsWith('/things')).toBeTruthy();

    await saveStepScreen('main-screen');

    await browser.click('#menu-button');
    await saveStepScreen('menu-open');

    await browser.click('#settings-menu-item');
    await saveStepScreen('settings');

    await browser.click('#addon-settings-link');
    await saveStepScreen('addon-settings');

    await browser.click('#discover-addons-button');
    await saveStepScreen('discovering-addons');

    await browser.waitForExist('#addon-install-virtual-things-adapter', 5000);
    await browser.click('#addon-install-virtual-things-adapter');
    await browser.waitForExist('.addon-discovery-settings-added', 5000);
    await saveStepScreen('adapter-added');

    await browser.click('#settings-back-button');
    await browser.click('#settings-back-button');
    await browser.click('#menu-button');
    await browser.click('#things-menu-item');
    await browser.click('#add-button');
    await saveStepScreen('add-things-list');

    await browser.click('#new-thing-virtual-things-2 > .new-thing-save-button');
    await browser.click('#new-thing-virtual-things-9 > .new-thing-save-button');
    await browser.click('#add-thing-back-button');
    await saveStepScreen('things-list');

    let things = (await browser.elements('.thing')).value;
    expect(things.length).toBe(2);
    await browser.elementIdClick(things[0].ELEMENT);
    await saveStepScreen('things-list-dcl-on');

    let link = await browser.elementIdElement(things[0].ELEMENT,
                                              '.thing-details-link');
    await browser.elementIdClick(link.value.ELEMENT);
    let detailUrl = await browser.getUrl();
    expect(detailUrl.endsWith('/things/virtual-things-2')).toBeTruthy();
    await saveStepScreen('dimmable-color-light-detail');

    await browser.click('#back-button');

    await browser.waitForExist('.unknown-thing', 2000);

    things = (await browser.elements('.thing')).value;
    expect(things.length).toBe(2);
    link = await browser.elementIdElement(things[1].ELEMENT,
                                          '.thing-details-link');
    await browser.elementIdClick(link.value.ELEMENT);
    detailUrl = await browser.getUrl();
    expect(detailUrl.endsWith('/things/virtual-things-9')).toBeTruthy();
    await saveStepScreen('unknown-thing-detail');

    await browser.end();
    child.kill();
  });
});
<commit_msg>Add more waiting based on test failure<commit_after>const fs = require('fs');
const webdriverio = require('webdriverio');

const {server} = require('../common');
const AddonManager = require('../../addon-manager');

const selenium = require('selenium-standalone');

const options = {
  desiredCapabilities: {
    browserName: 'firefox',
    acceptInsecureCerts: true,
    'moz:firefoxOptions': {
      args: ['-headless'],
    },
  },
};

describe('basic browser tests', function() {
  const originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
  beforeEach(() => {
    // Starting up and interacting with a browser takes forever
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 60 * 1000;
  });
  afterEach(async () => {
    jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
    try {
      await AddonManager.uninstallAddon('virtual-things-adapter', true, false);
    } catch (e) {
      console.warn('Unable to cleanup virtual-thing-adapter', e);
    }
  });

  it('creates a user', async () => {
    const url = `https://localhost:${server.address().port}`;

    await new Promise((res, rej) => {
      selenium.install(function(err) {
        if (err) {
          rej(err);
        } else {
          res();
        }
      });
    });

    const child = await new Promise((res, rej) => {
      selenium.start(function(err, child) {
        if (err) {
          rej(err);
        } else {
          res(child);
        }
      });
    });
    const browser = webdriverio
      .remote(options)
      .init();

    let stepNumber = 0;
    async function saveStepScreen(step) {
      let stepStr = stepNumber.toString();
      if (stepStr.length < 2) {
        stepStr = `0${stepStr}`;
      }

      if (!fs.existsSync('browser-test-output')) {
        fs.mkdirSync('browser-test-output');
      }
      await browser.saveScreenshot(
        `browser-test-output/${stepStr}-${step}.png`);
      stepNumber += 1;
    }

    await browser.url(url);

    await browser.waitForExist('#name', 5000);
    await browser.setValue('#name', 'Test User');
    await browser.setValue('#email', 'test@example.com');
    await browser.setValue('#password', 'rosebud');
    await browser.setValue('#confirm-password', 'rosebud');

    await saveStepScreen('create-user');

    await browser.click('#create-user-button');

    await browser.waitForExist('#menu-button', 5000);

    const newUrl = await browser.getUrl();
    expect(newUrl.endsWith('/things')).toBeTruthy();

    await saveStepScreen('main-screen');

    await browser.click('#menu-button');
    await saveStepScreen('menu-open');

    await browser.click('#settings-menu-item');
    await saveStepScreen('settings');

    await browser.click('#addon-settings-link');
    await saveStepScreen('addon-settings');

    await browser.click('#discover-addons-button');
    await saveStepScreen('discovering-addons');

    await browser.waitForExist('#addon-install-virtual-things-adapter', 5000);
    await browser.click('#addon-install-virtual-things-adapter');
    await browser.waitForExist('.addon-discovery-settings-added', 5000);
    await saveStepScreen('adapter-added');

    await browser.click('#settings-back-button');
    await browser.click('#settings-back-button');
    await browser.click('#menu-button');
    await browser.click('#things-menu-item');
    await browser.click('#add-button');
    await saveStepScreen('add-things-list');

    await browser.click('#new-thing-virtual-things-2 > .new-thing-save-button');
    await browser.click('#new-thing-virtual-things-9 > .new-thing-save-button');
    await browser.click('#add-thing-back-button');
    await saveStepScreen('things-list');

    let things = (await browser.elements('.thing')).value;
    expect(things.length).toBe(2);
    await browser.elementIdClick(things[0].ELEMENT);
    await saveStepScreen('things-list-dcl-on');

    let link = await browser.elementIdElement(things[0].ELEMENT,
                                              '.thing-details-link');
    await browser.elementIdClick(link.value.ELEMENT);
    let detailUrl = await browser.getUrl();
    expect(detailUrl.endsWith('/things/virtual-things-2')).toBeTruthy();
    await saveStepScreen('dimmable-color-light-detail');

    await browser.click('#back-button');

    await browser.waitForExist('.unknown-thing', 2000);

    things = (await browser.elements('.thing')).value;
    expect(things.length).toBe(2);
    link = await browser.elementIdElement(things[1].ELEMENT,
                                          '.thing-details-link');
    await browser.elementIdClick(link.value.ELEMENT);
    detailUrl = await browser.getUrl();
    expect(detailUrl.endsWith('/things/virtual-things-9')).toBeTruthy();
    await saveStepScreen('unknown-thing-detail');

    await browser.end();
    child.kill();
  });
});
<|endoftext|>


<commit_before>import describe from 'tape';
import dom from 'cheerio';
import React from 'react';
import ReactDOMServer from 'react-dom/server';

import withFeatures from '../../with-features';
import configureFeature from '../../configure-feature';

const render = ReactDOMServer.renderToStaticMarkup;

/*
Test Scenarios:
  no features
    no config
      no fallbackComponent ✗
      with fallbackComponent
    empty config object
      no fallbackComponent
      with fallbackComponent
  with features
    config with initialFeatures
      one feature enabled ✔
        with props
      two features enabled
      no features enabled
    config with initialFeatures plus overrides from URL search params
*/

describe('integration of withFeatures() & configureFeature()', ({ test }) => {
  test('...no config (no features), no fallbackComponent', ({ end, deepEqual }) => {
    const NotFound = () =>
No help for you today!
;
    const HelpChatComponent = () =>
Need help? Call XXX-XXX-XXXX
;
    const FeatureComponent = withFeatures()(HelpChatComponent);
    const ConfiguredFeature = configureFeature(NotFound)('help')(FeatureComponent);

    const $ = dom.load(
      render(
        <FeatureComponent>
          <ConfiguredFeature />
        </FeatureComponent>
      )
    );
    {
      const msg = 'it should render NotFound component';
      const actual = $('.not-found').length;
      const expected = 1;
      deepEqual(actual, expected, msg);
    }
    {
      const msg = 'it should not render the Feature component';
      const actual = $('.help-chat').length;
      const expected = 0;
      deepEqual(actual, expected, msg);
    }
    {
      const msg = 'it should not render the Fallback component';
      const actual = $('.fall-back').length;
      const expected = 0;
      deepEqual(actual, expected, msg);
    }
    end();
  });

  test('...config with initialFeatures, no fallbackComponent', ({ end, deepEqual }) => {
    const initialFeatures = {
      comments: {
        enabled: false,
        dependencies: []
      },
      help: {
        enabled: true,
        dependencies: []
      },
      sorting: {
        enabled: false,
        dependencies: []
      }
    };

    const NotFound = () =>
No help for you today!
;
    const HelpChatComponent = () =>
Need help? Call XXX-XXX-XXXX
;
    const FeatureComponent = withFeatures({ initialFeatures })(HelpChatComponent);
    const ConfiguredFeature = configureFeature(NotFound)('help')(FeatureComponent);

    const $ = dom.load(
      render(
        <FeatureComponent>
          <ConfiguredFeature />
        </FeatureComponent>
      )
    );
    {
      const msg = 'it should not render NotFound component';
      const actual = $('.not-found').length;
      const expected = 0;
      deepEqual(actual, expected, msg);
    }
    {
      const msg = 'it should render the Feature component';
      const actual = $('.help-chat').length;
      const expected = 1;
      deepEqual(actual, expected, msg);
    }
    {
      const msg = 'it should not render the Fallback component';
      const actual = $('.fall-back').length;
      const expected = 0;
      deepEqual(actual, expected, msg);
    }
    end();
  });

  // test('...config with initialFeatures, no fallbackComponent, with props', ({ end, deepEqual }) => {
  //   const initialFeatures = {
  //     comments: {
  //       enabled: false,
  //       dependencies: []
  //     },
  //     help: {
  //       enabled: true,
  //       dependencies: []
  //     },
  //     sorting: {
  //       enabled: false,
  //       dependencies: []
  //     }
  //   };
  //
  //   const WrappedComponent = createWrappedComponent();
  //   const FeatureComponent = withFeatures({ initialFeatures })(WrappedComponent);
  //   const NotFound = createTestComponent('not-found');
  //   const ConfiguredFeature = configureFeature(NotFound)('help')(FeatureComponent);
  //   // const FeatureWithContext = withContext([])(ConfiguredFeature);
  //
  //   const someOtherProp = 'bacon and eggs';
  //
  //   const $ = dom.load(
  //     render(<ConfiguredFeature someOtherProp={someOtherProp} />)
  //   );
  //   {
  //     const msg = 'it should not render NotFound component';
  //     const actual = $('.not-found').length;
  //     const expected = 0;
  //     deepEqual(actual, expected, msg);
  //   }
  //   {
  //     const msg = 'it should render the Feature component';
  //     const actual = $('.feature').length;
  //     const expected = 1;
  //     deepEqual(actual, expected, msg);
  //   }
  //   {
  //     const msg = 'it should not render the Fallback component';
  //     const actual = $('.fall-back').length;
  //     const expected = 0;
  //     deepEqual(actual, expected, msg);
  //   }
  //   {
  //     const msg = 'it should pass through received props';
  //     const actual = $('.some-other-prop').text();
  //     const expected = someOtherProp;
  //     deepEqual(actual, expected, msg);
  //   }
  //   end();
  // });
});
<commit_msg>Stop passing the result of withFeatures into configureFeature<commit_after>import describe from 'tape';
import dom from 'cheerio';
import React from 'react';
import ReactDOMServer from 'react-dom/server';

import withFeatures from '../../with-features';
import configureFeature from '../../configure-feature';

const render = ReactDOMServer.renderToStaticMarkup;

/*
Test Scenarios:
  no features
    no config
      no fallbackComponent ✗
      with fallbackComponent
    empty config object
      no fallbackComponent
      with fallbackComponent
  with features
    config with initialFeatures
      one feature enabled ✔
        with props
      two features enabled
      no features enabled
    config with initialFeatures plus overrides from URL search params
*/

describe('integration of withFeatures() & configureFeature()', ({ test }) => {
  test('...no config (no features), no fallbackComponent', ({ end, deepEqual }) => {
    const NotFound = () =>
No help for you today!
;
    const SomeComponent = () =>

;
    const HelpChatComponent = () =>
Need help? Call XXX-XXX-XXXX
;
    const Features = withFeatures()(SomeComponent);
    const ConfiguredFeature = configureFeature(NotFound)('help')(HelpChatComponent);

    const $ = dom.load(
      render(
        <Features>
          <ConfiguredFeature />
        </Features>
      )
    );
    {
      const msg = 'it should render NotFound component';
      const actual = $('.not-found').length;
      const expected = 1;
      deepEqual(actual, expected, msg);
    }
    {
      const msg = 'it should not render the Feature component';
      const actual = $('.help-chat').length;
      const expected = 0;
      deepEqual(actual, expected, msg);
    }
    {
      const msg = 'it should not render the Fallback component';
      const actual = $('.fall-back').length;
      const expected = 0;
      deepEqual(actual, expected, msg);
    }
    end();
  });

  test('...config with initialFeatures, no fallbackComponent', ({ end, deepEqual }) => {
    const initialFeatures = {
      comments: {
        enabled: false,
        dependencies: []
      },
      help: {
        enabled: true,
        dependencies: []
      },
      sorting: {
        enabled: false,
        dependencies: []
      }
    };

    const NotFound = () =>
No help for you today!
;
    const SomeComponent = () =>

;
    const HelpChatComponent = () =>
Need help? Call XXX-XXX-XXXX
;
    const Features = withFeatures({ initialFeatures })(SomeComponent);
    const ConfiguredFeature = configureFeature(NotFound)('help')(HelpChatComponent);

    const $ = dom.load(
      render(
        <Features>
          <ConfiguredFeature />
        </Features>
      )
    );
    {
      const msg = 'it should not render NotFound component';
      const actual = $('.not-found').length;
      const expected = 0;
      deepEqual(actual, expected, msg);
    }
    {
      const msg = 'it should render the Feature component';
      const actual = $('.help-chat').length;
      const expected = 1;
      deepEqual(actual, expected, msg);
    }
    {
      const msg = 'it should not render the Fallback component';
      const actual = $('.fall-back').length;
      const expected = 0;
      deepEqual(actual, expected, msg);
    }
    end();
  });

  // test('...config with initialFeatures, no fallbackComponent, with props', ({ end, deepEqual }) => {
  //   const initialFeatures = {
  //     comments: {
  //       enabled: false,
  //       dependencies: []
  //     },
  //     help: {
  //       enabled: true,
  //       dependencies: []
  //     },
  //     sorting: {
  //       enabled: false,
  //       dependencies: []
  //     }
  //   };
  //
  //   const WrappedComponent = createWrappedComponent();
  //   const FeatureComponent = withFeatures({ initialFeatures })(WrappedComponent);
  //   const NotFound = createTestComponent('not-found');
  //   const ConfiguredFeature = configureFeature(NotFound)('help')(FeatureComponent);
  //   // const FeatureWithContext = withContext([])(ConfiguredFeature);
  //
  //   const someOtherProp = 'bacon and eggs';
  //
  //   const $ = dom.load(
  //     render(<ConfiguredFeature someOtherProp={someOtherProp} />)
  //   );
  //   {
  //     const msg = 'it should not render NotFound component';
  //     const actual = $('.not-found').length;
  //     const expected = 0;
  //     deepEqual(actual, expected, msg);
  //   }
  //   {
  //     const msg = 'it should render the Feature component';
  //     const actual = $('.feature').length;
  //     const expected = 1;
  //     deepEqual(actual, expected, msg);
  //   }
  //   {
  //     const msg = 'it should not render the Fallback component';
  //     const actual = $('.fall-back').length;
  //     const expected = 0;
  //     deepEqual(actual, expected, msg);
  //   }
  //   {
  //     const msg = 'it should pass through received props';
  //     const actual = $('.some-other-prop').text();
  //     const expected = someOtherProp;
  //     deepEqual(actual, expected, msg);
  //   }
  //   end();
  // });
});
<|endoftext|>


<commit_before>import TriggerButton from './components/TriggerButton';
import Notification from './components/Notification';
import PopupModal from './components/PopupModal';
import setupGhostApi from './utils/api';
import AppContext from './AppContext';
import {hasMode} from './utils/check-mode';
import {getActivePage, isAccountPage} from './pages';
import * as Fixtures from './utils/fixtures';
import ActionHandler from './actions';
import './App.css';
import NotificationParser from './utils/notifications';
import {createPopupNotification} from './utils/helpers';
const React = require('react');

const DEV_MODE_DATA = {
    showPopup: true,
    site: Fixtures.site,
    member: Fixtures.member.paid,
    page: 'signup'
};
export default class App extends React.Component {
    constructor(props) {
        super(props);

        if (!props.testState) {
            // Setup custom trigger button handling
            this.setupCustomTriggerButton();
        }

        // testState is used by App.test to pass custom default state for testing
        this.state = props.testState || {
            site: null,
            member: null,
            page: 'loading',
            showPopup: false,
            action: 'init:running',
            initStatus: 'running',
            lastPage: null
        };
    }

    componentDidMount() {
        if (!this.props.testState) {
            this.initSetup();
        }
    }

    componentDidUpdate(prevProps, prevState) {
        if (prevState.showPopup !== this.state.showPopup) {
            this.handleCustomTriggerClassUpdate();
        }
    }

    componentWillUnmount() {
        clearTimeout(this.timeoutId);
        this.customTriggerButtons && this.customTriggerButtons.forEach((customTriggerButton) => {
            customTriggerButton.removeEventListener('click', this.clickHandler);
        });
    }

    /** Setup custom trigger buttons handling on page */
    setupCustomTriggerButton() {
        // Handler for custom buttons
        this.clickHandler = (event) => {
            const target = event.currentTarget;
            const pagePath = (target && target.dataset.portal);
            const pageFromPath = this.getPageFromPath(pagePath);

            event.preventDefault();
            this.onAction('openPopup', {page: pageFromPath});
        };
        const customTriggerSelector = '[data-portal]';
        const popupCloseClass = 'gh-members-popup-close';
        this.customTriggerButtons = document.querySelectorAll(customTriggerSelector) || [];
        this.customTriggerButtons.forEach((customTriggerButton) => {
            customTriggerButton.classList.add(popupCloseClass);
            // Remove any existing event listener
            customTriggerButton.removeEventListener('click', this.clickHandler);
            customTriggerButton.addEventListener('click', this.clickHandler);
        });
    }

    /** Handle portal class set on custom trigger buttons */
    handleCustomTriggerClassUpdate() {
        const popupOpenClass = 'gh-members-popup-open';
        const popupCloseClass = 'gh-members-popup-close';
        this.customTriggerButtons.forEach((customButton) => {
            const elAddClass = this.state.showPopup ? popupOpenClass : popupCloseClass;
            const elRemoveClass = this.state.showPopup ? popupCloseClass : popupOpenClass;
            customButton.classList.add(elAddClass);
            customButton.classList.remove(elRemoveClass);
        });
    }

    /** Initialize portal setup on load, fetch data and setup state*/
    async initSetup() {
        try {
            // Fetch data from API, links, preview, dev sources
            const {site, member, page, showPopup, popupNotification} = await this.fetchData();
            this.setState({
                site,
                member,
                page,
                showPopup,
                popupNotification,
                action: 'init:success',
                initStatus: 'success'
            });

            // Listen to preview mode changes
            this.hashHandler = () => {
                this.updateStateForPreview();
            };
            window.addEventListener('hashchange', this.hashHandler, false);
        } catch (e) {
            /* eslint-disable no-console */
            console.error(`[Portal] Failed to initialize:`, e);
            /* eslint-enable no-console */
            this.setState({
                action: 'init:failed',
                initStatus: 'failed'
            });
        }
    }

    /** Fetch data from all available sources */
    async fetchData() {
        const {site: apiSiteData, member} = await this.fetchApiData();
        const {site: devSiteData, ...restDevData} = this.fetchDevData();
        const {site: linkSiteData, ...restLinkData} = this.fetchLinkData();
        const {site: previewSiteData, ...restPreviewData} = this.fetchPreviewData();
        const {site: notificationSiteData, ...restNotificationData} = this.fetchNotificationData();

        // const stripeParam = this.getStripeUrlParam();
        let page = '';

        // /** Set page for magic link popup on stripe success*/
        // if (!member && stripeParam === 'success') {
        //     page = 'magiclink';
        // }

        return {
            member,
            page,
            site: {
                ...apiSiteData,
                ...linkSiteData,
                ...previewSiteData,
                ...notificationSiteData,
                ...devSiteData
            },
            ...restDevData,
            ...restLinkData,
            ...restNotificationData,
            ...restPreviewData
        };
    }

    /** Fetch state for Dev mode */
    fetchDevData() {
        // Setup custom dev mode data from fixtures
        if (hasMode(['dev'])) {
            return DEV_MODE_DATA;
        }
        return {};
    }

    /** Fetch state from Query String */
    fetchQueryStrData(qs = '') {
        const qsParams = new URLSearchParams(qs);
        const data = {
            site: {}
        };
        const allowedPlans = [];

        // Handle the query params key/value pairs
        for (let pair of qsParams.entries()) {
            const key = pair[0];
            const value = decodeURIComponent(pair[1]);
            if (key === 'button') {
                data.site.portal_button = JSON.parse(value);
            } else if (key === 'name') {
                data.site.portal_name = JSON.parse(value);
            } else if (key === 'isFree' && JSON.parse(value)) {
                allowedPlans.push('free');
            } else if (key === 'isMonthly' && JSON.parse(value)) {
                allowedPlans.push('monthly');
            } else if (key === 'isYearly' && JSON.parse(value)) {
                allowedPlans.push('yearly');
            } else if (key === 'plans') {
                data.site.portal_plans = value ? value.split(',') : [];
            } else if (key === 'page' && value) {
                data.page = value;
            } else if (key === 'accentColor' && (value === '' || value)) {
                data.site.accent_color = value;
            } else if (key === 'buttonIcon' && value) {
                data.site.portal_button_icon = value;
            } else if (key === 'signupButtonText') {
                data.site.portal_button_signup_text = value || '';
            } else if (key === 'buttonStyle' && value) {
                data.site.portal_button_style = value;
            }
        }
        data.site.portal_plans = allowedPlans;
        return data;
    }

    fetchNotificationData() {
        const {type, status, duration, autoHide, closeable} = NotificationParser({billingOnly: true}) || {};
        if (['stripe:billing-update'].includes(type)) {
            const popupNotification = createPopupNotification({
                type, status, duration, closeable, autoHide, state: this.state,
                message: status === 'success' ? 'Successfully updated Billing info' : 'Billing update was cancelled'
            });
            return {
                showPopup: true,
                popupNotification
            };
        }
        return {};
    }

    /** Fetch state from Portal Links */
    fetchLinkData() {
        const [path] = window.location.hash.substr(1).split('?');
        const linkRegex = /^\/portal(?:\/(\w+(?:\/\w+)?))?$/;
        if (path && linkRegex.test(path)) {
            const [,pagePath] = path.match(linkRegex);
            const page = this.getPageFromPath(pagePath);
            return {
                showPopup: true,
                ...(page ? {page} : {})
            };
        }
        return {};
    }

    /** Fetch state from Preview mode */
    fetchPreviewData() {
        const [, qs] = window.location.hash.substr(1).split('?');
        if (hasMode(['preview'])) {
            const data = this.fetchQueryStrData(qs);
            data.showPopup = true;
            return data;
        }
        return {};
    }

    /** Fetch site and member session data with Ghost Apis  */
    async fetchApiData() {
        try {
            const {siteUrl} = this.props;
            this.GhostApi = setupGhostApi({siteUrl});
            const {site, member} = await this.GhostApi.init();
            return {site, member};
        } catch (e) {
            if (hasMode(['dev', 'test'])) {
                return {};
            }
            throw e;
        }
    }

    /** Handle actions from across App and update state */
    async onAction(action, data) {
        clearTimeout(this.timeoutId);
        this.setState({
            action: `${action}:running`
        });
        try {
            const updatedState = await ActionHandler({action, data, state: this.state, api: this.GhostApi});
            this.setState(updatedState);

            /** Reset action state after short timeout if not failed*/
            if (updatedState && updatedState.action && !updatedState.action.includes(':failed')) {
                this.timeoutId = setTimeout(() => {
                    this.setState({
                        action: ''
                    });
                }, 2000);
            }
        } catch (error) {
            const popupNotification = createPopupNotification({
                type: `${action}:failed`,
                autoHide: true, closeable: true, status: 'error', state: this.state,
                meta: {
                    error
                }
            });
            this.setState({
                action: `${action}:failed`,
                popupNotification
            });
        }
    }

    /**Handle state update for preview url changes */
    updateStateForPreview() {
        const {site: previewSite, ...restPreviewData} = this.fetchPreviewData();
        this.setState({
            site: {
                ...this.state.site,
                ...(previewSite || {})
            },
            ...restPreviewData
        });
    }

    /**Fetch Stripe param from site url after redirect from Stripe page*/
    getStripeUrlParam() {
        const url = new URL(window.location);
        return url.searchParams.get('stripe');
    }

    /**Get Portal page from Link/Data-attribute path*/
    getPageFromPath(path) {
        if (path === 'signup') {
            return 'signup';
        } else if (path === 'signin') {
            return 'signin';
        } else if (path === 'account') {
            return 'accountHome';
        } else if (path === 'account/plans') {
            return 'accountPlan';
        } else if (path === 'account/profile') {
            return 'accountProfile';
        }
    }

    /**Get Accent color from site data, fallback to default*/
    getAccentColor() {
        const {accent_color: accentColor = '#3db0ef'} = this.state.site || {};
        return accentColor || '#3db0ef';
    }

    /**Get final page set in App context from state data*/
    getContextPage({page, member}) {
        /**Set default page based on logged-in status */
        if (!page) {
            page = member ? 'accountHome' : 'signup';
        }

        return getActivePage({page});
    }

    /**Get final member set in App context from state data*/
    getContextMember({page, member}) {
        if (hasMode(['dev', 'preview'])) {
            /** Use dummy member(free or paid) for account pages in dev/preview mode*/
            if (isAccountPage({page})) {
                if (hasMode(['dev'])) {
                    return member || Fixtures.member.free;
                } else if (hasMode(['preview'])) {
                    return Fixtures.member.preview;
                } else {
                    return Fixtures.member.paid;
                }
            }

            /** Ignore member for non-account pages in dev/preview mode*/
            return null;
        }
        return member;
    }

    /**Get final App level context from data/state*/
    getContextFromState() {
        const {site, member, action, page, lastPage, showPopup, popupNotification} = this.state;
        const contextPage = this.getContextPage({page, member});
        const contextMember = this.getContextMember({page: contextPage, member});
        return {
            site,
            action,
            brandColor: this.getAccentColor(),
            page: contextPage,
            member: contextMember,
            lastPage,
            showPopup,
            popupNotification,
            onAction: (_action, data) => this.onAction(_action, data)
        };
    }

    render() {
        if (this.state.initStatus === 'success') {
            return (
                <AppContext.Provider value={this.getContextFromState()}>
                    <PopupModal />
                    <TriggerButton />
                    <Notification />
                </AppContext.Provider>
            );
        }
        return null;
    }
}
<commit_msg>Fixed hash change for links<commit_after>import TriggerButton from './components/TriggerButton';
import Notification from './components/Notification';
import PopupModal from './components/PopupModal';
import setupGhostApi from './utils/api';
import AppContext from './AppContext';
import {hasMode} from './utils/check-mode';
import {getActivePage, isAccountPage} from './pages';
import * as Fixtures from './utils/fixtures';
import ActionHandler from './actions';
import './App.css';
import NotificationParser from './utils/notifications';
import {createPopupNotification} from './utils/helpers';
const React = require('react');

const DEV_MODE_DATA = {
    showPopup: true,
    site: Fixtures.site,
    member: Fixtures.member.paid,
    page: 'signup'
};
export default class App extends React.Component {
    constructor(props) {
        super(props);

        if (!props.testState) {
            // Setup custom trigger button handling
            this.setupCustomTriggerButton();
        }

        // testState is used by App.test to pass custom default state for testing
        this.state = props.testState || {
            site: null,
            member: null,
            page: 'loading',
            showPopup: false,
            action: 'init:running',
            initStatus: 'running',
            lastPage: null
        };
    }

    componentDidMount() {
        if (!this.props.testState) {
            this.initSetup();
        }
    }

    componentDidUpdate(prevProps, prevState) {
        if (prevState.showPopup !== this.state.showPopup) {
            this.handleCustomTriggerClassUpdate();
        }
    }

    componentWillUnmount() {
        clearTimeout(this.timeoutId);
        this.customTriggerButtons && this.customTriggerButtons.forEach((customTriggerButton) => {
            customTriggerButton.removeEventListener('click', this.clickHandler);
        });
    }

    /** Setup custom trigger buttons handling on page */
    setupCustomTriggerButton() {
        // Handler for custom buttons
        this.clickHandler = (event) => {
            const target = event.currentTarget;
            const pagePath = (target && target.dataset.portal);
            const pageFromPath = this.getPageFromPath(pagePath);

            event.preventDefault();
            this.onAction('openPopup', {page: pageFromPath});
        };
        const customTriggerSelector = '[data-portal]';
        const popupCloseClass = 'gh-members-popup-close';
        this.customTriggerButtons = document.querySelectorAll(customTriggerSelector) || [];
        this.customTriggerButtons.forEach((customTriggerButton) => {
            customTriggerButton.classList.add(popupCloseClass);
            // Remove any existing event listener
            customTriggerButton.removeEventListener('click', this.clickHandler);
            customTriggerButton.addEventListener('click', this.clickHandler);
        });
    }

    /** Handle portal class set on custom trigger buttons */
    handleCustomTriggerClassUpdate() {
        const popupOpenClass = 'gh-members-popup-open';
        const popupCloseClass = 'gh-members-popup-close';
        this.customTriggerButtons.forEach((customButton) => {
            const elAddClass = this.state.showPopup ? popupOpenClass : popupCloseClass;
            const elRemoveClass = this.state.showPopup ? popupCloseClass : popupOpenClass;
            customButton.classList.add(elAddClass);
            customButton.classList.remove(elRemoveClass);
        });
    }

    /** Initialize portal setup on load, fetch data and setup state*/
    async initSetup() {
        try {
            // Fetch data from API, links, preview, dev sources
            const {site, member, page, showPopup, popupNotification} = await this.fetchData();
            this.setState({
                site,
                member,
                page,
                showPopup,
                popupNotification,
                action: 'init:success',
                initStatus: 'success'
            });

            // Listen to preview mode changes
            this.hashHandler = () => {
                this.updateStateForPreviewLinks();
            };
            window.addEventListener('hashchange', this.hashHandler, false);
        } catch (e) {
            /* eslint-disable no-console */
            console.error(`[Portal] Failed to initialize:`, e);
            /* eslint-enable no-console */
            this.setState({
                action: 'init:failed',
                initStatus: 'failed'
            });
        }
    }

    /** Fetch data from all available sources */
    async fetchData() {
        const {site: apiSiteData, member} = await this.fetchApiData();
        const {site: devSiteData, ...restDevData} = this.fetchDevData();
        const {site: linkSiteData, ...restLinkData} = this.fetchLinkData();
        const {site: previewSiteData, ...restPreviewData} = this.fetchPreviewData();
        const {site: notificationSiteData, ...restNotificationData} = this.fetchNotificationData();

        // const stripeParam = this.getStripeUrlParam();
        let page = '';

        // /** Set page for magic link popup on stripe success*/
        // if (!member && stripeParam === 'success') {
        //     page = 'magiclink';
        // }

        return {
            member,
            page,
            site: {
                ...apiSiteData,
                ...linkSiteData,
                ...previewSiteData,
                ...notificationSiteData,
                ...devSiteData
            },
            ...restDevData,
            ...restLinkData,
            ...restNotificationData,
            ...restPreviewData
        };
    }

    /** Fetch state for Dev mode */
    fetchDevData() {
        // Setup custom dev mode data from fixtures
        if (hasMode(['dev'])) {
            return DEV_MODE_DATA;
        }
        return {};
    }

    /** Fetch state from Query String */
    fetchQueryStrData(qs = '') {
        const qsParams = new URLSearchParams(qs);
        const data = {
            site: {}
        };
        const allowedPlans = [];

        // Handle the query params key/value pairs
        for (let pair of qsParams.entries()) {
            const key = pair[0];
            const value = decodeURIComponent(pair[1]);
            if (key === 'button') {
                data.site.portal_button = JSON.parse(value);
            } else if (key === 'name') {
                data.site.portal_name = JSON.parse(value);
            } else if (key === 'isFree' && JSON.parse(value)) {
                allowedPlans.push('free');
            } else if (key === 'isMonthly' && JSON.parse(value)) {
                allowedPlans.push('monthly');
            } else if (key === 'isYearly' && JSON.parse(value)) {
                allowedPlans.push('yearly');
            } else if (key === 'plans') {
                data.site.portal_plans = value ? value.split(',') : [];
            } else if (key === 'page' && value) {
                data.page = value;
            } else if (key === 'accentColor' && (value === '' || value)) {
                data.site.accent_color = value;
            } else if (key === 'buttonIcon' && value) {
                data.site.portal_button_icon = value;
            } else if (key === 'signupButtonText') {
                data.site.portal_button_signup_text = value || '';
            } else if (key === 'buttonStyle' && value) {
                data.site.portal_button_style = value;
            }
        }
        data.site.portal_plans = allowedPlans;
        return data;
    }

    fetchNotificationData() {
        const {type, status, duration, autoHide, closeable} = NotificationParser({billingOnly: true}) || {};
        if (['stripe:billing-update'].includes(type)) {
            const popupNotification = createPopupNotification({
                type, status, duration, closeable, autoHide, state: this.state,
                message: status === 'success' ? 'Successfully updated Billing info' : 'Billing update was cancelled'
            });
            return {
                showPopup: true,
                popupNotification
            };
        }
        return {};
    }

    /** Fetch state from Portal Links */
    fetchLinkData() {
        const [path] = window.location.hash.substr(1).split('?');
        const linkRegex = /^\/portal(?:\/(\w+(?:\/\w+)?))?$/;
        if (path && linkRegex.test(path)) {
            const [,pagePath] = path.match(linkRegex);
            const page = this.getPageFromPath(pagePath);
            return {
                showPopup: true,
                ...(page ? {page} : {})
            };
        }
        return {};
    }

    /** Fetch state from Preview mode */
    fetchPreviewData() {
        const [, qs] = window.location.hash.substr(1).split('?');
        if (hasMode(['preview'])) {
            const data = this.fetchQueryStrData(qs);
            data.showPopup = true;
            return data;
        }
        return {};
    }

    /** Fetch site and member session data with Ghost Apis  */
    async fetchApiData() {
        try {
            const {siteUrl} = this.props;
            this.GhostApi = setupGhostApi({siteUrl});
            const {site, member} = await this.GhostApi.init();
            return {site, member};
        } catch (e) {
            if (hasMode(['dev', 'test'])) {
                return {};
            }
            throw e;
        }
    }

    /** Handle actions from across App and update state */
    async onAction(action, data) {
        clearTimeout(this.timeoutId);
        this.setState({
            action: `${action}:running`
        });
        try {
            const updatedState = await ActionHandler({action, data, state: this.state, api: this.GhostApi});
            this.setState(updatedState);

            /** Reset action state after short timeout if not failed*/
            if (updatedState && updatedState.action && !updatedState.action.includes(':failed')) {
                this.timeoutId = setTimeout(() => {
                    this.setState({
                        action: ''
                    });
                }, 2000);
            }
        } catch (error) {
            const popupNotification = createPopupNotification({
                type: `${action}:failed`,
                autoHide: true, closeable: true, status: 'error', state: this.state,
                meta: {
                    error
                }
            });
            this.setState({
                action: `${action}:failed`,
                popupNotification
            });
        }
    }

    /**Handle state update for preview url changes */
    updateStateForPreviewLinks() {
        const {site: previewSite, ...restPreviewData} = this.fetchPreviewData();
        const {site: linkSite, ...restLinkData} = this.fetchLinkData();
        this.setState({
            site: {
                ...this.state.site,
                ...(linkSite || {}),
                ...(previewSite || {})
            },
            ...restLinkData,
            ...restPreviewData
        });
    }

    /**Fetch Stripe param from site url after redirect from Stripe page*/
    getStripeUrlParam() {
        const url = new URL(window.location);
        return url.searchParams.get('stripe');
    }

    /**Get Portal page from Link/Data-attribute path*/
    getPageFromPath(path) {
        if (path === 'signup') {
            return 'signup';
        } else if (path === 'signin') {
            return 'signin';
        } else if (path === 'account') {
            return 'accountHome';
        } else if (path === 'account/plans') {
            return 'accountPlan';
        } else if (path === 'account/profile') {
            return 'accountProfile';
        }
    }

    /**Get Accent color from site data, fallback to default*/
    getAccentColor() {
        const {accent_color: accentColor = '#3db0ef'} = this.state.site || {};
        return accentColor || '#3db0ef';
    }

    /**Get final page set in App context from state data*/
    getContextPage({page, member}) {
        /**Set default page based on logged-in status */
        if (!page) {
            page = member ? 'accountHome' : 'signup';
        }

        return getActivePage({page});
    }

    /**Get final member set in App context from state data*/
    getContextMember({page, member}) {
        if (hasMode(['dev', 'preview'])) {
            /** Use dummy member(free or paid) for account pages in dev/preview mode*/
            if (isAccountPage({page})) {
                if (hasMode(['dev'])) {
                    return member || Fixtures.member.free;
                } else if (hasMode(['preview'])) {
                    return Fixtures.member.preview;
                } else {
                    return Fixtures.member.paid;
                }
            }

            /** Ignore member for non-account pages in dev/preview mode*/
            return null;
        }
        return member;
    }

    /**Get final App level context from data/state*/
    getContextFromState() {
        const {site, member, action, page, lastPage, showPopup, popupNotification} = this.state;
        const contextPage = this.getContextPage({page, member});
        const contextMember = this.getContextMember({page: contextPage, member});
        return {
            site,
            action,
            brandColor: this.getAccentColor(),
            page: contextPage,
            member: contextMember,
            lastPage,
            showPopup,
            popupNotification,
            onAction: (_action, data) => this.onAction(_action, data)
        };
    }

    render() {
        if (this.state.initStatus === 'success') {
            return (
                <AppContext.Provider value={this.getContextFromState()}>
                    <PopupModal />
                    <TriggerButton />
                    <Notification />
                </AppContext.Provider>
            );
        }
        return null;
    }
}
<|endoftext|>


<commit_before>var kickassService = require('../../services/kickassService');
// Setup
kickassService.setup();

module.exports = {
  'maxResults': 5,
  'regex': new RegExp('^[sS]earch for (.*)'),
  'run': function(input, context, callback) {
    var self = this;
    // console.log(input);
    var query = input.message;
    query = query.match(self.regex)[1];
    // console.log(query);
    kickassService.find({ query: { query: query } }, function(err, results) {
        // console.log(results);
        var message = 'Found '+results.length+' Torrent(s). Here are the top 5:\n';
        // List the available Torrents
        var len = Math.min(self.maxResults, results.length);
        for (var i=0; i<len; i++)
        {
          var torrent = results[i];
          message += (i+1)+'. '+ torrent.title + '\n';
        }
        // Create response
        var response = {
          'response': {
            'plain': message
            }
        };
        // Save Torrents to context
        context.foundTorrents = results;
        // return
        return callback(err, response);
    });
  }
};<commit_msg>See #3. Show more Torrent details when searching for new Torrent.<commit_after>var kickassService = require('../../services/kickassService');
// Setup
kickassService.setup();

module.exports = {
  'maxResults': 5,
  'regex': new RegExp('^[sS]earch for (.*)'),
  'run': function(input, context, callback) {
    var self = this;
    // console.log(input);
    var query = input.message;
    query = query.match(self.regex)[1];
    // console.log(query);
    kickassService.find({ query: { query: query } }, function(err, results) {
        // console.log(results);
        var message = 'Found '+results.length+' Torrent(s). Here are the top 5:\n';
        // List the available Torrents
        var len = Math.min(self.maxResults, results.length);
        for (var i=0; i<len; i++)
        {
          var torrent = results[i];
          var downloadSizeInMB = torrent.size / 1000 / 1000;
          var stats = (torrent.verified?'Verified and ':'') + Math.round(downloadSizeInMB * 100)/100 + ' MB with '+torrent.seeds+' seed and '+torrent.leechs+' leech';
          message += (i+1)+'. '+ torrent.title + ' ('+stats+') \n';
        }
        // Create response
        var response = {
          'response': {
            'plain': message
          }
        };
        // Save Torrents to context
        context.foundTorrents = results;
        // return
        return callback(err, response);
      });
  }
};<|endoftext|>


<commit_before>// Configs
const dir = require(`${global.baseDir}/global-dirs`)
const files = require(`${dir.includes}files`)

const shared = {
	cache: true,
	module: { rules: [{
		test: /\.jsx$/,
		loader: 'happypack/loader?id=jsx',
		include: [files],
	}, {
		test: /\.css$/,
		loader: 'happypack/loader?id=css',
	}, {
		test: /\.styl$/,
		loader: 'happypack/loader?id=styl',
		include: [files],
	}, {
		test: /\.(jpe?g|png|gif|svg)$/i,
		loader: 'url-loader?limit=10000',
	}, {
		test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
		loader: 'url-loader?limit=10000&minetype=application/font-woff',
	}, {
		test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
		loader: 'file-loader',
	}, {
		test: /\.html$|\.css$/,
		loader: 'file-loader?name=[name].[ext]',
	}]},
	resolve: {
		extensions: ['.js', '.jsx', '.json', '.css', '.styl'],
		modules: [files, 'node_modules'],
	},
}

const dev = {
	// colors: true,
	// debug: true,
	devtool: 'eval-source-map',
	// minimize: false,
	// prerender: false,
}

const prod = {
	// colors: false,
	// debug: false,
	devtool: false,
	// minimize: true,
	// noInfo: true,
	// prerender: true,
}

module.exports = {
	getDev: () => {
		const webpackDefaultConfig = Object.assign({}, shared, dev )
		webpackDefaultConfig.module.rules.push({
			test: /\.json$/,
			loaders: [
				'json-loader',
				'transform-loader?brfs',
			]
		})

		return webpackDefaultConfig
	},
	getProd: () => Object.assign({}, shared, prod),
}
<commit_msg>Cleaned up comments in webpack.config.default.js<commit_after>// Configs
const dir = require(`${global.baseDir}/global-dirs`)
const files = require(`${dir.includes}files`)

const shared = {
	cache: true,
	module: { rules: [{
		test: /\.jsx$/,
		loader: 'happypack/loader?id=jsx',
		include: [files],
	}, {
		test: /\.css$/,
		loader: 'happypack/loader?id=css',
	}, {
		test: /\.styl$/,
		loader: 'happypack/loader?id=styl',
		include: [files],
	}, {
		test: /\.(jpe?g|png|gif|svg)$/i,
		loader: 'url-loader?limit=10000',
	}, {
		test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
		loader: 'url-loader?limit=10000&minetype=application/font-woff',
	}, {
		test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
		loader: 'file-loader',
	}, {
		test: /\.html$|\.css$/,
		loader: 'file-loader?name=[name].[ext]',
	}]},
	resolve: {
		extensions: ['.js', '.jsx', '.json', '.css', '.styl'],
		modules: [files, 'node_modules'],
	},
}

const dev = {
	devtool: 'eval-source-map',
}

const prod = {
	devtool: false,
}

module.exports = {
	getDev: () => {
		const webpackDefaultConfig = Object.assign({}, shared, dev )
		webpackDefaultConfig.module.rules.push({
			test: /\.json$/,
			loaders: [
				'json-loader',
				'transform-loader?brfs',
			]
		})

		return webpackDefaultConfig
	},
	getProd: () => Object.assign({}, shared, prod),
}
<|endoftext|>


<commit_before>module('panel_manager');

test("panels navigatable", function(){
    stop();
    $.get('../media/panel_manager.html', function(data){
        var panelManager = $(data).find('#panelManager');
        start();
        ok(panelManager.length == 1, 'ajax request for setup successful');
        $(document.body).append(panelManager);
        var mngr = $('#panelManager').panelManager({top: '3em', left: '0px'});
        ok(mngr.length == 1, 'panelManager created.');
        ok($('li.ui-state-active a[href="#tab1"]').length == 1, 'Starts with tab1 active');
        $('a[href="#tab4"]').click();
        ok($("li.ui-state-active a[href='#tab4']").length == 1, 'Tab changed to tab4');
        equals(mngr.panelManager('getStack').length, 1, "only home panel in stack");
    });
});

test("home -> panel 1 navigatable", function(){
    var mngr = $('#panelManager');
    stop();
    mngr.unbind('panelChange');
    mngr.bind('panelChange', function(event, data){
        equals($(data).find('h1').html(), 'Panel 1', 'The appropriate panel loaded.');
        $('#link1').click();
        equals($(data).find('h1').html(), 'Panel 1', 'Clicking link to current panel causes no change.');
        equals(mngr.panelManager('getStack').length, 2, "only home panel and panel 1 in stack");
        start();
    });
    $('#panelTests').click();
});

test("panel 1 -> home works", function(){
    var mngr = $('#panelManager');
    mngr.unbind('panelChange');
    stop();
    mngr.bind('panelChange', function(event, data){
        equals($(data).find('a[href="#tab4"]').html(), 'Tab 4', 'Home panel shown.');
        equals(mngr.panelManager('getStack').length, 1, "only home panel in stack");
        start();
    });
    $('.back_link').click();
});

test("home -> panel 1", function(){
    var mngr = $('#panelManager');
    stop();
    mngr.unbind('panelChange');
    mngr.bind('panelChange', function(event, data){
        equals($(data).find('h1').html(), 'Panel 1', 'The appropriate panel loaded.');
        equals(mngr.panelManager('getStack').length, 2, "only home panel and panel 1 in stack");
        start();
    });
    $('#panelTests').click();
});

test("panel 1 -> panel 2", function(){
    var mngr = $('#panelManager');
    stop();
    mngr.unbind('panelChange');
    mngr.bind('panelChange', function(event, data){
        equals($(data).find('h1').html(), 'Panel 2', 'The appropriate panel loaded.');
        equals(mngr.panelManager('getStack').length, 3, "home, panel 1 & 2 in stack");
        start();
    });
    $('#link2').click();
});

test("panel 2 -> panel 3", function(){
    var mngr = $('#panelManager');
    stop();
    mngr.unbind('panelChange');
    mngr.bind('panelChange', function(event, data){
        equals($(data).find('h1').html(), 'Panel 3', 'The appropriate panel loaded.');
        equals(mngr.panelManager('getStack').length, 4, "home, panel 1, 2 & 3 in stack");
        start();
    });
    $('#link3').click();
});

test("panel 3 -> panel 2 via back button", function(){
    var mngr = $('#panelManager');
    stop();
    mngr.unbind('panelChange');
    mngr.bind('panelChange', function(event, data){
        equals($(data).find('h1').html(), 'Panel 2', 'The appropriate panel loaded.');
        equals(mngr.panelManager('getStack').length, 3, "home, panel 1 & 2 in stack");
        start();
    });
    $('.back_link').click();
});

test("panel 2 -> panel 1 via link", function(){
    var mngr = $('#panelManager');
    stop();
    mngr.unbind('panelChange');
    mngr.bind('panelChange', function(event, data){
        equals($(data).find('h1').html(), 'Panel 1', 'The appropriate panel loaded.');
        equals(mngr.panelManager('getStack').length, 2, "only home panel and panel 1 in stack");
        start();
    });
    $('#link1').click();
});

test("panel 1 -> panel 3 (hopping over 2)", function(){
    var mngr = $('#panelManager');
    stop();
    mngr.unbind('panelChange');
    mngr.bind('panelChange', function(event, data){
        equals($(data).find('h1').html(), 'Panel 3', 'The appropriate panel loaded.');
        equals(mngr.panelManager('getStack').length, 3, "home, panel 1 & 3 in stack");
        start();
    });
    $('#link3').click();
});

test("panel 3 -> panel 2", function(){
    var mngr = $('#panelManager');
    stop();
    mngr.unbind('panelChange');
    mngr.bind('panelChange', function(event, data){
        equals($(data).find('h1').html(), 'Panel 2', 'The appropriate panel loaded.');
        equals(mngr.panelManager('getStack').length, 3, "home, panel 1 & 2 in stack");
        start();
    });
    $('.back_link').click();
});

test("panel 2 -> panel 3", function(){
    var mngr = $('#panelManager');
    stop();
    mngr.unbind('panelChange');
    mngr.bind('panelChange', function(event, data){
        equals($(data).find('h1').html(), 'Panel 3', 'The appropriate panel loaded.');
        start();
    });
    $('#link3').click();
});<commit_msg>Removing tests for panel_manager since panel_manager no longer exists<commit_after><|endoftext|>