<commit_before>var jsTestUtil = (function() {
  var readFileUnavailable = false;
  var readFile = function(url, callbacks) {
    var onsuccess = callbacks.onsuccess || function() {};
    var onfail = callbacks.onfail || function() {};
    try {
      var xhr = new XMLHttpRequest();
      xhr.onload = function() {
        if (xhr.status === 200) {
          onsuccess(xhr.response);
        } else {
          onfail('XHR failed: ' + xhr.statusText);
        }
      };
      xhr.onerror = function() {
        onfail('XHR error occurred');
      };
      xhr.onabort = function() {
        onfail('XHR aborted');
      };
      xhr.open('GET', url);
      xhr.responseType = 'arraybuffer';
      xhr.send();
    } catch (e) {
      onfail('XHR failed to start');
    }
  };
  var dataURIFromArrayBuffer = function(ab) {
    var array = new Uint8Array(ab);
    var str = Array.from(
        array,
        function(code) { return String.fromCharCode(code); }
    ).join('');
    return 'data:application/octet-stream;base64,' + btoa(str);
  };
  var makeSelfTest = function() {
    var run = function(done) {
      readFile('data/hello.txt', {
        onsuccess: function(response) {
          var view = new Uint8Array(response);
          EXPECT_EQ( 14, view.length );
          EXPECT_EQ( 72 /* H */, view[0] );
          EXPECT_EQ( 33 /* ! */, view[12] );
          EXPECT_EQ( 10 /* \n */, view[13] );
          var datauri = dataURIFromArrayBuffer(response);
          var binary = compareUtil.binaryFromDataURI(datauri);
          EXPECT_EQ( 14, binary.length );
          EXPECT_EQ( 72 /* H */, binary.at(0) );
          EXPECT_EQ( 33 /* ! */, binary.at(12) );
          EXPECT_EQ( 10 /* \n */, binary.at(13) );
          done();
        },
        onfail: function(message) {
          WARN(message);
          WARN('Since XHR seems to be unavailable, all tests using data files will be skipped.');
          readFileUnavailable = true;
          done();
        }
      });
    };
    return { run: run };
  };
  var makeFileBasedTestRunner = function() {
    var doneXHR = 0;
    var failXHR = 0;
    var tests = [];
    var readFileAndTest = function(url, callback) {
      var test = function(done) {
        readFile(url, {
          onsuccess : function(response) {
            callback(response, function() {
              done();
            });
          },
          onfail: function(message) {
            ERROR('File cannot be read (' + message + '): "' + url + '" ');
            failXHR += 1;
            done();
          }
        });
      };
      tests.push(test);
    };
    var run = function(done) {
      if (readFileUnavailable) {
        WARN('skipped');
        done();
        return;
      }
      for (var i = 0; i < tests.length; i++) {
        tests[i](function() {
          doneXHR += 1;
          if (tests.length === doneXHR) {
            done();
          }
        });
      }
    };
    return {
      readFileAndTest: readFileAndTest,
      run: run
    };
  };
  return {
    readFile: readFile,
    dataURIFromArrayBuffer: dataURIFromArrayBuffer,
    makeSelfTest: makeSelfTest,
    makeFileBasedTestRunner: makeFileBasedTestRunner
  };
})();
<commit_msg>Add makeSequentialTest() to jstest-util.js #34<commit_after>var jsTestUtil = (function() {
  var makeSequentialTest = function(tests) {
    return function(done) {
      var runRest = done;
      for (var i = tests.length; i-- > 0; ) {
        runRest = (function(func, runRest) {
          return function() {
            if (func.length === 0) {
              func();
              runRest();
            } else {
              func(runRest);
            }
          };
        })(tests[i], runRest);
      }
      runRest();
    };
  };
  var readFileUnavailable = false;
  var readFile = function(url, callbacks) {
    var onsuccess = callbacks.onsuccess || function() {};
    var onfail = callbacks.onfail || function() {};
    try {
      var xhr = new XMLHttpRequest();
      xhr.onload = function() {
        if (xhr.status === 200) {
          onsuccess(xhr.response);
        } else {
          onfail('XHR failed: ' + xhr.statusText);
        }
      };
      xhr.onerror = function() {
        onfail('XHR error occurred');
      };
      xhr.onabort = function() {
        onfail('XHR aborted');
      };
      xhr.open('GET', url);
      xhr.responseType = 'arraybuffer';
      xhr.send();
    } catch (e) {
      onfail('XHR failed to start');
    }
  };
  var dataURIFromArrayBuffer = function(ab) {
    var array = new Uint8Array(ab);
    var str = Array.from(
        array,
        function(code) { return String.fromCharCode(code); }
    ).join('');
    return 'data:application/octet-stream;base64,' + btoa(str);
  };
  var makeSelfTest = function() {
    var readFileTest = function(done) {
      readFile('data/hello.txt', {
        onsuccess: function(response) {
          var view = new Uint8Array(response);
          EXPECT_EQ( 14, view.length );
          EXPECT_EQ( 72 /* H */, view[0] );
          EXPECT_EQ( 33 /* ! */, view[12] );
          EXPECT_EQ( 10 /* \n */, view[13] );
          done();
        },
        onfail: function(message) {
          WARN(message);
          WARN('Since XHR seems to be unavailable, all tests using data files will be skipped.');
          readFileUnavailable = true;
          done();
        }
      });
    };
    var dataURIFromArrayBufferTest = function() {
      var ab = new ArrayBuffer(14);
      var u8 = new Uint8Array(ab);
      // 'Hello, world!\n'
      u8[0] = 72;
      u8[1] = 101;
      u8[2] = 108;
      u8[3] = 108;
      u8[4] = 111;
      u8[5] = 44;
      u8[6] = 32;
      u8[7] = 119;
      u8[8] = 111;
      u8[9] = 114;
      u8[10] = 108;
      u8[11] = 100;
      u8[12] = 33;
      u8[13] = 10;
      var datauri = dataURIFromArrayBuffer(ab);
      EXPECT_EQ( 'data:application/octet-stream;base64,SGVsbG8sIHdvcmxkIQo=', datauri );
    };
    var run = makeSequentialTest([
      readFileTest,
      dataURIFromArrayBufferTest,
    ]);
    return { run: run };
  };
  var makeFileBasedTestRunner = function() {
    var doneXHR = 0;
    var failXHR = 0;
    var tests = [];
    var readFileAndTest = function(url, callback) {
      var test = function(done) {
        readFile(url, {
          onsuccess : function(response) {
            callback(response, function() {
              done();
            });
          },
          onfail: function(message) {
            ERROR('File cannot be read (' + message + '): "' + url + '" ');
            failXHR += 1;
            done();
          }
        });
      };
      tests.push(test);
    };
    var run = function(done) {
      if (readFileUnavailable) {
        WARN('skipped');
        done();
        return;
      }
      for (var i = 0; i < tests.length; i++) {
        tests[i](function() {
          doneXHR += 1;
          if (tests.length === doneXHR) {
            done();
          }
        });
      }
    };
    return {
      readFileAndTest: readFileAndTest,
      run: run
    };
  };
  return {
    makeSequentialTest: makeSequentialTest,
    readFile: readFile,
    dataURIFromArrayBuffer: dataURIFromArrayBuffer,
    makeSelfTest: makeSelfTest,
    makeFileBasedTestRunner: makeFileBasedTestRunner
  };
})();
<|endoftext|>


<commit_before>/*globals document*/

import React from 'react';
import Header from './Header/Header';
import Footer from './Footer/Footer';
import ApplicationStore from '../stores/ApplicationStore';
import { connectToStores, provideContext } from 'fluxible-addons-react';
import { handleHistory } from 'fluxible-router';
import pages from '../configs/routes';
import ErrorStore from '../stores/ErrorStore';
import Error from './Error/Error';
import ServiceErrorStore from '../stores/ServiceErrorStore';
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';

class Application extends React.Component {
    render() {
        let Handler = this.props.currentRoute.handler;
        if (this.props.ErrorStore.error) {
            return (

                    <Error error={this.props.ErrorStore.error} />

            );
        }
        else if (this.props.ServiceErrorStore.error) {
            const error = this.props.ServiceErrorStore.error;
            return (

                    {error.hasOwnProperty('statusCode') ?

                                                                {error.statusCode} {error.statusText}

Description: {error.description} {error.actionRequired}

Navigation: {error.navStack}

: ''
                    }
                <Handler />

            );
        }
        else {
            return (

                    <Handler />

            );
        }
    }

    componentDidUpdate(prevProps, prevState) {
        const newProps = this.props;
        if (newProps.pageTitle === prevProps.pageTitle) {
            return;
        }
        document.title = newProps.pageTitle;
    }
}

export default provideContext(handleHistory(connectToStores(
    DragDropContext(HTML5Backend)(Application),
    [ApplicationStore, ErrorStore, ServiceErrorStore],
    (context, props) => {
        let appStore = context.getStore(ApplicationStore);
        return {
            pageTitle: appStore.getPageTitle(),
            ErrorStore: context.getStore(ErrorStore).getState(),
            ServiceErrorStore: context.getStore(ServiceErrorStore).getState(),
        };
    }
)));
<commit_msg>Removing ServiceErrorStore and JSX code for service unavailable. We have it in Deck.js so not needed here<commit_after>/*globals document*/

import React from 'react';
import Header from './Header/Header';
import Footer from './Footer/Footer';
import ApplicationStore from '../stores/ApplicationStore';
import { connectToStores, provideContext } from 'fluxible-addons-react';
import { handleHistory } from 'fluxible-router';
import pages from '../configs/routes';
import ErrorStore from '../stores/ErrorStore';
import Error from './Error/Error';
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';

class Application extends React.Component {
    render() {
        let Handler = this.props.currentRoute.handler;
        if (this.props.ErrorStore.error) {
            return (

                    <Error error={this.props.ErrorStore.error} />

            );
        }
        else {
            return (

                    <Handler />

            );
        }
    }

    componentDidUpdate(prevProps, prevState) {
        const newProps = this.props;
        if (newProps.pageTitle === prevProps.pageTitle) {
            return;
        }
        document.title = newProps.pageTitle;
    }
}

export default provideContext(handleHistory(connectToStores(
    DragDropContext(HTML5Backend)(Application),
    [ApplicationStore, ErrorStore],
    (context, props) => {
        let appStore = context.getStore(ApplicationStore);
        return {
            pageTitle: appStore.getPageTitle(),
            ErrorStore: context.getStore(ErrorStore).getState(),
        };
    }
)));
<|endoftext|>


<commit_before>// @flow
import type { Dispatch, KnxAddress, Prefs, Rooms, State } from '../types';

import * as React from 'react';
import { connect } from 'react-redux';
import { withStyles } from 'material-ui/styles';
import { toggleAddrVal } from '../lib/shared/address-utils';

import { compose } from 'ramda';

import Light from './mapIcons/Light';

type Props = {
  addresses: Array<KnxAddress>,
  prefs: Prefs,
  rooms: Rooms,
  classes: Object,
  dispatch: Dispatch,
};

const styles = {
  furniture: {
    pointerEvents: 'none',
  },
  actorIcon: {
    pointerEvents: 'none',
  },
  lightOn: {
    color: 'orange',
  },
};

const Groundfloor = ({ addresses, dispatch, classes }: Props) => {
  const isOn = addr => addresses[addr].value;
  const onLightSwitch = addrId =>
    dispatch({
      type: 'WRITE_GROUP_ADDRESS',
      addr: toggleAddrVal(addresses[addrId]),
    });

  return (
    <svg
      id="SVGRoot"
      version="1.1"
      viewBox="0 0 800 600"
      preserveAspectRatio="xMidYMid meet"
      onClick={e => onLightSwitch(e.target.id)}
    >
      <g stroke="#000" className={classes.furniture}>
        <path
          d="m290 30v90h-280v470.94h770v-470.94l-250 1.6002v-91.6z"
          fill="#fff"
          strokeWidth="1.0188"
        />
        <g fill="none" className={classes.furniture}>
          <path d="m10 470h210v120" strokeWidth=".723px" />
          <path d="m220 470v-180h-210" strokeWidth=".82158px" />
          <path
            d="m510 590v-270l-40-40h-50l-75 75 55 55 50-50 60 60"
            strokeWidth="1px"
          />
          <path d="m390 590v-50l120-120" strokeWidth="1px" />
        </g>
      </g>
      <g fill="#e9e9e9" stroke="#000">
        <rect
          x="600"
          y="290"
          width="110"
          height="220"
          strokeDasharray="0.14832843, 0.14832843000000001"
          strokeWidth=".59331"
          className={classes.furniture}
        />
        <g strokeDasharray="0.16125, 0.16125000000000000" strokeWidth=".645">
          <rect
            x="570"
            y="150"
            width="170"
            height="70"
            className={classes.furniture}
          />
          <rect
            x="40"
            y="470"
            width="80"
            height="60"
            className={classes.furniture}
          />
          <rect
            x="30"
            y="560"
            width="190"
            height="30"
            className={classes.furniture}
          />
          <circle opacity="0.10" cx="505.22" cy="274.66" r="25" />
        </g>
        <rect
          x="250"
          y="120"
          width="40"
          height="110"
          strokeDasharray="0.16912043, 0.16912042999999999"
          strokeWidth=".67648"
          className={classes.furniture}
        />
        <rect
          x="220"
          y="130"
          width="30"
          height="90"
          strokeDasharray="0.15834435, 0.15834434999999999"
          strokeWidth=".63338"
          className={classes.furniture}
        />
        <rect
          x="220"
          y="540"
          width="40"
          height="50"
          strokeDasharray="0.14720044, 0.14720043999999999"
          strokeWidth=".5888"
          className={classes.furniture}
        />
        <g strokeDasharray="0.16125, 0.16125000000000000" strokeWidth=".645">
          <rect x="550" y="560" width="230" height="30" />
          <rect x="510" y="320" width="40" height="270" />
          <rect x="10" y="260" width="210" height="30" />
        </g>
      </g>
      <g className="actors">
        <Light
          id="1/2/4"
          desc="Deckenleuchte Esszimmer"
          isOn={isOn('1/2/4')}
          className="actorIcon"
          x="650"
          y="180"
        />
        <Light
          id="1/2/1"
          desc="4er-Leuchte Küche"
          className="actorIcon"
          isOn={isOn('1/2/1')}
          x="645"
          y="320"
        />
        <Light
          id="1/2/7"
          desc="Deckenleuchten Küche"
          className="actorIcon"
          isOn={isOn('1/2/7')}
          x="540"
          y="420"
        />
        <Light
          id="1/2/13"
          desc="Wandlampen Wohnzimmer"
          className="actorIcon"
          isOn={isOn('1/2/13')}
          x="760"
          y="180"
        />
        <Light
          id="1/2/14"
          desc="Schrankleuchte Nordwand-Wohnzimmer"
          className="actorIcon"
          isOn={isOn('1/2/14')}
          x="100"
          y="265"
        />
        <Light
          id="1/2/3"
          desc=" Deckenleuchte Büro / Emma"
          className="actorIcon"
          isOn={isOn('1/2/3')}
          x="100"
          y="360"
        />
        <Light
          id="1/2/12"
          desc="Hängelampe Büro / Emma"
          className="actorIcon"
          isOn={isOn('1/2/12')}
          x="100"
          y="445"
        />
        <Light
          id="1/2/0"
          desc="Deckenleuchte Technik"
          className="actorIcon"
          isOn={isOn('1/2/0')}
          x="160"
          y="500"
        />
        <Light
          id="1/2/11"
          desc="Wandlampe Flur"
          className="actorIcon"
          isOn={isOn('1/2/11')}
          x="220"
          y="390"
        />
        <Light
          id="1/2/5"
          desc="Deckenleuchte Flur"
          className="actorIcon"
          isOn={isOn('1/2/5')}
          x="300"
          y="450"
        />
        <Light
          id="1/2/6"
          desc="Deckenleuchte Gäste-WC"
          className="actorIcon"
          isOn={isOn('1/2/6')}
          x="440"
          y="540"
        />
        <Light
          id="1/2/10"
          desc="Deckenleuchte Wohnzimmer Erker Ost"
          className="actorIcon"
          isOn={isOn('1/2/10')}
          x="110"
          y="180"
        />
        <Light
          id="1/2/15"
          desc="Deckenleuchte Wohnzimmer/Essen Mitte"
          className="actorIcon"
          isOn={isOn('1/2/15')}
          x="400"
          y="170"
        />
      </g>
    </svg>
  );
};

export default compose(
  connect((state: State) => ({
    addresses: state.smartHome.livestate,
    prefs: state.app.prefs,
    rooms: state.app.rooms,
  })),
  withStyles(styles)
)(Groundfloor);
<commit_msg>Add missing light on groundfloor map and adjust Kitchen light position<commit_after>// @flow
import type { Dispatch, KnxAddress, Prefs, Rooms, State } from '../types';

import * as React from 'react';
import { connect } from 'react-redux';
import { withStyles } from 'material-ui/styles';
import { toggleAddrVal } from '../lib/shared/address-utils';

import { compose } from 'ramda';

import Light from './mapIcons/Light';

type Props = {
  addresses: Array<KnxAddress>,
  prefs: Prefs,
  rooms: Rooms,
  classes: Object,
  dispatch: Dispatch,
};

const styles = {
  furniture: {
    pointerEvents: 'none',
  },
  actorIcon: {
    pointerEvents: 'none',
  },
  lightOn: {
    color: 'orange',
  },
};

const Groundfloor = ({ addresses, dispatch, classes }: Props) => {
  const isOn = addr => addresses[addr].value;
  const onLightSwitch = addrId =>
    dispatch({
      type: 'WRITE_GROUP_ADDRESS',
      addr: toggleAddrVal(addresses[addrId]),
    });

  return (
    <svg
      id="SVGRoot"
      version="1.1"
      viewBox="0 0 800 600"
      preserveAspectRatio="xMidYMid meet"
      onClick={e => onLightSwitch(e.target.id)}
    >
      <g stroke="#000" className={classes.furniture}>
        <path
          d="m290 30v90h-280v470.94h770v-470.94l-250 1.6002v-91.6z"
          fill="#fff"
          strokeWidth="1.0188"
        />
        <g fill="none" className={classes.furniture}>
          <path d="m10 470h210v120" strokeWidth=".723px" />
          <path d="m220 470v-180h-210" strokeWidth=".82158px" />
          <path
            d="m510 590v-270l-40-40h-50l-75 75 55 55 50-50 60 60"
            strokeWidth="1px"
          />
          <path d="m390 590v-50l120-120" strokeWidth="1px" />
        </g>
      </g>
      <g fill="#e9e9e9" stroke="#000">
        <rect
          x="600"
          y="290"
          width="110"
          height="220"
          strokeDasharray="0.14832843, 0.14832843000000001"
          strokeWidth=".59331"
          className={classes.furniture}
        />
        <g strokeDasharray="0.16125, 0.16125000000000000" strokeWidth=".645">
          <rect
            x="570"
            y="150"
            width="170"
            height="70"
            className={classes.furniture}
          />
          <rect
            x="40"
            y="470"
            width="80"
            height="60"
            className={classes.furniture}
          />
          <rect
            x="30"
            y="560"
            width="190"
            height="30"
            className={classes.furniture}
          />
          <circle opacity="0.10" cx="505.22" cy="274.66" r="25" />
        </g>
        <rect
          x="250"
          y="120"
          width="40"
          height="110"
          strokeDasharray="0.16912043, 0.16912042999999999"
          strokeWidth=".67648"
          className={classes.furniture}
        />
        <rect
          x="220"
          y="130"
          width="30"
          height="90"
          strokeDasharray="0.15834435, 0.15834434999999999"
          strokeWidth=".63338"
          className={classes.furniture}
        />
        <rect
          x="220"
          y="540"
          width="40"
          height="50"
          strokeDasharray="0.14720044, 0.14720043999999999"
          strokeWidth=".5888"
          className={classes.furniture}
        />
        <g strokeDasharray="0.16125, 0.16125000000000000" strokeWidth=".645">
          <rect x="550" y="560" width="230" height="30" />
          <rect x="510" y="320" width="40" height="270" />
          <rect x="10" y="260" width="210" height="30" />
        </g>
      </g>
      <g className="actors">
        <Light
          id="1/2/4"
          desc="Deckenleuchte Esszimmer"
          isOn={isOn('1/2/4')}
          className="actorIcon"
          x="650"
          y="180"
        />
        <Light
          id="1/2/1"
          desc="4er-Leuchte Küche"
          className="actorIcon"
          isOn={isOn('1/2/1')}
          x="645"
          y="320"
        />
        <Light
          id="1/2/7"
          desc="Deckenleuchten Küche"
          className="actorIcon"
          isOn={isOn('1/2/7')}
          x="550"
          y="430"
        />
        <Light
          id="1/2/13"
          desc="Wandlampen Wohnzimmer"
          className="actorIcon"
          isOn={isOn('1/2/13')}
          x="760"
          y="180"
        />
        <Light
          id="1/2/14"
          desc="Schrankleuchte Nordwand-Wohnzimmer"
          className="actorIcon"
          isOn={isOn('1/2/14')}
          x="100"
          y="265"
        />
        <Light
          id="1/2/3"
          desc=" Deckenleuchte Büro / Emma"
          className="actorIcon"
          isOn={isOn('1/2/3')}
          x="100"
          y="360"
        />
        <Light
          id="1/2/12"
          desc="Hängelampe Büro / Emma"
          className="actorIcon"
          isOn={isOn('1/2/12')}
          x="100"
          y="445"
        />
        <Light
          id="1/2/0"
          desc="Deckenleuchte Technik"
          className="actorIcon"
          isOn={isOn('1/2/0')}
          x="160"
          y="500"
        />
        <Light
          id="1/2/11"
          desc="Wandlampe Flur"
          className="actorIcon"
          isOn={isOn('1/2/11')}
          x="220"
          y="390"
        />
        <Light
          id="1/2/5"
          desc="Deckenleuchte Flur"
          className="actorIcon"
          isOn={isOn('1/2/5')}
          x="300"
          y="450"
        />
        <Light
          id="1/2/6"
          desc="Deckenleuchte Gäste-WC"
          className="actorIcon"
          isOn={isOn('1/2/6')}
          x="440"
          y="540"
        />
        <Light
          id="1/2/10"
          desc="Deckenleuchte Wohnzimmer Erker Ost"
          className="actorIcon"
          isOn={isOn('1/2/10')}
          x="110"
          y="180"
        />
        <Light
          id="1/2/15"
          desc="Deckenleuchte Wohnzimmer/Essen Mitte"
          className="actorIcon"
          isOn={isOn('1/2/15')}
          x="400"
          y="170"
        />
        <Light
          id="11/1/0"
          desc="Wohnzimmer Steckdose / Stehlampe Erker Rechts"
          className="actorIcon"
          isOn={isOn('11/1/0')}
          x="505"
          y="35"
        />
      </g>
    </svg>
  );
};

export default compose(
  connect((state: State) => ({
    addresses: state.smartHome.livestate,
    prefs: state.app.prefs,
    rooms: state.app.rooms,
  })),
  withStyles(styles)
)(Groundfloor);
<|endoftext|>


<commit_before>import React, {
  Component,
  Image,
  ScrollView,
  StyleSheet,
  Text,
  View
} from 'react-native';
import moment from 'moment';

import api from '../utils/api';
import colors from '../utils/colors';

const styles = StyleSheet.create({
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  container: {
    marginTop: 44
  },
  heroImage: {
    flex: 1,
    height: 150
  },
  body: {
    margin: 10
  },
  sectionContainer: {
    paddingVertical: 10,
    borderColor: colors.border,
    borderBottomWidth: 0.5,

    flex: 1,
    flexDirection: 'row'
  },
  sectionLabels: {
    flex: 1,
    paddingRight: 20
  },
  sectionContent: {
    flex: 3
  },
  title: {
    fontSize: 20,
    fontWeight: '500'
  },
  sectionLabel: {
    textAlign: 'right'
  },
  description: {
    color: colors.lightFont
  }
});

class MovieDetail extends Component {
  // Constructor
  constructor(props) {
    super(props);

    this.state = {
      movieDetail: {},
      loaded: false
    }
  }

  // Lifecycle
  componentDidMount() {
    this.fetchData(this.props.movie.id);
  }

  // Functions
  fetchData(movieID) {
    api.loadMovieDetail(movieID)
      .then(response => {
        this.setState({
          movieDetail: response,
          loaded: true
        });
      })
      .done();
  }

  renderLoadingView() {
    return (
      <View style={styles.loadingContainer}>
        <Text>Loading details...</Text>
      </View>
    );
  }

  // Render
  render() {
    // Check if movie's details have loaded
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    const movie = this.props.movie;
    const movieDetail = this.state.movieDetail;
    const movieGenres = movieDetail.genres.map(g => g.name);

    return (
      <ScrollView style={styles.container}>
        <Image
          style={styles.heroImage}
          source={{uri: api.getPoster(movie.backdrop_path, 'w1280')}}
        />

        <View style={styles.body}>
          <Text style={styles.title}>{movie.title}</Text>

          {/* Metadata */}
          <View style={styles.sectionContainer}>
            <View style={styles.sectionLabels}>
              <Text style={styles.sectionLabel}>Released</Text>
              <Text style={styles.sectionLabel}>Rating</Text>
              <Text style={styles.sectionLabel}>Run Time</Text>
              <Text style={styles.sectionLabel}>Genres</Text>
            </View>

            <View style={styles.sectionContent}>
              <Text style={styles.description}>
                {moment(movie.release_date).format('LL')}
              </Text>

              <Text style={styles.description}>
                {parseFloat(movie.vote_average).toFixed(1)} / 10.0 ({movie.vote_count} votes)
              </Text>

              <Text style={styles.description}>
                {movieDetail.runtime} minutes
              </Text>

              <Text style={styles.description}>
                {movieGenres.join(', ')}
              </Text>
            </View>
          </View>

          {/* Overview */}
          <View style={styles.sectionContainer}>
            <View style={styles.sectionLabels}>
              <Text style={styles.sectionLabel}>Overview</Text>
            </View>

            <View style={styles.sectionContent}>
              <Text style={styles.description}>
                {movieDetail.overview}
              </Text>
            </View>
          </View>
        </View>
      </ScrollView>
    );
  }
}

export default MovieDetail;
<commit_msg>Updated MovieDetail section's margin and padding<commit_after>import React, {
  Component,
  Image,
  ScrollView,
  StyleSheet,
  Text,
  View
} from 'react-native';
import moment from 'moment';

import api from '../utils/api';
import colors from '../utils/colors';

const styles = StyleSheet.create({
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  container: {
    marginTop: 44
  },
  heroImage: {
    flex: 1,
    height: 150
  },
  body: {
    marginTop: 10,
    marginLeft: 10
  },
  sectionContainer: {
    paddingVertical: 10,
    paddingRight: 10,
    borderColor: colors.border,
    borderBottomWidth: 0.5,

    flex: 1,
    flexDirection: 'row'
  },
  sectionLabels: {
    flex: 1,
    paddingRight: 20
  },
  sectionContent: {
    flex: 3
  },
  title: {
    fontSize: 20,
    fontWeight: '500'
  },
  sectionLabel: {
    textAlign: 'right'
  },
  description: {
    color: colors.lightFont
  }
});

class MovieDetail extends Component {
  // Constructor
  constructor(props) {
    super(props);

    this.state = {
      movieDetail: {},
      loaded: false
    }
  }

  // Lifecycle
  componentDidMount() {
    this.fetchData(this.props.movie.id);
  }

  // Functions
  fetchData(movieID) {
    api.loadMovieDetail(movieID)
      .then(response => {
        this.setState({
          movieDetail: response,
          loaded: true
        });
      })
      .done();
  }

  renderLoadingView() {
    return (
      <View style={styles.loadingContainer}>
        <Text>Loading details...</Text>
      </View>
    );
  }

  // Render
  render() {
    // Check if movie's details have loaded
    if (!this.state.loaded) {
      return this.renderLoadingView();
    }

    const movie = this.props.movie;
    const movieDetail = this.state.movieDetail;
    const movieGenres = movieDetail.genres.map(g => g.name);

    return (
      <ScrollView style={styles.container}>
        <Image
          style={styles.heroImage}
          source={{uri: api.getPoster(movie.backdrop_path, 'w1280')}}
        />

        <View style={styles.body}>
          <Text style={styles.title}>{movie.title}</Text>

          {/* Metadata */}
          <View style={styles.sectionContainer}>
            <View style={styles.sectionLabels}>
              <Text style={styles.sectionLabel}>Released</Text>
              <Text style={styles.sectionLabel}>Rating</Text>
              <Text style={styles.sectionLabel}>Run Time</Text>
              <Text style={styles.sectionLabel}>Genres</Text>
            </View>

            <View style={styles.sectionContent}>
              <Text style={styles.description}>
                {moment(movie.release_date).format('LL')}
              </Text>

              <Text style={styles.description}>
                {parseFloat(movie.vote_average).toFixed(1)} / 10.0 ({movie.vote_count} votes)
              </Text>

              <Text style={styles.description}>
                {movieDetail.runtime} minutes
              </Text>

              <Text style={styles.description}>
                {movieGenres.join(', ')}
              </Text>
            </View>
          </View>

          {/* Overview */}
          <View style={styles.sectionContainer}>
            <View style={styles.sectionLabels}>
              <Text style={styles.sectionLabel}>Overview</Text>
            </View>

            <View style={styles.sectionContent}>
              <Text style={styles.description}>
                {movieDetail.overview}
              </Text>
            </View>
          </View>
        </View>
      </ScrollView>
    );
  }
}

export default MovieDetail;
<|endoftext|>


<commit_before>'use strict';

const express = require('express');
const chai = require('chai');
const request = require('supertest');
const multer = require('multer');
const mongo = require('mongodb');
const md5File = require('md5-file');
const fs = require('fs');
const sinon = require('sinon');
const crypto = require('crypto');
const utils = require('../lib/utils');
const GridFsStorage = require('../index');
const setting = require('./utils/settings');
const testUtils = require('./utils/testutils');
const files = testUtils.files;
const cleanStorage = testUtils.cleanStorage;
const getDb = testUtils.getDb;
const getClient = testUtils.getClient;

const MongoClient = mongo.MongoClient;
const expect = chai.expect;

describe('Backwards compatibility', () => {
  let result, app, storage, size;

  before((done) => {
    app = express();
    fs.readFile(files[0], (err, f) => {
      size = f.length;
      done(err);
    });
  });

  describe('Using GridStore streams to save files', () => {
    before((done) => {
      storage = new GridFsStorage({url: setting.mongoUrl});
      storage._legacy = true;

      const upload = multer({storage});

      app.post('/store', upload.single('photos'), (req, res) => {
        result = {headers: req.headers, file: req.file, body: req.body};
        res.end();
      }, (err, req, res, next) => {
        console.log(err);
        next();
      });

      storage.on('connection', () => {
        request(app)
          .post('/store')
          .attach('photos', files[0])
          .end(done);
      });
    });

    it('should store the files on upload', () => {
      expect(result.file).to.be.a('object');
    });

    it('should have each stored file the same MD5 signature than the uploaded file', () => {
      expect(result.file.md5).to.be.equal(md5File(files[0]));
    });

    it('should have a filename property', () => {
      expect(result.file).to.have.a.property('filename');
      expect(result.file.filename).to.be.a('string');
      expect(result.file.filename).to.match(/^[0-9a-f]{32}$/);
    });

    it('should have a metadata property', () => {
      expect(result.file).to.have.a.property('metadata');
      expect(result.file.metadata).to.equal(null);
    });

    it('should have a id property', () => {
      expect(result.file).to.have.a.property('id');
      expect(result.file.id).to.match(/^[0-9a-f]{24}$/);
    });

    it('should have a size property with the length of the file', () => {
      expect(result.file).to.have.a.property('size');
      expect(result.file.size).to.equal(size);
    });

    it('should have the default bucket name pointing to the fs collection', () => {
      expect(result.file).to.have.a.property('bucketName');
      expect(result.file.bucketName).to.equal('fs');
    });

    it('should have the date of the upload', () => {
      expect(result.file).to.have.a.property('uploadDate');
    });

    after(() => cleanStorage(storage));

  });

  describe('Changing file configuration', () => {
    before((done) => {
      let counter = 0;
      const data = ['foo', 'bar'];
      const sizes = [102400, 204800];
      const names = ['plants', 'animals'];
      const contentTypes = ['text/plain', 'image/jpeg'];
      storage = new GridFsStorage({
        url: setting.mongoUrl,
        file: () => {
          counter++;
          return {
            filename: 'file' + counter,
            metadata: data[counter - 1],
            id: counter,
            chunkSize: sizes[counter - 1],
            bucketName: names[counter - 1],
            contentType: contentTypes[counter - 1],
          };
        },
      });
      // Set to use GridStore
      storage._legacy = true;

      const upload = multer({storage});

      app.post('/config', upload.array('photos', 2), (req, res) => {
        result = {headers: req.headers, files: req.files, body: req.body};
        res.end();
      });

      storage.on('connection', () => {
        request(app)
          .post('/config')
          .attach('photos', files[0])
          .attach('photos', files[1])
          .end(done);
      });
    });

    it('should the request contain the two uploaded files', () => {
      expect(result.files).to.be.an('array');
      expect(result.files).to.have.length(2);
    });

    it('should be named with the provided value', () => {
      expect(result.files[0].filename).to.equal('file1');
      expect(result.files[1].filename).to.equal('file2');
    });

    it('should contain a metadata object with the provided object', () => {
      expect(result.files[0].metadata).to.equal('foo');
      expect(result.files[1].metadata).to.equal('bar');
    });

    it('should be stored with the provided chunkSize value', () => {
      expect(result.files[0].chunkSize).to.equal(102400);
      expect(result.files[1].chunkSize).to.equal(204800);
    });

    it('should change the id with the provided value', () => {
      expect(result.files[0].id).to.equal(1);
      expect(result.files[1].id).to.equal(2);
    });

    it('should be stored under in a collection with the provided value', function (done) {
      const db = storage.db;
      db.collection('plants.files', {strict: true}, function (err) {
        expect(err).to.be.equal(null);
        db.collection('animals.files', {strict: true}, function (err) {
          expect(err).to.be.equal(null);
          done();
        });
      });
    });

    it('should change the content type with the provided value', () => {
      expect(result.files[0].contentType).to.equal('text/plain');
      expect(result.files[1].contentType).to.equal('image/jpeg');
    });

    after(() => cleanStorage(storage));

  });

  describe('Using GridStore to delete files', () => {
    let error, db, client;

    before(() => {
      return MongoClient.connect(setting.mongoUrl).then(_db => {
        db = getDb(_db);
        client = getClient(_db);
        storage = new GridFsStorage({db});
        storage._legacy = true;

        const upload = multer({storage});

        app.post('/delete', upload.array('photos', 1), (err, req, res, next) => {
          result = {headers: req.headers, body: req.body, files: req.files};
          error = err;
          next();
        });

        return new Promise((resolve) => {
          storage.on('connection', () => {
            request(app)
              .post('/delete')
              .attach('photos', files[0])
              .attach('photos', files[1])
              .end(() => resolve());
          });
        });
      });
    });

    it('should remove the files on upload', () => {
      expect(error.storageErrors).to.have.lengthOf(0);
    });

    it('should not have any files stored in the database', () => {
      mongo.GridStore.list(db).then((files) => {
        expect(files).to.have.lengthOf(0);
      });
    });

    after(() => cleanStorage(storage, db, client));
  });

  describe('GridStore open error', () => {
    const errorSpy = sinon.spy();
    const fileSpy = sinon.spy();
    const err = new Error();

    before((done) => {
      sinon.stub(mongo, 'GridStore').returns({
        stream: sinon.stub().returns({
          on: sinon.stub(),
          gs: {
            open: sinon.stub().callsFake(cb => cb(err)),
          },
        }),
      });
      storage = GridFsStorage({url: setting.mongoUrl});
      storage._legacy = true;
      storage.on('streamError', errorSpy);
      storage.on('file', fileSpy);

      const upload = multer({storage});

      app.post('/storeopen', upload.single('photo'), (err, req, res, next) => { // eslint-disable-line no-unused-vars
        res.end();
      });

      request(app)
        .post('/storeopen')
        .attach('photo', files[0])
        .end(done);
    });

    it('should emit an error event when the store fails to open', () => {
      expect(errorSpy).to.be.calledOnce;
      expect(fileSpy).not.to.be.called;
      const call = errorSpy.getCall(0);
      expect(call.args[0]).to.equal(err);
    });

    after(() => {
      sinon.restore();
      cleanStorage(storage);
    });
  });

  describe('GridStore close error', () => {
    let emitterStub;
    const errorSpy = sinon.spy();
    const fileSpy = sinon.spy();
    const err = new Error();

    before((done) => {
      emitterStub = sinon.stub().callsFake((evt, cb) => {
        if (evt === 'end') {
          cb();
        }
      });
      sinon.stub(mongo, 'GridStore').returns({
        stream: sinon.stub().returns({
          on: emitterStub,
          gs: {
            open: sinon.stub().callsFake(cb => cb()),
            close: sinon.stub().callsFake(cb => cb(err)),
          },
        }),
      });
      storage = GridFsStorage({url: setting.mongoUrl});
      storage._legacy = true;
      storage.on('streamError', errorSpy);
      storage.on('file', fileSpy);

      const upload = multer({storage});

      app.post('/storeclose', upload.single('photo'), (err, req, res, next) => { // eslint-disable-line no-unused-vars
        res.end();
      });

      request(app)
        .post('/storeclose')
        .attach('photo', files[0])
        .end(done);
    });

    it('should emit an error event when the store fails to open', () => {
      expect(errorSpy).to.be.calledOnce;
      expect(fileSpy).not.to.be.called;
      const call = errorSpy.getCall(0);
      expect(call.args[0]).to.equal(err);
    });

    after(() => {
      sinon.restore();
      cleanStorage(storage);
    });
  });

  describe('MongoClient and Db handling', () => {
    let client, db, server, mongoSpy;
    const {host, port, database} = setting.connection;

    beforeEach(() => {
      server = new mongo.Server(host, port);
      db = new mongo.Db(database, server);
      client = new MongoClient(server);
    });

    it('should handle the database instance returned in mongo 2', () => {
      mongoSpy = sinon.stub(MongoClient, 'connect').callsFake(function () {
        const callback = arguments.length > 2 ? arguments[2] : null;
        if (callback) {
          return callback(null, db);
        }
        return Promise.resolve(db);
      });
      storage = GridFsStorage({url: setting.mongoUrl});

      storage.on('connection', (db, client) => {
        expect(db).to.be.an.instanceOf(mongo.Db);
        expect(client).to.equal(null);
      });
    });

    it('should handle the client instance returned in mongo 3', () => {
      if (!client.db) {
        client.db = () => {
        };
      } else {
        sinon.stub(client, 'db').callsFake(() => db);
      }
      mongoSpy = sinon.stub(MongoClient, 'connect').callsFake(function () {
        const callback = arguments.length > 2 ? arguments[2] : null;
        if (callback) {
          return callback(null, client);
        }
        return Promise.resolve(client);
      });
      storage = GridFsStorage({url: setting.mongoUrl});

      storage.on('connection', (db, client) => {
        expect(mongoSpy).to.have.been.calledOnce;
        expect(db).to.be.an.instanceOf(mongo.Db);
        expect(client).to.be.an.instanceOf(MongoClient);
      });
    });

    describe('Legacy functions', () => {
      it('should use pseudoRandomBytes if node is in the 0.x range', (done) => {
        const isOldNode = sinon
          .stub(utils, 'isOldNode')
          .onFirstCall().callThrough()
          .onSecondCall().callsFake(() => true);

        const randomBytesSpy = sinon.stub(crypto, 'randomBytes').callThrough();
        const psRandomBytesSpy = sinon.stub(crypto, 'pseudoRandomBytes').callThrough();
        storage = new GridFsStorage({url: setting.mongoUrl});

        const upload = multer({storage});

        app.post('/rb', upload.array('photos'), (req, res) => {
          res.end();
        });

        storage.on('connection', () => {
          request(app)
            .post('/rb')
            .attach('photos', files[0])
            .attach('photos', files[1])
            .end(() => {
              expect(isOldNode).to.have.been.calledTwice;
              expect(randomBytesSpy).to.have.been.calledOnce;
              expect(psRandomBytesSpy).to.have.been.calledOnce;
              expect(randomBytesSpy).to.have.been.calledBefore(psRandomBytesSpy);
              done();
            });
        });
      });

      afterEach(() => cleanStorage(storage));
    });

    afterEach(() => sinon.restore());
  });
});
<commit_msg>Small fix<commit_after>'use strict';

const express = require('express');
const chai = require('chai');
const request = require('supertest');
const multer = require('multer');
const mongo = require('mongodb');
const md5File = require('md5-file');
const fs = require('fs');
const sinon = require('sinon');
const crypto = require('crypto');
const utils = require('../lib/utils');
const GridFsStorage = require('../index');
const setting = require('./utils/settings');
const testUtils = require('./utils/testutils');
const files = testUtils.files;
const cleanStorage = testUtils.cleanStorage;
const getDb = testUtils.getDb;
const getClient = testUtils.getClient;

const MongoClient = mongo.MongoClient;
const expect = chai.expect;

describe('Backwards compatibility', () => {
  let result, app, storage, size;

  before((done) => {
    app = express();
    fs.readFile(files[0], (err, f) => {
      size = f.length;
      done(err);
    });
  });

  describe('Using GridStore streams to save files', () => {
    before((done) => {
      storage = new GridFsStorage({url: setting.mongoUrl});
      storage._legacy = true;

      const upload = multer({storage});

      app.post('/store', upload.single('photos'), (req, res) => {
        result = {headers: req.headers, file: req.file, body: req.body};
        res.end();
      }, (err, req, res, next) => {
        console.log(err);
        next();
      });

      storage.on('connection', () => {
        request(app)
          .post('/store')
          .attach('photos', files[0])
          .end(done);
      });
    });

    it('should store the files on upload', () => {
      expect(result.file).to.be.a('object');
    });

    it('should have each stored file the same MD5 signature than the uploaded file', () => {
      expect(result.file.md5).to.be.equal(md5File(files[0]));
    });

    it('should have a filename property', () => {
      expect(result.file).to.have.a.property('filename');
      expect(result.file.filename).to.be.a('string');
      expect(result.file.filename).to.match(/^[0-9a-f]{32}$/);
    });

    it('should have a metadata property', () => {
      expect(result.file).to.have.a.property('metadata');
      expect(result.file.metadata).to.equal(null);
    });

    it('should have a id property', () => {
      expect(result.file).to.have.a.property('id');
      expect(result.file.id).to.match(/^[0-9a-f]{24}$/);
    });

    it('should have a size property with the length of the file', () => {
      expect(result.file).to.have.a.property('size');
      expect(result.file.size).to.equal(size);
    });

    it('should have the default bucket name pointing to the fs collection', () => {
      expect(result.file).to.have.a.property('bucketName');
      expect(result.file.bucketName).to.equal('fs');
    });

    it('should have the date of the upload', () => {
      expect(result.file).to.have.a.property('uploadDate');
    });

    after(() => cleanStorage(storage));

  });

  describe('Changing file configuration', () => {
    before((done) => {
      let counter = 0;
      const data = ['foo', 'bar'];
      const sizes = [102400, 204800];
      const names = ['plants', 'animals'];
      const contentTypes = ['text/plain', 'image/jpeg'];
      storage = new GridFsStorage({
        url: setting.mongoUrl,
        file: () => {
          counter++;
          return {
            filename: 'file' + counter,
            metadata: data[counter - 1],
            id: counter,
            chunkSize: sizes[counter - 1],
            bucketName: names[counter - 1],
            contentType: contentTypes[counter - 1],
          };
        },
      });
      // Set to use GridStore
      storage._legacy = true;

      const upload = multer({storage});

      app.post('/config', upload.array('photos', 2), (req, res) => {
        result = {headers: req.headers, files: req.files, body: req.body};
        res.end();
      });

      storage.on('connection', () => {
        request(app)
          .post('/config')
          .attach('photos', files[0])
          .attach('photos', files[1])
          .end(done);
      });
    });

    it('should the request contain the two uploaded files', () => {
      expect(result.files).to.be.an('array');
      expect(result.files).to.have.length(2);
    });

    it('should be named with the provided value', () => {
      expect(result.files[0].filename).to.equal('file1');
      expect(result.files[1].filename).to.equal('file2');
    });

    it('should contain a metadata object with the provided object', () => {
      expect(result.files[0].metadata).to.equal('foo');
      expect(result.files[1].metadata).to.equal('bar');
    });

    it('should be stored with the provided chunkSize value', () => {
      expect(result.files[0].chunkSize).to.equal(102400);
      expect(result.files[1].chunkSize).to.equal(204800);
    });

    it('should change the id with the provided value', () => {
      expect(result.files[0].id).to.equal(1);
      expect(result.files[1].id).to.equal(2);
    });

    it('should be stored under in a collection with the provided value', function (done) {
      const db = storage.db;
      db.collection('plants.files', {strict: true}, function (err) {
        expect(err).to.be.equal(null);
        db.collection('animals.files', {strict: true}, function (err) {
          expect(err).to.be.equal(null);
          done();
        });
      });
    });

    it('should change the content type with the provided value', () => {
      expect(result.files[0].contentType).to.equal('text/plain');
      expect(result.files[1].contentType).to.equal('image/jpeg');
    });

    after(() => cleanStorage(storage));

  });

  describe('Using GridStore to delete files', () => {
    let error, db, client;

    before(() => {
      return MongoClient.connect(setting.mongoUrl).then(_db => {
        db = getDb(_db);
        client = getClient(_db);
        storage = new GridFsStorage({db});
        storage._legacy = true;

        const upload = multer({storage});

        app.post('/delete', upload.array('photos', 1), (err, req, res, next) => {
          result = {headers: req.headers, body: req.body, files: req.files};
          error = err;
          next();
        });

        return new Promise((resolve) => {
          storage.on('connection', () => {
            request(app)
              .post('/delete')
              .attach('photos', files[0])
              .attach('photos', files[1])
              .end(() => resolve());
          });
        });
      });
    });

    it('should remove the files on upload', () => {
      expect(error.storageErrors).to.have.lengthOf(0);
    });

    it('should not have any files stored in the database', () => {
      mongo.GridStore.list(db).then((files) => {
        expect(files).to.have.lengthOf(0);
      });
    });

    after(() => cleanStorage(storage, db, client));
  });

  describe('GridStore open error', () => {
    const errorSpy = sinon.spy();
    const fileSpy = sinon.spy();
    const err = new Error();

    before((done) => {
      sinon.stub(mongo, 'GridStore').returns({
        stream: sinon.stub().returns({
          on: sinon.stub(),
          gs: {
            open: sinon.stub().callsFake(cb => cb(err)),
          },
        }),
      });
      storage = GridFsStorage({url: setting.mongoUrl});
      storage._legacy = true;
      storage.on('streamError', errorSpy);
      storage.on('file', fileSpy);

      const upload = multer({storage});

      app.post('/storeopen', upload.single('photo'), (err, req, res, next) => { // eslint-disable-line no-unused-vars
        res.end();
      });

      request(app)
        .post('/storeopen')
        .attach('photo', files[0])
        .end(done);
    });

    it('should emit an error event when the store fails to open', () => {
      expect(errorSpy).to.be.calledOnce;
      expect(fileSpy).not.to.be.called;
      const call = errorSpy.getCall(0);
      expect(call.args[0]).to.equal(err);
    });

    after(() => {
      sinon.restore();
      cleanStorage(storage);
    });
  });

  describe('GridStore close error', () => {
    let emitterStub;
    const errorSpy = sinon.spy();
    const fileSpy = sinon.spy();
    const err = new Error();

    before((done) => {
      emitterStub = sinon.stub().callsFake((evt, cb) => {
        if (evt === 'end') {
          cb();
        }
      });
      sinon.stub(mongo, 'GridStore').returns({
        stream: sinon.stub().returns({
          on: emitterStub,
          gs: {
            open: sinon.stub().callsFake(cb => cb()),
            close: sinon.stub().callsFake(cb => cb(err)),
          },
        }),
      });
      storage = GridFsStorage({url: setting.mongoUrl});
      storage._legacy = true;
      storage.on('streamError', errorSpy);
      storage.on('file', fileSpy);

      const upload = multer({storage});

      app.post('/storeclose', upload.single('photo'), (err, req, res, next) => { // eslint-disable-line no-unused-vars
        res.end();
      });

      request(app)
        .post('/storeclose')
        .attach('photo', files[0])
        .end(done);
    });

    it('should emit an error event when the store fails to open', () => {
      expect(errorSpy).to.be.calledOnce;
      expect(fileSpy).not.to.be.called;
      const call = errorSpy.getCall(0);
      expect(call.args[0]).to.equal(err);
    });

    after(() => {
      sinon.restore();
      cleanStorage(storage);
    });
  });

  describe('MongoClient and Db handling', () => {
    let client, db, server, mongoSpy;
    const host = setting.connection.host;
    const port = setting.connection.port;
    const database = setting.connection.database;

    beforeEach(() => {
      server = new mongo.Server(host, port);
      db = new mongo.Db(database, server);
      client = new MongoClient(server);
    });

    it('should handle the database instance returned in mongo 2', () => {
      mongoSpy = sinon.stub(MongoClient, 'connect').callsFake(function () {
        const callback = arguments.length > 2 ? arguments[2] : null;
        if (callback) {
          return callback(null, db);
        }
        return Promise.resolve(db);
      });
      storage = GridFsStorage({url: setting.mongoUrl});

      storage.on('connection', (db, client) => {
        expect(db).to.be.an.instanceOf(mongo.Db);
        expect(client).to.equal(null);
      });
    });

    it('should handle the client instance returned in mongo 3', () => {
      if (!client.db) {
        client.db = () => {
        };
      } else {
        sinon.stub(client, 'db').callsFake(() => db);
      }
      mongoSpy = sinon.stub(MongoClient, 'connect').callsFake(function () {
        const callback = arguments.length > 2 ? arguments[2] : null;
        if (callback) {
          return callback(null, client);
        }
        return Promise.resolve(client);
      });
      storage = GridFsStorage({url: setting.mongoUrl});

      storage.on('connection', (db, client) => {
        expect(mongoSpy).to.have.been.calledOnce;
        expect(db).to.be.an.instanceOf(mongo.Db);
        expect(client).to.be.an.instanceOf(MongoClient);
      });
    });

    describe('Legacy functions', () => {
      it('should use pseudoRandomBytes if node is in the 0.x range', (done) => {
        const isOldNode = sinon
          .stub(utils, 'isOldNode')
          .onFirstCall().callThrough()
          .onSecondCall().callsFake(() => true);

        const randomBytesSpy = sinon.stub(crypto, 'randomBytes').callThrough();
        const psRandomBytesSpy = sinon.stub(crypto, 'pseudoRandomBytes').callThrough();
        storage = new GridFsStorage({url: setting.mongoUrl});

        const upload = multer({storage});

        app.post('/rb', upload.array('photos'), (req, res) => {
          res.end();
        });

        storage.on('connection', () => {
          request(app)
            .post('/rb')
            .attach('photos', files[0])
            .attach('photos', files[1])
            .end(() => {
              expect(isOldNode).to.have.been.calledTwice;
              expect(randomBytesSpy).to.have.been.calledOnce;
              expect(psRandomBytesSpy).to.have.been.calledOnce;
              expect(randomBytesSpy).to.have.been.calledBefore(psRandomBytesSpy);
              done();
            });
        });
      });

      afterEach(() => cleanStorage(storage));
    });

    afterEach(() => sinon.restore());
  });
});
<|endoftext|>


<commit_before><commit_msg>added more nci examples, mostly metal coordinations<commit_after><|endoftext|>


<commit_before>/* **************************************************************

   Copyright 2011 Zoovy, Inc.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

************************************************************** */

var admin_batchJob = function() {
	var theseTemplates = new Array('batchJobStatusTemplate','batchJobManagerPageTemplate','batchJobRowTemplate');
	var r = {

////////////////////////////////////   CALLBACKS    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

	callbacks : {
//executed when extension is loaded. should include any validation that needs to occur.
		init : {
			onSuccess : function()	{
				var r = true; //return false if extension won't load for some reason (account config, dependencies, etc).

				app.model.fetchNLoadTemplates(app.vars.baseURL+'extensions/admin/batchjob.html',theseTemplates);

				return r;
				},
			onError : function()	{
//errors will get reported for this callback as part of the extensions loading.  This is here for extra error handling purposes.
//you may or may not need it.
				app.u.dump('BEGIN admin_orders.callbacks.init.onError');
				}
			},

		showBatchJobStatus : {
			onSuccess : function(tagObj){
				if(tagObj.parentID)	{
					$(app.u.jqSelector('#',tagObj.parentID)).hideLoading();
					}
				else if(tagObj.jqObj && typeof tagObj.jqObj === 'object')	{
					tagObj.jqObj.hideLoading();
					}
				else	{} //nothing to hideloading on.
//error handling for no jobid is handled inside this function.
				app.ext.admin_batchJob.a.showBatchJobStatus(app.data[tagObj.datapointer].jobid);
				},
			onError : function(responseData)	{
				var $target = $(app.u.jqSelector('#',responseData._rtag.parentID));
				$target.hideLoading();
				$target.anymessage({'message':responseData,'persistent':true});
				}
			},

		}, //callbacks

////////////////////////////////////   ACTION    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

		a : {

			showBatchJobManager : function($tabContent){
				$tabContent.empty();
//generate some of the task list content right away so the user knows something is happening.
				$tabContent.showLoading({'message':'Fetching list of batch jobs'});
				app.ext.admin.calls.adminBatchJobList.init('',{'callback':function(rd){
					$tabContent.hideLoading();
					if(app.model.responseHasErrors(rd)){
						$tabContent.anymessage({'message':rd});
						}
					else	{
						$tabContent.anycontent({'templateID':'batchJobManagerPageTemplate','dataAttribs':{'id':'batchJobManagerContent'},'datapointer':rd.datapointer});
						$(".gridTable",$tabContent).anytable({'inverse':true}); //inverse will sort high to low.
						$(".gridTable",$tabContent).find('th').first().trigger('click'); //sort by batch job.
						app.u.handleAppEvents($tabContent);
						}
					}},'mutable');
				app.model.dispatchThis('mutable');
				},

//called by brian in the legacy UI. creates a batch job and then opens the job status.
			adminBatchJobCreate : function(opts){
				$(app.u.jqSelector('#',app.ext.admin.vars.tab+"Content")).showLoading({'message':'Registering Batch Job'});
//parentID is specified for error handling purposes. That's where error messages should go and also what the hideLoading() selector should be.
				app.ext.admin.calls.adminBatchJobCreate.init(opts,{'callback':'showBatchJobStatus','extension':'admin_batchJob','parentID':app.ext.admin.vars.tab+"Content"},'immutable');
				app.model.dispatchThis('immutable');
				},

			showBatchJobStatus : function(jobid,opts) {
				if(jobid)	{
					//get job details.
					var $target = $('#batchJobStatusModal'); //modal id.
					if($target.length)	{$target.empty()}
					else	{
						$target = $("
").attr({'id':'batchJobStatusModal','title':'Batch Job Status: '+jobid}).appendTo('body');
						$target.dialog({'modal':true,'width':500,'height':300,'autoOpen':false});
						}
					$target.dialog('open');
					$target.showLoading({'message':'Fetching Batch Job Details'});
					app.ext.admin.calls.adminBatchJobStatus.init(jobid,{'callback':'anycontent','jqObj':$target,'templateID':'batchJobStatusTemplate','dataAttribs': {'jobid':jobid}},'immutable');
					app.model.dispatchThis('immutable');
					}
				else	{
					app.u.throwMessage("No jobid specified in admin_batchJob.a.showBatchJobStatus");
					}
				}, //showTaskManager

			showReport : function($target,vars)	{
//				app.u.dump("BEGIN admin_batchjob.a.showReport");
				if($target && vars && vars.guid)	{
					$target.empty();
					$target.showLoading({'message':'Generating Report'}); //run after the empty or the loading gfx gets removed.
//					app.u.dump(" -> $target and vars.guid are set.");
					app.ext.admin.calls.adminReportDownload.init(vars.guid,{'callback':function(rd)	{
						if(app.model.responseHasErrors(rd)){
							$target.hideLoading();
							$target.anymessage({'message':rd});
							}
						else	{
							var L = app.data[rd.datapointer]['@HEAD'].length,
							reportElementID = 'batchReport_'+vars.guid
							tableHeads = new Array();

							if(app.data[rd.datapointer]['@BODY'] && app.data[rd.datapointer]['@BODY'].length)	{
//google visualization will error badly if the # of columns in the each body row doesn't match the # of columns in the head.
//								app.u.dump(" -> app.data[rd.datapointer]['@BODY'][0].length: "+app.data[rd.datapointer]['@BODY'][0].length);
//								app.u.dump(" -> app.data[rd.datapointer]['@HEAD'][0].length: "+app.data[rd.datapointer]['@HEAD'][0].length);
								if(app.data[rd.datapointer]['@BODY'][0].length == app.data[rd.datapointer]['@HEAD'].length)	{
//@HEAD is returned with each item as an object. google visualization wants a simple array. this handles the conversion.
									for(var i = 0; i < L; i += 1)	{
										tableHeads.push(app.data[rd.datapointer]['@HEAD'][i].name);
										}

									var $expBtn = $("<button \/>").text('Export to CSV').button().addClass('floatRight').on('click',function(){
//										$('.google-visualization-table-table').toCSV();  //exports just the page in focus.
										var L = app.data[rd.datapointer]['@BODY'].length;
										var csv = "";
										for(var i = 0; i < L; i += 1)	{
											csv += $.map(app.data[rd.datapointer]['@BODY'][i],function(val){
//												return '"'+((val == null) ? '' : escape(val))+'"';
												return '"'+((val == null) ? '' : val.replace(/"/g,'/"'))+'"'; //don't return 'null' into report.
												})+"\n"
											}

										app.ext.admin.u.fileDownloadInModal({
											'skipDecode':true,
											'filename':app.data[rd.datapointer].title+'.csv',
											'mime_type':'text/csv',
											'body':csv});
										}).appendTo($target);

									$target.append("
"+app.data[rd.datapointer].title || ""+"<\/h1>");
									$target.append("
"+app.data[rd.datapointer].subtitle || ""+"<\/h2>");
									$target.append($("
",{'id':reportElementID+"_toolbar"})); //add element to dom for visualization toolbar
									$target.append($("
",{'id':reportElementID}).addClass('smallTxt')); //add element to dom for visualization table

									app.ext.admin_reports.u.drawTable(reportElementID,tableHeads,app.data[rd.datapointer]['@BODY']);
									app.ext.admin_reports.u.drawToolbar(reportElementID+"_toolbar");

									}
								else	{
									var errorDetails = "";
									for(index in vars)	{
										errorDetails += "
"+index+": "+vars[index];
										}
									$target.anymessage({'message':'The number of columns in the data do not match the number of columns in the head. This is likely due to an old report being opened in the new report interface. Please re-run the report and open the new copy.  If the error persist, please report to support with the following error details:'+errorDetails,'persistent':true});
									}

								}
							else	{
								$target.anymessage({'message':'There are no rows/data in this report.','persistent':true});
								}

							$target.hideLoading(); //this is after drawTable, which may take a moment.
							}
						}},'mutable'); app.model.dispatchThis('mutable');
					app.model.dispatchThis('mutable');
					}
				else	{
					$('#globalMessaging').anymessage({'message':'In admin_batchjob.a.showReport, either $target ['+typeof $target+'] or batchGUID ['+vars.guid+'] not set.','gMessage':true});
					}

				}

			}, //Actions

////////////////////////////////////   RENDERFORMATS    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

		renderFormats : {
			}, //renderFormats

////////////////////////////////////   UTIL [u]   \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

		u : {

			}, //u
		e : {

//NOTE -> the batch_exec will = REPORT for reports.
			showReport : function($btn)	{
				if($btn.closest('tr').data('batch_exec') == 'REPORT' && $btn.closest('tr').data('status').indexOf('END') >= 0)	{
					$btn.button({text: false,icons: {primary: "ui-icon-image"}}).show();
					$btn.off('click.showReport').on('click.showReport',function(event){
						event.preventDefault();
						var $table = $btn.closest('table');

						$table.stickytab({'tabtext':'batch jobs','tabID':'batchJobsStickyTab'});
						$('button',$table).removeClass('ui-state-focus'); //removes class added by jqueryui onclick.
						$('button',$table).removeClass('ui-state-highlight');
						$btn.addClass('ui-state-highlight');
						app.ext.admin_batchJob.a.showReport($(app.u.jqSelector('#',app.ext.admin.vars.tab+"Content")),$btn.closest('tr').data());
//make sure buttons and links in the stickytab content area close the sticktab on click. good usability.
						$('button, a',$table).each(function(){
							$(this).off('close.stickytab').on('click.closeStickytab',function(){
								$table.stickytab('close');
								})
							})
						});
					}
				else	{
//btn hidden by default. no action needed.
					}
				},

			execBatchCleanup : function($btn)	{
				var $row = $btn.closest('tr');
				if($row.data('status') == 'ERROR' || $row.data('status') == 'finished' || $row.data('status') == 'abort')	{
					$btn.show({text: false,icons: {primary: "ui-icon-trash"}});
					$btn.button(); //daisy-chaining the button on the show didn't work. button didn't get classes.
					$btn.off('click.execBatchCleanup').on('click.execBatchCleanup',function(){
						var jobid = $btn.closest('[data-jobid]').data('jobid');
						if(jobid)	{
							$('#batchJobStatusModal').empty().addClass('loadingBG');
							app.ext.admin.calls.adminBatchJobCleanup.init(jobid,{'callback':'showMessaging','message':'Batch job has been cleaned up','parentID':'batchJobStatus_'+jobid},'immutable');
							app.model.dispatchThis('immutable');
							}
						else	{
							$('.appMessaging').anymessage({'message':'In admin_batchJob.e.execBatchCleanup, unable to ascertain jobID from DOM tree.','gMessage':true});
							}
						});
					}
				else	{
					$btn.hide();
					}
				},

			showBatchDetail : function($btn)	{
				$btn.button({text: false,icons: {primary: "ui-icon-info"}});
				$btn.off('click.showBatchDetail').on('click.showBatchDetail',function(event){
					event.preventDefault();
					app.ext.admin_batchJob.a.showBatchJobStatus($btn.closest("tr").data('id'));
					});
				}

			} //e

		} //r object.
	return r;
	}<commit_msg>admin reports - bug fix<commit_after>/* **************************************************************

   Copyright 2011 Zoovy, Inc.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

************************************************************** */

var admin_batchJob = function() {
	var theseTemplates = new Array('batchJobStatusTemplate','batchJobManagerPageTemplate','batchJobRowTemplate');
	var r = {

////////////////////////////////////   CALLBACKS    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

	callbacks : {
//executed when extension is loaded. should include any validation that needs to occur.
		init : {
			onSuccess : function()	{
				var r = true; //return false if extension won't load for some reason (account config, dependencies, etc).

				app.model.fetchNLoadTemplates(app.vars.baseURL+'extensions/admin/batchjob.html',theseTemplates);

				return r;
				},
			onError : function()	{
//errors will get reported for this callback as part of the extensions loading.  This is here for extra error handling purposes.
//you may or may not need it.
				app.u.dump('BEGIN admin_orders.callbacks.init.onError');
				}
			},

		showBatchJobStatus : {
			onSuccess : function(tagObj){
				if(tagObj.parentID)	{
					$(app.u.jqSelector('#',tagObj.parentID)).hideLoading();
					}
				else if(tagObj.jqObj && typeof tagObj.jqObj === 'object')	{
					tagObj.jqObj.hideLoading();
					}
				else	{} //nothing to hideloading on.
//error handling for no jobid is handled inside this function.
				app.ext.admin_batchJob.a.showBatchJobStatus(app.data[tagObj.datapointer].jobid);
				},
			onError : function(responseData)	{
				var $target = $(app.u.jqSelector('#',responseData._rtag.parentID));
				$target.hideLoading();
				$target.anymessage({'message':responseData,'persistent':true});
				}
			},

		}, //callbacks

////////////////////////////////////   ACTION    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

		a : {

			showBatchJobManager : function($tabContent){
				$tabContent.empty();
//generate some of the task list content right away so the user knows something is happening.
				$tabContent.showLoading({'message':'Fetching list of batch jobs'});
				app.ext.admin.calls.adminBatchJobList.init('',{'callback':function(rd){
					$tabContent.hideLoading();
					if(app.model.responseHasErrors(rd)){
						$tabContent.anymessage({'message':rd});
						}
					else	{
						$tabContent.anycontent({'templateID':'batchJobManagerPageTemplate','dataAttribs':{'id':'batchJobManagerContent'},'datapointer':rd.datapointer});
						$(".gridTable",$tabContent).anytable({'inverse':true}); //inverse will sort high to low.
						$(".gridTable",$tabContent).find('th').first().trigger('click'); //sort by batch job.
						app.u.handleAppEvents($tabContent);
						}
					}},'mutable');
				app.model.dispatchThis('mutable');
				},

//called by brian in the legacy UI. creates a batch job and then opens the job status.
			adminBatchJobCreate : function(opts){
				$(app.u.jqSelector('#',app.ext.admin.vars.tab+"Content")).showLoading({'message':'Registering Batch Job'});
//parentID is specified for error handling purposes. That's where error messages should go and also what the hideLoading() selector should be.
				app.ext.admin.calls.adminBatchJobCreate.init(opts,{'callback':'showBatchJobStatus','extension':'admin_batchJob','parentID':app.ext.admin.vars.tab+"Content"},'immutable');
				app.model.dispatchThis('immutable');
				},

			showBatchJobStatus : function(jobid,opts) {
				if(jobid)	{
					//get job details.
					var $target = $('#batchJobStatusModal'); //modal id.
					if($target.length)	{$target.empty()}
					else	{
						$target = $("
").attr({'id':'batchJobStatusModal','title':'Batch Job Status: '+jobid}).appendTo('body');
						$target.dialog({'modal':true,'width':500,'height':300,'autoOpen':false});
						}
					$target.dialog('open');
					$target.showLoading({'message':'Fetching Batch Job Details'});
					app.ext.admin.calls.adminBatchJobStatus.init(jobid,{'callback':'anycontent','jqObj':$target,'templateID':'batchJobStatusTemplate','dataAttribs': {'jobid':jobid}},'immutable');
					app.model.dispatchThis('immutable');
					}
				else	{
					app.u.throwMessage("No jobid specified in admin_batchJob.a.showBatchJobStatus");
					}
				}, //showTaskManager

			showReport : function($target,vars)	{
//				app.u.dump("BEGIN admin_batchjob.a.showReport");
				if($target && vars && vars.guid)	{
					$target.empty();
					$target.showLoading({'message':'Generating Report'}); //run after the empty or the loading gfx gets removed.
//					app.u.dump(" -> $target and vars.guid are set.");
					app.ext.admin.calls.adminReportDownload.init(vars.guid,{'callback':function(rd)	{
						if(app.model.responseHasErrors(rd)){
							$target.hideLoading();
							$target.anymessage({'message':rd});
							}
						else	{
							var L = app.data[rd.datapointer]['@HEAD'].length,
							reportElementID = 'batchReport_'+vars.guid
							tableHeads = new Array();

							if(app.data[rd.datapointer]['@BODY'] && app.data[rd.datapointer]['@BODY'].length)	{
//google visualization will error badly if the # of columns in the each body row doesn't match the # of columns in the head.
//								app.u.dump(" -> app.data[rd.datapointer]['@BODY'][0].length: "+app.data[rd.datapointer]['@BODY'][0].length);
//								app.u.dump(" -> app.data[rd.datapointer]['@HEAD'][0].length: "+app.data[rd.datapointer]['@HEAD'][0].length);
								if(app.data[rd.datapointer]['@BODY'][0].length == app.data[rd.datapointer]['@HEAD'].length)	{
//@HEAD is returned with each item as an object. google visualization wants a simple array. this handles the conversion.
									for(var i = 0; i < L; i += 1)	{
										tableHeads.push(app.data[rd.datapointer]['@HEAD'][i].name);
										}

									var $expBtn = $("<button \/>").text('Export to CSV').button().addClass('floatRight').on('click',function(){
//										$('.google-visualization-table-table').toCSV();  //exports just the page in focus.
										var L = app.data[rd.datapointer]['@BODY'].length;
										var csv = "";
										for(var i = 0; i < L; i += 1)	{
											csv += $.map(app.data[rd.datapointer]['@BODY'][i],function(val){
//												return '"'+((val == null) ? '' : escape(val))+'"';
												return '"'+((val == null) ? '' : val.replace(/"/g,'\"'))+'"'; //don't return 'null' into report.
												})+"\n"
											}

										app.ext.admin.u.fileDownloadInModal({
											'skipDecode':true,
											'filename':app.data[rd.datapointer].title+'.csv',
											'mime_type':'text/csv',
											'body':csv});
										}).appendTo($target);

									$target.append("
"+app.data[rd.datapointer].title || ""+"<\/h1>");
									$target.append("
"+app.data[rd.datapointer].subtitle || ""+"<\/h2>");
									$target.append($("
",{'id':reportElementID+"_toolbar"})); //add element to dom for visualization toolbar
									$target.append($("
",{'id':reportElementID}).addClass('smallTxt')); //add element to dom for visualization table

									app.ext.admin_reports.u.drawTable(reportElementID,tableHeads,app.data[rd.datapointer]['@BODY']);
									app.ext.admin_reports.u.drawToolbar(reportElementID+"_toolbar");

									}
								else	{
									var errorDetails = "";
									for(index in vars)	{
										errorDetails += "
"+index+": "+vars[index];
										}
									$target.anymessage({'message':'The number of columns in the data do not match the number of columns in the head. This is likely due to an old report being opened in the new report interface. Please re-run the report and open the new copy.  If the error persist, please report to support with the following error details:'+errorDetails,'persistent':true});
									}

								}
							else	{
								$target.anymessage({'message':'There are no rows/data in this report.','persistent':true});
								}

							$target.hideLoading(); //this is after drawTable, which may take a moment.
							}
						}},'mutable'); app.model.dispatchThis('mutable');
					app.model.dispatchThis('mutable');
					}
				else	{
					$('#globalMessaging').anymessage({'message':'In admin_batchjob.a.showReport, either $target ['+typeof $target+'] or batchGUID ['+vars.guid+'] not set.','gMessage':true});
					}

				}

			}, //Actions

////////////////////////////////////   RENDERFORMATS    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

		renderFormats : {
			}, //renderFormats

////////////////////////////////////   UTIL [u]   \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

		u : {

			}, //u
		e : {

//NOTE -> the batch_exec will = REPORT for reports.
			showReport : function($btn)	{
				if($btn.closest('tr').data('batch_exec') == 'REPORT' && $btn.closest('tr').data('status').indexOf('END') >= 0)	{
					$btn.button({text: false,icons: {primary: "ui-icon-image"}}).show();
					$btn.off('click.showReport').on('click.showReport',function(event){
						event.preventDefault();
						var $table = $btn.closest('table');

						$table.stickytab({'tabtext':'batch jobs','tabID':'batchJobsStickyTab'});
						$('button',$table).removeClass('ui-state-focus'); //removes class added by jqueryui onclick.
						$('button',$table).removeClass('ui-state-highlight');
						$btn.addClass('ui-state-highlight');
						app.ext.admin_batchJob.a.showReport($(app.u.jqSelector('#',app.ext.admin.vars.tab+"Content")),$btn.closest('tr').data());
//make sure buttons and links in the stickytab content area close the sticktab on click. good usability.
						$('button, a',$table).each(function(){
							$(this).off('close.stickytab').on('click.closeStickytab',function(){
								$table.stickytab('close');
								})
							})
						});
					}
				else	{
//btn hidden by default. no action needed.
					}
				},

			execBatchCleanup : function($btn)	{
				var $row = $btn.closest('tr');
				if($row.data('status') == 'ERROR' || $row.data('status') == 'finished' || $row.data('status') == 'abort')	{
					$btn.show({text: false,icons: {primary: "ui-icon-trash"}});
					$btn.button(); //daisy-chaining the button on the show didn't work. button didn't get classes.
					$btn.off('click.execBatchCleanup').on('click.execBatchCleanup',function(){
						var jobid = $btn.closest('[data-jobid]').data('jobid');
						if(jobid)	{
							$('#batchJobStatusModal').empty().addClass('loadingBG');
							app.ext.admin.calls.adminBatchJobCleanup.init(jobid,{'callback':'showMessaging','message':'Batch job has been cleaned up','parentID':'batchJobStatus_'+jobid},'immutable');
							app.model.dispatchThis('immutable');
							}
						else	{
							$('.appMessaging').anymessage({'message':'In admin_batchJob.e.execBatchCleanup, unable to ascertain jobID from DOM tree.','gMessage':true});
							}
						});
					}
				else	{
					$btn.hide();
					}
				},

			showBatchDetail : function($btn)	{
				$btn.button({text: false,icons: {primary: "ui-icon-info"}});
				$btn.off('click.showBatchDetail').on('click.showBatchDetail',function(event){
					event.preventDefault();
					app.ext.admin_batchJob.a.showBatchJobStatus($btn.closest("tr").data('id'));
					});
				}

			} //e

		} //r object.
	return r;
	}<|endoftext|>


<commit_before>var CoolToJSDemo;
(function (CoolToJSDemo) {
    'use strict';

    //#region set up editors and the console
    var coolEditor = CodeMirror(document.getElementById('cool-editor'), {
        value: CoolToJS.CoolProgramSources.HelloWorld,
        mode: 'cool',
        lineNumbers: true,
        indentUnit: 4,
    });

    var generatedJavaScriptEditor = CodeMirror(document.getElementById('generated-javascript'), {
        mode: 'javascript',
        lineNumbers: true,
        indentUnit: 4,
        readOnly: true
    });

    // global so we can access it inside a Cool program
    window.consoleController = $('.console').console({
        promptLabel: '> ',
        commandValidate: function (line) {
            return !(line === '');
        },
        commandHandle: function (line) {
            if (isInputRestrictedToNumbers && !/^\d*$/.test(line)) {
                return [{
                    msg: 'Invalid input: Please enter an integer',
                    className: "jquery-console-error"
                }];
            } else {
                if (inputFunction) {
                    inputFunction(line);
                    inputFunction = null;
                }

                return [{
                    msg: line,
                    className: "jquery-console-user-input"
                }];
            }
        },
        autofocus: false,
        animateScroll: true,
        promptHistory: true,
        charInsertTrigger: function (keycode, line) {
            // no restrictions on what can be entered into the console
            return true;
        }
    });
    //#endregion

    //#region IO methods
    var out_string = function (output) {
        window.consoleController.report([{
            msg: output,
            className: "jquery-console-output"
        }]);
    };

    var out_int = out_string;

    var isInputRestrictedToNumbers = false;

    var in_string = function(onInput) {
        isInputRestrictedToNumbers = false;
        inputFunction = onInput;
    };

    var in_int = function (onInput) {
        isInputRestrictedToNumbers = true;
        inputFunction = onInput;
    };

    // the function that will actually be called when input is entered
    var inputFunction = null;

    //#endregion

    document.getElementById('transpile-button').onclick = function () {

        var transpilerOutput = CoolToJS.Transpile({
            coolProgramSources: coolEditor.getValue(),
            out_string: out_string,
            out_int: out_int,
            in_string: in_string,
            in_int: in_int
        });

        if (transpilerOutput.success) {
            generatedJavaScriptEditor.setValue(transpilerOutput.generatedJavaScript);
        } else {
            if (transpilerOutput.errorMessages) {
                var errorComments = '/*\n\nThe following errors occured while transpiling:\n\n';
                for (var i = 0; i < transpilerOutput.errorMessages.length; i++) {
                    errorComments += '• ' + transpilerOutput.errorMessages[i] + '\n';
                }
                errorComments += '\n*/';
            } else {
                var errorComments = '/* An unknown error occured while transpiling */';
            }

            generatedJavaScriptEditor.setValue(errorComments);
        }
    };

    document.getElementById('play-button').onclick = function () {
        try {
            eval(generatedJavaScriptEditor.getValue());
        } catch (data) {
            window.consoleController.report([{
                msg: data,
                className: "jquery-console-error"
            }]);
            throw data;
        }
    };

})(CoolToJSDemo || (CoolToJSDemo = {}));<commit_msg>only reprint input if we're using it<commit_after>var CoolToJSDemo;
(function (CoolToJSDemo) {
    'use strict';

    //#region set up editors and the console
    var coolEditor = CodeMirror(document.getElementById('cool-editor'), {
        value: CoolToJS.CoolProgramSources.HelloWorld,
        mode: 'cool',
        lineNumbers: true,
        indentUnit: 4,
    });

    var generatedJavaScriptEditor = CodeMirror(document.getElementById('generated-javascript'), {
        mode: 'javascript',
        lineNumbers: true,
        indentUnit: 4,
        readOnly: true
    });

    // global so we can access it inside a Cool program
    window.consoleController = $('.console').console({
        promptLabel: '> ',
        commandValidate: function (line) {
            return !(line === '');
        },
        commandHandle: function (line) {
            if (isInputRestrictedToNumbers && !/^\d*$/.test(line)) {
                return [{
                    msg: 'Invalid input: Please enter an integer',
                    className: "jquery-console-error"
                }];
            } else {
                if (inputFunction) {
                    inputFunction(line);
                    inputFunction = null;

                    return [{
                        msg: line,
                        className: "jquery-console-user-input"
                    }];
                }

                return [{
                    msg: '',
                    className: "jquery-console-user-input"
                }];
            }
        },
        autofocus: false,
        animateScroll: true,
        promptHistory: true,
        charInsertTrigger: function (keycode, line) {
            // no restrictions on what can be entered into the console
            return true;
        }
    });
    //#endregion

    //#region IO methods
    var out_string = function (output) {
        window.consoleController.report([{
            msg: output,
            className: "jquery-console-output"
        }]);
    };

    var out_int = out_string;

    var isInputRestrictedToNumbers = false;

    var in_string = function(onInput) {
        isInputRestrictedToNumbers = false;
        inputFunction = onInput;
    };

    var in_int = function (onInput) {
        isInputRestrictedToNumbers = true;
        inputFunction = onInput;
    };

    // the function that will actually be called when input is entered
    var inputFunction = null;

    //#endregion

    document.getElementById('transpile-button').onclick = function () {

        var transpilerOutput = CoolToJS.Transpile({
            coolProgramSources: coolEditor.getValue(),
            out_string: out_string,
            out_int: out_int,
            in_string: in_string,
            in_int: in_int
        });

        if (transpilerOutput.success) {
            generatedJavaScriptEditor.setValue(transpilerOutput.generatedJavaScript);
        } else {
            if (transpilerOutput.errorMessages) {
                var errorComments = '/*\n\nThe following errors occured while transpiling:\n\n';
                for (var i = 0; i < transpilerOutput.errorMessages.length; i++) {
                    errorComments += '• ' + transpilerOutput.errorMessages[i] + '\n';
                }
                errorComments += '\n*/';
            } else {
                var errorComments = '/* An unknown error occured while transpiling */';
            }

            generatedJavaScriptEditor.setValue(errorComments);
        }
    };

    document.getElementById('play-button').onclick = function () {
        try {
            eval(generatedJavaScriptEditor.getValue());
        } catch (data) {
            window.consoleController.report([{
                msg: data,
                className: "jquery-console-error"
            }]);
            throw data;
        }
    };

})(CoolToJSDemo || (CoolToJSDemo = {}));<|endoftext|>