<commit_before>// node list to array helper method
const toArray = (nl) => [].slice.call(nl);

// window shortcut
var w;
if (typeof window !== 'undefined') {
  w = window;
} else {
  w = {
    addEventListener: function () {},
    removeEventListener: function () {},
  };
}

// states
const DrawState = {
  IDLE: 0,
  DIRTY_CONTENT: 1,
  DIRTY_LAYOUT: 2,
  DIRTY: 3
};

// all active fitty elements
let fitties = [];

// group all redraw calls till next frame, we cancel each frame request when a new one comes in. If no support for request animation frame, this is an empty function and supports for fitty stops.
let redrawFrame = null;
const requestRedraw = 'requestAnimationFrame' in w ? () => {
    w.cancelAnimationFrame(redrawFrame);
    redrawFrame = w.requestAnimationFrame(() => {
      redraw(fitties.filter(f => f.dirty));
    });
  } : () => {};

// sets all fitties to dirty so they are redrawn on the next redraw loop, then calls redraw
const redrawAll = (type) => () => {
  fitties.forEach(f => {
    f.dirty = type;
  });
  requestRedraw();
};

// redraws fitties so they nicely fit their parent container
const redraw = fitties => {

  // getting info from the DOM should not trigger a reflow, let's gather as much intel as possible before triggering a reflow

  // check if styles of all fitties have been computed
  fitties
    .filter(f => !f.styleComputed)
    .forEach(f => { f.styleComputed = computeStyle(f) });

  // restyle elements that require pre-styling, this triggers a reflow, please try to prevent by adding CSS rules (see docs)
  fitties
    .filter(shouldPreStyle)
    .forEach(applyStyle);

  // we now determine which fitties should be redrawn, and if so, we calculate final styles for these fitties
  fitties
    .filter(shouldRedraw)
    .forEach(calculateStyles);

  // now we apply the calculated styles from our previous loop
  fitties.forEach(applyStyles);

  // now we dispatch events for all restyled fitties
  fitties.forEach(dispatchFitEvent);
};

const calculateStyles = f => {

  // get available width from parent node
  f.availableWidth = f.element.parentNode.clientWidth;

  // the space our target element uses
  f.currentWidth = f.element.scrollWidth;

  // remember current font size
  f.previousFontSize = f.currentFontSize;

  // let's calculate the new font size
  f.currentFontSize = Math.min(
    Math.max(
      f.minSize,
      (f.availableWidth / f.currentWidth) * f.previousFontSize
    ),
    f.maxSize
  );

  // if allows wrapping, only wrap when at minimum font size (otherwise would break container)
  f.whiteSpace = f.multiLine && f.currentFontSize === f.minSize
    ? 'normal'
    : 'nowrap';

};

// should always redraw if is not dirty layout, if is dirty layout, only redraw if size has changed
const shouldRedraw = f => {
  return f.dirty !== DrawState.DIRTY_LAYOUT || (f.dirty === DrawState.DIRTY_LAYOUT && f.element.parentNode.clientWidth !== f.availableWidth);
};

// every fitty element is tested for invalid styles
const computeStyle = f => {

  // get style properties
  const style = w.getComputedStyle(f.element, null);

  // get current font size in pixels (if we already calculated it, use the calculated version)
  f.currentFontSize = parseInt(style.getPropertyValue('font-size'), 10);

  // get display type and wrap mode
  f.display = style.getPropertyValue('display');
  f.whiteSpace = style.getPropertyValue('white-space');
};

// determines if this fitty requires initial styling, can be prevented by applying correct styles through CSS
const shouldPreStyle = f => {

  let preStyle = false;

  // should have an inline style, if not, apply
  if (!/inline-/.test(f.display)) {
    preStyle = true;
    f.display = 'inline-block';
  }

  // to correctly calculate dimensions the element should have whiteSpace set to nowrap
  if (f.whiteSpace !== 'nowrap') {
    preStyle = true;
    f.whiteSpace = 'nowrap';
  }

  return preStyle;
};

// apply styles to array of fitties and automatically mark as non dirty
const applyStyles = f => {
  applyStyle(f);
  f.dirty = DrawState.IDLE;
};

// apply styles to single fitty
const applyStyle = f => {

  // remember original style, we need this to restore the fitty style when unsubscribing
  if (!f.originalStyle) {
    f.originalStyle = f.element.getAttribute('style') || '';
  }

  // set the new style to the original style plus the fitty styles
  f.element.style.cssText = `${f.originalStyle};white-space:${f.whiteSpace};display:${f.display};font-size:${f.currentFontSize}px`;
};

// dispatch a fit event on a fitty
const dispatchFitEvent = f => {
  f.element.dispatchEvent(new CustomEvent('fit', {
    detail:{
      oldValue: f.previousFontSize,
      newValue: f.currentFontSize,
      scaleFactor: f.currentFontSize / f.previousFontSize
    }
  }));
};

// fit method, marks the fitty as dirty and requests a redraw (this will also redraw any other fitty marked as dirty)
const fit = (f, type) => () => {
  f.dirty = type;
  requestRedraw();
};

// add a new fitty, does not redraw said fitty
const subscribe = f => {

  // this is a new fitty so we need to validate if it's styles are in order
  f.newbie = true;

  // because it's a new fitty it should also be dirty, we want it to redraw on the first loop
  f.dirty = true;

  // we want to be able to update this fitty
  fitties.push(f);
};

// remove an existing fitty
const unsubscribe = f => () => {

  // remove from fitties array
  fitties = fitties.filter(_ => _.element !== f.element);

  // stop observing DOM
  if (f.observeMutations) {
    f.observer.disconnect();
  }

  // reset font size to inherited size
  f.element.style.cssText = f.originalStyle;
};

const observeMutations = f => {

  // no observing?
  if (!f.observeMutations) {
    return;
  }

  // start observing mutations
  f.observer = new MutationObserver(fit(f, DrawState.DIRTY_CONTENT));

  // start observing
  f.observer.observe(
    f.element,
    f.observeMutations
  );

};

// default mutation observer settings
const mutationObserverDefaultSetting = {
  subtree: true,
  childList: true,
  characterData: true
};

// default fitty options
const defaultOptions = {
  minSize: 16,
  maxSize: 512,
  multiLine: true,
  observeMutations: 'MutationObserver' in w ? mutationObserverDefaultSetting : false
};

// array of elements in, fitty instances out
function fittyCreate(elements, options) {

  // set options object
  const fittyOptions = {

    // expand default options
    ...defaultOptions,

    // override with custom options
    ...options
  };

  // create fitties
  const publicFitties = elements.map(element => {

    // create fitty instance
    const f = {

      // expand defaults
      ...fittyOptions,

      // internal options for this fitty
      element
    };

    // register this fitty
    subscribe(f);

    // should we observe DOM mutations
    observeMutations(f);

    // expose API
    return {
      element,
      fit: fit(f, DrawState.DIRTY),
      unsubscribe: unsubscribe(f)
    };

  });

  // call redraw on newly initiated fitties
  requestRedraw();

  // expose fitties
  return publicFitties;
}

// fitty creation function
function fitty(target, options = {}) {

  // if target is a string
  return typeof target === 'string' ?

    // treat it as a querySelector
    fittyCreate( toArray( document.querySelectorAll(target) ), options) :

    // create single fitty
    fittyCreate([target], options)[0];
}

// handles viewport changes, redraws all fitties, but only does so after a timeout
let resizeDebounce = null;
const onWindowResized = () => {
  w.clearTimeout(resizeDebounce);
  resizeDebounce = w.setTimeout(
    redrawAll(DrawState.DIRTY_LAYOUT),
    fitty.observeWindowDelay
  );
};

// define observe window property, so when we set it to true or false events are automatically added and removed
const events = [ 'resize', 'orientationchange' ];
Object.defineProperty(fitty, 'observeWindow', {
  set: enabled => {
    const method = `${enabled ? 'add' : 'remove'}EventListener`;
    events.forEach(e => {
      w[method](e, onWindowResized);
    });
  }
});

// fitty global properties (by setting observeWindow to true the events above get added)
fitty.observeWindow = true;
fitty.observeWindowDelay = 100;

// public fit all method, will force redraw no matter what
fitty.fitAll = redrawAll(DrawState.DIRTY);

// export our fitty function, we don't want to keep it to our selves
export default fitty;
<commit_msg>wrap fitty in self executing function so execution can be halted when window object is not available<commit_after>export default ((w) => {

  // no window, early exit
  if (!w) {
    return;
  }

  // node list to array helper method
  const toArray = (nl) => [].slice.call(nl);

  // states
  const DrawState = {
    IDLE: 0,
    DIRTY_CONTENT: 1,
    DIRTY_LAYOUT: 2,
    DIRTY: 3
  };

  // all active fitty elements
  let fitties = [];

  // group all redraw calls till next frame, we cancel each frame request when a new one comes in. If no support for request animation frame, this is an empty function and supports for fitty stops.
  let redrawFrame = null;
  const requestRedraw = 'requestAnimationFrame' in w ? () => {
      w.cancelAnimationFrame(redrawFrame);
      redrawFrame = w.requestAnimationFrame(() => {
        redraw(fitties.filter(f => f.dirty));
      });
    } : () => {};

  // sets all fitties to dirty so they are redrawn on the next redraw loop, then calls redraw
  const redrawAll = (type) => () => {
    fitties.forEach(f => {
      f.dirty = type;
    });
    requestRedraw();
  };

  // redraws fitties so they nicely fit their parent container
  const redraw = fitties => {

    // getting info from the DOM should not trigger a reflow, let's gather as much intel as possible before triggering a reflow

    // check if styles of all fitties have been computed
    fitties
      .filter(f => !f.styleComputed)
      .forEach(f => { f.styleComputed = computeStyle(f) });

    // restyle elements that require pre-styling, this triggers a reflow, please try to prevent by adding CSS rules (see docs)
    fitties
      .filter(shouldPreStyle)
      .forEach(applyStyle);

    // we now determine which fitties should be redrawn, and if so, we calculate final styles for these fitties
    fitties
      .filter(shouldRedraw)
      .forEach(calculateStyles);

    // now we apply the calculated styles from our previous loop
    fitties.forEach(applyStyles);

    // now we dispatch events for all restyled fitties
    fitties.forEach(dispatchFitEvent);
  };

  const calculateStyles = f => {

    // get available width from parent node
    f.availableWidth = f.element.parentNode.clientWidth;

    // the space our target element uses
    f.currentWidth = f.element.scrollWidth;

    // remember current font size
    f.previousFontSize = f.currentFontSize;

    // let's calculate the new font size
    f.currentFontSize = Math.min(
      Math.max(
        f.minSize,
        (f.availableWidth / f.currentWidth) * f.previousFontSize
      ),
      f.maxSize
    );

    // if allows wrapping, only wrap when at minimum font size (otherwise would break container)
    f.whiteSpace = f.multiLine && f.currentFontSize === f.minSize
      ? 'normal'
      : 'nowrap';

  };

  // should always redraw if is not dirty layout, if is dirty layout, only redraw if size has changed
  const shouldRedraw = f => {
    return f.dirty !== DrawState.DIRTY_LAYOUT || (f.dirty === DrawState.DIRTY_LAYOUT && f.element.parentNode.clientWidth !== f.availableWidth);
  };

  // every fitty element is tested for invalid styles
  const computeStyle = f => {

    // get style properties
    const style = w.getComputedStyle(f.element, null);

    // get current font size in pixels (if we already calculated it, use the calculated version)
    f.currentFontSize = parseInt(style.getPropertyValue('font-size'), 10);

    // get display type and wrap mode
    f.display = style.getPropertyValue('display');
    f.whiteSpace = style.getPropertyValue('white-space');
  };

  // determines if this fitty requires initial styling, can be prevented by applying correct styles through CSS
  const shouldPreStyle = f => {

    let preStyle = false;

    // should have an inline style, if not, apply
    if (!/inline-/.test(f.display)) {
      preStyle = true;
      f.display = 'inline-block';
    }

    // to correctly calculate dimensions the element should have whiteSpace set to nowrap
    if (f.whiteSpace !== 'nowrap') {
      preStyle = true;
      f.whiteSpace = 'nowrap';
    }

    return preStyle;
  };

  // apply styles to array of fitties and automatically mark as non dirty
  const applyStyles = f => {
    applyStyle(f);
    f.dirty = DrawState.IDLE;
  };

  // apply styles to single fitty
  const applyStyle = f => {

    // remember original style, we need this to restore the fitty style when unsubscribing
    if (!f.originalStyle) {
      f.originalStyle = f.element.getAttribute('style') || '';
    }

    // set the new style to the original style plus the fitty styles
    f.element.style.cssText = `${f.originalStyle};white-space:${f.whiteSpace};display:${f.display};font-size:${f.currentFontSize}px`;
  };

  // dispatch a fit event on a fitty
  const dispatchFitEvent = f => {
    f.element.dispatchEvent(new CustomEvent('fit', {
      detail:{
        oldValue: f.previousFontSize,
        newValue: f.currentFontSize,
        scaleFactor: f.currentFontSize / f.previousFontSize
      }
    }));
  };

  // fit method, marks the fitty as dirty and requests a redraw (this will also redraw any other fitty marked as dirty)
  const fit = (f, type) => () => {
    f.dirty = type;
    requestRedraw();
  };

  // add a new fitty, does not redraw said fitty
  const subscribe = f => {

    // this is a new fitty so we need to validate if it's styles are in order
    f.newbie = true;

    // because it's a new fitty it should also be dirty, we want it to redraw on the first loop
    f.dirty = true;

    // we want to be able to update this fitty
    fitties.push(f);
  };

  // remove an existing fitty
  const unsubscribe = f => () => {

    // remove from fitties array
    fitties = fitties.filter(_ => _.element !== f.element);

    // stop observing DOM
    if (f.observeMutations) {
      f.observer.disconnect();
    }

    // reset font size to inherited size
    f.element.style.cssText = f.originalStyle;
  };

  const observeMutations = f => {

    // no observing?
    if (!f.observeMutations) {
      return;
    }

    // start observing mutations
    f.observer = new MutationObserver(fit(f, DrawState.DIRTY_CONTENT));

    // start observing
    f.observer.observe(
      f.element,
      f.observeMutations
    );

  };

  // default mutation observer settings
  const mutationObserverDefaultSetting = {
    subtree: true,
    childList: true,
    characterData: true
  };

  // default fitty options
  const defaultOptions = {
    minSize: 16,
    maxSize: 512,
    multiLine: true,
    observeMutations: 'MutationObserver' in w ? mutationObserverDefaultSetting : false
  };

  // array of elements in, fitty instances out
  function fittyCreate(elements, options) {

    // set options object
    const fittyOptions = {

      // expand default options
      ...defaultOptions,

      // override with custom options
      ...options
    };

    // create fitties
    const publicFitties = elements.map(element => {

      // create fitty instance
      const f = {

        // expand defaults
        ...fittyOptions,

        // internal options for this fitty
        element
      };

      // register this fitty
      subscribe(f);

      // should we observe DOM mutations
      observeMutations(f);

      // expose API
      return {
        element,
        fit: fit(f, DrawState.DIRTY),
        unsubscribe: unsubscribe(f)
      };

    });

    // call redraw on newly initiated fitties
    requestRedraw();

    // expose fitties
    return publicFitties;
  }

  // fitty creation function
  function fitty(target, options = {}) {

    // if target is a string
    return typeof target === 'string' ?

      // treat it as a querySelector
      fittyCreate( toArray( document.querySelectorAll(target) ), options) :

      // create single fitty
      fittyCreate([target], options)[0];
  }

  // handles viewport changes, redraws all fitties, but only does so after a timeout
  let resizeDebounce = null;
  const onWindowResized = () => {
    w.clearTimeout(resizeDebounce);
    resizeDebounce = w.setTimeout(
      redrawAll(DrawState.DIRTY_LAYOUT),
      fitty.observeWindowDelay
    );
  };

  // define observe window property, so when we set it to true or false events are automatically added and removed
  const events = [ 'resize', 'orientationchange' ];
  Object.defineProperty(fitty, 'observeWindow', {
    set: enabled => {
      const method = `${enabled ? 'add' : 'remove'}EventListener`;
      events.forEach(e => {
        w[method](e, onWindowResized);
      });
    }
  });

  // fitty global properties (by setting observeWindow to true the events above get added)
  fitty.observeWindow = true;
  fitty.observeWindowDelay = 100;

  // public fit all method, will force redraw no matter what
  fitty.fitAll = redrawAll(DrawState.DIRTY);

  // export our fitty function, we don't want to keep it to our selves
  return fitty;

})(typeof window === 'undefined' ? null : window);<|endoftext|>


<commit_before>'use strict'

function id (item) { return item }

function prop (key) {
  return (
    'string' == typeof key
    ? function (data) { return data[key] }
    : key && 'object' === typeof key && 'function' === typeof key.exec //regexp
    ? function (data) { var v = map.exec(data); return v && v[0] }
    : key || id
  )
}

var drain = exports.drain = function (op, done) {

  return function (read) {

    //this function is much simpler to write if you
    //just use recursion, but by using a while loop
    //we do not blow the stack if the stream happens to be sync.
    ;(function next() {
      var loop = true, cbed = false
      while(loop) {
        cbed = false
        read(null, function (end, data) {
          cbed = true
          if(end) {
            loop = false
            if(done) done(end === true ? null : end)
            else if(end && end !== true)
              throw end
          }
          else if(op && false === op(data)) {
            loop = false
            read(true, done || function () {})
          }
          else if(!loop){
            next()
          }
        })
        if(!cbed) {
          loop = false
          return
        }
      }
    })()

  }
}

var onEnd = exports.onEnd = function (done) {
  return drain(null, done)
}

var log = exports.log = function (done) {
  return drain(function (data) {
    console.log(data)
  }, done)
}

var find =
exports.find = function (test, cb) {
  var ended = false
  if(!cb)
    cb = test, test = id
  else
    test = prop(test) || id

  return drain(function (data) {
    if(test(data)) {
      ended = true
      cb(null, data)
    return false
    }
  }, function (err) {
    if(ended) return //already called back
    cb(err === true ? null : err, null)
  })
}

var reduce = exports.reduce = function (reduce, acc, cb) {

  return drain(function (data) {
    acc = reduce(acc, data)
  }, function (err) {
    cb(err, acc)
  })

}

var collect = exports.collect =
function (cb) {
  return reduce(function (arr, item) {
    arr.push(item)
    return arr
  }, [], cb)
}

var concat = exports.concat =
function (cb) {
  return reduce(function (a, b) {
    return a + b
  }, '', cb)
}

<commit_msg>can abort a drain manually, via sink.abort(err)<commit_after>'use strict'

function id (item) { return item }

function prop (key) {
  return (
    'string' == typeof key
    ? function (data) { return data[key] }
    : key && 'object' === typeof key && 'function' === typeof key.exec //regexp
    ? function (data) { var v = map.exec(data); return v && v[0] }
    : key || id
  )
}

var drain = exports.drain = function (op, done) {
  var read, abort

  function sink (_read) {
    read = _read
    if(abort) return sink.abort()
    //this function is much simpler to write if you
    //just use recursion, but by using a while loop
    //we do not blow the stack if the stream happens to be sync.
    ;(function next() {
        var loop = true, cbed = false
        while(loop) {
          cbed = false
          read(null, function (end, data) {
            cbed = true
            if(end = end || abort) {
              loop = false
              if(done) done(end === true ? null : end)
              else if(end && end !== true)
                throw end
            }
            else if(op && false === op(data) || abort) {
              loop = false
              read(abort || true, done || function () {})
            }
            else if(!loop){
              next()
            }
          })
          if(!cbed) {
            loop = false
            return
          }
        }
      })()
  }

  sink.abort = function (err) {
    abort = err || true
    if(read) return read(abort, function () {})
  }

  return sink
}

var onEnd = exports.onEnd = function (done) {
  return drain(null, done)
}

var log = exports.log = function (done) {
  return drain(function (data) {
    console.log(data)
  }, done)
}

var find =
exports.find = function (test, cb) {
  var ended = false
  if(!cb)
    cb = test, test = id
  else
    test = prop(test) || id

  return drain(function (data) {
    if(test(data)) {
      ended = true
      cb(null, data)
    return false
    }
  }, function (err) {
    if(ended) return //already called back
    cb(err === true ? null : err, null)
  })
}

var reduce = exports.reduce = function (reduce, acc, cb) {

  return drain(function (data) {
    acc = reduce(acc, data)
  }, function (err) {
    cb(err, acc)
  })

}

var collect = exports.collect =
function (cb) {
  return reduce(function (arr, item) {
    arr.push(item)
    return arr
  }, [], cb)
}

var concat = exports.concat =
function (cb) {
  return reduce(function (a, b) {
    return a + b
  }, '', cb)
}

<|endoftext|>


<commit_before>import GoogleClassroomHandler from 'core/social-handlers/GoogleClassroomHandler'
import factories from 'test/app/factories'
import api from 'core/api'

var gClassrooms = []

describe('markAsImported(gcId)', () => {
  beforeEach((done) => {
    gClassrooms =[{
        id: "id1",
        name: "test class 1"
      }, {
        id: "id2",
        name: "test class 2"
      }]
    me.set(factories.makeUser({role: 'teacher', googleClassrooms: gClassrooms}).attributes)
    spyOn(me, 'save').and.callFake(function() {
      return $.Deferred().resolve(me).promise();
    });
    done()
  })

  it('should set importedToOzaria=true for gcId in me.googleClassrooms', async function (done) {
    expect(me.get('googleClassrooms')[0].importedToOzaria).toBeUndefined()
    expect(me.get('googleClassrooms')[1].importedToOzaria).toBeUndefined()
    try {
      await GoogleClassroomHandler.markAsImported(gClassrooms[0].id)
      expect(me.get('googleClassrooms')[0].importedToOzaria).toBeDefined()
      expect(me.get('googleClassrooms')[0].importedToOzaria).toBe(true)
      expect(me.get('googleClassrooms')[1].importedToOzaria).toBeUndefined()
      done()
    }
    catch (err) {
      done.fail(new Error("This should not have been called"))
    }
  });

  it('should throw error if the google classroom id does not exist in me.googleClassrooms', async function (done) {
    expect(me.get('googleClassrooms')[0].importedToOzaria).toBeUndefined()
    expect(me.get('googleClassrooms')[1].importedToOzaria).toBeUndefined()
    try {
      await GoogleClassroomHandler.markAsImported("new-id")
      done.fail(new Error("This should not have been called"))
    }
    catch (err) {
      expect(me.get('googleClassrooms')[0].importedToOzaria).toBeUndefined()
      expect(me.get('googleClassrooms')[1].importedToOzaria).toBeUndefined()
      done()
    }
  });

})

describe('importClassrooms()', () => {
  beforeEach((done) => {
    gClassrooms = [{
        id: "id1",
        name: "test class 1"
      }, {
        id: "id2",
        name: "test class 2"
      }]
    me.set(factories.makeUser({role: 'teacher'}).attributes)
    spyOn(me, 'save').and.callFake(function() {
      return $.Deferred().resolve(me).promise();
    });
    done()
  })

  it('adds googleClassrooms to the `me` object', async function(done) {
    spyOn(GoogleClassroomHandler.gcApiHandler, 'loadClassroomsFromAPI').and.returnValue(Promise.resolve(gClassrooms))
    expect(me.get('googleClassrooms')).toBeUndefined()
    try {
      await GoogleClassroomHandler.importClassrooms()
      expect(me.get('googleClassrooms')).toBeDefined()
      expect(me.get('googleClassrooms').length).toBe(gClassrooms.length)
      done()
    }
    catch (err) {
      done.fail(new Error("This should not have been called"))
    }
  });

  it('updates the linked classrooms in me.googleClassrooms while keeping the importedToOzaria value', async function(done) {

    me.set('googleClassrooms', gClassrooms)

    // mark gClassrooms[0] as imported
    let importedClassroom = me.get('googleClassrooms').find((c) => c.id == gClassrooms[0].id)
    importedClassroom.importedToOzaria = true

    expect(me.get('googleClassrooms').length).toBe(2)
    expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id)).toBeDefined()
    expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id).name).toBe(importedClassroom.name)
    expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id).importedToOzaria).toBe(true)
    expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id)).toBeDefined()
    expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id).name).toBe(gClassrooms.find((gc) => gc.id!=importedClassroom.id).name)
    expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id).importedToOzaria).toBeUndefined()

    // new classrooms data recieved from google classroom API
    const newGClassrooms = [{
      id: "id1",
      name: "test class 1-new"
    },
    {
      id: "id2",
      name: "test class 2-new"
    }]
    spyOn(GoogleClassroomHandler.gcApiHandler, 'loadClassroomsFromAPI').and.returnValue(Promise.resolve(newGClassrooms))

    try {
      await GoogleClassroomHandler.importClassrooms()
      // names of linked classroom should be updated, and importedToOzaria field should remain same
      expect(me.get('googleClassrooms').length).toBe(2)
      expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id)).toBeDefined()
      expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id).name).toBe(newGClassrooms.find((gc) => gc.id==importedClassroom.id).name)
      expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id).importedToOzaria).toBe(true)
      expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id)).toBeDefined()
      expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id).name).toBe(newGClassrooms.find((gc) => gc.id!=importedClassroom.id).name)
      expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id).importedToOzaria).toBeUndefined()
      done()
    }
    catch (err) {
      done.fail(new Error("This should not have been called"))
    }
  })

  it('does not remove an already imported classroom from me.googleClassrooms if deleted from google classroom, and sets deletedFromGC flag', async function(done) {
    // mark gClassrooms[0] as imported
    me.set('googleClassrooms', [gClassrooms[0]])
    let importedClassroom = me.get('googleClassrooms').find((c) => c.id == gClassrooms[0].id)
    importedClassroom.importedToOzaria = true

    expect(me.get('googleClassrooms').length).toBe(1)

    // new classrooms data recieved from google classroom API - does not contain classroom id1
    const newGClassrooms = [{
      id: "id2",
      name: "test class 2"
    }]
    spyOn(GoogleClassroomHandler.gcApiHandler, 'loadClassroomsFromAPI').and.returnValue(Promise.resolve(newGClassrooms))

    try {
      await GoogleClassroomHandler.importClassrooms()
      // me.googleClassrooms should contain old imported classroom id1 as well as new classroom id2
      expect(me.get('googleClassrooms').length).toBe(2)
      expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id)).toBeDefined()
      expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id).importedToOzaria).toBe(true)
      expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id).deletedFromGC).toBe(true)
      expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id)).toBeDefined()
      expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id).name).toBe(newGClassrooms[0].name)
      expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id).importedToOzaria).toBeUndefined()
      expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id).deletedFromGC).toBeUndefined()
      done()
    }
    catch (err) {
      done.fail(new Error("This should not have been called"))
    }
  })

})

const gcStudents = [
  {
    userId: "studentId1",
    profile: {
      name: {
        givenName: "s1-firstName",
        familyName: "s1-lastName"
      },
      emailAddress: "student1@test.com"
    }
  },
  {
    userId: "studentId2",
    profile: {
      name: {
        givenName: "s2-firstName",
        familyName: "s2-lastName"
      },
      emailAddress: "student2@test.com"
    }
  }
]

describe('importStudentsToClassroom(cocoClassroom)', () => {
  beforeEach((done) => {
    me.set(factories.makeUser({role: 'teacher'}).attributes)
    spyOn(GoogleClassroomHandler.gcApiHandler, 'loadStudentsFromAPI').and.returnValue(Promise.resolve({students: gcStudents}))
    done()
  })

  it('signs up the imported students on codecombat with their google id and adds them to the classroom', async function(done) {
    const users = gcStudents.map((s) => {
      return factories.makeUser({
        gplusID: s.userId,
        firstName: s.profile.givenName,
        lastName: s.profile.familyName,
        email: s.profile.emailAddress,
        role: 'student'
      })
    })
    spyOn(api.users, 'signupFromGoogleClassroom').and.callFake(function(attrs) {
      return Promise.resolve(users.find((u) => u.get('gplusID')==attrs.gplusID))
    })

    const classroomWithNewMembers = factories.makeClassroom({googleClassroomId: "id1", members: users.map((u) => u._id)})
    spyOn(api.classrooms, 'addMembers').and.returnValue(Promise.resolve(classroomWithNewMembers))

    try {
      const cocoClassroom = factories.makeClassroom({googleClassroomId: "id1"})
      const classroomNewMembers = await GoogleClassroomHandler.importStudentsToClassroom(cocoClassroom)
      expect(api.users.signupFromGoogleClassroom).toHaveBeenCalled()
      expect(api.users.signupFromGoogleClassroom.calls.count()).toEqual(gcStudents.length)
      expect(api.classrooms.addMembers).toHaveBeenCalled()
      expect(classroomNewMembers.length).toEqual(gcStudents.length)
      expect(classroomNewMembers[0].get('gplusID')).toBe(gcStudents[0].userId)
      expect(classroomNewMembers[1].get('gplusID')).toBe(gcStudents[1].userId)
      done()
    }
    catch (err) {
      done.fail(new Error("This should not have been called"))
    }
  });

  describe ('if students already exist on codecombat', () => {
    it('does not add students if already exist in the classroom', async function(done) {
      const signUpResult = gcStudents.map((s) => {
        let user = factories.makeUser({
          gplusID: s.userId,
          firstName: s.profile.givenName,
          lastName: s.profile.familyName,
          email: s.profile.emailAddress,
          role: 'student'
        }).attributes
        return {
          isError: true,
          errorID: 'student-account-exists',
          error: user
        }
      })
      spyOn(api.users, 'signupFromGoogleClassroom').and.callFake(function(attrs) {
        return Promise.resolve(signUpResult.find((r) => r.error.gplusID==attrs.gplusID))
      })

      const classroomWithNewMembers = factories.makeClassroom({googleClassroomId: "id1", members: signUpResult.map((r) => r.error._id)})
      spyOn(api.classrooms, 'addMembers').and.returnValue(Promise.resolve(classroomWithNewMembers))

      try {
        await GoogleClassroomHandler.importStudentsToClassroom(classroomWithNewMembers)
        done.fail(new Error("This should not have been called"))
      }
      catch (err) {
        expect(api.users.signupFromGoogleClassroom).toHaveBeenCalled()
        expect(api.users.signupFromGoogleClassroom.calls.count()).toEqual(gcStudents.length)
        expect(api.classrooms.addMembers).not.toHaveBeenCalled()
        done()
      }
    });

    it('adds students to classroom if do not exist already', async function(done) {
      const signUpResult = gcStudents.map((s) => {
        let user = factories.makeUser({
          gplusID: s.userId,
          firstName: s.profile.givenName,
          lastName: s.profile.familyName,
          email: s.profile.emailAddress,
          role: 'student'
        }).attributes
        return {
          isError: true,
          errorID: 'student-account-exists',
          error: user
        }
      })
      spyOn(api.users, 'signupFromGoogleClassroom').and.callFake(function(attrs) {
        return Promise.resolve(signUpResult.find((r) => r.error.gplusID==attrs.gplusID))
      })

      const classroomWithNewMembers = factories.makeClassroom({googleClassroomId: "id1", members: signUpResult.map((r) => r.error._id)})
      spyOn(api.classrooms, 'addMembers').and.returnValue(Promise.resolve(classroomWithNewMembers))

      try {
        const cocoClassroom = factories.makeClassroom({googleClassroomId: "id1"})
        const newMembers = await GoogleClassroomHandler.importStudentsToClassroom(cocoClassroom)
        expect(api.users.signupFromGoogleClassroom).toHaveBeenCalled()
        expect(api.users.signupFromGoogleClassroom.calls.count()).toEqual(gcStudents.length)
        expect(api.classrooms.addMembers).toHaveBeenCalled()
        expect(newMembers.length).toEqual(gcStudents.length)
        done()
      }
      catch (err) {
        done.fail(new Error("This should not have been called"))
      }
    })
  })
})

describe('importStudentsToClassroom(cocoClassroom)', () => {
  it('calls `loadStudentsFromAPI` multiple times until previous api call returns nextPageToken', async function(done) {
    me.set(factories.makeUser({role: 'teacher'}).attributes)
    spyOn(GoogleClassroomHandler.gcApiHandler, 'loadStudentsFromAPI').and.returnValues(Promise.resolve({students: gcStudents[0], nextPageToken: 'abcd'}), Promise.resolve({students: gcStudents[1]}))

    const users = gcStudents.map((s) => {
      return factories.makeUser({
        gplusID: s.userId,
        firstName: s.profile.givenName,
        lastName: s.profile.familyName,
        email: s.profile.emailAddress,
        role: 'student'
      })
    })
    spyOn(api.users, 'signupFromGoogleClassroom').and.callFake(function(attrs) {
      return Promise.resolve(users.find((u) => u.get('gplusID')==attrs.gplusID))
    })

    const classroomWithNewMembers = factories.makeClassroom({googleClassroomId: "id1", members: users.map((u) => u._id)})
    spyOn(api.classrooms, 'addMembers').and.returnValue(Promise.resolve(classroomWithNewMembers))

    try {
      const cocoClassroom = factories.makeClassroom({googleClassroomId: "id1"})
      const classroomNewMembers = await GoogleClassroomHandler.importStudentsToClassroom(cocoClassroom)
      expect(GoogleClassroomHandler.gcApiHandler.loadStudentsFromAPI.calls.count()).toEqual(2)
      expect(classroomNewMembers.length).toEqual(gcStudents.length)
      done()
    }
    catch (err) {
      done.fail(new Error("This should not have been called"))
    }
  })
})
<commit_msg>Fix google classroom test<commit_after>import GoogleClassroomHandler from 'core/social-handlers/GoogleClassroomHandler'
import factories from 'test/app/factories'
import api from 'core/api'

var gClassrooms = []

describe('markAsImported(gcId)', () => {
  beforeEach((done) => {
    gClassrooms =[{
        id: "id1",
        name: "test class 1"
      }, {
        id: "id2",
        name: "test class 2"
      }]
    me.set(factories.makeUser({role: 'teacher', googleClassrooms: gClassrooms}).attributes)
    spyOn(me, 'save').and.callFake(function() {
      return $.Deferred().resolve(me).promise();
    });
    done()
  })

  it('should set importedToOzaria=true for gcId in me.googleClassrooms', async function (done) {
    expect(me.get('googleClassrooms')[0].importedToOzaria).toBeUndefined()
    expect(me.get('googleClassrooms')[1].importedToOzaria).toBeUndefined()
    try {
      await GoogleClassroomHandler.markAsImported(gClassrooms[0].id)
      expect(me.get('googleClassrooms')[0].importedToOzaria).toBeDefined()
      expect(me.get('googleClassrooms')[0].importedToOzaria).toBe(true)
      expect(me.get('googleClassrooms')[1].importedToOzaria).toBeUndefined()
      done()
    }
    catch (err) {
      done.fail(new Error("This should not have been called"))
    }
  });

  it('should throw error if the google classroom id does not exist in me.googleClassrooms', async function (done) {
    expect(me.get('googleClassrooms')[0].importedToOzaria).toBeUndefined()
    expect(me.get('googleClassrooms')[1].importedToOzaria).toBeUndefined()
    try {
      await GoogleClassroomHandler.markAsImported("new-id")
      done.fail(new Error("This should not have been called"))
    }
    catch (err) {
      expect(me.get('googleClassrooms')[0].importedToOzaria).toBeUndefined()
      expect(me.get('googleClassrooms')[1].importedToOzaria).toBeUndefined()
      done()
    }
  });

})

describe('importClassrooms()', () => {
  beforeEach((done) => {
    gClassrooms = [{
        id: "id1",
        name: "test class 1"
      }, {
        id: "id2",
        name: "test class 2"
      }]
    me.set(factories.makeUser({role: 'teacher'}).attributes)
    spyOn(me, 'save').and.callFake(function() {
      return $.Deferred().resolve(me).promise();
    });
    done()
  })

  it('adds googleClassrooms to the `me` object', async function(done) {
    spyOn(GoogleClassroomHandler.gcApiHandler, 'loadClassroomsFromAPI').and.returnValue(Promise.resolve(gClassrooms))
    expect(me.get('googleClassrooms')).toBeUndefined()
    try {
      await GoogleClassroomHandler.importClassrooms()
      expect(me.get('googleClassrooms')).toBeDefined()
      expect(me.get('googleClassrooms').length).toBe(gClassrooms.length)
      done()
    }
    catch (err) {
      done.fail(new Error("This should not have been called"))
    }
  });

  it('updates the linked classrooms in me.googleClassrooms while keeping the importedToOzaria value', async function(done) {

    me.set('googleClassrooms', gClassrooms)

    // mark gClassrooms[0] as imported
    let importedClassroom = me.get('googleClassrooms').find((c) => c.id == gClassrooms[0].id)
    importedClassroom.importedToOzaria = true

    expect(me.get('googleClassrooms').length).toBe(2)
    expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id)).toBeDefined()
    expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id).name).toBe(importedClassroom.name)
    expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id).importedToOzaria).toBe(true)
    expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id)).toBeDefined()
    expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id).name).toBe(gClassrooms.find((gc) => gc.id!=importedClassroom.id).name)
    expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id).importedToOzaria).toBeUndefined()

    // new classrooms data recieved from google classroom API
    const newGClassrooms = [{
      id: "id1",
      name: "test class 1-new"
    },
    {
      id: "id2",
      name: "test class 2-new"
    }]
    spyOn(GoogleClassroomHandler.gcApiHandler, 'loadClassroomsFromAPI').and.returnValue(Promise.resolve(newGClassrooms))

    try {
      await GoogleClassroomHandler.importClassrooms()
      // names of linked classroom should be updated, and importedToOzaria field should remain same
      expect(me.get('googleClassrooms').length).toBe(2)
      expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id)).toBeDefined()
      expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id).name).toBe(newGClassrooms.find((gc) => gc.id==importedClassroom.id).name)
      expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id).importedToOzaria).toBe(true)
      expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id)).toBeDefined()
      expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id).name).toBe(newGClassrooms.find((gc) => gc.id!=importedClassroom.id).name)
      expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id).importedToOzaria).toBeUndefined()
      done()
    }
    catch (err) {
      done.fail(new Error("This should not have been called"))
    }
  })

  it('does not remove an already imported classroom from me.googleClassrooms if deleted from google classroom, and sets deletedFromGC flag', async function(done) {
    // mark gClassrooms[0] as imported
    me.set('googleClassrooms', [gClassrooms[0]])
    let importedClassroom = me.get('googleClassrooms').find((c) => c.id == gClassrooms[0].id)
    importedClassroom.importedToOzaria = true

    expect(me.get('googleClassrooms').length).toBe(1)

    // new classrooms data recieved from google classroom API - does not contain classroom id1
    const newGClassrooms = [{
      id: "id2",
      name: "test class 2"
    }]
    spyOn(GoogleClassroomHandler.gcApiHandler, 'loadClassroomsFromAPI').and.returnValue(Promise.resolve(newGClassrooms))

    try {
      await GoogleClassroomHandler.importClassrooms()
      // me.googleClassrooms should contain old imported classroom id1 as well as new classroom id2
      expect(me.get('googleClassrooms').length).toBe(2)
      expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id)).toBeDefined()
      expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id).importedToOzaria).toBe(true)
      expect(me.get('googleClassrooms').find((gc) => gc.id == importedClassroom.id).deletedFromGC).toBe(true)
      expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id)).toBeDefined()
      expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id).name).toBe(newGClassrooms[0].name)
      expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id).importedToOzaria).toBeUndefined()
      expect(me.get('googleClassrooms').find((gc) => gc.id != importedClassroom.id).deletedFromGC).toBeUndefined()
      done()
    }
    catch (err) {
      done.fail(new Error("This should not have been called"))
    }
  })

})

const gcStudents = [
  {
    userId: "studentId1",
    profile: {
      name: {
        givenName: "s1-firstName",
        familyName: "s1-lastName"
      },
      emailAddress: "student1@test.com"
    }
  },
  {
    userId: "studentId2",
    profile: {
      name: {
        givenName: "s2-firstName",
        familyName: "s2-lastName"
      },
      emailAddress: "student2@test.com"
    }
  }
]

describe('importStudentsToClassroom(cocoClassroom)', () => {
  beforeEach((done) => {
    me.set(factories.makeUser({role: 'teacher'}).attributes)
    spyOn(GoogleClassroomHandler.gcApiHandler, 'loadStudentsFromAPI').and.returnValue(Promise.resolve({students: gcStudents}))
    done()
  })

  it('signs up the imported students on codecombat with their google id and adds them to the classroom', async function(done) {
    const users = gcStudents.map((s) => {
      return factories.makeUser({
        gplusID: s.userId,
        firstName: s.profile.givenName,
        lastName: s.profile.familyName,
        email: s.profile.emailAddress,
        role: 'student'
      })
    })
    spyOn(api.users, 'signupFromGoogleClassroom').and.callFake(function(attrs) {
      return Promise.resolve(users.find((u) => u.get('gplusID')==attrs.gplusID))
    })

    const classroomWithNewMembers = factories.makeClassroom({googleClassroomId: "id1", members: users.map((u) => u._id)})
    spyOn(api.classrooms, 'addMembers').and.returnValue(Promise.resolve(classroomWithNewMembers))

    try {
      const cocoClassroom = factories.makeClassroom({googleClassroomId: "id1"})
      const classroomNewMembers = await GoogleClassroomHandler.importStudentsToClassroom(cocoClassroom)
      expect(api.users.signupFromGoogleClassroom).toHaveBeenCalled()
      expect(api.users.signupFromGoogleClassroom.calls.count()).toEqual(gcStudents.length)
      expect(api.classrooms.addMembers).toHaveBeenCalled()
      expect(classroomNewMembers.length).toEqual(gcStudents.length)
      expect(classroomNewMembers[0].get('gplusID')).toBe(gcStudents[0].userId)
      expect(classroomNewMembers[1].get('gplusID')).toBe(gcStudents[1].userId)
      done()
    }
    catch (err) {
      done.fail(new Error("This should not have been called"))
    }
  });

  describe ('if students already exist on codecombat', () => {
    it('does not add students if already exist in the classroom', async function(done) {
      const signUpResult = gcStudents.map((s) => {
        let user = factories.makeUser({
          gplusID: s.userId,
          firstName: s.profile.givenName,
          lastName: s.profile.familyName,
          email: s.profile.emailAddress,
          role: 'student'
        }).attributes
        return {
          isError: true,
          errorID: 'student-account-exists',
          error: user
        }
      })
      spyOn(api.users, 'signupFromGoogleClassroom').and.callFake(function(attrs) {
        return Promise.resolve(signUpResult.find((r) => r.error.gplusID==attrs.gplusID))
      })

      const classroomWithNewMembers = factories.makeClassroom({googleClassroomId: "id1", members: signUpResult.map((r) => r.error._id)})
      spyOn(api.classrooms, 'addMembers').and.returnValue(Promise.resolve(classroomWithNewMembers))

      try {
        await GoogleClassroomHandler.importStudentsToClassroom(classroomWithNewMembers)
        expect(api.users.signupFromGoogleClassroom).toHaveBeenCalled()
        expect(api.users.signupFromGoogleClassroom.calls.count()).toEqual(gcStudents.length)
        expect(api.classrooms.addMembers).not.toHaveBeenCalled()
        done()
      }
      catch (err) {
        done.fail(new Error("This should not have been called"))
      }
    });

    it('adds students to classroom if do not exist already', async function(done) {
      const signUpResult = gcStudents.map((s) => {
        let user = factories.makeUser({
          gplusID: s.userId,
          firstName: s.profile.givenName,
          lastName: s.profile.familyName,
          email: s.profile.emailAddress,
          role: 'student'
        }).attributes
        return {
          isError: true,
          errorID: 'student-account-exists',
          error: user
        }
      })
      spyOn(api.users, 'signupFromGoogleClassroom').and.callFake(function(attrs) {
        return Promise.resolve(signUpResult.find((r) => r.error.gplusID==attrs.gplusID))
      })

      const classroomWithNewMembers = factories.makeClassroom({googleClassroomId: "id1", members: signUpResult.map((r) => r.error._id)})
      spyOn(api.classrooms, 'addMembers').and.returnValue(Promise.resolve(classroomWithNewMembers))

      try {
        const cocoClassroom = factories.makeClassroom({googleClassroomId: "id1"})
        const newMembers = await GoogleClassroomHandler.importStudentsToClassroom(cocoClassroom)
        expect(api.users.signupFromGoogleClassroom).toHaveBeenCalled()
        expect(api.users.signupFromGoogleClassroom.calls.count()).toEqual(gcStudents.length)
        expect(api.classrooms.addMembers).toHaveBeenCalled()
        expect(newMembers.length).toEqual(gcStudents.length)
        done()
      }
      catch (err) {
        done.fail(new Error("This should not have been called"))
      }
    })
  })
})

describe('importStudentsToClassroom(cocoClassroom)', () => {
  it('calls `loadStudentsFromAPI` multiple times until previous api call returns nextPageToken', async function(done) {
    me.set(factories.makeUser({role: 'teacher'}).attributes)
    spyOn(GoogleClassroomHandler.gcApiHandler, 'loadStudentsFromAPI').and.returnValues(Promise.resolve({students: gcStudents[0], nextPageToken: 'abcd'}), Promise.resolve({students: gcStudents[1]}))

    const users = gcStudents.map((s) => {
      return factories.makeUser({
        gplusID: s.userId,
        firstName: s.profile.givenName,
        lastName: s.profile.familyName,
        email: s.profile.emailAddress,
        role: 'student'
      })
    })
    spyOn(api.users, 'signupFromGoogleClassroom').and.callFake(function(attrs) {
      return Promise.resolve(users.find((u) => u.get('gplusID')==attrs.gplusID))
    })

    const classroomWithNewMembers = factories.makeClassroom({googleClassroomId: "id1", members: users.map((u) => u._id)})
    spyOn(api.classrooms, 'addMembers').and.returnValue(Promise.resolve(classroomWithNewMembers))

    try {
      const cocoClassroom = factories.makeClassroom({googleClassroomId: "id1"})
      const classroomNewMembers = await GoogleClassroomHandler.importStudentsToClassroom(cocoClassroom)
      expect(GoogleClassroomHandler.gcApiHandler.loadStudentsFromAPI.calls.count()).toEqual(2)
      expect(classroomNewMembers.length).toEqual(gcStudents.length)
      done()
    }
    catch (err) {
      done.fail(new Error("This should not have been called"))
    }
  })
})
<|endoftext|>


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

(function() {

  Tokenizer = {};

  Tokenizer.__proto__ = Array.prototype

  Tokenizer.extract = function(times) {
    times = times || 1;

    if (this.length <= 0)  return this;

    var matches = this[this.length - 1].match(this.regex);
    var newToken, rest;

    if (matches) {
      if (this.opts.preserveSplit) {
        if (matches[1].length > 0) {
          newToken = matches[1];
          rest = matches[2] + matches[3];
        } else {
          newToken = matches[1] + matches[2];
          rest = matches[3];
        }
        this.retoken.splice(this.retoken.length - 2, 0, '');
      } else {
        newToken = matches[1];
        this.retoken.splice(this.retoken.length - 2, 0, matches[2]);
        rest = matches[3];
      }

      this[this.length - 1] = newToken;
      this[this.length] = rest;
    }

    return (times > 1)? this.extract(times - 1) : this;
  }

  Tokenizer.push = function(str, retoken) {
    Array.prototype.push.call(this, str);
    this.retoken.push(retoken || '');
    return this;
  }

  Tokenizer.unshift = function(str, retoken) {
    Array.prototype.unshift.call(this, str);
    this.retoken.unshift();
    return this;
  }

  Tokenizer.splice = function() {
    return undefined;
  }

  Tokenizer.retract = function(times) {
    times = times || 1;
    if (this.length < 2) return;

    var retoken = this.retoken.splice(this.retoken.length - 2, 1)[0] || '';

    this[this.length - 2] += retoken + this[this.length - 1];
    this.pop();

    return (times > 1)? this.retract(times - 1) : this;
  }

  Tokenizer.replaceToken = function(token, replacement) {
     var index = this.indexOf(token);
     if (!~index) return this;

     this[index] = replacement;

     return this;
  }

  function tokenizer(regex, opts) {
    var instance = [ ];

    instance.__proto__ = Tokenizer;
    instance.opts = opts || {};
    instance.retoken = [];

    var reCore;

    if (regex instanceof RegExp) {
      reCore = regex.source.replace('/^\^/', '').replace('/\$$/', '');
    } else {
      reCore = regex;
      reCore.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
    }

    instance.regex = new RegExp('^(.*?)(' + reCore + ')(.*)$');

    return instance;
  }

  if (typeof module !== 'undefined' && module && module.exports) { // Node.js & CommonJS
    module.exports = tokenizer;
  } else if (typeof define === 'function' && define.amd) {
    define('retoken', [], function() {
      return tokenizer;
    });
  } else { // Browser
    window.retoken = tokenizer;
  }

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

(function() {

  Tokenizer = {};

  Tokenizer.__proto__ = Array.prototype

  Tokenizer.extract = function(times) {
    times = times || 1;

    if (this.length <= 0)  return this;

    var matches = this[this.length - 1].match(this.regex);
    var newToken, rest;

    if (matches) {
      if (this.opts.preserveSplit) {
        if (matches[1].length > 0) {
          newToken = matches[1];
          rest = matches[2] + matches[matches.length - 1];
        } else {
          newToken = matches[1] + matches[2];
          rest = matches[matches.length - 1];
        }
        this.retoken.splice(this.retoken.length - 2, 0, '');
      } else {
        newToken = matches[1];
        this.retoken.splice(this.retoken.length - 2, 0, matches[2]);
        rest = matches[3];
      }

      this[this.length - 1] = newToken;
      this[this.length] = rest;
    }

    return (times > 1)? this.extract(times - 1) : this;
  }

  Tokenizer.push = function(str, retoken) {
    Array.prototype.push.call(this, str);
    this.retoken.push(retoken || '');
    return this;
  }

  Tokenizer.unshift = function(str, retoken) {
    Array.prototype.unshift.call(this, str);
    this.retoken.unshift();
    return this;
  }

  Tokenizer.splice = function() {
    return undefined;
  }

  Tokenizer.retract = function(times) {
    times = times || 1;
    if (this.length < 2) return;

    var retoken = this.retoken.splice(this.retoken.length - 2, 1)[0] || '';

    this[this.length - 2] += retoken + this[this.length - 1];
    this.pop();

    return (times > 1)? this.retract(times - 1) : this;
  }

  Tokenizer.replaceToken = function(token, replacement) {
     var index = this.indexOf(token);
     if (!~index) return this;

     this[index] = replacement;

     return this;
  }

  function tokenizer(regex, opts) {
    var instance = [ ];

    instance.__proto__ = Tokenizer;
    instance.opts = opts || {};
    instance.retoken = [];

    var reCore;

    if (regex instanceof RegExp) {
      reCore = regex.source.replace('/^\^/', '').replace('/\$$/', '');
    } else {
      reCore = regex;
      reCore.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
    }

    instance.regex = new RegExp('^(.*?)(' + reCore + ')(.*)$');

    return instance;
  }

  if (typeof module !== 'undefined' && module && module.exports) { // Node.js & CommonJS
    module.exports = tokenizer;
  } else if (typeof define === 'function' && define.amd) {
    define('retoken', [], function() {
      return tokenizer;
    });
  } else { // Browser
    window.retoken = tokenizer;
  }

})();
<|endoftext|>


<commit_before>// Generated on 2015-09-01 using generator-angular 0.12.1
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Automatically load required Grunt tasks
  require('jit-grunt')(grunt, {
    useminPrepare: 'grunt-usemin',
    ngtemplates: 'grunt-angular-templates',
    cdnify: 'grunt-google-cdn'
  });

  // Configurable paths for the application
  var appConfig = {
    app: require('./bower.json').appPath || 'app',
    dist: 'dist'
  };

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    yeoman: appConfig,

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      js: {
        files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
        tasks: ['newer:jshint:all'],
        options: {
          livereload: '<%= connect.options.livereload %>'
        }
      },
      jsTest: {
        files: ['test/spec/{,*/}*.js'],
        tasks: ['newer:jshint:test', 'karma']
      },
      styles: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
        tasks: ['newer:copy:styles', 'autoprefixer']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect().use(
                '/app/styles',
                connect.static('./app/styles')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      test: {
        options: {
          port: 9001,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect.static('test'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>'
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: {
        src: [
          'Gruntfile.js',
          '<%= yeoman.app %>/scripts/{,*/}*.js'
        ]
      },
      test: {
        options: {
          jshintrc: 'test/.jshintrc'
        },
        src: ['test/spec/{,*/}*.js']
      }
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/{,*/}*',
            '!<%= yeoman.dist %>/server.js{,*/}*',
            '!<%= yeoman.dist %>/package.json{,*/}*',
            '!<%= yeoman.dist %>/deplist.txt{,*/}*',
            '!<%= yeoman.dist %>/.htaccess{,*/}*',
            '!<%= yeoman.dist %>/.openshift{,*/}*',
            '!<%= yeoman.dist %>/.git{,*/}*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      server: {
        options: {
          map: true,
        },
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      app: {
        src: ['<%= yeoman.app %>/index.html'],
        ignorePath:  /\.\.\//
      },
      test: {
        devDependencies: true,
        src: '<%= karma.unit.configFile %>',
        ignorePath:  /\.\.\//,
        fileTypes:{
          js: {
            block: /(([\s\t]*)\/{2}\s*?bower:\s*?(\S*))(\n|\r|.)*?(\/{2}\s*endbower)/gi,
              detect: {
                js: /'(.*\.js)'/gi
              },
              replace: {
                js: '\'{{filePath}}\','
              }
            }
          }
      }
    },

    // Renames files for browser caching purposes
    filerev: {
      dist: {
        src: [
          '<%= yeoman.dist %>/scripts/{,*/}*.js',
          '<%= yeoman.dist %>/styles/{,*/}*.css',
          '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
          '<%= yeoman.dist %>/styles/fonts/*'
        ]
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {}
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      js: ['<%= yeoman.dist %>/scripts/{,*/}*.js'],
      options: {
        assetsDirs: [
          '<%= yeoman.dist %>',
          '<%= yeoman.dist %>/images',
          '<%= yeoman.dist %>/styles'
        ],
        patterns: {
          js: [[/(images\/[^''""]*\.(png|jpg|jpeg|gif|webp|svg))/g, 'Replacing references to images']]
        }
      }
    },

    // The following *-min tasks will produce minified files in the dist folder
    // By default, your `index.html`'s <!-- Usemin block --> will take care of
    // minification. These next options are pre-configured if you do not wish
    // to use the Usemin blocks.
    // cssmin: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/styles/main.css': [
    //         '.tmp/styles/{,*/}*.css'
    //       ]
    //     }
    //   }
    // },
    // uglify: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/scripts/scripts.js': [
    //         '<%= yeoman.dist %>/scripts/scripts.js'
    //       ]
    //     }
    //   }
    // },
    // concat: {
    //   dist: {}
    // },

    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{png,jpg,jpeg,gif}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: true,
          conservativeCollapse: true,
          collapseBooleanAttributes: true,
          removeCommentsFromCDATA: true
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: ['*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    ngtemplates: {
      dist: {
        options: {
          module: 'benkeNgApp',
          htmlmin: '<%= htmlmin.dist.options %>',
          usemin: 'scripts/scripts.js'
        },
        cwd: '<%= yeoman.app %>',
        src: 'views/{,*/}*.html',
        dest: '.tmp/templateCache.js'
      }
    },

    // ng-annotate tries to make the code safe for minification automatically
    // by using the Angular long form for dependency injection.
    ngAnnotate: {
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/concat/scripts',
          src: '*.js',
          dest: '.tmp/concat/scripts'
        }]
      }
    },

    // Replace Google CDN references
    cdnify: {
      dist: {
        html: ['<%= yeoman.dist %>/*.html']
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '.htaccess',
            '*.html',
            'images/{,*/}*.{webp}',
            'styles/fonts/{,*/}*.*'
          ]
        }, {
          expand: true,
          cwd: '.tmp/images',
          dest: '<%= yeoman.dist %>/images',
          src: ['generated/*']
        }, {
          expand: true,
          cwd: 'bower_components/bootstrap/dist',
          src: 'fonts/*',
          dest: '<%= yeoman.dist %>'
        }]
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: [
        'copy:styles'
      ],
      test: [
        'copy:styles'
      ],
      dist: [
        'copy:styles',
        'imagemin',
        'svgmin'
      ]
    },

    // Test settings
    karma: {
      unit: {
        configFile: 'test/karma.conf.js',
        singleRun: true
      }
    }
  });

  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'autoprefixer:server',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('test', [
    'clean:server',
    'wiredep',
    'concurrent:test',
    'autoprefixer',
    'connect:test',
    'karma'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'ngtemplates',
    'concat',
    'ngAnnotate',
    'copy:dist',
    'cdnify',
    'cssmin',
    'uglify',
    'filerev',
    'usemin',
    'htmlmin'
  ]);

  grunt.registerTask('default', [
    'newer:jshint',
    'test',
    'build'
  ]);
};
<commit_msg>grunt ignore delete of README.md<commit_after>// Generated on 2015-09-01 using generator-angular 0.12.1
'use strict';

// # Globbing
// for performance reasons we're only matching one level down:
// 'test/spec/{,*/}*.js'
// use this if you want to recursively match all subfolders:
// 'test/spec/**/*.js'

module.exports = function (grunt) {

  // Time how long tasks take. Can help when optimizing build times
  require('time-grunt')(grunt);

  // Automatically load required Grunt tasks
  require('jit-grunt')(grunt, {
    useminPrepare: 'grunt-usemin',
    ngtemplates: 'grunt-angular-templates',
    cdnify: 'grunt-google-cdn'
  });

  // Configurable paths for the application
  var appConfig = {
    app: require('./bower.json').appPath || 'app',
    dist: 'dist'
  };

  // Define the configuration for all the tasks
  grunt.initConfig({

    // Project settings
    yeoman: appConfig,

    // Watches files for changes and runs tasks based on the changed files
    watch: {
      bower: {
        files: ['bower.json'],
        tasks: ['wiredep']
      },
      js: {
        files: ['<%= yeoman.app %>/scripts/{,*/}*.js'],
        tasks: ['newer:jshint:all'],
        options: {
          livereload: '<%= connect.options.livereload %>'
        }
      },
      jsTest: {
        files: ['test/spec/{,*/}*.js'],
        tasks: ['newer:jshint:test', 'karma']
      },
      styles: {
        files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
        tasks: ['newer:copy:styles', 'autoprefixer']
      },
      gruntfile: {
        files: ['Gruntfile.js']
      },
      livereload: {
        options: {
          livereload: '<%= connect.options.livereload %>'
        },
        files: [
          '<%= yeoman.app %>/{,*/}*.html',
          '.tmp/styles/{,*/}*.css',
          '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
        ]
      }
    },

    // The actual grunt server settings
    connect: {
      options: {
        port: 9000,
        // Change this to '0.0.0.0' to access the server from outside.
        hostname: 'localhost',
        livereload: 35729
      },
      livereload: {
        options: {
          open: true,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect().use(
                '/app/styles',
                connect.static('./app/styles')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      test: {
        options: {
          port: 9001,
          middleware: function (connect) {
            return [
              connect.static('.tmp'),
              connect.static('test'),
              connect().use(
                '/bower_components',
                connect.static('./bower_components')
              ),
              connect.static(appConfig.app)
            ];
          }
        }
      },
      dist: {
        options: {
          open: true,
          base: '<%= yeoman.dist %>'
        }
      }
    },

    // Make sure code styles are up to par and there are no obvious mistakes
    jshint: {
      options: {
        jshintrc: '.jshintrc',
        reporter: require('jshint-stylish')
      },
      all: {
        src: [
          'Gruntfile.js',
          '<%= yeoman.app %>/scripts/{,*/}*.js'
        ]
      },
      test: {
        options: {
          jshintrc: 'test/.jshintrc'
        },
        src: ['test/spec/{,*/}*.js']
      }
    },

    // Empties folders to start fresh
    clean: {
      dist: {
        files: [{
          dot: true,
          src: [
            '.tmp',
            '<%= yeoman.dist %>/{,*/}*',
            '!<%= yeoman.dist %>/README.md{,*/}*',
            '!<%= yeoman.dist %>/server.js{,*/}*',
            '!<%= yeoman.dist %>/package.json{,*/}*',
            '!<%= yeoman.dist %>/deplist.txt{,*/}*',
            '!<%= yeoman.dist %>/.htaccess{,*/}*',
            '!<%= yeoman.dist %>/.openshift{,*/}*',
            '!<%= yeoman.dist %>/.git{,*/}*'
          ]
        }]
      },
      server: '.tmp'
    },

    // Add vendor prefixed styles
    autoprefixer: {
      options: {
        browsers: ['last 1 version']
      },
      server: {
        options: {
          map: true,
        },
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      },
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/styles/',
          src: '{,*/}*.css',
          dest: '.tmp/styles/'
        }]
      }
    },

    // Automatically inject Bower components into the app
    wiredep: {
      app: {
        src: ['<%= yeoman.app %>/index.html'],
        ignorePath:  /\.\.\//
      },
      test: {
        devDependencies: true,
        src: '<%= karma.unit.configFile %>',
        ignorePath:  /\.\.\//,
        fileTypes:{
          js: {
            block: /(([\s\t]*)\/{2}\s*?bower:\s*?(\S*))(\n|\r|.)*?(\/{2}\s*endbower)/gi,
              detect: {
                js: /'(.*\.js)'/gi
              },
              replace: {
                js: '\'{{filePath}}\','
              }
            }
          }
      }
    },

    // Renames files for browser caching purposes
    filerev: {
      dist: {
        src: [
          '<%= yeoman.dist %>/scripts/{,*/}*.js',
          '<%= yeoman.dist %>/styles/{,*/}*.css',
          '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
          '<%= yeoman.dist %>/styles/fonts/*'
        ]
      }
    },

    // Reads HTML for usemin blocks to enable smart builds that automatically
    // concat, minify and revision files. Creates configurations in memory so
    // additional tasks can operate on them
    useminPrepare: {
      html: '<%= yeoman.app %>/index.html',
      options: {
        dest: '<%= yeoman.dist %>',
        flow: {
          html: {
            steps: {
              js: ['concat', 'uglifyjs'],
              css: ['cssmin']
            },
            post: {}
          }
        }
      }
    },

    // Performs rewrites based on filerev and the useminPrepare configuration
    usemin: {
      html: ['<%= yeoman.dist %>/{,*/}*.html'],
      css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
      js: ['<%= yeoman.dist %>/scripts/{,*/}*.js'],
      options: {
        assetsDirs: [
          '<%= yeoman.dist %>',
          '<%= yeoman.dist %>/images',
          '<%= yeoman.dist %>/styles'
        ],
        patterns: {
          js: [[/(images\/[^''""]*\.(png|jpg|jpeg|gif|webp|svg))/g, 'Replacing references to images']]
        }
      }
    },

    // The following *-min tasks will produce minified files in the dist folder
    // By default, your `index.html`'s <!-- Usemin block --> will take care of
    // minification. These next options are pre-configured if you do not wish
    // to use the Usemin blocks.
    // cssmin: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/styles/main.css': [
    //         '.tmp/styles/{,*/}*.css'
    //       ]
    //     }
    //   }
    // },
    // uglify: {
    //   dist: {
    //     files: {
    //       '<%= yeoman.dist %>/scripts/scripts.js': [
    //         '<%= yeoman.dist %>/scripts/scripts.js'
    //       ]
    //     }
    //   }
    // },
    // concat: {
    //   dist: {}
    // },

    imagemin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.{png,jpg,jpeg,gif}',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    svgmin: {
      dist: {
        files: [{
          expand: true,
          cwd: '<%= yeoman.app %>/images',
          src: '{,*/}*.svg',
          dest: '<%= yeoman.dist %>/images'
        }]
      }
    },

    htmlmin: {
      dist: {
        options: {
          collapseWhitespace: true,
          conservativeCollapse: true,
          collapseBooleanAttributes: true,
          removeCommentsFromCDATA: true
        },
        files: [{
          expand: true,
          cwd: '<%= yeoman.dist %>',
          src: ['*.html'],
          dest: '<%= yeoman.dist %>'
        }]
      }
    },

    ngtemplates: {
      dist: {
        options: {
          module: 'benkeNgApp',
          htmlmin: '<%= htmlmin.dist.options %>',
          usemin: 'scripts/scripts.js'
        },
        cwd: '<%= yeoman.app %>',
        src: 'views/{,*/}*.html',
        dest: '.tmp/templateCache.js'
      }
    },

    // ng-annotate tries to make the code safe for minification automatically
    // by using the Angular long form for dependency injection.
    ngAnnotate: {
      dist: {
        files: [{
          expand: true,
          cwd: '.tmp/concat/scripts',
          src: '*.js',
          dest: '.tmp/concat/scripts'
        }]
      }
    },

    // Replace Google CDN references
    cdnify: {
      dist: {
        html: ['<%= yeoman.dist %>/*.html']
      }
    },

    // Copies remaining files to places other tasks can use
    copy: {
      dist: {
        files: [{
          expand: true,
          dot: true,
          cwd: '<%= yeoman.app %>',
          dest: '<%= yeoman.dist %>',
          src: [
            '*.{ico,png,txt}',
            '.htaccess',
            '*.html',
            'images/{,*/}*.{webp}',
            'styles/fonts/{,*/}*.*'
          ]
        }, {
          expand: true,
          cwd: '.tmp/images',
          dest: '<%= yeoman.dist %>/images',
          src: ['generated/*']
        }, {
          expand: true,
          cwd: 'bower_components/bootstrap/dist',
          src: 'fonts/*',
          dest: '<%= yeoman.dist %>'
        }]
      },
      styles: {
        expand: true,
        cwd: '<%= yeoman.app %>/styles',
        dest: '.tmp/styles/',
        src: '{,*/}*.css'
      }
    },

    // Run some tasks in parallel to speed up the build process
    concurrent: {
      server: [
        'copy:styles'
      ],
      test: [
        'copy:styles'
      ],
      dist: [
        'copy:styles',
        'imagemin',
        'svgmin'
      ]
    },

    // Test settings
    karma: {
      unit: {
        configFile: 'test/karma.conf.js',
        singleRun: true
      }
    }
  });

  grunt.registerTask('serve', 'Compile then start a connect web server', function (target) {
    if (target === 'dist') {
      return grunt.task.run(['build', 'connect:dist:keepalive']);
    }

    grunt.task.run([
      'clean:server',
      'wiredep',
      'concurrent:server',
      'autoprefixer:server',
      'connect:livereload',
      'watch'
    ]);
  });

  grunt.registerTask('server', 'DEPRECATED TASK. Use the "serve" task instead', function (target) {
    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');
    grunt.task.run(['serve:' + target]);
  });

  grunt.registerTask('test', [
    'clean:server',
    'wiredep',
    'concurrent:test',
    'autoprefixer',
    'connect:test',
    'karma'
  ]);

  grunt.registerTask('build', [
    'clean:dist',
    'wiredep',
    'useminPrepare',
    'concurrent:dist',
    'autoprefixer',
    'ngtemplates',
    'concat',
    'ngAnnotate',
    'copy:dist',
    'cdnify',
    'cssmin',
    'uglify',
    'filerev',
    'usemin',
    'htmlmin'
  ]);

  grunt.registerTask('default', [
    'newer:jshint',
    'test',
    'build'
  ]);
};
<|endoftext|>


<commit_before>module.exports = function(grunt) {
'use strict';

var moment = require('moment');
var path = require('path');

var os = (function(){
  var platform = process.platform;
  if (/^win/.test(platform))    return "windows";
  if (/^darwin/.test(platform)) return "mac";
  if (/^linux/.test(platform))  return "linux";
  return null;
})();

//------------------------------------------------------------------------------
// ShellJS
//------------------------------------------------------------------------------

require('shelljs/global');
// shelljs/global makes the following imports:
//   cwd, pwd, ls, find, cp, rm, mv, mkdir, test, cat,
//   str.to, str.toEnd, sed, grep, which, echo,
//   pushd, popd, dirs, ln, exit, env, exec, chmod,
//   tempdir, error

var shellconfig = require('shelljs').config;
shellconfig.silent = false; // hide shell cmd output?
shellconfig.fatal = true;   // stop if cmd failed?

//------------------------------------------------------------------------------
// Grunt Config
//------------------------------------------------------------------------------

var leinJarUrl = 'https://github.com/technomancy/leiningen/releases/download/2.5.0/leiningen-2.5.0-standalone.jar';

grunt.initConfig({

  // LESS conversion
  less: {
    options: {
      compress: true
    },

    default: {
      files: {
        'app/css/main.min.css': 'less/main.less'
      }
    }
  },

  // watch
  watch: {
    options: {
      atBegin: true
    },

    less: {
      files: "less/*.less",
      tasks: "less:default"
    }
  },

  curl: {
    // download leiningen jar
    'app/bin/lein.jar': leinJarUrl
  },

  'download-atom-shell': {
    version: '0.20.5',
    outputDir: 'atom-shell'
  },

});

//------------------------------------------------------------------------------
// Third-party tasks
//------------------------------------------------------------------------------

grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-curl');
grunt.loadNpmTasks('grunt-download-atom-shell');
if (os == "mac") grunt.loadNpmTasks('grunt-appdmg');
grunt.loadNpmTasks('winresourcer');

//------------------------------------------------------------------------------
// Setup Tasks
//------------------------------------------------------------------------------

grunt.registerTask('ensure-config-exists', function() {
  pushd("app");
  if (!test("-f", "config.json")) {
    cp("example.config.json", "config.json");
  }
  popd();
});

grunt.registerTask('build-lein-profile-tool', function() {
  pushd("scripts");
  if (!test('-d', "add-lein-profile")) {
    exec("git clone https://github.com/shaunlebron/add-lein-profile.git");
  }
  else {
    pushd("add-lein-profile");
    exec("git pull");
    exec("lein clean");
    exec("lein uberjar");
    cp("-f", "target/add-lein-profile-*-standalone.jar", "../../app/bin/add-lein-profile.jar");
    popd();
  }
  popd();
});

grunt.registerTask('setup', [
  'curl',
  'download-atom-shell',
  'ensure-config-exists',
  'build-lein-profile-tool'
]);

//------------------------------------------------------------------------------
// Build/Release Tasks
//------------------------------------------------------------------------------

var atomShell = {
  windows: {
    cmdlineExe:  "atom.exe",
    exeToRename: "atom.exe",
    renamedExe:  "Cuttle.exe",
    resources:   "resources",
    installExt:  "exe"
  },
  mac: {
    cmdlineExe:  "Atom.app/Contents/MacOS/Atom",
    exeToRename: "Atom.app",
    renamedExe:  "Cuttle.app",
    plist:       "Atom.app/Contents/Info.plist",
    resources:   "Atom.app/Contents/Resources",
    installExt:  "dmg"
  },
  linux: {
    cmdlineExe:  "atom",
    exeToRename: "atom",
    renamedExe:  "Cuttle",
    resources:   "resources"
  }
}[os];

grunt.registerTask('fresh-build', function() {
  exec("lein cljsbuild clean");
  exec("lein cljsbuild once");
  grunt.task.run("less");
});

grunt.registerTask('launch', function() {
  exec(path.join("atom-shell", atomShell.cmdlineExe) + " app");
});

grunt.registerTask('release', function() {

  // setup build metadata
  var tokens = cat("project.clj").split(" ");
  var build = {
    name:    tokens[1],
    version: tokens[2].replace(/"/g, "").trim(),
    date:    moment().format("YYYY-MM-DD"),
    commit:  exec("git rev-parse HEAD", {silent:true}).output.trim()
  };
  build.releaseName = build.name + "-v" + build.version + "-" + os;

  // determine build paths
  var paths = {
    atom: "atom-shell",
    builds: "builds",
    devApp: "app",
    rootPkg: "package.json",
  };
  paths.release = paths.builds + '/' + build.releaseName;
  paths.resources = paths.release + '/' + atomShell.resources;
  paths.install = paths.release + "." + atomShell.installExt;
  paths.releaseApp = paths.resources + "/" + paths.devApp;
  paths.devPkg = paths.devApp + "/package.json";
  paths.releasePkg = paths.releaseApp + "/package.json";
  paths.releaseCfg = paths.releaseApp + "/config.json";
  paths.exeToRename = paths.release + "/" + atomShell.exeToRename;
  paths.renamedExe = paths.release + "/" + atomShell.renamedExe;

  mkdir('-p', paths.builds);
  rm('-rf', paths.install, paths.release);

  cp('-r', paths.atom+"/", paths.release);
  cp('-r', paths.devApp, paths.resources);

  rm('-f', paths.releaseApp + "/config.json");

  cp('-f', paths.rootPkg, paths.releaseApp);
  pushd(paths.releaseApp);
  exec('npm install --production');
  popd();
  cp('-f', paths.devPkg, paths.releaseApp);

  var pkg = grunt.file.readJSON(paths.releasePkg);
  pkg["version"] = build.version;
  pkg["build-commit"] = build.commit;
  pkg["build-date"] = build.date;
  JSON.stringify(pkg, null, "  ").to(paths.releasePkg);

  rm('-f', paths.releaseCfg);

  switch (os) {
    case "mac":     finalizeMacRelease(paths); break;
    case "linux":   finalizeLinuxRelease(paths); break;
    case "windows": finalizeWindowsRelease(paths); break;
  }

});

function finalizeMacRelease(paths) {
  var plist = __dirname + "/" + paths.release + "/" + atomShell.plist;
  grunt.log.writeln(plist);

  exec("defaults write " + plist + " CFBundleIconFile app/img/cuttle-logo.icns");
  exec("defaults write " + plist + " CFBundleDisplayName Cuttle");
  exec("defaults write " + plist + " CFBundleName Cuttle");
  exec("defaults write " + plist + " CFBundleIdentifier org.cuttle");

  mv(paths.exeToRename, paths.renamedExe);
  var app = paths.renamedExe;

  grunt.config.set("appdmg", {
    options: {
      "title": "Cuttle",
      "background": "scripts/dmg/background.png",
      "icon-size": 80,
      "contents": [
        { "x": 448, "y": 344, "type": "link", "path": "/Applications" },
        { "x": 192, "y": 344, "type": "file", "path": app }
      ]
    },
    target: {
      dest: paths.install
    }
  });
  grunt.task.run("appdmg");
}

function finalizeLinuxRelease(paths) {
  mv(paths.exeToRename, paths.renamedExe);
}

function finalizeWindowsRelease(paths) {
  mv(paths.exeToRename, paths.renamedExe);
  var app = paths.renamedExe;

  grunt.config.set("winresourcer", {
    main: {
      operation: "Update",
      exeFile: app,
      resourceType: "Icongroup",
      resourceName: "1",
      resourceFile: "app/img/cuttle-logo.ico"
    }
  });
  grunt.task.run("winresourcer");

  grunt.config.set("makensis", {
    releaseDir: paths.release,
    outFile: paths.install
  });
  grunt.task.run("makensis");
}

grunt.registerTask('makensis', function() {
  var config = grunt.config.get("makensis");
  exec(["makensis",
    "/DPRODUCT_VERSION=",
    "/DRELEASE_DIR=../"+config.releaseDir,
    "/DOUTFILE=../"+config.outFile,
    "scripts/build-windows-exe.nsi"].join(" "));
});

//------------------------------------------------------------------------------
// Default Task
//------------------------------------------------------------------------------

grunt.registerTask('default', ['watch']);

// end module.exports
};
<commit_msg>pass build info to finalize release functions (forgot to add version to makensis)<commit_after>module.exports = function(grunt) {
'use strict';

var moment = require('moment');
var path = require('path');

var os = (function(){
  var platform = process.platform;
  if (/^win/.test(platform))    return "windows";
  if (/^darwin/.test(platform)) return "mac";
  if (/^linux/.test(platform))  return "linux";
  return null;
})();

//------------------------------------------------------------------------------
// ShellJS
//------------------------------------------------------------------------------

require('shelljs/global');
// shelljs/global makes the following imports:
//   cwd, pwd, ls, find, cp, rm, mv, mkdir, test, cat,
//   str.to, str.toEnd, sed, grep, which, echo,
//   pushd, popd, dirs, ln, exit, env, exec, chmod,
//   tempdir, error

var shellconfig = require('shelljs').config;
shellconfig.silent = false; // hide shell cmd output?
shellconfig.fatal = true;   // stop if cmd failed?

//------------------------------------------------------------------------------
// Grunt Config
//------------------------------------------------------------------------------

var leinJarUrl = 'https://github.com/technomancy/leiningen/releases/download/2.5.0/leiningen-2.5.0-standalone.jar';

grunt.initConfig({

  // LESS conversion
  less: {
    options: {
      compress: true
    },

    default: {
      files: {
        'app/css/main.min.css': 'less/main.less'
      }
    }
  },

  // watch
  watch: {
    options: {
      atBegin: true
    },

    less: {
      files: "less/*.less",
      tasks: "less:default"
    }
  },

  curl: {
    // download leiningen jar
    'app/bin/lein.jar': leinJarUrl
  },

  'download-atom-shell': {
    version: '0.20.5',
    outputDir: 'atom-shell'
  },

});

//------------------------------------------------------------------------------
// Third-party tasks
//------------------------------------------------------------------------------

grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-curl');
grunt.loadNpmTasks('grunt-download-atom-shell');
if (os == "mac") grunt.loadNpmTasks('grunt-appdmg');
grunt.loadNpmTasks('winresourcer');

//------------------------------------------------------------------------------
// Setup Tasks
//------------------------------------------------------------------------------

grunt.registerTask('ensure-config-exists', function() {
  pushd("app");
  if (!test("-f", "config.json")) {
    cp("example.config.json", "config.json");
  }
  popd();
});

grunt.registerTask('build-lein-profile-tool', function() {
  pushd("scripts");
  if (!test('-d', "add-lein-profile")) {
    exec("git clone https://github.com/shaunlebron/add-lein-profile.git");
  }
  else {
    pushd("add-lein-profile");
    exec("git pull");
    exec("lein clean");
    exec("lein uberjar");
    cp("-f", "target/add-lein-profile-*-standalone.jar", "../../app/bin/add-lein-profile.jar");
    popd();
  }
  popd();
});

grunt.registerTask('setup', [
  'curl',
  'download-atom-shell',
  'ensure-config-exists',
  'build-lein-profile-tool'
]);

//------------------------------------------------------------------------------
// Build/Release Tasks
//------------------------------------------------------------------------------

var atomShell = {
  windows: {
    cmdlineExe:  "atom.exe",
    exeToRename: "atom.exe",
    renamedExe:  "Cuttle.exe",
    resources:   "resources",
    installExt:  "exe"
  },
  mac: {
    cmdlineExe:  "Atom.app/Contents/MacOS/Atom",
    exeToRename: "Atom.app",
    renamedExe:  "Cuttle.app",
    plist:       "Atom.app/Contents/Info.plist",
    resources:   "Atom.app/Contents/Resources",
    installExt:  "dmg"
  },
  linux: {
    cmdlineExe:  "atom",
    exeToRename: "atom",
    renamedExe:  "Cuttle",
    resources:   "resources"
  }
}[os];

grunt.registerTask('fresh-build', function() {
  exec("lein cljsbuild clean");
  exec("lein cljsbuild once");
  grunt.task.run("less");
});

grunt.registerTask('launch', function() {
  exec(path.join("atom-shell", atomShell.cmdlineExe) + " app");
});

grunt.registerTask('release', function() {

  // setup build metadata
  var tokens = cat("project.clj").split(" ");
  var build = {
    name:    tokens[1],
    version: tokens[2].replace(/"/g, "").trim(),
    date:    moment().format("YYYY-MM-DD"),
    commit:  exec("git rev-parse HEAD", {silent:true}).output.trim()
  };
  build.releaseName = build.name + "-v" + build.version + "-" + os;

  // determine build paths
  var paths = {
    atom: "atom-shell",
    builds: "builds",
    devApp: "app",
    rootPkg: "package.json",
  };
  paths.release = paths.builds + '/' + build.releaseName;
  paths.resources = paths.release + '/' + atomShell.resources;
  paths.install = paths.release + "." + atomShell.installExt;
  paths.releaseApp = paths.resources + "/" + paths.devApp;
  paths.devPkg = paths.devApp + "/package.json";
  paths.releasePkg = paths.releaseApp + "/package.json";
  paths.releaseCfg = paths.releaseApp + "/config.json";
  paths.exeToRename = paths.release + "/" + atomShell.exeToRename;
  paths.renamedExe = paths.release + "/" + atomShell.renamedExe;

  mkdir('-p', paths.builds);
  rm('-rf', paths.install, paths.release);

  cp('-r', paths.atom+"/", paths.release);
  cp('-r', paths.devApp, paths.resources);

  rm('-f', paths.releaseApp + "/config.json");

  cp('-f', paths.rootPkg, paths.releaseApp);
  pushd(paths.releaseApp);
  exec('npm install --production');
  popd();
  cp('-f', paths.devPkg, paths.releaseApp);

  var pkg = grunt.file.readJSON(paths.releasePkg);
  pkg["version"] = build.version;
  pkg["build-commit"] = build.commit;
  pkg["build-date"] = build.date;
  JSON.stringify(pkg, null, "  ").to(paths.releasePkg);

  rm('-f', paths.releaseCfg);

  switch (os) {
    case "mac":     finalizeMacRelease(build, paths); break;
    case "linux":   finalizeLinuxRelease(build, paths); break;
    case "windows": finalizeWindowsRelease(build, paths); break;
  }

});

function finalizeMacRelease(build, paths) {
  var plist = __dirname + "/" + paths.release + "/" + atomShell.plist;
  grunt.log.writeln(plist);

  exec("defaults write " + plist + " CFBundleIconFile app/img/cuttle-logo.icns");
  exec("defaults write " + plist + " CFBundleDisplayName Cuttle");
  exec("defaults write " + plist + " CFBundleName Cuttle");
  exec("defaults write " + plist + " CFBundleIdentifier org.cuttle");

  mv(paths.exeToRename, paths.renamedExe);
  var app = paths.renamedExe;

  grunt.config.set("appdmg", {
    options: {
      "title": "Cuttle",
      "background": "scripts/dmg/background.png",
      "icon-size": 80,
      "contents": [
        { "x": 448, "y": 344, "type": "link", "path": "/Applications" },
        { "x": 192, "y": 344, "type": "file", "path": app }
      ]
    },
    target: {
      dest: paths.install
    }
  });
  grunt.task.run("appdmg");
}

function finalizeLinuxRelease(build, paths) {
  mv(paths.exeToRename, paths.renamedExe);
}

function finalizeWindowsRelease(build, paths) {
  mv(paths.exeToRename, paths.renamedExe);
  var app = paths.renamedExe;

  grunt.config.set("winresourcer", {
    main: {
      operation: "Update",
      exeFile: app,
      resourceType: "Icongroup",
      resourceName: "1",
      resourceFile: "app/img/cuttle-logo.ico"
    }
  });
  grunt.task.run("winresourcer");

  grunt.config.set("makensis", {
    version:    build.version,
    releaseDir: paths.release,
    outFile:    paths.install
  });
  grunt.task.run("makensis");
}

grunt.registerTask('makensis', function() {
  var config = grunt.config.get("makensis");
  exec(["makensis",
    "/DPRODUCT_VERSION=" + config.version,
    "/DRELEASE_DIR=../" + config.releaseDir,
    "/DOUTFILE=../" + config.outFile,
    "scripts/build-windows-exe.nsi"].join(" "));
});

//------------------------------------------------------------------------------
// Default Task
//------------------------------------------------------------------------------

grunt.registerTask('default', ['watch']);

// end module.exports
};
<|endoftext|>