<commit_before>// @ts-check
/* eslint-disable no-param-reassign */
// This file contain any `private` method for the index.js

import is from 'is_js';
import handlerMatcher from './matchers';

export const NAME_PLACEHOLDER = '#{NAME}#';

/**
 * Return error message for checking the parameters of the constructor.
 *
 * @export
 * @param {string} paramName
 * @param {any} value
 * @returns {string}
 */
export function getConstructorErrorMessage(paramName, value) {
  return `[Form Validation - ${paramName}] Expect: non empty object. Actual: ${value}`;
}

/**
 * Check if an object is a non-empty object
 *
 * @param {any} obj
 * @returns {boolean}
 */
function isNonEmptyObject(obj) {
  return is.object(obj) && is.not.empty(obj);
}

/**
 * Type check for 2 parameters of the constructor
 *
 * @export
 * @param {object} component
 * @param {object} schema
 */
export function typeCheck(component, schema) {
  const isComponentValid = isNonEmptyObject(component);
  const isSchemaValid = isNonEmptyObject(schema);

  if (!isComponentValid) {
    throw new Error(
      getConstructorErrorMessage('Parameter component', component)
    );
  }

  if (!isSchemaValid) {
    throw new Error(getConstructorErrorMessage('Parameter schema', schema));
  }

  Object.keys(schema).forEach(prop => {
    if (!isNonEmptyObject(schema[prop])) {
      throw new Error(
        getConstructorErrorMessage(`schema.${prop}`, schema[prop])
      );
    }
  });
}

/**
 * Create initial value for a field if no default is provided.
 *
 * @export
 * @param {object} schema
 * @returns {boolean | string}
 */
export function createInitialValue(schema) {
  if (is.propertyDefined(schema, 'default')) {
    return schema.default;
  }

  if (is.propertyDefined(schema, 'min') || is.propertyDefined(schema, 'max')) {
    return '0';
  }

  return '';
}

/**
 * Create a new state for a field in componentState.formStatus.fields.field
 *
 * @export
 * @param {boolean} [needValue=false]
 * @param {object} [fieldSchema]
 * @returns {object}
 */
export function createNewFieldState(needValue = false, fieldSchema) {
  const result = {
    status: 'normal',
    errorText: ''
  };
  if (needValue) {
    result.value = createInitialValue(fieldSchema);
  }
  return result;
}

export function createInitialState(schema, userState) {
  const initialState = {
    isFormOK: false,
    ...userState
  };

  Object.keys(schema).forEach(prop => {
    initialState[prop] = createNewFieldState(true, schema[prop]);
  });

  const schemaItems = Object.keys(schema);
  schemaItems.forEach(name => {
    if (is.propertyDefined(userState, name)) {
      initialState[name] = {
        ...initialState[name],
        ...userState[name]
      };
    }
  });

  return initialState;
}

/**
 * Check if we should change the state or not.
 *
 * @export
 * @param {object} oldState
 * @param {object} newState
 * @returns {boolean}
 */
export function shouldChange(oldState, newState) {
  const isErrorDifferent = oldState.status !== newState.status;
  const isValueDifferent = oldState.value !== newState.value;
  return isErrorDifferent || isValueDifferent;
}

/**
 * throw an error with defined text, usually calls by handler.
 *
 * @export
 * @param {any} value
 * @param {string} errorText
 * @returns {never}
 */
export function throwError(value, errorText) {
  const result = createNewFieldState();
  result.value = value;
  result.status = 'error';
  result.errorText = errorText;
  throw result;
}

/**
 * Modify the state from { state } to { fieldName: state }
 *
 * @export
 * @param {string} name
 * @param {object} result
 * @returns {object}
 */
export function addNameToResult(name, result) {
  if (result.status === 'error') {
    result.errorText = result.errorText.replace(NAME_PLACEHOLDER, name);
  }
  return { [name]: result };
}

/**
 * It will run through the user's settings for a field,
 * and try matching to the matchers.js,
 * if according handler could be found,
 * it will then execute the according handler function.
 * For instance:
 * if user sets a `minLength` for a field,
 * This function will invoke the minLengthHandler()
 *
 * @param {object} matcher
 * @param {object} fieldState
 * @param {object} schema
 */
function runMatchers(matcher, fieldState, schema) {
  Object.keys(schema).forEach(ruleInSchema => {
    if (is.propertyDefined(matcher, ruleInSchema)) {
      matcher[ruleInSchema](fieldState.value, schema);
    } else if (ruleInSchema !== 'default') {
      console.warn(`No such rule: ${ruleInSchema}`);
    }
  });
  return fieldState;
}

/**
 * This is the main entry for all validator.
 *
 * @export
 * @param {any} value
 * @param {object} schema
 * @returns {object}
 */
export function validatorRunner(value, schema) {
  const fieldState = createNewFieldState();
  fieldState.value = value;
  return runMatchers(handlerMatcher, fieldState, schema);
}

/**
 * If field's status === `error` and its value isn't empty
 * we will set its status to `ok`
 * then return it.
 * It's fine to mutate here, since it's already a new state here.
 *
 * @export
 * @param {object} fieldState
 * @returns {object}
 */
export function checkFieldIsOK(fieldState) {
  if (
    fieldState.status !== 'error' &&
    is.existy(fieldState.value) &&
    is.not.empty(fieldState.value)
  ) {
    fieldState.status = 'ok';
  }
  return fieldState;
}

/**
 * If all fields in the state has their status !== error
 * Then we will set the isFormOK to true then return the state.
 * Just mutate the value since it's already a new state object
 *
 * @export
 * @param {object} componentState
 * @returns {object}}
 */
export function checkIsFormOK(schema, componentState) {
  const properties = Object.keys(schema);
  let isError = false;
  properties.some(prop => {
    if (componentState[prop].status === 'error') {
      isError = true;
      return true;
    }
    return false;
  });
  if (!isError) {
    componentState.isFormOK = true;
  }
  return componentState;
}

export function restoreErrorStatus(fieldState) {
  if (fieldState.status === 'error' && is.empty(fieldState.value)) {
    fieldState.status = 'normal';
    fieldState.errorText = '';
  }
  return fieldState;
}

function updateWhenNeeded(
  newFieldState,
  propName,
  update,
  schema,
  formStatus = ''
) {
  const fieldState = addNameToResult(propName, newFieldState);
  if (formStatus === '') {
    update(fieldState);
    return
  }

  const oldFieldState = formStatus[propName];
  const newFieldState1 = {
    ...oldFieldState,
    ...fieldState[propName]
  };

  if (!shouldChange(oldFieldState, newFieldState)) {
    return;
  }

  const finalState = {
    ...formStatus,
    [propName]: { ...newFieldState1 }
  };
  update(checkIsFormOK(schema, finalState));
}

export function startValidating(target, schema, update, allState) {
  const propName = target.name;
  const fieldInfo = {
    value: target.value,
    schema: schema[propName]
  };

  return Promise.resolve(fieldInfo)
    .then(info => validatorRunner(info.value, info.schema))
    .then(result1 => checkFieldIsOK(result1))
    .then(result2 => restoreErrorStatus(result2))
    .catch(errorState => errorState)
    .then(newFieldState =>
      updateWhenNeeded(newFieldState, propName, update, schema, allState)
    );
}
<commit_msg>Update a comment<commit_after>// @ts-check
/* eslint-disable no-param-reassign */
// This file contain any `private` method for the EasyV

import is from 'is_js';
import handlerMatcher from './matchers';

export const NAME_PLACEHOLDER = '#{NAME}#';

/**
 * Return error message for checking the parameters of the constructor.
 *
 * @export
 * @param {string} paramName
 * @param {any} value
 * @returns {string}
 */
export function getConstructorErrorMessage(paramName, value) {
  return `[Form Validation - ${paramName}] Expect: non empty object. Actual: ${value}`;
}

/**
 * Check if an object is a non-empty object
 *
 * @param {any} obj
 * @returns {boolean}
 */
function isNonEmptyObject(obj) {
  return is.object(obj) && is.not.empty(obj);
}

/**
 * Type check for 2 parameters of the constructor
 *
 * @export
 * @param {object} component
 * @param {object} schema
 */
export function typeCheck(component, schema) {
  const isComponentValid = isNonEmptyObject(component);
  const isSchemaValid = isNonEmptyObject(schema);

  if (!isComponentValid) {
    throw new Error(
      getConstructorErrorMessage('Parameter component', component)
    );
  }

  if (!isSchemaValid) {
    throw new Error(getConstructorErrorMessage('Parameter schema', schema));
  }

  Object.keys(schema).forEach(prop => {
    if (!isNonEmptyObject(schema[prop])) {
      throw new Error(
        getConstructorErrorMessage(`schema.${prop}`, schema[prop])
      );
    }
  });
}

/**
 * Create initial value for a field if no default is provided.
 *
 * @export
 * @param {object} schema
 * @returns {boolean | string}
 */
export function createInitialValue(schema) {
  if (is.propertyDefined(schema, 'default')) {
    return schema.default;
  }

  if (is.propertyDefined(schema, 'min') || is.propertyDefined(schema, 'max')) {
    return '0';
  }

  return '';
}

/**
 * Create a new state for a field in componentState.formStatus.fields.field
 *
 * @export
 * @param {boolean} [needValue=false]
 * @param {object} [fieldSchema]
 * @returns {object}
 */
export function createNewFieldState(needValue = false, fieldSchema) {
  const result = {
    status: 'normal',
    errorText: ''
  };
  if (needValue) {
    result.value = createInitialValue(fieldSchema);
  }
  return result;
}

export function createInitialState(schema, userState) {
  const initialState = {
    isFormOK: false,
    ...userState
  };

  Object.keys(schema).forEach(prop => {
    initialState[prop] = createNewFieldState(true, schema[prop]);
  });

  const schemaItems = Object.keys(schema);
  schemaItems.forEach(name => {
    if (is.propertyDefined(userState, name)) {
      initialState[name] = {
        ...initialState[name],
        ...userState[name]
      };
    }
  });

  return initialState;
}

/**
 * Check if we should change the state or not.
 *
 * @export
 * @param {object} oldState
 * @param {object} newState
 * @returns {boolean}
 */
export function shouldChange(oldState, newState) {
  const isErrorDifferent = oldState.status !== newState.status;
  const isValueDifferent = oldState.value !== newState.value;
  return isErrorDifferent || isValueDifferent;
}

/**
 * throw an error with defined text, usually calls by handler.
 *
 * @export
 * @param {any} value
 * @param {string} errorText
 * @returns {never}
 */
export function throwError(value, errorText) {
  const result = createNewFieldState();
  result.value = value;
  result.status = 'error';
  result.errorText = errorText;
  throw result;
}

/**
 * Modify the state from { state } to { fieldName: state }
 *
 * @export
 * @param {string} name
 * @param {object} result
 * @returns {object}
 */
export function addNameToResult(name, result) {
  if (result.status === 'error') {
    result.errorText = result.errorText.replace(NAME_PLACEHOLDER, name);
  }
  return { [name]: result };
}

/**
 * It will run through the user's settings for a field,
 * and try matching to the matchers.js,
 * if according handler could be found,
 * it will then execute the according handler function.
 * For instance:
 * if user sets a `minLength` for a field,
 * This function will invoke the minLengthHandler()
 *
 * @param {object} matcher
 * @param {object} fieldState
 * @param {object} schema
 */
function runMatchers(matcher, fieldState, schema) {
  Object.keys(schema).forEach(ruleInSchema => {
    if (is.propertyDefined(matcher, ruleInSchema)) {
      matcher[ruleInSchema](fieldState.value, schema);
    } else if (ruleInSchema !== 'default') {
      console.warn(`No such rule: ${ruleInSchema}`);
    }
  });
  return fieldState;
}

/**
 * This is the main entry for all validator.
 *
 * @export
 * @param {any} value
 * @param {object} schema
 * @returns {object}
 */
export function validatorRunner(value, schema) {
  const fieldState = createNewFieldState();
  fieldState.value = value;
  return runMatchers(handlerMatcher, fieldState, schema);
}

/**
 * If field's status === `error` and its value isn't empty
 * we will set its status to `ok`
 * then return it.
 * It's fine to mutate here, since it's already a new state here.
 *
 * @export
 * @param {object} fieldState
 * @returns {object}
 */
export function checkFieldIsOK(fieldState) {
  if (
    fieldState.status !== 'error' &&
    is.existy(fieldState.value) &&
    is.not.empty(fieldState.value)
  ) {
    fieldState.status = 'ok';
  }
  return fieldState;
}

/**
 * If all fields in the state has their status !== error
 * Then we will set the isFormOK to true then return the state.
 * Just mutate the value since it's already a new state object
 *
 * @export
 * @param {object} schema
 * @param {object} componentState
 * @returns {object}}
 */
export function checkIsFormOK(schema, componentState) {
  const properties = Object.keys(schema);
  let isError = false;
  properties.some(prop => {
    if (componentState[prop].status === 'error') {
      isError = true;
      return true;
    }
    return false;
  });
  if (!isError) {
    componentState.isFormOK = true;
  }
  return componentState;
}

export function restoreErrorStatus(fieldState) {
  if (fieldState.status === 'error' && is.empty(fieldState.value)) {
    fieldState.status = 'normal';
    fieldState.errorText = '';
  }
  return fieldState;
}

function updateWhenNeeded(
  newFieldState,
  propName,
  update,
  schema,
  formStatus = ''
) {
  const fieldState = addNameToResult(propName, newFieldState);
  if (formStatus === '') {
    update(fieldState);
    return
  }

  const oldFieldState = formStatus[propName];
  const newFieldState1 = {
    ...oldFieldState,
    ...fieldState[propName]
  };

  if (!shouldChange(oldFieldState, newFieldState)) {
    return;
  }

  const finalState = {
    ...formStatus,
    [propName]: { ...newFieldState1 }
  };
  update(checkIsFormOK(schema, finalState));
}

export function startValidating(target, schema, update, allState) {
  const propName = target.name;
  const fieldInfo = {
    value: target.value,
    schema: schema[propName]
  };

  return Promise.resolve(fieldInfo)
    .then(info => validatorRunner(info.value, info.schema))
    .then(result1 => checkFieldIsOK(result1))
    .then(result2 => restoreErrorStatus(result2))
    .catch(errorState => errorState)
    .then(newFieldState =>
      updateWhenNeeded(newFieldState, propName, update, schema, allState)
    );
}
<|endoftext|>


<commit_before>javascript:(function () {
    const membersWithCallingsUrl = 'https://lcr.churchofjesuschrist.org/services/report/members-with-callings';
    const subOrgNameHeirarchyUrl = 'https://lcr.churchofjesuschrist.org/services/orgs/sub-org-name-hierarchy';
    const imageUrl = 'https://lcr.churchofjesuschrist.org/services/photos/manage-photos/approved-image-individual/';

    const ignoredSubOrgs = [
        'Additional Aaronic Priesthood Quorums Callings',
        'Additional Callings',
        'Church Magazines',
        'Deacons Quorum Additional Callings',
        'For the Strength of Youth',
        'History',
        'Priests Quorum Additional Callings',
        'Presidency of the Aaronic Priesthood',
        'Single Adult',
        'Sunbeam',
        'Teachers Quorum Additional Callings',
        'Young Single Adult',
        'Young Women 12-13 Additional Callings',
        'Young Women 14-15 Additional Callings',
        'Young Women 16-18 Additional Callings',
    ];

    const overrides = {
        'Aaronic Priesthood Quorums': 'Bishop',
        'Auditor': 'Stake President',
        'Bishopric': 'Stake President',
        'Elders Quorum': 'Bishop',
        'Institute Teacher': 'Stake President',
        'Missionary': 'Full-Time Missionaries',
        'Other Callings': 'Bishopric First Counselor',
        'Primary': 'Bishopric Second Counselor',
        'Primary Activities - Boys 7 & 8': 'Primary Second Counselor',
        'Primary Activities - Boys 9 & 10': 'Primary Second Counselor',
        'Primary Activities - Girls 7': 'Primary Second Counselor',
        'Primary Activities - Girls 8': 'Primary Second Counselor',
        'Primary Activities - Girls 9 & 10': 'Primary Second Counselor',
        'Relief Society': 'Bishop',
        'Stake Baptism Coordinator': 'Stake President',
        'Stake Clerk': 'Stake President',
        'Stake High Councilor': 'Stake President',
        'Sunday School': 'Bishopric First Counselor',
        'Technology Specialist': 'Technology',
        'Temple and Family History': 'Elders Quorum President',
        'Ward Missionaries': 'Relief Society President',
        'Young Women': 'Bishop',
    };

    generateCallingsList();

    async function generateCallingsList() {
        let subOrgs = await getJson(subOrgNameHeirarchyUrl);
        let subOrgsAsMembers = getSubOrgsAsMembers(subOrgs, null);
        subOrgsAsMembers = subOrgsAsMembers.filter(subOrg => !ignoredSubOrgs.includes(subOrg.position));
        let members = await getJson(membersWithCallingsUrl);
        processMembers(members, subOrgsAsMembers);
    }

    async function getMemberDataUrl(member) {
        let image = await getJson(imageUrl + member.id);
        if (!image.image.tokenUrl.includes('nophoto.svg')) {
            let dataUrl = await getDataURL(image.image.tokenUrl + '/MEDIUM');
            member.dataUrl = dataUrl;
        }
        return member;
    }

    function getSubOrgsAsMembers(subOrgs, parent) {
        let subOrgsAsMembers = [];
        for (let subOrg of subOrgs) {
            let childrenAsMembers = getSubOrgsAsMembers(subOrg.children, subOrg);
            subOrgsAsMembers = subOrgsAsMembers.concat(childrenAsMembers);
            let subOrgAsMember = {
                id: subOrg.subOrgId,
                organization: parent ? parent.name : subOrg.name,
                parentSubOrgId: parent ? parent.subOrgId : null,
                position: subOrg.name,
                subOrgId: subOrg.subOrgId,
                supervisorId: parent ? parent.subOrgId : null,
            };
            subOrgsAsMembers.push(subOrgAsMember);
        }
        return subOrgsAsMembers;
    }

    function processMembers(members, subOrgsAsMembers) {
        setSupervisors(members);
        setNumberOfMonthsInCalling(members);
        setHasMoreThanOneCalling(members);
        uniquifyIds(members);
        members = members.concat(subOrgsAsMembers);
        applySupervisorOverrides(members);

        let headers = getHeaders(members);

        let csv = sanitize(headers).join(',') + '\n';

        for (let member of members) {
            csv += getRow(member, headers) + '\n';
        }
        saveCSV(csv);
    }

    function setSupervisors(members) {
        for (let member of members) {
            member.supervisorId = member.subOrgId;
        }
    }

    function applySupervisorOverrides(members) {
        for (let member of members) {
            let overridePosition = overrides[member.position];
            if (overridePosition) {
                let supervisor = members.find(m => m.position === overridePosition);
                if (supervisor) {
                    member.supervisorId = supervisor.id;
                }
            }
        }
    }

    function setNumberOfMonthsInCalling(members) {
        let now = new Date();
        for (member of members) {
            member.numberOfMonthsInCalling = monthDiff(new Date(member.sustainedDate), now);
        }
    }

    function monthDiff(d1, d2) {
        var months;
        months = (d2.getFullYear() - d1.getFullYear()) * 12;
        months -= d1.getMonth();
        months += d2.getMonth();
        return months <= 0 ? 0 : months;
    }

    function setHasMoreThanOneCalling(members) {
        for (let i = 0; i < members.length; i++) {
            let ids = members.map(m => m.id);
            let member = members[i];
            let lastIndexOfId = ids.lastIndexOf(member.id);
            if (lastIndexOfId >= 0 && lastIndexOfId !== i) {
                member.hasMoreThanOneCalling = true;
                members[lastIndexOfId].hasMoreThanOneCalling = true;
            } else if (member.hasMoreThanOneCalling === undefined) {
                member.hasMoreThanOneCalling = false;
            }
        }
    }

    function uniquifyIds(members) {
        for (let i = 0; i < members.length; i++) {
            let ids = members.map(m => m.id);
            let member = members[i];
            let lastIndexOfId = ids.lastIndexOf(member.id);
            if (lastIndexOfId >= 0 && lastIndexOfId !== i) {
                member.id = member.id * 10;
            }
        }
    }

    function getHeaders(members) {
        if (members.length === 0) {
            return [];
        }
        return Object.keys(members[0]);
    }

    function getRow(member, headers) {
        return headers.map(header => sanitize(member[header])).join(',');
    }

    function sanitize(value) {
        if (Array.isArray(value)) {
            return value.map(v => {
                if (isString(v)) {
                    return '"' + v + '"';
                }
                return v;
            });
        } else if (isString(value)) {
            return '"' + value + '"';
        }
        return value;
    }

    function isString(value) {
        return typeof value === 'string' || value instanceof String;
    }

    function saveCSV(text) {
        let blob = new Blob([text], {type: 'text/plain'});

        var a = document.createElement('a');
        a.style = 'display: none';
        a.href = window.URL.createObjectURL(blob);
        a.download = 'membersWithCallings.csv';

        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
    }

    function getDataURL(url) {
        return request({ method: 'GET', url: url, responseType: 'blob' }).then(data => {
            return new Promise((resolve, reject) => {
                let reader = new FileReader();
                reader.onloadend = () => {
                    resolve(reader.result);
                };
                reader.readAsDataURL(data);
            });
        });
    }

    function getJson(url) {
        return request({ method: 'GET', url: url }).then(data => {
            return JSON.parse(data);
        });
    }

    function request(obj) {
        return new Promise((resolve, reject) => {
            let xhr = new XMLHttpRequest();
            xhr.open(obj.method || 'GET', obj.url);
            if (obj.headers) {
                Object.keys(obj.headers).forEach(key => {
                    xhr.setRequestHeader(key, obj.headers[key]);
                });
            }
            xhr.onload = () => {
                if (xhr.status >= 200 && xhr.status < 300) {
                    resolve(xhr.response);
                } else {
                    reject(xhr.statusText);
                }
            };
            if (obj.responseType) {
                xhr.responseType = obj.responseType;
            }
            xhr.onerror = () => reject(xhr.statusText);
            xhr.send(obj.body);
        });
    }
}());
<commit_msg>Set number of callings<commit_after>javascript:(function () {
    const membersWithCallingsUrl = 'https://lcr.churchofjesuschrist.org/services/report/members-with-callings';
    const subOrgNameHeirarchyUrl = 'https://lcr.churchofjesuschrist.org/services/orgs/sub-org-name-hierarchy';
    const imageUrl = 'https://lcr.churchofjesuschrist.org/services/photos/manage-photos/approved-image-individual/';

    const ignoredSubOrgs = [
        'Additional Aaronic Priesthood Quorums Callings',
        'Additional Callings',
        'Church Magazines',
        'Deacons Quorum Additional Callings',
        'For the Strength of Youth',
        'History',
        'Priests Quorum Additional Callings',
        'Presidency of the Aaronic Priesthood',
        'Single Adult',
        'Sunbeam',
        'Teachers Quorum Additional Callings',
        'Young Single Adult',
        'Young Women 12-13 Additional Callings',
        'Young Women 14-15 Additional Callings',
        'Young Women 16-18 Additional Callings',
    ];

    const overrides = {
        'Aaronic Priesthood Quorums': 'Bishop',
        'Auditor': 'Stake President',
        'Bishopric': 'Stake President',
        'Elders Quorum': 'Bishop',
        'Institute Teacher': 'Stake President',
        'Missionary': 'Full-Time Missionaries',
        'Other Callings': 'Bishopric First Counselor',
        'Primary': 'Bishopric Second Counselor',
        'Primary Activities - Boys 7 & 8': 'Primary Second Counselor',
        'Primary Activities - Boys 9 & 10': 'Primary Second Counselor',
        'Primary Activities - Girls 7': 'Primary Second Counselor',
        'Primary Activities - Girls 8': 'Primary Second Counselor',
        'Primary Activities - Girls 9 & 10': 'Primary Second Counselor',
        'Relief Society': 'Bishop',
        'Stake Baptism Coordinator': 'Stake President',
        'Stake Clerk': 'Stake President',
        'Stake High Councilor': 'Stake President',
        'Sunday School': 'Bishopric First Counselor',
        'Technology Specialist': 'Technology',
        'Temple and Family History': 'Elders Quorum President',
        'Ward Missionaries': 'Relief Society President',
        'Young Women': 'Bishop',
    };

    generateCallingsList();

    async function generateCallingsList() {
        let subOrgs = await getJson(subOrgNameHeirarchyUrl);
        let subOrgsAsMembers = getSubOrgsAsMembers(subOrgs, null);
        subOrgsAsMembers = subOrgsAsMembers.filter(subOrg => !ignoredSubOrgs.includes(subOrg.position));
        let members = await getJson(membersWithCallingsUrl);
        processMembers(members, subOrgsAsMembers);
    }

    async function getMemberDataUrl(member) {
        let image = await getJson(imageUrl + member.id);
        if (!image.image.tokenUrl.includes('nophoto.svg')) {
            let dataUrl = await getDataURL(image.image.tokenUrl + '/MEDIUM');
            member.dataUrl = dataUrl;
        }
        return member;
    }

    function getSubOrgsAsMembers(subOrgs, parent) {
        let subOrgsAsMembers = [];
        for (let subOrg of subOrgs) {
            let childrenAsMembers = getSubOrgsAsMembers(subOrg.children, subOrg);
            subOrgsAsMembers = subOrgsAsMembers.concat(childrenAsMembers);
            let subOrgAsMember = {
                id: subOrg.subOrgId,
                organization: parent ? parent.name : subOrg.name,
                parentSubOrgId: parent ? parent.subOrgId : null,
                position: subOrg.name,
                subOrgId: subOrg.subOrgId,
                supervisorId: parent ? parent.subOrgId : null,
            };
            subOrgsAsMembers.push(subOrgAsMember);
        }
        return subOrgsAsMembers;
    }

    function processMembers(members, subOrgsAsMembers) {
        setSupervisors(members);
        setNumberOfMonthsInCalling(members);
        setNumberOfCallings(members);
        uniquifyIds(members);
        members = members.concat(subOrgsAsMembers);
        applySupervisorOverrides(members);

        let headers = getHeaders(members);

        let csv = sanitize(headers).join(',') + '\n';

        for (let member of members) {
            csv += getRow(member, headers) + '\n';
        }
        saveCSV(csv);
    }

    function setSupervisors(members) {
        for (let member of members) {
            member.supervisorId = member.subOrgId;
        }
    }

    function applySupervisorOverrides(members) {
        for (let member of members) {
            let overridePosition = overrides[member.position];
            if (overridePosition) {
                let supervisor = members.find(m => m.position === overridePosition);
                if (supervisor) {
                    member.supervisorId = supervisor.id;
                }
            }
        }
    }

    function setNumberOfMonthsInCalling(members) {
        let now = new Date();
        for (member of members) {
            member.numberOfMonthsInCalling = monthDiff(new Date(member.sustainedDate), now);
        }
    }

    function monthDiff(d1, d2) {
        var months;
        months = (d2.getFullYear() - d1.getFullYear()) * 12;
        months -= d1.getMonth();
        months += d2.getMonth();
        return months <= 0 ? 0 : months;
    }

    function setNumberOfCallings(members) {
        for (member of members) {
            let matches = members.filter(m => m.id === member.id);
            member.numberOfCallings = matches.length;
        }
    }

    function uniquifyIds(members) {
        for (let i = 0; i < members.length; i++) {
            let ids = members.map(m => m.id);
            let member = members[i];
            let lastIndexOfId = ids.lastIndexOf(member.id);
            if (lastIndexOfId >= 0 && lastIndexOfId !== i) {
                member.id = member.id * 10;
            }
        }
    }

    function getHeaders(members) {
        if (members.length === 0) {
            return [];
        }
        return Object.keys(members[0]);
    }

    function getRow(member, headers) {
        return headers.map(header => sanitize(member[header])).join(',');
    }

    function sanitize(value) {
        if (Array.isArray(value)) {
            return value.map(v => {
                if (isString(v)) {
                    return '"' + v + '"';
                }
                return v;
            });
        } else if (isString(value)) {
            return '"' + value + '"';
        }
        return value;
    }

    function isString(value) {
        return typeof value === 'string' || value instanceof String;
    }

    function saveCSV(text) {
        let blob = new Blob([text], {type: 'text/plain'});

        var a = document.createElement('a');
        a.style = 'display: none';
        a.href = window.URL.createObjectURL(blob);
        a.download = 'membersWithCallings.csv';

        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
    }

    function getDataURL(url) {
        return request({ method: 'GET', url: url, responseType: 'blob' }).then(data => {
            return new Promise((resolve, reject) => {
                let reader = new FileReader();
                reader.onloadend = () => {
                    resolve(reader.result);
                };
                reader.readAsDataURL(data);
            });
        });
    }

    function getJson(url) {
        return request({ method: 'GET', url: url }).then(data => {
            return JSON.parse(data);
        });
    }

    function request(obj) {
        return new Promise((resolve, reject) => {
            let xhr = new XMLHttpRequest();
            xhr.open(obj.method || 'GET', obj.url);
            if (obj.headers) {
                Object.keys(obj.headers).forEach(key => {
                    xhr.setRequestHeader(key, obj.headers[key]);
                });
            }
            xhr.onload = () => {
                if (xhr.status >= 200 && xhr.status < 300) {
                    resolve(xhr.response);
                } else {
                    reject(xhr.statusText);
                }
            };
            if (obj.responseType) {
                xhr.responseType = obj.responseType;
            }
            xhr.onerror = () => reject(xhr.statusText);
            xhr.send(obj.body);
        });
    }
}());
<|endoftext|>


<commit_before>var globals = require('../globals');

var BINARY = {
  '>':  true,
  '>=': true,
  '=':  true,
  '!=': true,
  '<':  true,
  '<=': true
};

module.exports = function(spec, encoding) {
  var filters = encoding.filter();

  if (!spec.data[0].transform)
    spec.data[0].transform = [];

  // add custom filters
  for (var i in filters) {
    var filter = filters[i];

    var condition = '';
    var operator = filter.operator;
    var operands = filter.operands;

    if (BINARY[operator]) {
      // expects a field and a value
      if (operator === '=') {
        operator = '==';
      }

      var op1 = operands[0];
      var op2 = operands[1];
      condition = 'd.data.' + op1 + operator + op2;
    } else if (operator === 'notNull') {
      // expects a number of fields
      for (var j in operands) {
        condition += '!!d.data.' + operands[j];
        if (j < operands.length - 1) {
          condition += ' && ';
        }
      }
    } else {
      console.warn('Unsupported operator: ', operator);
    }

    spec.data[0].transform.push({
      type: 'filter',
      test: condition
    });
  }

  // remove 0 values if we use log function
  encoding.forEach(function(encType, field) {
    if (encoding.scale(encType).type === 'log') {
      spec.data[0].transform.push({
        type: 'filter',
        test: 'd.data.' + field.name + '>0'
      });
    }
  });
};

<commit_msg>adding `!= "null"` clause just in case the value is a string "null".<commit_after>var globals = require('../globals');

var BINARY = {
  '>':  true,
  '>=': true,
  '=':  true,
  '!=': true,
  '<':  true,
  '<=': true
};

module.exports = function(spec, encoding) {
  var filters = encoding.filter();

  if (!spec.data[0].transform)
    spec.data[0].transform = [];

  // add custom filters
  for (var i in filters) {
    var filter = filters[i];

    var condition = '';
    var operator = filter.operator;
    var operands = filter.operands;

    if (BINARY[operator]) {
      // expects a field and a value
      if (operator === '=') {
        operator = '==';
      }

      var op1 = operands[0];
      var op2 = operands[1];
      condition = 'd.data.' + op1 + operator + op2;
    } else if (operator === 'notNull') {
      // expects a number of fields
      for (var j in operands) {
        condition += '(!!d.data.' + operands[j] + '&& d.data. != "null")';
        if (j < operands.length - 1) {
          condition += ' && ';
        }
      }
    } else {
      console.warn('Unsupported operator: ', operator);
    }

    spec.data[0].transform.push({
      type: 'filter',
      test: condition
    });
  }

  // remove 0 values if we use log function
  encoding.forEach(function(encType, field) {
    if (encoding.scale(encType).type === 'log') {
      spec.data[0].transform.push({
        type: 'filter',
        test: 'd.data.' + field.name + '>0'
      });
    }
  });
};

<|endoftext|>


<commit_before>/**
 * ngStash.js
 * v1.0.0
 * An Angular factory for working with localStorage and sessionStorage
 *
 * Copyright 2015 Derek Peruo
 * http://github.com/dperuo
 * Released under the MIT License
 */

(function(undefined) {
  'use strict';

  angular
    .module('ngStash', [])
    .factory('SessionStash', SessionStashFn)
    .factory('LocalStash', LocalStashFn);

  SessionStashFn.$inject = ['$window'];
  LocalStashFn.$inject = ['$window'];

  function SessionStashFn($window) {
    return {
      set: function(key, value) {
        $window.sessionStorage.setItem(JSON.stringify(key), JSON.stringify(value));
      },

      get: function(key) {
        return JSON.parse($window.sessionStorage.getItem(key));
      },

      remove: function(key) {
        $window.sessionStorage.removeItem(JSON.stringify(key));
      },

      clear: function () {
        $window.sessionStorage.clear();
      }
    };
  }

  function LocalStashFn($window) {
    return {
      set: function(key, value) {
        var stringKey = JSON.stringify(key);
        var stringVal = JSON.stringify(value);

        $window.localStorage.setItem(stringKey, stringVal);
      },

      get: function(key) {
        return JSON.parse($window.localStorage.getItem(key));
      },

      remove: function(key) {
        $window.localStorage.removeItem(JSON.stringify(key));
      },

      clear: function () {
        $window.localStorage.clear();
      }
    };
  }
})();
<commit_msg>add GenericStash()<commit_after>/**
 * ngStash.js
 * v1.0.0
 * An Angular factory for working with localStorage and sessionStorage
 *
 * Copyright 2015 Derek Peruo
 * http://github.com/dperuo
 * Released under the MIT License
 */

(function(undefined) {
  'use strict';

  angular
    .module('ngStash', [])
    .factory('SessionStash', SessionStashFn)
    .factory('LocalStash', LocalStashFn);

  SessionStashFn.$inject = ['$window'];
  LocalStashFn.$inject = ['$window'];

  function SessionStashFn($window) {
    return GenericStash('sessionStorage');
  }

  function LocalStashFn($window) {
    return GenericStash('localStorage');
  }

  function GenericStash(type) {
    return {
      set: function(key, value) {
        $window[type].setItem(JSON.stringify(key), JSON.stringify(value));
      },

      get: function(key) {
        return JSON.parse($window[type].getItem(key));
      },

      remove: function(key) {
        $window[type].removeItem(JSON.stringify(key));
      },

      clear: function () {
        $window[type].clear();
      }
    };
  }
})();
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import { Link } from 'react-router';

class App extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      loggedIn: false
    };
    this.BarLink = this.BarLink.bind(this);
  }

  BarLink(props){
    const classes = "nav-icon glyphicon glyphicon-" + props.glyph;
    return (

        <Link to={props.uri}>
          {props.text}

        </Link>

    );
  }

  render(){
    return (

                <this.BarLink uri="/" text="Home" glyph="home" />
                <this.BarLink uri="/view" text="View Polls" glyph="stats" />
                <this.BarLink uri="/create" text="Create" glyph="plus" />
                <this.BarLink uri="/login" text="Login" glyph="user" />

          {React.cloneElement(this.props.children, {
            loggedIn: this.state.loggedIn,
          })}

    );
  }
}

App.propTypes = {
  children: PropTypes.element,
};

export default App;
<commit_msg>Change login button to profile when already logged in<commit_after>import React, { PropTypes } from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';

class App extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      loggedIn: false
    };
    this.BarLink = this.BarLink.bind(this);
    this.LoginSlashProfile = this.LoginSlashProfile.bind(this);
  }

  BarLink(props){
    const classes = "nav-icon glyphicon glyphicon-" + props.glyph;
    return (

        <Link to={props.uri}>
          {props.text}

        </Link>

    );
  }

  LoginSlashProfile(props){
    if(props.token){
      return(
        <this.BarLink uri="/profile" text="Profile" glyph="user" />
      )
    }else{
      return(
        <this.BarLink uri="/login" text="Login" glyph="user" />
      )
    }
  }

  render(){
    return (

                <this.BarLink uri="/" text="Home" glyph="home" />
                <this.BarLink uri="/view" text="View Polls" glyph="stats" />
                <this.BarLink uri="/create" text="Create" glyph="plus" />
                <this.LoginSlashProfile token={this.props.token}/>

          {React.cloneElement(this.props.children, {
            loggedIn: this.state.loggedIn,
          })}

    );
  }
}
App.propTypes = {
  children: PropTypes.element,
};
const mapStateToProps = (state) => {
  return {
    token: state.login.token
  };
};

export default connect(mapStateToProps)(App);
<|endoftext|>


<commit_before>/**
 * SEO component that queries for data with
 *  Gatsby's useStaticQuery React hook
 *
 * See: https://www.gatsbyjs.org/docs/use-static-query/
 */

import React from 'react';
import PropTypes from 'prop-types';
import Helmet from 'react-helmet';
import { useStaticQuery, graphql } from 'gatsby';

// import logo from '../images/rocketbelt-og.jpg';

function SEO({ pageContext, pageMetadata, meta, title }) {
  const { site } = useStaticQuery(
    graphql`
      query {
        site {
          siteMetadata {
            title
            description
            author
          }
        }
      }
    `
  );

  const img = {
    lg: `rocketbelt-og-wide.png`,
    sm: `rocketbelt-og.jpg`,
  };

  const seo = {
    description:
      (pageContext && pageContext.frontmatter.description) ||
      site.siteMetadata.description,
    lang: 'en',
    title: (pageContext && pageContext.frontmatter.title) || title,
    author:
      (pageMetadata && pageMetadata.lastAuthor) || site.siteMetadata.author,
    url:
      typeof window !== `undefined` && pageMetadata
        ? window.location.origin + (pageMetadata && pageMetadata.slug)
        : `https://rocketbelt.io${pageMetadata && pageMetadata.slug}`,
    imagePath: {
      lg:
        (typeof window !== `undefined`
          ? window.location.origin
          : `https://rocketbelt.io`) + `/images/${img.lg}`,
      sm:
        (typeof window !== `undefined`
          ? window.location.origin
          : `https://rocketbelt.io`) + `/images/${img.sm}`,
    },
  };

  return (
    <Helmet
      htmlAttributes={seo.lang}
      title={seo.title}
      titleTemplate={`%s | ${site.siteMetadata.title}`}
      meta={[
        {
          name: 'viewport',
          content: 'width=device-width, initial-scale=1',
        },
        {
          name: `description`,
          content: seo.description,
        },
        {
          name: `author`,
          content: seo.author,
        },
        { name: `robots`, content: `index, follow` },
        {
          property: `og:title`,
          content: `${seo.title} | ${site.siteMetadata.title}`,
        },
        {
          property: `og:image`,
          content: `${seo.imagePath.lg}`,
        },
        { property: `og:image:type`, content: 'image/png' },
        { property: `og:image:width`, content: `1200` },
        { property: `og:image:height`, content: `630` },
        {
          property: `og:description`,
          content: seo.description,
        },
        {
          property: `og:type`,
          content: `website`,
        },
        { property: `og:url`, content: seo.url },
        { name: 'twitter:image', content: `${seo.imagePath.sm}` },
        {
          name: `twitter:card`,
          content: `summary`,
        },
        {
          name: `twitter:creator`,
          content: seo.author,
        },
        {
          name: `twitter:title`,
          content: `${seo.title} | ${site.siteMetadata.title}`,
        },
        {
          name: `twitter:description`,
          content: seo.description,
        },
      ].concat(meta)}
    >
      {/* <script type="application/ld+json">
        {JSON.stringify(jsonLDMarkdownRaw)}
      </script> */}
    </Helmet>
  );
}

SEO.defaultProps = {
  lang: `en`,
  meta: [],
  description: ``,
};

SEO.propTypes = {
  description: PropTypes.string,
  lang: PropTypes.string,
  meta: PropTypes.arrayOf(PropTypes.object),
  title: PropTypes.string,
};

export default SEO;
<commit_msg>fix(Sample Site): Tweak OG attributes.<commit_after>/**
 * SEO component that queries for data with
 *  Gatsby's useStaticQuery React hook
 *
 * See: https://www.gatsbyjs.org/docs/use-static-query/
 */

import React from 'react';
import PropTypes from 'prop-types';
import Helmet from 'react-helmet';
import { useStaticQuery, graphql } from 'gatsby';

// import logo from '../images/rocketbelt-og.jpg';

function SEO({ pageContext, pageMetadata, meta, title }) {
  const { site } = useStaticQuery(
    graphql`
      query {
        site {
          siteMetadata {
            title
            description
            author
          }
        }
      }
    `
  );

  const img = {
    lg: `rocketbelt-og-wide.png`,
    sm: `rocketbelt-og.jpg`,
  };

  const seo = {
    description:
      (pageContext && pageContext.frontmatter.description) ||
      site.siteMetadata.description,
    htmlAttrs: { lang: 'en' },
    title: (pageContext && pageContext.frontmatter.title) || title,
    author:
      (pageMetadata && pageMetadata.lastAuthor) || site.siteMetadata.author,
    url:
      typeof window !== `undefined` && pageMetadata
        ? window.location.origin + (pageMetadata && pageMetadata.slug)
        : `https://rocketbelt.io${pageMetadata && pageMetadata.slug}`,
    imagePath: {
      lg:
        (typeof window !== `undefined`
          ? window.location.origin
          : `https://rocketbelt.io`) + `/images/${img.lg}`,
      sm:
        (typeof window !== `undefined`
          ? window.location.origin
          : `https://rocketbelt.io`) + `/images/${img.sm}`,
    },
  };

  return (
    <Helmet
      htmlAttributes={seo.htmlAttrs}
      title={seo.title}
      titleTemplate={`%s | ${site.siteMetadata.title}`}
      meta={[
        {
          name: 'viewport',
          content: 'width=device-width, initial-scale=1',
        },
        {
          name: `description`,
          content: seo.description,
        },
        {
          name: `author`,
          content: seo.author,
        },
        { name: `robots`, content: `index, follow` },
        {
          property: `og:title`,
          content: `${seo.title} | ${site.siteMetadata.title}`,
        },
        {
          property: `og:image`,
          content: `${seo.imagePath.lg}`,
        },
        { property: `og:image:type`, content: 'image/png' },
        { property: `og:image:width`, content: `1200` },
        { property: `og:image:height`, content: `630` },
        {
          property: `og:description`,
          content: seo.description,
        },
        {
          property: `og:type`,
          content: `website`,
        },
        { property: `og:url`, content: seo.url },
        { name: 'twitter:image', content: `${seo.imagePath.sm}` },
        {
          name: `twitter:card`,
          content: `summary`,
        },
        {
          name: `twitter:creator`,
          content: seo.author,
        },
        {
          name: `twitter:title`,
          content: `${seo.title} | ${site.siteMetadata.title}`,
        },
        {
          name: `twitter:description`,
          content: seo.description,
        },
      ].concat(meta)}
    ></Helmet>
  );
}

SEO.defaultProps = {
  lang: `en`,
  meta: [],
  description: ``,
};

SEO.propTypes = {
  description: PropTypes.string,
  lang: PropTypes.string,
  meta: PropTypes.arrayOf(PropTypes.object),
  title: PropTypes.string,
};

export default SEO;
<|endoftext|>


<commit_before>const path = require('path')
const webpack = require('webpack')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const SvgStoreWebpackPlugin = require('webpack-svgstore-plugin')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const Visualizer = require('webpack-visualizer-plugin')
const yaml = require('js-yaml')
const AssetsPlugin = require('assets-webpack-plugin')
const WebpackMd5Hash = require('webpack-md5-hash')
const scrolex = require('scrolex').persistOpts({
  announce             : true,
  addCommandAsComponent: true,
  components           : `lanyon>config>webpack`,
})

const postCssLoader = {
  loader : 'postcss-loader',
  options: {
    sourceMap: true,
    ident    : 'postcss',
    plugins  : (loader) => [
      require('autoprefixer')({
        browsers: [
          '> 1%',
          'ie 10',
          'ie 8',
          'safari 4',
        ],
      }),
      // require('cssnano')(),
    ],
  },
}

const postCssLoaderProduction = {
  loader : 'postcss-loader',
  options: {
    sourceMap: true,
    ident    : 'postcss',
    plugins  : (loader) => [
      require('autoprefixer')({
        browsers: [
          '> 1%',
          'ie 10',
          'ie 8',
          'safari 4',
        ],
      }),
      // require('cssnano')(),
    ],
  },
}

module.exports = function ({runtime}) {
  function getFilename (extension, isChunk, isContent) {
    let filename = `[name].${extension}`

    if (!runtime.isDev) {
      filename = `[name].[chunkhash].${extension}`
      if (isContent) {
        filename = `[name].[contenthash].${extension}`
      }
    }

    if (isChunk) {
      filename = `[name].[chunkhash].[id].chunk.${extension}`
    }

    return filename
  }

  let webpackCfg = {
    entry: (function dynamicEntries () {
      var entries = {}

      runtime.entries.forEach(entry => {
        entries[entry] = [path.join(runtime.assetsSourceDir, `${entry}.js`)]

        if (entry === 'app' && runtime.isDev) {
          entries[entry].unshift('webpack-hot-middleware/client')
        }
      })

      if (runtime.common) {
        // e.g.: [ "jquery" ]
        // https://webpack.github.io/docs/code-splitting.html#split-app-and-vendor-code
        entries.common = runtime.common
      }

      return entries
    }()),
    node: {
      fs    : 'empty',
      module: 'empty',
    },
    target: 'web',
    output: {
      publicPath   : runtime.publicPath,
      path         : runtime.assetsBuildDir,
      filename     : getFilename('js'),
      chunkFilename: getFilename('js', true),
      // cssFilename  : getFilename('css'),
    },
    devtool: (function dynamicDevtool () {
      // https://webpack.js.org/configuration/devtool/#devtool
      if (runtime.isDev) {
        return 'inline-eval-cheap-source-map'
      }

      return 'source-map'
    }()),
    bail  : false, // <-- We use our own ReportErrors plugin as with bail errors details are lost. e.g.: `Error at NormalModule.onModuleBuildFailed`
    module: {
      rules: (function dynamicRules () {
        let rules = [
          {
            test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
            use : [
              {
                loader : 'url-loader',
                options: {
                  limit   : 10000,
                  mimetype: 'application/font-woff',
                },
              },
            ],
          }, {
            test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
            use : [
              {
                loader : 'url-loader',
                options: {
                  limit   : 10000,
                  mimetype: 'application/font-woff',
                },
              },
            ],
          }, {
            test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
            use : [
              {
                loader : 'url-loader',
                options: {
                  limit   : 10000,
                  mimetype: 'application/octet-stream',
                },
              },
            ],
          }, {
            test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
            use : [
              {
                loader: 'file-loader',
              },
            ],
          }, {
            test: /\.cur(\?v=\d+\.\d+\.\d+)?$/,
            use : [
              {
                loader: 'file-loader',
              },
            ],
          }, {
            test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
            use : [
              {
                loader : 'url-loader',
                options: {
                  limit   : 10000,
                  mimetype: 'image/svg+xml',
                },
              },
            ],
          },
          {
            test: /\.(png|gif|jpe?g)$/,
            use : [
              {
                loader : 'url-loader',
                options: {
                  limit   : 8096,
                  mimetype: 'application/octet-stream',
                },
              },
            ],
          },
          {
            // https://www.techchorus.net/blog/using-sass-version-of-bootstrap-with-webpack/
            test: /[\\/]bootstrap-sass[\\/]assets[\\/]javascripts[\\/]/,
            use : [
              // loader: 'imports?this=>window',
              {
                loader : 'imports-loader',
                options: {
                  this: '>window',
                },
              },
            ],
          },
          {
            test: /[\\/]jquery\..*\.js$/,
            use : [
              // loader: 'imports?this=>window',
              {
                loader : 'imports-loader',
                options: {
                  this: '>window',
                },
              },
            ],
          },
        ]

        if (runtime.isDev) {
          rules.push({
            test: /\.css$/,
            use : [
              {
                loader: 'style-loader',
              },
              {
                loader : 'css-loader',
                options: {
                  // sourceMap: true,
                  // { importLoaders: 1 }
                },
              },
              postCssLoader,
              {
                loader: 'resolve-url-loader',
              },
            ],
          })
          rules.push({
            test: /\.scss$/,
            use : [
              {
                loader: 'style-loader',
              },
              {
                loader : 'css-loader',
                options: {
                  // sourceMap: true,
                },
              },
              postCssLoader,
              {
                loader: 'resolve-url-loader',
              },
              {
                loader : 'sass-loader',
                options: {
                  // sourceMap: true,
                },
              },
            ],
          })
          rules.push({
            test: /\.less$/,
            use : [
              {
                loader: 'style-loader',
              },
              {
                loader : 'css-loader',
                options: {
                  // sourceMap: true,
                },
              },
              postCssLoader,
              {
                loader: 'resolve-url-loader',
              },
              {
                loader : 'less-loader',
                options: {
                  // sourceMap: true,
                },
              },
            ],
          })
        } else {
          rules.push({
            test: /\.css$/,
            use : ExtractTextPlugin.extract({
              fallback: 'style-loader',
              use     : [
                {
                  loader : 'css-loader',
                  options: {
                    sourceMap    : true,
                    importLoaders: 1,
                  },
                },
                postCssLoaderProduction,
                {
                  loader : 'resolve-url-loader',
                  options: {
                    sourceMap: true,
                  },
                },
              ],
            }),
          })
          rules.push({
            test: /\.scss$/,
            use : ExtractTextPlugin.extract({
              fallback: 'style-loader',
              use     : [
                {
                  loader : 'css-loader',
                  options: {
                    importLoaders: 1,
                    sourceMap    : true,
                  },
                },
                postCssLoaderProduction,
                {
                  loader : 'resolve-url-loader',
                  options: {
                    sourceMap: true,
                  },
                },
                {
                  loader : 'sass-loader',
                  options: {
                    sourceMap: true,
                  },
                },
              ],
            }),
          })
          rules.push({
            test: /\.less$/,
            use : ExtractTextPlugin.extract({
              fallback: 'style-loader',
              use     : [
                {
                  loader : 'css-loader',
                  options: {
                    sourceMap: true,
                  },
                },
                postCssLoaderProduction,
                {
                  loader : 'resolve-url-loader',
                  options: {
                    sourceMap: true,
                  },
                },
                {
                  loader : 'less-loader',
                  options: {
                    sourceMap: true,
                  },
                },
              ],
            }),
          })
        }

        rules.push({
          test   : /\.(js|jsx)$/,
          include: [
            `${runtime.assetsSourceDir}`,
          ],
          exclude: [
            /[\\/](node_modules|js-untouched)[\\/]/,
          ],
          use: [
            // {
            //   loader : 'react-hot-loader',
            //   options: {
            //
            //   },
            // },
            {
              loader : 'babel-loader',
              options: {
                babelrc: true,
                presets: [
                  require.resolve('babel-preset-es2015'),
                  require.resolve('babel-preset-react'),
                  require.resolve('babel-preset-stage-0'),
                ],
                plugins: [
                  require.resolve('babel-plugin-transform-class-properties'),
                ],
                // sourceRoot    : `${runtime.projectDir}`,
                cacheDirectory: `${runtime.cacheDir}/babelCache`,
              },
            },
          ],
        })
        return rules
      }()),
    },
    plugins: (function dynamicPlugins () {
      let plugins = [
        new webpack.ProvidePlugin({
          _: 'lodash',
        }),
        new SvgStoreWebpackPlugin({
          svgoOptions: {
            plugins: [
              { removeTitle: true },
            ],
          },
          prefix: 'icon-',
        }),
        // Until loaders are updated one can use the LoaderOptionsPlugin to switch loaders into debug mode:
        new webpack.LoaderOptionsPlugin({
          debug  : runtime.isDev,
          context: runtime.projectDir,
        }),
        new AssetsPlugin({
          filename: 'jekyll.lanyon_assets.yml',
          path    : runtime.cacheDir,
          processOutput (assets) {
            scrolex.stick(`Writing asset manifest to: "${runtime.cacheDir}/jekyll.lanyon_assets.yml"`)
            try {
              return yaml.safeDump({ lanyon_assets: assets })
            } catch (e) {
              console.error({ assets })
              throw new Error(`Unable to encode above config to YAML. ${e.message}`)
            }
          },
        }),
        new WebpackMd5Hash(),
      ]

      plugins.push(new webpack.optimize.ModuleConcatenationPlugin())

      if (runtime.isDev) {
        plugins.push(new webpack.HotModuleReplacementPlugin())
      } else {
        plugins.push(new ExtractTextPlugin({
          filename : getFilename('css'),
          allChunks: true,
        }))
        // Avoid warning:
        // Warning: It looks like you're using a minified copy of the development build of React.
        // When deploying React apps to production, make sure to use the production build which
        // skips development warnings and is faster. See https://fb.me/react-minification for more details.
        // https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build
        plugins.push(new webpack.DefinePlugin({
          'process.env': {
            NODE_ENV: JSON.stringify('production'),
          },
        }))
        plugins.push(new webpack.optimize.UglifyJsPlugin({
          compress: {
            warnings: false,
          },
          sourceMap: true,
          exclude  : /[\\/](node_modules|js-untouched)[\\/]/,
        }))

        // plugins.push(new webpack.NoErrorsPlugin())
        plugins.push(new OptimizeCssAssetsPlugin())
        plugins.push(new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 15 }))
        plugins.push(new webpack.optimize.MinChunkSizePlugin({ minChunkSize: 10000 }))
        plugins.push(function ReportErrors () {
          this.plugin('done', ({ compilation }) => {
            for (const asset in compilation.assets) {
              scrolex.stick(`Wrote ${runtime.assetsBuildDir}/${asset}`)
            }
            if (compilation.errors && compilation.errors.length) {
              scrolex.failure(compilation.errors)
              if (!runtime.isDev) {
                process.exit(1)
              }
            }
          })
        })
      }

      if (runtime.common) {
        plugins.push(new webpack.optimize.CommonsChunkPlugin({
          name    : 'common',
          filename: getFilename('js'),
        }))
      }

      if (!runtime.isDev && runtime.statistics) {
        // @todo: Once Vizualizer supports multiple entries, add support for that here
        // https://github.com/chrisbateman/webpack-visualizer/issues/5
        // Currently it just shows stats for all entries in one graph
        plugins.push(new Visualizer({
          filename: runtime.statistics,
        }))
      }

      return plugins
    }()),
    resolveLoader: {
      modules: [
        path.join(runtime.lanyonDir, 'node_modules'),
        path.join(runtime.npmRoot, 'node_modules'),
        path.join(runtime.projectDir, 'node_modules'),
      ],
    },
    recordsPath: runtime.recordsPath,
    stats      : {
      // Examine all modules
      maxModules         : Infinity,
      // Display bailout reasons
      optimizationBailout: true,
    },
    resolve: {
      modules: [
        runtime.assetsSourceDir,
        path.join(runtime.projectDir, 'node_modules'),
        path.join(runtime.npmRoot, 'node_modules'),
        path.join(runtime.lanyonDir, 'node_modules'),
      ],

      // These JSON files are read in directories
      descriptionFiles: ['package.json'],

      // These fields in the description files are looked up when trying to resolve the package directory
      mainFields: ['browser', 'main'],

      // These files are tried when trying to resolve a directory
      mainFiles: ['index'],

      // These fields in the description files offer aliasing in this package
      // The content of these fields is an object where requests to a key are mapped to the corresponding value
      aliasFields: ['browser'],

      // These extensions are tried when resolving a file
      extensions: ['.js', '.json'],

      // If false it will also try to use no extension from above
      enforceExtension: false,

      // If false it's also try to use no module extension from above
      enforceModuleExtension: false,
      // These aliasing is used when trying to resolve a module
      alias                 : runtime.alias,
    },
  }

  return webpackCfg
}
<commit_msg>Change sourcemaps in development (#20)<commit_after>const path = require('path')
const webpack = require('webpack')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const SvgStoreWebpackPlugin = require('webpack-svgstore-plugin')
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const Visualizer = require('webpack-visualizer-plugin')
const yaml = require('js-yaml')
const AssetsPlugin = require('assets-webpack-plugin')
const WebpackMd5Hash = require('webpack-md5-hash')
const scrolex = require('scrolex').persistOpts({
  announce             : true,
  addCommandAsComponent: true,
  components           : `lanyon>config>webpack`,
})

const postCssLoader = {
  loader : 'postcss-loader',
  options: {
    sourceMap: true,
    ident    : 'postcss',
    plugins  : (loader) => [
      require('autoprefixer')({
        browsers: [
          '> 1%',
          'ie 10',
          'ie 8',
          'safari 4',
        ],
      }),
      // require('cssnano')(),
    ],
  },
}

const postCssLoaderProduction = {
  loader : 'postcss-loader',
  options: {
    sourceMap: true,
    ident    : 'postcss',
    plugins  : (loader) => [
      require('autoprefixer')({
        browsers: [
          '> 1%',
          'ie 10',
          'ie 8',
          'safari 4',
        ],
      }),
      // require('cssnano')(),
    ],
  },
}

module.exports = function ({runtime}) {
  function getFilename (extension, isChunk, isContent) {
    let filename = `[name].${extension}`

    if (!runtime.isDev) {
      filename = `[name].[chunkhash].${extension}`
      if (isContent) {
        filename = `[name].[contenthash].${extension}`
      }
    }

    if (isChunk) {
      filename = `[name].[chunkhash].[id].chunk.${extension}`
    }

    return filename
  }

  let webpackCfg = {
    entry: (function dynamicEntries () {
      var entries = {}

      runtime.entries.forEach(entry => {
        entries[entry] = [path.join(runtime.assetsSourceDir, `${entry}.js`)]

        if (entry === 'app' && runtime.isDev) {
          entries[entry].unshift('webpack-hot-middleware/client')
        }
      })

      if (runtime.common) {
        // e.g.: [ "jquery" ]
        // https://webpack.github.io/docs/code-splitting.html#split-app-and-vendor-code
        entries.common = runtime.common
      }

      return entries
    }()),
    node: {
      fs    : 'empty',
      module: 'empty',
    },
    target: 'web',
    output: {
      publicPath   : runtime.publicPath,
      path         : runtime.assetsBuildDir,
      filename     : getFilename('js'),
      chunkFilename: getFilename('js', true),
      // cssFilename  : getFilename('css'),
    },
    devtool: (function dynamicDevtool () {
      // https://webpack.js.org/configuration/devtool/#devtool
      if (runtime.isDev) {
        return 'eval-source-map'
      }

      return 'source-map'
    }()),
    bail  : false, // <-- We use our own ReportErrors plugin as with bail errors details are lost. e.g.: `Error at NormalModule.onModuleBuildFailed`
    module: {
      rules: (function dynamicRules () {
        let rules = [
          {
            test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
            use : [
              {
                loader : 'url-loader',
                options: {
                  limit   : 10000,
                  mimetype: 'application/font-woff',
                },
              },
            ],
          }, {
            test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
            use : [
              {
                loader : 'url-loader',
                options: {
                  limit   : 10000,
                  mimetype: 'application/font-woff',
                },
              },
            ],
          }, {
            test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
            use : [
              {
                loader : 'url-loader',
                options: {
                  limit   : 10000,
                  mimetype: 'application/octet-stream',
                },
              },
            ],
          }, {
            test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
            use : [
              {
                loader: 'file-loader',
              },
            ],
          }, {
            test: /\.cur(\?v=\d+\.\d+\.\d+)?$/,
            use : [
              {
                loader: 'file-loader',
              },
            ],
          }, {
            test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
            use : [
              {
                loader : 'url-loader',
                options: {
                  limit   : 10000,
                  mimetype: 'image/svg+xml',
                },
              },
            ],
          },
          {
            test: /\.(png|gif|jpe?g)$/,
            use : [
              {
                loader : 'url-loader',
                options: {
                  limit   : 8096,
                  mimetype: 'application/octet-stream',
                },
              },
            ],
          },
          {
            // https://www.techchorus.net/blog/using-sass-version-of-bootstrap-with-webpack/
            test: /[\\/]bootstrap-sass[\\/]assets[\\/]javascripts[\\/]/,
            use : [
              // loader: 'imports?this=>window',
              {
                loader : 'imports-loader',
                options: {
                  this: '>window',
                },
              },
            ],
          },
          {
            test: /[\\/]jquery\..*\.js$/,
            use : [
              // loader: 'imports?this=>window',
              {
                loader : 'imports-loader',
                options: {
                  this: '>window',
                },
              },
            ],
          },
        ]

        if (runtime.isDev) {
          rules.push({
            test: /\.css$/,
            use : [
              {
                loader: 'style-loader',
              },
              {
                loader : 'css-loader',
                options: {
                  // sourceMap: true,
                  // { importLoaders: 1 }
                },
              },
              postCssLoader,
              {
                loader: 'resolve-url-loader',
              },
            ],
          })
          rules.push({
            test: /\.scss$/,
            use : [
              {
                loader: 'style-loader',
              },
              {
                loader : 'css-loader',
                options: {
                  // sourceMap: true,
                },
              },
              postCssLoader,
              {
                loader: 'resolve-url-loader',
              },
              {
                loader : 'sass-loader',
                options: {
                  // sourceMap: true,
                },
              },
            ],
          })
          rules.push({
            test: /\.less$/,
            use : [
              {
                loader: 'style-loader',
              },
              {
                loader : 'css-loader',
                options: {
                  // sourceMap: true,
                },
              },
              postCssLoader,
              {
                loader: 'resolve-url-loader',
              },
              {
                loader : 'less-loader',
                options: {
                  // sourceMap: true,
                },
              },
            ],
          })
        } else {
          rules.push({
            test: /\.css$/,
            use : ExtractTextPlugin.extract({
              fallback: 'style-loader',
              use     : [
                {
                  loader : 'css-loader',
                  options: {
                    sourceMap    : true,
                    importLoaders: 1,
                  },
                },
                postCssLoaderProduction,
                {
                  loader : 'resolve-url-loader',
                  options: {
                    sourceMap: true,
                  },
                },
              ],
            }),
          })
          rules.push({
            test: /\.scss$/,
            use : ExtractTextPlugin.extract({
              fallback: 'style-loader',
              use     : [
                {
                  loader : 'css-loader',
                  options: {
                    importLoaders: 1,
                    sourceMap    : true,
                  },
                },
                postCssLoaderProduction,
                {
                  loader : 'resolve-url-loader',
                  options: {
                    sourceMap: true,
                  },
                },
                {
                  loader : 'sass-loader',
                  options: {
                    sourceMap: true,
                  },
                },
              ],
            }),
          })
          rules.push({
            test: /\.less$/,
            use : ExtractTextPlugin.extract({
              fallback: 'style-loader',
              use     : [
                {
                  loader : 'css-loader',
                  options: {
                    sourceMap: true,
                  },
                },
                postCssLoaderProduction,
                {
                  loader : 'resolve-url-loader',
                  options: {
                    sourceMap: true,
                  },
                },
                {
                  loader : 'less-loader',
                  options: {
                    sourceMap: true,
                  },
                },
              ],
            }),
          })
        }

        rules.push({
          test   : /\.(js|jsx)$/,
          include: [
            `${runtime.assetsSourceDir}`,
          ],
          exclude: [
            /[\\/](node_modules|js-untouched)[\\/]/,
          ],
          use: [
            // {
            //   loader : 'react-hot-loader',
            //   options: {
            //
            //   },
            // },
            {
              loader : 'babel-loader',
              options: {
                babelrc: true,
                presets: [
                  require.resolve('babel-preset-es2015'),
                  require.resolve('babel-preset-react'),
                  require.resolve('babel-preset-stage-0'),
                ],
                plugins: [
                  require.resolve('babel-plugin-transform-class-properties'),
                ],
                // sourceRoot    : `${runtime.projectDir}`,
                cacheDirectory: `${runtime.cacheDir}/babelCache`,
              },
            },
          ],
        })
        return rules
      }()),
    },
    plugins: (function dynamicPlugins () {
      let plugins = [
        new webpack.ProvidePlugin({
          _: 'lodash',
        }),
        new SvgStoreWebpackPlugin({
          svgoOptions: {
            plugins: [
              { removeTitle: true },
            ],
          },
          prefix: 'icon-',
        }),
        // Until loaders are updated one can use the LoaderOptionsPlugin to switch loaders into debug mode:
        new webpack.LoaderOptionsPlugin({
          debug  : runtime.isDev,
          context: runtime.projectDir,
        }),
        new AssetsPlugin({
          filename: 'jekyll.lanyon_assets.yml',
          path    : runtime.cacheDir,
          processOutput (assets) {
            scrolex.stick(`Writing asset manifest to: "${runtime.cacheDir}/jekyll.lanyon_assets.yml"`)
            try {
              return yaml.safeDump({ lanyon_assets: assets })
            } catch (e) {
              console.error({ assets })
              throw new Error(`Unable to encode above config to YAML. ${e.message}`)
            }
          },
        }),
        new WebpackMd5Hash(),
      ]

      plugins.push(new webpack.optimize.ModuleConcatenationPlugin())

      if (runtime.isDev) {
        plugins.push(new webpack.HotModuleReplacementPlugin())
      } else {
        plugins.push(new ExtractTextPlugin({
          filename : getFilename('css'),
          allChunks: true,
        }))
        // Avoid warning:
        // Warning: It looks like you're using a minified copy of the development build of React.
        // When deploying React apps to production, make sure to use the production build which
        // skips development warnings and is faster. See https://fb.me/react-minification for more details.
        // https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build
        plugins.push(new webpack.DefinePlugin({
          'process.env': {
            NODE_ENV: JSON.stringify('production'),
          },
        }))
        plugins.push(new webpack.optimize.UglifyJsPlugin({
          compress: {
            warnings: false,
          },
          sourceMap: true,
          exclude  : /[\\/](node_modules|js-untouched)[\\/]/,
        }))

        // plugins.push(new webpack.NoErrorsPlugin())
        plugins.push(new OptimizeCssAssetsPlugin())
        plugins.push(new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 15 }))
        plugins.push(new webpack.optimize.MinChunkSizePlugin({ minChunkSize: 10000 }))
        plugins.push(function ReportErrors () {
          this.plugin('done', ({ compilation }) => {
            for (const asset in compilation.assets) {
              scrolex.stick(`Wrote ${runtime.assetsBuildDir}/${asset}`)
            }
            if (compilation.errors && compilation.errors.length) {
              scrolex.failure(compilation.errors)
              if (!runtime.isDev) {
                process.exit(1)
              }
            }
          })
        })
      }

      if (runtime.common) {
        plugins.push(new webpack.optimize.CommonsChunkPlugin({
          name    : 'common',
          filename: getFilename('js'),
        }))
      }

      if (!runtime.isDev && runtime.statistics) {
        // @todo: Once Vizualizer supports multiple entries, add support for that here
        // https://github.com/chrisbateman/webpack-visualizer/issues/5
        // Currently it just shows stats for all entries in one graph
        plugins.push(new Visualizer({
          filename: runtime.statistics,
        }))
      }

      return plugins
    }()),
    resolveLoader: {
      modules: [
        path.join(runtime.lanyonDir, 'node_modules'),
        path.join(runtime.npmRoot, 'node_modules'),
        path.join(runtime.projectDir, 'node_modules'),
      ],
    },
    recordsPath: runtime.recordsPath,
    stats      : {
      // Examine all modules
      maxModules         : Infinity,
      // Display bailout reasons
      optimizationBailout: true,
    },
    resolve: {
      modules: [
        runtime.assetsSourceDir,
        path.join(runtime.projectDir, 'node_modules'),
        path.join(runtime.npmRoot, 'node_modules'),
        path.join(runtime.lanyonDir, 'node_modules'),
      ],

      // These JSON files are read in directories
      descriptionFiles: ['package.json'],

      // These fields in the description files are looked up when trying to resolve the package directory
      mainFields: ['browser', 'main'],

      // These files are tried when trying to resolve a directory
      mainFiles: ['index'],

      // These fields in the description files offer aliasing in this package
      // The content of these fields is an object where requests to a key are mapped to the corresponding value
      aliasFields: ['browser'],

      // These extensions are tried when resolving a file
      extensions: ['.js', '.json'],

      // If false it will also try to use no extension from above
      enforceExtension: false,

      // If false it's also try to use no module extension from above
      enforceModuleExtension: false,
      // These aliasing is used when trying to resolve a module
      alias                 : runtime.alias,
    },
  }

  return webpackCfg
}
<|endoftext|>


<commit_before>import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

let bindModel = [
    {
        id: 1,
        username: 'YippieKiYay',
        emailAddress: 'john.mcclane@domain.com',
        firstName: 'John',
        lastName: 'McClane',
        isAdmin: true,
        createdAt: '2017-01-01',
        updatedAt: '2017-01-02'
    },
    {
        id: 2,
        username: 'HanSolo',
        emailAddress: 'smugglerOne@domain.com',
        firstName: 'Han',
        lastName: 'Solo',
        isAdmin: false,
        createdAt: '2015-12-18',
        updatedAt: '2015-12-19'
    }
];
let columns = [
    {
        property: 'username',
        label: 'Username',
        type: 'text',
        defaultSort: 'username',
    },
    {
        property: 'email-address',
        label: 'Email',
        type: 'text',
    },
    {
        property: 'first-name',
        label: 'First Name',
        type: 'text',
    },
    {
        property: 'last-name',
        label: 'Last Name',
        type: 'text',
    },
    {
        property: 'updated-at',
        label: 'Last Updated',
        type: 'date',
    },
];
let columnsLabels = [
    {
        label: 'Username',
    },
    {
        label: 'Email',
    },
    {
        label: 'First Name',
    },
    {
        label: 'Last Name',
    },
    {
        label: 'Last Updated',
    },
];

moduleForComponent('ember-table-jsonapi', 'Integration | Component | ember table jsonapi', {
    integration: true,
});

test('Render header yield', function(assert) {
    this.set('columns', columns);
    this.render(hbs`
        {{#ember-table-jsonapi columns=columns bindModel=bindModel hasActions="true" as |section|}}
            {{#if section.isHeader}}

                    Test Header Yield

            {{/if}}
        {{/ember-table-jsonapi}}
    `);

    var $component = this.$();
    assert.equal($component.find('.header').text().trim(), 'Test Header Yield');
});

test('Render body yield', function(assert) {
    this.set('columns', columns);
    this.render(hbs`
        {{#ember-table-jsonapi columns=columns bindModel=bindModel hasActions="true" as |section|}}
            {{#if section.isBody}}

                    Test Body Yield

            {{/if}}
        {{/ember-table-jsonapi}}
    `);
    // Set bindModel after render b/c of component this.reset()
    this.set('bindModel', bindModel);

    var $component = this.$();
    assert.equal($component.find('.body').text().trim(), 'Test Body Yield');
});

test('Render footer yield', function(assert) {
    this.set('columns', columns);
    this.render(hbs`
        {{#ember-table-jsonapi columns=columns bindModel=bindModel hasActions="true" as |section|}}
            {{#if section.isFooter}}

                    Test Footer Yield

            {{/if}}
        {{/ember-table-jsonapi}}
    `);

    var $component = this.$();
    assert.equal($component.find('.footer').text().trim(), 'Test Footer Yield');
});

test('Render filter component', function(assert) {
    this.set('columns', columns);
    this.render(hbs`
        {{#ember-table-jsonapi columns=columns bindModel=bindModel hasActions="true" as |section|}}
            {{#if section.isFooter}}
                ...
            {{/if}}
        {{/ember-table-jsonapi}}
    `);

    var $component = this.$();
    assert.equal($component.find('thead tr:eq(1) th:eq(0) input').length, 1, 'Table Filter Input - Username');
    assert.equal($component.find('thead tr:eq(1) th:eq(1) input').length, 1, 'Table Filter Input - Email');
    assert.equal($component.find('thead tr:eq(1) th:eq(2) input').length, 1, 'Table Filter Input - First Name');
    assert.equal($component.find('thead tr:eq(1) th:eq(3) input').length, 1, 'Table Filter Input - Last Name');
    assert.equal($component.find('thead tr:eq(1) th:eq(4) input').length, 1, 'Table Filter Input - Last Updated');
    assert.equal($component.find('thead tr:eq(1) th:eq(5) input').length, 0, 'Table Filter No Input - Actions');
});

test('Do not render filter component', function(assert) {
    this.set('columnsLabels', columnsLabels);
    this.render(hbs`
        {{#ember-table-jsonapi columns=columnsLabels bindModel=bindModel hasActions="true" as |section|}}
            {{#if section.isFooter}}
                ...
            {{/if}}
        {{/ember-table-jsonapi}}
    `);

    var $component = this.$();
    assert.equal($component.find('thead tr').length, 1, 'Do not render filter row');
});

test('Render global filter component', function(assert) {
    this.set('columns', columns);
    this.render(hbs`
        {{#ember-table-jsonapi columns=columns bindModel=bindModel searchable="true" hasActions="true" as |section|}}
            {{#if section.isFooter}}
                ...
            {{/if}}
        {{/ember-table-jsonapi}}
    `);

    var $component = this.$();
    assert.equal($component.find('.table-filter').length, 1, 'Test global filter');
});
<commit_msg>Update test to check render of global table sub component<commit_after>import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

let bindModel = [
    {
        id: 1,
        username: 'YippieKiYay',
        emailAddress: 'john.mcclane@domain.com',
        firstName: 'John',
        lastName: 'McClane',
        isAdmin: true,
        createdAt: '2017-01-01',
        updatedAt: '2017-01-02'
    },
    {
        id: 2,
        username: 'HanSolo',
        emailAddress: 'smugglerOne@domain.com',
        firstName: 'Han',
        lastName: 'Solo',
        isAdmin: false,
        createdAt: '2015-12-18',
        updatedAt: '2015-12-19'
    }
];
let columns = [
    {
        property: 'username',
        label: 'Username',
        type: 'text',
        defaultSort: 'username',
    },
    {
        property: 'email-address',
        label: 'Email',
        type: 'text',
    },
    {
        property: 'first-name',
        label: 'First Name',
        type: 'text',
    },
    {
        property: 'last-name',
        label: 'Last Name',
        type: 'text',
    },
    {
        property: 'updated-at',
        label: 'Last Updated',
        type: 'date',
    },
];
let columnsLabels = [
    {
        label: 'Username',
    },
    {
        label: 'Email',
    },
    {
        label: 'First Name',
    },
    {
        label: 'Last Name',
    },
    {
        label: 'Last Updated',
    },
];

moduleForComponent('ember-table-jsonapi', 'Integration | Component | ember table jsonapi', {
    integration: true,
});

test('Render header yield', function(assert) {
    this.set('columns', columns);
    this.render(hbs`
        {{#ember-table-jsonapi columns=columns bindModel=bindModel hasActions="true" as |section|}}
            {{#if section.isHeader}}

                    Test Header Yield

            {{/if}}
        {{/ember-table-jsonapi}}
    `);

    var $component = this.$();
    assert.equal($component.find('.header').text().trim(), 'Test Header Yield');
});

test('Render body yield', function(assert) {
    this.set('columns', columns);
    this.render(hbs`
        {{#ember-table-jsonapi columns=columns bindModel=bindModel hasActions="true" as |section|}}
            {{#if section.isBody}}

                    Test Body Yield

            {{/if}}
        {{/ember-table-jsonapi}}
    `);
    // Set bindModel after render b/c of component this.reset()
    this.set('bindModel', bindModel);

    var $component = this.$();
    assert.equal($component.find('.body').text().trim(), 'Test Body Yield');
});

test('Render footer yield', function(assert) {
    this.set('columns', columns);
    this.render(hbs`
        {{#ember-table-jsonapi columns=columns bindModel=bindModel hasActions="true" as |section|}}
            {{#if section.isFooter}}

                    Test Footer Yield

            {{/if}}
        {{/ember-table-jsonapi}}
    `);

    var $component = this.$();
    assert.equal($component.find('.footer').text().trim(), 'Test Footer Yield');
});

test('Render filter component', function(assert) {
    this.set('columns', columns);
    this.render(hbs`
        {{#ember-table-jsonapi columns=columns bindModel=bindModel hasActions="true" as |section|}}
            {{#if section.isFooter}}
                ...
            {{/if}}
        {{/ember-table-jsonapi}}
    `);

    var $component = this.$();
    assert.equal($component.find('thead tr:eq(1) th:eq(0) input').length, 1, 'Table Filter Input - Username');
    assert.equal($component.find('thead tr:eq(1) th:eq(1) input').length, 1, 'Table Filter Input - Email');
    assert.equal($component.find('thead tr:eq(1) th:eq(2) input').length, 1, 'Table Filter Input - First Name');
    assert.equal($component.find('thead tr:eq(1) th:eq(3) input').length, 1, 'Table Filter Input - Last Name');
    assert.equal($component.find('thead tr:eq(1) th:eq(4) input').length, 1, 'Table Filter Input - Last Updated');
    assert.equal($component.find('thead tr:eq(1) th:eq(5) input').length, 0, 'Table Filter No Input - Actions');
});

test('Do not render filter component', function(assert) {
    this.set('columnsLabels', columnsLabels);
    this.render(hbs`
        {{#ember-table-jsonapi columns=columnsLabels bindModel=bindModel hasActions="true" as |section|}}
            {{#if section.isFooter}}
                ...
            {{/if}}
        {{/ember-table-jsonapi}}
    `);

    var $component = this.$();
    assert.equal($component.find('thead tr').length, 1, 'Do not render filter row');
});

test('Render global filter component', function(assert) {
    this.set('columns', columns);
    this.render(hbs`
        {{#ember-table-jsonapi columns=columns bindModel=bindModel hasActions="true" filter=filter as |section|}}
            {{#if section.isHeader}}
                {{ember-table-jsonapi-global-filter filter=filter filterProperty="username" filterPlaceholder="Search by Username"}}
            {{/if}}
        {{/ember-table-jsonapi}}
    `);

    var $component = this.$();
    assert.equal($component.find('.table-filter').length, 1, 'Test global filter');
});
<|endoftext|>


<commit_before>beforeAll(() => {
  window.CBOR = require('../../node_modules/cbor-js/cbor.js')
  require('../../app/assets/javascripts/registerSecurityKey.js')

  // disable console.error() so we don't see it in test output
  // you might need to comment this out to debug some failures
  jest.spyOn(console, 'error').mockImplementation(() => {})

  // populate missing values to allow consistent jest.spyOn()
  window.fetch = () => {}
  window.navigator.credentials = { create: () => {} }
})

afterAll(() => {
  require('./support/teardown.js')

  // restore window attributes to their original undefined state
  delete window.fetch
  delete window.navigator.credentials
})

describe('Register security key', () => {
  let button

  beforeEach(() => {
    document.body.innerHTML = `

        Register a key
      `

    button = document.querySelector('[data-module="register-security-key"]')
    window.GOVUK.modules.start()
  })

  test('creates a new credential and reloads', (done) => {
    jest.spyOn(window.navigator.credentials, 'create').mockImplementation((options) => {
      expect(options).toEqual('options')

      // fake PublicKeyCredential response from WebAuthn API
      // both of the nested properties are Array(Buffer) objects
      return Promise.resolve({
        response: {
          attestationObject: [1, 2, 3],
          clientDataJSON: [4, 5, 6],
        }
      })
    })

    // pretend window.location exists in test env
    Object.defineProperty(window, 'location', {
      // signal that the async promise chain was called
      value: { reload: () => done() },
      // allow global property to be redefined in other tests
      writable: true,
    })

    jest.spyOn(window, 'fetch').mockImplementation((_url, options = {}) => {
      // initial fetch of options from the server
      if (!options.method) {
        // options from the server are CBOR-encoded
        webauthnOptions = window.CBOR.encode('options')

        return Promise.resolve({
          ok: true, arrayBuffer: () => webauthnOptions
        })

      // subsequent POST of credential data to server
      } else {
        decodedData = window.CBOR.decode(options.body)
        expect(decodedData.clientDataJSON).toEqual(new Uint8Array([4,5,6]))
        expect(decodedData.attestationObject).toEqual(new Uint8Array([1,2,3]))
        expect(options.headers['X-CSRFToken']).toBe()
        return Promise.resolve({ ok: true })
      }
    })

    button.click()
  })

  test.each([
    ['network'],
    ['server'],
  ])('alerts if fetching WebAuthn options fails (%s error)', (errorType, done) => {
    jest.spyOn(window, 'fetch').mockImplementation((_url, options = {}) => {
      if (errorType == 'network') {
        return Promise.reject('error')
      } else {
        return Promise.resolve({ ok: false, statusText: 'error' })
      }
    })

    jest.spyOn(window, 'alert').mockImplementation((msg) => {
      expect(msg).toEqual('Error during registration.\n\nerror')
      done()
    })

    button.click()
  })

  test.each([
    ['network'],
    ['server'],
  ])('alerts if sending WebAuthn credentials fails (%s error)', ({errorType}, done) => {
    jest.spyOn(window.navigator.credentials, 'create').mockImplementation(() => {
      // fake PublicKeyCredential response from WebAuthn API
      return Promise.resolve({ response: {} })
    })

    jest.spyOn(window, 'fetch').mockImplementation((_url, options = {}) => {
      // initial fetch of options from the server
      if (!options.method) {
        webauthnOptions = window.CBOR.encode('options')

        return Promise.resolve({
          ok: true, arrayBuffer: () => webauthnOptions
        })

      // subsequent POST of credential data to server
      } else {
        if (errorType == 'network') {
          return Promise.reject('error')
        } else {
          return Promise.resolve({ ok: false, statusText: 'error' })
        }
      }
    })

    jest.spyOn(window, 'alert').mockImplementation((msg) => {
      expect(msg).toEqual('Error during registration.\n\nerror')
      done()
    })

    button.click()
  })

  test('alerts if comms with the authenticator fails', (done) => {
    jest.spyOn(window.navigator.credentials, 'create').mockImplementation(() => {
      return Promise.reject(new DOMException('error'))
    })

    jest.spyOn(window, 'fetch').mockImplementation((_url, options) => {
      // initial fetch of options from the server
      webauthnOptions = window.CBOR.encode('options')

      return Promise.resolve({
        ok: true, arrayBuffer: () => webauthnOptions
      })
    })

    jest.spyOn(window, 'alert').mockImplementation((msg) => {
      expect(msg).toEqual('Error during registration.\n\nerror')
      done()
    })

    button.click()
  })
})
<commit_msg>Switch to jest.spyOn() for window.location<commit_after>beforeAll(() => {
  window.CBOR = require('../../node_modules/cbor-js/cbor.js')
  require('../../app/assets/javascripts/registerSecurityKey.js')

  // disable console.error() so we don't see it in test output
  // you might need to comment this out to debug some failures
  jest.spyOn(console, 'error').mockImplementation(() => {})

  // populate missing values to allow consistent jest.spyOn()
  window.fetch = () => {}
  window.navigator.credentials = { create: () => {} }
})

afterAll(() => {
  require('./support/teardown.js')

  // restore window attributes to their original undefined state
  delete window.fetch
  delete window.navigator.credentials
})

describe('Register security key', () => {
  let button

  beforeEach(() => {
    document.body.innerHTML = `

        Register a key
      `

    button = document.querySelector('[data-module="register-security-key"]')
    window.GOVUK.modules.start()
  })

  test('creates a new credential and reloads', (done) => {
    jest.spyOn(window.navigator.credentials, 'create').mockImplementation((options) => {
      expect(options).toEqual('options')

      // fake PublicKeyCredential response from WebAuthn API
      // both of the nested properties are Array(Buffer) objects
      return Promise.resolve({
        response: {
          attestationObject: [1, 2, 3],
          clientDataJSON: [4, 5, 6],
        }
      })
    })

    jest.spyOn(window.location, 'reload').mockImplementation(() => {
      // signal that the async promise chain was called
      done()
    })

    jest.spyOn(window, 'fetch').mockImplementation((_url, options = {}) => {
      // initial fetch of options from the server
      if (!options.method) {
        // options from the server are CBOR-encoded
        webauthnOptions = window.CBOR.encode('options')

        return Promise.resolve({
          ok: true, arrayBuffer: () => webauthnOptions
        })

      // subsequent POST of credential data to server
      } else {
        decodedData = window.CBOR.decode(options.body)
        expect(decodedData.clientDataJSON).toEqual(new Uint8Array([4,5,6]))
        expect(decodedData.attestationObject).toEqual(new Uint8Array([1,2,3]))
        expect(options.headers['X-CSRFToken']).toBe()
        return Promise.resolve({ ok: true })
      }
    })

    button.click()
  })

  test.each([
    ['network'],
    ['server'],
  ])('alerts if fetching WebAuthn options fails (%s error)', (errorType, done) => {
    jest.spyOn(window, 'fetch').mockImplementation((_url, options = {}) => {
      if (errorType == 'network') {
        return Promise.reject('error')
      } else {
        return Promise.resolve({ ok: false, statusText: 'error' })
      }
    })

    jest.spyOn(window, 'alert').mockImplementation((msg) => {
      expect(msg).toEqual('Error during registration.\n\nerror')
      done()
    })

    button.click()
  })

  test.each([
    ['network'],
    ['server'],
  ])('alerts if sending WebAuthn credentials fails (%s error)', ({errorType}, done) => {
    jest.spyOn(window.navigator.credentials, 'create').mockImplementation(() => {
      // fake PublicKeyCredential response from WebAuthn API
      return Promise.resolve({ response: {} })
    })

    jest.spyOn(window, 'fetch').mockImplementation((_url, options = {}) => {
      // initial fetch of options from the server
      if (!options.method) {
        webauthnOptions = window.CBOR.encode('options')

        return Promise.resolve({
          ok: true, arrayBuffer: () => webauthnOptions
        })

      // subsequent POST of credential data to server
      } else {
        if (errorType == 'network') {
          return Promise.reject('error')
        } else {
          return Promise.resolve({ ok: false, statusText: 'error' })
        }
      }
    })

    jest.spyOn(window, 'alert').mockImplementation((msg) => {
      expect(msg).toEqual('Error during registration.\n\nerror')
      done()
    })

    button.click()
  })

  test('alerts if comms with the authenticator fails', (done) => {
    jest.spyOn(window.navigator.credentials, 'create').mockImplementation(() => {
      return Promise.reject(new DOMException('error'))
    })

    jest.spyOn(window, 'fetch').mockImplementation((_url, options) => {
      // initial fetch of options from the server
      webauthnOptions = window.CBOR.encode('options')

      return Promise.resolve({
        ok: true, arrayBuffer: () => webauthnOptions
      })
    })

    jest.spyOn(window, 'alert').mockImplementation((msg) => {
      expect(msg).toEqual('Error during registration.\n\nerror')
      done()
    })

    button.click()
  })
})
<|endoftext|>