<commit_before>// @flow
import * as I from 'immutable'
import * as FsGen from '../actions/fs-gen'
import * as Constants from '../constants/fs'
import * as Types from '../constants/types/fs'

const initialState = Constants.makeState()

export default function(state: Types.State = initialState, action: FsGen.Actions) {
  switch (action.type) {
    case FsGen.resetStore:
      return initialState
    case FsGen.filePreviewLoaded:
      return state.updateIn(['pathItems', action.payload.path], (original: Types.PathItem) => {
        const {meta} = action.payload
        if (original.type !== 'file' || meta.type !== 'file') {
          return meta
        }

        return Constants.shouldUseOldMimeType(original, meta) ? meta.set('mimeType', original.mimeType) : meta
      })
    case FsGen.folderListLoaded: {
      let toRemove = []
      const toMerge = action.payload.pathItems.map((item, path) => {
        const original = state.pathItems.get(path)

        if (original && original.type === 'file' && item.type === 'file') {
          return Constants.shouldUseOldMimeType(original, item)
            ? item.set('mimeType', original.mimeType)
            : item
        }

        if (item.type !== 'folder') return item
        if (!original || original.type !== 'folder') return item

        // Make flow happy by referencing them with a new name that's
        // explicitly typed.
        const originalFolder: Types.FolderPathItem = original
        let newItem: Types.FolderPathItem = item

        if (originalFolder.progress === 'loaded' && newItem.progress === 'pending') {
          // We don't want to override a loaded folder into pending, because
          // otherwise next time user goes into that folder we'd show
          // placeholders. We also don't want to simply use the original
          // PathItem, since it's possible some metadata has updated. So use
          // the new item, but reuse children, progress, and tlfMeta fields.
          if (originalFolder.type === 'folder' && newItem.type === 'folder') {
            // make flow happy
            newItem = newItem
              .set('children', originalFolder.children)
              .set('tlfMeta', originalFolder.tlfMeta)
              .set('badgeCount', originalFolder.badgeCount)
              .set('progress', 'loaded')
          }
        }

        toRemove = toRemove.concat(
          originalFolder.children
            .filter(child => !newItem.children.includes(child))
            .toArray()
            .map(name => Types.pathConcat(path, name))
        )

        // Since `folderListLoaded`, `favoritesLoaded`, and `loadResetsResult`
        // can change `pathItems`, we need to make sure that neither one
        // clobbers the others' work.
        return newItem
          .set('badgeCount', originalFolder.badgeCount)
          .set('tlfMeta', originalFolder.tlfMeta)
          .set('favoriteChildren', originalFolder.favoriteChildren)
      })
      toRemove.length && console.log({msg: 'Removing entries in state.fs.pathItems', toRemove: toRemove})
      return state
        .set('pathItems', state.pathItems.filter((item, path) => !toRemove.includes(path)))
        .mergeIn(['pathItems'], toMerge)
        .update('loadingPaths', loadingPaths => loadingPaths.delete(action.payload.path))
    }
    case FsGen.folderListLoad:
      return state.update('loadingPaths', loadingPaths => loadingPaths.add(action.payload.path))
    case FsGen.favoritesLoad:
      return state
    case FsGen.favoritesLoaded:
      const toMerge = action.payload.folders.mapEntries(([path, item]) => {
        // We ForceType because Flow keeps thinking this is a _PathItem not a FolderPathItem.
        const original: $ForceType = state.pathItems.get(path, Constants.makeFolder({name: item.name}))
        // This cannot happen, but it's needed to make Flow happy.
        if (original.type !== 'folder') return [path, original]

        return [
          path,
          // Since `folderListLoaded`, `favoritesLoaded`, and `loadResetsResult`
          // can change `pathItems`, we need to make sure that neither one
          // clobbers the others' work.
          original
            .set('badgeCount', item.badgeCount)
            .set('tlfMeta', item.tlfMeta)
            .set('favoriteChildren', item.favoriteChildren),
        ]
      })
      return state.mergeIn(['pathItems'], toMerge)
    case FsGen.sortSetting:
      const {path, sortSetting} = action.payload
      return state.setIn(['pathUserSettings', path, 'sort'], sortSetting)
    case FsGen.downloadStarted: {
      const {key, path, localPath, intent, opID} = action.payload
      const entryType =
        action.payload.entryType ||
        (() => {
          const item = state.pathItems.get(path)
          return item ? item.type : 'unknown'
        })()
      return state.setIn(
        ['downloads', key],
        Constants.makeDownload({
          meta: Constants.makeDownloadMeta({
            entryType,
            intent,
            path,
            localPath,
            opID,
          }),
          state: Constants.makeDownloadState({
            completePortion: 0,
            isDone: false,
            startedAt: Date.now(),
          }),
        })
      )
    }
    case FsGen.downloadProgress: {
      const {key, completePortion, endEstimate} = action.payload
      return state.updateIn(['downloads', key, 'state'], (original: Types.DownloadState) =>
        original.set('completePortion', completePortion).set('endEstimate', endEstimate)
      )
    }
    case FsGen.downloadFinished: {
      const {key, error} = action.payload
      return state.updateIn(['downloads', key, 'state'], (original: Types.DownloadState) =>
        original.set('isDone', true).set('error', error)
      )
    }
    case FsGen.dismissDownload: {
      return state.removeIn(['downloads', action.payload.key])
    }
    case FsGen.uploadStarted:
      return state.updateIn(['uploads', 'writingToJournal'], writingToJournal =>
        writingToJournal.add(action.payload.path)
      )
    case FsGen.uploadWritingFinished: {
      const {path, error} = action.payload
      const withError = error ? state.setIn(['uploads', 'errors', path], error) : state
      return withError.updateIn(['uploads', 'writingToJournal'], writingToJournal =>
        writingToJournal.remove(action.payload.path)
      )
    }
    case FsGen.journalUpdate: {
      const {syncingPaths, totalSyncingBytes, endEstimate} = action.payload
      return state
        .setIn(['uploads', 'syncingPaths'], I.Set(syncingPaths))
        .setIn(['uploads', 'totalSyncingBytes'], totalSyncingBytes)
        .setIn(['uploads', 'endEstimate'], endEstimate || undefined)
    }
    case FsGen.fuseStatusResult:
      return state.merge({fuseStatus: action.payload.status})
    case FsGen.setFlags:
      return state.mergeIn(['flags'], action.payload)
    case FsGen.installFuse:
      return state.mergeIn(['flags'], {fuseInstalling: true, kextPermissionError: false})
    case FsGen.installFuseResult:
      // To prevent races, we overlap flags set to true. So we don't unset the
      // fuseInstalling flag here.
      return state.mergeIn(['flags'], action.payload)
    case FsGen.installKBFS:
      return state.mergeIn(['flags'], {kbfsInstalling: true})
    case FsGen.localHTTPServerInfo:
      return state.set('localHTTPServerInfo', Constants.makeLocalHTTPServer(action.payload))
    case FsGen.favoriteIgnore:
      return state.mergeIn(['pathItems', Types.pathToString(action.payload.path), 'tlfMeta'], {
        isIgnored: true,
      })
    case FsGen.favoriteIgnoreError:
      return state.mergeIn(['pathItems', Types.pathToString(action.payload.path), 'tlfMeta'], {
        isIgnored: false,
      })
    case FsGen.mimeTypeLoaded:
      return state.updateIn(
        ['pathItems', action.payload.path],
        pathItem => (pathItem.type === 'file' ? pathItem.set('mimeType', action.payload.mimeType) : pathItem)
      )
    case FsGen.newFolderRow:
      const {parentPath} = action.payload
      const parentPathItem = state.pathItems.get(parentPath, Constants.unknownPathItem)
      if (parentPathItem.type !== 'folder') {
        console.warn(`bad parentPath: ${parentPathItem.type}`)
        return state
      }
      let newFolderName = 'New Folder'
      for (let i = 2; parentPathItem.children.has(newFolderName); ++i) {
        newFolderName = `New Folder ${i}`
      }

      return state.mergeIn(
        ['edits'],
        [
          [
            Constants.makeEditID(),
            Constants.makeNewFolder({
              name: newFolderName,
              hint: newFolderName,
              parentPath,
            }),
          ],
        ]
      )
    case FsGen.newFolderName:
      // $FlowFixMe
      return state.updateIn(
        ['edits', action.payload.editID],
        editItem => editItem && editItem.set('name', action.payload.name)
      )
    case FsGen.editSuccess:
    case FsGen.discardEdit:
      // $FlowFixMe
      return state.removeIn(['edits', action.payload.editID])
    case FsGen.editFailed:
      // $FlowFixMe
      return state.setIn(['edits', action.payload.editID, 'status'], 'failed')
    case FsGen.filePreviewLoad:
    case FsGen.cancelDownload:
    case FsGen.download:
    case FsGen.openInFileUI:
    case FsGen.fuseStatus:
    case FsGen.uninstallKBFSConfirm:
    case FsGen.fsActivity:
    case FsGen.setupFSHandlers:
    case FsGen.openSecurityPreferences:
    case FsGen.refreshLocalHTTPServerInfo:
    case FsGen.shareNative:
    case FsGen.saveMedia:
    case FsGen.openFinderPopup:
    case FsGen.mimeTypeLoad:
    case FsGen.openPathItem:
    case FsGen.commitEdit:
    case FsGen.letResetUserBackIn:
    case FsGen.pickAndUpload:
    case FsGen.upload:
      return state
    default:
      /*::
      declare var ifFlowErrorsHereItsCauseYouDidntHandleAllActionTypesAbove: (action: empty) => any
      ifFlowErrorsHereItsCauseYouDidntHandleAllActionTypesAbove(action);
      */
      return state
  }
}
<commit_msg>fs: reducer cleanup (#12729)<commit_after>// @flow
import * as I from 'immutable'
import * as FsGen from '../actions/fs-gen'
import * as Constants from '../constants/fs'
import * as Types from '../constants/types/fs'

const initialState = Constants.makeState()

export default function(state: Types.State = initialState, action: FsGen.Actions) {
  switch (action.type) {
    case FsGen.resetStore:
      return initialState
    case FsGen.filePreviewLoaded:
      return state.updateIn(['pathItems', action.payload.path], (original: Types.PathItem) => {
        const {meta} = action.payload
        if (original.type !== 'file' || meta.type !== 'file') {
          return meta
        }

        return Constants.shouldUseOldMimeType(original, meta) ? meta.set('mimeType', original.mimeType) : meta
      })
    case FsGen.folderListLoaded: {
      let toRemove = new Set()
      const toMerge = action.payload.pathItems.map((item, path) => {
        const original = state.pathItems.get(path)

        if (original && original.type === 'file' && item.type === 'file') {
          return Constants.shouldUseOldMimeType(original, item)
            ? item.set('mimeType', original.mimeType)
            : item
        }

        if (item.type !== 'folder') return item
        if (!original || original.type !== 'folder') return item

        // Make flow happy by referencing them with a new name that's
        // explicitly typed.
        const originalFolder: Types.FolderPathItem = original
        let newItem: Types.FolderPathItem = item

        if (originalFolder.progress === 'loaded' && item.progress === 'pending') {
          // We don't want to override a loaded folder into pending, because
          // otherwise next time user goes into that folder we'd show
          // placeholders. We also don't want to simply use the original
          // PathItem, since it's possible some metadata has updated. So use
          // the new item, but reuse children, progress, and tlfMeta fields.
          if (originalFolder.type === 'folder' && item.type === 'folder') {
            // make flow happy
            newItem = item.set('children', originalFolder.children).set('progress', 'loaded')
          }
        }

        originalFolder.children.forEach(
          name => !newItem.children.includes(name) && toRemove.add(Types.pathConcat(path, name))
        )

        // Since `folderListLoaded`, `favoritesLoaded`, and `loadResetsResult`
        // can change `pathItems`, we need to make sure that neither one
        // clobbers the others' work.
        return newItem.withMutations(i =>
          i
            .set('badgeCount', originalFolder.badgeCount)
            .set('tlfMeta', originalFolder.tlfMeta)
            .set('favoriteChildren', originalFolder.favoriteChildren)
        )
      })
      return state
        .set(
          'pathItems',
          state.pathItems.withMutations(pathItems => pathItems.deleteAll(toRemove).merge(toMerge))
        )
        .update('loadingPaths', loadingPaths => loadingPaths.delete(action.payload.path))
    }
    case FsGen.folderListLoad:
      return state.update('loadingPaths', loadingPaths => loadingPaths.add(action.payload.path))
    case FsGen.favoritesLoad:
      return state
    case FsGen.favoritesLoaded:
      const toMerge = action.payload.folders.mapEntries(([path, item]) => {
        // We ForceType because Flow keeps thinking this is a _PathItem not a FolderPathItem.
        const original: $ForceType = state.pathItems.get(path, Constants.makeFolder({name: item.name}))
        // This cannot happen, but it's needed to make Flow happy.
        if (original.type !== 'folder') return [path, original]

        return [
          path,
          // Since `folderListLoaded`, `favoritesLoaded`, and `loadResetsResult`
          // can change `pathItems`, we need to make sure that neither one
          // clobbers the others' work.
          original.withMutations(i =>
            i
              .set('badgeCount', item.badgeCount)
              .set('tlfMeta', item.tlfMeta)
              .set('favoriteChildren', item.favoriteChildren)
          ),
        ]
      })
      return state.mergeIn(['pathItems'], toMerge)
    case FsGen.sortSetting:
      const {path, sortSetting} = action.payload
      return state.setIn(['pathUserSettings', path, 'sort'], sortSetting)
    case FsGen.downloadStarted: {
      const {key, path, localPath, intent, opID} = action.payload
      const entryType =
        action.payload.entryType ||
        (() => {
          const item = state.pathItems.get(path)
          return item ? item.type : 'unknown'
        })()
      return state.setIn(
        ['downloads', key],
        Constants.makeDownload({
          meta: Constants.makeDownloadMeta({
            entryType,
            intent,
            path,
            localPath,
            opID,
          }),
          state: Constants.makeDownloadState({
            completePortion: 0,
            isDone: false,
            startedAt: Date.now(),
          }),
        })
      )
    }
    case FsGen.downloadProgress: {
      const {key, completePortion, endEstimate} = action.payload
      return state.updateIn(['downloads', key, 'state'], (original: Types.DownloadState) =>
        original.set('completePortion', completePortion).set('endEstimate', endEstimate)
      )
    }
    case FsGen.downloadFinished: {
      const {key, error} = action.payload
      return state.updateIn(['downloads', key, 'state'], (original: Types.DownloadState) =>
        original.set('isDone', true).set('error', error)
      )
    }
    case FsGen.dismissDownload: {
      return state.removeIn(['downloads', action.payload.key])
    }
    case FsGen.uploadStarted:
      return state.updateIn(['uploads', 'writingToJournal'], writingToJournal =>
        writingToJournal.add(action.payload.path)
      )
    case FsGen.uploadWritingFinished: {
      const {path, error} = action.payload
      const withError = error ? state.setIn(['uploads', 'errors', path], error) : state
      return withError.updateIn(['uploads', 'writingToJournal'], writingToJournal =>
        writingToJournal.remove(action.payload.path)
      )
    }
    case FsGen.journalUpdate: {
      const {syncingPaths, totalSyncingBytes, endEstimate} = action.payload
      return state
        .setIn(['uploads', 'syncingPaths'], I.Set(syncingPaths))
        .setIn(['uploads', 'totalSyncingBytes'], totalSyncingBytes)
        .setIn(['uploads', 'endEstimate'], endEstimate || undefined)
    }
    case FsGen.fuseStatusResult:
      return state.merge({fuseStatus: action.payload.status})
    case FsGen.setFlags:
      return state.mergeIn(['flags'], action.payload)
    case FsGen.installFuse:
      return state.mergeIn(['flags'], {fuseInstalling: true, kextPermissionError: false})
    case FsGen.installFuseResult:
      // To prevent races, we overlap flags set to true. So we don't unset the
      // fuseInstalling flag here.
      return state.mergeIn(['flags'], action.payload)
    case FsGen.installKBFS:
      return state.mergeIn(['flags'], {kbfsInstalling: true})
    case FsGen.localHTTPServerInfo:
      return state.set('localHTTPServerInfo', Constants.makeLocalHTTPServer(action.payload))
    case FsGen.favoriteIgnore:
      return state.mergeIn(['pathItems', Types.pathToString(action.payload.path), 'tlfMeta'], {
        isIgnored: true,
      })
    case FsGen.favoriteIgnoreError:
      return state.mergeIn(['pathItems', Types.pathToString(action.payload.path), 'tlfMeta'], {
        isIgnored: false,
      })
    case FsGen.mimeTypeLoaded:
      return state.updateIn(
        ['pathItems', action.payload.path],
        pathItem => (pathItem.type === 'file' ? pathItem.set('mimeType', action.payload.mimeType) : pathItem)
      )
    case FsGen.newFolderRow:
      const {parentPath} = action.payload
      const parentPathItem = state.pathItems.get(parentPath, Constants.unknownPathItem)
      if (parentPathItem.type !== 'folder') {
        console.warn(`bad parentPath: ${parentPathItem.type}`)
        return state
      }
      let newFolderName = 'New Folder'
      for (let i = 2; parentPathItem.children.has(newFolderName); ++i) {
        newFolderName = `New Folder ${i}`
      }

      return state.mergeIn(
        ['edits'],
        [
          [
            Constants.makeEditID(),
            Constants.makeNewFolder({
              name: newFolderName,
              hint: newFolderName,
              parentPath,
            }),
          ],
        ]
      )
    case FsGen.newFolderName:
      // $FlowFixMe
      return state.updateIn(
        ['edits', action.payload.editID],
        editItem => editItem && editItem.set('name', action.payload.name)
      )
    case FsGen.editSuccess:
    case FsGen.discardEdit:
      // $FlowFixMe
      return state.removeIn(['edits', action.payload.editID])
    case FsGen.editFailed:
      // $FlowFixMe
      return state.setIn(['edits', action.payload.editID, 'status'], 'failed')
    case FsGen.filePreviewLoad:
    case FsGen.cancelDownload:
    case FsGen.download:
    case FsGen.openInFileUI:
    case FsGen.fuseStatus:
    case FsGen.uninstallKBFSConfirm:
    case FsGen.fsActivity:
    case FsGen.setupFSHandlers:
    case FsGen.openSecurityPreferences:
    case FsGen.refreshLocalHTTPServerInfo:
    case FsGen.shareNative:
    case FsGen.saveMedia:
    case FsGen.openFinderPopup:
    case FsGen.mimeTypeLoad:
    case FsGen.openPathItem:
    case FsGen.commitEdit:
    case FsGen.letResetUserBackIn:
    case FsGen.pickAndUpload:
    case FsGen.upload:
      return state
    default:
      /*::
      declare var ifFlowErrorsHereItsCauseYouDidntHandleAllActionTypesAbove: (action: empty) => any
      ifFlowErrorsHereItsCauseYouDidntHandleAllActionTypesAbove(action);
      */
      return state
  }
}
<|endoftext|>


<commit_before>(function(){

  /**
   * @private
   * @param {String} eventName
   * @param {Function} handler
   */
  function _removeEventListener(eventName, handler) {
    if (this.__eventListeners[eventName]) {
      if (handler) {
        fabric.util.removeFromArray(this.__eventListeners[eventName], handler);
      }
      else {
        this.__eventListeners[eventName].length = 0;
      }
    }
  }

  /**
   * Observes specified event
   * @deprecated `observe` deprecated since 0.8.34 (use `on` instead)
   * @memberOf fabric.Observable
   * @alias on
   * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
   * @param {Function} handler Function that receives a notification when an event of the specified type occurs
   * @return {Self} thisArg
   * @chainable
   */
  function observe(eventName, handler) {
    if (!this.__eventListeners) {
      this.__eventListeners = { };
    }
    // one object with key/value pairs was passed
    if (arguments.length === 1) {
      for (var prop in eventName) {
        this.on(prop, eventName[prop]);
      }
    }
    else {
      if (!this.__eventListeners[eventName]) {
        this.__eventListeners[eventName] = [ ];
      }
      this.__eventListeners[eventName].push(handler);
    }
    return this;
  }

  /**
   * Stops event observing for a particular event handler
   * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead)
   * @memberOf fabric.Observable
   * @alias off
   * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
   * @param {Function} handler Function to be deleted from EventListeners
   * @return {Self} thisArg
   * @chainable
   */
  function stopObserving(eventName, handler) {
    if (!this.__eventListeners) {
      this.__eventListeners = { };
    }
    // one object with key/value pairs was passed
    if (arguments.length === 1 && typeof arguments[0] === 'object') {
      for (var prop in eventName) {
        _removeEventListener.call(this, prop, eventName[prop]);
      }
    }
    else {
      _removeEventListener.call(this, eventName, handler);
    }
    return this;
  }

  /**
   * Fires event with an optional options object
   * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead)
   * @memberOf fabric.Observable
   * @alias trigger
   * @param {String} eventName Event name to fire
   * @param {Object} [options] Options object
   * @return {Self} thisArg
   * @chainable
   */
  function fire(eventName, options) {
    if (!this.__eventListeners) {
      this.__eventListeners = { };
    }
    var listenersForEvent = this.__eventListeners[eventName];
    if (!listenersForEvent) return;
    for (var i = 0, len = listenersForEvent.length; i < len; i++) {
      // avoiding try/catch for perf. reasons
      listenersForEvent[i].call(this, options || { });
    }
    return this;
  }

  /**
   * @namespace fabric.Observable
   */
  fabric.Observable = {
    observe: observe,
    stopObserving: stopObserving,
    fire: fire,

    on: observe,
    off: stopObserving,
    trigger: fire
  };
})();
<commit_msg>Some small changes in fabric.Observable<commit_after>(function(){

  /**
   * @private
   * @param {String} eventName
   * @param {Function} handler
   */
  function _removeEventListener(eventName, handler) {
    if (!this.__eventListeners[eventName]) return;

    if (handler) {
      fabric.util.removeFromArray(this.__eventListeners[eventName], handler);
    }
    else {
      this.__eventListeners[eventName].length = 0;
    }
  }

  /**
   * Observes specified event
   * @deprecated `observe` deprecated since 0.8.34 (use `on` instead)
   * @memberOf fabric.Observable
   * @alias on
   * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
   * @param {Function} handler Function that receives a notification when an event of the specified type occurs
   * @return {Self} thisArg
   * @chainable
   */
  function observe(eventName, handler) {
    if (!this.__eventListeners) {
      this.__eventListeners = { };
    }
    // one object with key/value pairs was passed
    if (arguments.length === 1) {
      for (var prop in eventName) {
        this.on(prop, eventName[prop]);
      }
    }
    else {
      if (!this.__eventListeners[eventName]) {
        this.__eventListeners[eventName] = [ ];
      }
      this.__eventListeners[eventName].push(handler);
    }
    return this;
  }

  /**
   * Stops event observing for a particular event handler
   * @deprecated `stopObserving` deprecated since 0.8.34 (use `off` instead)
   * @memberOf fabric.Observable
   * @alias off
   * @param {String|Object} eventName Event name (eg. 'after:render') or object with key/value pairs (eg. {'after:render': handler, 'selection:cleared': handler})
   * @param {Function} handler Function to be deleted from EventListeners
   * @return {Self} thisArg
   * @chainable
   */
  function stopObserving(eventName, handler) {
    if (!this.__eventListeners) return;

    // one object with key/value pairs was passed
    if (arguments.length === 1 && typeof arguments[0] === 'object') {
      for (var prop in eventName) {
        _removeEventListener.call(this, prop, eventName[prop]);
      }
    }
    else {
      _removeEventListener.call(this, eventName, handler);
    }
    return this;
  }

  /**
   * Fires event with an optional options object
   * @deprecated `fire` deprecated since 1.0.7 (use `trigger` instead)
   * @memberOf fabric.Observable
   * @alias trigger
   * @param {String} eventName Event name to fire
   * @param {Object} [options] Options object
   * @return {Self} thisArg
   * @chainable
   */
  function fire(eventName, options) {
    if (!this.__eventListeners) return;

    var listenersForEvent = this.__eventListeners[eventName];
    if (!listenersForEvent) return;
    for (var i = 0, len = listenersForEvent.length; i < len; i++) {
      // avoiding try/catch for perf. reasons
      listenersForEvent[i].call(this, options || { });
    }
    return this;
  }

  /**
   * @namespace fabric.Observable
   */
  fabric.Observable = {
    observe: observe,
    stopObserving: stopObserving,
    fire: fire,

    on: observe,
    off: stopObserving,
    trigger: fire
  };
})();
<|endoftext|>


<commit_before>defineSuite([
        'Core/EllipsoidGeometry',
        'Core/Cartesian3',
        'Core/Math',
        'Core/VertexFormat',
        'Specs/createPackableSpecs'
    ], function(
        EllipsoidGeometry,
        Cartesian3,
        CesiumMath,
        VertexFormat,
        createPackableSpecs) {
    'use strict';

    it('constructor rounds floating-point slicePartitions', function() {
        var m = new EllipsoidGeometry({
            slicePartitions: 3.5,
            stackPartitions: 3
        });
        expect(m._slicePartitions).toEqual(4);
    });

    it('constructor rounds floating-point stackPartitions', function() {
        var m = new EllipsoidGeometry({
            slicePartitions: 3,
            stackPartitions: 3.5
        });
        expect(m._stackPartitions).toEqual(4);
    });

    it('constructor throws with invalid slicePartitions', function() {
        expect(function() {
            return new EllipsoidGeometry({
                slicePartitions : -1
            });
        }).toThrowDeveloperError();
    });

    it('constructor throws with invalid stackPartitions', function() {
        expect(function() {
            return new EllipsoidGeometry({
                stackPartitions : -1
            });
        }).toThrowDeveloperError();
    });

    it('computes positions', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            slicePartitions: 3,
            stackPartitions: 3
        }));

        var numVertices = 16; // 4 rows * 4 positions
        var numTriangles = 12; //3 top + 3 bottom + 6 around the sides
        expect(m.attributes.position.values.length).toEqual(numVertices * 3);
        expect(m.indices.length).toEqual(numTriangles * 3);
        expect(m.boundingSphere.radius).toEqual(1);
    });

    it('compute all vertex attributes', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.ALL,
            slicePartitions: 3,
            stackPartitions: 3
        }));

        var numVertices = 16;
        var numTriangles = 12;
        expect(m.attributes.position.values.length).toEqual(numVertices * 3);
        expect(m.attributes.st.values.length).toEqual(numVertices * 2);
        expect(m.attributes.normal.values.length).toEqual(numVertices * 3);
        expect(m.attributes.tangent.values.length).toEqual(numVertices * 3);
        expect(m.attributes.bitangent.values.length).toEqual(numVertices * 3);
        expect(m.indices.length).toEqual(numTriangles * 3);
    });

    it('computes attributes for a unit sphere', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.ALL,
            slicePartitions: 3,
            stackPartitions: 3
        }));

        var positions = m.attributes.position.values;
        var normals = m.attributes.normal.values;
        var tangents = m.attributes.tangent.values;
        var bitangents = m.attributes.bitangent.values;

        for ( var i = 0; i < positions.length; i += 3) {
            var position = Cartesian3.fromArray(positions, i);
            var normal = Cartesian3.fromArray(normals, i);
            var tangent = Cartesian3.fromArray(tangents, i);
            var bitangent = Cartesian3.fromArray(bitangents, i);

            expect(Cartesian3.magnitude(position)).toEqualEpsilon(1.0, CesiumMath.EPSILON10);
            expect(normal).toEqualEpsilon(Cartesian3.normalize(position, new Cartesian3()), CesiumMath.EPSILON7);
            expect(Cartesian3.dot(Cartesian3.UNIT_Z, tangent)).not.toBeLessThan(0.0);
            expect(bitangent).toEqualEpsilon(Cartesian3.cross(normal, tangent, new Cartesian3()), CesiumMath.EPSILON7);
        }
    });

    it('undefined is returned if the x, y, or z radii are equal or less than zero', function() {
        var ellipsoid0 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(0.0, 500000.0, 500000.0)
        });
        var ellipsoid1 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(1000000.0, 0.0, 500000.0)
        });
        var ellipsoid2 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(1000000.0, 500000.0, 0.0)
        });
        var ellipsoid3 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(-10.0, 500000.0, 500000.0)
        });
        var ellipsoid4 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(1000000.0, -10.0, 500000.0)
        });
        var ellipsoid5 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(1000000.0, 500000.0, -10.0)
        });

        var geometry0 = EllipsoidGeometry.createGeometry(ellipsoid0);
        var geometry1 = EllipsoidGeometry.createGeometry(ellipsoid1);
        var geometry2 = EllipsoidGeometry.createGeometry(ellipsoid2);
        var geometry3 = EllipsoidGeometry.createGeometry(ellipsoid3);
        var geometry4 = EllipsoidGeometry.createGeometry(ellipsoid4);
        var geometry5 = EllipsoidGeometry.createGeometry(ellipsoid5);

        expect(geometry0).toBeUndefined();
        expect(geometry1).toBeUndefined();
        expect(geometry2).toBeUndefined();
        expect(geometry3).toBeUndefined();
        expect(geometry4).toBeUndefined();
        expect(geometry5).toBeUndefined();
    });

    var ellipsoidgeometry = new EllipsoidGeometry({
        vertexFormat : VertexFormat.POSITION_ONLY,
        radii : new Cartesian3(1.0, 2.0, 3.0),
        slicePartitions: 3,
        stackPartitions: 3
    });
    var packedInstance = [1.0, 2.0, 3.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.0, 3.0];
    createPackableSpecs(EllipsoidGeometry, ellipsoidgeometry, packedInstance);
});
<commit_msg>Added unit tests for partial ellipsoids<commit_after>defineSuite([
        'Core/EllipsoidGeometry',
        'Core/Cartesian3',
        'Core/Math',
        'Core/VertexFormat',
        'Specs/createPackableSpecs'
    ], function(
        EllipsoidGeometry,
        Cartesian3,
        CesiumMath,
        VertexFormat,
        createPackableSpecs) {
    'use strict';

    it('constructor rounds floating-point slicePartitions', function() {
        var m = new EllipsoidGeometry({
            slicePartitions: 3.5,
            stackPartitions: 3
        });
        expect(m._slicePartitions).toEqual(4);
    });

    it('constructor rounds floating-point stackPartitions', function() {
        var m = new EllipsoidGeometry({
            slicePartitions: 3,
            stackPartitions: 3.5
        });
        expect(m._stackPartitions).toEqual(4);
    });

    it('constructor throws with invalid slicePartitions', function() {
        expect(function() {
            return new EllipsoidGeometry({
                slicePartitions : -1
            });
        }).toThrowDeveloperError();
    });

    it('constructor throws with invalid stackPartitions', function() {
        expect(function() {
            return new EllipsoidGeometry({
                stackPartitions : -1
            });
        }).toThrowDeveloperError();
    });

    it('computes positions', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            slicePartitions: 3,
            stackPartitions: 3
        }));

        // The vertices are 6x6 because an additional slice and stack are added
        // and the first and last clock and cone angles are duplicated (3 + 1 + 2 = 6)
        var numVertices = 36; // 6 rows * 6 positions
        var numTriangles = 18; // 6 top + 6 bottom + 6 around the sides
        expect(m.attributes.position.values.length).toEqual(numVertices * 3);
        expect(m.indices.length).toEqual(numTriangles * 3);
        expect(m.boundingSphere.radius).toEqual(1);
    });

    it('compute all vertex attributes', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.ALL,
            slicePartitions: 3,
            stackPartitions: 3
        }));

        var numVertices = 36;
        var numTriangles = 18;
        expect(m.attributes.position.values.length).toEqual(numVertices * 3);
        expect(m.attributes.st.values.length).toEqual(numVertices * 2);
        expect(m.attributes.normal.values.length).toEqual(numVertices * 3);
        expect(m.attributes.tangent.values.length).toEqual(numVertices * 3);
        expect(m.attributes.bitangent.values.length).toEqual(numVertices * 3);
        expect(m.indices.length).toEqual(numTriangles * 3);
    });

    it('computes attributes for a unit sphere', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.ALL,
            slicePartitions: 3,
            stackPartitions: 3
        }));

        var positions = m.attributes.position.values;
        var normals = m.attributes.normal.values;
        var tangents = m.attributes.tangent.values;
        var bitangents = m.attributes.bitangent.values;

        for ( var i = 0; i < positions.length; i += 3) {
            var position = Cartesian3.fromArray(positions, i);
            var normal = Cartesian3.fromArray(normals, i);
            var tangent = Cartesian3.fromArray(tangents, i);
            var bitangent = Cartesian3.fromArray(bitangents, i);

            expect(Cartesian3.magnitude(position)).toEqualEpsilon(1.0, CesiumMath.EPSILON10);
            expect(normal).toEqualEpsilon(Cartesian3.normalize(position, new Cartesian3()), CesiumMath.EPSILON7);
            expect(Cartesian3.dot(Cartesian3.UNIT_Z, tangent)).not.toBeLessThan(0.0);
            expect(bitangent).toEqualEpsilon(Cartesian3.cross(normal, tangent, new Cartesian3()), CesiumMath.EPSILON7);
        }
    });

    it('computes positions with inner surface', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            slicePartitions: 3,
            stackPartitions: 3,
            innerRadii: new Cartesian3(0.5, 0.5, 0.5)
        }));

        var numVertices = 72; // 6 rows * 6 positions * 2 surfaces
        var numTriangles = 36; // (6 top + 6 bottom + 6 around the sides) * 2 surfaces
        expect(m.attributes.position.values.length).toEqual(numVertices * 3);
        expect(m.indices.length).toEqual(numTriangles * 3);
        expect(m.boundingSphere.radius).toEqual(1);
    });

    it('computes positions with inner surface and partial clock range', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            slicePartitions: 4,
            stackPartitions: 4,
            innerRadii: new Cartesian3(0.5, 0.5, 0.5),
            minimumClock: CesiumMath.toRadians(90.0),
            maximumClock: CesiumMath.toRadians(270.0)
        }));

        var numVertices = 70;
        var numTriangles = 48;
        expect(m.attributes.position.values.length).toEqual(numVertices * 3);
        expect(m.indices.length).toEqual(numTriangles * 3);
        expect(m.boundingSphere.radius).toEqual(1);
    });

    it('computes positions with inner surface and partial clock range and open top', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            slicePartitions: 4,
            stackPartitions: 4,
            innerRadii: new Cartesian3(0.5, 0.5, 0.5),
            minimumClock: CesiumMath.toRadians(90.0),
            maximumClock: CesiumMath.toRadians(270.0),
            minimumCone: CesiumMath.toRadians(30.0)
        }));

        var numVertices = 60;
        var numTriangles = 40;
        expect(m.attributes.position.values.length).toEqual(numVertices * 3);
        expect(m.indices.length).toEqual(numTriangles * 3);
        expect(m.boundingSphere.radius).toEqual(1);
    });

    it('computes positions with inner surface and partial clock range and open top and bottom', function() {
        var m = EllipsoidGeometry.createGeometry(new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            slicePartitions: 4,
            stackPartitions: 4,
            innerRadii: new Cartesian3(0.5, 0.5, 0.5),
            minimumClock: CesiumMath.toRadians(90.0),
            maximumClock: CesiumMath.toRadians(270.0),
            minimumCone: CesiumMath.toRadians(30.0),
            maximumCone: CesiumMath.toRadians(120.0)
        }));

        var numVertices = 50;
        var numTriangles = 32;
        expect(m.attributes.position.values.length).toEqual(numVertices * 3);
        expect(m.indices.length).toEqual(numTriangles * 3);
        expect(m.boundingSphere.radius).toEqual(1);
    });

    it('undefined is returned if the x, y, or z radii or innerRadii are equal or less than zero', function() {
        var ellipsoid0 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(0.0, 500000.0, 500000.0)
        });
        var ellipsoid1 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(1000000.0, 0.0, 500000.0)
        });
        var ellipsoid2 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(1000000.0, 500000.0, 0.0)
        });
        var ellipsoid3 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(-10.0, 500000.0, 500000.0)
        });
        var ellipsoid4 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(1000000.0, -10.0, 500000.0)
        });
        var ellipsoid5 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(1000000.0, 500000.0, -10.0)
        });
        var ellipsoid6 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(500000.0, 500000.0, 500000.0),
            innerRadii : new Cartesian3(0.0, 100000.0, 100000.0)
        });
        var ellipsoid7 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(500000.0, 500000.0, 500000.0),
            innerRadii : new Cartesian3(100000.0, 0.0, 100000.0)
        });
        var ellipsoid8 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(500000.0, 500000.0, 500000.0),
            innerRadii : new Cartesian3(100000.0, 100000.0, 0.0)
        });
        var ellipsoid9 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(500000.0, 500000.0, 500000.0),
            innerRadii : new Cartesian3(-10.0, 100000.0, 100000.0)
        });
        var ellipsoid10 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(500000.0, 500000.0, 500000.0),
            innerRadii : new Cartesian3(100000.0, -10.0, 100000.0)
        });
        var ellipsoid11 = new EllipsoidGeometry({
            vertexFormat : VertexFormat.POSITION_ONLY,
            radii : new Cartesian3(500000.0, 500000.0, 500000.0),
            innerRadii : new Cartesian3(100000.0, 100000.0, -10.0)
        });

        var geometry0 = EllipsoidGeometry.createGeometry(ellipsoid0);
        var geometry1 = EllipsoidGeometry.createGeometry(ellipsoid1);
        var geometry2 = EllipsoidGeometry.createGeometry(ellipsoid2);
        var geometry3 = EllipsoidGeometry.createGeometry(ellipsoid3);
        var geometry4 = EllipsoidGeometry.createGeometry(ellipsoid4);
        var geometry5 = EllipsoidGeometry.createGeometry(ellipsoid5);
        var geometry6 = EllipsoidGeometry.createGeometry(ellipsoid6);
        var geometry7 = EllipsoidGeometry.createGeometry(ellipsoid7);
        var geometry8 = EllipsoidGeometry.createGeometry(ellipsoid8);
        var geometry9 = EllipsoidGeometry.createGeometry(ellipsoid9);
        var geometry10 = EllipsoidGeometry.createGeometry(ellipsoid10);
        var geometry11 = EllipsoidGeometry.createGeometry(ellipsoid11);

        expect(geometry0).toBeUndefined();
        expect(geometry1).toBeUndefined();
        expect(geometry2).toBeUndefined();
        expect(geometry3).toBeUndefined();
        expect(geometry4).toBeUndefined();
        expect(geometry5).toBeUndefined();
        expect(geometry6).toBeUndefined();
        expect(geometry7).toBeUndefined();
        expect(geometry8).toBeUndefined();
        expect(geometry9).toBeUndefined();
        expect(geometry10).toBeUndefined();
        expect(geometry11).toBeUndefined();
    });

    var ellipsoidgeometry = new EllipsoidGeometry({
        vertexFormat : VertexFormat.POSITION_ONLY,
        radii : new Cartesian3(1.0, 2.0, 3.0),
        innerRadii : new Cartesian3(0.5, 0.6, 0.7),
        minimumClock : 0.1,
        maximumClock : 0.2,
        minimumCone : 0.3,
        maximumCone : 0.4,
        slicePartitions : 3,
        stackPartitions : 3
    });
    var packedInstance = [
        1.0, 2.0, 3.0,
        0.5, 0.6, 0.7,
        1.0, 0.0, 0.0, 0.0, 0.0, 0.0,
        0.1, 0.2,
        0.3, 0.4,
        3.0, 3.0
    ];
    createPackableSpecs(EllipsoidGeometry, ellipsoidgeometry, packedInstance);
});
<|endoftext|>


<commit_before>// Document
// HTMLDocument is an extension of Document.  If the browser has HTMLDocument but not Document, the former will suffice as an alias for the latter.
if (!this.Document){this.Document = this.HTMLDocument; }

// Element
if (!window.HTMLElement) { window.HTMLElement = window.Element; }

// Date.now
if(!Date.now){ Date.now = function now() {	return new Date().getTime(); }; }

// performance.now
if (!window.performance.now) {
	var startTime = Date.now(), wd = this;
	wd.performance = {};

	wd.performance.now = function () {
		return Date.now() - startTime;
	};
}

// Array.prototype.indexOf
if (!Array.prototype.indexOf) {
	Array.prototype.indexOf = function indexOf(searchElement) {
		if (this === undefined || this === null) {
			throw new TypeError(this + 'is not an object');
		}

		var
		arraylike = this instanceof String ? this.split('') : this,
		length = Math.max(Math.min(arraylike.length, 9007199254740991), 0) || 0,
		index = Number(arguments[1]) || 0;

		index = (index < 0 ? Math.max(length + index, 0) : index) - 1;

		while (++index < length) {
			if (index in arraylike && arraylike[index] === searchElement) {
				return index;
			}
		}

		return -1;
	};
}

// getComputedStyle
if (!('getComputedStyle' in window)) {
	(function(){
		function getComputedStylePixel(element, property, fontSize) {
			var
			// Internet Explorer sometimes struggles to read currentStyle until the element's document is accessed.
			value = element.document && element.currentStyle[property].match(/([\d\.]+)(%|cm|em|in|mm|pc|pt|)/) || [0, 0, ''],
			size = value[1],
			suffix = value[2],
			rootSize;

			fontSize = !fontSize ? fontSize : /%|em/.test(suffix) && element.parentElement ? getComputedStylePixel(element.parentElement, 'fontSize', null) : 16;
			rootSize = property == 'fontSize' ? fontSize : /width/i.test(property) ? element.clientWidth : element.clientHeight;

			return suffix == '%' ? size / 100 * rootSize :
				suffix == 'cm' ? size * 0.3937 * 96 :
				suffix == 'em' ? size * fontSize :
				suffix == 'in' ? size * 96 :
				suffix == 'mm' ? size * 0.3937 * 96 / 10 :
				suffix == 'pc' ? size * 12 * 96 / 72 :
				suffix == 'pt' ? size * 96 / 72 :
				size;
		}

		function setShortStyleProperty(style, property) {
			var	borderSuffix = property == 'border' ? 'Width' : '',
				t = property + 'Top' + borderSuffix,
				r = property + 'Right' + borderSuffix,
				b = property + 'Bottom' + borderSuffix,
				l = property + 'Left' + borderSuffix;

			style[property] = (style[t] == style[r] && style[t] == style[b] && style[t] == style[l] ? [ style[t] ] :
							style[t] == style[b] && style[l] == style[r] ? [ style[t], style[r] ] :
							style[l] == style[r] ? [ style[t], style[r], style[b] ] :
							[ style[t], style[r], style[b], style[l] ]).join(' ');
		}

		// <CSSStyleDeclaration>
		function CSSStyleDeclaration(element) {
			var style = this,
			currentStyle = element.currentStyle,
			fontSize = getComputedStylePixel(element, 'fontSize'),
			unCamelCase = function (match) {
				return '-' + match.toLowerCase();
			},
			property;

			for (property in currentStyle) {
				Array.prototype.push.call(style, property == 'styleFloat' ? 'float' : property.replace(/[A-Z]/, unCamelCase));

				if (property == 'width') {
					style[property] = element.offsetWidth + 'px';
				} else if (property == 'height') {
					style[property] = element.offsetHeight + 'px';
				} else if (property == 'styleFloat') {
					style.float = currentStyle[property];
				} else if (/margin.|padding.|border.+W/.test(property) && style[property] != 'auto') {
					style[property] = Math.round(getComputedStylePixel(element, property, fontSize)) + 'px';
				} else if (/^outline/.test(property)) {
					// errors on checking outline
					try {
						style[property] = currentStyle[property];
					} catch (error) {
						style.outlineColor = currentStyle.color;
						style.outlineStyle = style.outlineStyle || 'none';
						style.outlineWidth = style.outlineWidth || '0px';
						style.outline = [style.outlineColor, style.outlineWidth, style.outlineStyle].join(' ');
					}
				} else {
					style[property] = currentStyle[property];
				}
			}

			setShortStyleProperty(style, 'margin');
			setShortStyleProperty(style, 'padding');
			setShortStyleProperty(style, 'border');

			style.fontSize = Math.round(fontSize) + 'px';

			style.prototype = {
				// constructor: CSSStyleDeclaration,
				// <CSSStyleDeclaration>.getPropertyPriority
				getPropertyPriority: function () {
					throw new Error('NotSupportedError: DOM Exception 9');
				},
				// <CSSStyleDeclaration>.getPropertyValue
				getPropertyValue: function (property) {
					return this[property.replace(/-\w/g, function (match) {
						return match[1].toUpperCase();
					})];
				},
				// <CSSStyleDeclaration>.item
				item: function (index) {
					return this[index];
				},
				// <CSSStyleDeclaration>.removeProperty
				removeProperty: function () {
					throw new Error('NoModificationAllowedError: DOM Exception 7');
				},
				// <CSSStyleDeclaration>.setProperty
				setProperty: function () {
					throw new Error('NoModificationAllowedError: DOM Exception 7');
				},
				// <CSSStyleDeclaration>.getPropertyCSSValue
				getPropertyCSSValue: function () {
					throw new Error('NotSupportedError: DOM Exception 9');
				}
			};
		}

		// <Global>.getComputedStyle
		window.getComputedStyle = function getComputedStyle(element) {
			return new CSSStyleDeclaration(element);
		};
	})();
}

// requestAnimationFrame
if (!window.requestAnimationFrame) {

	var	lastTime = Date.now();
	window.requestAnimationFrame = function (callback) {
		'use strict';
		if (typeof callback !== 'function') {
			throw new TypeError(callback + 'is not a function');
		}

		var
		currentTime = Date.now(),
		delay = 16 + lastTime - currentTime;

		if (delay < 0) {
			delay = 0;
		}

		lastTime = currentTime;

		return setTimeout(function () {
			lastTime = Date.now();

			callback(performance.now());
		}, delay);
	};

	window.cancelAnimationFrame = function (id) {
		clearTimeout(id);
	};
}

// Event
if (!window.addEventListener||!Window.prototype.addEventListener) {

	window.Event = Window.prototype.Event = function Event(type, eventInitDict) {
		if (!type) {
			throw new Error('Not enough arguments');
		}

		var event = document.createEventObject();

		event.type = type;
		event.bubbles = eventInitDict && eventInitDict.bubbles !== undefined ? eventInitDict.bubbles : false;
		event.cancelable = eventInitDict && eventInitDict.cancelable !== undefined ? eventInitDict.cancelable : false;

		return event;
	};

	window.addEventListener = Window.prototype.addEventListener = Document.prototype.addEventListener = Element.prototype.addEventListener = function addEventListener() {
		var	element = this,
		type = arguments[0],
		listener = arguments[1];

		if (!element._events) {
			element._events = {};
		}

		if (!element._events[type]) {
			element._events[type] = function (event) {
				var
				list = element._events[event.type].list,
				events = list.slice(),
				index = -1,
				length = events.length,
				eventElement;

				event.preventDefault = function preventDefault() {
					if (event.cancelable !== false) {
						event.returnValue = false;
					}
				};

				event.stopPropagation = function stopPropagation() {
					event.cancelBubble = true;
				};

				event.stopImmediatePropagation = function stopImmediatePropagation() {
					event.cancelBubble = true;
					event.cancelImmediate = true;
				};

				event.currentTarget = element;
				event.relatedTarget = event.fromElement || null;
				event.target = event.target || event.srcElement || element;
				event.timeStamp = new Date().getTime();

				if (event.clientX) {
					event.pageX = event.clientX + document.documentElement.scrollLeft;
					event.pageY = event.clientY + document.documentElement.scrollTop;
				}

				while (++index < length && !event.cancelImmediate) {
					if (index in events) {
						eventElement = events[index];

						if (list.indexOf(eventElement) !== -1 && typeof eventElement === 'function') {
							eventElement.call(element, event);
						}
					}
				}
			};

			element._events[type].list = [];

			if (element.attachEvent) {
				element.attachEvent('on' + type, element._events[type]);
			}
		}

		element._events[type].list.push(listener);
	};

	window.removeEventListener = Window.prototype.removeEventListener = Document.prototype.removeEventListener = Element.prototype.removeEventListener = function removeEventListener() {
		var
		element = this,
		type = arguments[0],
		listener = arguments[1],
		index;

		if (element._events && element._events[type] && element._events[type].list) {
			index = element._events[type].list.indexOf(listener);

			if (index !== -1) {
				element._events[type].list.splice(index, 1);

				if (!element._events[type].list.length) {
					if (element.detachEvent) {
						element.detachEvent('on' + type, element._events[type]);
					}
					delete element._events[type];
				}
			}
		}
	};

	window.dispatchEvent = Window.prototype.dispatchEvent = Document.prototype.dispatchEvent = Element.prototype.dispatchEvent = function dispatchEvent(event) {
		if (!arguments.length) {
			throw new Error('Not enough arguments');
		}

		if (!event || typeof event.type !== 'string') {
			throw new Error('DOM Events Exception 0');
		}

		var element = this, type = event.type;

		try {
			if (!event.bubbles) {
				event.cancelBubble = true;

				var cancelBubbleEvent = function (event) {
					event.cancelBubble = true;

					(element || window).detachEvent('on' + type, cancelBubbleEvent);
				};

				this.attachEvent('on' + type, cancelBubbleEvent);
			}

			this.fireEvent('on' + type, event);
		} catch (error) {
			event.target = element;

			do {
				event.currentTarget = element;

				if ('_events' in element && typeof element._events[type] === 'function') {
					element._events[type].call(element, event);
				}

				if (typeof element['on' + type] === 'function') {
					element['on' + type].call(element, event);
				}

				element = element.nodeType === 9 ? element.parentWindow : element.parentNode;
			} while (element && !event.cancelBubble);
		}

		return true;
	};
}

// CustomEvent
if (!window.CustomEvent) {
	window.CustomEvent = function CustomEvent(type, eventInitDict) {
		var event = new Event(type, eventInitDict);
		event.detail = eventInitDict && eventInitDict.detail || null;
		return event;
	};
}
<commit_msg>Update minifill.js<commit_after>// Document
// HTMLDocument is an extension of Document.  If the browser has HTMLDocument but not Document, the former will suffice as an alias for the latter.
if (!this.Document){this.Document = this.HTMLDocument; }

// Element
if (!window.HTMLElement) { window.HTMLElement = window.Element; }

// Date.now
if(!Date.now){ Date.now = function now() { return new Date().getTime(); }; }

// performance.now
(function(){
	if ("performance" in window == false) {
		window.performance = {};
	}

	if ("now" in window.performance == false){
		var nowOffset = Date.now();

		window.performance.now = function now(){
			return Date.now() - nowOffset;
		}
	}
})();

// Array.prototype.indexOf
if (!Array.prototype.indexOf) {
	Array.prototype.indexOf = function indexOf(searchElement) {
		if (this === undefined || this === null) {
			throw new TypeError(this + 'is not an object');
		}

		var	arraylike = this instanceof String ? this.split('') : this,
			length = Math.max(Math.min(arraylike.length, 9007199254740991), 0) || 0,
			index = Number(arguments[1]) || 0;

		index = (index < 0 ? Math.max(length + index, 0) : index) - 1;

		while (++index < length) {
			if (index in arraylike && arraylike[index] === searchElement) {
				return index;
			}
		}

		return -1;
	};
}

// getComputedStyle
if (!('getComputedStyle' in window)) {
	(function(){
		function getComputedStylePixel(element, property, fontSize) {

			// Internet Explorer sometimes struggles to read currentStyle until the element's document is accessed.
			var value = element.document && element.currentStyle[property].match(/([\d\.]+)(%|cm|em|in|mm|pc|pt|)/) || [0, 0, ''],
				size = value[1],
				suffix = value[2],
				rootSize;

			fontSize = !fontSize ? fontSize : /%|em/.test(suffix) && element.parentElement ? getComputedStylePixel(element.parentElement, 'fontSize', null) : 16;
			rootSize = property == 'fontSize' ? fontSize : /width/i.test(property) ? element.clientWidth : element.clientHeight;

			return suffix == '%' ? size / 100 * rootSize :
				suffix == 'cm' ? size * 0.3937 * 96 :
				suffix == 'em' ? size * fontSize :
				suffix == 'in' ? size * 96 :
				suffix == 'mm' ? size * 0.3937 * 96 / 10 :
				suffix == 'pc' ? size * 12 * 96 / 72 :
				suffix == 'pt' ? size * 96 / 72 :
				size;
		}

		function setShortStyleProperty(style, property) {
			var	borderSuffix = property == 'border' ? 'Width' : '',
				t = property + 'Top' + borderSuffix,
				r = property + 'Right' + borderSuffix,
				b = property + 'Bottom' + borderSuffix,
				l = property + 'Left' + borderSuffix;

			style[property] = (style[t] == style[r] && style[t] == style[b] && style[t] == style[l] ? [ style[t] ] :
							style[t] == style[b] && style[l] == style[r] ? [ style[t], style[r] ] :
							style[l] == style[r] ? [ style[t], style[r], style[b] ] :
							[ style[t], style[r], style[b], style[l] ]).join(' ');
		}

		// <CSSStyleDeclaration>
		function CSSStyleDeclaration(element) {
			var style = this,
			currentStyle = element.currentStyle,
			fontSize = getComputedStylePixel(element, 'fontSize'),
			unCamelCase = function (match) {
				return '-' + match.toLowerCase();
			},
			property;

			for (property in currentStyle) {
				Array.prototype.push.call(style, property == 'styleFloat' ? 'float' : property.replace(/[A-Z]/, unCamelCase));

				if (property == 'width') {
					style[property] = element.offsetWidth + 'px';
				} else if (property == 'height') {
					style[property] = element.offsetHeight + 'px';
				} else if (property == 'styleFloat') {
					style.float = currentStyle[property];
				} else if (/margin.|padding.|border.+W/.test(property) && style[property] != 'auto') {
					style[property] = Math.round(getComputedStylePixel(element, property, fontSize)) + 'px';
				} else if (/^outline/.test(property)) {
					// errors on checking outline
					try {
						style[property] = currentStyle[property];
					} catch (error) {
						style.outlineColor = currentStyle.color;
						style.outlineStyle = style.outlineStyle || 'none';
						style.outlineWidth = style.outlineWidth || '0px';
						style.outline = [style.outlineColor, style.outlineWidth, style.outlineStyle].join(' ');
					}
				} else {
					style[property] = currentStyle[property];
				}
			}

			setShortStyleProperty(style, 'margin');
			setShortStyleProperty(style, 'padding');
			setShortStyleProperty(style, 'border');

			style.fontSize = Math.round(fontSize) + 'px';
		}

		CSSStyleDeclaration.prototype = {
			constructor: CSSStyleDeclaration,
			// <CSSStyleDeclaration>.getPropertyPriority
			getPropertyPriority: function () {
				throw new Error('NotSupportedError: DOM Exception 9');
			},
			// <CSSStyleDeclaration>.getPropertyValue
			getPropertyValue: function (property) {
				return this[property.replace(/-\w/g, function (match) {
					return match[1].toUpperCase();
				})];
			},
			// <CSSStyleDeclaration>.item
			item: function (index) {
				return this[index];
			},
			// <CSSStyleDeclaration>.removeProperty
			removeProperty: function () {
				throw new Error('NoModificationAllowedError: DOM Exception 7');
			},
			// <CSSStyleDeclaration>.setProperty
			setProperty: function () {
				throw new Error('NoModificationAllowedError: DOM Exception 7');
			},
			// <CSSStyleDeclaration>.getPropertyCSSValue
			getPropertyCSSValue: function () {
				throw new Error('NotSupportedError: DOM Exception 9');
			}
		};

		// <Global>.getComputedStyle
		window.getComputedStyle = function getComputedStyle(element) {
			return new CSSStyleDeclaration(element);
		};
	})();
}

// requestAnimationFrame
if (!window.requestAnimationFrame) {

	var	lastTime = Date.now();
	window.requestAnimationFrame = function (callback) {
		'use strict';
		if (typeof callback !== 'function') {
			throw new TypeError(callback + 'is not a function');
		}

		var
		currentTime = Date.now(),
		delay = 16 + lastTime - currentTime;

		if (delay < 0) {
			delay = 0;
		}

		lastTime = currentTime;

		return setTimeout(function () {
			lastTime = Date.now();

			callback(performance.now());
		}, delay);
	};

	window.cancelAnimationFrame = function (id) {
		clearTimeout(id);
	};
}

// Event
if (!window.addEventListener||!Window.prototype.addEventListener) {

	window.Event = Window.prototype.Event = function Event(type, eventInitDict) {
		if (!type) {
			throw new Error('Not enough arguments');
		}

		var event = document.createEventObject();

		event.type = type;
		event.bubbles = eventInitDict && eventInitDict.bubbles !== undefined ? eventInitDict.bubbles : false;
		event.cancelable = eventInitDict && eventInitDict.cancelable !== undefined ? eventInitDict.cancelable : false;

		return event;
	};

	window.addEventListener = Window.prototype.addEventListener = Document.prototype.addEventListener = Element.prototype.addEventListener = function addEventListener() {
		var	element = this,
		type = arguments[0],
		listener = arguments[1];

		if (!element._events) {
			element._events = {};
		}

		if (!element._events[type]) {
			element._events[type] = function (event) {
				var
				list = element._events[event.type].list,
				events = list.slice(),
				index = -1,
				length = events.length,
				eventElement;

				event.preventDefault = function preventDefault() {
					if (event.cancelable !== false) {
						event.returnValue = false;
					}
				};

				event.stopPropagation = function stopPropagation() {
					event.cancelBubble = true;
				};

				event.stopImmediatePropagation = function stopImmediatePropagation() {
					event.cancelBubble = true;
					event.cancelImmediate = true;
				};

				event.currentTarget = element;
				event.relatedTarget = event.fromElement || null;
				event.target = event.target || event.srcElement || element;
				event.timeStamp = new Date().getTime();

				if (event.clientX) {
					event.pageX = event.clientX + document.documentElement.scrollLeft;
					event.pageY = event.clientY + document.documentElement.scrollTop;
				}

				while (++index < length && !event.cancelImmediate) {
					if (index in events) {
						eventElement = events[index];

						if (list.indexOf(eventElement) !== -1 && typeof eventElement === 'function') {
							eventElement.call(element, event);
						}
					}
				}
			};

			element._events[type].list = [];

			if (element.attachEvent) {
				element.attachEvent('on' + type, element._events[type]);
			}
		}

		element._events[type].list.push(listener);
	};

	window.removeEventListener = Window.prototype.removeEventListener = Document.prototype.removeEventListener = Element.prototype.removeEventListener = function removeEventListener() {
		var
		element = this,
		type = arguments[0],
		listener = arguments[1],
		index;

		if (element._events && element._events[type] && element._events[type].list) {
			index = element._events[type].list.indexOf(listener);

			if (index !== -1) {
				element._events[type].list.splice(index, 1);

				if (!element._events[type].list.length) {
					if (element.detachEvent) {
						element.detachEvent('on' + type, element._events[type]);
					}
					delete element._events[type];
				}
			}
		}
	};

	window.dispatchEvent = Window.prototype.dispatchEvent = Document.prototype.dispatchEvent = Element.prototype.dispatchEvent = function dispatchEvent(event) {
		if (!arguments.length) {
			throw new Error('Not enough arguments');
		}

		if (!event || typeof event.type !== 'string') {
			throw new Error('DOM Events Exception 0');
		}

		var element = this, type = event.type;

		try {
			if (!event.bubbles) {
				event.cancelBubble = true;

				var cancelBubbleEvent = function (event) {
					event.cancelBubble = true;

					(element || window).detachEvent('on' + type, cancelBubbleEvent);
				};

				this.attachEvent('on' + type, cancelBubbleEvent);
			}

			this.fireEvent('on' + type, event);
		} catch (error) {
			event.target = element;

			do {
				event.currentTarget = element;

				if ('_events' in element && typeof element._events[type] === 'function') {
					element._events[type].call(element, event);
				}

				if (typeof element['on' + type] === 'function') {
					element['on' + type].call(element, event);
				}

				element = element.nodeType === 9 ? element.parentWindow : element.parentNode;
			} while (element && !event.cancelBubble);
		}

		return true;
	};
}

// CustomEvent
if (!window.CustomEvent) {
	window.CustomEvent = function CustomEvent(type, eventInitDict) {
		var event = new Event(type, eventInitDict);
		event.detail = eventInitDict && eventInitDict.detail || null;
		return event;
	};
}
<|endoftext|>


<commit_before>var PRODUCT_NAME="api-gateway-mapping-template-integration-test";

if (!process.env.AWS_ACCOUNT_ID) {
  throw "process.env.AWS_ACCOUNT_ID is required";
}

process.env.AWS_REGION     = process.env.AWS_REGION     || "ap-northeast-1";
process.env.AWS_PROFILE    = process.env.AWS_PROFILE    || PRODUCT_NAME;
process.env.LAMBDA_ROLE    = process.env.LAMBDA_ROLE    || "arn:aws:iam::" + process.env.AWS_ACCOUNT_ID + ":role/" + PRODUCT_NAME;

var stream = require('stream');
var fs = require('fs');
var crypto = require('crypto');

var sprintf = require("sprintf-js").sprintf;
var Zip = require('adm-zip');
var Promise = require('bluebird');

var Endpoint = require('./endpoint');
var post = require('./post');

(function(examples) {
  var zip = new Zip();
  zip.addFile('index.js', new Buffer("exports.handler = function(event, context) { context.succeed(JSON.stringify(event)); };"));

  var requestTemplates = examples.reduce(function(product, e, i) { product["text/test-" + i] = e.template; return product; }, {});
  var responseModels = examples.reduce(function(product, e, i) { product["text/test-" + i] = "Empty"; return product; }, {});
  var responseTemplates = examples.reduce(function(product, e, i) { product["text/test-" + i] = "$input.path('$')"; return product; }, {});

  var endpoint = new Endpoint({
    region: process.env.AWS_REGION,
    accountId: process.env.AWS_ACCOUNT_ID,
    apiName: PRODUCT_NAME,
    lambdaRole: process.env.LAMBDA_ROLE,
    functionName: PRODUCT_NAME + "-console-log",
    functionZip: zip
  });

  endpoint
    .createFunction()
    .createRestApi()
    .putMethod()
    .putIntegration(requestTemplates)
    .putMethodResponse(responseModels)
    .putIntegrationResponse(responseTemplates)
    .addPermission()
    .deploy()
    .then(function() {
      console.log(endpoint.url);

      var q = examples.map(function(ex, i) {
        var req = function() { return post(endpoint.url, extend({}, ex.headers, {'Content-Type': "text/test-" + i}), ex.payload); };
        return req()
          .then(function(res) {
            if (res.statusCode === 500) {
              return wait(1000).req();
            }

            res.headers = ex.headers;
            res.template = ex.template;
            res.payload = ex.payload;
            return res;
          });
      });

      return Promise.all(q);
    })
    .then(function(results) {
      var testOut = process.env.TEST ? fs.createWriteStream(process.env.TEST) : process.stdout;
      var markdownOut = process.env.MD ? fs.createWriteStream(process.env.MD) : process.stdout;

      results = results.map(function(result) {
        var r = new Result();
        r.mappingTemplate = result.template;
        r.requestBody = result.payload;
        r.requestHeaders = result.headers;
        r.statusCode = result.statusCode;
        r.responseBody = result.body;
        return r;
      });

      // write markdown
      results.forEach(function(r, i) {
        markdownOut.write(sprintf("## example-%s\n", hash(JSON.stringify(r))));
        r.toMarkdown(markdownOut);
      });

      // write test
      testOut.write(sprintf("// This file is generated by `TEST=%s node misc/gen.js`\n", process.env.TEST));
      testOut.write("\n");
      testOut.write("var assert = require('assert')\n");
      testOut.write("var mappingTemplate = require('../')\n");
      testOut.write("\n");
      testOut.write("describe('$input.path|$input.json', function() {\n");
      var mdUrl = "https://github.com/ToQoz/api-gateway-mapping-template/blob/master/test/_.md";
      results.forEach(function(r, i) {
        testOut.write(sprintf("  // %s#example-%s\n", mdUrl, hash(JSON.stringify(r))));
        r.toTest(testOut, 1);
      });
      testOut.write("});");

      return "";
    })
    .then(function() {
      endpoint.cleanup();
    })
    .catch(function(err) {
      console.error(err);
      err.stack.split('\n').forEach(function(t) { console.error(t); });
      endpoint.cleanup();
    });
})([
  {
    template: '"$input.path(\'$\')"',
    payload: "a=b",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: '"a=b"',
    headers: {},
  },
  {
    template: '"$input.json(\'$\')"',
    payload: "a=b",
    headers: {},
  },
  {
    template: '"$input.json(\'$\')"',
    payload: '"a=b"',
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "{}",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: '"{}"',
    headers: {},
  },
  {
    template: '"$input.json(\'$\')"',
    payload: "{}",
    headers: {},
  },
  {
    template: '{"name": "$input.path(\'$\')"}',
    payload: "name=toqoz",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "{a",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "a{b",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "[a",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "a[",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "null{",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "true{",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "false{",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "undefined{",
    headers: {},
  },
  {
    template: '$input',
    payload: "",
    headers: {},
  },
  {
    template: '$input.keySet',
    payload: "",
    headers: {},
  },
  {
    template: '$input.params.keySet',
    payload: "",
    headers: {},
  },
  {
    template: '$util',
    payload: "",
    headers: {},
  },
  {
    template: '$input.params',
    payload: "",
    headers: {},
  },
  {
    template: '$input.json',
    payload: "",
    headers: {},
  },
  {
    template: '$util.urlEncode',
    payload: "",
    headers: {},
  },
]);

function Result() {
  this.mappingTemplate = null;
  this.requestBody = null;
  this.requestHeaders = null;
  this.statusCode = null;
  this.responseBody = null;
}

Result.prototype.toMarkdown = function(out) {
  var s = "";
  var header = Object.keys(this.requestHeaders).map(function(k) { return k + " : " + this.requestHeaders[k]; }.bind(this)).join("\n");

  var h = ["Template", "Header", "Payload", "Status code", "Result"];
  s += h.join("|") + "\n";
  s += h.map(function(v) { return v.replace(/./g, "-"); }).join("|") + "\n";

  s += [
    "`" + this.mappingTemplate + "`",
    "`" + (header || "None") + "`",
    "`" + this.requestBody + "`",
    "`" + this.statusCode + "`",
    "`" + this.responseBody + "`",
  ].join("|");

  out.write(s);
  out.write("\n\n");
};

Result.prototype.toTest = function(out, indentLevel) {
  var indent = "";
  for (var i = 0; i < indentLevel; i++) {
    indent += "  ";
  }

  if (this.statusCode !== 200 || this.responseBody.indexOf("{errorMessage=Unable") === 0) {
    out.write(sprintf(indent + "describe('H=`%s` P=`%s` ===> T=`%s`', function() {\n", JSON.stringify(this.requestHeaders).replace(/'/g, "\\'"), this.requestBody.replace(/'/g, "\\'"), this.mappingTemplate.replace(/'/g, "\\'")));
    out.write(indent         + "  it('throw error', function() {\n");
    out.write(sprintf(indent + "    assert.throws(function() { mappingTemplate(%s, %s, {}); });\n", JSON.stringify(this.mappingTemplate), JSON.stringify(this.requestBody)));
    out.write(        indent + "  });\n");
    out.write(        indent + "});\n");
  } else {
    out.write(sprintf(indent + "describe('H=`%s` P=`%s` ===> T=`%s`', function() {\n", JSON.stringify(this.requestHeaders).replace(/'/g, "\\'"), this.requestBody.replace(/'/g, "\\'"), this.mappingTemplate.replace(/'/g, "\\'")));
    out.write(sprintf(indent + "  it('return %s', function() {\n", this.requestBody.replace(/'/g, "\\'")));
    out.write(sprintf(indent + "    var expected = %s;\n", this.responseBody));
    out.write(sprintf(indent + "    var actual = JSON.parse(mappingTemplate(%s, %s, {}));\n", JSON.stringify(this.mappingTemplate), JSON.stringify(this.requestBody)));
    out.write(        indent + "    assert.deepEqual(expected, actual);\n");
    out.write(        indent + "  });\n");
    out.write(        indent + "});\n");
  }
};

function extend(target) {
  var sources = [].slice.call(arguments, 1);
  sources.forEach(function (source) {
    for (var prop in source) {
      target[prop] = source[prop];
    }
  });
  return target;
}

function hash(input) {
  var sha1 = crypto.createHash('sha1');
  sha1.update(input);
  return sha1.digest('hex').slice(0, 8);
}

function wait(delay) {
  return function() {
    return new Promise(function(resolve) {
      setTimeout(function() { resolve(); }, delay);
    });
  };
}
<commit_msg>fix(test): wait(d).fn -> wait(d).then(fn)<commit_after>var PRODUCT_NAME="api-gateway-mapping-template-integration-test";

if (!process.env.AWS_ACCOUNT_ID) {
  throw "process.env.AWS_ACCOUNT_ID is required";
}

process.env.AWS_REGION     = process.env.AWS_REGION     || "ap-northeast-1";
process.env.AWS_PROFILE    = process.env.AWS_PROFILE    || PRODUCT_NAME;
process.env.LAMBDA_ROLE    = process.env.LAMBDA_ROLE    || "arn:aws:iam::" + process.env.AWS_ACCOUNT_ID + ":role/" + PRODUCT_NAME;

var stream = require('stream');
var fs = require('fs');
var crypto = require('crypto');

var sprintf = require("sprintf-js").sprintf;
var Zip = require('adm-zip');
var Promise = require('bluebird');

var Endpoint = require('./endpoint');
var post = require('./post');

(function(examples) {
  var zip = new Zip();
  zip.addFile('index.js', new Buffer("exports.handler = function(event, context) { context.succeed(JSON.stringify(event)); };"));

  var requestTemplates = examples.reduce(function(product, e, i) { product["text/test-" + i] = e.template; return product; }, {});
  var responseModels = examples.reduce(function(product, e, i) { product["text/test-" + i] = "Empty"; return product; }, {});
  var responseTemplates = examples.reduce(function(product, e, i) { product["text/test-" + i] = "$input.path('$')"; return product; }, {});

  var endpoint = new Endpoint({
    region: process.env.AWS_REGION,
    accountId: process.env.AWS_ACCOUNT_ID,
    apiName: PRODUCT_NAME,
    lambdaRole: process.env.LAMBDA_ROLE,
    functionName: PRODUCT_NAME + "-console-log",
    functionZip: zip
  });

  endpoint
    .createFunction()
    .createRestApi()
    .putMethod()
    .putIntegration(requestTemplates)
    .putMethodResponse(responseModels)
    .putIntegrationResponse(responseTemplates)
    .addPermission()
    .deploy()
    .then(function() {
      console.log(endpoint.url);

      var q = examples.map(function(ex, i) {
        var req = function() { return post(endpoint.url, extend({}, ex.headers, {'Content-Type': "text/test-" + i}), ex.payload); };
        return req()
          .then(function(res) {
            if (res.statusCode === 500) {
              return wait(1000)().then(req);
            }

            res.headers = ex.headers;
            res.template = ex.template;
            res.payload = ex.payload;
            return res;
          });
      });

      return Promise.all(q);
    })
    .then(function(results) {
      var testOut = process.env.TEST ? fs.createWriteStream(process.env.TEST) : process.stdout;
      var markdownOut = process.env.MD ? fs.createWriteStream(process.env.MD) : process.stdout;

      results = results.map(function(result) {
        var r = new Result();
        r.mappingTemplate = result.template;
        r.requestBody = result.payload;
        r.requestHeaders = result.headers;
        r.statusCode = result.statusCode;
        r.responseBody = result.body;
        return r;
      });

      // write markdown
      results.forEach(function(r, i) {
        markdownOut.write(sprintf("## example-%s\n", hash(JSON.stringify(r))));
        r.toMarkdown(markdownOut);
      });

      // write test
      testOut.write(sprintf("// This file is generated by `TEST=%s node misc/gen.js`\n", process.env.TEST));
      testOut.write("\n");
      testOut.write("var assert = require('assert')\n");
      testOut.write("var mappingTemplate = require('../')\n");
      testOut.write("\n");
      testOut.write("describe('$input.path|$input.json', function() {\n");
      var mdUrl = "https://github.com/ToQoz/api-gateway-mapping-template/blob/master/test/_.md";
      results.forEach(function(r, i) {
        testOut.write(sprintf("  // %s#example-%s\n", mdUrl, hash(JSON.stringify(r))));
        r.toTest(testOut, 1);
      });
      testOut.write("});");

      return "";
    })
    .then(function() {
      endpoint.cleanup();
    })
    .catch(function(err) {
      console.error(err);
      err.stack.split('\n').forEach(function(t) { console.error(t); });
      endpoint.cleanup();
    });
})([
  {
    template: '"$input.path(\'$\')"',
    payload: "a=b",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: '"a=b"',
    headers: {},
  },
  {
    template: '"$input.json(\'$\')"',
    payload: "a=b",
    headers: {},
  },
  {
    template: '"$input.json(\'$\')"',
    payload: '"a=b"',
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "{}",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: '"{}"',
    headers: {},
  },
  {
    template: '"$input.json(\'$\')"',
    payload: "{}",
    headers: {},
  },
  {
    template: '{"name": "$input.path(\'$\')"}',
    payload: "name=toqoz",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "{a",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "a{b",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "[a",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "a[",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "null{",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "true{",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "false{",
    headers: {},
  },
  {
    template: '"$input.path(\'$\')"',
    payload: "undefined{",
    headers: {},
  },
  {
    template: '$input',
    payload: "",
    headers: {},
  },
  {
    template: '$input.keySet',
    payload: "",
    headers: {},
  },
  {
    template: '$input.params.keySet',
    payload: "",
    headers: {},
  },
  {
    template: '$util',
    payload: "",
    headers: {},
  },
  {
    template: '$input.params',
    payload: "",
    headers: {},
  },
  {
    template: '$input.json',
    payload: "",
    headers: {},
  },
  {
    template: '$util.urlEncode',
    payload: "",
    headers: {},
  },
]);

function Result() {
  this.mappingTemplate = null;
  this.requestBody = null;
  this.requestHeaders = null;
  this.statusCode = null;
  this.responseBody = null;
}

Result.prototype.toMarkdown = function(out) {
  var s = "";
  var header = Object.keys(this.requestHeaders).map(function(k) { return k + " : " + this.requestHeaders[k]; }.bind(this)).join("\n");

  var h = ["Template", "Header", "Payload", "Status code", "Result"];
  s += h.join("|") + "\n";
  s += h.map(function(v) { return v.replace(/./g, "-"); }).join("|") + "\n";

  s += [
    "`" + this.mappingTemplate + "`",
    "`" + (header || "None") + "`",
    "`" + this.requestBody + "`",
    "`" + this.statusCode + "`",
    "`" + this.responseBody + "`",
  ].join("|");

  out.write(s);
  out.write("\n\n");
};

Result.prototype.toTest = function(out, indentLevel) {
  var indent = "";
  for (var i = 0; i < indentLevel; i++) {
    indent += "  ";
  }

  if (this.statusCode !== 200 || this.responseBody.indexOf("{errorMessage=Unable") === 0) {
    out.write(sprintf(indent + "describe('H=`%s` P=`%s` ===> T=`%s`', function() {\n", JSON.stringify(this.requestHeaders).replace(/'/g, "\\'"), this.requestBody.replace(/'/g, "\\'"), this.mappingTemplate.replace(/'/g, "\\'")));
    out.write(indent         + "  it('throw error', function() {\n");
    out.write(sprintf(indent + "    assert.throws(function() { mappingTemplate(%s, %s, {}); });\n", JSON.stringify(this.mappingTemplate), JSON.stringify(this.requestBody)));
    out.write(        indent + "  });\n");
    out.write(        indent + "});\n");
  } else {
    out.write(sprintf(indent + "describe('H=`%s` P=`%s` ===> T=`%s`', function() {\n", JSON.stringify(this.requestHeaders).replace(/'/g, "\\'"), this.requestBody.replace(/'/g, "\\'"), this.mappingTemplate.replace(/'/g, "\\'")));
    out.write(sprintf(indent + "  it('return %s', function() {\n", this.requestBody.replace(/'/g, "\\'")));
    out.write(sprintf(indent + "    var expected = %s;\n", this.responseBody));
    out.write(sprintf(indent + "    var actual = JSON.parse(mappingTemplate(%s, %s, {}));\n", JSON.stringify(this.mappingTemplate), JSON.stringify(this.requestBody)));
    out.write(        indent + "    assert.deepEqual(expected, actual);\n");
    out.write(        indent + "  });\n");
    out.write(        indent + "});\n");
  }
};

function extend(target) {
  var sources = [].slice.call(arguments, 1);
  sources.forEach(function (source) {
    for (var prop in source) {
      target[prop] = source[prop];
    }
  });
  return target;
}

function hash(input) {
  var sha1 = crypto.createHash('sha1');
  sha1.update(input);
  return sha1.digest('hex').slice(0, 8);
}

function wait(delay) {
  return function() {
    return new Promise(function(resolve) {
      setTimeout(function() { resolve(); }, delay);
    });
  };
}
<|endoftext|>